@codedrifters/configulator 0.0.328 → 0.0.330
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/lib/index.d.mts +254 -44
- package/lib/index.d.ts +255 -45
- package/lib/index.js +1735 -734
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +1731 -733
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -586,7 +586,8 @@ var agendaAnalystSubAgent = {
|
|
|
586
586
|
" or cut content \u2014 do not ship a math-broken agenda.",
|
|
587
587
|
"",
|
|
588
588
|
"7. **Create the folder `index.md` if missing.** Populate it",
|
|
589
|
-
" following the
|
|
589
|
+
" following the section-index-page contract in the",
|
|
590
|
+
" `shared-editing-safety` rule:",
|
|
590
591
|
" - Frontmatter `title` (short sidebar label) and `description`",
|
|
591
592
|
" (one line).",
|
|
592
593
|
" - A 2\u20134 sentence summary of the meeting's objective and desired",
|
|
@@ -1159,11 +1160,7 @@ var awsCdkBundle = {
|
|
|
1159
1160
|
// src/agent/bundles/issue-templates.ts
|
|
1160
1161
|
var DEFAULT_ISSUE_TEMPLATES_ENABLED = true;
|
|
1161
1162
|
var DEFAULT_ISSUE_TEMPLATES_PATH = "docs/src/content/docs/agents/issue-templates.md";
|
|
1162
|
-
var DEFAULT_ISSUE_TEMPLATES_BUNDLE_PATH_PATTERNS = [
|
|
1163
|
-
"packages/@codedrifters/configulator/src/agent/bundles/**/*.ts",
|
|
1164
|
-
".claude/agents/**/*.md",
|
|
1165
|
-
".claude/skills/**/*.md"
|
|
1166
|
-
];
|
|
1163
|
+
var DEFAULT_ISSUE_TEMPLATES_BUNDLE_PATH_PATTERNS = [".claude/agents/**/*.md", ".claude/skills/**/*.md"];
|
|
1167
1164
|
var DEFAULT_ISSUE_TEMPLATES_EMIT_CHECKER = false;
|
|
1168
1165
|
var DEFAULT_ISSUE_TEMPLATES_EMIT_STARTER = false;
|
|
1169
1166
|
var DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE = true;
|
|
@@ -2252,6 +2249,55 @@ function renderSharedEditingRuleContent(se) {
|
|
|
2252
2249
|
}
|
|
2253
2250
|
}
|
|
2254
2251
|
lines.push(
|
|
2252
|
+
"## Defer Shared-Index Commit to Final Pre-Push Step",
|
|
2253
|
+
"",
|
|
2254
|
+
"Even with the single-entry, deterministic-sort discipline above,",
|
|
2255
|
+
"two sessions that **prepare** their row inserts at the same time",
|
|
2256
|
+
"still race on push: whichever session pushes second sees the",
|
|
2257
|
+
"first session's commit on the remote and has to rebase its own",
|
|
2258
|
+
"commit on top, regenerating the same insert position calculation",
|
|
2259
|
+
"against a now-changed file. Repeated rebases multiply the chance",
|
|
2260
|
+
"of a mis-merge that silently drops a row.",
|
|
2261
|
+
"",
|
|
2262
|
+
"Sessions that produce both content and a shared-index row insert",
|
|
2263
|
+
"shrink this race window by deferring the index commit to the",
|
|
2264
|
+
"**final pre-push step** \u2014 after every content commit has landed",
|
|
2265
|
+
"locally, and immediately before `git push`:",
|
|
2266
|
+
"",
|
|
2267
|
+
"1. **Commit content first.** Write and commit every non-index",
|
|
2268
|
+
" change that the session produces (profile body, transcript",
|
|
2269
|
+
" extraction, requirement document, etc.) in its own focused",
|
|
2270
|
+
" commit or commits.",
|
|
2271
|
+
"2. **Rebase against the remote default branch** before touching",
|
|
2272
|
+
" the shared index:",
|
|
2273
|
+
"",
|
|
2274
|
+
" ```bash",
|
|
2275
|
+
" git fetch origin",
|
|
2276
|
+
` git pull --${se.conflictStrategy} origin <default-branch>`,
|
|
2277
|
+
" ```",
|
|
2278
|
+
"",
|
|
2279
|
+
"3. **Re-read the shared index** from the now-up-to-date working",
|
|
2280
|
+
" tree. Another session may have appended a row while this",
|
|
2281
|
+
" session's content commits were in flight.",
|
|
2282
|
+
"4. **Re-compute the insert position** in declared sort order",
|
|
2283
|
+
" against the freshly-read rows. Do not assume the position",
|
|
2284
|
+
" computed earlier in the session is still correct.",
|
|
2285
|
+
"5. **Insert the row and commit the index edit on its own** \u2014",
|
|
2286
|
+
" one focused commit whose only file is the shared index. Run",
|
|
2287
|
+
" the commit-path verification (above) against that commit",
|
|
2288
|
+
" before continuing.",
|
|
2289
|
+
"6. **Push immediately.** The shorter the wall-clock gap between",
|
|
2290
|
+
" the rebase / re-read in step 2 and the push, the smaller the",
|
|
2291
|
+
" window in which another session can land a competing row.",
|
|
2292
|
+
"",
|
|
2293
|
+
"Per-agent workflows that touch a shared index file should call",
|
|
2294
|
+
"out this defer-to-final-commit sequence explicitly \u2014 see the",
|
|
2295
|
+
"`meeting-analyst` and `software-profile-analyst` sub-agent",
|
|
2296
|
+
"prompts for the canonical wording. The pattern is mechanical",
|
|
2297
|
+
"(rebase, re-read, re-compute, focused commit, push) rather than",
|
|
2298
|
+
"editorial, so the same recipe applies to every shared-index",
|
|
2299
|
+
"row-producing agent regardless of what content surrounds it.",
|
|
2300
|
+
"",
|
|
2255
2301
|
"## Merge-Conflict Resolution",
|
|
2256
2302
|
"",
|
|
2257
2303
|
"When `git push` reports a conflict on a shared index file (two",
|
|
@@ -2283,7 +2329,66 @@ function renderSharedEditingRuleContent(se) {
|
|
|
2283
2329
|
"conflicts; rewriting or reordering existing rows almost always",
|
|
2284
2330
|
"does. If a shared index needs a structural change (column",
|
|
2285
2331
|
"added, sort key changed), file a dedicated issue for the change",
|
|
2286
|
-
"rather than bundling it into a content-contributing PR."
|
|
2332
|
+
"rather than bundling it into a content-contributing PR.",
|
|
2333
|
+
"",
|
|
2334
|
+
"## Section Index Page Shape",
|
|
2335
|
+
"",
|
|
2336
|
+
"When any agent creates or updates an `index.md` (or `README.md`)",
|
|
2337
|
+
"in a docs subdirectory under a Starlight content root, the file's",
|
|
2338
|
+
"body must include:",
|
|
2339
|
+
"",
|
|
2340
|
+
"1. **A 1\u20132 paragraph summary** of the section's purpose and how",
|
|
2341
|
+
" it fits into the larger research, requirements, or capability",
|
|
2342
|
+
" area. Readers landing on the page should understand what's in",
|
|
2343
|
+
" the section without falling back to the sidebar.",
|
|
2344
|
+
"",
|
|
2345
|
+
"2. **A grouped, linked listing of the directory's children**",
|
|
2346
|
+
" (table or bullet list). When a natural taxonomy exists (e.g.",
|
|
2347
|
+
" organizations grouped by sub-segment, regulations grouped by",
|
|
2348
|
+
" jurisdiction, capabilities grouped by tier), use it. Otherwise",
|
|
2349
|
+
" sort alphabetically by title. Every listing entry must link",
|
|
2350
|
+
" to the child page.",
|
|
2351
|
+
"",
|
|
2352
|
+
"3. **No body `# Heading`.** Starlight renders the frontmatter",
|
|
2353
|
+
" `title:` as the page H1 automatically \u2014 a body H1 produces a",
|
|
2354
|
+
" duplicate. The same no-body-H1 contract that applies to skill",
|
|
2355
|
+
" templates and inline agent templates also applies to every",
|
|
2356
|
+
" index page emitted by an agent.",
|
|
2357
|
+
"",
|
|
2358
|
+
"### When this applies",
|
|
2359
|
+
"",
|
|
2360
|
+
"The convention applies to every `index.md` / `README.md` file",
|
|
2361
|
+
"covered by the shared-index path globs listed above:",
|
|
2362
|
+
"",
|
|
2363
|
+
"- `docs/src/content/docs/**/index.md`",
|
|
2364
|
+
"- `docs/src/content/docs/**/README.md`",
|
|
2365
|
+
"",
|
|
2366
|
+
"Any agent that scaffolds a new directory under a Starlight",
|
|
2367
|
+
"content root MUST populate the directory's index page following",
|
|
2368
|
+
"this contract \u2014 a one-sentence stub is not acceptable.",
|
|
2369
|
+
"",
|
|
2370
|
+
"### Reference shapes",
|
|
2371
|
+
"",
|
|
2372
|
+
"Existing rich indexes are the canonical reference shape. Examples:",
|
|
2373
|
+
"",
|
|
2374
|
+
"- A `<MEETINGS_ROOT>/<YYYY-MM-DD>-<slug>/index.md` populated by",
|
|
2375
|
+
" the `agenda-analyst` bundle: frontmatter `title` /",
|
|
2376
|
+
" `description`, a 2\u20134 sentence summary, and a `## Documents`",
|
|
2377
|
+
" section listing every page in the folder.",
|
|
2378
|
+
"- A regulations scope index that opens with two paragraphs of",
|
|
2379
|
+
" context and groups every linked regulation under a",
|
|
2380
|
+
" `## Regulations` table by jurisdiction.",
|
|
2381
|
+
"- A standards-organizations index that groups every linked",
|
|
2382
|
+
" organization under a `## Organizations` heading by role",
|
|
2383
|
+
" (governance, working group, implementer).",
|
|
2384
|
+
"",
|
|
2385
|
+
"### Out of scope",
|
|
2386
|
+
"",
|
|
2387
|
+
"- Auto-generated child listings via Astro/Starlight components.",
|
|
2388
|
+
" Agents emit hand-curated tables or bullet lists; the rule",
|
|
2389
|
+
" defines the shape, not the rendering technology.",
|
|
2390
|
+
"- Backfilling pre-existing stub indexes is a downstream-consumer",
|
|
2391
|
+
" cleanup task, not a configulator change."
|
|
2287
2392
|
);
|
|
2288
2393
|
return lines.join("\n");
|
|
2289
2394
|
}
|
|
@@ -2299,9 +2404,14 @@ function renderSharedEditingBundleHook(se, bundleLabel) {
|
|
|
2299
2404
|
"the latest default branch before editing, insert exactly one row",
|
|
2300
2405
|
"in deterministic sort position, commit the index edit in its own",
|
|
2301
2406
|
"focused commit, and verify the row is present in the commit",
|
|
2302
|
-
"before pushing.
|
|
2303
|
-
"
|
|
2304
|
-
"
|
|
2407
|
+
"before pushing. **Defer the index commit to the final pre-push",
|
|
2408
|
+
"step** \u2014 land every content commit first, then rebase against",
|
|
2409
|
+
"the remote default branch, re-read the index, re-compute the",
|
|
2410
|
+
"insert position, write the row, commit the index on its own,",
|
|
2411
|
+
"and push immediately. See the `shared-editing-safety` rule for",
|
|
2412
|
+
"the full protocol, the list of files covered, the",
|
|
2413
|
+
"defer-to-final-commit sequence, and the merge-conflict",
|
|
2414
|
+
"resolution recipe."
|
|
2305
2415
|
].join("\n");
|
|
2306
2416
|
}
|
|
2307
2417
|
function renderSharedEditingHelperScript(_se) {
|
|
@@ -2833,71 +2943,6 @@ function assertValidProductContextPath(value) {
|
|
|
2833
2943
|
}
|
|
2834
2944
|
}
|
|
2835
2945
|
|
|
2836
|
-
// src/agent/bundles/stub-index-convention.ts
|
|
2837
|
-
function renderStubIndexConventionRuleContent() {
|
|
2838
|
-
return [
|
|
2839
|
-
"# Section Index Pages",
|
|
2840
|
-
"",
|
|
2841
|
-
"When any agent creates or updates an `index.md` (or `README.md`)",
|
|
2842
|
-
"in a docs subdirectory under a Starlight content root, the file's",
|
|
2843
|
-
"body must include:",
|
|
2844
|
-
"",
|
|
2845
|
-
"1. **A 1\u20132 paragraph summary** of the section's purpose and how",
|
|
2846
|
-
" it fits into the larger research, requirements, or capability",
|
|
2847
|
-
" area. Readers landing on the page should understand what's in",
|
|
2848
|
-
" the section without falling back to the sidebar.",
|
|
2849
|
-
"",
|
|
2850
|
-
"2. **A grouped, linked listing of the directory's children**",
|
|
2851
|
-
" (table or bullet list). When a natural taxonomy exists (e.g.",
|
|
2852
|
-
" organizations grouped by sub-segment, regulations grouped by",
|
|
2853
|
-
" jurisdiction, capabilities grouped by tier), use it. Otherwise",
|
|
2854
|
-
" sort alphabetically by title. Every listing entry must link",
|
|
2855
|
-
" to the child page.",
|
|
2856
|
-
"",
|
|
2857
|
-
"3. **No body `# Heading`.** Starlight renders the frontmatter",
|
|
2858
|
-
" `title:` as the page H1 automatically \u2014 a body H1 produces a",
|
|
2859
|
-
" duplicate. The same no-body-H1 contract that applies to skill",
|
|
2860
|
-
" templates and inline agent templates also applies to every",
|
|
2861
|
-
" index page emitted by an agent.",
|
|
2862
|
-
"",
|
|
2863
|
-
"## When this applies",
|
|
2864
|
-
"",
|
|
2865
|
-
"The convention applies to every `index.md` / `README.md` file",
|
|
2866
|
-
"covered by the shared-index path globs documented in the",
|
|
2867
|
-
"`shared-editing-safety` rule:",
|
|
2868
|
-
"",
|
|
2869
|
-
"- `docs/src/content/docs/**/index.md`",
|
|
2870
|
-
"- `docs/src/content/docs/**/README.md`",
|
|
2871
|
-
"",
|
|
2872
|
-
"Any agent that scaffolds a new directory under a Starlight",
|
|
2873
|
-
"content root MUST populate the directory's index page following",
|
|
2874
|
-
"this contract \u2014 a one-sentence stub is not acceptable.",
|
|
2875
|
-
"",
|
|
2876
|
-
"## Reference shapes",
|
|
2877
|
-
"",
|
|
2878
|
-
"Existing rich indexes are the canonical reference shape. Examples:",
|
|
2879
|
-
"",
|
|
2880
|
-
"- A `<MEETINGS_ROOT>/<YYYY-MM-DD>-<slug>/index.md` populated by",
|
|
2881
|
-
" the `agenda-analyst` bundle: frontmatter `title` /",
|
|
2882
|
-
" `description`, a 2\u20134 sentence summary, and a `## Documents`",
|
|
2883
|
-
" section listing every page in the folder.",
|
|
2884
|
-
"- A regulations scope index that opens with two paragraphs of",
|
|
2885
|
-
" context and groups every linked regulation under a",
|
|
2886
|
-
" `## Regulations` table by jurisdiction.",
|
|
2887
|
-
"- A standards-organizations index that groups every linked",
|
|
2888
|
-
" organization under a `## Organizations` heading by role",
|
|
2889
|
-
" (governance, working group, implementer).",
|
|
2890
|
-
"",
|
|
2891
|
-
"## Out of scope",
|
|
2892
|
-
"",
|
|
2893
|
-
"- Auto-generated child listings via Astro/Starlight components.",
|
|
2894
|
-
" Agents emit hand-curated tables or bullet lists; the rule",
|
|
2895
|
-
" defines the shape, not the rendering technology.",
|
|
2896
|
-
"- Backfilling pre-existing stub indexes is a downstream-consumer",
|
|
2897
|
-
" cleanup task, not a configulator change."
|
|
2898
|
-
].join("\n");
|
|
2899
|
-
}
|
|
2900
|
-
|
|
2901
2946
|
// src/agent/bundles/temporal-framing.ts
|
|
2902
2947
|
var DEFAULT_TEMPORAL_FRAMING_ENABLED = true;
|
|
2903
2948
|
var DEFAULT_TEMPORAL_FRAMING_PATHS = [
|
|
@@ -4066,6 +4111,12 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
|
|
|
4066
4111
|
{
|
|
4067
4112
|
name: "issue-label-conventions",
|
|
4068
4113
|
description: "Priority and status label taxonomy, defaults, inference rules, and blocking rules for agent-created or updated issues",
|
|
4114
|
+
// ALWAYS scope: every agent (and the user) needs the label
|
|
4115
|
+
// taxonomy whenever an issue gets created or updated, which
|
|
4116
|
+
// happens from any context — not only when editing agent /
|
|
4117
|
+
// skill / bundle source. Consumers that want to narrow the
|
|
4118
|
+
// load can override via `agentConfig.additionalRulePaths`
|
|
4119
|
+
// or `excludeRules`.
|
|
4069
4120
|
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
4070
4121
|
content: [
|
|
4071
4122
|
"# Issue Label Conventions",
|
|
@@ -4431,7 +4482,13 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
|
|
|
4431
4482
|
{
|
|
4432
4483
|
name: "progress-file-convention",
|
|
4433
4484
|
description: "Progress-file schema and write rules, partial-resume protocol, stale-branch decision tree, and [BLOCKED] comment format that let phased agents survive crashes without losing work.",
|
|
4434
|
-
scope: AGENT_RULE_SCOPE.
|
|
4485
|
+
scope: AGENT_RULE_SCOPE.FILE_PATTERN,
|
|
4486
|
+
// Bundle defaults exclude paths into configulator's own source
|
|
4487
|
+
// (only meaningful when configulator is a workspace package,
|
|
4488
|
+
// i.e. in codedrifters/packages itself). That repo restores
|
|
4489
|
+
// them via `agentConfig.additionalRulePaths`; other consumers
|
|
4490
|
+
// get a clean default.
|
|
4491
|
+
filePatterns: [".claude/agents/*.md", ".claude/procedures/**"],
|
|
4435
4492
|
content: renderProgressFilesRuleContent(resolveProgressFiles()),
|
|
4436
4493
|
platforms: {
|
|
4437
4494
|
cursor: { exclude: true }
|
|
@@ -4441,27 +4498,26 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
|
|
|
4441
4498
|
{
|
|
4442
4499
|
name: "shared-editing-safety",
|
|
4443
4500
|
description: "Shared-editing safety: single-entry deterministic-sort inserts on index files, commit-path verification, and the merge-conflict resolution recipe that keeps concurrent agent sessions from dropping each other's rows on shared registries and feature matrices.",
|
|
4444
|
-
scope: AGENT_RULE_SCOPE.
|
|
4501
|
+
scope: AGENT_RULE_SCOPE.FILE_PATTERN,
|
|
4502
|
+
filePatterns: DEFAULT_SHARED_INDEX_PATHS,
|
|
4445
4503
|
content: renderSharedEditingRuleContent(resolveSharedEditing()),
|
|
4446
4504
|
platforms: {
|
|
4447
4505
|
cursor: { exclude: true }
|
|
4448
4506
|
},
|
|
4449
4507
|
tags: ["workflow"]
|
|
4450
4508
|
},
|
|
4451
|
-
{
|
|
4452
|
-
name: "stub-index-convention",
|
|
4453
|
-
description: "Section-index convention: every agent-emitted `index.md` / `README.md` under a Starlight docs root carries a contextual summary plus a grouped, linked listing of the directory's children \u2014 and no body `# Heading` (Starlight renders the frontmatter `title:` as the page H1).",
|
|
4454
|
-
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
4455
|
-
content: renderStubIndexConventionRuleContent(),
|
|
4456
|
-
platforms: {
|
|
4457
|
-
cursor: { exclude: true }
|
|
4458
|
-
},
|
|
4459
|
-
tags: ["workflow"]
|
|
4460
|
-
},
|
|
4461
4509
|
{
|
|
4462
4510
|
name: "temporal-framing-convention",
|
|
4463
4511
|
description: "Temporal-framing convention: every agent-authored time-sensitive factual claim (ownership, leadership tenure, regulatory status, litigation, dated metrics) carries an inline `as of [YYYY-MM-DD]` or `as of [Month YYYY]` qualifier on first occurrence so refresh agents have a mechanical signal for which claims to re-verify.",
|
|
4464
|
-
scope: AGENT_RULE_SCOPE.
|
|
4512
|
+
scope: AGENT_RULE_SCOPE.FILE_PATTERN,
|
|
4513
|
+
filePatterns: [
|
|
4514
|
+
"docs/src/content/docs/profiles/**/*.md",
|
|
4515
|
+
"docs/src/content/docs/industry-research/**/*.md",
|
|
4516
|
+
"docs/src/content/docs/software-research/**/*.md",
|
|
4517
|
+
"docs/src/content/docs/regulatory-research/**/*.md",
|
|
4518
|
+
"docs/src/content/docs/standards-research/**/*.md",
|
|
4519
|
+
"docs/src/content/docs/customer-research/**/*.md"
|
|
4520
|
+
],
|
|
4465
4521
|
content: renderTemporalFramingRuleContent(resolveTemporalFraming()),
|
|
4466
4522
|
platforms: {
|
|
4467
4523
|
cursor: { exclude: true }
|
|
@@ -4471,7 +4527,10 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
|
|
|
4471
4527
|
{
|
|
4472
4528
|
name: "skill-evals",
|
|
4473
4529
|
description: "Skill eval harness contract: declarative prompt/expected-output regression suites per skill at `<skillsRoot>/<skill-name>/evals/evals.json`, parameterised by a shared product-context fixture so the same eval shape works across every configulator-consuming project without forking fixtures.",
|
|
4474
|
-
scope: AGENT_RULE_SCOPE.
|
|
4530
|
+
scope: AGENT_RULE_SCOPE.FILE_PATTERN,
|
|
4531
|
+
// Bundle defaults exclude paths into configulator's own
|
|
4532
|
+
// source — see comment on progress-file-convention above.
|
|
4533
|
+
filePatterns: [".claude/skills/**"],
|
|
4475
4534
|
content: renderSkillEvalsRuleContent(resolveSkillEvals()),
|
|
4476
4535
|
platforms: {
|
|
4477
4536
|
cursor: { exclude: true }
|
|
@@ -4481,7 +4540,14 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
|
|
|
4481
4540
|
{
|
|
4482
4541
|
name: "issue-templates-convention",
|
|
4483
4542
|
description: `Issue-templates convention: a single hand-authored reference page under \`${paths.docsRoot}/agents/issue-templates.md\` that carries one canonical \`gh issue create\` recipe per downstream phase label, and the reference-don't-inline rule that keeps bundle rules and agent prompts citing that page instead of duplicating full template invocations.`,
|
|
4484
|
-
scope: AGENT_RULE_SCOPE.
|
|
4543
|
+
scope: AGENT_RULE_SCOPE.FILE_PATTERN,
|
|
4544
|
+
// Bundle defaults exclude paths into configulator's own
|
|
4545
|
+
// source — see comment on progress-file-convention above.
|
|
4546
|
+
filePatterns: [
|
|
4547
|
+
".claude/skills/**/SKILL.md",
|
|
4548
|
+
".claude/agents/*.md",
|
|
4549
|
+
`${paths.docsRoot}/agents/issue-templates.md`
|
|
4550
|
+
],
|
|
4485
4551
|
content: renderIssueTemplatesRuleContent(resolveIssueTemplates()),
|
|
4486
4552
|
platforms: {
|
|
4487
4553
|
cursor: { exclude: true }
|
|
@@ -5804,8 +5870,8 @@ function buildBusinessModelsAnalystSubAgent(paths) {
|
|
|
5804
5870
|
"",
|
|
5805
5871
|
"7. **Create the segment index** at",
|
|
5806
5872
|
" `<BUSINESS_MODELS_ROOT>/<industry>/segments/<SEGMENT_SLUG>/index.md`",
|
|
5807
|
-
" if it does not already exist. Follow the",
|
|
5808
|
-
" `
|
|
5873
|
+
" if it does not already exist. Follow the section-index-page",
|
|
5874
|
+
" contract in the `shared-editing-safety` rule: a 1\u20132 paragraph summary of the",
|
|
5809
5875
|
" segment plus a linked listing of every page in the folder",
|
|
5810
5876
|
" (`./business-model.md` and any value-stream / capability pages",
|
|
5811
5877
|
" that land later). No body `# Heading` \u2014 the frontmatter",
|
|
@@ -9762,6 +9828,395 @@ var setIssueTypeProcedure = {
|
|
|
9762
9828
|
`echo "$result" | jq -c '.data.updateIssueIssueType.issue'`
|
|
9763
9829
|
].join("\n")
|
|
9764
9830
|
};
|
|
9831
|
+
var cleanMergedBranchesProcedure = {
|
|
9832
|
+
name: "clean-merged-branches.sh",
|
|
9833
|
+
description: "Analyse local branches and classify each as MERGED, UNMERGED, EMPTY, or SKIP_WORKTREE against a base branch (default: main). Analysis-only \u2014 never deletes branches. Handles squash merges via content equality.",
|
|
9834
|
+
content: [
|
|
9835
|
+
"#!/usr/bin/env bash",
|
|
9836
|
+
"# clean-merged-branches.sh \u2014 Analyse local branches against a base.",
|
|
9837
|
+
"#",
|
|
9838
|
+
"# Reports each local branch (other than HEAD and the base branch) as",
|
|
9839
|
+
"# MERGED, UNMERGED, EMPTY, or SKIP_WORKTREE. MERGED means every file the",
|
|
9840
|
+
"# branch added or modified now matches the base \u2014 so the branch content",
|
|
9841
|
+
"# is fully on the base even when the merge was a squash.",
|
|
9842
|
+
"#",
|
|
9843
|
+
"# This procedure is analysis-only. It never runs `git branch -D`. The",
|
|
9844
|
+
"# /clean-merged-branches slash-command skill wraps it with the deletion",
|
|
9845
|
+
"# prompt; the procedure itself is safe to invoke non-interactively from",
|
|
9846
|
+
"# any agent.",
|
|
9847
|
+
"#",
|
|
9848
|
+
"# Usage:",
|
|
9849
|
+
"# .claude/procedures/clean-merged-branches.sh # default base: main",
|
|
9850
|
+
"# .claude/procedures/clean-merged-branches.sh --base develop",
|
|
9851
|
+
"#",
|
|
9852
|
+
"# Output (one line per branch, stable format for grepping):",
|
|
9853
|
+
"# MERGED <branch> files=<n>",
|
|
9854
|
+
"# UNMERGED <branch> files=<n> differs=<n>",
|
|
9855
|
+
"# EMPTY <branch>",
|
|
9856
|
+
"# SKIP_WORKTREE <branch> worktree=<path>",
|
|
9857
|
+
"#",
|
|
9858
|
+
"# Exit codes:",
|
|
9859
|
+
"# 0 \u2014 analysis completed (whether or not any MERGED branches were found)",
|
|
9860
|
+
"# 2 \u2014 argument error",
|
|
9861
|
+
"# 3 \u2014 required command not on PATH",
|
|
9862
|
+
"# 4 \u2014 not a git repository",
|
|
9863
|
+
"",
|
|
9864
|
+
"set -uo pipefail",
|
|
9865
|
+
"",
|
|
9866
|
+
"# \u2500\u2500 helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
9867
|
+
"",
|
|
9868
|
+
"err() {",
|
|
9869
|
+
' printf "clean-merged-branches.sh: %s\\n" "$*" >&2',
|
|
9870
|
+
"}",
|
|
9871
|
+
"",
|
|
9872
|
+
"usage() {",
|
|
9873
|
+
" cat >&2 <<'USAGE'",
|
|
9874
|
+
"Usage: clean-merged-branches.sh [--base <name>]",
|
|
9875
|
+
"",
|
|
9876
|
+
" --base <name> Base branch to compare against (default: main; falls",
|
|
9877
|
+
" back to whatever origin/HEAD points at if main is",
|
|
9878
|
+
" not present locally).",
|
|
9879
|
+
"",
|
|
9880
|
+
"Reports MERGED / UNMERGED / EMPTY / SKIP_WORKTREE for every local",
|
|
9881
|
+
"branch other than HEAD and the base branch. Does not delete anything.",
|
|
9882
|
+
"USAGE",
|
|
9883
|
+
"}",
|
|
9884
|
+
"",
|
|
9885
|
+
"# \u2500\u2500 argument parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
9886
|
+
"",
|
|
9887
|
+
'base=""',
|
|
9888
|
+
"while [[ $# -gt 0 ]]; do",
|
|
9889
|
+
' case "$1" in',
|
|
9890
|
+
" --base)",
|
|
9891
|
+
' if [[ $# -lt 2 || -z "${2:-}" ]]; then',
|
|
9892
|
+
' err "--base requires a value"',
|
|
9893
|
+
" usage",
|
|
9894
|
+
" exit 2",
|
|
9895
|
+
" fi",
|
|
9896
|
+
' base="$2"',
|
|
9897
|
+
" shift 2",
|
|
9898
|
+
" ;;",
|
|
9899
|
+
" -h|--help)",
|
|
9900
|
+
" usage",
|
|
9901
|
+
" exit 0",
|
|
9902
|
+
" ;;",
|
|
9903
|
+
" *)",
|
|
9904
|
+
' err "unknown argument: $1"',
|
|
9905
|
+
" usage",
|
|
9906
|
+
" exit 2",
|
|
9907
|
+
" ;;",
|
|
9908
|
+
" esac",
|
|
9909
|
+
"done",
|
|
9910
|
+
"",
|
|
9911
|
+
"# \u2500\u2500 dependency checks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
9912
|
+
"",
|
|
9913
|
+
"for cmd in git; do",
|
|
9914
|
+
' if ! command -v "$cmd" >/dev/null 2>&1; then',
|
|
9915
|
+
' err "required command not found on PATH: $cmd"',
|
|
9916
|
+
" exit 3",
|
|
9917
|
+
" fi",
|
|
9918
|
+
"done",
|
|
9919
|
+
"",
|
|
9920
|
+
"# \u2500\u2500 repo + base resolution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
9921
|
+
"",
|
|
9922
|
+
"if ! git rev-parse --git-dir >/dev/null 2>&1; then",
|
|
9923
|
+
' err "not inside a git repository"',
|
|
9924
|
+
" exit 4",
|
|
9925
|
+
"fi",
|
|
9926
|
+
"",
|
|
9927
|
+
"# Default base = main if it exists locally, otherwise whatever",
|
|
9928
|
+
"# origin/HEAD points at (e.g. master, trunk, develop).",
|
|
9929
|
+
'if [[ -z "$base" ]]; then',
|
|
9930
|
+
' if git show-ref --verify --quiet "refs/heads/main"; then',
|
|
9931
|
+
' base="main"',
|
|
9932
|
+
" else",
|
|
9933
|
+
" origin_head=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null || true)",
|
|
9934
|
+
' if [[ -n "$origin_head" ]]; then',
|
|
9935
|
+
' base="${origin_head#origin/}"',
|
|
9936
|
+
" else",
|
|
9937
|
+
' base="main"',
|
|
9938
|
+
" fi",
|
|
9939
|
+
" fi",
|
|
9940
|
+
"fi",
|
|
9941
|
+
"",
|
|
9942
|
+
"# Verify the chosen base actually exists as a local branch.",
|
|
9943
|
+
'if ! git show-ref --verify --quiet "refs/heads/$base"; then',
|
|
9944
|
+
` err "base branch '$base' does not exist locally"`,
|
|
9945
|
+
" exit 4",
|
|
9946
|
+
"fi",
|
|
9947
|
+
"",
|
|
9948
|
+
"# Refresh remote-tracking refs and prune deleted upstream branches",
|
|
9949
|
+
"# so the gone-upstream signal is fresh (best effort \u2014 never fatal).",
|
|
9950
|
+
"git fetch --prune origin >/dev/null 2>&1 || true",
|
|
9951
|
+
"",
|
|
9952
|
+
'current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")',
|
|
9953
|
+
"",
|
|
9954
|
+
"# \u2500\u2500 worktree map (branch -> worktree path, excluding the current) \u2500\u2500\u2500",
|
|
9955
|
+
"",
|
|
9956
|
+
"# Use git worktree list --porcelain to detect any branch checked out in",
|
|
9957
|
+
"# a separate worktree. We must skip those because `git branch -D` would",
|
|
9958
|
+
"# refuse to delete a branch that is checked out elsewhere.",
|
|
9959
|
+
"#",
|
|
9960
|
+
"# We use parallel arrays (not an associative array) to stay compatible",
|
|
9961
|
+
"# with bash 3 \u2014 macOS still ships bash 3.2 as /bin/bash, and the script",
|
|
9962
|
+
"# is invoked via #!/usr/bin/env bash which picks whichever bash is",
|
|
9963
|
+
"# first on PATH. Lookups are O(n) but n is tiny (worktree count).",
|
|
9964
|
+
"worktree_branches=()",
|
|
9965
|
+
"worktree_paths=()",
|
|
9966
|
+
'cwd_top=$(git rev-parse --show-toplevel 2>/dev/null || echo "")',
|
|
9967
|
+
'wt_path=""',
|
|
9968
|
+
"while IFS= read -r line; do",
|
|
9969
|
+
' case "$line" in',
|
|
9970
|
+
" worktree\\ *)",
|
|
9971
|
+
' wt_path="${line#worktree }"',
|
|
9972
|
+
" ;;",
|
|
9973
|
+
" branch\\ *)",
|
|
9974
|
+
' wt_branch="${line#branch refs/heads/}"',
|
|
9975
|
+
" # Skip the worktree that matches the current top-level \u2014 that one",
|
|
9976
|
+
" # is the *current* checkout; skipping HEAD is handled separately.",
|
|
9977
|
+
' if [[ -n "$wt_path" && "$wt_path" != "$cwd_top" ]]; then',
|
|
9978
|
+
' worktree_branches+=("$wt_branch")',
|
|
9979
|
+
' worktree_paths+=("$wt_path")',
|
|
9980
|
+
" fi",
|
|
9981
|
+
' wt_path=""',
|
|
9982
|
+
" ;;",
|
|
9983
|
+
' "")',
|
|
9984
|
+
' wt_path=""',
|
|
9985
|
+
" ;;",
|
|
9986
|
+
" esac",
|
|
9987
|
+
"done < <(git worktree list --porcelain 2>/dev/null)",
|
|
9988
|
+
"",
|
|
9989
|
+
"# Echo the worktree path for the given branch, or empty string if the",
|
|
9990
|
+
"# branch is not checked out in another worktree.",
|
|
9991
|
+
"lookup_worktree() {",
|
|
9992
|
+
' local needle="$1"',
|
|
9993
|
+
" local i",
|
|
9994
|
+
' for i in "${!worktree_branches[@]}"; do',
|
|
9995
|
+
' if [[ "${worktree_branches[$i]}" == "$needle" ]]; then',
|
|
9996
|
+
` printf '%s\\n' "\${worktree_paths[$i]}"`,
|
|
9997
|
+
" return 0",
|
|
9998
|
+
" fi",
|
|
9999
|
+
" done",
|
|
10000
|
+
" return 0",
|
|
10001
|
+
"}",
|
|
10002
|
+
"",
|
|
10003
|
+
"# \u2500\u2500 walk local branches \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
10004
|
+
"",
|
|
10005
|
+
'echo "Analysing local branches against ${base}\u2026"',
|
|
10006
|
+
"echo",
|
|
10007
|
+
"",
|
|
10008
|
+
"while IFS= read -r branch; do",
|
|
10009
|
+
' [[ -z "$branch" ]] && continue',
|
|
10010
|
+
' [[ "$branch" == "$base" ]] && continue',
|
|
10011
|
+
' [[ "$branch" == "$current" ]] && continue',
|
|
10012
|
+
"",
|
|
10013
|
+
' wt=$(lookup_worktree "$branch")',
|
|
10014
|
+
' if [[ -n "$wt" ]]; then',
|
|
10015
|
+
` printf 'SKIP_WORKTREE %s worktree=%s\\n' "$branch" "$wt"`,
|
|
10016
|
+
" continue",
|
|
10017
|
+
" fi",
|
|
10018
|
+
"",
|
|
10019
|
+
' mb=$(git merge-base "$base" "$branch" 2>/dev/null || true)',
|
|
10020
|
+
' if [[ -z "$mb" ]]; then',
|
|
10021
|
+
" # No common ancestor with base \u2014 treat as UNMERGED (don't risk it).",
|
|
10022
|
+
` printf 'UNMERGED %s files=? differs=?\\n' "$branch"`,
|
|
10023
|
+
" continue",
|
|
10024
|
+
" fi",
|
|
10025
|
+
"",
|
|
10026
|
+
' files=$(git diff --name-only "$mb" "$branch" 2>/dev/null || true)',
|
|
10027
|
+
' if [[ -z "$files" ]]; then',
|
|
10028
|
+
` printf 'EMPTY %s\\n' "$branch"`,
|
|
10029
|
+
" continue",
|
|
10030
|
+
" fi",
|
|
10031
|
+
"",
|
|
10032
|
+
" # Count how many of those files still differ from the base today.",
|
|
10033
|
+
" # We use NUL-delimited git output to survive filenames with spaces.",
|
|
10034
|
+
" diff_count=0",
|
|
10035
|
+
" while IFS= read -r -d '' f; do",
|
|
10036
|
+
" diff_count=$((diff_count + 1))",
|
|
10037
|
+
' done < <(git diff -z --name-only "$branch" "$base" -- $files 2>/dev/null)',
|
|
10038
|
+
"",
|
|
10039
|
+
` total=$(echo "$files" | wc -l | tr -d ' ')`,
|
|
10040
|
+
"",
|
|
10041
|
+
' if [[ "$diff_count" -eq 0 ]]; then',
|
|
10042
|
+
` printf 'MERGED %s files=%s\\n' "$branch" "$total"`,
|
|
10043
|
+
" else",
|
|
10044
|
+
` printf 'UNMERGED %s files=%s differs=%s\\n' "$branch" "$total" "$diff_count"`,
|
|
10045
|
+
" fi",
|
|
10046
|
+
"done < <(git branch --format='%(refname:short)')",
|
|
10047
|
+
"",
|
|
10048
|
+
"exit 0"
|
|
10049
|
+
].join("\n")
|
|
10050
|
+
};
|
|
10051
|
+
var cleanMergedBranchesSkill = {
|
|
10052
|
+
name: "clean-merged-branches",
|
|
10053
|
+
description: "Report every local branch as MERGED or UNMERGED against the base (handles squash merges via content equality), then prompt the user to force-delete the MERGED list. Defaults base to main; --base overrides. Skips current branch, base branch, and any branch checked out in another worktree.",
|
|
10054
|
+
disableModelInvocation: true,
|
|
10055
|
+
userInvocable: true,
|
|
10056
|
+
platforms: { cursor: { exclude: true } },
|
|
10057
|
+
instructions: [
|
|
10058
|
+
"# Clean Merged Branches",
|
|
10059
|
+
"",
|
|
10060
|
+
"Identify local branches whose content is fully on the base branch \u2014",
|
|
10061
|
+
"including branches that were squash-merged (where `git branch -d`",
|
|
10062
|
+
"refuses because the branch commits are not ancestors of the base) \u2014",
|
|
10063
|
+
"report them to the user, and force-delete them after explicit",
|
|
10064
|
+
"confirmation.",
|
|
10065
|
+
"",
|
|
10066
|
+
"## Usage",
|
|
10067
|
+
"",
|
|
10068
|
+
"```",
|
|
10069
|
+
"/clean-merged-branches # default base: main",
|
|
10070
|
+
"/clean-merged-branches --base develop # custom base branch",
|
|
10071
|
+
"```",
|
|
10072
|
+
"",
|
|
10073
|
+
"### Flags",
|
|
10074
|
+
"",
|
|
10075
|
+
"- **`--base <name>`** \u2014 base branch to compare against. Defaults to",
|
|
10076
|
+
" `main`; falls back to whatever `origin/HEAD` points at if `main`",
|
|
10077
|
+
" does not exist locally.",
|
|
10078
|
+
"",
|
|
10079
|
+
"## What This Skill Does",
|
|
10080
|
+
"",
|
|
10081
|
+
"1. **Analyse.** Runs `.claude/procedures/clean-merged-branches.sh`",
|
|
10082
|
+
" (passing through any `--base` flag). The procedure walks every",
|
|
10083
|
+
" local branch and prints one stable log line per branch:",
|
|
10084
|
+
" - `MERGED <branch> files=<n>` \u2014 every file the branch added",
|
|
10085
|
+
" or modified now matches the base. Safe to delete.",
|
|
10086
|
+
" - `UNMERGED <branch> files=<n> differs=<n>` \u2014 branch content",
|
|
10087
|
+
" still differs from the base. Do NOT delete.",
|
|
10088
|
+
" - `EMPTY <branch>` \u2014 branch has no file changes vs. its",
|
|
10089
|
+
" merge-base. Safe to delete.",
|
|
10090
|
+
" - `SKIP_WORKTREE <branch> worktree=<path>` \u2014 branch is checked",
|
|
10091
|
+
" out in another worktree. Skipped (cannot be deleted while it's",
|
|
10092
|
+
" in use).",
|
|
10093
|
+
"",
|
|
10094
|
+
"2. **Confirm.** If the report turns up at least one `MERGED` (or",
|
|
10095
|
+
" `EMPTY`) branch, print the exact deletion list and a single",
|
|
10096
|
+
" `[y/N]` prompt. Empty input defaults to **no** and aborts.",
|
|
10097
|
+
"",
|
|
10098
|
+
"3. **Delete.** On explicit `y` / `Y`, run `git branch -D <branch>`",
|
|
10099
|
+
" for each confirmed branch and emit one `DELETED <branch>` line",
|
|
10100
|
+
" per success. On any other answer (including empty input),",
|
|
10101
|
+
" abort with `Aborted. No branches deleted.` and exit cleanly.",
|
|
10102
|
+
"",
|
|
10103
|
+
"If the report finds zero deletable branches, exit cleanly without",
|
|
10104
|
+
"prompting \u2014 there is nothing to confirm.",
|
|
10105
|
+
"",
|
|
10106
|
+
"## Behaviour Guarantees",
|
|
10107
|
+
"",
|
|
10108
|
+
"- **Always skip the current branch and the base branch.** The",
|
|
10109
|
+
" procedure enforces this; the skill never needs to filter again.",
|
|
10110
|
+
"- **Never delete without an explicit `y`.** The default on empty",
|
|
10111
|
+
" input is no. The skill never runs `git branch -D` until after",
|
|
10112
|
+
" the prompt returns `y` or `Y`.",
|
|
10113
|
+
"- **Content equality is the proof.** A branch classifies as",
|
|
10114
|
+
" `MERGED` when every file it added or modified matches the base",
|
|
10115
|
+
" today \u2014 handles squash merges, file deletions, and renames.",
|
|
10116
|
+
" The `[origin/<branch>: gone]` indicator is a useful secondary",
|
|
10117
|
+
" signal but is NOT the deletion gate.",
|
|
10118
|
+
"- **Safe across worktrees.** Branches checked out in another",
|
|
10119
|
+
" worktree are logged as `SKIP_WORKTREE` and excluded from the",
|
|
10120
|
+
" deletion list.",
|
|
10121
|
+
"",
|
|
10122
|
+
"## Output",
|
|
10123
|
+
"",
|
|
10124
|
+
"The procedure's per-branch log lines first, then (if any deletable",
|
|
10125
|
+
"branches exist) the confirmation prompt, then one `DELETED <branch>`",
|
|
10126
|
+
"line per successful deletion or `Aborted. No branches deleted.`",
|
|
10127
|
+
"on abort.",
|
|
10128
|
+
"",
|
|
10129
|
+
"## Implementation Recipe",
|
|
10130
|
+
"",
|
|
10131
|
+
"```bash",
|
|
10132
|
+
"# 1. Run the analysis-only procedure and capture its output.",
|
|
10133
|
+
'report=$(.claude/procedures/clean-merged-branches.sh "$@")',
|
|
10134
|
+
'printf "%s\\n" "$report"',
|
|
10135
|
+
"",
|
|
10136
|
+
"# 2. Extract the MERGED + EMPTY branch names from the report.",
|
|
10137
|
+
"mergeable=()",
|
|
10138
|
+
"while IFS= read -r line; do",
|
|
10139
|
+
' case "$line" in',
|
|
10140
|
+
' "MERGED "*|"EMPTY "*)',
|
|
10141
|
+
" # The branch name is the second whitespace-delimited token.",
|
|
10142
|
+
` branch=$(echo "$line" | awk '{print $2}')`,
|
|
10143
|
+
' [[ -n "$branch" ]] && mergeable+=("$branch")',
|
|
10144
|
+
" ;;",
|
|
10145
|
+
" esac",
|
|
10146
|
+
'done <<< "$report"',
|
|
10147
|
+
"",
|
|
10148
|
+
"# 3. If nothing to delete, exit cleanly without prompting.",
|
|
10149
|
+
"if [[ ${#mergeable[@]} -eq 0 ]]; then",
|
|
10150
|
+
" echo",
|
|
10151
|
+
' echo "No merged branches to delete."',
|
|
10152
|
+
" exit 0",
|
|
10153
|
+
"fi",
|
|
10154
|
+
"",
|
|
10155
|
+
"# 4. Show the list and prompt the user once.",
|
|
10156
|
+
"echo",
|
|
10157
|
+
'echo "The following ${#mergeable[@]} branches are safe to delete:"',
|
|
10158
|
+
`printf ' %s\\n' "\${mergeable[@]}"`,
|
|
10159
|
+
"echo",
|
|
10160
|
+
'read -r -p "Force-delete all ${#mergeable[@]} with git branch -D? [y/N] " answer',
|
|
10161
|
+
"",
|
|
10162
|
+
"# 5. On explicit y/Y only, delete each branch.",
|
|
10163
|
+
'if [[ "$answer" == "y" || "$answer" == "Y" ]]; then',
|
|
10164
|
+
' for b in "${mergeable[@]}"; do',
|
|
10165
|
+
' if git branch -D "$b" >/dev/null 2>&1; then',
|
|
10166
|
+
' echo "DELETED $b"',
|
|
10167
|
+
" else",
|
|
10168
|
+
' echo "DELETE_FAILED $b" >&2',
|
|
10169
|
+
" fi",
|
|
10170
|
+
" done",
|
|
10171
|
+
"else",
|
|
10172
|
+
' echo "Aborted. No branches deleted."',
|
|
10173
|
+
"fi",
|
|
10174
|
+
"```",
|
|
10175
|
+
"",
|
|
10176
|
+
"## Composability",
|
|
10177
|
+
"",
|
|
10178
|
+
"The procedure (`.claude/procedures/clean-merged-branches.sh`) is",
|
|
10179
|
+
"analysis-only and safe to invoke from any agent. The skill is the",
|
|
10180
|
+
"interactive entry point \u2014 use it when a human is at the keyboard.",
|
|
10181
|
+
"Background workers (orchestrator, maintenance-audit) should call",
|
|
10182
|
+
"the procedure directly and report its output without acting on it."
|
|
10183
|
+
].join("\n"),
|
|
10184
|
+
referenceFiles: [
|
|
10185
|
+
{
|
|
10186
|
+
path: "evals/evals.json",
|
|
10187
|
+
content: JSON.stringify(
|
|
10188
|
+
{
|
|
10189
|
+
skill_name: "clean-merged-branches",
|
|
10190
|
+
evals: [
|
|
10191
|
+
{
|
|
10192
|
+
id: 1,
|
|
10193
|
+
prompt: "/clean-merged-branches",
|
|
10194
|
+
expected_output: "The skill runs the analysis-only `clean-merged-branches.sh` procedure against the current checkout's base branch (default: `main`). Each local branch other than HEAD and `main` is reported on one stable line as `MERGED`, `UNMERGED`, `EMPTY`, or `SKIP_WORKTREE` with a file count. When at least one branch classifies as `MERGED` or `EMPTY`, the skill prints the exact deletion list and prompts `Force-delete all <n> with git branch -D? [y/N]`. On explicit `y` / `Y` it force-deletes each branch with `git branch -D` and emits one `DELETED <branch>` line per success. Empty input or any other answer aborts with `Aborted. No branches deleted.` and no branches are deleted. When zero branches are deletable, the skill exits cleanly without prompting.",
|
|
10195
|
+
files: [],
|
|
10196
|
+
product_context_refs: []
|
|
10197
|
+
},
|
|
10198
|
+
{
|
|
10199
|
+
id: 2,
|
|
10200
|
+
prompt: "/clean-merged-branches --base develop",
|
|
10201
|
+
expected_output: "The skill forwards `--base develop` to the procedure, which compares every local branch (other than HEAD and `develop`) against `develop` instead of `main`. Branches whose content matches `develop` classify as `MERGED`; others as `UNMERGED`. The base-branch override is also reflected in the deletion prompt (`...with git branch -D?`), and the skill never deletes `develop` itself or the current HEAD. If `develop` does not exist as a local branch, the procedure exits non-zero with a clear diagnostic and the skill aborts without prompting.",
|
|
10202
|
+
files: [],
|
|
10203
|
+
product_context_refs: []
|
|
10204
|
+
},
|
|
10205
|
+
{
|
|
10206
|
+
id: 3,
|
|
10207
|
+
prompt: "/clean-merged-branches \u2014 I have a branch called feat/old-feature that I checked out in a sibling worktree under /tmp/work. Confirm it's skipped.",
|
|
10208
|
+
expected_output: "The procedure detects `feat/old-feature` via `git worktree list --porcelain` and reports `SKIP_WORKTREE feat/old-feature worktree=/tmp/work` instead of classifying it. The branch is excluded from the deletion list shown at the confirmation prompt. Even if the user answers `y`, the skill never runs `git branch -D feat/old-feature` because the branch is not in the mergeable list.",
|
|
10209
|
+
files: [],
|
|
10210
|
+
product_context_refs: []
|
|
10211
|
+
}
|
|
10212
|
+
]
|
|
10213
|
+
},
|
|
10214
|
+
null,
|
|
10215
|
+
2
|
|
10216
|
+
)
|
|
10217
|
+
}
|
|
10218
|
+
]
|
|
10219
|
+
};
|
|
9765
10220
|
var githubWorkflowBundle = {
|
|
9766
10221
|
name: "github-workflow",
|
|
9767
10222
|
description: "GitHub issue and PR workflow automation patterns",
|
|
@@ -9783,6 +10238,10 @@ var githubWorkflowBundle = {
|
|
|
9783
10238
|
{
|
|
9784
10239
|
name: "create-issue-workflow",
|
|
9785
10240
|
description: "Automated workflow for creating a new GitHub issue",
|
|
10241
|
+
// ALWAYS scope: users invoke "create an issue" from any
|
|
10242
|
+
// context, not only when editing agent / skill / bundle source.
|
|
10243
|
+
// Consumers that want to narrow the load can override via
|
|
10244
|
+
// `agentConfig.additionalRulePaths` or `excludeRules`.
|
|
9786
10245
|
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
9787
10246
|
content: [
|
|
9788
10247
|
"# Create Issue Workflow",
|
|
@@ -9909,9 +10368,36 @@ var githubWorkflowBundle = {
|
|
|
9909
10368
|
"- Delegate merge to the `pr-reviewer` sub-agent \u2014 do not merge manually and do not enable auto-merge directly"
|
|
9910
10369
|
].join("\n"),
|
|
9911
10370
|
tags: ["workflow"]
|
|
10371
|
+
},
|
|
10372
|
+
{
|
|
10373
|
+
name: "branch-cleanup",
|
|
10374
|
+
description: "Local-branch hygiene helpers shipped with the github-workflow bundle, including the /clean-merged-branches skill for safely force-deleting branches whose content has already merged into the base (handles squash merges).",
|
|
10375
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
10376
|
+
content: [
|
|
10377
|
+
"# Branch Cleanup",
|
|
10378
|
+
"",
|
|
10379
|
+
"Local branches accumulate after every merged PR. In squash-merge",
|
|
10380
|
+
"repositories `git branch -d` refuses to delete them because the",
|
|
10381
|
+
"commit hash on the base differs, even when the branch content is",
|
|
10382
|
+
"fully merged. The `github-workflow` bundle ships two affordances",
|
|
10383
|
+
"that use content-equality (not commit-graph reachability) to",
|
|
10384
|
+
"identify branches safe to force-delete:",
|
|
10385
|
+
"",
|
|
10386
|
+
"- `/clean-merged-branches` \u2014 interactive slash-command skill that",
|
|
10387
|
+
" classifies every local branch, prompts for confirmation, then",
|
|
10388
|
+
" runs `git branch -D` on the confirmed list. See",
|
|
10389
|
+
" `.claude/skills/clean-merged-branches/SKILL.md` for usage,",
|
|
10390
|
+
" output format, and the squash-merge verification algorithm.",
|
|
10391
|
+
"- `.claude/procedures/clean-merged-branches.sh` \u2014 analysis-only",
|
|
10392
|
+
" procedure for non-interactive agent use (orchestrator,",
|
|
10393
|
+
" maintenance-audit). NEVER deletes \u2014 only reports `MERGED` /",
|
|
10394
|
+
" `UNMERGED` / `EMPTY` / `SKIP_WORKTREE` lines."
|
|
10395
|
+
].join("\n"),
|
|
10396
|
+
tags: ["workflow"]
|
|
9912
10397
|
}
|
|
9913
10398
|
],
|
|
9914
|
-
|
|
10399
|
+
skills: [cleanMergedBranchesSkill],
|
|
10400
|
+
procedures: [setIssueTypeProcedure, cleanMergedBranchesProcedure]
|
|
9915
10401
|
};
|
|
9916
10402
|
|
|
9917
10403
|
// src/agent/bundles/industry-discovery.ts
|
|
@@ -11548,12 +12034,14 @@ function buildMeetingAnalystSubAgent(tier) {
|
|
|
11548
12034
|
" Interest (signal threshold still applies); capture customer",
|
|
11549
12035
|
" pain points as candidate BR (not FR).",
|
|
11550
12036
|
"",
|
|
11551
|
-
"5. **
|
|
11552
|
-
" `<meetingsRoot>/insights/index.md` and",
|
|
12037
|
+
"5. **Plan the `insights/` tree index update \u2014 do not commit it",
|
|
12038
|
+
" yet.** Ensure `<meetingsRoot>/insights/index.md` and",
|
|
11553
12039
|
" `<meetingsRoot>/insights/{type}/index.md` exist (create them",
|
|
11554
|
-
" following the `section-index` convention if missing)
|
|
11555
|
-
"
|
|
11556
|
-
"
|
|
12040
|
+
" following the `section-index` convention if missing). Note",
|
|
12041
|
+
" the basename row this phase will eventually append, but",
|
|
12042
|
+
" **defer** the row insert and its commit to step 8 below so",
|
|
12043
|
+
" the shared-index commit can land in the smallest possible",
|
|
12044
|
+
" window before push.",
|
|
11557
12045
|
"",
|
|
11558
12046
|
"6. **Create downstream phase issues** using `gh issue create`:",
|
|
11559
12047
|
" - Always create a `meeting:notes` issue (blocked on this extract issue)",
|
|
@@ -11564,7 +12052,35 @@ function buildMeetingAnalystSubAgent(tier) {
|
|
|
11564
12052
|
" - Always create a `meeting:link` issue \u2014 blocked on the draft issue if one",
|
|
11565
12053
|
" was created, otherwise blocked on the notes issue",
|
|
11566
12054
|
"",
|
|
11567
|
-
"7. Commit
|
|
12055
|
+
"7. **Commit the extraction content first.** Stage and commit",
|
|
12056
|
+
" the `insights/{type}/<basename>.md` file (and any newly",
|
|
12057
|
+
" created `insights/index.md` / `insights/{type}/index.md`",
|
|
12058
|
+
" stub pages from step 5 that do not yet exist on the remote)",
|
|
12059
|
+
" in a single focused commit. **Do not push yet.**",
|
|
12060
|
+
"",
|
|
12061
|
+
"8. **Defer the shared-index row insert to a final pre-push",
|
|
12062
|
+
" commit.** Per the `shared-editing-safety` rule's",
|
|
12063
|
+
" **Defer Shared-Index Commit to Final Pre-Push Step**",
|
|
12064
|
+
" subsection, the index update for an existing",
|
|
12065
|
+
" `insights/{type}/index.md` is a **shared-index row insert**",
|
|
12066
|
+
" that races other parallel meeting sessions writing rows into",
|
|
12067
|
+
" the same partition file. Apply the deferred sequence:",
|
|
12068
|
+
"",
|
|
12069
|
+
" ```bash",
|
|
12070
|
+
" git fetch origin",
|
|
12071
|
+
" git pull --rebase origin <default-branch>",
|
|
12072
|
+
" ```",
|
|
12073
|
+
"",
|
|
12074
|
+
" Re-read `insights/{type}/index.md` from the now-up-to-date",
|
|
12075
|
+
" working tree, re-compute the alphabetical insert position",
|
|
12076
|
+
" for the current meeting's basename row (another session may",
|
|
12077
|
+
" have appended a sibling row in the meantime), insert exactly",
|
|
12078
|
+
" one row, and commit the index edit in its **own focused",
|
|
12079
|
+
" commit** whose only file is the shared index. Run the",
|
|
12080
|
+
" commit-path verification step (`git show HEAD:<index-path>`",
|
|
12081
|
+
" + grep count) against that commit before pushing.",
|
|
12082
|
+
"",
|
|
12083
|
+
"9. **Push and close.** Push the branch and close the extract issue.",
|
|
11568
12084
|
"",
|
|
11569
12085
|
"---",
|
|
11570
12086
|
"",
|
|
@@ -11624,13 +12140,41 @@ function buildMeetingAnalystSubAgent(tier) {
|
|
|
11624
12140
|
" - Action Items (table: who, what, when)",
|
|
11625
12141
|
" - Open Questions",
|
|
11626
12142
|
" - Follow-up items",
|
|
11627
|
-
"4. **
|
|
11628
|
-
" `<meetingsRoot>/notes/index.md` and",
|
|
12143
|
+
"4. **Plan the `notes/` tree index update \u2014 do not commit it",
|
|
12144
|
+
" yet.** Ensure `<meetingsRoot>/notes/index.md` and",
|
|
11629
12145
|
" `<meetingsRoot>/notes/{type}/index.md` exist (create them",
|
|
11630
|
-
" following the `section-index` convention if missing)
|
|
11631
|
-
"
|
|
11632
|
-
"
|
|
11633
|
-
"
|
|
12146
|
+
" following the `section-index` convention if missing). Note",
|
|
12147
|
+
" the basename row this phase will eventually append, but",
|
|
12148
|
+
" **defer** the row insert and its commit to step 6 below so",
|
|
12149
|
+
" the shared-index commit can land in the smallest possible",
|
|
12150
|
+
" window before push.",
|
|
12151
|
+
"5. **Commit the notes content first.** Stage and commit the",
|
|
12152
|
+
" `notes/{type}/<basename>.md` file (and any newly created",
|
|
12153
|
+
" `notes/index.md` / `notes/{type}/index.md` stub pages from",
|
|
12154
|
+
" step 4 that do not yet exist on the remote) in a single",
|
|
12155
|
+
" focused commit. **Do not push yet.**",
|
|
12156
|
+
"6. **Defer the shared-index row insert to a final pre-push",
|
|
12157
|
+
" commit.** Per the `shared-editing-safety` rule's",
|
|
12158
|
+
" **Defer Shared-Index Commit to Final Pre-Push Step**",
|
|
12159
|
+
" subsection, the index update for an existing",
|
|
12160
|
+
" `notes/{type}/index.md` is a **shared-index row insert**",
|
|
12161
|
+
" that races other parallel meeting sessions writing rows",
|
|
12162
|
+
" into the same partition file. Apply the deferred sequence:",
|
|
12163
|
+
"",
|
|
12164
|
+
" ```bash",
|
|
12165
|
+
" git fetch origin",
|
|
12166
|
+
" git pull --rebase origin <default-branch>",
|
|
12167
|
+
" ```",
|
|
12168
|
+
"",
|
|
12169
|
+
" Re-read `notes/{type}/index.md` from the now-up-to-date",
|
|
12170
|
+
" working tree, re-compute the alphabetical insert position",
|
|
12171
|
+
" for the current meeting's basename row (another session may",
|
|
12172
|
+
" have appended a sibling row in the meantime), insert exactly",
|
|
12173
|
+
" one row, and commit the index edit in its **own focused",
|
|
12174
|
+
" commit** whose only file is the shared index. Run the",
|
|
12175
|
+
" commit-path verification step against that commit before",
|
|
12176
|
+
" pushing.",
|
|
12177
|
+
"7. Push and close the notes issue.",
|
|
11634
12178
|
"",
|
|
11635
12179
|
"---",
|
|
11636
12180
|
"",
|
|
@@ -15272,12 +15816,25 @@ var issueWorkerSubAgent = {
|
|
|
15272
15816
|
" `file` (and optional `line`). Track `comment_id` per item so you can",
|
|
15273
15817
|
" report which items were handled and which (if any) failed.",
|
|
15274
15818
|
"",
|
|
15275
|
-
" **Synthetic rebase items.**
|
|
15276
|
-
"
|
|
15277
|
-
"
|
|
15278
|
-
"
|
|
15279
|
-
"
|
|
15280
|
-
"
|
|
15819
|
+
" **Synthetic rebase items.** Two `comment_id` values flag the",
|
|
15820
|
+
" reviewer's signal that the PR's head branch is BEHIND the default",
|
|
15821
|
+
" branch with merge conflicts that `gh pr update-branch` could not",
|
|
15822
|
+
" resolve. For either item the work is **not** an editorial change",
|
|
15823
|
+
" \u2014 it is a rebase plus conflict resolution. The two ids select",
|
|
15824
|
+
" different recipes:",
|
|
15825
|
+
"",
|
|
15826
|
+
" - `synthetic:rebase-behind-main` \u2014 generic conflict. Resolve",
|
|
15827
|
+
" each conflicting file by hand (read both sides, reconcile,",
|
|
15828
|
+
" stage), then `git rebase --continue` until the rebase",
|
|
15829
|
+
" completes.",
|
|
15830
|
+
" - `synthetic:rebase-shared-index` \u2014 every conflicting file is a",
|
|
15831
|
+
" row-insert race on a shared-index file (registry / index /",
|
|
15832
|
+
" feature-matrix under a Starlight content root). Apply the",
|
|
15833
|
+
" explicit re-insert recipe carried in the item's `instruction`",
|
|
15834
|
+
" field \u2014 do **not** hand-merge.",
|
|
15835
|
+
"",
|
|
15836
|
+
" **For `synthetic:rebase-behind-main`.** Run the following",
|
|
15837
|
+
" sequence:",
|
|
15281
15838
|
"",
|
|
15282
15839
|
" ```bash",
|
|
15283
15840
|
" git fetch origin",
|
|
@@ -15301,6 +15858,75 @@ var issueWorkerSubAgent = {
|
|
|
15301
15858
|
" history. Push with a regular non-force `git push origin <branch>`",
|
|
15302
15859
|
" and report the rebased head SHA as the worker's commit.",
|
|
15303
15860
|
"",
|
|
15861
|
+
" **For `synthetic:rebase-shared-index`.** Apply the typed",
|
|
15862
|
+
" re-insert recipe step-by-step. Read the recipe verbatim from the",
|
|
15863
|
+
" item's `instruction` field; the steps below summarise the",
|
|
15864
|
+
" contract the reviewer encodes and the precondition guards you",
|
|
15865
|
+
" must enforce:",
|
|
15866
|
+
"",
|
|
15867
|
+
" 1. **Pull and rebase** onto the default branch:",
|
|
15868
|
+
"",
|
|
15869
|
+
" ```bash",
|
|
15870
|
+
" git fetch origin",
|
|
15871
|
+
" git pull --rebase origin {{repository.defaultBranch}}",
|
|
15872
|
+
" ```",
|
|
15873
|
+
"",
|
|
15874
|
+
" 2. **For each conflicting file**, inspect the conflict markers",
|
|
15875
|
+
" against the shared-index glob set and the precondition",
|
|
15876
|
+
" guards. The shared-index globs (sourced from the",
|
|
15877
|
+
" `shared-editing-safety` rule) are:",
|
|
15878
|
+
"",
|
|
15879
|
+
...DEFAULT_SHARED_INDEX_PATHS.map((p) => ` - \`${p}\``),
|
|
15880
|
+
"",
|
|
15881
|
+
" **Precondition guards.** Before re-inserting, confirm every",
|
|
15882
|
+
" `<<<<<<<` / `=======` / `>>>>>>>` hunk in the file touches",
|
|
15883
|
+
" only data rows (lines starting with `| ` that are not the",
|
|
15884
|
+
" table header or `|---|---|` separator). If any hunk touches",
|
|
15885
|
+
" the frontmatter (lines between the opening / closing `---`",
|
|
15886
|
+
" fences), the page H1, surrounding prose paragraphs, the",
|
|
15887
|
+
" table header row, or the separator row, **stop**, run",
|
|
15888
|
+
" `git rebase --abort`, and record the item as `failed` with",
|
|
15889
|
+
" the structured marker `BLOCKED <reason>` (e.g.",
|
|
15890
|
+
" `BLOCKED conflict touches table header in <path>`). Do not",
|
|
15891
|
+
" hand-merge \u2014 the typed recipe applies only to pure",
|
|
15892
|
+
" row-insert races.",
|
|
15893
|
+
"",
|
|
15894
|
+
" When the guards pass, read the rebased version of the file",
|
|
15895
|
+
" (it now contains the other PR's row), extract this PR's row",
|
|
15896
|
+
" from the `<<<<<<<` side of the conflict markers, re-insert",
|
|
15897
|
+
" that row in declared sort order using the file's documented",
|
|
15898
|
+
" sort key (alphabetical on the first column by default), and",
|
|
15899
|
+
" stage the file:",
|
|
15900
|
+
"",
|
|
15901
|
+
" ```bash",
|
|
15902
|
+
" git add <path>",
|
|
15903
|
+
" ```",
|
|
15904
|
+
"",
|
|
15905
|
+
" 3. **Run the commit-path verification** for each re-inserted",
|
|
15906
|
+
" row. The marker must appear exactly once in the staged file",
|
|
15907
|
+
" (zero means missing, more than one means duplicated by a",
|
|
15908
|
+
" mis-merge):",
|
|
15909
|
+
"",
|
|
15910
|
+
" ```bash",
|
|
15911
|
+
' count=$(git show :<path> | grep -Fc "<row-unique-marker>")',
|
|
15912
|
+
' [ "$count" = "1" ] || { echo "BLOCKED verification failed for <path>"; exit 1; }',
|
|
15913
|
+
" ```",
|
|
15914
|
+
"",
|
|
15915
|
+
" 4. **Continue the rebase and push** with a non-force push once",
|
|
15916
|
+
" every file is staged and verified:",
|
|
15917
|
+
"",
|
|
15918
|
+
" ```bash",
|
|
15919
|
+
" git rebase --continue",
|
|
15920
|
+
" git push origin <branch>",
|
|
15921
|
+
" ```",
|
|
15922
|
+
"",
|
|
15923
|
+
" On any `BLOCKED` precondition failure, exit non-zero with the",
|
|
15924
|
+
" `BLOCKED <reason>` line, run `git rebase --abort`, record the",
|
|
15925
|
+
" item as `failed`, and proceed to the report step. The reviewer",
|
|
15926
|
+
" will see the `failed` outcome on the next pass and fall through",
|
|
15927
|
+
" to the human-required hand-off via `review:awaiting-human`. Do",
|
|
15928
|
+
" not force-push under any circumstance.",
|
|
15929
|
+
"",
|
|
15304
15930
|
"4. When complete, prepare a short structured report (PR number, commit",
|
|
15305
15931
|
" SHAs you will push, items handled by `comment_id`, items that failed",
|
|
15306
15932
|
" to apply) \u2014 you will return this after Phase 6.",
|
|
@@ -15364,11 +15990,11 @@ var issueWorkerSubAgent = {
|
|
|
15364
15990
|
"",
|
|
15365
15991
|
"**Synthetic-rebase items skip the `fix(review)` commit.** When the",
|
|
15366
15992
|
"fix-list contained a `comment_id` of `synthetic:rebase-behind-main`",
|
|
15367
|
-
"and you completed the rebase in
|
|
15368
|
-
"
|
|
15369
|
-
"wrap in a `fix(review)`
|
|
15370
|
-
"`git
|
|
15371
|
-
"directly:",
|
|
15993
|
+
"or `synthetic:rebase-shared-index` and you completed the rebase in",
|
|
15994
|
+
"Phase 4, the rebased commit history is itself the deliverable \u2014",
|
|
15995
|
+
"there is no per-item editorial change to wrap in a `fix(review)`",
|
|
15996
|
+
"commit. Skip `git add` / `git commit` / `git pull --rebase` for",
|
|
15997
|
+
"that item and push the rebased branch directly:",
|
|
15372
15998
|
"",
|
|
15373
15999
|
"```bash",
|
|
15374
16000
|
"git push origin <branch-name>",
|
|
@@ -15646,7 +16272,15 @@ var orchestratorBundle = {
|
|
|
15646
16272
|
{
|
|
15647
16273
|
name: "orchestrator-conventions",
|
|
15648
16274
|
description: "Guidelines for orchestrator agent behavior and pipeline management, including the funnel-tier dispatch sort, scope gate, and per-agent scheduled-task layout",
|
|
15649
|
-
scope: AGENT_RULE_SCOPE.
|
|
16275
|
+
scope: AGENT_RULE_SCOPE.FILE_PATTERN,
|
|
16276
|
+
// Bundle defaults exclude paths into configulator's own source
|
|
16277
|
+
// (only meaningful when configulator is a workspace package).
|
|
16278
|
+
// codedrifters/packages restores them via
|
|
16279
|
+
// `agentConfig.additionalRulePaths`.
|
|
16280
|
+
filePatterns: [
|
|
16281
|
+
".claude/agents/orchestrator.md",
|
|
16282
|
+
".claude/scheduled-tasks/**"
|
|
16283
|
+
],
|
|
15650
16284
|
content: buildOrchestratorConventionsContent(
|
|
15651
16285
|
DEFAULT_AGENT_TIERS,
|
|
15652
16286
|
resolveScopeGate(),
|
|
@@ -15655,7 +16289,6 @@ var orchestratorBundle = {
|
|
|
15655
16289
|
resolveUnblockDependents()
|
|
15656
16290
|
),
|
|
15657
16291
|
platforms: {
|
|
15658
|
-
claude: { target: "claude-md" },
|
|
15659
16292
|
cursor: { exclude: true }
|
|
15660
16293
|
},
|
|
15661
16294
|
tags: ["workflow"]
|
|
@@ -16779,6 +17412,32 @@ var pnpmBundle = {
|
|
|
16779
17412
|
]
|
|
16780
17413
|
};
|
|
16781
17414
|
|
|
17415
|
+
// src/agent/bundles/pr-review-policy.ts
|
|
17416
|
+
var DEFAULT_PATHS_EXEMPT_FROM_SIZE = [
|
|
17417
|
+
"docs/**"
|
|
17418
|
+
];
|
|
17419
|
+
function resolvePrReviewPolicy(config) {
|
|
17420
|
+
const pathsExemptFromSize = config?.autoMerge?.pathsExemptFromSize ?? DEFAULT_PATHS_EXEMPT_FROM_SIZE;
|
|
17421
|
+
assertValidPathsExemptFromSize(pathsExemptFromSize);
|
|
17422
|
+
return {
|
|
17423
|
+
autoMerge: {
|
|
17424
|
+
pathsExemptFromSize: [...pathsExemptFromSize]
|
|
17425
|
+
}
|
|
17426
|
+
};
|
|
17427
|
+
}
|
|
17428
|
+
function validatePrReviewPolicyConfig(config) {
|
|
17429
|
+
return resolvePrReviewPolicy(config);
|
|
17430
|
+
}
|
|
17431
|
+
function assertValidPathsExemptFromSize(paths) {
|
|
17432
|
+
for (const path8 of paths) {
|
|
17433
|
+
if (typeof path8 !== "string" || path8.trim().length === 0) {
|
|
17434
|
+
throw new Error(
|
|
17435
|
+
"prReviewPolicy.autoMerge.pathsExemptFromSize entries must be non-empty strings"
|
|
17436
|
+
);
|
|
17437
|
+
}
|
|
17438
|
+
}
|
|
17439
|
+
}
|
|
17440
|
+
|
|
16782
17441
|
// src/agent/bundles/pr-review.ts
|
|
16783
17442
|
var prReviewerSubAgent = {
|
|
16784
17443
|
name: "pr-reviewer",
|
|
@@ -16969,11 +17628,16 @@ var prReviewerSubAgent = {
|
|
|
16969
17628
|
"### Step 2: Evaluate in precedence order",
|
|
16970
17629
|
"",
|
|
16971
17630
|
"Walk the following checks in order. The **first match wins** and fixes",
|
|
16972
|
-
"the mode; record the triggering condition as the `reason
|
|
16973
|
-
"
|
|
16974
|
-
"`human-required
|
|
16975
|
-
"
|
|
16976
|
-
"
|
|
17631
|
+
"the mode; record the triggering condition as the `reason` **and**",
|
|
17632
|
+
"record the numeric **`matched_rule`** index (one of `2`, `3`, `4`,",
|
|
17633
|
+
"`5`, or `6` for any human-required match \u2014 see the rule numbers",
|
|
17634
|
+
"below) so later phases can branch on which precedence rule fired.",
|
|
17635
|
+
"When rule 1 (force-auto label) or rule 7 (default) fixes the mode,",
|
|
17636
|
+
"record `matched_rule: null` \u2014 only the human-required rules carry an",
|
|
17637
|
+
"actionable index. Mixed-match PRs (signals from both sides) resolve",
|
|
17638
|
+
"conservatively to `human-required` \u2014 force-auto only wins when it is",
|
|
17639
|
+
"the single highest match and no later human-required signal changes",
|
|
17640
|
+
"the outcome under step 2c.",
|
|
16977
17641
|
"",
|
|
16978
17642
|
"1. **Force-auto label** \u2014 if the PR carries any label listed under",
|
|
16979
17643
|
" `auto-merge.labels-that-force-auto` (e.g. `review:auto-ok`), set",
|
|
@@ -16990,9 +17654,18 @@ var prReviewerSubAgent = {
|
|
|
16990
17654
|
" (fetched in Phase 2) matches any entry in",
|
|
16991
17655
|
" `human-required.issue-types` (case-insensitive), set",
|
|
16992
17656
|
" `mode = human-required`.",
|
|
16993
|
-
"6. **Size thresholds** \u2014
|
|
16994
|
-
" `
|
|
16995
|
-
"
|
|
17657
|
+
"6. **Size thresholds** \u2014 first read",
|
|
17658
|
+
" `auto-merge.paths-exempt-from-size` from the policy (defaults to",
|
|
17659
|
+
" the documented carve-out list). For every file in the PR diff,",
|
|
17660
|
+
" evaluate whether the file path matches at least one glob in the",
|
|
17661
|
+
" carve-out list. If **every** changed path matches the carve-out,",
|
|
17662
|
+
" **skip rule #6** entirely and continue to rule #7 \u2014 the PR is",
|
|
17663
|
+
" exempt from the size threshold regardless of its `files` or",
|
|
17664
|
+
" `insertions` count. Otherwise (any changed path falls outside",
|
|
17665
|
+
" the carve-out list), apply the size check: if the PR exceeds",
|
|
17666
|
+
" either threshold under `human-required.size` (`files` count or",
|
|
17667
|
+
" `insertions` count), set `mode = human-required` and record the",
|
|
17668
|
+
" triggered axis (files vs. insertions) as the reason.",
|
|
16996
17669
|
"7. **Default** \u2014 if no rule above matched, apply the `default` field",
|
|
16997
17670
|
" from the policy (typically `auto-merge`).",
|
|
16998
17671
|
"",
|
|
@@ -17006,14 +17679,23 @@ var prReviewerSubAgent = {
|
|
|
17006
17679
|
"",
|
|
17007
17680
|
"### Step 3: Record the decision",
|
|
17008
17681
|
"",
|
|
17009
|
-
"Persist the evaluated mode
|
|
17010
|
-
"any downstream summary writer can
|
|
17682
|
+
"Persist the evaluated mode, the reason, and the matched-rule index",
|
|
17683
|
+
"for later phases so Phase 4 and any downstream summary writer can",
|
|
17684
|
+
"cite them:",
|
|
17011
17685
|
"",
|
|
17012
17686
|
"```",
|
|
17013
17687
|
"Review mode: <auto-merge | human-required>",
|
|
17014
17688
|
"Reason: <short explanation \u2014 label name, path+glob, issue type, size threshold, default>",
|
|
17689
|
+
"Matched rule: <2 | 3 | 4 | 5 | 6 | null>",
|
|
17015
17690
|
"```",
|
|
17016
17691
|
"",
|
|
17692
|
+
"`Matched rule` is the numeric index of the precedence rule that",
|
|
17693
|
+
"fixed the mode in Step 2. It is populated only for human-required",
|
|
17694
|
+
"matches (rules 2\u20136) \u2014 force-auto (rule 1) and default (rule 7)",
|
|
17695
|
+
"record `null`. Phase 4's `gh pr update-branch` gate consults this",
|
|
17696
|
+
"field to decide whether the size-only carve-out (rule 6) allows the",
|
|
17697
|
+
"bot to keep a human-required branch fresh against the default branch.",
|
|
17698
|
+
"",
|
|
17017
17699
|
"Phases 3 (acceptance-criteria comparison) and CI verification run",
|
|
17018
17700
|
"unchanged regardless of mode. Only the terminal action in Phase 4",
|
|
17019
17701
|
"branches on the decided mode.",
|
|
@@ -17465,9 +18147,42 @@ var prReviewerSubAgent = {
|
|
|
17465
18147
|
"gh pr view <pr-number> --json mergeStateStatus --jq '.mergeStateStatus'",
|
|
17466
18148
|
"```",
|
|
17467
18149
|
"",
|
|
17468
|
-
"
|
|
17469
|
-
"
|
|
17470
|
-
"
|
|
18150
|
+
"Before running `gh pr update-branch`, evaluate the **eligibility",
|
|
18151
|
+
"gate** below. The step runs when `mergeStateStatus == BEHIND` **AND**",
|
|
18152
|
+
"either of the following holds:",
|
|
18153
|
+
"",
|
|
18154
|
+
"- The review mode decided in Phase 2.75 is `auto-merge`, **or**",
|
|
18155
|
+
"- The review mode is `human-required` **and** `matched_rule == 6`",
|
|
18156
|
+
" (size threshold is the sole trigger that fixed the mode).",
|
|
18157
|
+
"",
|
|
18158
|
+
"All other `human-required` matches (rules 2\u20135) continue to block",
|
|
18159
|
+
"`update-branch`. Concretely, **never** run `gh pr update-branch`",
|
|
18160
|
+
"when the mode is `human-required` and any of the following fired:",
|
|
18161
|
+
"",
|
|
18162
|
+
"- rule 2 (`review:human-required` label),",
|
|
18163
|
+
"- rule 3 (any `labels-that-force-human` label such as `priority:critical`),",
|
|
18164
|
+
"- rule 4 (`human-required.paths` glob match), or",
|
|
18165
|
+
"- rule 5 (`human-required.issue-types` match).",
|
|
18166
|
+
"",
|
|
18167
|
+
"The rationale: rule 6 fires on the **volume** of the diff alone \u2014",
|
|
18168
|
+
"there is nothing about the changed paths or labels that suggests a",
|
|
18169
|
+
"human reviewer has explicit ownership of the branch's lifecycle.",
|
|
18170
|
+
"Pushing the default branch into a size-tripped human-required PR",
|
|
18171
|
+
"keeps it from sitting stale while the human is still drafting",
|
|
18172
|
+
"their review. By contrast, each of rules 2\u20135 signals a human",
|
|
18173
|
+
"reviewer who owns the branch's lifecycle; silently pushing main",
|
|
18174
|
+
"into those PRs expands the diff under review without their consent.",
|
|
18175
|
+
"",
|
|
18176
|
+
"When the gate **denies** `update-branch` (`human-required` mode and",
|
|
18177
|
+
"`matched_rule` in 2\u20135), record",
|
|
18178
|
+
"`Branch updated: not eligible (human-required by rule <N>)` and skip",
|
|
18179
|
+
"the rest of this sub-section. The human reviewer keeps branch-",
|
|
18180
|
+
"lifecycle ownership.",
|
|
18181
|
+
"",
|
|
18182
|
+
"When the gate **permits** `update-branch`, attempt to bring the head",
|
|
18183
|
+
"branch current with the default branch via `gh pr update-branch`",
|
|
18184
|
+
"(default merge strategy \u2014 **never** `--rebase`, which would rewrite",
|
|
18185
|
+
"commits on a published branch):",
|
|
17471
18186
|
"",
|
|
17472
18187
|
"```bash",
|
|
17473
18188
|
"gh pr update-branch <pr-number>",
|
|
@@ -17476,8 +18191,9 @@ var prReviewerSubAgent = {
|
|
|
17476
18191
|
"Branch on the outcome:",
|
|
17477
18192
|
"",
|
|
17478
18193
|
"- **Success** \u2014 record `Branch updated: yes` for the per-PR report and",
|
|
17479
|
-
" stop. Auto-merge will fire when required checks pass
|
|
17480
|
-
" SHA. Do **not** poll for the merge here \u2014 Phase 5
|
|
18194
|
+
" stop. Auto-merge (when enabled) will fire when required checks pass",
|
|
18195
|
+
" on the new head SHA. Do **not** poll for the merge here \u2014 Phase 5",
|
|
18196
|
+
" owns polling.",
|
|
17481
18197
|
"- **Failure for reasons other than a merge conflict** (permission",
|
|
17482
18198
|
" denied, branch protection refusing the merge commit, transient",
|
|
17483
18199
|
" network error) \u2014 record `Branch updated: failed (<reason>)`, post a",
|
|
@@ -17491,13 +18207,6 @@ var prReviewerSubAgent = {
|
|
|
17491
18207
|
"an `update-branch` attempt \u2014 every other state either has nothing to do",
|
|
17492
18208
|
"or is already gated on a different signal that Phase 5 picks up.",
|
|
17493
18209
|
"",
|
|
17494
|
-
"Never run `gh pr update-branch` on a PR whose review mode is",
|
|
17495
|
-
"`human-required`. Pushing main into a human-required PR expands the",
|
|
17496
|
-
"scope of the diff the human is reviewing without their consent. The",
|
|
17497
|
-
"`update-branch` step lives **only** under the `Mode auto-merge` branch",
|
|
17498
|
-
"of this phase \u2014 `human-required` skips straight to its hand-off block",
|
|
17499
|
-
"below.",
|
|
17500
|
-
"",
|
|
17501
18210
|
"##### Conflict-resolution delegation (BEHIND + conflicts)",
|
|
17502
18211
|
"",
|
|
17503
18212
|
"When `gh pr update-branch <pr-number>` fails because the merge would",
|
|
@@ -17511,9 +18220,13 @@ var prReviewerSubAgent = {
|
|
|
17511
18220
|
"fall through to the fallback at the end of this sub-section instead.",
|
|
17512
18221
|
"",
|
|
17513
18222
|
"1. **Review mode is `auto-merge`.** Never delegate conflict",
|
|
17514
|
-
" resolution on `human-required` PRs \u2014 pushing
|
|
17515
|
-
" them expands the diff
|
|
17516
|
-
"
|
|
18223
|
+
" resolution on `human-required` PRs \u2014 pushing worker-resolved",
|
|
18224
|
+
" merge content into them expands the diff under review without",
|
|
18225
|
+
" the human reviewer's consent. (Unlike the `update-branch` step",
|
|
18226
|
+
" above, which permits a size-only `human-required` carve-out, the",
|
|
18227
|
+
" conflict-resolution delegation flow is auto-merge-only across",
|
|
18228
|
+
" the board: a worker rebase push is a stronger branch mutation",
|
|
18229
|
+
" than the merge-commit `gh pr update-branch` performs.)",
|
|
17517
18230
|
"2. **Delegation invocation guard permits the hand-off** \u2014 the PR",
|
|
17518
18231
|
" carries the `origin:issue-worker` label, **or** the reviewer was",
|
|
17519
18232
|
" invoked with `--allow-human-author`. The same guard used for the",
|
|
@@ -17538,8 +18251,53 @@ var prReviewerSubAgent = {
|
|
|
17538
18251
|
" and conflicts there should be resolved by re-running synth, not by",
|
|
17539
18252
|
" merging the conflict markers.",
|
|
17540
18253
|
"",
|
|
17541
|
-
"When every guard above passes,
|
|
17542
|
-
"
|
|
18254
|
+
"When every guard above passes, **classify each conflicting file**",
|
|
18255
|
+
"before composing the fix-list. The classification picks one of two",
|
|
18256
|
+
"typed recipes \u2014 a precise `shared-index` resolver when every",
|
|
18257
|
+
"conflict is a row-insert race on a registry / index / feature-matrix",
|
|
18258
|
+
"file, or the generic rebase recipe in every other case.",
|
|
18259
|
+
"",
|
|
18260
|
+
"**Classification step.** For each conflicting path reported by the",
|
|
18261
|
+
"failed `update-branch`, decide whether the file is `shared-index` or",
|
|
18262
|
+
"`generic` against these criteria (the shared-index glob set comes",
|
|
18263
|
+
"from the `shared-editing-safety` rule \u2014 see the bundle's",
|
|
18264
|
+
"`shared-editing.ts` for the canonical constant):",
|
|
18265
|
+
"",
|
|
18266
|
+
"- `shared-index` \u2014 the path matches one of the shared-editing glob",
|
|
18267
|
+
" patterns:",
|
|
18268
|
+
...DEFAULT_SHARED_INDEX_PATHS.map((p) => ` - \`${p}\``),
|
|
18269
|
+
" **AND** the conflict diff is bounded to row insertions only.",
|
|
18270
|
+
" Inspect the merge conflict diff for the file: every `<<<<<<<` /",
|
|
18271
|
+
" `=======` / `>>>>>>>` hunk must touch only data rows (lines that",
|
|
18272
|
+
" start with `| ` and are not the table header or the `|---|---|`",
|
|
18273
|
+
" separator). The conflict must **not** touch:",
|
|
18274
|
+
" - The frontmatter block (lines between the opening and",
|
|
18275
|
+
" closing `---` fences at the top of the file).",
|
|
18276
|
+
" - The page H1 or any surrounding prose paragraphs.",
|
|
18277
|
+
" - The table header row (the first `| Column | ... |` row of",
|
|
18278
|
+
" any table).",
|
|
18279
|
+
" - The separator row (`|---|---|...|`).",
|
|
18280
|
+
" If the conflict hunks touch any of the above, classify the file",
|
|
18281
|
+
" as `generic` \u2014 the mechanical row-insert recipe cannot safely",
|
|
18282
|
+
" reconcile structural edits.",
|
|
18283
|
+
"- `generic` \u2014 anything else (path outside the shared-editing glob",
|
|
18284
|
+
" set, **or** conflict hunks touch the frontmatter / header /",
|
|
18285
|
+
" separator / surrounding prose).",
|
|
18286
|
+
"",
|
|
18287
|
+
"**Branching emit step.**",
|
|
18288
|
+
"",
|
|
18289
|
+
"- **Every conflicting file is `shared-index`** \u2014 emit the typed",
|
|
18290
|
+
' fix-list item with `comment_id: "synthetic:rebase-shared-index"`',
|
|
18291
|
+
" carrying the explicit re-insert recipe (see step 3 below). The",
|
|
18292
|
+
" worker mechanically re-inserts each row in declared sort order",
|
|
18293
|
+
" against the rebased file.",
|
|
18294
|
+
"- **Any conflicting file is `generic`** \u2014 emit the existing generic",
|
|
18295
|
+
' fix-list item with `comment_id: "synthetic:rebase-behind-main"`',
|
|
18296
|
+
" (see step 3 below). The worker performs a hand-merge of the",
|
|
18297
|
+
" conflict markers.",
|
|
18298
|
+
"",
|
|
18299
|
+
"Hand off to `issue-worker` with the chosen single synthetic",
|
|
18300
|
+
"fix-list item:",
|
|
17543
18301
|
"",
|
|
17544
18302
|
"1. **Disable auto-merge** so a fast CI pass cannot land the PR",
|
|
17545
18303
|
" mid-delegation (idempotent \u2014 safe no-op when auto-merge was never",
|
|
@@ -17558,9 +18316,18 @@ var prReviewerSubAgent = {
|
|
|
17558
18316
|
" ```",
|
|
17559
18317
|
"",
|
|
17560
18318
|
"3. **Post a fix-list comment** containing exactly one synthetic item",
|
|
17561
|
-
" describing the rebase. The `comment_id`
|
|
17562
|
-
"
|
|
17563
|
-
"
|
|
18319
|
+
" describing the rebase. The `comment_id` field selects the typed",
|
|
18320
|
+
" recipe:",
|
|
18321
|
+
"",
|
|
18322
|
+
" - `synthetic:rebase-behind-main` \u2014 generic conflict; the worker",
|
|
18323
|
+
" resolves conflicting hunks by hand.",
|
|
18324
|
+
" - `synthetic:rebase-shared-index` \u2014 every conflict is a pure",
|
|
18325
|
+
" row-insert race on a shared-index file; the worker re-inserts",
|
|
18326
|
+
" each row in declared sort order against the rebased file.",
|
|
18327
|
+
"",
|
|
18328
|
+
" The next reviewer pass identifies the item by its `comment_id`.",
|
|
18329
|
+
"",
|
|
18330
|
+
" **Generic shape (`synthetic:rebase-behind-main`):**",
|
|
17564
18331
|
"",
|
|
17565
18332
|
" ```markdown",
|
|
17566
18333
|
" ## Reviewer: fix list for @issue-worker",
|
|
@@ -17579,6 +18346,27 @@ var prReviewerSubAgent = {
|
|
|
17579
18346
|
" ```",
|
|
17580
18347
|
" ```",
|
|
17581
18348
|
"",
|
|
18349
|
+
" **Typed shape (`synthetic:rebase-shared-index`).** The",
|
|
18350
|
+
" `instruction` field carries the full re-insert recipe \u2014 the",
|
|
18351
|
+
" worker reads it imperatively, so spell every step out:",
|
|
18352
|
+
"",
|
|
18353
|
+
" ```markdown",
|
|
18354
|
+
" ## Reviewer: fix list for @issue-worker",
|
|
18355
|
+
"",
|
|
18356
|
+
" - [ ] @reviewer \u2014 rebase onto origin/<default-branch> and re-insert shared-index rows in: <space-separated list of conflicting files>",
|
|
18357
|
+
"",
|
|
18358
|
+
" ```json fix-list",
|
|
18359
|
+
" {",
|
|
18360
|
+
' "pr": <pr-number>,',
|
|
18361
|
+
' "branch": "<head-ref-name>",',
|
|
18362
|
+
' "generated_at": "<ISO-8601 timestamp>",',
|
|
18363
|
+
' "items": [',
|
|
18364
|
+
' {"comment_id": "synthetic:rebase-shared-index", "author": "reviewer", "file": "<first-conflicting-file>", "instruction": "Branch is BEHIND default-branch with shared-index row-insert conflicts only. Apply this recipe: (1) Fetch and rebase: `git fetch origin && git pull --rebase origin <default-branch>`. (2) For each conflicting shared-index file: read the rebased version (it now contains the other PR\'s row), extract this PR\'s row from the `<<<<<<<` side of the conflict markers, re-insert that row in declared sort order using the file\'s documented sort key (alphabetical on the first column by default), stage the file with `git add <path>`. (3) Run the commit-path verification for each row: `count=$(git show :<path> | grep -Fc <row-unique-marker>) && [ \\"$count\\" = \\"1\\" ] || exit 1`. (4) `git rebase --continue` and push with non-force `git push origin <branch>`. (5) If any conflict marker touches the frontmatter, table header row, `|---|---|` separator, or surrounding prose, abort the recipe and emit `BLOCKED <reason>` \u2014 the precondition guards from the reviewer\'s classification step were violated."}',
|
|
18365
|
+
" ]",
|
|
18366
|
+
" }",
|
|
18367
|
+
" ```",
|
|
18368
|
+
" ```",
|
|
18369
|
+
"",
|
|
17582
18370
|
"4. **Invoke `issue-worker` in feedback mode** with the same prompt",
|
|
17583
18371
|
" shape used by the in-scope-fix flow: include the literal phrase",
|
|
17584
18372
|
" `feedback mode: PR #<n>` plus the repository identifier",
|
|
@@ -17623,7 +18411,16 @@ var prReviewerSubAgent = {
|
|
|
17623
18411
|
" gh pr edit <pr-number> --add-label 'review:awaiting-human'",
|
|
17624
18412
|
" ```",
|
|
17625
18413
|
"",
|
|
17626
|
-
"2.
|
|
18414
|
+
"2. **If `matched_rule == 6`** (size threshold was the sole trigger),",
|
|
18415
|
+
" run the `Update the branch when `mergeStateStatus` is `BEHIND``",
|
|
18416
|
+
" step from the `Mode auto-merge` branch above before exiting. The",
|
|
18417
|
+
" eligibility gate documented in that sub-section explicitly permits",
|
|
18418
|
+
" `gh pr update-branch` on size-only human-required PRs so the bot",
|
|
18419
|
+
" keeps the branch fresh against the default branch while the human",
|
|
18420
|
+
" reviews. Skip this sub-step for `matched_rule` in 2\u20135 \u2014 the gate",
|
|
18421
|
+
" denies `update-branch` there and the human owns branch-lifecycle.",
|
|
18422
|
+
"",
|
|
18423
|
+
"3. Exit cleanly after the acceptance-criteria check completes and any",
|
|
17627
18424
|
" summary comment the reviewer posts. Proceed to Phase 5 only if a",
|
|
17628
18425
|
" merge occurred \u2014 in `human-required` mode the reviewer stops at",
|
|
17629
18426
|
" the hand-off and does not poll for merge.",
|
|
@@ -17868,13 +18665,20 @@ var prReviewerSubAgent = {
|
|
|
17868
18665
|
" AC-drift pushback, any failed-fix pushback, and any human-required",
|
|
17869
18666
|
" hand-off all keep auto-merge disabled until the human resolves",
|
|
17870
18667
|
" the underlying state.",
|
|
17871
|
-
"15. **
|
|
17872
|
-
"
|
|
17873
|
-
"
|
|
17874
|
-
"
|
|
17875
|
-
"
|
|
18668
|
+
"15. **Restrict `gh pr update-branch` on `human-required` PRs.** The",
|
|
18669
|
+
" `update-branch` step is permitted when the review mode is",
|
|
18670
|
+
" `auto-merge`, **or** when the mode is `human-required` **and**",
|
|
18671
|
+
" Phase 2.75's `matched_rule == 6` (size threshold was the sole",
|
|
18672
|
+
" trigger). All other human-required matches (rule 2 force-human",
|
|
18673
|
+
" label, rule 3 listed force-human label, rule 4 path glob, rule 5",
|
|
18674
|
+
" issue type) continue to block `update-branch`: pushing main into",
|
|
18675
|
+
" those PRs expands the diff under review without the human",
|
|
18676
|
+
" reviewer's consent. The same restriction applies to delegating",
|
|
17876
18677
|
" conflict resolution to `issue-worker`: never delegate a rebase",
|
|
17877
|
-
" on a `human-required` PR
|
|
18678
|
+
" on a `human-required` PR regardless of `matched_rule` \u2014 the",
|
|
18679
|
+
" typed-recipe delegation flow stays auto-merge-only because a",
|
|
18680
|
+
" worker push to a human-required branch is a stronger mutation",
|
|
18681
|
+
" than the merge-commit `gh pr update-branch` performs.",
|
|
17878
18682
|
"16. **Never delegate conflict resolution involving generated or",
|
|
17879
18683
|
" projen-managed files.** When `gh pr update-branch` fails on a",
|
|
17880
18684
|
" BEHIND PR with conflicts and any conflicting path is a lockfile,",
|
|
@@ -18042,364 +18846,442 @@ var reviewPrsSkill = {
|
|
|
18042
18846
|
"comment on that PR and continue with the next."
|
|
18043
18847
|
].join("\n")
|
|
18044
18848
|
};
|
|
18045
|
-
|
|
18046
|
-
|
|
18047
|
-
|
|
18048
|
-
|
|
18049
|
-
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
18053
|
-
|
|
18054
|
-
|
|
18055
|
-
|
|
18056
|
-
|
|
18057
|
-
|
|
18058
|
-
|
|
18059
|
-
|
|
18060
|
-
|
|
18061
|
-
|
|
18062
|
-
|
|
18063
|
-
|
|
18064
|
-
|
|
18065
|
-
|
|
18066
|
-
|
|
18067
|
-
|
|
18068
|
-
|
|
18069
|
-
|
|
18070
|
-
|
|
18071
|
-
|
|
18072
|
-
|
|
18073
|
-
|
|
18074
|
-
|
|
18075
|
-
|
|
18076
|
-
|
|
18077
|
-
|
|
18078
|
-
|
|
18079
|
-
|
|
18080
|
-
|
|
18081
|
-
|
|
18082
|
-
|
|
18083
|
-
|
|
18084
|
-
|
|
18085
|
-
|
|
18086
|
-
|
|
18087
|
-
|
|
18088
|
-
|
|
18089
|
-
|
|
18090
|
-
|
|
18091
|
-
|
|
18092
|
-
|
|
18093
|
-
|
|
18094
|
-
|
|
18095
|
-
|
|
18096
|
-
|
|
18097
|
-
|
|
18098
|
-
|
|
18099
|
-
|
|
18100
|
-
|
|
18101
|
-
|
|
18102
|
-
|
|
18103
|
-
|
|
18104
|
-
|
|
18105
|
-
|
|
18106
|
-
|
|
18107
|
-
|
|
18108
|
-
|
|
18109
|
-
|
|
18110
|
-
|
|
18111
|
-
|
|
18112
|
-
|
|
18113
|
-
|
|
18114
|
-
|
|
18115
|
-
|
|
18116
|
-
|
|
18117
|
-
|
|
18118
|
-
|
|
18119
|
-
|
|
18120
|
-
|
|
18121
|
-
|
|
18122
|
-
|
|
18123
|
-
|
|
18124
|
-
|
|
18125
|
-
|
|
18126
|
-
|
|
18127
|
-
|
|
18128
|
-
|
|
18129
|
-
|
|
18130
|
-
|
|
18131
|
-
|
|
18132
|
-
|
|
18133
|
-
|
|
18134
|
-
|
|
18135
|
-
|
|
18136
|
-
|
|
18137
|
-
|
|
18138
|
-
|
|
18139
|
-
|
|
18140
|
-
|
|
18141
|
-
|
|
18142
|
-
|
|
18143
|
-
|
|
18144
|
-
|
|
18145
|
-
|
|
18146
|
-
|
|
18147
|
-
|
|
18148
|
-
|
|
18149
|
-
|
|
18150
|
-
|
|
18151
|
-
|
|
18152
|
-
|
|
18153
|
-
|
|
18154
|
-
|
|
18155
|
-
|
|
18156
|
-
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
|
|
18160
|
-
|
|
18161
|
-
|
|
18162
|
-
|
|
18163
|
-
|
|
18164
|
-
|
|
18165
|
-
|
|
18166
|
-
|
|
18167
|
-
|
|
18168
|
-
|
|
18169
|
-
|
|
18170
|
-
|
|
18171
|
-
|
|
18172
|
-
|
|
18173
|
-
|
|
18174
|
-
|
|
18175
|
-
|
|
18176
|
-
|
|
18177
|
-
|
|
18178
|
-
|
|
18179
|
-
|
|
18849
|
+
function buildPrReviewBundle(policy = resolvePrReviewPolicy()) {
|
|
18850
|
+
return {
|
|
18851
|
+
name: "pr-review",
|
|
18852
|
+
description: "Pull request review workflow: verifies PRs against their linked issues' acceptance criteria and orchestrates squash-merge, single or looped over all eligible PRs",
|
|
18853
|
+
// Default-apply: the PR review workflow is safe to include everywhere,
|
|
18854
|
+
// and keeping review/merge policy centralised in the pr-reviewer agent
|
|
18855
|
+
// means consumers get consistent behaviour out of the box. Consumers can
|
|
18856
|
+
// still exclude it explicitly via `excludeBundles` if desired.
|
|
18857
|
+
appliesWhen: () => true,
|
|
18858
|
+
rules: [
|
|
18859
|
+
{
|
|
18860
|
+
name: "pr-review-policy",
|
|
18861
|
+
description: "Declarative policy that tells the pr-reviewer which PRs may auto-merge and which must wait for a human reviewer",
|
|
18862
|
+
scope: AGENT_RULE_SCOPE.FILE_PATTERN,
|
|
18863
|
+
// Bundle defaults exclude paths into configulator's own source
|
|
18864
|
+
// (only meaningful when configulator is a workspace package).
|
|
18865
|
+
// codedrifters/packages restores them via
|
|
18866
|
+
// `agentConfig.additionalRulePaths`.
|
|
18867
|
+
filePatterns: [
|
|
18868
|
+
".claude/agents/pr-reviewer.md",
|
|
18869
|
+
".claude/skills/review-pr/**"
|
|
18870
|
+
],
|
|
18871
|
+
content: [
|
|
18872
|
+
"# PR Review Policy",
|
|
18873
|
+
"",
|
|
18874
|
+
"The `pr-reviewer` sub-agent evaluates every PR it reviews against the",
|
|
18875
|
+
"policy below and routes the PR into one of two modes:",
|
|
18876
|
+
"",
|
|
18877
|
+
"- **`auto-merge`** \u2014 the reviewer may enable squash auto-merge once",
|
|
18878
|
+
" all acceptance criteria are met and CI is green.",
|
|
18879
|
+
"- **`human-required`** \u2014 the reviewer runs the full AC/CI check but",
|
|
18880
|
+
" never calls `gh pr merge --auto`. It applies the",
|
|
18881
|
+
" `review:awaiting-human` label and hands off to a human reviewer.",
|
|
18882
|
+
"",
|
|
18883
|
+
"## Policy",
|
|
18884
|
+
"",
|
|
18885
|
+
"```yaml",
|
|
18886
|
+
"version: 1",
|
|
18887
|
+
"default: auto-merge",
|
|
18888
|
+
"",
|
|
18889
|
+
"human-required:",
|
|
18890
|
+
" paths:",
|
|
18891
|
+
' - "docs/src/content/docs/requirements/architectural-decisions/**"',
|
|
18892
|
+
' - "docs/src/content/docs/project-context.md"',
|
|
18893
|
+
' - ".github/workflows/**"',
|
|
18894
|
+
' - ".github/CODEOWNERS"',
|
|
18895
|
+
' - ".projenrc.ts"',
|
|
18896
|
+
' - "projenrc/**"',
|
|
18897
|
+
' - "CLAUDE.md"',
|
|
18898
|
+
' - ".claude/**"',
|
|
18899
|
+
' - "packages/**/package.json"',
|
|
18900
|
+
" issue-types:",
|
|
18901
|
+
" - release",
|
|
18902
|
+
" - hotfix",
|
|
18903
|
+
" size:",
|
|
18904
|
+
" files: 10",
|
|
18905
|
+
" insertions: 500",
|
|
18906
|
+
" labels-that-force-human:",
|
|
18907
|
+
' - "review:human-required"',
|
|
18908
|
+
' - "priority:critical"',
|
|
18909
|
+
"",
|
|
18910
|
+
"auto-merge:",
|
|
18911
|
+
" labels-that-force-auto:",
|
|
18912
|
+
' - "review:auto-ok"',
|
|
18913
|
+
" paths-exempt-from-size:",
|
|
18914
|
+
...renderPathsExemptFromSizeYaml(
|
|
18915
|
+
policy.autoMerge.pathsExemptFromSize
|
|
18916
|
+
),
|
|
18917
|
+
"```",
|
|
18918
|
+
"",
|
|
18919
|
+
"## Precedence",
|
|
18920
|
+
"",
|
|
18921
|
+
"The reviewer walks the following checks in order. The **first match**",
|
|
18922
|
+
"fixes the mode; any mixed-match PR (signals from both sides) resolves",
|
|
18923
|
+
"conservatively to `human-required` \u2014 `auto-merge` only wins when the",
|
|
18924
|
+
"force-auto label is the single top-priority match.",
|
|
18925
|
+
"",
|
|
18926
|
+
"1. **`auto-merge.labels-that-force-auto`** \u2014 if the PR carries any of",
|
|
18927
|
+
" these labels (e.g. `review:auto-ok`), the mode is `auto-merge`",
|
|
18928
|
+
" outright. This is the only escape hatch from the conservative",
|
|
18929
|
+
" default; it requires a maintainer to apply the label explicitly.",
|
|
18930
|
+
"2. **`review:human-required` label** \u2014 reserved force-human label;",
|
|
18931
|
+
" if present (and no force-auto label beat it in step 1), the mode",
|
|
18932
|
+
" is `human-required`.",
|
|
18933
|
+
"3. **`human-required.labels-that-force-human`** \u2014 any listed label on",
|
|
18934
|
+
" the PR (e.g. `priority:critical`) forces `human-required`.",
|
|
18935
|
+
"4. **`human-required.paths`** \u2014 if any file in the PR diff matches",
|
|
18936
|
+
" any glob here, the mode is `human-required`. Matching uses",
|
|
18937
|
+
" standard glob semantics (`**` for recursive directories,",
|
|
18938
|
+
" `*` for a single path segment).",
|
|
18939
|
+
"5. **`human-required.issue-types`** \u2014 if the linked issue's GitHub",
|
|
18940
|
+
" issue type matches any entry (case-insensitive), the mode is",
|
|
18941
|
+
" `human-required`.",
|
|
18942
|
+
"6. **`human-required.size`** \u2014 first read",
|
|
18943
|
+
" `auto-merge.paths-exempt-from-size` from the policy block above.",
|
|
18944
|
+
" For every file in the PR diff, evaluate whether the file path",
|
|
18945
|
+
" matches at least one glob in that carve-out list. If **every**",
|
|
18946
|
+
" changed path matches the carve-out, **skip rule #6** entirely",
|
|
18947
|
+
" and continue to rule #7 \u2014 the PR is exempt from the size",
|
|
18948
|
+
" threshold regardless of its `files` or `insertions` count.",
|
|
18949
|
+
" Otherwise (any changed path falls outside the carve-out list),",
|
|
18950
|
+
" apply the size check: if the PR exceeds either the `files`",
|
|
18951
|
+
" count or the `insertions` count, the mode is `human-required`.",
|
|
18952
|
+
"7. **`default`** \u2014 applied only when no rule above matched",
|
|
18953
|
+
" (normally `auto-merge`).",
|
|
18954
|
+
"",
|
|
18955
|
+
"The `auto-merge.paths-exempt-from-size` carve-out exists so",
|
|
18956
|
+
"**doc-only PRs** that routinely exceed the 500-insertion size",
|
|
18957
|
+
"threshold (large migrations, bulk additions, refresh passes)",
|
|
18958
|
+
"are not forced into `human-required` mode for a reason that does",
|
|
18959
|
+
"not reflect production risk. The default carve-out exempts the",
|
|
18960
|
+
"entire `docs/**` tree \u2014 every consumer of configulator places its",
|
|
18961
|
+
"Starlight docs site there. A PR mixing docs and code still falls",
|
|
18962
|
+
"into `human-required` at rule #6 because the non-docs path fails",
|
|
18963
|
+
"the carve-out check, so the rule only relaxes the threshold for",
|
|
18964
|
+
"PRs whose **every** changed path is doc-only.",
|
|
18965
|
+
"",
|
|
18966
|
+
"The `pr-reviewer` sub-agent records the decided mode, the",
|
|
18967
|
+
"triggering reason, and the numeric **matched-rule index** (2\u20136",
|
|
18968
|
+
"for human-required matches; `null` for rule 1 force-auto or",
|
|
18969
|
+
"rule 7 default) in its Phase 2.75 output. Downstream phases and",
|
|
18970
|
+
"the sticky summary cite the specific rule that applied.",
|
|
18971
|
+
"",
|
|
18972
|
+
"### Rule-#6 carve-out for `gh pr update-branch`",
|
|
18973
|
+
"",
|
|
18974
|
+
"The reviewer's BEHIND-branch refresh step (`gh pr update-branch`)",
|
|
18975
|
+
"is normally restricted to `auto-merge` PRs because pushing the",
|
|
18976
|
+
"default branch into a `human-required` PR expands the diff the",
|
|
18977
|
+
"human is reviewing without their consent. A narrow exception",
|
|
18978
|
+
"applies when rule #6 (size threshold) is the **sole** trigger",
|
|
18979
|
+
"for `human-required` mode: the bot may still run `gh pr",
|
|
18980
|
+
"update-branch` so a code-heavy size-tripped PR does not sit",
|
|
18981
|
+
"BEHIND while the human drafts their review.",
|
|
18982
|
+
"",
|
|
18983
|
+
"The exception is keyed on the matched-rule index recorded in",
|
|
18984
|
+
"Phase 2.75. All other `human-required` triggers \u2014 rule 2",
|
|
18985
|
+
"(`review:human-required` label), rule 3 (any",
|
|
18986
|
+
"`labels-that-force-human` label such as `priority:critical`),",
|
|
18987
|
+
"rule 4 (`human-required.paths` glob match), and rule 5",
|
|
18988
|
+
"(`human-required.issue-types` match) \u2014 continue to block",
|
|
18989
|
+
"`update-branch` because each one signals a human reviewer who",
|
|
18990
|
+
"has explicit ownership of the branch's lifecycle.",
|
|
18991
|
+
"",
|
|
18992
|
+
"This carve-out is largely belt-and-suspenders given the doc-only",
|
|
18993
|
+
"size carve-out above. Doc-only PRs that trip rule #6 now route",
|
|
18994
|
+
"directly to `auto-merge`, so the rule-#6 `update-branch` carve-",
|
|
18995
|
+
"out only kicks in for **code-heavy** PRs that legitimately trip",
|
|
18996
|
+
"rule #6 (mixed-content diffs whose non-doc paths fail the",
|
|
18997
|
+
"`paths-exempt-from-size` check, or consumers that disable the",
|
|
18998
|
+
"doc-only carve-out entirely)."
|
|
18999
|
+
].join("\n"),
|
|
19000
|
+
tags: ["policy", "review"]
|
|
18180
19001
|
},
|
|
18181
|
-
|
|
18182
|
-
|
|
18183
|
-
|
|
18184
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
18187
|
-
|
|
18188
|
-
|
|
18189
|
-
|
|
18190
|
-
|
|
18191
|
-
|
|
18192
|
-
|
|
18193
|
-
|
|
18194
|
-
|
|
18195
|
-
|
|
18196
|
-
|
|
18197
|
-
|
|
18198
|
-
|
|
18199
|
-
|
|
18200
|
-
|
|
18201
|
-
|
|
18202
|
-
|
|
18203
|
-
|
|
18204
|
-
|
|
18205
|
-
|
|
18206
|
-
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
|
|
18210
|
-
|
|
18211
|
-
|
|
18212
|
-
|
|
18213
|
-
|
|
18214
|
-
|
|
18215
|
-
|
|
18216
|
-
|
|
18217
|
-
|
|
18218
|
-
|
|
18219
|
-
|
|
18220
|
-
|
|
18221
|
-
|
|
18222
|
-
"
|
|
18223
|
-
|
|
18224
|
-
|
|
18225
|
-
|
|
18226
|
-
|
|
18227
|
-
"| `-1` | Declined as out-of-scope. A separate tracking issue was created; the reviewer's reply links to it. | **Yes** |",
|
|
18228
|
-
"",
|
|
18229
|
-
"Terminal reactions (`+1`, `rocket`, `-1`) are applied **only after**",
|
|
18230
|
-
"the corresponding action has truly completed \u2014 the fix accepted,",
|
|
18231
|
-
"the commit landed on the branch, or the out-of-scope tracking",
|
|
18232
|
-
"issue created and linked in a reply. The reviewer never applies a",
|
|
18233
|
-
"terminal reaction pre-emptively.",
|
|
18234
|
-
"",
|
|
18235
|
-
"A comment carrying only `eyes` or `thinking_face` from the",
|
|
18236
|
-
"reviewer is **non-terminal** and will be re-evaluated on the next",
|
|
18237
|
-
"pass. A comment carrying any terminal reaction authored by the",
|
|
18238
|
-
"reviewer is dropped from future classification.",
|
|
18239
|
-
"",
|
|
18240
|
-
"GitHub's reactions API uses `confused` as the content string for",
|
|
18241
|
-
"the `thinking_face` reaction (`content=confused` when POSTing).",
|
|
18242
|
-
"",
|
|
18243
|
-
"### Resolving a Pushback",
|
|
18244
|
-
"",
|
|
18245
|
-
"When the reviewer pushes back on a comment with `thinking_face`,",
|
|
18246
|
-
"auto-merge is blocked until the dispute is resolved. Humans have",
|
|
18247
|
-
"three ways to clear a pushback:",
|
|
18248
|
-
"",
|
|
18249
|
-
"1. **Withdraw the comment.** Delete the comment, or edit out the",
|
|
18250
|
-
" disputed request, then re-invoke `/review-pr <n>`. The reviewer",
|
|
18251
|
-
" drops the withdrawn item from its queue on the next pass.",
|
|
18252
|
-
"2. **Reply with clarification.** Post a reply on the same thread",
|
|
18253
|
-
" that addresses the reviewer's objection (cite the acceptance",
|
|
18254
|
-
" criterion you meant, supply the missing context, or concede the",
|
|
18255
|
-
" point). Re-invoke `/review-pr <n>` \u2014 the reviewer re-classifies",
|
|
18256
|
-
" the thread and may promote `thinking_face` to `+1` if the",
|
|
18257
|
-
" clarification satisfies it.",
|
|
18258
|
-
"3. **Force through with `review:auto-ok`.** Apply the",
|
|
18259
|
-
" `review:auto-ok` label to the PR as an explicit maintainer",
|
|
18260
|
-
" override. The reviewer will log the override in the sticky",
|
|
18261
|
-
" `## Reviewer notes` comment and proceed with auto-merge even",
|
|
18262
|
-
" though the dispute was never resolved by reply or withdrawal.",
|
|
18263
|
-
"",
|
|
18264
|
-
"### Fix-List Comment Format",
|
|
18265
|
-
"",
|
|
18266
|
-
"When Phase 4 delegates in-scope fixes to `issue-worker`, it posts",
|
|
18267
|
-
"a single PR-level comment whose body carries both a human-readable",
|
|
18268
|
-
"checkbox summary and a fenced ```json fix-list``` block. The JSON",
|
|
18269
|
-
"block is the authoritative payload the worker parses; the",
|
|
18270
|
-
"checkbox list is for humans reading the PR.",
|
|
18271
|
-
"",
|
|
18272
|
-
"```markdown",
|
|
18273
|
-
"## Reviewer: fix list for @issue-worker",
|
|
18274
|
-
"",
|
|
18275
|
-
"- [ ] @<author> \u2014 <instruction summary> (<file>:<line>)",
|
|
18276
|
-
"",
|
|
18277
|
-
"```json fix-list",
|
|
18278
|
-
"{",
|
|
18279
|
-
' "pr": <pr-number>,',
|
|
18280
|
-
' "branch": "<head-ref-name>",',
|
|
18281
|
-
' "generated_at": "<ISO-8601 timestamp>",',
|
|
18282
|
-
' "items": [',
|
|
18283
|
-
' {"comment_id": "<id>", "author": "<login>", "file": "<path>", "line": <n>, "instruction": "<imperative instruction>"}',
|
|
18284
|
-
" ]",
|
|
18285
|
-
"}",
|
|
18286
|
-
"```",
|
|
18287
|
-
"```",
|
|
18288
|
-
"",
|
|
18289
|
-
"Each `items[]` entry corresponds to one in-scope comment the",
|
|
18290
|
-
"reviewer queued on this pass. The `comment_id` is preserved",
|
|
18291
|
-
"exactly as returned by the GitHub API so that `issue-worker` can",
|
|
18292
|
-
"report per-item outcomes and the reviewer can apply `rocket` or",
|
|
18293
|
-
"`thinking_face` to the correct source comment on the next pass.",
|
|
18294
|
-
"",
|
|
18295
|
-
"### Sticky `## Reviewer notes` Comment",
|
|
18296
|
-
"",
|
|
18297
|
-
"Every PR has **one** canonical reviewer-notes comment. The",
|
|
18298
|
-
"reviewer creates it on the first pass, then **edits it in place**",
|
|
18299
|
-
"on every subsequent pass via",
|
|
18300
|
-
"`gh api .../issues/comments/<id> -X PATCH`. It is never",
|
|
18301
|
-
"duplicated and never replaced by a fresh per-pass summary.",
|
|
18302
|
-
"",
|
|
18303
|
-
"This sticky comment is the **single human-facing source of truth**",
|
|
18304
|
-
"for the PR's current state. Humans scanning the PR should read",
|
|
18305
|
-
"the sticky first, before scrolling back through individual threads.",
|
|
18306
|
-
"It carries, at a minimum:",
|
|
18307
|
-
"",
|
|
18308
|
-
"- **Mode** \u2014 `auto-merge` or `human-required`, with the Phase 2.75",
|
|
18309
|
-
" reason that chose that mode.",
|
|
18310
|
-
"- **AC status** \u2014 met, partial, or missing, with evidence links",
|
|
18311
|
-
" to files or tests.",
|
|
18312
|
-
"- **CI status** \u2014 green, pending, or red.",
|
|
18313
|
-
"- **Outstanding** \u2014 comments still carrying a non-terminal",
|
|
18314
|
-
" reviewer reaction (`eyes`, open `thinking_face`).",
|
|
18315
|
-
"- **Pushbacks** \u2014 every unresolved `thinking_face` the reviewer",
|
|
18316
|
-
" has left, with the reason captured in its pushback reply.",
|
|
18317
|
-
"- **Last pass** \u2014 the ISO 8601 timestamp of the most recent run.",
|
|
18318
|
-
"",
|
|
18319
|
-
"The sticky is updated on every pass \u2014 including passes that ended",
|
|
18320
|
-
"in a pushback-gated skip, a `NEEDS_CHANGES` findings comment, or",
|
|
18321
|
-
"a `human-required` hand-off \u2014 so it never goes stale while the",
|
|
18322
|
-
"reviewer is actively processing the PR.",
|
|
18323
|
-
"",
|
|
18324
|
-
"### Label Glossary",
|
|
18325
|
-
"",
|
|
18326
|
-
"Five review-workflow labels drive the feedback loop. Consumers",
|
|
18327
|
-
"that adopt this workflow are responsible for creating them in",
|
|
18328
|
-
"their own repos (the same way they create `priority:*` and",
|
|
18329
|
-
"`status:*` labels).",
|
|
18330
|
-
"",
|
|
18331
|
-
"| Label | Purpose |",
|
|
18332
|
-
"|-------|---------|",
|
|
18333
|
-
"| `origin:issue-worker` | PR was opened by the `issue-worker` agent. Eligible for auto-delegation of in-scope fixes. Human-authored PRs lack this label and will not trigger delegation unless the reviewer is invoked with `--allow-human-author`. |",
|
|
18334
|
-
"| `review:human-required` | Force human review regardless of what the policy would otherwise decide. The reviewer never enables auto-merge on a PR carrying this label. |",
|
|
18335
|
-
"| `review:auto-ok` | Force auto-merge regardless of what the policy would otherwise decide. **Also resolves outstanding `thinking_face` pushbacks** as an explicit maintainer override; the reviewer logs the override in the sticky summary. |",
|
|
18336
|
-
"| `review:awaiting-human` | Set by the reviewer when it completes its work on a `human-required` PR and is handing off the final merge decision. Cleared by a human (or by `review:auto-ok` flipping the PR back to `auto-merge` mode). |",
|
|
18337
|
-
"| `review:fixing` | Short-lived lease held by the reviewer while an `issue-worker` feedback-mode delegation is mid-run. Released automatically at the end of Phase 4 step (g). Contention on this label means a prior delegation crashed without releasing it and needs human investigation. |",
|
|
18338
|
-
"",
|
|
18339
|
-
"### Reviewing Human-Authored PRs: the `--allow-human-author` Flag",
|
|
18340
|
-
"",
|
|
18341
|
-
"By default the reviewer only **delegates** in-scope fixes on",
|
|
18342
|
-
"bot-authored PRs \u2014 those carrying the `origin:issue-worker`",
|
|
18343
|
-
"label. Running `/review-pr <n>` or `/review-prs` on a",
|
|
18344
|
-
"human-authored PR still produces a full review (reactions,",
|
|
18345
|
-
"replies, sticky summary, and auto-merge when the policy allows",
|
|
18346
|
-
"it) but skips the delegation hand-off to `issue-worker` \u2014 the",
|
|
18347
|
-
"human author is expected to apply the fixes themselves.",
|
|
18348
|
-
"",
|
|
18349
|
-
"Pass `--allow-human-author` to opt into delegation on",
|
|
18350
|
-
"human-authored PRs for a single invocation:",
|
|
18351
|
-
"",
|
|
18352
|
-
"```",
|
|
18353
|
-
"/review-pr <pr-number> --allow-human-author",
|
|
18354
|
-
"/review-prs --allow-human-author",
|
|
18355
|
-
"```",
|
|
18356
|
-
"",
|
|
18357
|
-
"The flag does **not** persist across invocations. Subsequent",
|
|
18358
|
-
"invocations return to the bot-only default and require the flag",
|
|
18359
|
-
"to be re-supplied if delegation on a human-authored PR is desired",
|
|
18360
|
-
"again."
|
|
18361
|
-
].join("\n"),
|
|
18362
|
-
platforms: {
|
|
18363
|
-
cursor: { exclude: true }
|
|
19002
|
+
{
|
|
19003
|
+
name: "pr-review-workflow",
|
|
19004
|
+
description: "Describes the /review-pr and /review-prs skills and their delegation to the pr-reviewer sub-agent",
|
|
19005
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
19006
|
+
content: [
|
|
19007
|
+
"# PR Review Workflow",
|
|
19008
|
+
"",
|
|
19009
|
+
"Two skills are available, both backed by the same `pr-reviewer`",
|
|
19010
|
+
"sub-agent:",
|
|
19011
|
+
"",
|
|
19012
|
+
"- **`/review-pr <pr-number>`** \u2014 review a single targeted PR.",
|
|
19013
|
+
"- **`/review-prs`** \u2014 loop over every eligible open PR in the",
|
|
19014
|
+
" repository and review each one in turn.",
|
|
19015
|
+
"",
|
|
19016
|
+
"The `pr-reviewer` sub-agent:",
|
|
19017
|
+
"",
|
|
19018
|
+
"1. Runs a pre-flight eligibility filter (mergeable, CI not failing,",
|
|
19019
|
+
" has a linked issue). Ineligible PRs get a short comment and are",
|
|
19020
|
+
" skipped.",
|
|
19021
|
+
"2. Fetches the PR, its diff, CI status, and the linked issue",
|
|
19022
|
+
"3. **Evaluates the PR Review Policy** (see the `PR Review Policy`",
|
|
19023
|
+
" section above) to decide whether the PR is `auto-merge` or",
|
|
19024
|
+
" `human-required`, and records the triggering reason",
|
|
19025
|
+
"4. Builds a checklist from the issue's acceptance criteria",
|
|
19026
|
+
"5. Verifies the diff satisfies each criterion and that CI is green",
|
|
19027
|
+
"6. **Enables squash auto-merge** (with `--delete-branch`) when all",
|
|
19028
|
+
" checks pass **and** the review mode is `auto-merge`",
|
|
19029
|
+
"7. **Applies `review:awaiting-human`** and hands off to a human",
|
|
19030
|
+
" reviewer when the review mode is `human-required` (no auto-merge,",
|
|
19031
|
+
" even if every acceptance criterion is met)",
|
|
19032
|
+
"8. **Comments with grouped findings** when any check fails (plain",
|
|
19033
|
+
" `gh pr comment`, not a formal `--request-changes` review)",
|
|
19034
|
+
"9. After a successful merge, verifies the linked issue is closed",
|
|
19035
|
+
" and closes it explicitly if the merge commit did not",
|
|
19036
|
+
"10. Cleans up the local branch after merge",
|
|
19037
|
+
"",
|
|
19038
|
+
"The reviewer **never** implements code and **never** pushes commits",
|
|
19039
|
+
"to a PR's branch \u2014 it only reviews, decides, and orchestrates merge",
|
|
19040
|
+
"or comment. In loop mode, a failed review for one PR never stops",
|
|
19041
|
+
"the loop; the reviewer comments and moves on. See the `pr-reviewer`",
|
|
19042
|
+
"agent definition for the full phase-by-phase contract."
|
|
19043
|
+
].join("\n"),
|
|
19044
|
+
platforms: {
|
|
19045
|
+
cursor: { exclude: true }
|
|
19046
|
+
},
|
|
19047
|
+
tags: ["workflow"]
|
|
18364
19048
|
},
|
|
18365
|
-
|
|
18366
|
-
|
|
18367
|
-
|
|
18368
|
-
|
|
18369
|
-
|
|
18370
|
-
|
|
18371
|
-
|
|
18372
|
-
|
|
18373
|
-
|
|
18374
|
-
|
|
18375
|
-
|
|
18376
|
-
|
|
18377
|
-
|
|
18378
|
-
|
|
18379
|
-
|
|
18380
|
-
|
|
18381
|
-
|
|
18382
|
-
|
|
18383
|
-
|
|
18384
|
-
|
|
18385
|
-
|
|
18386
|
-
|
|
18387
|
-
|
|
18388
|
-
|
|
18389
|
-
|
|
18390
|
-
|
|
18391
|
-
|
|
18392
|
-
|
|
18393
|
-
|
|
18394
|
-
|
|
18395
|
-
|
|
18396
|
-
|
|
18397
|
-
|
|
18398
|
-
|
|
18399
|
-
|
|
18400
|
-
|
|
18401
|
-
|
|
18402
|
-
|
|
19049
|
+
{
|
|
19050
|
+
name: "pr-review-feedback-protocol",
|
|
19051
|
+
description: "Documents the human-in-the-loop feedback loop on PR review: reaction state machine, pushback resolution, fix-list comment format, sticky reviewer-notes comment, label glossary, and human-author opt-in flag.",
|
|
19052
|
+
scope: AGENT_RULE_SCOPE.FILE_PATTERN,
|
|
19053
|
+
// Bundle defaults exclude paths into configulator's own source
|
|
19054
|
+
// (only meaningful when configulator is a workspace package).
|
|
19055
|
+
// codedrifters/packages restores them via
|
|
19056
|
+
// `agentConfig.additionalRulePaths`.
|
|
19057
|
+
filePatterns: [
|
|
19058
|
+
".claude/agents/pr-reviewer.md",
|
|
19059
|
+
".claude/agents/issue-worker.md"
|
|
19060
|
+
],
|
|
19061
|
+
content: [
|
|
19062
|
+
"# PR Review Feedback Protocol",
|
|
19063
|
+
"",
|
|
19064
|
+
"## Human-in-the-Loop Feedback Protocol",
|
|
19065
|
+
"",
|
|
19066
|
+
"The PR review pipeline is a **human-in-the-loop feedback loop**.",
|
|
19067
|
+
"Reviewers (human or agent) leave comments on the PR; the",
|
|
19068
|
+
"`pr-reviewer` sub-agent classifies each comment, reacts to it,",
|
|
19069
|
+
"delegates in-scope fixes to `issue-worker`, and updates a single",
|
|
19070
|
+
"sticky `## Reviewer notes` comment that is the canonical record of",
|
|
19071
|
+
"PR state. The sections below document the conventions humans need",
|
|
19072
|
+
"to read and drive that loop.",
|
|
19073
|
+
"",
|
|
19074
|
+
"### Trigger Model: Human-Triggered, Single-Pass",
|
|
19075
|
+
"",
|
|
19076
|
+
"Each reviewer pass runs exactly once and does not self-chain. A",
|
|
19077
|
+
"human re-invokes `/review-pr <n>` (or `/review-prs`) whenever the",
|
|
19078
|
+
"PR state changes enough to warrant another look \u2014 a new comment,",
|
|
19079
|
+
"a new commit, a resolved pushback, a label flip. The reviewer",
|
|
19080
|
+
"never reschedules itself and never loops back after handing off to",
|
|
19081
|
+
"`issue-worker`: the worker's run is the terminal step of that",
|
|
19082
|
+
"pass, and a human must re-invoke the reviewer to see the follow-up",
|
|
19083
|
+
"reactions and the auto-merge re-enablement decision.",
|
|
19084
|
+
"",
|
|
19085
|
+
"This keeps the loop cheap to reason about: every agent action is",
|
|
19086
|
+
"traceable to a specific human invocation, and there is no",
|
|
19087
|
+
"background automation to pause or cancel.",
|
|
19088
|
+
"",
|
|
19089
|
+
"### Reaction State Machine",
|
|
19090
|
+
"",
|
|
19091
|
+
"The reviewer signals its disposition toward each human comment via",
|
|
19092
|
+
"GitHub reactions on that comment. Five reactions carry meaning in",
|
|
19093
|
+
"this workflow; every other reaction is ignored.",
|
|
19094
|
+
"",
|
|
19095
|
+
"| Reaction | Meaning | Terminal? |",
|
|
19096
|
+
"|----------|---------|-----------|",
|
|
19097
|
+
"| `eyes` | Seen by reviewer; no terminal decision yet. Queued for processing on this or a later pass. | No |",
|
|
19098
|
+
"| `+1` | Reviewer accepted the comment's request; a fix has been queued or has already landed. | **Yes** |",
|
|
19099
|
+
"| `rocket` | The accepted fix has landed on the branch. The reviewer's reply cites the commit SHA that applied it. | **Yes** |",
|
|
19100
|
+
"| `thinking_face` | Reviewer pushback \u2014 the comment conflicts with an acceptance criterion, a CLAUDE.md convention, the project-context doc, or is ambiguous. **Blocks auto-merge** until resolved. | No |",
|
|
19101
|
+
"| `-1` | Declined as out-of-scope. A separate tracking issue was created; the reviewer's reply links to it. | **Yes** |",
|
|
19102
|
+
"",
|
|
19103
|
+
"Terminal reactions (`+1`, `rocket`, `-1`) are applied **only after**",
|
|
19104
|
+
"the corresponding action has truly completed \u2014 the fix accepted,",
|
|
19105
|
+
"the commit landed on the branch, or the out-of-scope tracking",
|
|
19106
|
+
"issue created and linked in a reply. The reviewer never applies a",
|
|
19107
|
+
"terminal reaction pre-emptively.",
|
|
19108
|
+
"",
|
|
19109
|
+
"A comment carrying only `eyes` or `thinking_face` from the",
|
|
19110
|
+
"reviewer is **non-terminal** and will be re-evaluated on the next",
|
|
19111
|
+
"pass. A comment carrying any terminal reaction authored by the",
|
|
19112
|
+
"reviewer is dropped from future classification.",
|
|
19113
|
+
"",
|
|
19114
|
+
"GitHub's reactions API uses `confused` as the content string for",
|
|
19115
|
+
"the `thinking_face` reaction (`content=confused` when POSTing).",
|
|
19116
|
+
"",
|
|
19117
|
+
"### Resolving a Pushback",
|
|
19118
|
+
"",
|
|
19119
|
+
"When the reviewer pushes back on a comment with `thinking_face`,",
|
|
19120
|
+
"auto-merge is blocked until the dispute is resolved. Humans have",
|
|
19121
|
+
"three ways to clear a pushback:",
|
|
19122
|
+
"",
|
|
19123
|
+
"1. **Withdraw the comment.** Delete the comment, or edit out the",
|
|
19124
|
+
" disputed request, then re-invoke `/review-pr <n>`. The reviewer",
|
|
19125
|
+
" drops the withdrawn item from its queue on the next pass.",
|
|
19126
|
+
"2. **Reply with clarification.** Post a reply on the same thread",
|
|
19127
|
+
" that addresses the reviewer's objection (cite the acceptance",
|
|
19128
|
+
" criterion you meant, supply the missing context, or concede the",
|
|
19129
|
+
" point). Re-invoke `/review-pr <n>` \u2014 the reviewer re-classifies",
|
|
19130
|
+
" the thread and may promote `thinking_face` to `+1` if the",
|
|
19131
|
+
" clarification satisfies it.",
|
|
19132
|
+
"3. **Force through with `review:auto-ok`.** Apply the",
|
|
19133
|
+
" `review:auto-ok` label to the PR as an explicit maintainer",
|
|
19134
|
+
" override. The reviewer will log the override in the sticky",
|
|
19135
|
+
" `## Reviewer notes` comment and proceed with auto-merge even",
|
|
19136
|
+
" though the dispute was never resolved by reply or withdrawal.",
|
|
19137
|
+
"",
|
|
19138
|
+
"### Fix-List Comment Format",
|
|
19139
|
+
"",
|
|
19140
|
+
"When Phase 4 delegates in-scope fixes to `issue-worker`, it posts",
|
|
19141
|
+
"a single PR-level comment whose body carries both a human-readable",
|
|
19142
|
+
"checkbox summary and a fenced ```json fix-list``` block. The JSON",
|
|
19143
|
+
"block is the authoritative payload the worker parses; the",
|
|
19144
|
+
"checkbox list is for humans reading the PR.",
|
|
19145
|
+
"",
|
|
19146
|
+
"```markdown",
|
|
19147
|
+
"## Reviewer: fix list for @issue-worker",
|
|
19148
|
+
"",
|
|
19149
|
+
"- [ ] @<author> \u2014 <instruction summary> (<file>:<line>)",
|
|
19150
|
+
"",
|
|
19151
|
+
"```json fix-list",
|
|
19152
|
+
"{",
|
|
19153
|
+
' "pr": <pr-number>,',
|
|
19154
|
+
' "branch": "<head-ref-name>",',
|
|
19155
|
+
' "generated_at": "<ISO-8601 timestamp>",',
|
|
19156
|
+
' "items": [',
|
|
19157
|
+
' {"comment_id": "<id>", "author": "<login>", "file": "<path>", "line": <n>, "instruction": "<imperative instruction>"}',
|
|
19158
|
+
" ]",
|
|
19159
|
+
"}",
|
|
19160
|
+
"```",
|
|
19161
|
+
"```",
|
|
19162
|
+
"",
|
|
19163
|
+
"Each `items[]` entry corresponds to one in-scope comment the",
|
|
19164
|
+
"reviewer queued on this pass. The `comment_id` is preserved",
|
|
19165
|
+
"exactly as returned by the GitHub API so that `issue-worker` can",
|
|
19166
|
+
"report per-item outcomes and the reviewer can apply `rocket` or",
|
|
19167
|
+
"`thinking_face` to the correct source comment on the next pass.",
|
|
19168
|
+
"",
|
|
19169
|
+
"### Sticky `## Reviewer notes` Comment",
|
|
19170
|
+
"",
|
|
19171
|
+
"Every PR has **one** canonical reviewer-notes comment. The",
|
|
19172
|
+
"reviewer creates it on the first pass, then **edits it in place**",
|
|
19173
|
+
"on every subsequent pass via",
|
|
19174
|
+
"`gh api .../issues/comments/<id> -X PATCH`. It is never",
|
|
19175
|
+
"duplicated and never replaced by a fresh per-pass summary.",
|
|
19176
|
+
"",
|
|
19177
|
+
"This sticky comment is the **single human-facing source of truth**",
|
|
19178
|
+
"for the PR's current state. Humans scanning the PR should read",
|
|
19179
|
+
"the sticky first, before scrolling back through individual threads.",
|
|
19180
|
+
"It carries, at a minimum:",
|
|
19181
|
+
"",
|
|
19182
|
+
"- **Mode** \u2014 `auto-merge` or `human-required`, with the Phase 2.75",
|
|
19183
|
+
" reason that chose that mode.",
|
|
19184
|
+
"- **AC status** \u2014 met, partial, or missing, with evidence links",
|
|
19185
|
+
" to files or tests.",
|
|
19186
|
+
"- **CI status** \u2014 green, pending, or red.",
|
|
19187
|
+
"- **Outstanding** \u2014 comments still carrying a non-terminal",
|
|
19188
|
+
" reviewer reaction (`eyes`, open `thinking_face`).",
|
|
19189
|
+
"- **Pushbacks** \u2014 every unresolved `thinking_face` the reviewer",
|
|
19190
|
+
" has left, with the reason captured in its pushback reply.",
|
|
19191
|
+
"- **Last pass** \u2014 the ISO 8601 timestamp of the most recent run.",
|
|
19192
|
+
"",
|
|
19193
|
+
"The sticky is updated on every pass \u2014 including passes that ended",
|
|
19194
|
+
"in a pushback-gated skip, a `NEEDS_CHANGES` findings comment, or",
|
|
19195
|
+
"a `human-required` hand-off \u2014 so it never goes stale while the",
|
|
19196
|
+
"reviewer is actively processing the PR.",
|
|
19197
|
+
"",
|
|
19198
|
+
"### Label Glossary",
|
|
19199
|
+
"",
|
|
19200
|
+
"Five review-workflow labels drive the feedback loop. Consumers",
|
|
19201
|
+
"that adopt this workflow are responsible for creating them in",
|
|
19202
|
+
"their own repos (the same way they create `priority:*` and",
|
|
19203
|
+
"`status:*` labels).",
|
|
19204
|
+
"",
|
|
19205
|
+
"| Label | Purpose |",
|
|
19206
|
+
"|-------|---------|",
|
|
19207
|
+
"| `origin:issue-worker` | PR was opened by the `issue-worker` agent. Eligible for auto-delegation of in-scope fixes. Human-authored PRs lack this label and will not trigger delegation unless the reviewer is invoked with `--allow-human-author`. |",
|
|
19208
|
+
"| `review:human-required` | Force human review regardless of what the policy would otherwise decide. The reviewer never enables auto-merge on a PR carrying this label. |",
|
|
19209
|
+
"| `review:auto-ok` | Force auto-merge regardless of what the policy would otherwise decide. **Also resolves outstanding `thinking_face` pushbacks** as an explicit maintainer override; the reviewer logs the override in the sticky summary. |",
|
|
19210
|
+
"| `review:awaiting-human` | Set by the reviewer when it completes its work on a `human-required` PR and is handing off the final merge decision. Cleared by a human (or by `review:auto-ok` flipping the PR back to `auto-merge` mode). |",
|
|
19211
|
+
"| `review:fixing` | Short-lived lease held by the reviewer while an `issue-worker` feedback-mode delegation is mid-run. Released automatically at the end of Phase 4 step (g). Contention on this label means a prior delegation crashed without releasing it and needs human investigation. |",
|
|
19212
|
+
"",
|
|
19213
|
+
"### Reviewing Human-Authored PRs: the `--allow-human-author` Flag",
|
|
19214
|
+
"",
|
|
19215
|
+
"By default the reviewer only **delegates** in-scope fixes on",
|
|
19216
|
+
"bot-authored PRs \u2014 those carrying the `origin:issue-worker`",
|
|
19217
|
+
"label. Running `/review-pr <n>` or `/review-prs` on a",
|
|
19218
|
+
"human-authored PR still produces a full review (reactions,",
|
|
19219
|
+
"replies, sticky summary, and auto-merge when the policy allows",
|
|
19220
|
+
"it) but skips the delegation hand-off to `issue-worker` \u2014 the",
|
|
19221
|
+
"human author is expected to apply the fixes themselves.",
|
|
19222
|
+
"",
|
|
19223
|
+
"Pass `--allow-human-author` to opt into delegation on",
|
|
19224
|
+
"human-authored PRs for a single invocation:",
|
|
19225
|
+
"",
|
|
19226
|
+
"```",
|
|
19227
|
+
"/review-pr <pr-number> --allow-human-author",
|
|
19228
|
+
"/review-prs --allow-human-author",
|
|
19229
|
+
"```",
|
|
19230
|
+
"",
|
|
19231
|
+
"The flag does **not** persist across invocations. Subsequent",
|
|
19232
|
+
"invocations return to the bot-only default and require the flag",
|
|
19233
|
+
"to be re-supplied if delegation on a human-authored PR is desired",
|
|
19234
|
+
"again."
|
|
19235
|
+
].join("\n"),
|
|
19236
|
+
platforms: {
|
|
19237
|
+
cursor: { exclude: true }
|
|
19238
|
+
},
|
|
19239
|
+
tags: ["workflow"]
|
|
19240
|
+
}
|
|
19241
|
+
],
|
|
19242
|
+
skills: [reviewPrSkill, reviewPrsSkill],
|
|
19243
|
+
subAgents: [prReviewerSubAgent],
|
|
19244
|
+
labels: [
|
|
19245
|
+
{
|
|
19246
|
+
name: "type:pr-review",
|
|
19247
|
+
color: "5319E7",
|
|
19248
|
+
description: "PR review tasks"
|
|
19249
|
+
},
|
|
19250
|
+
{
|
|
19251
|
+
name: "origin:issue-worker",
|
|
19252
|
+
color: "5319E7",
|
|
19253
|
+
description: "PR opened by the issue-worker agent"
|
|
19254
|
+
},
|
|
19255
|
+
{
|
|
19256
|
+
name: "review:auto-ok",
|
|
19257
|
+
color: "0E8A16",
|
|
19258
|
+
description: "Force auto-merge regardless of policy"
|
|
19259
|
+
},
|
|
19260
|
+
{
|
|
19261
|
+
name: "review:human-required",
|
|
19262
|
+
color: "D93F0B",
|
|
19263
|
+
description: "Force human review regardless of policy"
|
|
19264
|
+
},
|
|
19265
|
+
{
|
|
19266
|
+
name: "review:awaiting-human",
|
|
19267
|
+
color: "FBCA04",
|
|
19268
|
+
description: "Reviewer handed off; awaiting human merge decision"
|
|
19269
|
+
},
|
|
19270
|
+
{
|
|
19271
|
+
name: "review:fixing",
|
|
19272
|
+
color: "D4C5F9",
|
|
19273
|
+
description: "Short-lived lease while issue-worker applies feedback fixes"
|
|
19274
|
+
}
|
|
19275
|
+
]
|
|
19276
|
+
};
|
|
19277
|
+
}
|
|
19278
|
+
function renderPathsExemptFromSizeYaml(paths) {
|
|
19279
|
+
if (paths.length === 0) {
|
|
19280
|
+
return [" []"];
|
|
19281
|
+
}
|
|
19282
|
+
return paths.map((path8) => ` - "${path8}"`);
|
|
19283
|
+
}
|
|
19284
|
+
var prReviewBundle = buildPrReviewBundle();
|
|
18403
19285
|
|
|
18404
19286
|
// src/agent/bundles/projen.ts
|
|
18405
19287
|
var projenBundle = {
|
|
@@ -18410,6 +19292,10 @@ var projenBundle = {
|
|
|
18410
19292
|
{
|
|
18411
19293
|
name: "development-commands",
|
|
18412
19294
|
description: "Projen development commands for building, testing, linting, and validating changes",
|
|
19295
|
+
// ALWAYS scope: developers ask "how do I build/test/lint?" from
|
|
19296
|
+
// anywhere in the repo, not only when editing projen config.
|
|
19297
|
+
// Consumers that want to narrow the load can override via
|
|
19298
|
+
// `agentConfig.additionalRulePaths` or `excludeRules`.
|
|
18413
19299
|
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
18414
19300
|
content: [
|
|
18415
19301
|
"# Development Commands",
|
|
@@ -19118,7 +20004,8 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
|
|
|
19118
20004
|
"7. **Create or update the scope index page** at",
|
|
19119
20005
|
" `<SCOPE_INDEX_PAGE>` so downstream regulations link back to a",
|
|
19120
20006
|
" coherent landing page for the scope. Follow the",
|
|
19121
|
-
"
|
|
20007
|
+
" section-index-page contract in the `shared-editing-safety` rule:",
|
|
20008
|
+
" the body must carry a 1\u20132",
|
|
19122
20009
|
" paragraph contextual summary plus a grouped, linked listing of",
|
|
19123
20010
|
" every regulation in the scope (e.g. by jurisdiction). No body",
|
|
19124
20011
|
" `# Heading` \u2014 the frontmatter `title:` already renders as the",
|
|
@@ -25462,8 +26349,39 @@ function buildSoftwareProfileAnalystSubAgent(paths, issueDefaults, tier) {
|
|
|
25462
26349
|
" every profile is mapped back to the capability model regardless",
|
|
25463
26350
|
" of whether any adjacent products were surfaced.",
|
|
25464
26351
|
"",
|
|
25465
|
-
"7. **Commit
|
|
25466
|
-
"
|
|
26352
|
+
"7. **Commit any profile updates first \u2014 do not touch the",
|
|
26353
|
+
" matrix yet.** If step 4 surfaced edits to the per-product",
|
|
26354
|
+
" profile file (e.g. notes added to `## Risks / Open",
|
|
26355
|
+
" Questions` about column drift), stage and commit those",
|
|
26356
|
+
" profile changes in a focused commit. **Do not push yet,**",
|
|
26357
|
+
" and **do not** include the matrix file in this commit.",
|
|
26358
|
+
"",
|
|
26359
|
+
"8. **Defer the feature-matrix row insert to a final pre-push",
|
|
26360
|
+
" commit.** Per the `shared-editing-safety` rule's",
|
|
26361
|
+
" **Defer Shared-Index Commit to Final Pre-Push Step**",
|
|
26362
|
+
" subsection, the feature matrix is the canonical example of",
|
|
26363
|
+
" a **shared index file** \u2014 multiple software-profile",
|
|
26364
|
+
" sessions racing to append rows for different products is",
|
|
26365
|
+
" the exact contention this rule exists to mitigate. Apply",
|
|
26366
|
+
" the deferred sequence:",
|
|
26367
|
+
"",
|
|
26368
|
+
" ```bash",
|
|
26369
|
+
" git fetch origin",
|
|
26370
|
+
" git pull --rebase origin <default-branch>",
|
|
26371
|
+
" ```",
|
|
26372
|
+
"",
|
|
26373
|
+
" Re-read `<MATRIX_FILE>` from the now-up-to-date working",
|
|
26374
|
+
" tree, re-compute the deterministic insert position for the",
|
|
26375
|
+
" current product's rows (another session may have appended",
|
|
26376
|
+
" rows for a different product, or extended the segment",
|
|
26377
|
+
" columns, in the meantime), insert the rows in sort order,",
|
|
26378
|
+
" and commit the matrix edit in its **own focused commit**",
|
|
26379
|
+
" whose only file is `<MATRIX_FILE>`. Run the commit-path",
|
|
26380
|
+
" verification step (`git show HEAD:<MATRIX_FILE>` + grep",
|
|
26381
|
+
" count on the product slug as the unique marker) against",
|
|
26382
|
+
" that commit before pushing.",
|
|
26383
|
+
"",
|
|
26384
|
+
"9. **Push and close.** Push the branch and close the matrix issue.",
|
|
25467
26385
|
"",
|
|
25468
26386
|
"---",
|
|
25469
26387
|
"",
|
|
@@ -26572,7 +27490,8 @@ function buildStandardsResearchAnalystSubAgent(paths, issueDefaults) {
|
|
|
26572
27490
|
" pages.",
|
|
26573
27491
|
"",
|
|
26574
27492
|
"4. **Write the overview page** at `<OVERVIEW_PAGE>`. Follow",
|
|
26575
|
-
" the
|
|
27493
|
+
" the section-index-page contract in the",
|
|
27494
|
+
" `shared-editing-safety` rule: the body opens with a",
|
|
26576
27495
|
" 1\u20132 paragraph contextual summary of the standard and its",
|
|
26577
27496
|
" versions, and the version pages, modules, and extensions",
|
|
26578
27497
|
" sections below double as the grouped, linked listing of",
|
|
@@ -28405,6 +29324,62 @@ function bundleNameForWorkflowRule(ruleName) {
|
|
|
28405
29324
|
);
|
|
28406
29325
|
return entry?.bundle;
|
|
28407
29326
|
}
|
|
29327
|
+
function buildConventionsRegistryRule(rules) {
|
|
29328
|
+
const entries = [];
|
|
29329
|
+
for (const rule of rules) {
|
|
29330
|
+
if (rule.scope !== AGENT_RULE_SCOPE.FILE_PATTERN) {
|
|
29331
|
+
continue;
|
|
29332
|
+
}
|
|
29333
|
+
if (rule.platforms?.claude?.exclude === true) {
|
|
29334
|
+
continue;
|
|
29335
|
+
}
|
|
29336
|
+
if (!rule.filePatterns || rule.filePatterns.length === 0) {
|
|
29337
|
+
continue;
|
|
29338
|
+
}
|
|
29339
|
+
entries.push(rule);
|
|
29340
|
+
}
|
|
29341
|
+
if (entries.length === 0) {
|
|
29342
|
+
return void 0;
|
|
29343
|
+
}
|
|
29344
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
29345
|
+
const lines = [
|
|
29346
|
+
"# Conventions Registry",
|
|
29347
|
+
"",
|
|
29348
|
+
"Each row below is a `FILE_PATTERN`-scoped convention rule that",
|
|
29349
|
+
"auto-loads on demand when the agent's current file matches one",
|
|
29350
|
+
"of the listed paths. Full rule bodies live under",
|
|
29351
|
+
"`.claude/rules/<slug>.md` so they only consume context when",
|
|
29352
|
+
"they apply. Consult the matching rule before doing work in any",
|
|
29353
|
+
"of the listed surfaces.",
|
|
29354
|
+
"",
|
|
29355
|
+
"| Rule | Auto-loads when editing | Purpose |",
|
|
29356
|
+
"| --- | --- | --- |"
|
|
29357
|
+
];
|
|
29358
|
+
const MAX_PATTERNS_SHOWN = 3;
|
|
29359
|
+
for (const rule of entries) {
|
|
29360
|
+
const ruleLink = `[\`${rule.name}\`](.claude/rules/${rule.name}.md)`;
|
|
29361
|
+
const patterns = rule.filePatterns ?? [];
|
|
29362
|
+
let patternCell;
|
|
29363
|
+
if (patterns.length <= MAX_PATTERNS_SHOWN) {
|
|
29364
|
+
patternCell = patterns.map((p) => `\`${p}\``).join(", ");
|
|
29365
|
+
} else {
|
|
29366
|
+
const shown = patterns.slice(0, MAX_PATTERNS_SHOWN).map((p) => `\`${p}\``).join(", ");
|
|
29367
|
+
patternCell = `${shown}, \u2026 ([full list](.claude/rules/${rule.name}.md))`;
|
|
29368
|
+
}
|
|
29369
|
+
const purposeCell = rule.description.replace(/\s+/g, " ").replace(/\|/g, "\\|").trim();
|
|
29370
|
+
lines.push(`| ${ruleLink} | ${patternCell} | ${purposeCell} |`);
|
|
29371
|
+
}
|
|
29372
|
+
return {
|
|
29373
|
+
name: "conventions-registry",
|
|
29374
|
+
description: "Routing table for every FILE_PATTERN-scoped convention rule. Lists each rule's slug, the file patterns that auto-trigger it, and a one-line purpose so agents whose current file does not match any rule's paths: can still discover the full convention set.",
|
|
29375
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
29376
|
+
content: lines.join("\n"),
|
|
29377
|
+
platforms: {
|
|
29378
|
+
cursor: { exclude: true }
|
|
29379
|
+
},
|
|
29380
|
+
tags: ["workflow"]
|
|
29381
|
+
};
|
|
29382
|
+
}
|
|
28408
29383
|
function buildAgentRegistryRule(bundles, paths) {
|
|
28409
29384
|
const activeNames = new Set(bundles.map((b) => b.name));
|
|
28410
29385
|
const rows = AGENT_REGISTRY_ENTRIES.filter(
|
|
@@ -28820,7 +29795,7 @@ function renderPriorityRulesSection(rules) {
|
|
|
28820
29795
|
}
|
|
28821
29796
|
|
|
28822
29797
|
// src/agent/bundles/index.ts
|
|
28823
|
-
function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, defaultAgentTier = AGENT_MODEL.BALANCED, bundleAgentTiers = /* @__PURE__ */ new Map()) {
|
|
29798
|
+
function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, defaultAgentTier = AGENT_MODEL.BALANCED, bundleAgentTiers = /* @__PURE__ */ new Map(), prReviewPolicy = resolvePrReviewPolicy()) {
|
|
28824
29799
|
const tierFor = (bundle) => bundleAgentTiers.get(bundle) ?? defaultAgentTier;
|
|
28825
29800
|
return [
|
|
28826
29801
|
buildBaseBundle(paths),
|
|
@@ -28837,7 +29812,7 @@ function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAUL
|
|
|
28837
29812
|
buildMeetingAnalysisBundle(tierFor("meeting-analysis")),
|
|
28838
29813
|
agendaBundle,
|
|
28839
29814
|
orchestratorBundle,
|
|
28840
|
-
|
|
29815
|
+
buildPrReviewBundle(prReviewPolicy),
|
|
28841
29816
|
buildRequirementsAnalystBundle(paths, issueDefaults),
|
|
28842
29817
|
buildRequirementsWriterBundle(paths, issueDefaults),
|
|
28843
29818
|
buildRequirementsReviewerBundle(paths, issueDefaults),
|
|
@@ -28901,210 +29876,10 @@ function prefix(rel, entry) {
|
|
|
28901
29876
|
return path.posix.join(rel, entry);
|
|
28902
29877
|
}
|
|
28903
29878
|
|
|
28904
|
-
// src/agent/renderers/
|
|
29879
|
+
// src/agent/renderers/claude-renderer.ts
|
|
28905
29880
|
import { JsonFile as JsonFile2 } from "projen";
|
|
28906
29881
|
import { TextFile as TextFile2 } from "projen/lib/textfile";
|
|
28907
|
-
var GENERATED_MARKER = "
|
|
28908
|
-
var CursorRenderer = class _CursorRenderer {
|
|
28909
|
-
/**
|
|
28910
|
-
* Render all Cursor configuration files.
|
|
28911
|
-
*/
|
|
28912
|
-
static render(component, rules, skills, subAgents, mcpServers, settings) {
|
|
28913
|
-
_CursorRenderer.renderRules(component, rules);
|
|
28914
|
-
_CursorRenderer.renderSkills(component, skills);
|
|
28915
|
-
_CursorRenderer.renderSubAgents(component, subAgents);
|
|
28916
|
-
_CursorRenderer.renderMcpServers(component, mcpServers);
|
|
28917
|
-
_CursorRenderer.renderHooks(component, settings);
|
|
28918
|
-
_CursorRenderer.renderIgnoreFiles(component, settings);
|
|
28919
|
-
}
|
|
28920
|
-
static renderRules(component, rules) {
|
|
28921
|
-
for (const rule of rules) {
|
|
28922
|
-
if (rule.platforms?.cursor?.exclude) continue;
|
|
28923
|
-
const lines = [];
|
|
28924
|
-
const description = rule.platforms?.cursor?.description ?? rule.description;
|
|
28925
|
-
const isAlways = rule.scope === AGENT_RULE_SCOPE.ALWAYS;
|
|
28926
|
-
lines.push("---");
|
|
28927
|
-
lines.push(`description: "${description}"`);
|
|
28928
|
-
lines.push(`alwaysApply: ${isAlways}`);
|
|
28929
|
-
if (!isAlways && rule.filePatterns && rule.filePatterns.length > 0) {
|
|
28930
|
-
lines.push(`path: ${JSON.stringify([...rule.filePatterns])}`);
|
|
28931
|
-
}
|
|
28932
|
-
lines.push("---");
|
|
28933
|
-
lines.push("");
|
|
28934
|
-
lines.push(...rule.content.split("\n"));
|
|
28935
|
-
new TextFile2(component, `.cursor/rules/${rule.name}.mdc`, { lines });
|
|
28936
|
-
}
|
|
28937
|
-
}
|
|
28938
|
-
static renderSkills(component, skills) {
|
|
28939
|
-
for (const skill of skills) {
|
|
28940
|
-
if (skill.platforms?.cursor?.exclude) continue;
|
|
28941
|
-
const lines = [];
|
|
28942
|
-
lines.push("---");
|
|
28943
|
-
lines.push(`name: "${skill.name}"`);
|
|
28944
|
-
lines.push(`description: "${skill.description}"`);
|
|
28945
|
-
if (skill.disableModelInvocation) {
|
|
28946
|
-
lines.push(`disable-model-invocation: true`);
|
|
28947
|
-
}
|
|
28948
|
-
if (skill.userInvocable === false) {
|
|
28949
|
-
lines.push(`user-invocable: false`);
|
|
28950
|
-
}
|
|
28951
|
-
if (skill.context) {
|
|
28952
|
-
lines.push(`context: "${skill.context}"`);
|
|
28953
|
-
}
|
|
28954
|
-
if (skill.agent) {
|
|
28955
|
-
lines.push(`agent: "${skill.agent}"`);
|
|
28956
|
-
}
|
|
28957
|
-
if (skill.shell) {
|
|
28958
|
-
lines.push(`shell: "${skill.shell}"`);
|
|
28959
|
-
}
|
|
28960
|
-
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
28961
|
-
lines.push(`allowed-tools:`);
|
|
28962
|
-
for (const tool of skill.allowedTools) {
|
|
28963
|
-
lines.push(` - "${tool}"`);
|
|
28964
|
-
}
|
|
28965
|
-
}
|
|
28966
|
-
lines.push("---");
|
|
28967
|
-
lines.push("");
|
|
28968
|
-
lines.push(...skill.instructions.split("\n"));
|
|
28969
|
-
new TextFile2(component, `.cursor/skills/${skill.name}/SKILL.md`, {
|
|
28970
|
-
lines
|
|
28971
|
-
});
|
|
28972
|
-
if (skill.referenceFiles && skill.referenceFiles.length > 0) {
|
|
28973
|
-
for (const file of skill.referenceFiles) {
|
|
28974
|
-
new TextFile2(component, `.cursor/skills/${skill.name}/${file.path}`, {
|
|
28975
|
-
lines: file.content.split("\n")
|
|
28976
|
-
});
|
|
28977
|
-
}
|
|
28978
|
-
}
|
|
28979
|
-
}
|
|
28980
|
-
}
|
|
28981
|
-
static renderSubAgents(component, subAgents) {
|
|
28982
|
-
for (const agent of subAgents) {
|
|
28983
|
-
if (agent.platforms?.cursor?.exclude) continue;
|
|
28984
|
-
const lines = [];
|
|
28985
|
-
lines.push("---");
|
|
28986
|
-
lines.push(`name: ${agent.name}`);
|
|
28987
|
-
lines.push(`description: >-`);
|
|
28988
|
-
lines.push(` ${agent.description}`);
|
|
28989
|
-
if (agent.platforms?.cursor?.readonly) {
|
|
28990
|
-
lines.push(`readonly: true`);
|
|
28991
|
-
}
|
|
28992
|
-
if (agent.platforms?.cursor?.isBackground) {
|
|
28993
|
-
lines.push(`is_background: true`);
|
|
28994
|
-
}
|
|
28995
|
-
lines.push("---");
|
|
28996
|
-
lines.push("");
|
|
28997
|
-
lines.push(...agent.prompt.split("\n"));
|
|
28998
|
-
new TextFile2(component, `.cursor/agents/${agent.name}.md`, { lines });
|
|
28999
|
-
}
|
|
29000
|
-
}
|
|
29001
|
-
static renderMcpServers(component, mcpServers) {
|
|
29002
|
-
const serverNames = Object.keys(mcpServers);
|
|
29003
|
-
if (serverNames.length === 0) return;
|
|
29004
|
-
const obj = { mcpServers: {} };
|
|
29005
|
-
const servers = obj.mcpServers;
|
|
29006
|
-
for (const [name, config] of Object.entries(mcpServers)) {
|
|
29007
|
-
const server = {};
|
|
29008
|
-
if (config.transport) server.transport = config.transport;
|
|
29009
|
-
if (config.command) server.command = config.command;
|
|
29010
|
-
if (config.args) server.args = [...config.args];
|
|
29011
|
-
if (config.url) server.url = config.url;
|
|
29012
|
-
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
29013
|
-
server.headers = { ...config.headers };
|
|
29014
|
-
}
|
|
29015
|
-
if (config.env) server.env = { ...config.env };
|
|
29016
|
-
servers[name] = server;
|
|
29017
|
-
}
|
|
29018
|
-
new JsonFile2(component, ".cursor/mcp.json", { obj });
|
|
29019
|
-
}
|
|
29020
|
-
static renderHooks(component, settings) {
|
|
29021
|
-
if (!settings?.hooks) return;
|
|
29022
|
-
const hooks = {};
|
|
29023
|
-
const hookEntries = settings.hooks;
|
|
29024
|
-
for (const [event, actions] of Object.entries(hookEntries)) {
|
|
29025
|
-
if (actions && actions.length > 0) {
|
|
29026
|
-
hooks[event] = actions.map((h) => ({
|
|
29027
|
-
command: h.command
|
|
29028
|
-
}));
|
|
29029
|
-
}
|
|
29030
|
-
}
|
|
29031
|
-
if (Object.keys(hooks).length === 0) return;
|
|
29032
|
-
new JsonFile2(component, ".cursor/hooks.json", {
|
|
29033
|
-
obj: { version: 1, hooks }
|
|
29034
|
-
});
|
|
29035
|
-
}
|
|
29036
|
-
static renderIgnoreFiles(component, settings) {
|
|
29037
|
-
if (settings?.ignorePatterns && settings.ignorePatterns.length > 0) {
|
|
29038
|
-
new TextFile2(component, ".cursorignore", {
|
|
29039
|
-
lines: [GENERATED_MARKER, "", ...settings.ignorePatterns]
|
|
29040
|
-
});
|
|
29041
|
-
}
|
|
29042
|
-
if (settings?.indexingIgnorePatterns && settings.indexingIgnorePatterns.length > 0) {
|
|
29043
|
-
new TextFile2(component, ".cursorindexingignore", {
|
|
29044
|
-
lines: [GENERATED_MARKER, "", ...settings.indexingIgnorePatterns]
|
|
29045
|
-
});
|
|
29046
|
-
}
|
|
29047
|
-
}
|
|
29048
|
-
};
|
|
29049
|
-
|
|
29050
|
-
// src/agent/template-resolver.ts
|
|
29051
|
-
var FALLBACKS = {
|
|
29052
|
-
"repository.owner": "<owner>",
|
|
29053
|
-
"repository.name": "<repo>",
|
|
29054
|
-
"repository.defaultBranch": "main",
|
|
29055
|
-
"organization.name": "<organization>",
|
|
29056
|
-
"organization.githubOrg": "<org>",
|
|
29057
|
-
"githubProject.name": "<project-name>",
|
|
29058
|
-
"githubProject.number": "<project-number>",
|
|
29059
|
-
"githubProject.nodeId": "<project-node-id>",
|
|
29060
|
-
docsPath: "<docs-path>",
|
|
29061
|
-
// The monorepo-layout seed block is additive: when absent, the
|
|
29062
|
-
// seeded `project-context.md` template reads cleanly without it.
|
|
29063
|
-
// Fall back to an empty string so no placeholder text leaks into
|
|
29064
|
-
// rendered prompts for repos that predate the layout contract.
|
|
29065
|
-
monorepoLayoutSeedBlock: ""
|
|
29066
|
-
};
|
|
29067
|
-
var TEMPLATE_RE = /\{\{(\w+(?:\.\w+)*)\}\}/g;
|
|
29068
|
-
function getNestedValue(obj, path8) {
|
|
29069
|
-
const parts = path8.split(".");
|
|
29070
|
-
let current = obj;
|
|
29071
|
-
for (const part of parts) {
|
|
29072
|
-
if (current == null || typeof current !== "object") {
|
|
29073
|
-
return void 0;
|
|
29074
|
-
}
|
|
29075
|
-
current = current[part];
|
|
29076
|
-
}
|
|
29077
|
-
if (current == null) {
|
|
29078
|
-
return void 0;
|
|
29079
|
-
}
|
|
29080
|
-
return String(current);
|
|
29081
|
-
}
|
|
29082
|
-
function resolveTemplateVariables(template, metadata) {
|
|
29083
|
-
if (!TEMPLATE_RE.test(template)) {
|
|
29084
|
-
return { resolved: template, unresolvedKeys: [] };
|
|
29085
|
-
}
|
|
29086
|
-
const unresolvedKeys = [];
|
|
29087
|
-
TEMPLATE_RE.lastIndex = 0;
|
|
29088
|
-
const resolved = template.replace(TEMPLATE_RE, (_match, key) => {
|
|
29089
|
-
if (metadata) {
|
|
29090
|
-
const value = getNestedValue(
|
|
29091
|
-
metadata,
|
|
29092
|
-
key
|
|
29093
|
-
);
|
|
29094
|
-
if (value !== void 0) {
|
|
29095
|
-
return value;
|
|
29096
|
-
}
|
|
29097
|
-
}
|
|
29098
|
-
unresolvedKeys.push(key);
|
|
29099
|
-
return FALLBACKS[key] ?? `<${key}>`;
|
|
29100
|
-
});
|
|
29101
|
-
return { resolved, unresolvedKeys };
|
|
29102
|
-
}
|
|
29103
|
-
|
|
29104
|
-
// src/agent/renderers/claude-renderer.ts
|
|
29105
|
-
import { JsonFile as JsonFile3 } from "projen";
|
|
29106
|
-
import { TextFile as TextFile3 } from "projen/lib/textfile";
|
|
29107
|
-
var GENERATED_MARKER2 = "<!-- ~~ Generated by @codedrifters/configulator. Edits welcome \u2014 please contribute improvements back. ~~ -->";
|
|
29882
|
+
var GENERATED_MARKER = "<!-- ~~ Generated by @codedrifters/configulator. Edits welcome \u2014 please contribute improvements back. ~~ -->";
|
|
29108
29883
|
var ClaudeRenderer = class _ClaudeRenderer {
|
|
29109
29884
|
/**
|
|
29110
29885
|
* Render all Claude Code configuration files.
|
|
@@ -29129,12 +29904,12 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29129
29904
|
return target === CLAUDE_RULE_TARGET.CLAUDE_MD;
|
|
29130
29905
|
});
|
|
29131
29906
|
if (claudeMdRules.length === 0) return;
|
|
29132
|
-
const lines = [
|
|
29907
|
+
const lines = [GENERATED_MARKER, ""];
|
|
29133
29908
|
for (let i = 0; i < claudeMdRules.length; i++) {
|
|
29134
29909
|
if (i > 0) lines.push("", "---", "");
|
|
29135
29910
|
lines.push(...claudeMdRules[i].content.split("\n"));
|
|
29136
29911
|
}
|
|
29137
|
-
new
|
|
29912
|
+
new TextFile2(component, "CLAUDE.md", { lines });
|
|
29138
29913
|
}
|
|
29139
29914
|
static renderScopedRules(component, rules) {
|
|
29140
29915
|
const scopedRules = rules.filter((r) => {
|
|
@@ -29154,7 +29929,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29154
29929
|
lines.push("");
|
|
29155
29930
|
}
|
|
29156
29931
|
lines.push(...rule.content.split("\n"));
|
|
29157
|
-
new
|
|
29932
|
+
new TextFile2(component, `.claude/rules/${rule.name}.md`, { lines });
|
|
29158
29933
|
}
|
|
29159
29934
|
}
|
|
29160
29935
|
static renderSettings(component, mcpServers, settings) {
|
|
@@ -29279,7 +30054,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29279
30054
|
hasContent = true;
|
|
29280
30055
|
}
|
|
29281
30056
|
if (!hasContent) return;
|
|
29282
|
-
new
|
|
30057
|
+
new JsonFile2(component, ".claude/settings.json", { obj });
|
|
29283
30058
|
}
|
|
29284
30059
|
static buildSandboxObj(sandbox) {
|
|
29285
30060
|
const obj = {};
|
|
@@ -29366,12 +30141,12 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29366
30141
|
lines.push("---");
|
|
29367
30142
|
lines.push("");
|
|
29368
30143
|
lines.push(...skill.instructions.split("\n"));
|
|
29369
|
-
new
|
|
30144
|
+
new TextFile2(component, `.claude/skills/${skill.name}/SKILL.md`, {
|
|
29370
30145
|
lines
|
|
29371
30146
|
});
|
|
29372
30147
|
if (skill.referenceFiles && skill.referenceFiles.length > 0) {
|
|
29373
30148
|
for (const file of skill.referenceFiles) {
|
|
29374
|
-
new
|
|
30149
|
+
new TextFile2(component, `.claude/skills/${skill.name}/${file.path}`, {
|
|
29375
30150
|
lines: file.content.split("\n")
|
|
29376
30151
|
});
|
|
29377
30152
|
}
|
|
@@ -29429,7 +30204,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29429
30204
|
lines.push("---");
|
|
29430
30205
|
lines.push("");
|
|
29431
30206
|
lines.push(...agent.prompt.split("\n"));
|
|
29432
|
-
new
|
|
30207
|
+
new TextFile2(component, `.claude/agents/${agent.name}.md`, { lines });
|
|
29433
30208
|
}
|
|
29434
30209
|
}
|
|
29435
30210
|
static buildMcpServerObj(config) {
|
|
@@ -29446,7 +30221,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29446
30221
|
}
|
|
29447
30222
|
static renderProcedures(component, procedures) {
|
|
29448
30223
|
for (const proc of procedures) {
|
|
29449
|
-
new
|
|
30224
|
+
new TextFile2(component, `.claude/procedures/${proc.name}`, {
|
|
29450
30225
|
lines: proc.content.split("\n"),
|
|
29451
30226
|
executable: true
|
|
29452
30227
|
});
|
|
@@ -29473,7 +30248,7 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29473
30248
|
lines.push("---");
|
|
29474
30249
|
lines.push("");
|
|
29475
30250
|
lines.push(...command.content.split("\n"));
|
|
29476
|
-
new
|
|
30251
|
+
new TextFile2(component, `.claude/commands/${command.name}.md`, { lines });
|
|
29477
30252
|
}
|
|
29478
30253
|
}
|
|
29479
30254
|
/**
|
|
@@ -29485,6 +30260,206 @@ var ClaudeRenderer = class _ClaudeRenderer {
|
|
|
29485
30260
|
}
|
|
29486
30261
|
};
|
|
29487
30262
|
|
|
30263
|
+
// src/agent/renderers/cursor-renderer.ts
|
|
30264
|
+
import { JsonFile as JsonFile3 } from "projen";
|
|
30265
|
+
import { TextFile as TextFile3 } from "projen/lib/textfile";
|
|
30266
|
+
var GENERATED_MARKER2 = "# ~~ Generated by @codedrifters/configulator. Edits welcome \u2014 please contribute improvements back. ~~";
|
|
30267
|
+
var CursorRenderer = class _CursorRenderer {
|
|
30268
|
+
/**
|
|
30269
|
+
* Render all Cursor configuration files.
|
|
30270
|
+
*/
|
|
30271
|
+
static render(component, rules, skills, subAgents, mcpServers, settings) {
|
|
30272
|
+
_CursorRenderer.renderRules(component, rules);
|
|
30273
|
+
_CursorRenderer.renderSkills(component, skills);
|
|
30274
|
+
_CursorRenderer.renderSubAgents(component, subAgents);
|
|
30275
|
+
_CursorRenderer.renderMcpServers(component, mcpServers);
|
|
30276
|
+
_CursorRenderer.renderHooks(component, settings);
|
|
30277
|
+
_CursorRenderer.renderIgnoreFiles(component, settings);
|
|
30278
|
+
}
|
|
30279
|
+
static renderRules(component, rules) {
|
|
30280
|
+
for (const rule of rules) {
|
|
30281
|
+
if (rule.platforms?.cursor?.exclude) continue;
|
|
30282
|
+
const lines = [];
|
|
30283
|
+
const description = rule.platforms?.cursor?.description ?? rule.description;
|
|
30284
|
+
const isAlways = rule.scope === AGENT_RULE_SCOPE.ALWAYS;
|
|
30285
|
+
lines.push("---");
|
|
30286
|
+
lines.push(`description: "${description}"`);
|
|
30287
|
+
lines.push(`alwaysApply: ${isAlways}`);
|
|
30288
|
+
if (!isAlways && rule.filePatterns && rule.filePatterns.length > 0) {
|
|
30289
|
+
lines.push(`path: ${JSON.stringify([...rule.filePatterns])}`);
|
|
30290
|
+
}
|
|
30291
|
+
lines.push("---");
|
|
30292
|
+
lines.push("");
|
|
30293
|
+
lines.push(...rule.content.split("\n"));
|
|
30294
|
+
new TextFile3(component, `.cursor/rules/${rule.name}.mdc`, { lines });
|
|
30295
|
+
}
|
|
30296
|
+
}
|
|
30297
|
+
static renderSkills(component, skills) {
|
|
30298
|
+
for (const skill of skills) {
|
|
30299
|
+
if (skill.platforms?.cursor?.exclude) continue;
|
|
30300
|
+
const lines = [];
|
|
30301
|
+
lines.push("---");
|
|
30302
|
+
lines.push(`name: "${skill.name}"`);
|
|
30303
|
+
lines.push(`description: "${skill.description}"`);
|
|
30304
|
+
if (skill.disableModelInvocation) {
|
|
30305
|
+
lines.push(`disable-model-invocation: true`);
|
|
30306
|
+
}
|
|
30307
|
+
if (skill.userInvocable === false) {
|
|
30308
|
+
lines.push(`user-invocable: false`);
|
|
30309
|
+
}
|
|
30310
|
+
if (skill.context) {
|
|
30311
|
+
lines.push(`context: "${skill.context}"`);
|
|
30312
|
+
}
|
|
30313
|
+
if (skill.agent) {
|
|
30314
|
+
lines.push(`agent: "${skill.agent}"`);
|
|
30315
|
+
}
|
|
30316
|
+
if (skill.shell) {
|
|
30317
|
+
lines.push(`shell: "${skill.shell}"`);
|
|
30318
|
+
}
|
|
30319
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
30320
|
+
lines.push(`allowed-tools:`);
|
|
30321
|
+
for (const tool of skill.allowedTools) {
|
|
30322
|
+
lines.push(` - "${tool}"`);
|
|
30323
|
+
}
|
|
30324
|
+
}
|
|
30325
|
+
lines.push("---");
|
|
30326
|
+
lines.push("");
|
|
30327
|
+
lines.push(...skill.instructions.split("\n"));
|
|
30328
|
+
new TextFile3(component, `.cursor/skills/${skill.name}/SKILL.md`, {
|
|
30329
|
+
lines
|
|
30330
|
+
});
|
|
30331
|
+
if (skill.referenceFiles && skill.referenceFiles.length > 0) {
|
|
30332
|
+
for (const file of skill.referenceFiles) {
|
|
30333
|
+
new TextFile3(component, `.cursor/skills/${skill.name}/${file.path}`, {
|
|
30334
|
+
lines: file.content.split("\n")
|
|
30335
|
+
});
|
|
30336
|
+
}
|
|
30337
|
+
}
|
|
30338
|
+
}
|
|
30339
|
+
}
|
|
30340
|
+
static renderSubAgents(component, subAgents) {
|
|
30341
|
+
for (const agent of subAgents) {
|
|
30342
|
+
if (agent.platforms?.cursor?.exclude) continue;
|
|
30343
|
+
const lines = [];
|
|
30344
|
+
lines.push("---");
|
|
30345
|
+
lines.push(`name: ${agent.name}`);
|
|
30346
|
+
lines.push(`description: >-`);
|
|
30347
|
+
lines.push(` ${agent.description}`);
|
|
30348
|
+
if (agent.platforms?.cursor?.readonly) {
|
|
30349
|
+
lines.push(`readonly: true`);
|
|
30350
|
+
}
|
|
30351
|
+
if (agent.platforms?.cursor?.isBackground) {
|
|
30352
|
+
lines.push(`is_background: true`);
|
|
30353
|
+
}
|
|
30354
|
+
lines.push("---");
|
|
30355
|
+
lines.push("");
|
|
30356
|
+
lines.push(...agent.prompt.split("\n"));
|
|
30357
|
+
new TextFile3(component, `.cursor/agents/${agent.name}.md`, { lines });
|
|
30358
|
+
}
|
|
30359
|
+
}
|
|
30360
|
+
static renderMcpServers(component, mcpServers) {
|
|
30361
|
+
const serverNames = Object.keys(mcpServers);
|
|
30362
|
+
if (serverNames.length === 0) return;
|
|
30363
|
+
const obj = { mcpServers: {} };
|
|
30364
|
+
const servers = obj.mcpServers;
|
|
30365
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
30366
|
+
const server = {};
|
|
30367
|
+
if (config.transport) server.transport = config.transport;
|
|
30368
|
+
if (config.command) server.command = config.command;
|
|
30369
|
+
if (config.args) server.args = [...config.args];
|
|
30370
|
+
if (config.url) server.url = config.url;
|
|
30371
|
+
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
30372
|
+
server.headers = { ...config.headers };
|
|
30373
|
+
}
|
|
30374
|
+
if (config.env) server.env = { ...config.env };
|
|
30375
|
+
servers[name] = server;
|
|
30376
|
+
}
|
|
30377
|
+
new JsonFile3(component, ".cursor/mcp.json", { obj });
|
|
30378
|
+
}
|
|
30379
|
+
static renderHooks(component, settings) {
|
|
30380
|
+
if (!settings?.hooks) return;
|
|
30381
|
+
const hooks = {};
|
|
30382
|
+
const hookEntries = settings.hooks;
|
|
30383
|
+
for (const [event, actions] of Object.entries(hookEntries)) {
|
|
30384
|
+
if (actions && actions.length > 0) {
|
|
30385
|
+
hooks[event] = actions.map((h) => ({
|
|
30386
|
+
command: h.command
|
|
30387
|
+
}));
|
|
30388
|
+
}
|
|
30389
|
+
}
|
|
30390
|
+
if (Object.keys(hooks).length === 0) return;
|
|
30391
|
+
new JsonFile3(component, ".cursor/hooks.json", {
|
|
30392
|
+
obj: { version: 1, hooks }
|
|
30393
|
+
});
|
|
30394
|
+
}
|
|
30395
|
+
static renderIgnoreFiles(component, settings) {
|
|
30396
|
+
if (settings?.ignorePatterns && settings.ignorePatterns.length > 0) {
|
|
30397
|
+
new TextFile3(component, ".cursorignore", {
|
|
30398
|
+
lines: [GENERATED_MARKER2, "", ...settings.ignorePatterns]
|
|
30399
|
+
});
|
|
30400
|
+
}
|
|
30401
|
+
if (settings?.indexingIgnorePatterns && settings.indexingIgnorePatterns.length > 0) {
|
|
30402
|
+
new TextFile3(component, ".cursorindexingignore", {
|
|
30403
|
+
lines: [GENERATED_MARKER2, "", ...settings.indexingIgnorePatterns]
|
|
30404
|
+
});
|
|
30405
|
+
}
|
|
30406
|
+
}
|
|
30407
|
+
};
|
|
30408
|
+
|
|
30409
|
+
// src/agent/template-resolver.ts
|
|
30410
|
+
var FALLBACKS = {
|
|
30411
|
+
"repository.owner": "<owner>",
|
|
30412
|
+
"repository.name": "<repo>",
|
|
30413
|
+
"repository.defaultBranch": "main",
|
|
30414
|
+
"organization.name": "<organization>",
|
|
30415
|
+
"organization.githubOrg": "<org>",
|
|
30416
|
+
"githubProject.name": "<project-name>",
|
|
30417
|
+
"githubProject.number": "<project-number>",
|
|
30418
|
+
"githubProject.nodeId": "<project-node-id>",
|
|
30419
|
+
docsPath: "<docs-path>",
|
|
30420
|
+
// The monorepo-layout seed block is additive: when absent, the
|
|
30421
|
+
// seeded `project-context.md` template reads cleanly without it.
|
|
30422
|
+
// Fall back to an empty string so no placeholder text leaks into
|
|
30423
|
+
// rendered prompts for repos that predate the layout contract.
|
|
30424
|
+
monorepoLayoutSeedBlock: ""
|
|
30425
|
+
};
|
|
30426
|
+
var TEMPLATE_RE = /\{\{(\w+(?:\.\w+)*)\}\}/g;
|
|
30427
|
+
function getNestedValue(obj, path8) {
|
|
30428
|
+
const parts = path8.split(".");
|
|
30429
|
+
let current = obj;
|
|
30430
|
+
for (const part of parts) {
|
|
30431
|
+
if (current == null || typeof current !== "object") {
|
|
30432
|
+
return void 0;
|
|
30433
|
+
}
|
|
30434
|
+
current = current[part];
|
|
30435
|
+
}
|
|
30436
|
+
if (current == null) {
|
|
30437
|
+
return void 0;
|
|
30438
|
+
}
|
|
30439
|
+
return String(current);
|
|
30440
|
+
}
|
|
30441
|
+
function resolveTemplateVariables(template, metadata) {
|
|
30442
|
+
if (!TEMPLATE_RE.test(template)) {
|
|
30443
|
+
return { resolved: template, unresolvedKeys: [] };
|
|
30444
|
+
}
|
|
30445
|
+
const unresolvedKeys = [];
|
|
30446
|
+
TEMPLATE_RE.lastIndex = 0;
|
|
30447
|
+
const resolved = template.replace(TEMPLATE_RE, (_match, key) => {
|
|
30448
|
+
if (metadata) {
|
|
30449
|
+
const value = getNestedValue(
|
|
30450
|
+
metadata,
|
|
30451
|
+
key
|
|
30452
|
+
);
|
|
30453
|
+
if (value !== void 0) {
|
|
30454
|
+
return value;
|
|
30455
|
+
}
|
|
30456
|
+
}
|
|
30457
|
+
unresolvedKeys.push(key);
|
|
30458
|
+
return FALLBACKS[key] ?? `<${key}>`;
|
|
30459
|
+
});
|
|
30460
|
+
return { resolved, unresolvedKeys };
|
|
30461
|
+
}
|
|
30462
|
+
|
|
29488
30463
|
// src/agent/renderers/codex-renderer.ts
|
|
29489
30464
|
var CodexRenderer = class {
|
|
29490
30465
|
static render(_component, _rules, _skills, _subAgents) {
|
|
@@ -29701,6 +30676,7 @@ var SHARED_EDITING_BUNDLE_HOOKS = [
|
|
|
29701
30676
|
["customer-profile-workflow", "customer-profile"],
|
|
29702
30677
|
["industry-discovery-workflow", "industry-discovery"],
|
|
29703
30678
|
["meeting-agenda-workflow", "agenda"],
|
|
30679
|
+
["meeting-processing-workflow", "meeting-analyst"],
|
|
29704
30680
|
["people-profile-workflow", "people-profile"],
|
|
29705
30681
|
["regulatory-research-workflow", "regulatory-research"],
|
|
29706
30682
|
["requirements-reviewer-workflow", "requirements-reviewer"],
|
|
@@ -29890,7 +30866,8 @@ var AgentConfig = class _AgentConfig extends Component8 {
|
|
|
29890
30866
|
this.resolvedPaths,
|
|
29891
30867
|
resolveIssueDefaults(this.options.issueDefaults),
|
|
29892
30868
|
resolveDefaultAgentTier(this.options),
|
|
29893
|
-
resolveBundleAgentTiers(this.options)
|
|
30869
|
+
resolveBundleAgentTiers(this.options),
|
|
30870
|
+
resolvePrReviewPolicy(this.options.prReviewPolicy)
|
|
29894
30871
|
);
|
|
29895
30872
|
}
|
|
29896
30873
|
return this.cachedBundles;
|
|
@@ -29932,6 +30909,7 @@ var AgentConfig = class _AgentConfig extends Component8 {
|
|
|
29932
30909
|
super.preSynthesize();
|
|
29933
30910
|
validateAgentTierConfig(this.options.tiers);
|
|
29934
30911
|
validateScopeGateConfig(this.options.scopeGate);
|
|
30912
|
+
validatePrReviewPolicyConfig(this.options.prReviewPolicy);
|
|
29935
30913
|
const resolvedRunRatio = resolveRunRatio(this.options.runRatio);
|
|
29936
30914
|
if (resolvedRunRatio.enabled) {
|
|
29937
30915
|
this.project.gitignore.addPatterns(`/${resolvedRunRatio.stateFilePath}`);
|
|
@@ -30124,6 +31102,20 @@ ${extra}`
|
|
|
30124
31102
|
}
|
|
30125
31103
|
}
|
|
30126
31104
|
}
|
|
31105
|
+
if (this.options.additionalRulePaths) {
|
|
31106
|
+
for (const [name, extraPaths] of Object.entries(
|
|
31107
|
+
this.options.additionalRulePaths
|
|
31108
|
+
)) {
|
|
31109
|
+
if (extraPaths.length === 0) continue;
|
|
31110
|
+
const existing = ruleMap.get(name);
|
|
31111
|
+
if (!existing) continue;
|
|
31112
|
+
if (existing.scope !== AGENT_RULE_SCOPE.FILE_PATTERN) continue;
|
|
31113
|
+
ruleMap.set(name, {
|
|
31114
|
+
...existing,
|
|
31115
|
+
filePatterns: [...existing.filePatterns ?? [], ...extraPaths]
|
|
31116
|
+
});
|
|
31117
|
+
}
|
|
31118
|
+
}
|
|
30127
31119
|
if (this.options.priorityRules && this.options.priorityRules.length > 0) {
|
|
30128
31120
|
const issueLabelRule = ruleMap.get("issue-label-conventions");
|
|
30129
31121
|
if (issueLabelRule) {
|
|
@@ -30330,9 +31322,6 @@ ${hook}`
|
|
|
30330
31322
|
}
|
|
30331
31323
|
}
|
|
30332
31324
|
}
|
|
30333
|
-
if (!hasAnyDocsEmittingBundle(excludedBundleNames)) {
|
|
30334
|
-
ruleMap.delete("stub-index-convention");
|
|
30335
|
-
}
|
|
30336
31325
|
if (injectBundleHooks && resolvedIssueTemplatesForRules.enabled && hasDownstreamBundles) {
|
|
30337
31326
|
for (const [ruleName, label] of ISSUE_TEMPLATES_BUNDLE_HOOKS) {
|
|
30338
31327
|
const existing = ruleMap.get(ruleName);
|
|
@@ -30449,6 +31438,12 @@ ${meetingsSection}`;
|
|
|
30449
31438
|
});
|
|
30450
31439
|
}
|
|
30451
31440
|
}
|
|
31441
|
+
const conventionsRegistryRule = buildConventionsRegistryRule(
|
|
31442
|
+
ruleMap.values()
|
|
31443
|
+
);
|
|
31444
|
+
if (conventionsRegistryRule) {
|
|
31445
|
+
ruleMap.set(conventionsRegistryRule.name, conventionsRegistryRule);
|
|
31446
|
+
}
|
|
30452
31447
|
return [...ruleMap.values()].sort((a, b) => {
|
|
30453
31448
|
if (a.name === "project-overview") return -1;
|
|
30454
31449
|
if (b.name === "project-overview") return 1;
|
|
@@ -36470,6 +37465,7 @@ export {
|
|
|
36470
37465
|
DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE,
|
|
36471
37466
|
DEFAULT_OFF_PEAK_CRON_EXAMPLE,
|
|
36472
37467
|
DEFAULT_PARTIAL_UNBLOCK_COMMENT_TEMPLATE,
|
|
37468
|
+
DEFAULT_PATHS_EXEMPT_FROM_SIZE,
|
|
36473
37469
|
DEFAULT_PRIORITY_LABELS,
|
|
36474
37470
|
DEFAULT_PRODUCT_CONTEXT_PATH,
|
|
36475
37471
|
DEFAULT_PROGRESS_FILES_ENABLED,
|
|
@@ -36572,6 +37568,7 @@ export {
|
|
|
36572
37568
|
buildMeetingAnalysisBundle,
|
|
36573
37569
|
buildOrchestratorConventionsContent,
|
|
36574
37570
|
buildPeopleProfileBundle,
|
|
37571
|
+
buildPrReviewBundle,
|
|
36575
37572
|
buildRegulatoryResearchBundle,
|
|
36576
37573
|
buildReport,
|
|
36577
37574
|
buildRequirementsAnalystBundle,
|
|
@@ -36681,7 +37678,6 @@ export {
|
|
|
36681
37678
|
renderSkillEvalsRuleContent,
|
|
36682
37679
|
renderSkillEvalsRunnerScript,
|
|
36683
37680
|
renderSourceTierExamples,
|
|
36684
|
-
renderStubIndexConventionRuleContent,
|
|
36685
37681
|
renderTemporalFramingCheckerScript,
|
|
36686
37682
|
renderTemporalFramingRuleContent,
|
|
36687
37683
|
renderUnblockDependentsScript,
|
|
@@ -36702,6 +37698,7 @@ export {
|
|
|
36702
37698
|
resolveOrchestratorAssets,
|
|
36703
37699
|
resolveOutdirFromPackageName,
|
|
36704
37700
|
resolveOverrideForLabels,
|
|
37701
|
+
resolvePrReviewPolicy,
|
|
36705
37702
|
resolveProgressFiles,
|
|
36706
37703
|
resolveReactViteSiteProjectOutdir,
|
|
36707
37704
|
resolveRunRatio,
|
|
@@ -36725,6 +37722,7 @@ export {
|
|
|
36725
37722
|
validateIssueDefaultsConfig,
|
|
36726
37723
|
validateIssueTemplatesConfig,
|
|
36727
37724
|
validateMonorepoLayout,
|
|
37725
|
+
validatePrReviewPolicyConfig,
|
|
36728
37726
|
validateProgressFilesConfig,
|
|
36729
37727
|
validateRunRatioConfig,
|
|
36730
37728
|
validateScheduledTasksConfig,
|