@mmerterden/multi-agent-pipeline 20.16.0 → 20.17.0
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 +30 -0
- package/README.md +6 -5
- package/README.tr.md +6 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +14 -12
- package/docs/features.md +9 -1
- package/install/catalog-history.json +1 -1
- package/install/claude.mjs +41 -6
- package/install/codex.mjs +3 -3
- package/manifest.json +62 -49
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/bug-bash/SKILL.md +154 -0
- package/pipeline/commands/multi-agent/review/SKILL.md +13 -9
- package/pipeline/commands/multi-agent/setup/SKILL.md +8 -6
- package/pipeline/commands/multi-agent/sync/SKILL.md +4 -3
- package/pipeline/commands/multi-agent/update/SKILL.md +5 -1
- package/pipeline/commands/sim-test.md +71 -6
- package/pipeline/lib/plugin-skill-roots.mjs +80 -0
- package/pipeline/lib/usage-lifecycle.mjs +83 -0
- package/pipeline/multi-agent-refs/cross-cli-contract.md +20 -4
- package/pipeline/multi-agent-refs/features/assertion-judge.md +76 -0
- package/pipeline/multi-agent-refs/features/exploratory-testing.md +140 -0
- package/pipeline/multi-agent-refs/features/skill-conformance.md +1 -1
- package/pipeline/multi-agent-refs/features/usage-reporting.md +59 -5
- package/pipeline/multi-agent-refs/features/valven.md +30 -3
- package/pipeline/multi-agent-refs/help/en.md +2 -0
- package/pipeline/multi-agent-refs/help/tr.md +2 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +2 -2
- package/pipeline/schemas/bug-bash-verification.schema.json +41 -0
- package/pipeline/schemas/explore-run.schema.json +108 -0
- package/pipeline/schemas/prefs.schema.json +1 -1
- package/pipeline/scripts/_stack-routing.mjs +2 -0
- package/pipeline/scripts/bug-bash.mjs +379 -0
- package/pipeline/scripts/explore-findings.mjs +410 -0
- package/pipeline/scripts/gen-skills-index.mjs +1 -2
- package/pipeline/scripts/pr-timeline.mjs +135 -0
- package/pipeline/scripts/pre-commit-check.sh +4 -2
- package/pipeline/scripts/skill-conformance.mjs +17 -71
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +1 -1
- package/pipeline/scripts/uninstall.mjs +29 -8
- package/pipeline/scripts/usage-identity.mjs +36 -10
- package/pipeline/scripts/usage-register.mjs +95 -8
- package/pipeline/scripts/usage-report.mjs +52 -2
- package/pipeline/scripts/valven-gate.mjs +292 -11
- package/pipeline/skills/.skill-manifest.json +18 -10
- package/pipeline/skills/.skills-index.json +28 -6
- package/pipeline/skills/shared/README.md +6 -4
- package/pipeline/skills/shared/core/multi-agent-bug-bash/SKILL.md +150 -0
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +5 -1
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +4 -3
- package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +4 -1
- package/pipeline/skills/shared/external/e2e-testing/SKILL.md +124 -0
- package/pipeline/skills/shared/external/e2e-testing/references/document-fixtures.md +65 -0
- package/pipeline/skills/shared/external/valven-rules/SKILL.md +93 -0
- package/pipeline/skills/shared/{core → external}/valven-rules/references/rules.yml +52 -6
- package/pipeline/skills/skills-index.md +4 -2
- package/pipeline/skills/shared/core/valven-rules/SKILL.md +0 -62
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* valven-gate.mjs --mode dev|commit|review
|
|
9
9
|
* [--repo <dir>] [--base <ref>] [--head <ref> | --worktree] [--branch <name>]
|
|
10
10
|
* [--diff-file <patch>] [--commits-file <path>] [--pr-body-file <path>]
|
|
11
|
+
* [--pr-meta-file <json>]
|
|
11
12
|
* [--rules <rules.yml>] [--prefs <prefs.json>] [--now <ISO date>]
|
|
12
13
|
* [--out <file>]
|
|
13
14
|
*
|
|
@@ -20,6 +21,12 @@
|
|
|
20
21
|
* (one subject per line), --branch and --pr-body-file when known. The rework
|
|
21
22
|
* ratio needs history, so it is reported `not-measured`.
|
|
22
23
|
*
|
|
24
|
+
* --pr-meta-file adds the pull request's timing (`gh pr view --json
|
|
25
|
+
* author,closedAt,createdAt,isDraft,mergedAt,reviewRequests,reviews`, or the normalized
|
|
26
|
+
* shape normalizePrMeta() documents). Without it the pickup and review durations
|
|
27
|
+
* are `not-measured`; coding time is measured up to now in commit mode, where the
|
|
28
|
+
* pull request is about to open.
|
|
29
|
+
*
|
|
23
30
|
* A rule that cannot be measured is `not-measured` with the reason, never a pass.
|
|
24
31
|
* Generated output, lockfiles and snapshots are removed with the same pattern
|
|
25
32
|
* list the reviewers use (review-file-filter.mjs), so no budget counts them.
|
|
@@ -40,10 +47,12 @@
|
|
|
40
47
|
import { execFileSync } from "node:child_process";
|
|
41
48
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
42
49
|
import { join } from "node:path";
|
|
50
|
+
import { homedir } from "node:os";
|
|
43
51
|
import { loadPatterns, partition } from "./review-file-filter.mjs";
|
|
44
52
|
import { unquotePathIfNeeded } from "./git-path.mjs";
|
|
45
53
|
import { runMain } from "../lib/fatal.mjs";
|
|
46
54
|
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
55
|
+
import { pluginSkillRootCandidates } from "../lib/plugin-skill-roots.mjs";
|
|
47
56
|
import { messages, outputLanguage, prefsPath } from "../lib/user-facing.mjs";
|
|
48
57
|
|
|
49
58
|
export const CRITERIA_SOURCE = "valven-rules";
|
|
@@ -56,16 +65,29 @@ const SPLIT_RULES = new Set(["VLV-SIZE-01", "VLV-SIZE-02", "VLV-SCOPE-01"]);
|
|
|
56
65
|
const DEFAULT_PATTERNS = join(import.meta.dirname, "..", "schemas", "review-file-exclusions.json");
|
|
57
66
|
|
|
58
67
|
/**
|
|
59
|
-
* Candidate locations of rules.yml
|
|
60
|
-
*
|
|
61
|
-
*
|
|
68
|
+
* Candidate locations of rules.yml. The gate reads the registry that was
|
|
69
|
+
* installed with it, so its evaluators and its rules come from one release: on
|
|
70
|
+
* Claude Code `<root>/multi-agent-refs/registries/valven-rules` (the installer
|
|
71
|
+
* lays it down there because Claude Code loads the skill itself from the
|
|
72
|
+
* ai-common-toolkit plugin), on Copilot `<root>/skills`, on Codex
|
|
73
|
+
* `<root>/multi-agent-refs/skills`, and in a checkout the source. Searching the
|
|
74
|
+
* registries dir before `<root>/skills` also keeps a `~/.claude/skills/valven-rules`
|
|
75
|
+
* left by an older install from being read. The plugin copies come last, for a
|
|
76
|
+
* layout that has none of these.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} [dir] directory of this script
|
|
79
|
+
* @param {string} [home]
|
|
62
80
|
*/
|
|
63
|
-
export function rulesCandidates(dir = import.meta.dirname) {
|
|
81
|
+
export function rulesCandidates(dir = import.meta.dirname, home = homedir()) {
|
|
64
82
|
const rel = ["valven-rules", "references", "rules.yml"];
|
|
65
83
|
return [
|
|
84
|
+
join(dir, "..", "multi-agent-refs", "registries", ...rel),
|
|
66
85
|
join(dir, "..", "skills", ...rel),
|
|
67
86
|
join(dir, "..", "multi-agent-refs", "skills", ...rel),
|
|
68
|
-
join(dir, "..", "skills", "shared", "
|
|
87
|
+
join(dir, "..", "skills", "shared", "external", ...rel),
|
|
88
|
+
...pluginSkillRootCandidates(home, ["ai-common-toolkit"]).map((root) =>
|
|
89
|
+
join(root, "knowledge", ...rel),
|
|
90
|
+
),
|
|
69
91
|
];
|
|
70
92
|
}
|
|
71
93
|
|
|
@@ -246,6 +268,102 @@ export function anchorFile(human) {
|
|
|
246
268
|
return [...pool].sort((a, b) => b.added + b.deleted - (a.added + a.deleted))[0].path;
|
|
247
269
|
}
|
|
248
270
|
|
|
271
|
+
/**
|
|
272
|
+
* The Valven score band of a duration: 100 under `full_under_minutes`, 75 under
|
|
273
|
+
* `three_quarter_under_minutes`, 50 under `half_under_minutes`, otherwise 0.
|
|
274
|
+
* Each bound is exclusive, as in Valven's table ("under 30 min", "30 min - 2.5 hours").
|
|
275
|
+
*
|
|
276
|
+
* @param {number} minutes
|
|
277
|
+
* @param {{full_under_minutes:number, three_quarter_under_minutes:number, half_under_minutes:number}} rule
|
|
278
|
+
*/
|
|
279
|
+
export function bandOf(minutes, rule) {
|
|
280
|
+
if (minutes < rule.full_under_minutes) return 100;
|
|
281
|
+
if (minutes < rule.three_quarter_under_minutes) return 75;
|
|
282
|
+
if (minutes < rule.half_under_minutes) return 50;
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const isoOrNull = (v) => {
|
|
287
|
+
if (!v) return null;
|
|
288
|
+
const d = new Date(v);
|
|
289
|
+
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/** The earliest valid date that is not before `floor`, or null. */
|
|
293
|
+
const earliest = (dates, floor) => {
|
|
294
|
+
const valid = dates
|
|
295
|
+
.map(isoOrNull)
|
|
296
|
+
.filter((d) => d && d >= floor)
|
|
297
|
+
.sort();
|
|
298
|
+
return valid[0] ?? null;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Pull request timing in one shape. Accepts `gh pr view --json
|
|
303
|
+
* author,closedAt,createdAt,isDraft,mergedAt,reviewRequests,reviews` as is, or the
|
|
304
|
+
* normalized form a Bitbucket caller writes: `{createdAt, readyAt, isDraft,
|
|
305
|
+
* reviewerCount, firstReviewAt, approvedAt, mergedAt, closedAt}`. Reviews by the pull
|
|
306
|
+
* request's own author, bots and pending reviews never count, and a review left
|
|
307
|
+
* before the pull request was ready for review (or before it opened) is not the
|
|
308
|
+
* first review: pickup and review time start at ready. A reviewer count the input does not carry
|
|
309
|
+
* stays null, so it reads as unknown rather than as "no reviewer". Returns null
|
|
310
|
+
* without a valid `createdAt`, because every duration here is anchored to it.
|
|
311
|
+
*
|
|
312
|
+
* @param {object|null} raw
|
|
313
|
+
*/
|
|
314
|
+
export function normalizePrMeta(raw) {
|
|
315
|
+
if (!raw || typeof raw !== "object") return null;
|
|
316
|
+
const createdAt = isoOrNull(raw.createdAt);
|
|
317
|
+
if (!createdAt) return null;
|
|
318
|
+
const base = {
|
|
319
|
+
createdAt,
|
|
320
|
+
readyAt: isoOrNull(raw.readyAt),
|
|
321
|
+
isDraft: raw.isDraft === true,
|
|
322
|
+
mergedAt: isoOrNull(raw.mergedAt),
|
|
323
|
+
closedAt: isoOrNull(raw.closedAt),
|
|
324
|
+
};
|
|
325
|
+
const floor = base.readyAt ?? createdAt;
|
|
326
|
+
if (Array.isArray(raw.reviews) || Array.isArray(raw.reviewRequests)) {
|
|
327
|
+
const author = raw.author?.login ?? null;
|
|
328
|
+
const isBot = (who) => who?.is_bot === true || /\[bot\]$/.test(who?.login ?? "");
|
|
329
|
+
const reviews = (raw.reviews ?? []).filter(
|
|
330
|
+
(r) => r?.author?.login !== author && r?.state !== "PENDING" && !isBot(r?.author),
|
|
331
|
+
);
|
|
332
|
+
const people = new Set([
|
|
333
|
+
...(raw.reviewRequests ?? []).map((r) => r?.login ?? r?.name ?? r?.slug).filter(Boolean),
|
|
334
|
+
...reviews.map((r) => r.author?.login).filter(Boolean),
|
|
335
|
+
]);
|
|
336
|
+
return {
|
|
337
|
+
...base,
|
|
338
|
+
reviewerCount: people.size,
|
|
339
|
+
firstReviewAt: earliest(
|
|
340
|
+
reviews.map((r) => r.submittedAt),
|
|
341
|
+
floor,
|
|
342
|
+
),
|
|
343
|
+
approvedAt: earliest(
|
|
344
|
+
reviews.filter((r) => r.state === "APPROVED").map((r) => r.submittedAt),
|
|
345
|
+
floor,
|
|
346
|
+
),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
...base,
|
|
351
|
+
reviewerCount: Number.isFinite(raw.reviewerCount) ? raw.reviewerCount : null,
|
|
352
|
+
firstReviewAt: earliest([raw.firstReviewAt], floor),
|
|
353
|
+
approvedAt: earliest([raw.approvedAt], floor),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const minutesBetween = (from, to) =>
|
|
358
|
+
Math.max(0, Math.floor((new Date(to).getTime() - new Date(from).getTime()) / 60000));
|
|
359
|
+
|
|
360
|
+
function duration(minutes, lang) {
|
|
361
|
+
const tr = lang === "tr";
|
|
362
|
+
if (minutes < 60) return tr ? `${minutes} dakika` : `${minutes} min`;
|
|
363
|
+
const hours = Math.round((minutes / 60) * 10) / 10;
|
|
364
|
+
return tr ? `${String(hours).replace(".", ",")} saat` : `${hours} h`;
|
|
365
|
+
}
|
|
366
|
+
|
|
249
367
|
const TEXT = {
|
|
250
368
|
en: {
|
|
251
369
|
size: (n, max) =>
|
|
@@ -270,6 +388,20 @@ const TEXT = {
|
|
|
270
388
|
`The branch is ${n} commits behind its base and its merge-base is ${d} days old. Carry the change onto a fresh branch with valven-port.`,
|
|
271
389
|
foreign: (s, key) =>
|
|
272
390
|
`Commit "${s}" belongs to another issue (branch key ${key}). Move it to its own PR.`,
|
|
391
|
+
coding: (d, band) =>
|
|
392
|
+
`Coding took ${d} from the first commit to opening the pull request (Valven score ${band}%). Open the pull request as soon as the first slice works, and keep slices small.`,
|
|
393
|
+
pickup: (d, band, waiting) =>
|
|
394
|
+
waiting
|
|
395
|
+
? `The pull request has waited ${d} for its first review (Valven score ${band}%). Remind the reviewers or ask the team for one.`
|
|
396
|
+
: `The first review came ${d} after the pull request was ready (Valven score ${band}%).`,
|
|
397
|
+
review: (d, band, open) =>
|
|
398
|
+
open
|
|
399
|
+
? `The pull request has been in review for ${d} without an approval (Valven score ${band}%). Answer every comment in one round and push the fixes together.`
|
|
400
|
+
: `Review took ${d} from the first review to approval (Valven score ${band}%). Answer every comment in one round and push the fixes together.`,
|
|
401
|
+
noReviewer:
|
|
402
|
+
"The pull request has no reviewer, so nobody is asked to review it. Assign the default reviewers.",
|
|
403
|
+
draft: (d) =>
|
|
404
|
+
`The pull request has been a draft for ${d}. Mark it ready for review once its gates pass, or close it.`,
|
|
273
405
|
},
|
|
274
406
|
tr: {
|
|
275
407
|
size: (n, max) =>
|
|
@@ -294,6 +426,19 @@ const TEXT = {
|
|
|
294
426
|
`Branch base'in ${n} commit gerisinde ve merge-base'i ${d} günlük. Değişikliği valven-port ile güncel base'den açılan yeni bir branch'e taşı.`,
|
|
295
427
|
foreign: (s, key) =>
|
|
296
428
|
`"${s}" commit'i başka bir işe ait (branch ID'si ${key}). Onu ayrı bir PR'a taşı.`,
|
|
429
|
+
coding: (d, band) =>
|
|
430
|
+
`İlk commit'ten PR'ın açılmasına kadar ${d} geçmiş (Valven puanı %${band}). İlk dilim çalışır hale gelince PR'ı hemen aç, dilimleri küçük tut.`,
|
|
431
|
+
pickup: (d, band, waiting) =>
|
|
432
|
+
waiting
|
|
433
|
+
? `PR ${d} süredir ilk review'u bekliyor (Valven puanı %${band}). Reviewer'lara hatırlat ya da takımdan birini iste.`
|
|
434
|
+
: `İlk review, PR hazır olduktan ${d} sonra geldi (Valven puanı %${band}).`,
|
|
435
|
+
review: (d, band, open) =>
|
|
436
|
+
open
|
|
437
|
+
? `PR ${d} süredir review'da ve onay almadı (Valven puanı %${band}). Bütün yorumları tek turda yanıtla, düzeltmeleri birlikte push'la.`
|
|
438
|
+
: `İlk review'dan onaya kadar ${d} geçmiş (Valven puanı %${band}). Bütün yorumları tek turda yanıtla, düzeltmeleri birlikte push'la.`,
|
|
439
|
+
noReviewer: "PR'da reviewer yok, kimseden review istenmiyor. Varsayılan reviewer'ları ekle.",
|
|
440
|
+
draft: (d) =>
|
|
441
|
+
`PR ${d} süredir draft. Gate'leri geçtiyse review'a hazır olarak işaretle ya da kapat.`,
|
|
297
442
|
},
|
|
298
443
|
};
|
|
299
444
|
|
|
@@ -308,7 +453,9 @@ const TEXT = {
|
|
|
308
453
|
* @param {string[]|null} input.commits subjects, null when unknown
|
|
309
454
|
* @param {string|null} input.prBody null when unknown
|
|
310
455
|
* @param {{ratio:number, recent:number, removed:number}|{reason:string}|null} input.rework
|
|
311
|
-
* @param {{oldestDays:number|null, behind:number|null, baseDays:number|null}|null} [input.history]
|
|
456
|
+
* @param {{oldestDays:number|null, oldestCommitAt?:Date|null, behind:number|null, baseDays:number|null}|null} [input.history]
|
|
457
|
+
* @param {ReturnType<typeof normalizePrMeta>} [input.prMeta] null when no pull request is known
|
|
458
|
+
* @param {Date} [input.now]
|
|
312
459
|
* @param {string} [input.lang]
|
|
313
460
|
*/
|
|
314
461
|
export function evaluate({
|
|
@@ -320,6 +467,8 @@ export function evaluate({
|
|
|
320
467
|
prBody,
|
|
321
468
|
rework,
|
|
322
469
|
history = null,
|
|
470
|
+
prMeta = null,
|
|
471
|
+
now = new Date(),
|
|
323
472
|
lang = "en",
|
|
324
473
|
}) {
|
|
325
474
|
const t = messages(TEXT, lang);
|
|
@@ -337,7 +486,19 @@ export function evaluate({
|
|
|
337
486
|
criteriaSource: CRITERIA_SOURCE,
|
|
338
487
|
tag: TAG,
|
|
339
488
|
});
|
|
340
|
-
const record = (rule, status, detail) =>
|
|
489
|
+
const record = (rule, status, detail, score) =>
|
|
490
|
+
results.push(
|
|
491
|
+
score === undefined
|
|
492
|
+
? { ruleId: rule.id, status, detail }
|
|
493
|
+
: { ruleId: rule.id, status, detail, score },
|
|
494
|
+
);
|
|
495
|
+
const scoreDuration = (rule, minutes, issue, detail) => {
|
|
496
|
+
const band = bandOf(minutes, rule);
|
|
497
|
+
if (band <= 50) {
|
|
498
|
+
add(rule, rule.severity, issue(duration(minutes, lang), band));
|
|
499
|
+
record(rule, "violated", detail, band);
|
|
500
|
+
} else record(rule, "pass", detail, band);
|
|
501
|
+
};
|
|
341
502
|
|
|
342
503
|
for (const rule of rules) {
|
|
343
504
|
const modes = Array.isArray(rule.modes) ? rule.modes : [];
|
|
@@ -425,8 +586,14 @@ export function evaluate({
|
|
|
425
586
|
record(rule, "not-measured", rework?.reason ?? "no history");
|
|
426
587
|
break;
|
|
427
588
|
}
|
|
589
|
+
const score = Math.round((1 - rework.ratio) * 100);
|
|
428
590
|
if (rework.removed < rule.min_removed_lines) {
|
|
429
|
-
record(
|
|
591
|
+
record(
|
|
592
|
+
rule,
|
|
593
|
+
"pass",
|
|
594
|
+
`${rework.removed} removed lines, below ${rule.min_removed_lines}`,
|
|
595
|
+
score,
|
|
596
|
+
);
|
|
430
597
|
} else if (rework.ratio > rule.ratio_over) {
|
|
431
598
|
const pct = Math.round(rework.ratio * 100);
|
|
432
599
|
add(
|
|
@@ -434,8 +601,8 @@ export function evaluate({
|
|
|
434
601
|
rule.severity,
|
|
435
602
|
t.rework(pct, Math.round(rule.ratio_over * 100), rule.window_days),
|
|
436
603
|
);
|
|
437
|
-
record(rule, "violated", `${rework.recent}/${rework.removed}
|
|
438
|
-
} else record(rule, "pass", `${rework.recent}/${rework.removed}
|
|
604
|
+
record(rule, "violated", `${rework.recent}/${rework.removed}`, score);
|
|
605
|
+
} else record(rule, "pass", `${rework.recent}/${rework.removed}`, score);
|
|
439
606
|
break;
|
|
440
607
|
}
|
|
441
608
|
case "VLV-PRDESC-01": {
|
|
@@ -503,6 +670,97 @@ export function evaluate({
|
|
|
503
670
|
} else record(rule, "pass", `${n} <= ${rule.important_over}`);
|
|
504
671
|
break;
|
|
505
672
|
}
|
|
673
|
+
case "VLV-CYCLE-01": {
|
|
674
|
+
const start = history?.oldestCommitAt ?? null;
|
|
675
|
+
const end = prMeta ? prMeta.createdAt : mode === "commit" ? now : null;
|
|
676
|
+
if (!start) {
|
|
677
|
+
record(rule, "not-measured", "commit dates unknown");
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
if (!end) {
|
|
681
|
+
record(rule, "not-measured", "no pull request opening time (branch input)");
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
const m = minutesBetween(start, end);
|
|
685
|
+
scoreDuration(rule, m, t.coding, `${m} min to open`);
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
case "VLV-CYCLE-02": {
|
|
689
|
+
if (!prMeta) {
|
|
690
|
+
record(rule, "not-measured", "no pull request metadata");
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
const ready = prMeta.readyAt ?? (prMeta.isDraft ? null : prMeta.createdAt);
|
|
694
|
+
if (!ready) {
|
|
695
|
+
record(rule, "not-measured", "still a draft; the pickup clock has not started");
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
if (!prMeta.firstReviewAt && (prMeta.mergedAt || prMeta.closedAt)) {
|
|
699
|
+
record(
|
|
700
|
+
rule,
|
|
701
|
+
"not-measured",
|
|
702
|
+
prMeta.mergedAt ? "merged without a review" : "closed without a review",
|
|
703
|
+
);
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
const waiting = !prMeta.firstReviewAt;
|
|
707
|
+
const m = minutesBetween(ready, prMeta.firstReviewAt ?? now);
|
|
708
|
+
scoreDuration(
|
|
709
|
+
rule,
|
|
710
|
+
m,
|
|
711
|
+
(d, band) => t.pickup(d, band, waiting),
|
|
712
|
+
waiting ? `${m} min, still waiting` : `${m} min to first review`,
|
|
713
|
+
);
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
case "VLV-CYCLE-03": {
|
|
717
|
+
if (!prMeta?.firstReviewAt) {
|
|
718
|
+
record(rule, "not-measured", prMeta ? "no review yet" : "no pull request metadata");
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
const done = prMeta.approvedAt ?? prMeta.mergedAt ?? prMeta.closedAt;
|
|
722
|
+
const m = minutesBetween(prMeta.firstReviewAt, done ?? now);
|
|
723
|
+
scoreDuration(
|
|
724
|
+
rule,
|
|
725
|
+
m,
|
|
726
|
+
(d, band) => t.review(d, band, !done),
|
|
727
|
+
done ? `${m} min to approval` : `${m} min, still in review`,
|
|
728
|
+
);
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
case "VLV-PICKUP-01": {
|
|
732
|
+
if (!prMeta) {
|
|
733
|
+
record(rule, "not-measured", "no pull request metadata");
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
if (prMeta.mergedAt || prMeta.closedAt) {
|
|
737
|
+
record(rule, "not-applicable", prMeta.mergedAt ? "already merged" : "closed");
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
const problems = [];
|
|
741
|
+
const reviewerUnknown =
|
|
742
|
+
prMeta.reviewerCount === null && !prMeta.firstReviewAt && !prMeta.isDraft;
|
|
743
|
+
if (prMeta.reviewerCount === 0 && !prMeta.firstReviewAt && !prMeta.isDraft) {
|
|
744
|
+
add(rule, rule.severity, t.noReviewer);
|
|
745
|
+
problems.push("no reviewer");
|
|
746
|
+
}
|
|
747
|
+
const draftFor = minutesBetween(prMeta.createdAt, now);
|
|
748
|
+
if (prMeta.isDraft && draftFor > rule.draft_minutes_over) {
|
|
749
|
+
add(rule, rule.draft_severity ?? rule.severity, t.draft(duration(draftFor, lang)));
|
|
750
|
+
problems.push(`draft for ${draftFor} min`);
|
|
751
|
+
}
|
|
752
|
+
if (problems.length) record(rule, "violated", problems.join(", "));
|
|
753
|
+
else if (reviewerUnknown) record(rule, "not-measured", "reviewer count unknown");
|
|
754
|
+
else
|
|
755
|
+
record(
|
|
756
|
+
rule,
|
|
757
|
+
"pass",
|
|
758
|
+
prMeta.reviewerCount === null
|
|
759
|
+
? "already reviewed"
|
|
760
|
+
: `${prMeta.reviewerCount} reviewer(s)`,
|
|
761
|
+
);
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
506
764
|
default:
|
|
507
765
|
record(rule, "not-measured", "rule has no evaluator in this version of the gate");
|
|
508
766
|
}
|
|
@@ -510,7 +768,10 @@ export function evaluate({
|
|
|
510
768
|
|
|
511
769
|
const splitRequired = results.some((r) => SPLIT_RULES.has(r.ruleId) && r.status === "violated");
|
|
512
770
|
const failing = findings.some((f) => f.severity === "blocking" || f.severity === "important");
|
|
513
|
-
|
|
771
|
+
const scores = Object.fromEntries(
|
|
772
|
+
results.filter((r) => r.score !== undefined).map((r) => [r.ruleId, r.score]),
|
|
773
|
+
);
|
|
774
|
+
return { results, findings, splitRequired, failing, scores };
|
|
514
775
|
}
|
|
515
776
|
|
|
516
777
|
// ---------------------------------------------------------------------------
|
|
@@ -745,7 +1006,23 @@ async function main() {
|
|
|
745
1006
|
}
|
|
746
1007
|
const oldest = commitDates.length ? Math.min(...commitDates.map((d) => d.getTime())) : null;
|
|
747
1008
|
history.oldestDays = oldest === null ? null : daysBetween(new Date(oldest), now);
|
|
1009
|
+
history.oldestCommitAt = oldest === null ? null : new Date(oldest);
|
|
748
1010
|
const prBody = args["pr-body-file"] ? readFileSync(args["pr-body-file"], "utf-8") : null;
|
|
1011
|
+
let prMeta = null;
|
|
1012
|
+
if (args["pr-meta-file"]) {
|
|
1013
|
+
let problem = null;
|
|
1014
|
+
try {
|
|
1015
|
+
prMeta = normalizePrMeta(JSON.parse(readFileSync(args["pr-meta-file"], "utf-8")));
|
|
1016
|
+
if (!prMeta) problem = "no valid createdAt";
|
|
1017
|
+
} catch (err) {
|
|
1018
|
+
problem = err.message;
|
|
1019
|
+
}
|
|
1020
|
+
if (problem) {
|
|
1021
|
+
process.stderr.write(
|
|
1022
|
+
`valven-gate: --pr-meta-file ${args["pr-meta-file"]}: ${problem}; the timeline rules report not-measured\n`,
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
749
1026
|
|
|
750
1027
|
const metrics = measure(files, patterns);
|
|
751
1028
|
const verdict = evaluate({
|
|
@@ -757,6 +1034,8 @@ async function main() {
|
|
|
757
1034
|
prBody,
|
|
758
1035
|
rework,
|
|
759
1036
|
history,
|
|
1037
|
+
prMeta,
|
|
1038
|
+
now,
|
|
760
1039
|
lang: args.lang ?? outputLanguage(prefsFile),
|
|
761
1040
|
});
|
|
762
1041
|
|
|
@@ -775,6 +1054,8 @@ async function main() {
|
|
|
775
1054
|
excludedFiles: metrics.excluded.length,
|
|
776
1055
|
rework: "reason" in rework ? null : rework,
|
|
777
1056
|
},
|
|
1057
|
+
prMeta,
|
|
1058
|
+
scores: verdict.scores,
|
|
778
1059
|
results: verdict.results,
|
|
779
1060
|
findings: verdict.findings,
|
|
780
1061
|
splitRequired: verdict.splitRequired,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-10-
|
|
4
|
-
"skillCount":
|
|
3
|
+
"generatedAt": "2026-10-07T15:02:16Z",
|
|
4
|
+
"skillCount": 223,
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
7
7
|
"path": "shared/core/apple-archive-compliance/SKILL.md",
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
"path": "shared/core/multi-agent-autopilot/SKILL.md",
|
|
40
40
|
"sha256": "a9731771fbc1f45260f214f069b976eff16e7b25e2f1375b0d9a213575271bc6"
|
|
41
41
|
},
|
|
42
|
+
{
|
|
43
|
+
"path": "shared/core/multi-agent-bug-bash/SKILL.md",
|
|
44
|
+
"sha256": "a44ad2520ef24444b68d9040519f82498abda3ebb6467890bb6b5e1460299400"
|
|
45
|
+
},
|
|
42
46
|
{
|
|
43
47
|
"path": "shared/core/multi-agent-build-optimize/SKILL.md",
|
|
44
48
|
"sha256": "8534891d1198597a0893d131e6b944d15443f62b24cb5584c486a235845fd281"
|
|
@@ -161,7 +165,7 @@
|
|
|
161
165
|
},
|
|
162
166
|
{
|
|
163
167
|
"path": "shared/core/multi-agent-review/SKILL.md",
|
|
164
|
-
"sha256": "
|
|
168
|
+
"sha256": "2b6934d6086a467bdf145b326061ab4b83d5479bfb1a2fb36420ff8012a02e5d"
|
|
165
169
|
},
|
|
166
170
|
{
|
|
167
171
|
"path": "shared/core/multi-agent-route-off/SKILL.md",
|
|
@@ -209,7 +213,7 @@
|
|
|
209
213
|
},
|
|
210
214
|
{
|
|
211
215
|
"path": "shared/core/multi-agent-setup/SKILL.md",
|
|
212
|
-
"sha256": "
|
|
216
|
+
"sha256": "28c00a0a32b5d636240ced308983b4aaee919016693a686a18ccb0e1e9103ec3"
|
|
213
217
|
},
|
|
214
218
|
{
|
|
215
219
|
"path": "shared/core/multi-agent-stack/SKILL.md",
|
|
@@ -229,7 +233,7 @@
|
|
|
229
233
|
},
|
|
230
234
|
{
|
|
231
235
|
"path": "shared/core/multi-agent-sync/SKILL.md",
|
|
232
|
-
"sha256": "
|
|
236
|
+
"sha256": "b34c0e3bd9160552b9222d448e54be14504761597ceea875f0447a227df90772"
|
|
233
237
|
},
|
|
234
238
|
{
|
|
235
239
|
"path": "shared/core/multi-agent-test-accessibility/SKILL.md",
|
|
@@ -261,16 +265,12 @@
|
|
|
261
265
|
},
|
|
262
266
|
{
|
|
263
267
|
"path": "shared/core/multi-agent-update/SKILL.md",
|
|
264
|
-
"sha256": "
|
|
268
|
+
"sha256": "37f5950dca1a09b1ca0c2e2b1b2d20de418f6398a8aed37b062917cf6d95b4a5"
|
|
265
269
|
},
|
|
266
270
|
{
|
|
267
271
|
"path": "shared/core/multi-agent/SKILL.md",
|
|
268
272
|
"sha256": "3dd44ccc00e41c09e59ad223900a391f80158bd3f69e45d67c684285bc42418d"
|
|
269
273
|
},
|
|
270
|
-
{
|
|
271
|
-
"path": "shared/core/valven-rules/SKILL.md",
|
|
272
|
-
"sha256": "274299b75e36991941b8402ae025967ce553878100c1a3878a955d27f5b7db9e"
|
|
273
|
-
},
|
|
274
274
|
{
|
|
275
275
|
"path": "shared/external/accessibility-compliance-accessibility-audit/SKILL.md",
|
|
276
276
|
"sha256": "0508b6e832b43d54c6ed5f57d42c292925b81e4df1c132ba1786860385ea2838"
|
|
@@ -455,6 +455,10 @@
|
|
|
455
455
|
"path": "shared/external/docker-expert/SKILL.md",
|
|
456
456
|
"sha256": "b3db936caa89a2c249ae64896cfb4c0a72ac45064cc081d6f99c78efe6a1f9e9"
|
|
457
457
|
},
|
|
458
|
+
{
|
|
459
|
+
"path": "shared/external/e2e-testing/SKILL.md",
|
|
460
|
+
"sha256": "e76dac61b973a5c509a9902a15a7c18ed226d838406fd0f8b513f24b45ea4025"
|
|
461
|
+
},
|
|
458
462
|
{
|
|
459
463
|
"path": "shared/external/energykit/SKILL.md",
|
|
460
464
|
"sha256": "a557acc6e324f4b841f411cfb0d9040094c8d2934aefe5cb4eecd5a2a6b0e402"
|
|
@@ -839,6 +843,10 @@
|
|
|
839
843
|
"path": "shared/external/typescript-patterns/SKILL.md",
|
|
840
844
|
"sha256": "784d294aa6ba09ff29bc7ec0f106eb512a006e79c96a01ec92d1699bfa122077"
|
|
841
845
|
},
|
|
846
|
+
{
|
|
847
|
+
"path": "shared/external/valven-rules/SKILL.md",
|
|
848
|
+
"sha256": "4dcb5f937310523e13250780ed381b5a2677bc4a4f44b974fba2b89fe8b60173"
|
|
849
|
+
},
|
|
842
850
|
{
|
|
843
851
|
"path": "shared/external/vision-framework/SKILL.md",
|
|
844
852
|
"sha256": "370fdf6eaf71833dff0e8f2d3989a849b5c42ff3198605aa818c2fd0c12c7ef7"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"skillCount":
|
|
3
|
+
"skillCount": 223,
|
|
4
4
|
"entries": [
|
|
5
5
|
{
|
|
6
6
|
"name": "accessibility-compliance-accessibility-audit",
|
|
@@ -519,6 +519,17 @@
|
|
|
519
519
|
"triggerPaths": [],
|
|
520
520
|
"relativePath": "shared/external/docker-expert/SKILL.md"
|
|
521
521
|
},
|
|
522
|
+
{
|
|
523
|
+
"name": "e2e-testing",
|
|
524
|
+
"description": "End-to-end testing of a running web or mobile app through the multi-agent-toolkit MCP server: element locators instead of coordinates, expect steps that return passed / failed / blocked verdicts with codes, secrets by secret_ref, failure traces, saved and replayed step sequences, parallel web sessio",
|
|
525
|
+
"platform": null,
|
|
526
|
+
"group": "external",
|
|
527
|
+
"plugin": "ai-common-toolkit",
|
|
528
|
+
"invokeAs": "ai-common-toolkit:e2e-testing",
|
|
529
|
+
"triggerKeywords": [],
|
|
530
|
+
"triggerPaths": [],
|
|
531
|
+
"relativePath": "shared/external/e2e-testing/SKILL.md"
|
|
532
|
+
},
|
|
522
533
|
{
|
|
523
534
|
"name": "energykit",
|
|
524
535
|
"description": "EnergyKit on iOS 26: grid electricity forecasts via ElectricityGuidance, EnergyVenue, EV and HVAC load events, ElectricityInsightService history. Use when building smart home, EV charging, HVAC scheduling or energy dashboard features that shift home power use to cleaner or cheaper grid periods.",
|
|
@@ -1025,6 +1036,17 @@
|
|
|
1025
1036
|
"triggerPaths": [],
|
|
1026
1037
|
"relativePath": "shared/core/multi-agent-autopilot-status/SKILL.md"
|
|
1027
1038
|
},
|
|
1039
|
+
{
|
|
1040
|
+
"name": "multi-agent-bug-bash",
|
|
1041
|
+
"description": "Bug bash: parallel exploratory sessions by charter and persona, each with a budget; a finding is reported only when a repro test fails for its reason. Use when an app or branch needs broad bug hunting.",
|
|
1042
|
+
"platform": null,
|
|
1043
|
+
"group": "core",
|
|
1044
|
+
"plugin": null,
|
|
1045
|
+
"invokeAs": "multi-agent-bug-bash",
|
|
1046
|
+
"triggerKeywords": [],
|
|
1047
|
+
"triggerPaths": [],
|
|
1048
|
+
"relativePath": "shared/core/multi-agent-bug-bash/SKILL.md"
|
|
1049
|
+
},
|
|
1028
1050
|
{
|
|
1029
1051
|
"name": "multi-agent-build-optimize",
|
|
1030
1052
|
"description": "Wrapper that dispatches to xcode-build-orchestrator on iOS repos. Benchmarks the current Xcode build, runs compilation / project / SPM analyzers, produces a recommend-first optimization plan in .build-benchmark/, and re-benchmarks after the developer approves changes. Fails fast on non-iOS stacks. U",
|
|
@@ -2292,14 +2314,14 @@
|
|
|
2292
2314
|
},
|
|
2293
2315
|
{
|
|
2294
2316
|
"name": "valven-rules",
|
|
2295
|
-
"description": "Delivery-flow rules from
|
|
2317
|
+
"description": "Delivery-flow rules from metrics Valven reports: PR size, one concern per PR, tests with source, issue keys, no WIP commits, churn, PR body, and coding, pickup and review time on Valven's bands. Use when developing, opening or reviewing a pull request held to these rules, or splitting a branch into ",
|
|
2296
2318
|
"platform": null,
|
|
2297
|
-
"group": "
|
|
2298
|
-
"plugin":
|
|
2299
|
-
"invokeAs": "valven-rules",
|
|
2319
|
+
"group": "external",
|
|
2320
|
+
"plugin": "ai-common-toolkit",
|
|
2321
|
+
"invokeAs": "ai-common-toolkit:valven-rules",
|
|
2300
2322
|
"triggerKeywords": [],
|
|
2301
2323
|
"triggerPaths": [],
|
|
2302
|
-
"relativePath": "shared/
|
|
2324
|
+
"relativePath": "shared/external/valven-rules/SKILL.md"
|
|
2303
2325
|
},
|
|
2304
2326
|
{
|
|
2305
2327
|
"name": "vision-framework",
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Single source of truth for skills delivered to both Claude Code (`~/.claude/skills/`) and Copilot CLI (`~/.copilot/skills/`) by the installer.
|
|
4
4
|
|
|
5
|
-
**Total:**
|
|
5
|
+
**Total:** 223 skills (67 core + 156 external). Auto-generated by `scripts/gen-skills-index.mjs` - do not edit by hand.
|
|
6
6
|
|
|
7
7
|
## Directory layout
|
|
8
8
|
|
|
9
9
|
- **`core/`** - 67 `multi-agent*` orchestration skills that are pipeline-critical. Edits here are core-code changes.
|
|
10
|
-
- **`external/`** -
|
|
10
|
+
- **`external/`** - 156 iOS / Android / generic skills that ship into the stack plugins. Each names its origin in `metadata.source`: `multi-agent-pipeline` when written here, or the upstream and license a `NOTICE-*.md` in this directory covers.
|
|
11
11
|
- Install destinations (ADR-0009): Claude Code gets NO local copy of `external/` - it loads those skills from the `multi-agent-plugins` marketplace, namespaced (`ai-<stack>-toolkit:<name>`); only the two compliance catalogs from `core/` land in `~/.claude/skills/`. Copilot CLI and Codex CLI receive a flat copy filtered to the enabled stacks. `external/` remains the single authoring source that `build-stack-plugins.mjs` publishes from.
|
|
12
12
|
|
|
13
13
|
Source layout is logical grouping only - skill discovery at runtime is unchanged.
|
|
@@ -19,7 +19,7 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
|
|
|
19
19
|
- [Android / Kotlin](#android-kotlin) - 16
|
|
20
20
|
- [Web](#web) - 10
|
|
21
21
|
- [Backend / API](#backend-api) - 11
|
|
22
|
-
- [Cross-cutting](#cross-cutting) -
|
|
22
|
+
- [Cross-cutting](#cross-cutting) - 30
|
|
23
23
|
|
|
24
24
|
## Pipeline Orchestration
|
|
25
25
|
|
|
@@ -35,6 +35,7 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
|
|
|
35
35
|
| [`multi-agent-autopilot-off`](./core/multi-agent-autopilot-off/) | `core` | Turn off continuous mode on this machine: the schedule is removed, work already running finishes, the repo selection is kept. Use when the m |
|
|
36
36
|
| [`multi-agent-autopilot-on`](./core/multi-agent-autopilot-on/) | `core` | Turn on continuous mode on THIS machine: pick the repos it watches; labelled GitHub issues and assigned+labelled Jira items then run in a wo |
|
|
37
37
|
| [`multi-agent-autopilot-status`](./core/multi-agent-autopilot-status/) | `core` | Show what continuous mode is doing here: what runs at which phase, what is queued, what waits for an answer, what PRs it opened today. Use w |
|
|
38
|
+
| [`multi-agent-bug-bash`](./core/multi-agent-bug-bash/) | `core` | Bug bash: parallel exploratory sessions by charter and persona, each with a budget; a finding is reported only when a repro test fails for i |
|
|
38
39
|
| [`multi-agent-build-optimize`](./core/multi-agent-build-optimize/) | `core` | Wrapper that dispatches to xcode-build-orchestrator on iOS repos. Benchmarks the current Xcode build, runs compilation / project / SPM analy |
|
|
39
40
|
| [`multi-agent-channels`](./core/multi-agent-channels/) | `core` | Multi-channel reporter - Jira/Confluence/Wiki/PR description. Multi-select channels + content, humanizer pass, reviewer-preserving Bitbuck |
|
|
40
41
|
| [`multi-agent-complaint-analysis`](./core/multi-agent-complaint-analysis/) | `core` | Customer-complaint triage. Ingests complaints (paste, csv/xlsx/txt/json file, Jira issue, Confluence URL), fetches Graylog evidence per trx/ |
|
|
@@ -91,7 +92,6 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
|
|
|
91
92
|
| [`multi-agent-testflight-validation`](./core/multi-agent-testflight-validation/) | `core` | iOS-pinned alias of store-ready: same three gates on an iOS build, one implementation. Validates a TestFlight / App Store package, never upl |
|
|
92
93
|
| [`multi-agent-uninstall`](./core/multi-agent-uninstall/) | `core` | Uninstall the pipeline from Claude Code + Copilot CLI. Keychain access tokens are always left untouched; --all-data also clears pipeline set |
|
|
93
94
|
| [`multi-agent-update`](./core/multi-agent-update/) | `core` | Update the pipeline to the latest version: git pull, install, migrate. Use when the installed pipeline is behind and should be brought to th |
|
|
94
|
-
| [`valven-rules`](./core/valven-rules/) | `core` | Delivery-flow rules from the metrics Valven reports: PR size, one concern per PR, tests with source, issue keys, no WIP commits, rework, PR |
|
|
95
95
|
|
|
96
96
|
## iOS / Apple Ecosystem
|
|
97
97
|
|
|
@@ -255,6 +255,7 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
|
|
|
255
255
|
| [`cryptokit`](./external/cryptokit/) | `external` | CryptoKit in Swift: SHA-2, SHA-3, HMAC, AES-GCM, ChaChaPoly, P256, P384, P521, Curve25519 and ML-DSA signatures, ECDH with HKDF, HPKE, ML-KE |
|
|
256
256
|
| [`debugging-instruments`](./external/debugging-instruments/) | `external` | Xcode debugging and profiling: LLDB, Memory Graph Debugger, Instruments, crashes, leaks and retain cycles, main-thread hangs, slow rendering |
|
|
257
257
|
| [`debugging-strategies`](./external/debugging-strategies/) | `external` | Language-neutral debugging method: reproduce, minimise, falsifiable hypotheses, git bisect, instrumentation, production traces, concurrency |
|
|
258
|
+
| [`e2e-testing`](./external/e2e-testing/) | `external` | \| |
|
|
258
259
|
| [`evidence-github`](./external/evidence-github/) | `external` | Search GitHub for facts an analysis can cite - a known issue in a dependency, the PR that fixed it, what a release actually changed, prior a |
|
|
259
260
|
| [`evidence-registry`](./external/evidence-registry/) | `external` | Check a package's real version, deprecation status and release notes on npm, PyPI, Maven Central or Swift Package Index. Use when a spec or |
|
|
260
261
|
| [`github-actions-templates`](./external/github-actions-templates/) | `external` | Authoring GitHub Actions workflows: triggers, matrices, caching, concurrency, least-privilege permissions, SHA pinning, OIDC, reusable workf |
|
|
@@ -266,6 +267,7 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
|
|
|
266
267
|
| [`signal-community`](./external/signal-community/) | `external` | Check whether other people hit the same problem, via Stack Overflow and Hacker News (keyless) plus opt-in Reddit and X. Use when a bug repor |
|
|
267
268
|
| [`skill-creator`](./external/skill-creator/) | `external` | House guide for authoring plugin skills: creating a new skill, trimming or splitting a bloated one, writing or sharpening a description, and |
|
|
268
269
|
| [`spm-build-analysis`](./external/spm-build-analysis/) | `external` | Analyze Swift Package Manager dependencies, package plugins, module variants, and CI-oriented build overhead that slow Xcode builds. Use whe |
|
|
270
|
+
| [`valven-rules`](./external/valven-rules/) | `external` | Delivery-flow rules from metrics Valven reports: PR size, one concern per PR, tests with source, issue keys, no WIP commits, churn, PR body, |
|
|
269
271
|
| [`xcode-build-benchmark`](./external/xcode-build-benchmark/) | `external` | Benchmark Xcode clean and incremental builds with repeatable inputs, timing summaries, and timestamped `.build-benchmark/` artifacts. Use wh |
|
|
270
272
|
| [`xcode-build-fixer`](./external/xcode-build-fixer/) | `external` | Apply approved Xcode build optimization changes following best practices, then re-benchmark to verify improvement. Use when a developer has |
|
|
271
273
|
| [`xcode-build-orchestrator`](./external/xcode-build-orchestrator/) | `external` | Orchestrate Xcode build optimization by benchmarking first, running the specialist analysis skills, prioritizing findings, requesting explic |
|