@codedrifters/configulator 0.0.406 → 0.0.407
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 +285 -160
- package/lib/index.d.ts +286 -161
- package/lib/index.js +482 -89
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +476 -89
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -277,6 +277,7 @@ __export(index_exports, {
|
|
|
277
277
|
DOCS_SYNC_AUDIT_SCHEMA_VERSION: () => DOCS_SYNC_AUDIT_SCHEMA_VERSION,
|
|
278
278
|
GITHUB_ISSUE_TYPES: () => GITHUB_ISSUE_TYPES,
|
|
279
279
|
GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX: () => GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX,
|
|
280
|
+
ISSUE_TEMPLATES_GENERATED_SUFFIX: () => ISSUE_TEMPLATES_GENERATED_SUFFIX,
|
|
280
281
|
JsiiFaker: () => JsiiFaker,
|
|
281
282
|
LAYOUT_ENFORCEMENT: () => LAYOUT_ENFORCEMENT,
|
|
282
283
|
LAYOUT_ROOT_BY_PROJECT_TYPE: () => LAYOUT_ROOT_BY_PROJECT_TYPE,
|
|
@@ -372,6 +373,7 @@ __export(index_exports, {
|
|
|
372
373
|
checkLinksProcedure: () => checkLinksProcedure,
|
|
373
374
|
classifyIssueScope: () => classifyIssueScope,
|
|
374
375
|
classifyRun: () => classifyRun,
|
|
376
|
+
collectIssueTemplateRecipeStubs: () => collectIssueTemplateRecipeStubs,
|
|
375
377
|
companyProfileBundle: () => companyProfileBundle,
|
|
376
378
|
compileFencedSamples: () => compileFencedSamples,
|
|
377
379
|
createApiDiffCheck: () => createApiDiffCheck,
|
|
@@ -397,6 +399,8 @@ __export(index_exports, {
|
|
|
397
399
|
isScheduledTaskOwnedByExcluded: () => isScheduledTaskOwnedByExcluded,
|
|
398
400
|
isSuppressedWorkflowRule: () => isSuppressedWorkflowRule,
|
|
399
401
|
isTypeLabelOwnedByExcluded: () => isTypeLabelOwnedByExcluded,
|
|
402
|
+
issueTemplatesChildGlob: () => issueTemplatesChildGlob,
|
|
403
|
+
issueTemplatesGeneratedPath: () => issueTemplatesGeneratedPath,
|
|
400
404
|
jestBundle: () => jestBundle,
|
|
401
405
|
labelsForPhase: () => labelsForPhase,
|
|
402
406
|
maintenanceAuditBundle: () => maintenanceAuditBundle,
|
|
@@ -447,8 +451,10 @@ __export(index_exports, {
|
|
|
447
451
|
renderFocusSection: () => renderFocusSection,
|
|
448
452
|
renderGithubIssueTypeSection: () => renderGithubIssueTypeSection,
|
|
449
453
|
renderGithubIssueTypeSectionLines: () => renderGithubIssueTypeSectionLines,
|
|
454
|
+
renderIssueTemplateLabelsCheckerScript: () => renderIssueTemplateLabelsCheckerScript,
|
|
450
455
|
renderIssueTemplatesBundleHook: () => renderIssueTemplatesBundleHook,
|
|
451
456
|
renderIssueTemplatesCheckerScript: () => renderIssueTemplatesCheckerScript,
|
|
457
|
+
renderIssueTemplatesGeneratedPage: () => renderIssueTemplatesGeneratedPage,
|
|
452
458
|
renderIssueTemplatesRuleContent: () => renderIssueTemplatesRuleContent,
|
|
453
459
|
renderIssueTemplatesStarterPage: () => renderIssueTemplatesStarterPage,
|
|
454
460
|
renderIssueTypeAssignmentBlanket: () => renderIssueTypeAssignmentBlanket,
|
|
@@ -2199,12 +2205,111 @@ var awsCdkBundle = {
|
|
|
2199
2205
|
]
|
|
2200
2206
|
};
|
|
2201
2207
|
|
|
2208
|
+
// src/agent/bundles/issue-defaults.ts
|
|
2209
|
+
var VALID_STATUS_VALUES = [
|
|
2210
|
+
"ready",
|
|
2211
|
+
"blocked",
|
|
2212
|
+
"in-progress",
|
|
2213
|
+
"ready-for-review",
|
|
2214
|
+
"needs-attention",
|
|
2215
|
+
"done",
|
|
2216
|
+
"deferred"
|
|
2217
|
+
];
|
|
2218
|
+
var VALID_PRIORITY_VALUES = [
|
|
2219
|
+
"critical",
|
|
2220
|
+
"high",
|
|
2221
|
+
"medium",
|
|
2222
|
+
"low",
|
|
2223
|
+
"trivial"
|
|
2224
|
+
];
|
|
2225
|
+
var DEFAULT_ISSUE_STATUS = "ready";
|
|
2226
|
+
var DEFAULT_ISSUE_PRIORITY = "medium";
|
|
2227
|
+
var DEFAULT_RESOLVED_ISSUE_DEFAULTS = {
|
|
2228
|
+
defaults: {
|
|
2229
|
+
status: DEFAULT_ISSUE_STATUS,
|
|
2230
|
+
priority: DEFAULT_ISSUE_PRIORITY
|
|
2231
|
+
},
|
|
2232
|
+
overrides: {}
|
|
2233
|
+
};
|
|
2234
|
+
function resolveIssueDefaults(config) {
|
|
2235
|
+
if (config === void 0) {
|
|
2236
|
+
return DEFAULT_RESOLVED_ISSUE_DEFAULTS;
|
|
2237
|
+
}
|
|
2238
|
+
const overrides = {};
|
|
2239
|
+
for (const [phaseLabel, override] of Object.entries(config)) {
|
|
2240
|
+
assertValidPhaseLabel(phaseLabel);
|
|
2241
|
+
assertValidOverride(phaseLabel, override);
|
|
2242
|
+
overrides[phaseLabel] = {
|
|
2243
|
+
status: override.status ?? DEFAULT_ISSUE_STATUS,
|
|
2244
|
+
priority: override.priority ?? DEFAULT_ISSUE_PRIORITY
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
return {
|
|
2248
|
+
defaults: {
|
|
2249
|
+
status: DEFAULT_ISSUE_STATUS,
|
|
2250
|
+
priority: DEFAULT_ISSUE_PRIORITY
|
|
2251
|
+
},
|
|
2252
|
+
overrides
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
function validateIssueDefaultsConfig(config) {
|
|
2256
|
+
return resolveIssueDefaults(config);
|
|
2257
|
+
}
|
|
2258
|
+
function labelsForPhase(resolved, phaseLabel) {
|
|
2259
|
+
return resolved.overrides[phaseLabel] ?? resolved.defaults;
|
|
2260
|
+
}
|
|
2261
|
+
function assertValidPhaseLabel(phaseLabel) {
|
|
2262
|
+
if (typeof phaseLabel !== "string" || phaseLabel.trim() === "") {
|
|
2263
|
+
throw new Error(
|
|
2264
|
+
"AgentConfigOptions.issueDefaults: phase-label keys must be non-empty strings (e.g. `people:research`)."
|
|
2265
|
+
);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
function assertValidOverride(phaseLabel, override) {
|
|
2269
|
+
if (override === null || typeof override !== "object" || Array.isArray(override)) {
|
|
2270
|
+
throw new Error(
|
|
2271
|
+
`AgentConfigOptions.issueDefaults["${phaseLabel}"] must be an object with optional \`status\` and \`priority\` fields.`
|
|
2272
|
+
);
|
|
2273
|
+
}
|
|
2274
|
+
const { status, priority } = override;
|
|
2275
|
+
if (status === void 0 && priority === void 0) {
|
|
2276
|
+
throw new Error(
|
|
2277
|
+
`AgentConfigOptions.issueDefaults["${phaseLabel}"] must declare at least one of \`status\` or \`priority\`. Empty entries are rejected because they are almost always a typo on the field name.`
|
|
2278
|
+
);
|
|
2279
|
+
}
|
|
2280
|
+
if (status !== void 0 && !VALID_STATUS_VALUES.includes(status)) {
|
|
2281
|
+
throw new Error(
|
|
2282
|
+
`AgentConfigOptions.issueDefaults["${phaseLabel}"].status="${status}" is not a recognised status value. Allowed values: ${VALID_STATUS_VALUES.join(", ")}.`
|
|
2283
|
+
);
|
|
2284
|
+
}
|
|
2285
|
+
if (priority !== void 0 && !VALID_PRIORITY_VALUES.includes(priority)) {
|
|
2286
|
+
throw new Error(
|
|
2287
|
+
`AgentConfigOptions.issueDefaults["${phaseLabel}"].priority="${priority}" is not a recognised priority value. Allowed values: ${VALID_PRIORITY_VALUES.join(", ")}.`
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2202
2292
|
// src/agent/bundles/issue-templates.ts
|
|
2203
2293
|
var DEFAULT_ISSUE_TEMPLATES_ENABLED = true;
|
|
2204
2294
|
var DEFAULT_ISSUE_TEMPLATES_PATH = "docs/src/content/docs/agents/issue-templates.md";
|
|
2205
2295
|
var DEFAULT_ISSUE_TEMPLATES_BUNDLE_PATH_PATTERNS = [".claude/agents/**/*.md", ".claude/skills/**/*.md"];
|
|
2206
2296
|
var DEFAULT_ISSUE_TEMPLATES_EMIT_CHECKER = false;
|
|
2207
2297
|
var DEFAULT_ISSUE_TEMPLATES_EMIT_STARTER = false;
|
|
2298
|
+
var ISSUE_TEMPLATES_GENERATED_SUFFIX = "-generated";
|
|
2299
|
+
function issueTemplatesGeneratedPath(templatesPath) {
|
|
2300
|
+
const slash = templatesPath.lastIndexOf("/");
|
|
2301
|
+
const dot = templatesPath.lastIndexOf(".");
|
|
2302
|
+
if (dot > slash) {
|
|
2303
|
+
return `${templatesPath.slice(0, dot)}${ISSUE_TEMPLATES_GENERATED_SUFFIX}${templatesPath.slice(dot)}`;
|
|
2304
|
+
}
|
|
2305
|
+
return `${templatesPath}${ISSUE_TEMPLATES_GENERATED_SUFFIX}`;
|
|
2306
|
+
}
|
|
2307
|
+
function issueTemplatesChildGlob(templatesPath) {
|
|
2308
|
+
const slash = templatesPath.lastIndexOf("/");
|
|
2309
|
+
const dot = templatesPath.lastIndexOf(".");
|
|
2310
|
+
const stem = dot > slash ? templatesPath.slice(0, dot) : templatesPath;
|
|
2311
|
+
return `${stem}/*.md`;
|
|
2312
|
+
}
|
|
2208
2313
|
var DEFAULT_ISSUE_TEMPLATES_REQUIRE_REFERENCE = true;
|
|
2209
2314
|
function resolveIssueTemplates(config) {
|
|
2210
2315
|
const templatesPath = config?.templatesPath ?? DEFAULT_ISSUE_TEMPLATES_PATH;
|
|
@@ -2288,7 +2393,40 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
|
|
|
2288
2393
|
"issue kind appends one section; retiring a kind removes one. The",
|
|
2289
2394
|
"label taxonomy, body template shape, and placeholder conventions",
|
|
2290
2395
|
"are shared across every section.",
|
|
2291
|
-
""
|
|
2396
|
+
""
|
|
2397
|
+
];
|
|
2398
|
+
if (it.emitStarterDoc) {
|
|
2399
|
+
lines.push(
|
|
2400
|
+
"## Scaffolded Starting Point",
|
|
2401
|
+
"",
|
|
2402
|
+
"The page is hand-authored, but it does not start empty. Two files",
|
|
2403
|
+
"ship together:",
|
|
2404
|
+
"",
|
|
2405
|
+
`1. \`${it.templatesPath}\` \u2014 **written once**, then yours. Titles,`,
|
|
2406
|
+
" bodies, and the callers list are hand-authored here and never",
|
|
2407
|
+
" overwritten.",
|
|
2408
|
+
`2. \`${issueTemplatesGeneratedPath(it.templatesPath)}\` \u2014`,
|
|
2409
|
+
" **regenerated on every synth**. One",
|
|
2410
|
+
" `## Template: <phase-label>` stub per phase label the active",
|
|
2411
|
+
" bundles contribute to the label registry, with the phase label,",
|
|
2412
|
+
" the `type:<bundle>` label its owning bundle declares, and the",
|
|
2413
|
+
" configured `status:*` / `priority:*` defaults pre-filled.",
|
|
2414
|
+
"",
|
|
2415
|
+
"Only the **label set** is generated. Title and body stay",
|
|
2416
|
+
"angle-bracket placeholders, so the scaffold answers *which labels*",
|
|
2417
|
+
"without dictating *what the issue says*. Copy a stub into the",
|
|
2418
|
+
"hand-authored page, flesh out its body, and leave the label block",
|
|
2419
|
+
"as generated.",
|
|
2420
|
+
"",
|
|
2421
|
+
"The split is load-bearing. A write-once scaffold alone freezes",
|
|
2422
|
+
"every consumer on whatever skeleton shipped the day they adopted",
|
|
2423
|
+
"the convention \u2014 a new phase label minted upstream never reaches",
|
|
2424
|
+
"them. Regenerating the label half fixes that without ever",
|
|
2425
|
+
"clobbering an authored body.",
|
|
2426
|
+
""
|
|
2427
|
+
);
|
|
2428
|
+
}
|
|
2429
|
+
lines.push(
|
|
2292
2430
|
"## Reference-Don't-Inline Rule",
|
|
2293
2431
|
"",
|
|
2294
2432
|
`Every bundle rule, agent prompt, or skill instruction that files a`,
|
|
@@ -2359,8 +2497,12 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
|
|
|
2359
2497
|
" placeholders (`<Organization Name>`, `<slug>`) that match",
|
|
2360
2498
|
" existing templates. Consistency across sections lets callers",
|
|
2361
2499
|
" substitute without re-reading the body every time.",
|
|
2500
|
+
"5. **Does the recipe assign a GitHub issue type?** `gh issue",
|
|
2501
|
+
" create` cannot set it and the `type:*` label does not either, so",
|
|
2502
|
+
" every section ends with the follow-up call. An issue that skips",
|
|
2503
|
+
" it stays untyped forever.",
|
|
2362
2504
|
""
|
|
2363
|
-
|
|
2505
|
+
);
|
|
2364
2506
|
if (it.emitChecker) {
|
|
2365
2507
|
lines.push(
|
|
2366
2508
|
"## Automated Lint",
|
|
@@ -2391,6 +2533,33 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
|
|
|
2391
2533
|
"(which documents the conventional `gh issue create` call shape",
|
|
2392
2534
|
"as taxonomy, not a downstream template). All other bundle",
|
|
2393
2535
|
"files in the pattern set are in scope.",
|
|
2536
|
+
"",
|
|
2537
|
+
"### Recipe label correctness",
|
|
2538
|
+
"",
|
|
2539
|
+
"A companion lint,",
|
|
2540
|
+
"`.claude/procedures/check-issue-template-labels.sh`, checks the",
|
|
2541
|
+
"recipes themselves rather than where they live. It walks the",
|
|
2542
|
+
"templates page, its router-style child pages, and the generated",
|
|
2543
|
+
"label-set companion, and fails non-zero when a",
|
|
2544
|
+
"`## Template: <phase-label>` section:",
|
|
2545
|
+
"",
|
|
2546
|
+
"- never passes `--label <phase-label>` \u2014 the heading and the",
|
|
2547
|
+
" command disagree;",
|
|
2548
|
+
"- carries no `type:*` label, more than one, or one that is not",
|
|
2549
|
+
" the `type:<bundle>` label the phase label's owning bundle",
|
|
2550
|
+
" declares;",
|
|
2551
|
+
"- has no GitHub issue-type assignment step.",
|
|
2552
|
+
"",
|
|
2553
|
+
"```bash",
|
|
2554
|
+
".claude/procedures/check-issue-template-labels.sh",
|
|
2555
|
+
"```",
|
|
2556
|
+
"",
|
|
2557
|
+
"The phase-label \u2192 type-label pairing is generated from the same",
|
|
2558
|
+
"bundle-ownership map that drives `.github/labels.yml` and the",
|
|
2559
|
+
"orchestrator's invariant sweep, so a hand-edit can never",
|
|
2560
|
+
"reintroduce a mismatch the registry has already ruled out.",
|
|
2561
|
+
"Sections whose heading matches no bundle-owned phase label are",
|
|
2562
|
+
"skipped \u2014 consumer-specific labels are deliberately not policed.",
|
|
2394
2563
|
""
|
|
2395
2564
|
);
|
|
2396
2565
|
}
|
|
@@ -2399,8 +2568,10 @@ function renderIssueTemplatesRuleContent(it, hasDownstreamBundles = true) {
|
|
|
2399
2568
|
"",
|
|
2400
2569
|
"The convention intentionally does not:",
|
|
2401
2570
|
"",
|
|
2402
|
-
"- Auto-generate
|
|
2403
|
-
"
|
|
2571
|
+
"- Auto-generate recipe **bodies**. Only the label set is",
|
|
2572
|
+
" scaffolded, because labels are derivable from the bundle",
|
|
2573
|
+
" registry and bodies are not \u2014 repo-specific objectives,",
|
|
2574
|
+
" acceptance criteria, and callers lists stay hand-authored.",
|
|
2404
2575
|
"- Prescribe a fixed body shape across templates. Each section",
|
|
2405
2576
|
" may carry its own `## Acceptance Criteria`, `## Scope Size`,",
|
|
2406
2577
|
" or `## Output Path` fields as the downstream agent requires;",
|
|
@@ -2431,7 +2602,8 @@ function renderIssueTemplatesBundleHook(it, bundleLabel) {
|
|
|
2431
2602
|
"as an inline template vs. a prose mention."
|
|
2432
2603
|
].join("\n");
|
|
2433
2604
|
}
|
|
2434
|
-
function renderIssueTemplatesStarterPage(
|
|
2605
|
+
function renderIssueTemplatesStarterPage(it) {
|
|
2606
|
+
const generatedLink = `./${basename(issueTemplatesGeneratedPath(it.templatesPath))}`;
|
|
2435
2607
|
return [
|
|
2436
2608
|
"---",
|
|
2437
2609
|
"title: Issue Templates",
|
|
@@ -2466,6 +2638,20 @@ function renderIssueTemplatesStarterPage(_it) {
|
|
|
2466
2638
|
"bundle's rule \u2014 the issue-templates page is a recipe catalogue,",
|
|
2467
2639
|
"not a duplicate-check layer.",
|
|
2468
2640
|
"",
|
|
2641
|
+
"## Generated label sets",
|
|
2642
|
+
"",
|
|
2643
|
+
`[Generated Issue-Template Label Sets](${generatedLink}) carries one`,
|
|
2644
|
+
"correct-by-construction stub per phase label the active bundles",
|
|
2645
|
+
"emit \u2014 the phase label paired with the `type:<bundle>` label its",
|
|
2646
|
+
"owning bundle declares, plus the configured `status:*` /",
|
|
2647
|
+
"`priority:*` defaults. That page regenerates on every `projen` run,",
|
|
2648
|
+
"so it never goes stale as phase labels come and go.",
|
|
2649
|
+
"",
|
|
2650
|
+
"This page is the opposite: written once, then owned by you. Copy a",
|
|
2651
|
+
"stub across, flesh out its title and body here, and leave the label",
|
|
2652
|
+
"block exactly as generated \u2014 the label-consistency lint holds both",
|
|
2653
|
+
"copies to the same pairing.",
|
|
2654
|
+
"",
|
|
2469
2655
|
"## Template: example-phase",
|
|
2470
2656
|
"",
|
|
2471
2657
|
"Replace this example with the real templates for this repo. Each",
|
|
@@ -2505,6 +2691,115 @@ function renderIssueTemplatesStarterPage(_it) {
|
|
|
2505
2691
|
"```"
|
|
2506
2692
|
].join("\n");
|
|
2507
2693
|
}
|
|
2694
|
+
function collectIssueTemplateRecipeStubs(bundles, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
|
|
2695
|
+
const stubs = /* @__PURE__ */ new Map();
|
|
2696
|
+
for (const bundle of bundles) {
|
|
2697
|
+
for (const label of bundle.labels ?? []) {
|
|
2698
|
+
const typeLabel = typeLabelForPhaseLabel(label.name);
|
|
2699
|
+
if (typeLabel === void 0) {
|
|
2700
|
+
continue;
|
|
2701
|
+
}
|
|
2702
|
+
if (stubs.has(label.name)) {
|
|
2703
|
+
continue;
|
|
2704
|
+
}
|
|
2705
|
+
const defaults = labelsForPhase(issueDefaults, label.name);
|
|
2706
|
+
stubs.set(label.name, {
|
|
2707
|
+
phaseLabel: label.name,
|
|
2708
|
+
typeLabel,
|
|
2709
|
+
bundleName: bundle.name,
|
|
2710
|
+
description: label.description ?? "",
|
|
2711
|
+
status: defaults.status,
|
|
2712
|
+
priority: defaults.priority,
|
|
2713
|
+
issueType: githubIssueTypeForTitle(`${label.name}: placeholder`)
|
|
2714
|
+
});
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
return [...stubs.values()].sort(
|
|
2718
|
+
(a, b) => a.phaseLabel.localeCompare(b.phaseLabel)
|
|
2719
|
+
);
|
|
2720
|
+
}
|
|
2721
|
+
function renderIssueTemplatesGeneratedPage(it, stubs) {
|
|
2722
|
+
const lines = [
|
|
2723
|
+
"---",
|
|
2724
|
+
"title: Generated Issue-Template Label Sets",
|
|
2725
|
+
"description: Auto-generated label-correct gh issue create stubs \u2014 one per phase label.",
|
|
2726
|
+
"---",
|
|
2727
|
+
"",
|
|
2728
|
+
"**Generated file \u2014 do not edit.** Every section below is",
|
|
2729
|
+
"regenerated from the active bundle set on each `projen` run; hand",
|
|
2730
|
+
"edits are overwritten. The hand-authored recipes live in",
|
|
2731
|
+
`[the issue-templates page](./${basename(it.templatesPath)}) \u2014 this`,
|
|
2732
|
+
"page exists so the **label half** of every recipe stays",
|
|
2733
|
+
"correct-by-construction as phase labels come and go.",
|
|
2734
|
+
"",
|
|
2735
|
+
"## How to use",
|
|
2736
|
+
"",
|
|
2737
|
+
"Each section carries the exact label set the phase-label invariant",
|
|
2738
|
+
"requires: the phase label itself, the `type:<bundle>` label its",
|
|
2739
|
+
"owning bundle declares, and the configured `status:*` / `priority:*`",
|
|
2740
|
+
"defaults for that phase. Title and body are placeholders \u2014 copy the",
|
|
2741
|
+
"stub into the hand-authored templates page and flesh out the body",
|
|
2742
|
+
"there, or run it as-is when no repo-specific body exists yet.",
|
|
2743
|
+
"",
|
|
2744
|
+
"Every stub ends with the **GitHub issue type** assignment. `gh issue",
|
|
2745
|
+
"create` cannot set the issue type and the `type:*` label does not",
|
|
2746
|
+
"set it either, so an issue that skips the follow-up call stays",
|
|
2747
|
+
"untyped forever. See **Assigning the GitHub issue type** in the",
|
|
2748
|
+
"`issue-conventions` rule for the no-helper fallback.",
|
|
2749
|
+
""
|
|
2750
|
+
];
|
|
2751
|
+
if (stubs.length === 0) {
|
|
2752
|
+
lines.push(
|
|
2753
|
+
"## No phase labels",
|
|
2754
|
+
"",
|
|
2755
|
+
"No active bundle contributes a phase label, so there is nothing to",
|
|
2756
|
+
"scaffold. Enable a phased-pipeline bundle (or drop it from",
|
|
2757
|
+
"`excludeBundles`) and re-run `projen`."
|
|
2758
|
+
);
|
|
2759
|
+
return lines.join("\n");
|
|
2760
|
+
}
|
|
2761
|
+
for (const stub of stubs) {
|
|
2762
|
+
lines.push(
|
|
2763
|
+
`## Template: ${stub.phaseLabel}`,
|
|
2764
|
+
"",
|
|
2765
|
+
...stub.description === "" ? [] : [stub.description, ""],
|
|
2766
|
+
`**Owning bundle:** \`${stub.bundleName}\` \u2014 **required type label:**`,
|
|
2767
|
+
`\`${stub.typeLabel}\` (paired by the bundle-ownership map).`,
|
|
2768
|
+
"",
|
|
2769
|
+
"**Callers:** <name the bundles and agents that file this issue>",
|
|
2770
|
+
"",
|
|
2771
|
+
"```bash",
|
|
2772
|
+
"gh issue create \\",
|
|
2773
|
+
` --title "${stub.phaseLabel}: <short description>" \\`,
|
|
2774
|
+
` --label "${stub.typeLabel}" \\`,
|
|
2775
|
+
` --label "${stub.phaseLabel}" \\`,
|
|
2776
|
+
` --label "priority:${stub.priority}" \\`,
|
|
2777
|
+
` --label "status:${stub.status}" \\`,
|
|
2778
|
+
' --body "## Objective',
|
|
2779
|
+
"",
|
|
2780
|
+
"<1-3 sentences describing the work>.",
|
|
2781
|
+
"",
|
|
2782
|
+
"## Context",
|
|
2783
|
+
"",
|
|
2784
|
+
"- **Discovered in:** <source description> (#<parent-issue>)",
|
|
2785
|
+
"",
|
|
2786
|
+
"## Acceptance Criteria",
|
|
2787
|
+
"",
|
|
2788
|
+
"- [ ] <criterion 1>",
|
|
2789
|
+
'"',
|
|
2790
|
+
"```",
|
|
2791
|
+
"",
|
|
2792
|
+
`Then assign the GitHub issue type (\`${stub.issueType}\`):`,
|
|
2793
|
+
"",
|
|
2794
|
+
"```bash",
|
|
2795
|
+
`${SET_ISSUE_TYPE_HELPER_PATH} <issue-number> ${stub.issueType}`,
|
|
2796
|
+
"```",
|
|
2797
|
+
""
|
|
2798
|
+
);
|
|
2799
|
+
}
|
|
2800
|
+
lines.pop();
|
|
2801
|
+
return lines.join("\n");
|
|
2802
|
+
}
|
|
2508
2803
|
function renderIssueTemplatesCheckerScript(it) {
|
|
2509
2804
|
const patternsLiteral = it.bundlePathPatterns.map((p) => ` ${JSON.stringify(p)}`).join("\n");
|
|
2510
2805
|
return [
|
|
@@ -2693,6 +2988,157 @@ function renderIssueTemplatesCheckerScript(it) {
|
|
|
2693
2988
|
"exit 0"
|
|
2694
2989
|
].join("\n");
|
|
2695
2990
|
}
|
|
2991
|
+
function renderIssueTemplateLabelsCheckerScript(it) {
|
|
2992
|
+
const generatedPath = issueTemplatesGeneratedPath(it.templatesPath);
|
|
2993
|
+
const childGlob = issueTemplatesChildGlob(it.templatesPath);
|
|
2994
|
+
return [
|
|
2995
|
+
"#!/usr/bin/env bash",
|
|
2996
|
+
"# check-issue-template-labels.sh \u2014 Enforce recipe label correctness.",
|
|
2997
|
+
"#",
|
|
2998
|
+
"# Usage:",
|
|
2999
|
+
"# .claude/procedures/check-issue-template-labels.sh [<file>...]",
|
|
3000
|
+
"#",
|
|
3001
|
+
"# With no arguments the lint walks the templates page, its",
|
|
3002
|
+
"# router-style child pages, and the generated label-set companion.",
|
|
3003
|
+
"# Positional arguments override that default set.",
|
|
3004
|
+
"#",
|
|
3005
|
+
"# For every `## Template: <phase-label>` section it asserts the",
|
|
3006
|
+
"# recipe passes the heading's phase label, carries exactly one",
|
|
3007
|
+
"# `type:*` label matching the phase-label invariant, and assigns a",
|
|
3008
|
+
"# GitHub issue type. Sections whose heading matches no bundle-owned",
|
|
3009
|
+
"# phase label are skipped \u2014 unrecognised labels are consumer-",
|
|
3010
|
+
"# specific and deliberately not policed.",
|
|
3011
|
+
"#",
|
|
3012
|
+
"# The resolver below is generated from the canonical bundle-",
|
|
3013
|
+
"# ownership map \u2014 do not edit by hand; regenerate via",
|
|
3014
|
+
"# `pnpm exec projen`.",
|
|
3015
|
+
"",
|
|
3016
|
+
"set -uo pipefail",
|
|
3017
|
+
"",
|
|
3018
|
+
"err() {",
|
|
3019
|
+
' printf "check-issue-template-labels.sh: %s\\n" "$*" >&2',
|
|
3020
|
+
"}",
|
|
3021
|
+
"",
|
|
3022
|
+
renderPhaseTypeInvariantShellHelpers(),
|
|
3023
|
+
"",
|
|
3024
|
+
`templates_path=${JSON.stringify(it.templatesPath)}`,
|
|
3025
|
+
`generated_path=${JSON.stringify(generatedPath)}`,
|
|
3026
|
+
`child_glob=${JSON.stringify(childGlob)}`,
|
|
3027
|
+
"",
|
|
3028
|
+
"# Emit one TAB-separated record per `## Template:` section:",
|
|
3029
|
+
"# <file>\\t<line>\\t<phase-label>\\t<comma-joined labels>\\t<0|1 typed>",
|
|
3030
|
+
"# Quote characters are folded to spaces up front so a recipe written",
|
|
3031
|
+
"# with single quotes, double quotes, or none at all parses the same.",
|
|
3032
|
+
"# Fenced blocks are tracked so the `## Objective` heading inside a",
|
|
3033
|
+
"# recipe's --body string never reads as the end of the section.",
|
|
3034
|
+
"scan_file() {",
|
|
3035
|
+
' local file="$1"',
|
|
3036
|
+
` tr '\\042\\047' ' ' < "$file" | awk -v FNAME="$file" '`,
|
|
3037
|
+
" function flush() {",
|
|
3038
|
+
' if (phase != "") {',
|
|
3039
|
+
' printf "%s\\t%d\\t%s\\t%s\\t%d\\n", FNAME, start, phase, labels, hastype;',
|
|
3040
|
+
" }",
|
|
3041
|
+
' phase = ""; labels = ""; hastype = 0;',
|
|
3042
|
+
" }",
|
|
3043
|
+
" {",
|
|
3044
|
+
" if ($0 ~ /^[ \\t]*```/) {",
|
|
3045
|
+
" fence = 1 - fence;",
|
|
3046
|
+
' } else if (fence == 0 && index($0, "## ") == 1) {',
|
|
3047
|
+
" flush();",
|
|
3048
|
+
' if (index($0, "## Template: ") == 1) {',
|
|
3049
|
+
" phase = substr($0, 14);",
|
|
3050
|
+
' gsub(/[ \\t`]/, "", phase);',
|
|
3051
|
+
" start = NR;",
|
|
3052
|
+
" }",
|
|
3053
|
+
" next;",
|
|
3054
|
+
" }",
|
|
3055
|
+
' if (phase == "") next;',
|
|
3056
|
+
" if ($0 ~ /set-issue-type\\.sh/ || $0 ~ /updateIssueIssueType/) {",
|
|
3057
|
+
" hastype = 1;",
|
|
3058
|
+
" }",
|
|
3059
|
+
" line = $0;",
|
|
3060
|
+
" while (match(line, /--label[ \\t]+[^ \\t]+/)) {",
|
|
3061
|
+
" tok = substr(line, RSTART, RLENGTH);",
|
|
3062
|
+
" line = substr(line, RSTART + RLENGTH);",
|
|
3063
|
+
' sub(/^--label[ \\t]+/, "", tok);',
|
|
3064
|
+
' gsub(/[\\\\`]/, "", tok);',
|
|
3065
|
+
' labels = labels tok ",";',
|
|
3066
|
+
" }",
|
|
3067
|
+
" }",
|
|
3068
|
+
" END { flush(); }",
|
|
3069
|
+
" '",
|
|
3070
|
+
"}",
|
|
3071
|
+
"",
|
|
3072
|
+
"# Collect the file list: positional args override the default set.",
|
|
3073
|
+
"files=()",
|
|
3074
|
+
"if [[ $# -gt 0 ]]; then",
|
|
3075
|
+
' files=("$@")',
|
|
3076
|
+
"else",
|
|
3077
|
+
' [[ -f "$templates_path" ]] && files+=("$templates_path")',
|
|
3078
|
+
' [[ -f "$generated_path" ]] && files+=("$generated_path")',
|
|
3079
|
+
" for child in $child_glob; do",
|
|
3080
|
+
' [[ -f "$child" ]] && files+=("$child")',
|
|
3081
|
+
" done",
|
|
3082
|
+
"fi",
|
|
3083
|
+
"",
|
|
3084
|
+
"if [[ ${#files[@]} -eq 0 ]]; then",
|
|
3085
|
+
" exit 0",
|
|
3086
|
+
"fi",
|
|
3087
|
+
"",
|
|
3088
|
+
"violations=()",
|
|
3089
|
+
"checked=0",
|
|
3090
|
+
"",
|
|
3091
|
+
'for file in "${files[@]}"; do',
|
|
3092
|
+
' [[ -f "$file" ]] || continue',
|
|
3093
|
+
" while IFS=$'\\t' read -r rec_file rec_line phase labels hastype; do",
|
|
3094
|
+
' [[ -z "$phase" ]] && continue',
|
|
3095
|
+
' required=$(phase_label_type_of "$phase")',
|
|
3096
|
+
' [[ -z "$required" ]] && continue',
|
|
3097
|
+
" checked=$(( checked + 1 ))",
|
|
3098
|
+
' case ",${labels}" in',
|
|
3099
|
+
' *",${phase},"*) ;;',
|
|
3100
|
+
` *) violations+=("$rec_file:$rec_line: recipe for '$phase' never passes --label $phase") ;;`,
|
|
3101
|
+
" esac",
|
|
3102
|
+
' type_labels=""',
|
|
3103
|
+
" type_count=0",
|
|
3104
|
+
' for entry in $(printf "%s" "$labels" | tr , " "); do',
|
|
3105
|
+
' case "$entry" in',
|
|
3106
|
+
" type:*)",
|
|
3107
|
+
' type_labels="${type_labels}${entry} "',
|
|
3108
|
+
" type_count=$(( type_count + 1 ))",
|
|
3109
|
+
" ;;",
|
|
3110
|
+
" esac",
|
|
3111
|
+
" done",
|
|
3112
|
+
' if [[ "$type_count" -eq 0 ]]; then',
|
|
3113
|
+
` violations+=("$rec_file:$rec_line: recipe for '$phase' carries no type:* label (expected $required)")`,
|
|
3114
|
+
' elif [[ "$type_count" -gt 1 ]]; then',
|
|
3115
|
+
" violations+=(\"$rec_file:$rec_line: recipe for '$phase' carries ${type_count} type:* labels (${type_labels% }); exactly one is allowed\")",
|
|
3116
|
+
' elif [[ "${type_labels% }" != "$required" ]]; then',
|
|
3117
|
+
` violations+=("$rec_file:$rec_line: recipe for '$phase' carries \${type_labels% } but the phase label requires $required")`,
|
|
3118
|
+
" fi",
|
|
3119
|
+
' if [[ "$hastype" != "1" ]]; then',
|
|
3120
|
+
` violations+=("$rec_file:$rec_line: recipe for '$phase' has no GitHub issue-type assignment step")`,
|
|
3121
|
+
" fi",
|
|
3122
|
+
' done < <(scan_file "$file")',
|
|
3123
|
+
"done",
|
|
3124
|
+
"",
|
|
3125
|
+
"if [[ ${#violations[@]} -gt 0 ]]; then",
|
|
3126
|
+
' err "issue-template recipes disagree with the phase-label invariant:"',
|
|
3127
|
+
' for v in "${violations[@]}"; do',
|
|
3128
|
+
' err " - $v"',
|
|
3129
|
+
" done",
|
|
3130
|
+
` err "each '## Template: <phase-label>' recipe must pass its own phase label, exactly one matching type:<bundle> label, and a set-issue-type.sh (or updateIssueIssueType) step"`,
|
|
3131
|
+
" exit 1",
|
|
3132
|
+
"fi",
|
|
3133
|
+
"",
|
|
3134
|
+
'printf "check-issue-template-labels.sh: %d recipe(s) OK\\n" "$checked"',
|
|
3135
|
+
"exit 0"
|
|
3136
|
+
].join("\n");
|
|
3137
|
+
}
|
|
3138
|
+
function basename(path8) {
|
|
3139
|
+
const slash = path8.lastIndexOf("/");
|
|
3140
|
+
return slash === -1 ? path8 : path8.slice(slash + 1);
|
|
3141
|
+
}
|
|
2696
3142
|
function assertValidTemplatesPath(value) {
|
|
2697
3143
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2698
3144
|
throw new Error(
|
|
@@ -5695,90 +6141,6 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
|
|
|
5695
6141
|
}
|
|
5696
6142
|
var baseBundle = buildBaseBundle();
|
|
5697
6143
|
|
|
5698
|
-
// src/agent/bundles/issue-defaults.ts
|
|
5699
|
-
var VALID_STATUS_VALUES = [
|
|
5700
|
-
"ready",
|
|
5701
|
-
"blocked",
|
|
5702
|
-
"in-progress",
|
|
5703
|
-
"ready-for-review",
|
|
5704
|
-
"needs-attention",
|
|
5705
|
-
"done",
|
|
5706
|
-
"deferred"
|
|
5707
|
-
];
|
|
5708
|
-
var VALID_PRIORITY_VALUES = [
|
|
5709
|
-
"critical",
|
|
5710
|
-
"high",
|
|
5711
|
-
"medium",
|
|
5712
|
-
"low",
|
|
5713
|
-
"trivial"
|
|
5714
|
-
];
|
|
5715
|
-
var DEFAULT_ISSUE_STATUS = "ready";
|
|
5716
|
-
var DEFAULT_ISSUE_PRIORITY = "medium";
|
|
5717
|
-
var DEFAULT_RESOLVED_ISSUE_DEFAULTS = {
|
|
5718
|
-
defaults: {
|
|
5719
|
-
status: DEFAULT_ISSUE_STATUS,
|
|
5720
|
-
priority: DEFAULT_ISSUE_PRIORITY
|
|
5721
|
-
},
|
|
5722
|
-
overrides: {}
|
|
5723
|
-
};
|
|
5724
|
-
function resolveIssueDefaults(config) {
|
|
5725
|
-
if (config === void 0) {
|
|
5726
|
-
return DEFAULT_RESOLVED_ISSUE_DEFAULTS;
|
|
5727
|
-
}
|
|
5728
|
-
const overrides = {};
|
|
5729
|
-
for (const [phaseLabel, override] of Object.entries(config)) {
|
|
5730
|
-
assertValidPhaseLabel(phaseLabel);
|
|
5731
|
-
assertValidOverride(phaseLabel, override);
|
|
5732
|
-
overrides[phaseLabel] = {
|
|
5733
|
-
status: override.status ?? DEFAULT_ISSUE_STATUS,
|
|
5734
|
-
priority: override.priority ?? DEFAULT_ISSUE_PRIORITY
|
|
5735
|
-
};
|
|
5736
|
-
}
|
|
5737
|
-
return {
|
|
5738
|
-
defaults: {
|
|
5739
|
-
status: DEFAULT_ISSUE_STATUS,
|
|
5740
|
-
priority: DEFAULT_ISSUE_PRIORITY
|
|
5741
|
-
},
|
|
5742
|
-
overrides
|
|
5743
|
-
};
|
|
5744
|
-
}
|
|
5745
|
-
function validateIssueDefaultsConfig(config) {
|
|
5746
|
-
return resolveIssueDefaults(config);
|
|
5747
|
-
}
|
|
5748
|
-
function labelsForPhase(resolved, phaseLabel) {
|
|
5749
|
-
return resolved.overrides[phaseLabel] ?? resolved.defaults;
|
|
5750
|
-
}
|
|
5751
|
-
function assertValidPhaseLabel(phaseLabel) {
|
|
5752
|
-
if (typeof phaseLabel !== "string" || phaseLabel.trim() === "") {
|
|
5753
|
-
throw new Error(
|
|
5754
|
-
"AgentConfigOptions.issueDefaults: phase-label keys must be non-empty strings (e.g. `people:research`)."
|
|
5755
|
-
);
|
|
5756
|
-
}
|
|
5757
|
-
}
|
|
5758
|
-
function assertValidOverride(phaseLabel, override) {
|
|
5759
|
-
if (override === null || typeof override !== "object" || Array.isArray(override)) {
|
|
5760
|
-
throw new Error(
|
|
5761
|
-
`AgentConfigOptions.issueDefaults["${phaseLabel}"] must be an object with optional \`status\` and \`priority\` fields.`
|
|
5762
|
-
);
|
|
5763
|
-
}
|
|
5764
|
-
const { status, priority } = override;
|
|
5765
|
-
if (status === void 0 && priority === void 0) {
|
|
5766
|
-
throw new Error(
|
|
5767
|
-
`AgentConfigOptions.issueDefaults["${phaseLabel}"] must declare at least one of \`status\` or \`priority\`. Empty entries are rejected because they are almost always a typo on the field name.`
|
|
5768
|
-
);
|
|
5769
|
-
}
|
|
5770
|
-
if (status !== void 0 && !VALID_STATUS_VALUES.includes(status)) {
|
|
5771
|
-
throw new Error(
|
|
5772
|
-
`AgentConfigOptions.issueDefaults["${phaseLabel}"].status="${status}" is not a recognised status value. Allowed values: ${VALID_STATUS_VALUES.join(", ")}.`
|
|
5773
|
-
);
|
|
5774
|
-
}
|
|
5775
|
-
if (priority !== void 0 && !VALID_PRIORITY_VALUES.includes(priority)) {
|
|
5776
|
-
throw new Error(
|
|
5777
|
-
`AgentConfigOptions.issueDefaults["${phaseLabel}"].priority="${priority}" is not a recognised priority value. Allowed values: ${VALID_PRIORITY_VALUES.join(", ")}.`
|
|
5778
|
-
);
|
|
5779
|
-
}
|
|
5780
|
-
}
|
|
5781
|
-
|
|
5782
6144
|
// src/agent/bundles/bcm-writer.ts
|
|
5783
6145
|
function buildBcmWriterSubAgent(paths, issueDefaults) {
|
|
5784
6146
|
return {
|
|
@@ -33025,6 +33387,8 @@ var UPSTREAM_CONFIGULATOR_DOCS_DETAIL = [
|
|
|
33025
33387
|
" --body $'## Summary\\n\\nThe `software-profile-analyst` Phase 2 prompt does not capture the vendor pricing tier (free / paid / enterprise), which downstream consumers need for segment matching.\\n\\n## Details\\n\\n- Add a `Pricing tier` field to the profile template at `docs/src/content/docs/agents/software-profile-analyst.md`\\n- Update the Phase 2 instructions to require the field before commit\\n- Add a regression test in `bundles.test.ts`'",
|
|
33026
33388
|
"```",
|
|
33027
33389
|
"",
|
|
33390
|
+
"The call above does **not** set the GitHub issue type \u2014 `gh issue create` cannot. Assign it immediately, in the same session, with the cross-repo GraphQL flow above (`feat:` title \u2192 **Feature**; every other prefix except `epic:` / `fix:` \u2192 **Task**). Skipping it leaves the upstream issue untyped, and nobody comes back for it later.",
|
|
33391
|
+
"",
|
|
33028
33392
|
"After filing, reference the upstream issue from any local workaround so it can be reverted once the upstream fix lands:",
|
|
33029
33393
|
"",
|
|
33030
33394
|
"```typescript",
|
|
@@ -35201,11 +35565,34 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
|
|
|
35201
35565
|
).split("\n"),
|
|
35202
35566
|
executable: true
|
|
35203
35567
|
});
|
|
35568
|
+
new import_projen8.TextFile(
|
|
35569
|
+
this,
|
|
35570
|
+
".claude/procedures/check-issue-template-labels.sh",
|
|
35571
|
+
{
|
|
35572
|
+
lines: renderIssueTemplateLabelsCheckerScript(
|
|
35573
|
+
resolvedIssueTemplates
|
|
35574
|
+
).split("\n"),
|
|
35575
|
+
executable: true
|
|
35576
|
+
}
|
|
35577
|
+
);
|
|
35204
35578
|
}
|
|
35205
35579
|
if (resolvedIssueTemplates.emitStarterDoc) {
|
|
35206
35580
|
new import_projen8.SampleFile(this.project, resolvedIssueTemplates.templatesPath, {
|
|
35207
35581
|
contents: renderIssueTemplatesStarterPage(resolvedIssueTemplates)
|
|
35208
35582
|
});
|
|
35583
|
+
new import_projen8.TextFile(
|
|
35584
|
+
this,
|
|
35585
|
+
issueTemplatesGeneratedPath(resolvedIssueTemplates.templatesPath),
|
|
35586
|
+
{
|
|
35587
|
+
lines: renderIssueTemplatesGeneratedPage(
|
|
35588
|
+
resolvedIssueTemplates,
|
|
35589
|
+
collectIssueTemplateRecipeStubs(
|
|
35590
|
+
this.activeBundles,
|
|
35591
|
+
resolveIssueDefaults(this.options.issueDefaults)
|
|
35592
|
+
)
|
|
35593
|
+
).split("\n")
|
|
35594
|
+
}
|
|
35595
|
+
);
|
|
35209
35596
|
}
|
|
35210
35597
|
}
|
|
35211
35598
|
const resolvedTemporalFraming = validateTemporalFramingConfig(
|
|
@@ -41860,6 +42247,7 @@ export const collections = {
|
|
|
41860
42247
|
DOCS_SYNC_AUDIT_SCHEMA_VERSION,
|
|
41861
42248
|
GITHUB_ISSUE_TYPES,
|
|
41862
42249
|
GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX,
|
|
42250
|
+
ISSUE_TEMPLATES_GENERATED_SUFFIX,
|
|
41863
42251
|
JsiiFaker,
|
|
41864
42252
|
LAYOUT_ENFORCEMENT,
|
|
41865
42253
|
LAYOUT_ROOT_BY_PROJECT_TYPE,
|
|
@@ -41955,6 +42343,7 @@ export const collections = {
|
|
|
41955
42343
|
checkLinksProcedure,
|
|
41956
42344
|
classifyIssueScope,
|
|
41957
42345
|
classifyRun,
|
|
42346
|
+
collectIssueTemplateRecipeStubs,
|
|
41958
42347
|
companyProfileBundle,
|
|
41959
42348
|
compileFencedSamples,
|
|
41960
42349
|
createApiDiffCheck,
|
|
@@ -41980,6 +42369,8 @@ export const collections = {
|
|
|
41980
42369
|
isScheduledTaskOwnedByExcluded,
|
|
41981
42370
|
isSuppressedWorkflowRule,
|
|
41982
42371
|
isTypeLabelOwnedByExcluded,
|
|
42372
|
+
issueTemplatesChildGlob,
|
|
42373
|
+
issueTemplatesGeneratedPath,
|
|
41983
42374
|
jestBundle,
|
|
41984
42375
|
labelsForPhase,
|
|
41985
42376
|
maintenanceAuditBundle,
|
|
@@ -42030,8 +42421,10 @@ export const collections = {
|
|
|
42030
42421
|
renderFocusSection,
|
|
42031
42422
|
renderGithubIssueTypeSection,
|
|
42032
42423
|
renderGithubIssueTypeSectionLines,
|
|
42424
|
+
renderIssueTemplateLabelsCheckerScript,
|
|
42033
42425
|
renderIssueTemplatesBundleHook,
|
|
42034
42426
|
renderIssueTemplatesCheckerScript,
|
|
42427
|
+
renderIssueTemplatesGeneratedPage,
|
|
42035
42428
|
renderIssueTemplatesRuleContent,
|
|
42036
42429
|
renderIssueTemplatesStarterPage,
|
|
42037
42430
|
renderIssueTypeAssignmentBlanket,
|