@codedrifters/configulator 0.0.403 → 0.0.405
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 +230 -4
- package/lib/index.d.ts +231 -5
- package/lib/index.js +1370 -804
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +1370 -814
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -213,6 +213,7 @@ __export(index_exports, {
|
|
|
213
213
|
CDK_WATCH_DEFAULTS_BY_STAGE: () => CDK_WATCH_DEFAULTS_BY_STAGE,
|
|
214
214
|
CLAUDE_RULE_TARGET: () => CLAUDE_RULE_TARGET,
|
|
215
215
|
COMPLETE_JOB_ID: () => COMPLETE_JOB_ID,
|
|
216
|
+
CONVENTIONAL_COMMIT_TYPE_LABELS: () => CONVENTIONAL_COMMIT_TYPE_LABELS,
|
|
216
217
|
CdkCli: () => CdkCli,
|
|
217
218
|
DEFAULT_AC_THRESHOLDS: () => DEFAULT_AC_THRESHOLDS,
|
|
218
219
|
DEFAULT_AGENT_PATHS: () => DEFAULT_AGENT_PATHS,
|
|
@@ -222,6 +223,7 @@ __export(index_exports, {
|
|
|
222
223
|
DEFAULT_API_EXTRACTOR_REPORT_FILENAME: () => DEFAULT_API_EXTRACTOR_REPORT_FILENAME,
|
|
223
224
|
DEFAULT_API_EXTRACTOR_REPORT_FOLDER: () => DEFAULT_API_EXTRACTOR_REPORT_FOLDER,
|
|
224
225
|
DEFAULT_AUDIT_REPORT_DIR: () => DEFAULT_AUDIT_REPORT_DIR,
|
|
226
|
+
DEFAULT_BUILD_POLICY: () => DEFAULT_BUILD_POLICY,
|
|
225
227
|
DEFAULT_BUNDLE_OVERRIDES: () => DEFAULT_BUNDLE_OVERRIDES,
|
|
226
228
|
DEFAULT_DECOMPOSITION_TEMPLATE: () => DEFAULT_DECOMPOSITION_TEMPLATE,
|
|
227
229
|
DEFAULT_DISPATCH_MODEL: () => DEFAULT_DISPATCH_MODEL,
|
|
@@ -283,6 +285,7 @@ __export(index_exports, {
|
|
|
283
285
|
MONOREPO_LAYOUT: () => MONOREPO_LAYOUT,
|
|
284
286
|
MonorepoProject: () => MonorepoProject,
|
|
285
287
|
Nvmrc: () => Nvmrc,
|
|
288
|
+
PHASE_LABEL_TYPE_MAP: () => PHASE_LABEL_TYPE_MAP,
|
|
286
289
|
PROD_DEPLOY_NAME: () => PROD_DEPLOY_NAME,
|
|
287
290
|
PROGRESS_FILES_FORMAT_VALUES: () => PROGRESS_FILES_FORMAT_VALUES,
|
|
288
291
|
PnpmWorkspace: () => PnpmWorkspace,
|
|
@@ -342,6 +345,7 @@ __export(index_exports, {
|
|
|
342
345
|
buildCompanyProfileBundle: () => buildCompanyProfileBundle,
|
|
343
346
|
buildCustomerProfileBundle: () => buildCustomerProfileBundle,
|
|
344
347
|
buildDocsSyncBundle: () => buildDocsSyncBundle,
|
|
348
|
+
buildGithubWorkflowBundle: () => buildGithubWorkflowBundle,
|
|
345
349
|
buildIndustryDiscoveryBundle: () => buildIndustryDiscoveryBundle,
|
|
346
350
|
buildMaintenanceAuditBundle: () => buildMaintenanceAuditBundle,
|
|
347
351
|
buildMeetingAnalysisBundle: () => buildMeetingAnalysisBundle,
|
|
@@ -356,6 +360,7 @@ __export(index_exports, {
|
|
|
356
360
|
buildResearchPipelineBundle: () => buildResearchPipelineBundle,
|
|
357
361
|
buildSoftwareProfileBundle: () => buildSoftwareProfileBundle,
|
|
358
362
|
buildStandardsResearchBundle: () => buildStandardsResearchBundle,
|
|
363
|
+
buildTurborepoBundle: () => buildTurborepoBundle,
|
|
359
364
|
buildUnblockDependentsProcedure: () => buildUnblockDependentsProcedure,
|
|
360
365
|
bundleNameForWorkflowRule: () => bundleNameForWorkflowRule,
|
|
361
366
|
businessModelsBundle: () => businessModelsBundle,
|
|
@@ -441,6 +446,8 @@ __export(index_exports, {
|
|
|
441
446
|
renderIssueTemplatesStarterPage: () => renderIssueTemplatesStarterPage,
|
|
442
447
|
renderMeetingTypesSection: () => renderMeetingTypesSection,
|
|
443
448
|
renderNextRequirementIdProcedure: () => renderNextRequirementIdProcedure,
|
|
449
|
+
renderPhaseTypeInvariantSection: () => renderPhaseTypeInvariantSection,
|
|
450
|
+
renderPhaseTypeInvariantShellHelpers: () => renderPhaseTypeInvariantShellHelpers,
|
|
444
451
|
renderPriorityRulesSection: () => renderPriorityRulesSection,
|
|
445
452
|
renderProgressFileName: () => renderProgressFileName,
|
|
446
453
|
renderProgressFilePath: () => renderProgressFilePath,
|
|
@@ -473,6 +480,7 @@ __export(index_exports, {
|
|
|
473
480
|
resolveAgentTiers: () => resolveAgentTiers,
|
|
474
481
|
resolveAstroProjectOutdir: () => resolveAstroProjectOutdir,
|
|
475
482
|
resolveAwsCdkProjectOutdir: () => resolveAwsCdkProjectOutdir,
|
|
483
|
+
resolveBuildPolicy: () => resolveBuildPolicy,
|
|
476
484
|
resolveBundleAgentTiers: () => resolveBundleAgentTiers,
|
|
477
485
|
resolveDefaultAgentTier: () => resolveDefaultAgentTier,
|
|
478
486
|
resolveIssueDefaults: () => resolveIssueDefaults,
|
|
@@ -491,6 +499,7 @@ __export(index_exports, {
|
|
|
491
499
|
resolveSkillEvals: () => resolveSkillEvals,
|
|
492
500
|
resolveTemplateVariables: () => resolveTemplateVariables,
|
|
493
501
|
resolveTemporalFraming: () => resolveTemporalFraming,
|
|
502
|
+
resolveTypeLabelForLabels: () => resolveTypeLabelForLabels,
|
|
494
503
|
resolveTypeScriptProjectOutdir: () => resolveTypeScriptProjectOutdir,
|
|
495
504
|
resolveUnblockDependents: () => resolveUnblockDependents,
|
|
496
505
|
runScan: () => runScan,
|
|
@@ -500,6 +509,7 @@ __export(index_exports, {
|
|
|
500
509
|
stripToolArtifactTagsProcedure: () => stripToolArtifactTagsProcedure,
|
|
501
510
|
tsdocRecordToFindings: () => tsdocRecordToFindings,
|
|
502
511
|
turborepoBundle: () => turborepoBundle,
|
|
512
|
+
typeLabelForPhaseLabel: () => typeLabelForPhaseLabel,
|
|
503
513
|
typescriptBundle: () => typescriptBundle,
|
|
504
514
|
upstreamConfigulatorDocsBundle: () => upstreamConfigulatorDocsBundle,
|
|
505
515
|
validateAgentTierConfig: () => validateAgentTierConfig,
|
|
@@ -5904,6 +5914,403 @@ function buildBcmWriterBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAU
|
|
|
5904
5914
|
}
|
|
5905
5915
|
var bcmWriterBundle = buildBcmWriterBundle();
|
|
5906
5916
|
|
|
5917
|
+
// src/turbo/turbo-repo.ts
|
|
5918
|
+
var import_lib2 = require("projen/lib");
|
|
5919
|
+
var import_workflows_model = require("projen/lib/github/workflows-model");
|
|
5920
|
+
|
|
5921
|
+
// src/turbo/turbo-repo-task.ts
|
|
5922
|
+
var import_lib = require("projen/lib");
|
|
5923
|
+
var TurboRepoTask = class extends import_lib.Component {
|
|
5924
|
+
constructor(project, options) {
|
|
5925
|
+
super(project);
|
|
5926
|
+
this.project = project;
|
|
5927
|
+
this.name = options.name;
|
|
5928
|
+
this.dependsOn = options.dependsOn ?? [];
|
|
5929
|
+
this.env = options.env ?? [];
|
|
5930
|
+
this.passThroughEnv = options.passThroughEnv ?? [];
|
|
5931
|
+
this.outputs = options.outputs ?? [];
|
|
5932
|
+
this.cache = options.cache ?? true;
|
|
5933
|
+
this.inputs = [
|
|
5934
|
+
...options.inputs ?? [],
|
|
5935
|
+
// rerun if projen config changes
|
|
5936
|
+
".projen/**",
|
|
5937
|
+
// ignore mac files
|
|
5938
|
+
"!.DS_Store",
|
|
5939
|
+
"!**/.DS_Store"
|
|
5940
|
+
];
|
|
5941
|
+
this.outputLogs = options.outputLogs ?? "new-only";
|
|
5942
|
+
this.persistent = options.persistent ?? false;
|
|
5943
|
+
this.interactive = options.interactive ?? false;
|
|
5944
|
+
this.isActive = true;
|
|
5945
|
+
}
|
|
5946
|
+
taskConfig() {
|
|
5947
|
+
return {
|
|
5948
|
+
dependsOn: this.dependsOn,
|
|
5949
|
+
env: this.env,
|
|
5950
|
+
passThroughEnv: this.passThroughEnv,
|
|
5951
|
+
outputs: this.outputs,
|
|
5952
|
+
cache: this.cache,
|
|
5953
|
+
inputs: this.inputs,
|
|
5954
|
+
outputLogs: this.outputLogs,
|
|
5955
|
+
persistent: this.persistent,
|
|
5956
|
+
interactive: this.interactive
|
|
5957
|
+
};
|
|
5958
|
+
}
|
|
5959
|
+
};
|
|
5960
|
+
|
|
5961
|
+
// src/turbo/turbo-repo.ts
|
|
5962
|
+
var ROOT_TURBO_TASK_NAME = "turbo:build";
|
|
5963
|
+
var ROOT_CI_TASK_NAME = "build:all";
|
|
5964
|
+
var _TurboRepo = class _TurboRepo extends import_lib2.Component {
|
|
5965
|
+
constructor(project, options = {}) {
|
|
5966
|
+
super(project);
|
|
5967
|
+
this.project = project;
|
|
5968
|
+
/**
|
|
5969
|
+
* Sub-Tasks to run
|
|
5970
|
+
*/
|
|
5971
|
+
this.tasks = [];
|
|
5972
|
+
this.turboVersion = options.turboVersion ?? "catalog:";
|
|
5973
|
+
this.isRootProject = project === project.root;
|
|
5974
|
+
if (this.isRootProject) {
|
|
5975
|
+
project.addDevDeps(`turbo@${this.turboVersion}`);
|
|
5976
|
+
}
|
|
5977
|
+
project.gitignore.addPatterns("/.turbo");
|
|
5978
|
+
project.npmignore?.addPatterns("/.turbo/");
|
|
5979
|
+
this.extends = options.extends ?? (this.isRootProject ? [] : ["//"]);
|
|
5980
|
+
this.globalDependencies = options.globalDependencies ?? [];
|
|
5981
|
+
this.globalEnv = options.globalEnv ?? [];
|
|
5982
|
+
this.globalPassThroughEnv = options.globalPassThroughEnv ?? [];
|
|
5983
|
+
this.ui = options.ui ?? "stream";
|
|
5984
|
+
this.dangerouslyDisablePackageManagerCheck = options.dangerouslyDisablePackageManagerCheck ?? false;
|
|
5985
|
+
this.cacheDir = options.cacheDir ?? ".turbo/cache";
|
|
5986
|
+
this.daemon = options.daemon ?? true;
|
|
5987
|
+
this.envMode = options.envMode ?? "strict";
|
|
5988
|
+
this.runOptions = {
|
|
5989
|
+
...options.runOptions,
|
|
5990
|
+
summarize: options.runOptions?.summarize ?? true,
|
|
5991
|
+
concurrency: options.runOptions?.concurrency ?? 10
|
|
5992
|
+
};
|
|
5993
|
+
this.remoteCacheOptions = options.remoteCacheOptions;
|
|
5994
|
+
this.buildAllTaskEnvVars = options.buildAllTaskEnvVars ?? {};
|
|
5995
|
+
this.buildTask = new TurboRepoTask(this.project, {
|
|
5996
|
+
name: ROOT_TURBO_TASK_NAME,
|
|
5997
|
+
dependsOn: this.isRootProject ? [`^${ROOT_TURBO_TASK_NAME}`] : []
|
|
5998
|
+
});
|
|
5999
|
+
if (this.isRootProject) {
|
|
6000
|
+
this.buildAllTask = this.project.tasks.addTask(ROOT_CI_TASK_NAME, {
|
|
6001
|
+
description: "Root build followed by sub-project builds. Mimics the CI build process in one step."
|
|
6002
|
+
});
|
|
6003
|
+
this.buildAllTask.exec("turbo telemetry disable");
|
|
6004
|
+
if (this.buildAllTaskEnvVars) {
|
|
6005
|
+
Object.entries(this.buildAllTaskEnvVars).forEach(([name, value]) => {
|
|
6006
|
+
this.addGlobalEnvVar(name, value);
|
|
6007
|
+
});
|
|
6008
|
+
}
|
|
6009
|
+
if (!this.remoteCacheOptions) {
|
|
6010
|
+
this.buildAllTask.exec(
|
|
6011
|
+
`turbo ${ROOT_TURBO_TASK_NAME} ${this.renderRunArgs(false)}`
|
|
6012
|
+
);
|
|
6013
|
+
} else {
|
|
6014
|
+
this.buildAllTask.exec(
|
|
6015
|
+
`turbo ${ROOT_TURBO_TASK_NAME} ${this.renderRunArgs(true)}`,
|
|
6016
|
+
{
|
|
6017
|
+
condition: '[ ! -n "$CI" ]',
|
|
6018
|
+
env: {
|
|
6019
|
+
TURBO_ENDPOINT: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.endpointParamName} --query Parameter.Value --output text --profile ${this.remoteCacheOptions.profileName})`,
|
|
6020
|
+
TURBO_TOKEN: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.tokenParamName} --query Parameter.Value --output text --profile ${this.remoteCacheOptions.profileName})`
|
|
6021
|
+
}
|
|
6022
|
+
}
|
|
6023
|
+
);
|
|
6024
|
+
this.buildAllTask.exec(
|
|
6025
|
+
`turbo ${ROOT_TURBO_TASK_NAME} ${this.renderRunArgs(true)}`,
|
|
6026
|
+
{
|
|
6027
|
+
condition: '[ -n "$CI" ]',
|
|
6028
|
+
env: {
|
|
6029
|
+
TURBO_ENDPOINT: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.endpointParamName} --query Parameter.Value --output text)`,
|
|
6030
|
+
TURBO_TOKEN: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.tokenParamName} --query Parameter.Value --output text)`
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
6033
|
+
);
|
|
6034
|
+
}
|
|
6035
|
+
}
|
|
6036
|
+
if (!this.isRootProject) {
|
|
6037
|
+
this.preCompileTask = new TurboRepoTask(project, {
|
|
6038
|
+
name: options.preCompileTask?.name ?? "pre-compile",
|
|
6039
|
+
inputs: ["src/**"]
|
|
6040
|
+
});
|
|
6041
|
+
this.compileTask = new TurboRepoTask(project, {
|
|
6042
|
+
name: options.compileTask?.name ?? "compile",
|
|
6043
|
+
inputs: ["src/**"]
|
|
6044
|
+
});
|
|
6045
|
+
this.postCompileTask = new TurboRepoTask(project, {
|
|
6046
|
+
name: options.postCompileTask?.name ?? "post-compile",
|
|
6047
|
+
inputs: ["src/**"]
|
|
6048
|
+
});
|
|
6049
|
+
this.testTask = new TurboRepoTask(project, {
|
|
6050
|
+
name: options.testTask?.name ?? "test"
|
|
6051
|
+
});
|
|
6052
|
+
this.packageTask = new TurboRepoTask(project, {
|
|
6053
|
+
name: options.packageTask?.name ?? "package",
|
|
6054
|
+
inputs: [".npmignore"]
|
|
6055
|
+
});
|
|
6056
|
+
this.tasks.push(
|
|
6057
|
+
this.preCompileTask,
|
|
6058
|
+
this.compileTask,
|
|
6059
|
+
this.postCompileTask,
|
|
6060
|
+
this.testTask,
|
|
6061
|
+
this.packageTask
|
|
6062
|
+
);
|
|
6063
|
+
}
|
|
6064
|
+
}
|
|
6065
|
+
/**
|
|
6066
|
+
* Static method to discovert turbo in a project.
|
|
6067
|
+
*/
|
|
6068
|
+
static of(project) {
|
|
6069
|
+
const isDefined = (c) => c instanceof _TurboRepo;
|
|
6070
|
+
return project.components.find(isDefined);
|
|
6071
|
+
}
|
|
6072
|
+
/**
|
|
6073
|
+
* Render the `turbo run` CLI flag string for the `build:all` / `reset:all`
|
|
6074
|
+
* task commands from {@link runOptions}. With no `runOptions` configured the
|
|
6075
|
+
* output matches the historically hard-coded flags exactly.
|
|
6076
|
+
*
|
|
6077
|
+
* @param remote - when `true`, also emit the remote-cache flags
|
|
6078
|
+
* (`--cache=remote:rw` plus `--api` / `--token` / `--team` derived from
|
|
6079
|
+
* {@link remoteCacheOptions}). The remote-cache `build:all` variant passes
|
|
6080
|
+
* `true`; `reset:all` and the local `build:all` variant pass `false`.
|
|
6081
|
+
*/
|
|
6082
|
+
renderRunArgs(remote) {
|
|
6083
|
+
const run = this.runOptions;
|
|
6084
|
+
const args = [];
|
|
6085
|
+
if (run.summarize) {
|
|
6086
|
+
args.push("--summarize");
|
|
6087
|
+
}
|
|
6088
|
+
args.push(`--concurrency=${run.concurrency}`);
|
|
6089
|
+
if (run.force) {
|
|
6090
|
+
args.push("--force");
|
|
6091
|
+
}
|
|
6092
|
+
if (run.noCache) {
|
|
6093
|
+
args.push("--no-cache");
|
|
6094
|
+
}
|
|
6095
|
+
if (run.only) {
|
|
6096
|
+
args.push("--only");
|
|
6097
|
+
}
|
|
6098
|
+
if (run.affected) {
|
|
6099
|
+
args.push("--affected");
|
|
6100
|
+
}
|
|
6101
|
+
if (run.cacheWorkers !== void 0) {
|
|
6102
|
+
args.push(`--cache-workers=${run.cacheWorkers}`);
|
|
6103
|
+
}
|
|
6104
|
+
if (run.continueOn !== void 0) {
|
|
6105
|
+
args.push(`--continue=${run.continueOn}`);
|
|
6106
|
+
}
|
|
6107
|
+
if (run.frameworkInference !== void 0) {
|
|
6108
|
+
args.push(`--framework-inference=${run.frameworkInference}`);
|
|
6109
|
+
}
|
|
6110
|
+
for (const filter of run.filter ?? []) {
|
|
6111
|
+
args.push(`--filter=${filter}`);
|
|
6112
|
+
}
|
|
6113
|
+
if (run.outputLogs !== void 0) {
|
|
6114
|
+
args.push(`--output-logs=${run.outputLogs}`);
|
|
6115
|
+
}
|
|
6116
|
+
if (run.logOrder !== void 0) {
|
|
6117
|
+
args.push(`--log-order=${run.logOrder}`);
|
|
6118
|
+
}
|
|
6119
|
+
if (run.logPrefix !== void 0) {
|
|
6120
|
+
args.push(`--log-prefix=${run.logPrefix}`);
|
|
6121
|
+
}
|
|
6122
|
+
if (run.dryRun !== void 0) {
|
|
6123
|
+
args.push(`--dry-run=${run.dryRun}`);
|
|
6124
|
+
}
|
|
6125
|
+
const cache = run.cache ?? (remote ? "remote:rw" : void 0);
|
|
6126
|
+
if (cache !== void 0) {
|
|
6127
|
+
args.push(`--cache=${cache}`);
|
|
6128
|
+
}
|
|
6129
|
+
if (remote && this.remoteCacheOptions) {
|
|
6130
|
+
args.push(
|
|
6131
|
+
"--api=$TURBO_ENDPOINT",
|
|
6132
|
+
"--token=$TURBO_TOKEN",
|
|
6133
|
+
`--team=${this.remoteCacheOptions.teamName}`
|
|
6134
|
+
);
|
|
6135
|
+
}
|
|
6136
|
+
if (run.additionalArgs) {
|
|
6137
|
+
args.push(...run.additionalArgs);
|
|
6138
|
+
}
|
|
6139
|
+
return args.join(" ");
|
|
6140
|
+
}
|
|
6141
|
+
/**
|
|
6142
|
+
* Add an env var to the global env vars for all tasks.
|
|
6143
|
+
* This will also become an input for the build:all task cache at the root.
|
|
6144
|
+
*/
|
|
6145
|
+
addGlobalEnvVar(name, value) {
|
|
6146
|
+
this.buildAllTask?.env(name, value);
|
|
6147
|
+
if (this.isRootProject) {
|
|
6148
|
+
this.globalEnv.push(name);
|
|
6149
|
+
}
|
|
6150
|
+
}
|
|
6151
|
+
activateBranchNameEnvVar(options) {
|
|
6152
|
+
const value = '$([ -n "$GIT_BRANCH_NAME" ] && echo "$GIT_BRANCH_NAME" || git rev-parse --abbrev-ref HEAD)';
|
|
6153
|
+
if (options === void 0) {
|
|
6154
|
+
this.project.logger.warn(
|
|
6155
|
+
"TurboRepo.activateBranchNameEnvVar() with no arguments is deprecated. It writes GIT_BRANCH_NAME to the root `globalEnv`, which forces every task in the monorepo to miss cache on every branch switch. Pass `{ tasks: [...] }` and name only the tasks that actually consume the branch (e.g. CDK synth/package) to preserve cross-branch cache hits for everything else."
|
|
6156
|
+
);
|
|
6157
|
+
this.addGlobalEnvVar("GIT_BRANCH_NAME", value);
|
|
6158
|
+
return;
|
|
6159
|
+
}
|
|
6160
|
+
const knownTaskNames = this.tasks.map((task) => task.name);
|
|
6161
|
+
const unknown = options.tasks.filter(
|
|
6162
|
+
(name) => !knownTaskNames.includes(name)
|
|
6163
|
+
);
|
|
6164
|
+
if (unknown.length > 0) {
|
|
6165
|
+
throw new Error(
|
|
6166
|
+
`TurboRepo.activateBranchNameEnvVar: unknown task name(s) ${JSON.stringify(
|
|
6167
|
+
unknown
|
|
6168
|
+
)}. Known tasks on this TurboRepo: ${JSON.stringify(knownTaskNames)}.`
|
|
6169
|
+
);
|
|
6170
|
+
}
|
|
6171
|
+
for (const name of options.tasks) {
|
|
6172
|
+
const task = this.tasks.find((t) => t.name === name);
|
|
6173
|
+
if (task && !task.env.includes("GIT_BRANCH_NAME")) {
|
|
6174
|
+
task.env.push("GIT_BRANCH_NAME");
|
|
6175
|
+
}
|
|
6176
|
+
}
|
|
6177
|
+
}
|
|
6178
|
+
/**
|
|
6179
|
+
* Paths of all generated files in the project, deduped, for use as task
|
|
6180
|
+
* inputs so the compile cache invalidates when they change. Includes
|
|
6181
|
+
* projen-managed `FileBase` files plus generated-once `SampleFile` /
|
|
6182
|
+
* `SampleDir` — whose paths projen keeps private, so they are read defensively
|
|
6183
|
+
* and skipped if that internal shape ever changes. Computed at synth time so
|
|
6184
|
+
* files added after this component (e.g. a subclass's SampleFiles) are seen.
|
|
6185
|
+
*/
|
|
6186
|
+
generatedFileInputs() {
|
|
6187
|
+
const inputs = /* @__PURE__ */ new Set();
|
|
6188
|
+
for (const component of this.project.components) {
|
|
6189
|
+
if (component instanceof import_lib2.FileBase) {
|
|
6190
|
+
inputs.add(component.path);
|
|
6191
|
+
} else if (component instanceof import_lib2.SampleFile) {
|
|
6192
|
+
const filePath = component.filePath;
|
|
6193
|
+
if (typeof filePath === "string") {
|
|
6194
|
+
inputs.add(filePath);
|
|
6195
|
+
}
|
|
6196
|
+
} else if (component instanceof import_lib2.SampleDir) {
|
|
6197
|
+
const dir = component.dir;
|
|
6198
|
+
if (typeof dir === "string") {
|
|
6199
|
+
inputs.add(`${dir}/**`);
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
return Array.from(inputs);
|
|
6204
|
+
}
|
|
6205
|
+
preSynthesize() {
|
|
6206
|
+
let nextDependsOn = this.project.deps.all.filter((d) => d.version === "workspace:*").map((d) => [d.name, ROOT_TURBO_TASK_NAME].join("#"));
|
|
6207
|
+
if (!this.isRootProject) {
|
|
6208
|
+
[
|
|
6209
|
+
[this.project.preCompileTask, this.preCompileTask],
|
|
6210
|
+
[this.project.compileTask, this.compileTask],
|
|
6211
|
+
[this.project.postCompileTask, this.postCompileTask],
|
|
6212
|
+
[this.project.testTask, this.testTask],
|
|
6213
|
+
[this.project.packageTask, this.packageTask]
|
|
6214
|
+
].forEach(([pjTask, turboTask]) => {
|
|
6215
|
+
if (pjTask && turboTask && pjTask.steps.length > 0) {
|
|
6216
|
+
if (nextDependsOn.length > 0) {
|
|
6217
|
+
turboTask.dependsOn.push(...nextDependsOn);
|
|
6218
|
+
}
|
|
6219
|
+
nextDependsOn = [turboTask.name];
|
|
6220
|
+
} else {
|
|
6221
|
+
turboTask.isActive = false;
|
|
6222
|
+
}
|
|
6223
|
+
});
|
|
6224
|
+
this.buildTask.dependsOn.push(...nextDependsOn);
|
|
6225
|
+
}
|
|
6226
|
+
const generatedInputs = this.generatedFileInputs();
|
|
6227
|
+
const appendGeneratedInputs = (task) => {
|
|
6228
|
+
if (!task) {
|
|
6229
|
+
return;
|
|
6230
|
+
}
|
|
6231
|
+
for (const input of generatedInputs) {
|
|
6232
|
+
if (!task.inputs.includes(input)) {
|
|
6233
|
+
task.inputs.push(input);
|
|
6234
|
+
}
|
|
6235
|
+
}
|
|
6236
|
+
};
|
|
6237
|
+
if (this.isRootProject) {
|
|
6238
|
+
appendGeneratedInputs(this.buildTask);
|
|
6239
|
+
} else {
|
|
6240
|
+
appendGeneratedInputs(this.preCompileTask);
|
|
6241
|
+
appendGeneratedInputs(this.compileTask);
|
|
6242
|
+
appendGeneratedInputs(this.postCompileTask);
|
|
6243
|
+
}
|
|
6244
|
+
const fileName = "turbo.json";
|
|
6245
|
+
this.project.addPackageIgnore(fileName);
|
|
6246
|
+
new import_lib2.JsonFile(this.project, fileName, {
|
|
6247
|
+
obj: {
|
|
6248
|
+
extends: this.extends.length ? this.extends : void 0,
|
|
6249
|
+
globalDependencies: this.isRootProject && this.globalDependencies.length ? this.globalDependencies : void 0,
|
|
6250
|
+
globalEnv: this.isRootProject && this.globalEnv.length ? this.globalEnv : void 0,
|
|
6251
|
+
globalPassThroughEnv: this.isRootProject && this.globalPassThroughEnv.length ? this.globalPassThroughEnv : void 0,
|
|
6252
|
+
ui: this.isRootProject ? this.ui : void 0,
|
|
6253
|
+
dangerouslyDisablePackageManagerCheck: this.isRootProject ? this.dangerouslyDisablePackageManagerCheck : void 0,
|
|
6254
|
+
cacheDir: this.isRootProject ? this.cacheDir : void 0,
|
|
6255
|
+
envMode: this.isRootProject ? this.envMode : void 0,
|
|
6256
|
+
/**
|
|
6257
|
+
* All tasks
|
|
6258
|
+
*/
|
|
6259
|
+
tasks: this.tasks.filter((task) => task.isActive).reduce(
|
|
6260
|
+
(acc, task) => {
|
|
6261
|
+
acc[task.name] = {
|
|
6262
|
+
...task.taskConfig()
|
|
6263
|
+
};
|
|
6264
|
+
return acc;
|
|
6265
|
+
},
|
|
6266
|
+
{
|
|
6267
|
+
[this.buildTask.name]: { ...this.buildTask.taskConfig() }
|
|
6268
|
+
}
|
|
6269
|
+
)
|
|
6270
|
+
}
|
|
6271
|
+
});
|
|
6272
|
+
super.preSynthesize();
|
|
6273
|
+
}
|
|
6274
|
+
};
|
|
6275
|
+
_TurboRepo.buildWorkflowOptions = (remoteCacheOptions) => {
|
|
6276
|
+
return {
|
|
6277
|
+
env: {
|
|
6278
|
+
GIT_BRANCH_NAME: "${{ github.head_ref || github.ref_name }}"
|
|
6279
|
+
},
|
|
6280
|
+
permissions: {
|
|
6281
|
+
contents: import_workflows_model.JobPermission.WRITE,
|
|
6282
|
+
idToken: import_workflows_model.JobPermission.WRITE
|
|
6283
|
+
},
|
|
6284
|
+
preBuildSteps: [
|
|
6285
|
+
{
|
|
6286
|
+
name: "AWS Creds for SSM",
|
|
6287
|
+
uses: "aws-actions/configure-aws-credentials@v6",
|
|
6288
|
+
with: {
|
|
6289
|
+
["role-to-assume"]: remoteCacheOptions.oidcRole,
|
|
6290
|
+
["aws-region"]: "us-east-1",
|
|
6291
|
+
["role-duration-seconds"]: "900"
|
|
6292
|
+
}
|
|
6293
|
+
}
|
|
6294
|
+
]
|
|
6295
|
+
};
|
|
6296
|
+
};
|
|
6297
|
+
var TurboRepo = _TurboRepo;
|
|
6298
|
+
|
|
6299
|
+
// src/agent/bundles/build-policy.ts
|
|
6300
|
+
var DEFAULT_BUILD_POLICY = {
|
|
6301
|
+
remoteCacheEnabled: false
|
|
6302
|
+
};
|
|
6303
|
+
function resolveBuildPolicy(project) {
|
|
6304
|
+
const remoteCacheOptions = TurboRepo.of(project)?.remoteCacheOptions;
|
|
6305
|
+
if (!remoteCacheOptions) {
|
|
6306
|
+
return DEFAULT_BUILD_POLICY;
|
|
6307
|
+
}
|
|
6308
|
+
return {
|
|
6309
|
+
remoteCacheEnabled: true,
|
|
6310
|
+
awsProfileName: remoteCacheOptions.profileName
|
|
6311
|
+
};
|
|
6312
|
+
}
|
|
6313
|
+
|
|
5907
6314
|
// src/agent/bundles/business-models.ts
|
|
5908
6315
|
var TEMPLATE_CANVAS = `---
|
|
5909
6316
|
title: "Business Model: <Segment Name>"
|
|
@@ -10842,188 +11249,210 @@ var cleanMergedBranchesSkill = {
|
|
|
10842
11249
|
}
|
|
10843
11250
|
]
|
|
10844
11251
|
};
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
10850
|
-
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
"
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
"
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
"
|
|
10885
|
-
"
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
|
|
10902
|
-
|
|
10903
|
-
|
|
10904
|
-
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
|
|
10917
|
-
|
|
10918
|
-
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
|
|
10930
|
-
|
|
10931
|
-
|
|
10932
|
-
|
|
10933
|
-
|
|
10934
|
-
|
|
10935
|
-
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
|
|
10939
|
-
|
|
10940
|
-
|
|
10941
|
-
|
|
10942
|
-
|
|
10943
|
-
|
|
10944
|
-
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
10959
|
-
|
|
10960
|
-
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
|
|
10965
|
-
|
|
10966
|
-
|
|
10967
|
-
|
|
10968
|
-
|
|
10969
|
-
|
|
10970
|
-
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
"",
|
|
10976
|
-
|
|
10977
|
-
|
|
10978
|
-
|
|
10979
|
-
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
|
|
11013
|
-
|
|
11014
|
-
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
"
|
|
11019
|
-
"
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
|
|
11026
|
-
|
|
11252
|
+
function renderPrWorkflowBuildStep(policy) {
|
|
11253
|
+
const lines = [
|
|
11254
|
+
"2. **Run the build \u2014 scoped to what changed.**",
|
|
11255
|
+
"",
|
|
11256
|
+
" - **Documentation-only changes** \u2014 a diff confined to documentation content (markdown pages, no source, config, or projen inputs) does **not** require `pnpm build:all`. There is nothing to compile, and CI is the authoritative content gate. Commit the content and proceed to the next step.",
|
|
11257
|
+
" - **Everything else** \u2014 when the change touches build inputs (source code, `.projenrc.ts`, `projenrc/**`, or any other projen-generated config), run `pnpm build:all` to compile, lint, and test all packages (mirrors the CI pipeline). If the build produces changes to turbo inputs (typically snapshot files or ESLint auto-fixes), commit those changes and run `pnpm build:all` again \u2014 the build must complete cleanly with no uncommitted changes."
|
|
11258
|
+
];
|
|
11259
|
+
if (policy.remoteCacheEnabled && policy.awsProfileName) {
|
|
11260
|
+
lines.push(
|
|
11261
|
+
"",
|
|
11262
|
+
` \`pnpm build:all\` uses the Turborepo **remote cache**, which requires the user to be authenticated to AWS on the \`${policy.awsProfileName}\` profile. If the command fails due to AWS credentials, ask the user to authenticate first.`
|
|
11263
|
+
);
|
|
11264
|
+
}
|
|
11265
|
+
lines.push(
|
|
11266
|
+
"",
|
|
11267
|
+
" **Never background a long build and end the turn.** If you run a build, run it in the **foreground** to completion before committing, pushing, or opening the PR. Launching `pnpm build:all` as a background process and ending your turn while it is still running strands the deliverable uncommitted \u2014 that work is lost. This is the documented root cause of autonomous worker-stall incidents."
|
|
11268
|
+
);
|
|
11269
|
+
return lines;
|
|
11270
|
+
}
|
|
11271
|
+
function buildGithubWorkflowBundle(buildPolicy = DEFAULT_BUILD_POLICY) {
|
|
11272
|
+
return {
|
|
11273
|
+
name: "github-workflow",
|
|
11274
|
+
description: "GitHub issue and PR workflow automation patterns",
|
|
11275
|
+
appliesWhen: (project) => hasComponent(project, import_github.GitHub),
|
|
11276
|
+
rules: [
|
|
11277
|
+
{
|
|
11278
|
+
name: "issue-workflow",
|
|
11279
|
+
description: "Automated workflow for starting work on a GitHub issue",
|
|
11280
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
11281
|
+
content: [
|
|
11282
|
+
"# Issue Workflow",
|
|
11283
|
+
"",
|
|
11284
|
+
'## "Work on issue X" Automation',
|
|
11285
|
+
"",
|
|
11286
|
+
"When the user says **work on issue X** (or similar), invoke the `issue-worker` agent in interactive mode, passing the issue number in the prompt. Do not perform the branch creation, issue fetching, or planning steps yourself \u2014 the agent handles the full workflow (claim, branch, plan, implement, PR) and will pause for your approval at the appropriate checkpoints."
|
|
11287
|
+
].join("\n"),
|
|
11288
|
+
tags: ["workflow"]
|
|
11289
|
+
},
|
|
11290
|
+
{
|
|
11291
|
+
name: "create-issue-workflow",
|
|
11292
|
+
description: "Automated workflow for creating a new GitHub issue",
|
|
11293
|
+
// ALWAYS scope: users invoke "create an issue" from any
|
|
11294
|
+
// context, not only when editing agent / skill / bundle source.
|
|
11295
|
+
// Consumers that want to narrow the load can override via
|
|
11296
|
+
// `agentConfig.additionalRulePaths` or `excludeRules`.
|
|
11297
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
11298
|
+
content: [
|
|
11299
|
+
"# Create Issue Workflow",
|
|
11300
|
+
"",
|
|
11301
|
+
'## "Create an issue" Automation',
|
|
11302
|
+
"",
|
|
11303
|
+
"When the user says **create an issue** (or similar), follow these steps exactly:",
|
|
11304
|
+
"",
|
|
11305
|
+
"1. **Determine the issue type prefix** from the user's description:",
|
|
11306
|
+
" - `epic:` \u2014 Large initiatives spanning multiple child issues",
|
|
11307
|
+
" - `feat:` \u2014 New features or functionality",
|
|
11308
|
+
" - `fix:` \u2014 Bug fixes",
|
|
11309
|
+
" - `chore:` \u2014 Maintenance: deps, tooling, config",
|
|
11310
|
+
" - `docs:` \u2014 Documentation-only work",
|
|
11311
|
+
" - `refactor:` \u2014 Code restructure, no behavior change",
|
|
11312
|
+
" - `release:` \u2014 Release preparation, version bumps",
|
|
11313
|
+
" - `hotfix:` \u2014 Urgent production fixes",
|
|
11314
|
+
" - If unclear, ask the user which type applies",
|
|
11315
|
+
"2. **Compose the issue title** in the format: `<type>: <short description>`",
|
|
11316
|
+
"3. **Determine the GitHub issue type** based on the prefix:",
|
|
11317
|
+
" - `epic:` \u2192 Epic",
|
|
11318
|
+
" - `feat:` \u2192 Feature",
|
|
11319
|
+
" - `fix:` \u2192 Bug",
|
|
11320
|
+
" - `chore:`, `docs:`, `refactor:`, `release:`, `hotfix:` \u2192 Task",
|
|
11321
|
+
"4. **Identify prerequisite issues** \u2014 if the user mentions dependencies or blockers, include a **Dependencies** section in the body with `Depends on: #<issue-number>`",
|
|
11322
|
+
"5. **Determine labels** \u2014 every issue must be created with the following labels:",
|
|
11323
|
+
" - **`type:*`** \u2014 derived from the issue title prefix:",
|
|
11324
|
+
" - `epic:` \u2192 `type:feat`",
|
|
11325
|
+
" - `feat:` \u2192 `type:feat`",
|
|
11326
|
+
" - `fix:` \u2192 `type:fix`",
|
|
11327
|
+
" - `chore:` \u2192 `type:chore`",
|
|
11328
|
+
" - `docs:` \u2192 `type:docs`",
|
|
11329
|
+
" - `refactor:` \u2192 `type:refactor`",
|
|
11330
|
+
" - `release:` \u2192 `type:release`",
|
|
11331
|
+
" - `hotfix:` \u2192 `type:hotfix`",
|
|
11332
|
+
' - **`priority:*`** \u2014 infer from the user\'s description when possible (e.g., "urgent"/"critical" \u2192 `priority:critical`, "important" \u2192 `priority:high`, "minor"/"low priority" \u2192 `priority:low`). If the priority is unclear, ask the user before creating the issue. Valid values: `priority:critical`, `priority:high`, `priority:medium`, `priority:low`, `priority:trivial`',
|
|
11333
|
+
" - **`status:ready`** \u2014 always add unless the issue has dependencies or blockers, in which case use `status:blocked`",
|
|
11334
|
+
"6. **Create the issue** using `gh issue create`:",
|
|
11335
|
+
" - `--title '<type>: <description>'`",
|
|
11336
|
+
" - `--body '<issue body>'`",
|
|
11337
|
+
" - `--label '<type-label>' --label '<priority-label>' --label '<status-label>'`",
|
|
11338
|
+
"7. **Set the GitHub issue type** by invoking the `set-issue-type.sh` helper (shipped with this bundle):",
|
|
11339
|
+
"",
|
|
11340
|
+
" ```sh",
|
|
11341
|
+
" .claude/procedures/set-issue-type.sh <issue-number> <Feature|Task|Epic|Bug>",
|
|
11342
|
+
" ```",
|
|
11343
|
+
"",
|
|
11344
|
+
" The helper resolves owner/repo, looks up the issue type node ID, looks up the issue node ID, and applies the `updateIssueIssueType` mutation in one step. It exits non-zero with a clear diagnostic on any error and lists available types if the type name is not recognised.",
|
|
11345
|
+
"",
|
|
11346
|
+
" **Under the hood** (documented fallback if the helper is unavailable): the helper performs a two-step GraphQL flow \u2014 first a `repository(...).issueTypes` query to map the human-readable type name to a node ID, then the `updateIssueIssueType` mutation. The canonical queries are:",
|
|
11347
|
+
"",
|
|
11348
|
+
" ```sh",
|
|
11349
|
+
" gh api graphql -f query='query($owner:String!,$repo:String!){repository(owner:$owner,name:$repo){issueTypes(first:50){nodes{id name}}}}' -f owner=<owner> -f repo=<repo>",
|
|
11350
|
+
" gh api graphql -f query='mutation($issueId:ID!,$typeId:ID!){updateIssueIssueType(input:{issueId:$issueId,issueTypeId:$typeId}){issue{number issueType{name}}}}' -f issueId=<issue-node-id> -f typeId=<issue-type-node-id>",
|
|
11351
|
+
" ```",
|
|
11352
|
+
"",
|
|
11353
|
+
"### Issue Body Template",
|
|
11354
|
+
"",
|
|
11355
|
+
"```markdown",
|
|
11356
|
+
"## Summary",
|
|
11357
|
+
"",
|
|
11358
|
+
"<1-3 sentences describing the issue>",
|
|
11359
|
+
"",
|
|
11360
|
+
"## Details",
|
|
11361
|
+
"",
|
|
11362
|
+
"<Detailed description, acceptance criteria, or reproduction steps as appropriate>",
|
|
11363
|
+
"",
|
|
11364
|
+
"## Dependencies",
|
|
11365
|
+
"",
|
|
11366
|
+
"Depends on: #<issue-number> (if any, otherwise omit this section)",
|
|
11367
|
+
"```",
|
|
11368
|
+
"",
|
|
11369
|
+
"### Important",
|
|
11370
|
+
"",
|
|
11371
|
+
"- Always use the conventional prefix in the issue title",
|
|
11372
|
+
"- Always assign the correct GitHub issue type via the `set-issue-type.sh` helper (step 7) \u2014 never via `gh issue create --type`",
|
|
11373
|
+
"- Always include `type:*`, `priority:*`, and `status:*` labels",
|
|
11374
|
+
"- If the user does not specify a type, ask before creating the issue",
|
|
11375
|
+
"- If the priority cannot be inferred from the description, ask the user before creating the issue",
|
|
11376
|
+
"- Keep titles concise and descriptive"
|
|
11377
|
+
].join("\n"),
|
|
11378
|
+
tags: ["workflow"]
|
|
11379
|
+
},
|
|
11380
|
+
{
|
|
11381
|
+
name: "pr-workflow",
|
|
11382
|
+
description: "Automated workflow for opening a pull request",
|
|
11383
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
11384
|
+
content: [
|
|
11385
|
+
"# PR Workflow",
|
|
11386
|
+
"",
|
|
11387
|
+
'## "Open a PR" Automation',
|
|
11388
|
+
"",
|
|
11389
|
+
"When the user says **open a PR** (or similar), follow these steps exactly:",
|
|
11390
|
+
"",
|
|
11391
|
+
"1. **Regenerate project files** \u2014 run the three-step regen sequence (`pnpm i`, then `pnpm exec projen`, then `pnpm i` again) to ensure all generated files are up to date. The leading `pnpm i` is required because `pnpm exec projen` synthesises against whatever version of configulator (and projen, and any projen plugins) is currently resolved in `node_modules`; if the lockfile has moved past `node_modules` (typically right after `git pull` lands a dependency upgrade, or on a fresh checkout), synth runs against stale templates and produces phantom drift in `.claude/`, `.github/labels.yml`, `CLAUDE.md`, and other generated files. The trailing `pnpm i` picks up any dependency changes projen wrote into `package.json` during synth. Check `git diff` after the third step \u2014 if there are changes, commit them before proceeding.",
|
|
11392
|
+
...renderPrWorkflowBuildStep(buildPolicy),
|
|
11393
|
+
"3. **Check for uncommitted changes** \u2014 if any exist, commit them with a conventional commit message",
|
|
11394
|
+
"4. **Pull and rebase from the default branch** \u2014 run `git pull origin {{repository.defaultBranch}} --rebase` to incorporate the latest changes and resolve any conflicts before pushing",
|
|
11395
|
+
"5. **Push the branch** to origin: `git push -u origin <branch>`",
|
|
11396
|
+
"6. **Create the PR** using `gh pr create`:",
|
|
11397
|
+
" - **Title**: use a conventional commit style title (e.g., `feat(scope): short description`)",
|
|
11398
|
+
" - **Body**: include `Closes #<issue-number>` (derived from the branch name) and a brief summary of changes",
|
|
11399
|
+
"7. **Delegate review and merge to the `pr-reviewer` sub-agent.** After the PR is created, invoke the `/review-pr <pr-number>` skill (or otherwise hand the new PR number to the `pr-reviewer` sub-agent). The reviewer verifies the diff against the linked issue's acceptance criteria and enables squash auto-merge when all checks pass. Do **not** run `gh pr merge --auto` yourself \u2014 review/merge policy lives solely in the `pr-reviewer` agent.",
|
|
11400
|
+
"",
|
|
11401
|
+
"### PR Body Template",
|
|
11402
|
+
"",
|
|
11403
|
+
"```markdown",
|
|
11404
|
+
"## Summary",
|
|
11405
|
+
"",
|
|
11406
|
+
"<1-3 bullet points describing what changed and why>",
|
|
11407
|
+
"",
|
|
11408
|
+
"Closes #<issue-number>",
|
|
11409
|
+
"",
|
|
11410
|
+
"## Test Plan",
|
|
11411
|
+
"",
|
|
11412
|
+
"- [ ] Tests pass locally",
|
|
11413
|
+
"- [ ] Relevant changes have been reviewed",
|
|
11414
|
+
"```",
|
|
11415
|
+
"",
|
|
11416
|
+
"### Important",
|
|
11417
|
+
"",
|
|
11418
|
+
"- Always derive the issue number from the branch name (e.g., `feat/42-add-login` \u2192 `#42`)",
|
|
11419
|
+
"- Use conventional commit format for the PR title",
|
|
11420
|
+
"- Delegate merge to the `pr-reviewer` sub-agent \u2014 do not merge manually and do not enable auto-merge directly"
|
|
11421
|
+
].join("\n"),
|
|
11422
|
+
tags: ["workflow"]
|
|
11423
|
+
},
|
|
11424
|
+
{
|
|
11425
|
+
name: "branch-cleanup",
|
|
11426
|
+
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).",
|
|
11427
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
11428
|
+
content: [
|
|
11429
|
+
"# Branch Cleanup",
|
|
11430
|
+
"",
|
|
11431
|
+
"Local branches accumulate after every merged PR. In squash-merge",
|
|
11432
|
+
"repositories `git branch -d` refuses to delete them because the",
|
|
11433
|
+
"commit hash on the base differs, even when the branch content is",
|
|
11434
|
+
"fully merged. The `github-workflow` bundle ships two affordances",
|
|
11435
|
+
"that use content-equality (not commit-graph reachability) to",
|
|
11436
|
+
"identify branches safe to force-delete:",
|
|
11437
|
+
"",
|
|
11438
|
+
"- `/clean-merged-branches` \u2014 interactive slash-command skill that",
|
|
11439
|
+
" classifies every local branch, prompts for confirmation, then",
|
|
11440
|
+
" runs `git branch -D` on the confirmed list. See",
|
|
11441
|
+
" `.claude/skills/clean-merged-branches/SKILL.md` for usage,",
|
|
11442
|
+
" output format, and the squash-merge verification algorithm.",
|
|
11443
|
+
"- `.claude/procedures/clean-merged-branches.sh` \u2014 analysis-only",
|
|
11444
|
+
" procedure for non-interactive agent use (orchestrator,",
|
|
11445
|
+
" maintenance-audit). NEVER deletes \u2014 only reports `MERGED` /",
|
|
11446
|
+
" `UNMERGED` / `EMPTY` / `SKIP_WORKTREE` lines."
|
|
11447
|
+
].join("\n"),
|
|
11448
|
+
tags: ["workflow"]
|
|
11449
|
+
}
|
|
11450
|
+
],
|
|
11451
|
+
skills: [cleanMergedBranchesSkill],
|
|
11452
|
+
procedures: [setIssueTypeProcedure, cleanMergedBranchesProcedure]
|
|
11453
|
+
};
|
|
11454
|
+
}
|
|
11455
|
+
var githubWorkflowBundle = buildGithubWorkflowBundle();
|
|
11027
11456
|
|
|
11028
11457
|
// src/agent/bundles/industry-discovery.ts
|
|
11029
11458
|
function buildIndustryDiscoveryAnalystSubAgent(paths, issueDefaults) {
|
|
@@ -13223,188 +13652,6 @@ function buildMeetingAnalysisBundle(tier = AGENT_MODEL.BALANCED) {
|
|
|
13223
13652
|
}
|
|
13224
13653
|
var meetingAnalysisBundle = buildMeetingAnalysisBundle();
|
|
13225
13654
|
|
|
13226
|
-
// src/agent/bundles/run-ratio.ts
|
|
13227
|
-
var DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO = 4;
|
|
13228
|
-
var DEFAULT_STATE_FILE_PATH = ".state/orchestrator-runs.json";
|
|
13229
|
-
var DEFAULT_DISPATCH_MODEL = "opus";
|
|
13230
|
-
var DEFAULT_HOUSEKEEPING_MODEL = "sonnet";
|
|
13231
|
-
function resolveRunRatio(config) {
|
|
13232
|
-
const ratio = config?.ratio ?? DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO;
|
|
13233
|
-
assertValidRatio(ratio);
|
|
13234
|
-
const stateFilePath = config?.stateFilePath ?? DEFAULT_STATE_FILE_PATH;
|
|
13235
|
-
assertValidStateFilePath(stateFilePath);
|
|
13236
|
-
return {
|
|
13237
|
-
enabled: config?.enabled ?? true,
|
|
13238
|
-
ratio,
|
|
13239
|
-
stateFilePath,
|
|
13240
|
-
dispatchModel: config?.dispatchModel ?? DEFAULT_DISPATCH_MODEL,
|
|
13241
|
-
housekeepingModel: config?.housekeepingModel ?? DEFAULT_HOUSEKEEPING_MODEL
|
|
13242
|
-
};
|
|
13243
|
-
}
|
|
13244
|
-
function validateRunRatioConfig(config) {
|
|
13245
|
-
return resolveRunRatio(config);
|
|
13246
|
-
}
|
|
13247
|
-
function classifyRun(runCounter, ratio) {
|
|
13248
|
-
if (!ratio.enabled) {
|
|
13249
|
-
return "dispatch";
|
|
13250
|
-
}
|
|
13251
|
-
const cycle = ratio.ratio + 1;
|
|
13252
|
-
return runCounter > 0 && runCounter % cycle === 0 ? "housekeeping" : "dispatch";
|
|
13253
|
-
}
|
|
13254
|
-
function renderRunRatioSection(ratio) {
|
|
13255
|
-
const lines = [
|
|
13256
|
-
"## Run ratio (dispatch vs housekeeping)",
|
|
13257
|
-
"",
|
|
13258
|
-
"The orchestrator keeps a **persistent run counter** and interleaves",
|
|
13259
|
-
"dispatch runs (pick the next ready issue, recommend a worker) with",
|
|
13260
|
-
"**housekeeping runs** (batch PR review + maintenance scan) on a",
|
|
13261
|
-
"configurable ratio. This mirrors openhi's `DISPATCHER.md` contract:",
|
|
13262
|
-
"multiple dispatch runs feed the worker queue, then one batched",
|
|
13263
|
-
"housekeeping run flushes review backlog and runs maintenance",
|
|
13264
|
-
"triage so the pipeline never drifts.",
|
|
13265
|
-
""
|
|
13266
|
-
];
|
|
13267
|
-
if (!ratio.enabled) {
|
|
13268
|
-
lines.push(
|
|
13269
|
-
"**The run ratio is disabled for this project.** Every orchestrator",
|
|
13270
|
-
"run executes the full dispatch pipeline; PR review and maintenance",
|
|
13271
|
-
"remain manual invocations. Enable the ratio via",
|
|
13272
|
-
"`AgentConfigOptions.runRatio.enabled = true` once the operator is",
|
|
13273
|
-
"comfortable with the counter-backed cadence.",
|
|
13274
|
-
""
|
|
13275
|
-
);
|
|
13276
|
-
return lines.join("\n");
|
|
13277
|
-
}
|
|
13278
|
-
const cycle = ratio.ratio + 1;
|
|
13279
|
-
lines.push(
|
|
13280
|
-
"### Cadence",
|
|
13281
|
-
"",
|
|
13282
|
-
`The cycle length is **${cycle}** runs:`,
|
|
13283
|
-
"",
|
|
13284
|
-
`- Runs 1 through ${ratio.ratio} execute the **dispatch** pipeline`,
|
|
13285
|
-
` (recommended model: \`${ratio.dispatchModel}\`).`,
|
|
13286
|
-
`- Run ${cycle} executes the **housekeeping** pipeline`,
|
|
13287
|
-
` (recommended model: \`${ratio.housekeepingModel}\`).`,
|
|
13288
|
-
`- The counter wraps \u2014 run ${cycle + 1} is a dispatch run again, run ${cycle * 2} is the next housekeeping run, and so on.`,
|
|
13289
|
-
"",
|
|
13290
|
-
"The orchestrator increments the counter **once per invocation** at",
|
|
13291
|
-
"the top of the run, before any pipeline phase executes. The",
|
|
13292
|
-
"pre-increment value is never observed \u2014 the tick always returns",
|
|
13293
|
-
"the post-increment counter and the classified run type in a single",
|
|
13294
|
-
"atomic update.",
|
|
13295
|
-
"",
|
|
13296
|
-
"### State file",
|
|
13297
|
-
"",
|
|
13298
|
-
`The run counter persists at \`${ratio.stateFilePath}\`. The file is`,
|
|
13299
|
-
"plain JSON with a single `run_counter` integer field:",
|
|
13300
|
-
"",
|
|
13301
|
-
"```json",
|
|
13302
|
-
'{ "run_counter": 42 }',
|
|
13303
|
-
"```",
|
|
13304
|
-
"",
|
|
13305
|
-
"The state file is **gitignored** in consumer repos (it is local to",
|
|
13306
|
-
"each operator's machine). On a first run, or if the file is missing",
|
|
13307
|
-
"or corrupt, the orchestrator recreates it with `run_counter: 1`.",
|
|
13308
|
-
"",
|
|
13309
|
-
"### Dispatch-run pipeline",
|
|
13310
|
-
"",
|
|
13311
|
-
"1. Phase A \u2014 startup (fetch + checkout default branch).",
|
|
13312
|
-
"2. Phase C \u2014 triage unblock (resolve `Depends on:` chains).",
|
|
13313
|
-
"3. Phase E \u2014 queue scan (pick the top `PICK` line, run the scope",
|
|
13314
|
-
" gate, emit `NEXT_WORK_ITEM`).",
|
|
13315
|
-
"4. Phase F \u2014 cleanup.",
|
|
13316
|
-
"",
|
|
13317
|
-
"### Housekeeping-run pipeline",
|
|
13318
|
-
"",
|
|
13319
|
-
"1. Phase A \u2014 startup.",
|
|
13320
|
-
"2. Phase B \u2014 batch PR review across every eligible open PR.",
|
|
13321
|
-
"3. Phase D \u2014 maintenance scan (stale detection, orphaned branches,",
|
|
13322
|
-
" needs-attention summary).",
|
|
13323
|
-
"4. Phase F \u2014 cleanup.",
|
|
13324
|
-
"",
|
|
13325
|
-
"### Model recommendations",
|
|
13326
|
-
"",
|
|
13327
|
-
`Dispatch runs should use \`${ratio.dispatchModel}\` \u2014 the routing`,
|
|
13328
|
-
"logic, scope gate, and funnel-tier sort benefit from the stronger",
|
|
13329
|
-
"reasoning model. Housekeeping runs are mechanical (read CI status,",
|
|
13330
|
-
"toggle labels, post canned comments) and should use",
|
|
13331
|
-
`\`${ratio.housekeepingModel}\` so the batched PR review and`,
|
|
13332
|
-
"maintenance scan cost less per invocation.",
|
|
13333
|
-
"",
|
|
13334
|
-
"These strings are **informational** \u2014 they surface in the",
|
|
13335
|
-
"orchestrator's rendered rule content so operators know which model",
|
|
13336
|
-
"to run each session against. Configulator does not inject them as",
|
|
13337
|
-
"`model:` frontmatter on the sub-agent definition; the operator (or",
|
|
13338
|
-
"a scheduled task) picks the model at invocation time."
|
|
13339
|
-
);
|
|
13340
|
-
return lines.join("\n");
|
|
13341
|
-
}
|
|
13342
|
-
function renderRunRatioShellHelpers(ratio) {
|
|
13343
|
-
const cycle = ratio.ratio + 1;
|
|
13344
|
-
return [
|
|
13345
|
-
"# Increment the orchestrator run counter and classify the run.",
|
|
13346
|
-
"# Reads the state file (creating it on first run or corruption),",
|
|
13347
|
-
"# increments the counter, writes back atomically, and echoes",
|
|
13348
|
-
"# `run=<n> type=<dispatch|housekeeping>` on stdout.",
|
|
13349
|
-
"#",
|
|
13350
|
-
"# Uses the cycle length (ratio + 1) hard-coded from the resolved",
|
|
13351
|
-
"# RunRatioConfig so the shell helper matches the rendered rule",
|
|
13352
|
-
"# content byte-for-byte.",
|
|
13353
|
-
"run_counter_tick() {",
|
|
13354
|
-
' local state_file="$ORCHESTRATOR_STATE_FILE"',
|
|
13355
|
-
" local state_dir",
|
|
13356
|
-
' state_dir=$(dirname "$state_file")',
|
|
13357
|
-
' mkdir -p "$state_dir" 2>/dev/null || true',
|
|
13358
|
-
"",
|
|
13359
|
-
" local current=0",
|
|
13360
|
-
' if [ -f "$state_file" ]; then',
|
|
13361
|
-
" # jq returns empty string on parse failure; guard against it.",
|
|
13362
|
-
` current=$(jq -r '.run_counter // 0' "$state_file" 2>/dev/null || echo 0)`,
|
|
13363
|
-
' case "$current" in',
|
|
13364
|
-
" ''|*[!0-9]*) current=0 ;;",
|
|
13365
|
-
" esac",
|
|
13366
|
-
" fi",
|
|
13367
|
-
"",
|
|
13368
|
-
" local next=$((current + 1))",
|
|
13369
|
-
"",
|
|
13370
|
-
' local tmp_file="${state_file}.tmp.$$"',
|
|
13371
|
-
` printf '{ "run_counter": %d }\\n' "$next" > "$tmp_file"`,
|
|
13372
|
-
' mv "$tmp_file" "$state_file"',
|
|
13373
|
-
"",
|
|
13374
|
-
" local run_type=dispatch",
|
|
13375
|
-
` if [ $((next % ${cycle})) -eq 0 ]; then`,
|
|
13376
|
-
" run_type=housekeeping",
|
|
13377
|
-
" fi",
|
|
13378
|
-
` printf 'run=%d type=%s\\n' "$next" "$run_type"`,
|
|
13379
|
-
"}"
|
|
13380
|
-
].join("\n");
|
|
13381
|
-
}
|
|
13382
|
-
function assertValidRatio(ratio) {
|
|
13383
|
-
if (!Number.isInteger(ratio)) {
|
|
13384
|
-
throw new Error(
|
|
13385
|
-
`RunRatioConfig.ratio must be a positive integer; got ${ratio}`
|
|
13386
|
-
);
|
|
13387
|
-
}
|
|
13388
|
-
if (ratio < 1) {
|
|
13389
|
-
throw new Error(
|
|
13390
|
-
`RunRatioConfig.ratio must be a positive integer; got ${ratio}`
|
|
13391
|
-
);
|
|
13392
|
-
}
|
|
13393
|
-
}
|
|
13394
|
-
function assertValidStateFilePath(stateFilePath) {
|
|
13395
|
-
const trimmed = stateFilePath.trim();
|
|
13396
|
-
if (trimmed.length === 0) {
|
|
13397
|
-
throw new Error(
|
|
13398
|
-
"RunRatioConfig.stateFilePath must be a non-empty string relative to the repo root"
|
|
13399
|
-
);
|
|
13400
|
-
}
|
|
13401
|
-
if (trimmed.startsWith("/")) {
|
|
13402
|
-
throw new Error(
|
|
13403
|
-
`RunRatioConfig.stateFilePath must be relative to the repo root (no leading '/'); got ${stateFilePath}`
|
|
13404
|
-
);
|
|
13405
|
-
}
|
|
13406
|
-
}
|
|
13407
|
-
|
|
13408
13655
|
// src/agent/bundles/bundle-ownership.ts
|
|
13409
13656
|
var BUNDLE_OWNERSHIP = {
|
|
13410
13657
|
agenda: {
|
|
@@ -13548,6 +13795,255 @@ var BUNDLE_OWNERSHIP = {
|
|
|
13548
13795
|
downstreamIssueKinds: true
|
|
13549
13796
|
}
|
|
13550
13797
|
};
|
|
13798
|
+
var CONVENTIONAL_COMMIT_TYPE_LABELS = [
|
|
13799
|
+
"type:chore",
|
|
13800
|
+
"type:docs",
|
|
13801
|
+
"type:feat",
|
|
13802
|
+
"type:fix",
|
|
13803
|
+
"type:hotfix",
|
|
13804
|
+
"type:refactor",
|
|
13805
|
+
"type:release"
|
|
13806
|
+
];
|
|
13807
|
+
var PHASE_LABEL_TYPE_MAP = buildPhaseLabelTypeMap();
|
|
13808
|
+
function typeLabelForPhaseLabel(phaseLabel) {
|
|
13809
|
+
const exact = PHASE_LABEL_TYPE_MAP[phaseLabel];
|
|
13810
|
+
if (exact !== void 0 && !phaseLabel.endsWith(":")) {
|
|
13811
|
+
return exact;
|
|
13812
|
+
}
|
|
13813
|
+
let bestMatcher;
|
|
13814
|
+
for (const matcher of Object.keys(PHASE_LABEL_TYPE_MAP)) {
|
|
13815
|
+
if (!matcher.endsWith(":")) {
|
|
13816
|
+
continue;
|
|
13817
|
+
}
|
|
13818
|
+
if (!phaseLabel.startsWith(matcher)) {
|
|
13819
|
+
continue;
|
|
13820
|
+
}
|
|
13821
|
+
if (bestMatcher === void 0 || matcher.length > bestMatcher.length) {
|
|
13822
|
+
bestMatcher = matcher;
|
|
13823
|
+
}
|
|
13824
|
+
}
|
|
13825
|
+
return bestMatcher === void 0 ? void 0 : PHASE_LABEL_TYPE_MAP[bestMatcher];
|
|
13826
|
+
}
|
|
13827
|
+
function resolveTypeLabelForLabels(labels) {
|
|
13828
|
+
const phaseLabels = [];
|
|
13829
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
13830
|
+
for (const label of labels) {
|
|
13831
|
+
const typeLabel = typeLabelForPhaseLabel(label);
|
|
13832
|
+
if (typeLabel === void 0) {
|
|
13833
|
+
continue;
|
|
13834
|
+
}
|
|
13835
|
+
phaseLabels.push(label);
|
|
13836
|
+
candidates.add(typeLabel);
|
|
13837
|
+
}
|
|
13838
|
+
const candidateTypeLabels = Array.from(candidates).sort();
|
|
13839
|
+
if (candidateTypeLabels.length === 0) {
|
|
13840
|
+
return { outcome: "none", candidateTypeLabels: [], phaseLabels: [] };
|
|
13841
|
+
}
|
|
13842
|
+
if (candidateTypeLabels.length === 1) {
|
|
13843
|
+
return {
|
|
13844
|
+
outcome: "match",
|
|
13845
|
+
typeLabel: candidateTypeLabels[0],
|
|
13846
|
+
candidateTypeLabels,
|
|
13847
|
+
phaseLabels
|
|
13848
|
+
};
|
|
13849
|
+
}
|
|
13850
|
+
return { outcome: "ambiguous", candidateTypeLabels, phaseLabels };
|
|
13851
|
+
}
|
|
13852
|
+
function renderPhaseTypeInvariantSection(excludeBundles = []) {
|
|
13853
|
+
const rows = Object.keys(PHASE_LABEL_TYPE_MAP).filter(
|
|
13854
|
+
(matcher) => !isPhaseLabelMatcherOwnedByExcluded(matcher, excludeBundles)
|
|
13855
|
+
).sort().map((matcher) => {
|
|
13856
|
+
const display = matcher.endsWith(":") ? `${matcher}*` : matcher;
|
|
13857
|
+
const kind = matcher.endsWith(":") ? "prefix" : "exact";
|
|
13858
|
+
return `| \`${display}\` | ${kind} | \`${PHASE_LABEL_TYPE_MAP[matcher]}\` |`;
|
|
13859
|
+
});
|
|
13860
|
+
return [
|
|
13861
|
+
"## Phase-label \u2192 `type:<bundle>` invariant",
|
|
13862
|
+
"",
|
|
13863
|
+
"Every phased-pipeline bundle pairs its `<bundle>:<phase>` labels",
|
|
13864
|
+
"with exactly one `type:<bundle>` label. That type label is the",
|
|
13865
|
+
"**dedup + dispatch signal**: agents de-duplicate downstream work",
|
|
13866
|
+
'with `gh issue list --label "type:<bundle>"`, and Phase E derives',
|
|
13867
|
+
"its funnel-tier sort key from the issue's `type:*` label. An issue",
|
|
13868
|
+
"that carries the phase label but a conventional-commit `type:*`",
|
|
13869
|
+
"(`type:feat`, `type:docs`, \u2026, stamped from its title prefix by the",
|
|
13870
|
+
"generic create-issue workflow) is invisible to that dedup query and",
|
|
13871
|
+
"mis-tiers in dispatch.",
|
|
13872
|
+
"",
|
|
13873
|
+
"The pairing below is derived from the canonical bundle-ownership",
|
|
13874
|
+
"map \u2014 the same source of truth that generates the label registry.",
|
|
13875
|
+
"Matchers ending in `*` match by prefix; the rest match exactly, and",
|
|
13876
|
+
"an exact match always beats a prefix match.",
|
|
13877
|
+
"",
|
|
13878
|
+
"| Phase label | Match | Required type label |",
|
|
13879
|
+
"|-------------|-------|---------------------|",
|
|
13880
|
+
...rows,
|
|
13881
|
+
"",
|
|
13882
|
+
"### Enforcement",
|
|
13883
|
+
"",
|
|
13884
|
+
"The Phase D maintenance sweep runs the invariant in auto-correct",
|
|
13885
|
+
"mode and emits one summary line:",
|
|
13886
|
+
"",
|
|
13887
|
+
"```",
|
|
13888
|
+
"LABEL_INVARIANT_DONE mode=fix checked=<N> ok=<O> violations=<V> corrected=<C> flagged=<F>",
|
|
13889
|
+
"```",
|
|
13890
|
+
"",
|
|
13891
|
+
"A **report-only** audit is available for repos that prefer",
|
|
13892
|
+
"report-then-fix over auto-correct \u2014 same sweep, no mutations:",
|
|
13893
|
+
"",
|
|
13894
|
+
"```bash",
|
|
13895
|
+
".claude/procedures/check-blocked.sh label-invariant # report only",
|
|
13896
|
+
".claude/procedures/check-blocked.sh label-invariant --fix # apply",
|
|
13897
|
+
"```",
|
|
13898
|
+
"",
|
|
13899
|
+
"### Correction policy",
|
|
13900
|
+
"",
|
|
13901
|
+
"An issue must carry **exactly one** `type:*` label. Merely *adding*",
|
|
13902
|
+
"the required type label to an issue that already carries a",
|
|
13903
|
+
"conventional-commit one would leave two, making the funnel-tier sort",
|
|
13904
|
+
"key (which reads the **first** `type:*` label) non-deterministic and",
|
|
13905
|
+
"violating the label conventions. The correction therefore",
|
|
13906
|
+
"**replaces**, in a single atomic `gh issue edit`, so the issue is",
|
|
13907
|
+
"never observable carrying two `type:*` labels:",
|
|
13908
|
+
"",
|
|
13909
|
+
"| Issue state | Action |",
|
|
13910
|
+
"|-------------|--------|",
|
|
13911
|
+
"| Required type label present, nothing else | none \u2014 compliant, left untouched |",
|
|
13912
|
+
"| No `type:*` label at all | **add** the required label |",
|
|
13913
|
+
"| Only conventional-commit `type:*` label(s) | **replace** \u2014 remove them, add the required label |",
|
|
13914
|
+
"| Required label present **plus** a stray conventional-commit one | **remove** the stray, leaving exactly one |",
|
|
13915
|
+
"| A `type:*` label owned by a **different** bundle | **flag** `status:needs-attention` \u2014 never guessed at |",
|
|
13916
|
+
"| Phase labels imply **two or more** type labels | **flag** `status:needs-attention` \u2014 never auto-corrected |",
|
|
13917
|
+
"",
|
|
13918
|
+
"Only a conventional-commit `type:*` label is ever removed. A",
|
|
13919
|
+
"`type:*` label owned by another bundle carries real routing",
|
|
13920
|
+
"information, so the sweep refuses to guess and hands the issue to a",
|
|
13921
|
+
"human instead. Ambiguity is narrower than it looks: all three",
|
|
13922
|
+
"requirements bundles declare `type:requirement`, so every `req:*`",
|
|
13923
|
+
"phase label resolves to the same type label \u2014 genuine ambiguity",
|
|
13924
|
+
"needs phase labels from two bundles with *different* type labels.",
|
|
13925
|
+
"",
|
|
13926
|
+
"`status:needs-attention` is applied **additively** \u2014 the base",
|
|
13927
|
+
"`status:*` label always stays, per the additive-flag rule. Flagging",
|
|
13928
|
+
"is idempotent: an issue already carrying the flag is reported but",
|
|
13929
|
+
"not re-flagged."
|
|
13930
|
+
].join("\n");
|
|
13931
|
+
}
|
|
13932
|
+
function isPhaseLabelMatcherOwnedByExcluded(matcher, excludeBundles) {
|
|
13933
|
+
if (excludeBundles.length === 0) {
|
|
13934
|
+
return false;
|
|
13935
|
+
}
|
|
13936
|
+
let owned = false;
|
|
13937
|
+
for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
|
|
13938
|
+
if (!ownership.phaseLabelPrefixes.includes(matcher)) {
|
|
13939
|
+
continue;
|
|
13940
|
+
}
|
|
13941
|
+
owned = true;
|
|
13942
|
+
if (!excludeBundles.includes(bundleName)) {
|
|
13943
|
+
return false;
|
|
13944
|
+
}
|
|
13945
|
+
}
|
|
13946
|
+
return owned;
|
|
13947
|
+
}
|
|
13948
|
+
function renderPhaseTypeInvariantShellHelpers() {
|
|
13949
|
+
const matchers = Object.keys(PHASE_LABEL_TYPE_MAP);
|
|
13950
|
+
const exactMatchers = matchers.filter((m) => !m.endsWith(":")).sort();
|
|
13951
|
+
const prefixMatchers = matchers.filter((m) => m.endsWith(":")).sort((a, b) => b.length - a.length || a.localeCompare(b));
|
|
13952
|
+
const lines = [
|
|
13953
|
+
"# Resolve ONE phase label to the `type:<bundle>` label its owning",
|
|
13954
|
+
"# bundle declares. Echoes the label (with the `type:` prefix) or",
|
|
13955
|
+
"# nothing when no bundle owns it. Exact-match branches come first,",
|
|
13956
|
+
"# so `req:write` (requirements-writer) beats the `req:` prefix",
|
|
13957
|
+
"# (requirements-analyst). Generated from BUNDLE_OWNERSHIP \u2014 do not",
|
|
13958
|
+
"# hand-edit.",
|
|
13959
|
+
"phase_label_type_of() {",
|
|
13960
|
+
' case "${1:-}" in'
|
|
13961
|
+
];
|
|
13962
|
+
for (const matcher of exactMatchers) {
|
|
13963
|
+
lines.push(` ${matcher}) echo "${PHASE_LABEL_TYPE_MAP[matcher]}" ;;`);
|
|
13964
|
+
}
|
|
13965
|
+
for (const matcher of prefixMatchers) {
|
|
13966
|
+
lines.push(` ${matcher}*) echo "${PHASE_LABEL_TYPE_MAP[matcher]}" ;;`);
|
|
13967
|
+
}
|
|
13968
|
+
lines.push(
|
|
13969
|
+
" *) : ;;",
|
|
13970
|
+
" esac",
|
|
13971
|
+
"}",
|
|
13972
|
+
"",
|
|
13973
|
+
"# Return 0 when the argument is a conventional-commit `type:*`",
|
|
13974
|
+
"# label \u2014 the ONLY type labels the invariant correction may remove.",
|
|
13975
|
+
"# A `type:*` label owned by another bundle is never removed; the",
|
|
13976
|
+
"# issue is flagged for human triage instead.",
|
|
13977
|
+
"is_conventional_type_label() {",
|
|
13978
|
+
` case "\${1:-}" in`,
|
|
13979
|
+
` ${CONVENTIONAL_COMMIT_TYPE_LABELS.join("|")}) return 0 ;;`,
|
|
13980
|
+
" *) return 1 ;;",
|
|
13981
|
+
" esac",
|
|
13982
|
+
"}",
|
|
13983
|
+
"",
|
|
13984
|
+
"# Resolve an issue's FULL label list (one label per line on stdin)",
|
|
13985
|
+
"# to the type label the phase-label invariant requires. Emits",
|
|
13986
|
+
"# KEY=VALUE assignments the caller parses:",
|
|
13987
|
+
"# OUTCOME=none \u2014 no recognised phase label; invariant N/A",
|
|
13988
|
+
"# OUTCOME=match \u2014 plus TYPE_LABEL=<type:bundle>",
|
|
13989
|
+
"# OUTCOME=ambiguous \u2014 plus CANDIDATE_TYPE_LABELS=<space-separated>",
|
|
13990
|
+
"# PHASE_LABELS=<space-separated recognised phase labels>",
|
|
13991
|
+
"phase_type_of() {",
|
|
13992
|
+
" local label resolved",
|
|
13993
|
+
' local matched_types=""',
|
|
13994
|
+
' local matched_phase=""',
|
|
13995
|
+
" while IFS= read -r label; do",
|
|
13996
|
+
' [ -z "$label" ] && continue',
|
|
13997
|
+
' resolved=$(phase_label_type_of "$label")',
|
|
13998
|
+
' [ -z "$resolved" ] && continue',
|
|
13999
|
+
' matched_phase="${matched_phase}${label} "',
|
|
14000
|
+
' case " ${matched_types} " in',
|
|
14001
|
+
' *" ${resolved} "*) ;;',
|
|
14002
|
+
' *) matched_types="${matched_types}${resolved} " ;;',
|
|
14003
|
+
" esac",
|
|
14004
|
+
" done",
|
|
14005
|
+
" local count=0",
|
|
14006
|
+
" for resolved in $matched_types; do",
|
|
14007
|
+
" count=$((count + 1))",
|
|
14008
|
+
" done",
|
|
14009
|
+
' if [ "$count" -eq 0 ]; then',
|
|
14010
|
+
' echo "OUTCOME=none"',
|
|
14011
|
+
' elif [ "$count" -eq 1 ]; then',
|
|
14012
|
+
' echo "OUTCOME=match"',
|
|
14013
|
+
' echo "TYPE_LABEL=${matched_types% }"',
|
|
14014
|
+
" else",
|
|
14015
|
+
' echo "OUTCOME=ambiguous"',
|
|
14016
|
+
' echo "CANDIDATE_TYPE_LABELS=${matched_types% }"',
|
|
14017
|
+
" fi",
|
|
14018
|
+
' echo "PHASE_LABELS=${matched_phase% }"',
|
|
14019
|
+
"}"
|
|
14020
|
+
);
|
|
14021
|
+
return lines.join("\n");
|
|
14022
|
+
}
|
|
14023
|
+
function buildPhaseLabelTypeMap() {
|
|
14024
|
+
const map = {};
|
|
14025
|
+
for (const [bundleName, ownership] of Object.entries(BUNDLE_OWNERSHIP)) {
|
|
14026
|
+
if (ownership.phaseLabelPrefixes.length === 0) {
|
|
14027
|
+
continue;
|
|
14028
|
+
}
|
|
14029
|
+
if (ownership.typeLabels.length !== 1) {
|
|
14030
|
+
throw new Error(
|
|
14031
|
+
`BUNDLE_OWNERSHIP["${bundleName}"] declares ${ownership.phaseLabelPrefixes.length} phase-label matcher(s) but ${ownership.typeLabels.length} type label(s); a phase label must pair with exactly one type label`
|
|
14032
|
+
);
|
|
14033
|
+
}
|
|
14034
|
+
const typeLabel = `type:${ownership.typeLabels[0]}`;
|
|
14035
|
+
for (const matcher of ownership.phaseLabelPrefixes) {
|
|
14036
|
+
const existing = map[matcher];
|
|
14037
|
+
if (existing !== void 0 && existing !== typeLabel) {
|
|
14038
|
+
throw new Error(
|
|
14039
|
+
`Phase-label matcher "${matcher}" resolves to both "${existing}" and "${typeLabel}"; a matcher must imply exactly one type label`
|
|
14040
|
+
);
|
|
14041
|
+
}
|
|
14042
|
+
map[matcher] = typeLabel;
|
|
14043
|
+
}
|
|
14044
|
+
}
|
|
14045
|
+
return map;
|
|
14046
|
+
}
|
|
13551
14047
|
function isTypeLabelOwnedByExcluded(typeLabel, excludedBundles) {
|
|
13552
14048
|
if (excludedBundles.length === 0) {
|
|
13553
14049
|
return false;
|
|
@@ -13637,6 +14133,188 @@ function findOwnersOfTypeLabel(typeLabel) {
|
|
|
13637
14133
|
return owners;
|
|
13638
14134
|
}
|
|
13639
14135
|
|
|
14136
|
+
// src/agent/bundles/run-ratio.ts
|
|
14137
|
+
var DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO = 4;
|
|
14138
|
+
var DEFAULT_STATE_FILE_PATH = ".state/orchestrator-runs.json";
|
|
14139
|
+
var DEFAULT_DISPATCH_MODEL = "opus";
|
|
14140
|
+
var DEFAULT_HOUSEKEEPING_MODEL = "sonnet";
|
|
14141
|
+
function resolveRunRatio(config) {
|
|
14142
|
+
const ratio = config?.ratio ?? DEFAULT_DISPATCH_TO_HOUSEKEEPING_RATIO;
|
|
14143
|
+
assertValidRatio(ratio);
|
|
14144
|
+
const stateFilePath = config?.stateFilePath ?? DEFAULT_STATE_FILE_PATH;
|
|
14145
|
+
assertValidStateFilePath(stateFilePath);
|
|
14146
|
+
return {
|
|
14147
|
+
enabled: config?.enabled ?? true,
|
|
14148
|
+
ratio,
|
|
14149
|
+
stateFilePath,
|
|
14150
|
+
dispatchModel: config?.dispatchModel ?? DEFAULT_DISPATCH_MODEL,
|
|
14151
|
+
housekeepingModel: config?.housekeepingModel ?? DEFAULT_HOUSEKEEPING_MODEL
|
|
14152
|
+
};
|
|
14153
|
+
}
|
|
14154
|
+
function validateRunRatioConfig(config) {
|
|
14155
|
+
return resolveRunRatio(config);
|
|
14156
|
+
}
|
|
14157
|
+
function classifyRun(runCounter, ratio) {
|
|
14158
|
+
if (!ratio.enabled) {
|
|
14159
|
+
return "dispatch";
|
|
14160
|
+
}
|
|
14161
|
+
const cycle = ratio.ratio + 1;
|
|
14162
|
+
return runCounter > 0 && runCounter % cycle === 0 ? "housekeeping" : "dispatch";
|
|
14163
|
+
}
|
|
14164
|
+
function renderRunRatioSection(ratio) {
|
|
14165
|
+
const lines = [
|
|
14166
|
+
"## Run ratio (dispatch vs housekeeping)",
|
|
14167
|
+
"",
|
|
14168
|
+
"The orchestrator keeps a **persistent run counter** and interleaves",
|
|
14169
|
+
"dispatch runs (pick the next ready issue, recommend a worker) with",
|
|
14170
|
+
"**housekeeping runs** (batch PR review + maintenance scan) on a",
|
|
14171
|
+
"configurable ratio. This mirrors openhi's `DISPATCHER.md` contract:",
|
|
14172
|
+
"multiple dispatch runs feed the worker queue, then one batched",
|
|
14173
|
+
"housekeeping run flushes review backlog and runs maintenance",
|
|
14174
|
+
"triage so the pipeline never drifts.",
|
|
14175
|
+
""
|
|
14176
|
+
];
|
|
14177
|
+
if (!ratio.enabled) {
|
|
14178
|
+
lines.push(
|
|
14179
|
+
"**The run ratio is disabled for this project.** Every orchestrator",
|
|
14180
|
+
"run executes the full dispatch pipeline; PR review and maintenance",
|
|
14181
|
+
"remain manual invocations. Enable the ratio via",
|
|
14182
|
+
"`AgentConfigOptions.runRatio.enabled = true` once the operator is",
|
|
14183
|
+
"comfortable with the counter-backed cadence.",
|
|
14184
|
+
""
|
|
14185
|
+
);
|
|
14186
|
+
return lines.join("\n");
|
|
14187
|
+
}
|
|
14188
|
+
const cycle = ratio.ratio + 1;
|
|
14189
|
+
lines.push(
|
|
14190
|
+
"### Cadence",
|
|
14191
|
+
"",
|
|
14192
|
+
`The cycle length is **${cycle}** runs:`,
|
|
14193
|
+
"",
|
|
14194
|
+
`- Runs 1 through ${ratio.ratio} execute the **dispatch** pipeline`,
|
|
14195
|
+
` (recommended model: \`${ratio.dispatchModel}\`).`,
|
|
14196
|
+
`- Run ${cycle} executes the **housekeeping** pipeline`,
|
|
14197
|
+
` (recommended model: \`${ratio.housekeepingModel}\`).`,
|
|
14198
|
+
`- The counter wraps \u2014 run ${cycle + 1} is a dispatch run again, run ${cycle * 2} is the next housekeeping run, and so on.`,
|
|
14199
|
+
"",
|
|
14200
|
+
"The orchestrator increments the counter **once per invocation** at",
|
|
14201
|
+
"the top of the run, before any pipeline phase executes. The",
|
|
14202
|
+
"pre-increment value is never observed \u2014 the tick always returns",
|
|
14203
|
+
"the post-increment counter and the classified run type in a single",
|
|
14204
|
+
"atomic update.",
|
|
14205
|
+
"",
|
|
14206
|
+
"### State file",
|
|
14207
|
+
"",
|
|
14208
|
+
`The run counter persists at \`${ratio.stateFilePath}\`. The file is`,
|
|
14209
|
+
"plain JSON with a single `run_counter` integer field:",
|
|
14210
|
+
"",
|
|
14211
|
+
"```json",
|
|
14212
|
+
'{ "run_counter": 42 }',
|
|
14213
|
+
"```",
|
|
14214
|
+
"",
|
|
14215
|
+
"The state file is **gitignored** in consumer repos (it is local to",
|
|
14216
|
+
"each operator's machine). On a first run, or if the file is missing",
|
|
14217
|
+
"or corrupt, the orchestrator recreates it with `run_counter: 1`.",
|
|
14218
|
+
"",
|
|
14219
|
+
"### Dispatch-run pipeline",
|
|
14220
|
+
"",
|
|
14221
|
+
"1. Phase A \u2014 startup (fetch + checkout default branch).",
|
|
14222
|
+
"2. Phase C \u2014 triage unblock (resolve `Depends on:` chains).",
|
|
14223
|
+
"3. Phase E \u2014 queue scan (pick the top `PICK` line, run the scope",
|
|
14224
|
+
" gate, emit `NEXT_WORK_ITEM`).",
|
|
14225
|
+
"4. Phase F \u2014 cleanup.",
|
|
14226
|
+
"",
|
|
14227
|
+
"### Housekeeping-run pipeline",
|
|
14228
|
+
"",
|
|
14229
|
+
"1. Phase A \u2014 startup.",
|
|
14230
|
+
"2. Phase B \u2014 batch PR review across every eligible open PR.",
|
|
14231
|
+
"3. Phase D \u2014 maintenance scan (stale detection, orphaned branches,",
|
|
14232
|
+
" needs-attention summary).",
|
|
14233
|
+
"4. Phase F \u2014 cleanup.",
|
|
14234
|
+
"",
|
|
14235
|
+
"### Model recommendations",
|
|
14236
|
+
"",
|
|
14237
|
+
`Dispatch runs should use \`${ratio.dispatchModel}\` \u2014 the routing`,
|
|
14238
|
+
"logic, scope gate, and funnel-tier sort benefit from the stronger",
|
|
14239
|
+
"reasoning model. Housekeeping runs are mechanical (read CI status,",
|
|
14240
|
+
"toggle labels, post canned comments) and should use",
|
|
14241
|
+
`\`${ratio.housekeepingModel}\` so the batched PR review and`,
|
|
14242
|
+
"maintenance scan cost less per invocation.",
|
|
14243
|
+
"",
|
|
14244
|
+
"These strings are **informational** \u2014 they surface in the",
|
|
14245
|
+
"orchestrator's rendered rule content so operators know which model",
|
|
14246
|
+
"to run each session against. Configulator does not inject them as",
|
|
14247
|
+
"`model:` frontmatter on the sub-agent definition; the operator (or",
|
|
14248
|
+
"a scheduled task) picks the model at invocation time."
|
|
14249
|
+
);
|
|
14250
|
+
return lines.join("\n");
|
|
14251
|
+
}
|
|
14252
|
+
function renderRunRatioShellHelpers(ratio) {
|
|
14253
|
+
const cycle = ratio.ratio + 1;
|
|
14254
|
+
return [
|
|
14255
|
+
"# Increment the orchestrator run counter and classify the run.",
|
|
14256
|
+
"# Reads the state file (creating it on first run or corruption),",
|
|
14257
|
+
"# increments the counter, writes back atomically, and echoes",
|
|
14258
|
+
"# `run=<n> type=<dispatch|housekeeping>` on stdout.",
|
|
14259
|
+
"#",
|
|
14260
|
+
"# Uses the cycle length (ratio + 1) hard-coded from the resolved",
|
|
14261
|
+
"# RunRatioConfig so the shell helper matches the rendered rule",
|
|
14262
|
+
"# content byte-for-byte.",
|
|
14263
|
+
"run_counter_tick() {",
|
|
14264
|
+
' local state_file="$ORCHESTRATOR_STATE_FILE"',
|
|
14265
|
+
" local state_dir",
|
|
14266
|
+
' state_dir=$(dirname "$state_file")',
|
|
14267
|
+
' mkdir -p "$state_dir" 2>/dev/null || true',
|
|
14268
|
+
"",
|
|
14269
|
+
" local current=0",
|
|
14270
|
+
' if [ -f "$state_file" ]; then',
|
|
14271
|
+
" # jq returns empty string on parse failure; guard against it.",
|
|
14272
|
+
` current=$(jq -r '.run_counter // 0' "$state_file" 2>/dev/null || echo 0)`,
|
|
14273
|
+
' case "$current" in',
|
|
14274
|
+
" ''|*[!0-9]*) current=0 ;;",
|
|
14275
|
+
" esac",
|
|
14276
|
+
" fi",
|
|
14277
|
+
"",
|
|
14278
|
+
" local next=$((current + 1))",
|
|
14279
|
+
"",
|
|
14280
|
+
' local tmp_file="${state_file}.tmp.$$"',
|
|
14281
|
+
` printf '{ "run_counter": %d }\\n' "$next" > "$tmp_file"`,
|
|
14282
|
+
' mv "$tmp_file" "$state_file"',
|
|
14283
|
+
"",
|
|
14284
|
+
" local run_type=dispatch",
|
|
14285
|
+
` if [ $((next % ${cycle})) -eq 0 ]; then`,
|
|
14286
|
+
" run_type=housekeeping",
|
|
14287
|
+
" fi",
|
|
14288
|
+
` printf 'run=%d type=%s\\n' "$next" "$run_type"`,
|
|
14289
|
+
"}"
|
|
14290
|
+
].join("\n");
|
|
14291
|
+
}
|
|
14292
|
+
function assertValidRatio(ratio) {
|
|
14293
|
+
if (!Number.isInteger(ratio)) {
|
|
14294
|
+
throw new Error(
|
|
14295
|
+
`RunRatioConfig.ratio must be a positive integer; got ${ratio}`
|
|
14296
|
+
);
|
|
14297
|
+
}
|
|
14298
|
+
if (ratio < 1) {
|
|
14299
|
+
throw new Error(
|
|
14300
|
+
`RunRatioConfig.ratio must be a positive integer; got ${ratio}`
|
|
14301
|
+
);
|
|
14302
|
+
}
|
|
14303
|
+
}
|
|
14304
|
+
function assertValidStateFilePath(stateFilePath) {
|
|
14305
|
+
const trimmed = stateFilePath.trim();
|
|
14306
|
+
if (trimmed.length === 0) {
|
|
14307
|
+
throw new Error(
|
|
14308
|
+
"RunRatioConfig.stateFilePath must be a non-empty string relative to the repo root"
|
|
14309
|
+
);
|
|
14310
|
+
}
|
|
14311
|
+
if (trimmed.startsWith("/")) {
|
|
14312
|
+
throw new Error(
|
|
14313
|
+
`RunRatioConfig.stateFilePath must be relative to the repo root (no leading '/'); got ${stateFilePath}`
|
|
14314
|
+
);
|
|
14315
|
+
}
|
|
14316
|
+
}
|
|
14317
|
+
|
|
13640
14318
|
// src/agent/bundles/scheduled-tasks.ts
|
|
13641
14319
|
var SCHEDULED_TASK_MODEL_VALUES = ["opus", "sonnet", "haiku"];
|
|
13642
14320
|
var SCHEDULED_TASK_KIND_VALUES = ["issue-worker", "pipeline"];
|
|
@@ -15592,6 +16270,7 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
15592
16270
|
"# .claude/procedures/check-blocked.sh maintenance",
|
|
15593
16271
|
"# .claude/procedures/check-blocked.sh prs",
|
|
15594
16272
|
"# .claude/procedures/check-blocked.sh scope <issue-number>",
|
|
16273
|
+
"# .claude/procedures/check-blocked.sh label-invariant [--fix]",
|
|
15595
16274
|
"",
|
|
15596
16275
|
"set -uo pipefail",
|
|
15597
16276
|
"",
|
|
@@ -15641,6 +16320,8 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
15641
16320
|
"",
|
|
15642
16321
|
scopeHelperIndented,
|
|
15643
16322
|
"",
|
|
16323
|
+
renderPhaseTypeInvariantShellHelpers(),
|
|
16324
|
+
"",
|
|
15644
16325
|
...renderDelegationActiveSignalsHelper(),
|
|
15645
16326
|
"",
|
|
15646
16327
|
"# \u2500\u2500 subcommands \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",
|
|
@@ -16276,16 +16957,43 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
16276
16957
|
" esac",
|
|
16277
16958
|
' done <<< "$lease_output"',
|
|
16278
16959
|
"",
|
|
16960
|
+
" # \u2500\u2500 phase-label \u2192 type:<bundle> invariant (MUTATING) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
16961
|
+
" # Run the label-invariant sweep in --fix mode and surface its",
|
|
16962
|
+
" # per-issue lines for log visibility. cmd_label_invariant applies",
|
|
16963
|
+
" # the label edits itself (a single atomic remove+add per issue, so",
|
|
16964
|
+
" # an issue is never observable carrying two type:* labels) and",
|
|
16965
|
+
" # flags the cases it must not guess at. Its terminal",
|
|
16966
|
+
" # LABEL_INVARIANT_DONE line is captured and re-emitted alongside",
|
|
16967
|
+
" # MAINTENANCE_DONE so the orchestrator reads every summary from",
|
|
16968
|
+
" # one maintenance invocation.",
|
|
16969
|
+
" local label_output",
|
|
16970
|
+
" label_output=$(cmd_label_invariant --fix)",
|
|
16971
|
+
' local label_summary="LABEL_INVARIANT_DONE mode=fix checked=0 ok=0 violations=0 corrected=0 flagged=0"',
|
|
16972
|
+
"",
|
|
16973
|
+
" while IFS= read -r line; do",
|
|
16974
|
+
' [[ -z "$line" ]] && continue',
|
|
16975
|
+
' case "$line" in',
|
|
16976
|
+
" 'LABEL_INVARIANT_DONE '*)",
|
|
16977
|
+
' label_summary="$line"',
|
|
16978
|
+
" ;;",
|
|
16979
|
+
" *)",
|
|
16980
|
+
' echo "$line"',
|
|
16981
|
+
" ;;",
|
|
16982
|
+
" esac",
|
|
16983
|
+
' done <<< "$label_output"',
|
|
16984
|
+
"",
|
|
16279
16985
|
" # \u2500\u2500 needs-attention total \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",
|
|
16280
16986
|
" local needs_attention_total",
|
|
16281
16987
|
' needs_attention_total=$(gh issue list --label "status:needs-attention" --state open \\',
|
|
16282
16988
|
" --json number --limit 100 2>/dev/null | jq 'length' 2>/dev/null || echo 0)",
|
|
16283
16989
|
" needs_attention_total=${needs_attention_total:-0}",
|
|
16284
16990
|
"",
|
|
16285
|
-
" #
|
|
16286
|
-
" # MAINTENANCE_DONE line
|
|
16991
|
+
" # Three summary lines consumed by the orchestrator: the",
|
|
16992
|
+
" # issue/orphan MAINTENANCE_DONE line, the PR-lease LEASE_RECONCILE",
|
|
16993
|
+
" # line, and the LABEL_INVARIANT_DONE line.",
|
|
16287
16994
|
' echo "MAINTENANCE_DONE flagged_stale=${flagged_stale_count} flagged_blocked=${flagged_blocked_count} orphan_branches=${orphan_branches_count} orphan_prs=${orphan_prs_count} needs_attention_total=${needs_attention_total}"',
|
|
16288
16995
|
' echo "$lease_summary"',
|
|
16996
|
+
' echo "$label_summary"',
|
|
16289
16997
|
"}",
|
|
16290
16998
|
"",
|
|
16291
16999
|
"cmd_prs() {",
|
|
@@ -16351,6 +17059,196 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
16351
17059
|
' done <<< "$eligible"',
|
|
16352
17060
|
"}",
|
|
16353
17061
|
"",
|
|
17062
|
+
"cmd_label_invariant() {",
|
|
17063
|
+
" # Phase-label \u2192 type:<bundle> invariant sweep.",
|
|
17064
|
+
" #",
|
|
17065
|
+
" # Every phased-pipeline bundle pairs its <bundle>:<phase> labels",
|
|
17066
|
+
" # with exactly one type:<bundle> label. An agent that reconstructs",
|
|
17067
|
+
" # a gh issue create call from prose can stamp a conventional-commit",
|
|
17068
|
+
" # type label derived from the title prefix instead, producing an",
|
|
17069
|
+
" # issue that carries the phase label but the WRONG type label \u2014",
|
|
17070
|
+
" # invisible to the --label type:<bundle> duplicate-check idiom and",
|
|
17071
|
+
" # mis-tiered by the funnel-tier sort in cmd_eligible.",
|
|
17072
|
+
" #",
|
|
17073
|
+
" # Default mode is REPORT-ONLY (the consumer-runnable audit); pass",
|
|
17074
|
+
" # --fix to apply corrections. cmd_maintenance runs the --fix mode",
|
|
17075
|
+
" # as part of the Phase D sweep.",
|
|
17076
|
+
" #",
|
|
17077
|
+
" # CORRECTION POLICY. An issue must carry EXACTLY ONE type:* label",
|
|
17078
|
+
" # (cmd_eligible derives its funnel-tier sort key from the FIRST",
|
|
17079
|
+
" # one, and the label conventions mandate exactly one), so the",
|
|
17080
|
+
" # correction REPLACES rather than merely adds:",
|
|
17081
|
+
" #",
|
|
17082
|
+
" # no type:* at all \u2192 add the implied type label",
|
|
17083
|
+
" # only conventional-commit \u2192 remove them, add the implied",
|
|
17084
|
+
" # type labels present label (single atomic edit)",
|
|
17085
|
+
" # implied label already there \u2192 remove the stray conventional",
|
|
17086
|
+
" # alongside a conventional one label, leaving exactly one",
|
|
17087
|
+
" # a type:* owned by ANOTHER \u2192 FLAG status:needs-attention;",
|
|
17088
|
+
" # bundle never guess which to drop",
|
|
17089
|
+
" # phase labels imply 2+ types \u2192 FLAG status:needs-attention",
|
|
17090
|
+
" #",
|
|
17091
|
+
" # Only a conventional-commit type label is ever removed \u2014 the set",
|
|
17092
|
+
" # is_conventional_type_label() recognises. A type:* label owned by",
|
|
17093
|
+
" # a different bundle is a genuine conflict a human must resolve.",
|
|
17094
|
+
" # status:needs-attention is applied ADDITIVELY; the base status:*",
|
|
17095
|
+
" # label always stays (see the additive-flag rule).",
|
|
17096
|
+
" local apply=0",
|
|
17097
|
+
' if [[ "${1:-}" == "--fix" ]]; then',
|
|
17098
|
+
" apply=1",
|
|
17099
|
+
" fi",
|
|
17100
|
+
' local mode="report"',
|
|
17101
|
+
' [[ "$apply" -eq 1 ]] && mode="fix"',
|
|
17102
|
+
"",
|
|
17103
|
+
" local issues",
|
|
17104
|
+
" issues=$(gh issue list --state open --json number,labels \\",
|
|
17105
|
+
' --limit 1000 2>/dev/null || echo "[]")',
|
|
17106
|
+
"",
|
|
17107
|
+
" # Filter jq-side and emit a TWO-field record. The label list is",
|
|
17108
|
+
" # last AND guaranteed non-empty by the select, so no field can",
|
|
17109
|
+
" # collapse under IFS=tab and shift the record left (#884). The",
|
|
17110
|
+
" # title is deliberately not threaded through \u2014 every output line",
|
|
17111
|
+
" # keys off the issue number.",
|
|
17112
|
+
" local issue_data",
|
|
17113
|
+
` issue_data=$(echo "$issues" | jq -r '`,
|
|
17114
|
+
" .[] |",
|
|
17115
|
+
" (.labels | map(.name)) as $names |",
|
|
17116
|
+
" select($names | length > 0) |",
|
|
17117
|
+
' "\\(.number)\\t\\($names | join(","))"',
|
|
17118
|
+
" ' 2>/dev/null)",
|
|
17119
|
+
"",
|
|
17120
|
+
" local checked_count=0",
|
|
17121
|
+
" local ok_count=0",
|
|
17122
|
+
" local corrected_count=0",
|
|
17123
|
+
" local flagged_count=0",
|
|
17124
|
+
"",
|
|
17125
|
+
" while IFS=$'\\t' read -r num labels_csv; do",
|
|
17126
|
+
' [[ -z "$num" ]] && continue',
|
|
17127
|
+
"",
|
|
17128
|
+
" local labels_nl",
|
|
17129
|
+
` labels_nl=$(printf '%s' "$labels_csv" | tr ',' '\\n')`,
|
|
17130
|
+
"",
|
|
17131
|
+
" local resolution assignment",
|
|
17132
|
+
" local outcome='' type_label='' phase_labels='' candidates=''",
|
|
17133
|
+
` resolution=$(printf '%s\\n' "$labels_nl" | phase_type_of)`,
|
|
17134
|
+
" while IFS= read -r assignment; do",
|
|
17135
|
+
' case "$assignment" in',
|
|
17136
|
+
" OUTCOME=*) outcome=${assignment#OUTCOME=} ;;",
|
|
17137
|
+
" TYPE_LABEL=*) type_label=${assignment#TYPE_LABEL=} ;;",
|
|
17138
|
+
" CANDIDATE_TYPE_LABELS=*) candidates=${assignment#CANDIDATE_TYPE_LABELS=} ;;",
|
|
17139
|
+
" PHASE_LABELS=*) phase_labels=${assignment#PHASE_LABELS=} ;;",
|
|
17140
|
+
" esac",
|
|
17141
|
+
' done <<< "$resolution"',
|
|
17142
|
+
"",
|
|
17143
|
+
" # No recognised phase label \u2014 the invariant does not apply.",
|
|
17144
|
+
" # Consumer-specific `foo:bar` labels are deliberately not policed.",
|
|
17145
|
+
' [[ "$outcome" == "none" ]] && continue',
|
|
17146
|
+
" checked_count=$((checked_count + 1))",
|
|
17147
|
+
"",
|
|
17148
|
+
" # Idempotent flagging: read the existing flag off the labels we",
|
|
17149
|
+
" # already fetched rather than spending another API call.",
|
|
17150
|
+
" local already_flagged=0",
|
|
17151
|
+
' case ",${labels_csv}," in',
|
|
17152
|
+
' *",status:needs-attention,"*) already_flagged=1 ;;',
|
|
17153
|
+
" esac",
|
|
17154
|
+
"",
|
|
17155
|
+
' if [[ "$outcome" == "ambiguous" ]]; then',
|
|
17156
|
+
' echo "LABEL_AMBIGUOUS #${num} phase=\\"${phase_labels}\\" candidates=\\"${candidates}\\""',
|
|
17157
|
+
" flagged_count=$((flagged_count + 1))",
|
|
17158
|
+
' if [[ "$apply" -eq 1 && "$already_flagged" -eq 0 ]]; then',
|
|
17159
|
+
' if gh issue edit "$num" --add-label "status:needs-attention" >/dev/null 2>&1; then',
|
|
17160
|
+
' gh issue comment "$num" \\',
|
|
17161
|
+
' --body "Label invariant: phase label(s) ${phase_labels} imply more than one bundle type label (${candidates}). Flagged for human triage \u2014 an ambiguous pairing is never auto-corrected." >/dev/null 2>&1 || true',
|
|
17162
|
+
' echo "LABEL_FLAGGED #${num} \u2014 added status:needs-attention (ambiguous)"',
|
|
17163
|
+
" else",
|
|
17164
|
+
' echo "LABEL_FLAG_FAILED #${num} \u2014 could not add status:needs-attention (ambiguous)"',
|
|
17165
|
+
" fi",
|
|
17166
|
+
" fi",
|
|
17167
|
+
" continue",
|
|
17168
|
+
" fi",
|
|
17169
|
+
"",
|
|
17170
|
+
" # Partition the issue's existing type:* labels against the",
|
|
17171
|
+
" # implied one.",
|
|
17172
|
+
" local label",
|
|
17173
|
+
" local has_expected=0",
|
|
17174
|
+
" local conv_types=''",
|
|
17175
|
+
" local foreign_types=''",
|
|
17176
|
+
" while IFS= read -r label; do",
|
|
17177
|
+
' [ -z "$label" ] && continue',
|
|
17178
|
+
' case "$label" in',
|
|
17179
|
+
" type:*) ;;",
|
|
17180
|
+
" *) continue ;;",
|
|
17181
|
+
" esac",
|
|
17182
|
+
' if [ "$label" = "$type_label" ]; then',
|
|
17183
|
+
" has_expected=1",
|
|
17184
|
+
" continue",
|
|
17185
|
+
" fi",
|
|
17186
|
+
' if is_conventional_type_label "$label"; then',
|
|
17187
|
+
' conv_types="${conv_types}${label} "',
|
|
17188
|
+
" else",
|
|
17189
|
+
' foreign_types="${foreign_types}${label} "',
|
|
17190
|
+
" fi",
|
|
17191
|
+
' done <<< "$labels_nl"',
|
|
17192
|
+
"",
|
|
17193
|
+
' if [[ "$has_expected" -eq 1 && -z "${conv_types}${foreign_types}" ]]; then',
|
|
17194
|
+
' echo "LABEL_OK #${num} type=${type_label}"',
|
|
17195
|
+
" ok_count=$((ok_count + 1))",
|
|
17196
|
+
" continue",
|
|
17197
|
+
" fi",
|
|
17198
|
+
"",
|
|
17199
|
+
" # A type:* label owned by a DIFFERENT bundle is a conflict the",
|
|
17200
|
+
" # sweep must not guess at: removing it would destroy routing",
|
|
17201
|
+
" # information, and adding alongside it would leave two bundle",
|
|
17202
|
+
" # type labels and a non-deterministic funnel tier.",
|
|
17203
|
+
' if [[ -n "$foreign_types" ]]; then',
|
|
17204
|
+
' echo "LABEL_CONFLICT #${num} phase=\\"${phase_labels}\\" expected=${type_label} foreign=\\"${foreign_types% }\\""',
|
|
17205
|
+
" flagged_count=$((flagged_count + 1))",
|
|
17206
|
+
' if [[ "$apply" -eq 1 && "$already_flagged" -eq 0 ]]; then',
|
|
17207
|
+
' if gh issue edit "$num" --add-label "status:needs-attention" >/dev/null 2>&1; then',
|
|
17208
|
+
' gh issue comment "$num" \\',
|
|
17209
|
+
' --body "Label invariant: phase label(s) ${phase_labels} require ${type_label}, but this issue carries ${foreign_types% }, which is owned by another bundle. Flagged for human triage \u2014 the sweep only removes conventional-commit type labels." >/dev/null 2>&1 || true',
|
|
17210
|
+
' echo "LABEL_FLAGGED #${num} \u2014 added status:needs-attention (conflicting bundle type)"',
|
|
17211
|
+
" else",
|
|
17212
|
+
' echo "LABEL_FLAG_FAILED #${num} \u2014 could not add status:needs-attention (conflict)"',
|
|
17213
|
+
" fi",
|
|
17214
|
+
" fi",
|
|
17215
|
+
" continue",
|
|
17216
|
+
" fi",
|
|
17217
|
+
"",
|
|
17218
|
+
" # Correctable: add the implied label and/or drop the",
|
|
17219
|
+
" # conventional-commit label(s) shadowing it, in ONE edit so the",
|
|
17220
|
+
" # issue is never observable carrying two type:* labels.",
|
|
17221
|
+
' local removing="${conv_types% }"',
|
|
17222
|
+
" local adding=''",
|
|
17223
|
+
' [[ "$has_expected" -eq 0 ]] && adding="$type_label"',
|
|
17224
|
+
' echo "LABEL_VIOLATION #${num} phase=\\"${phase_labels}\\" expected=${type_label} add=\\"${adding}\\" remove=\\"${removing}\\""',
|
|
17225
|
+
' if [[ "$apply" -eq 0 ]]; then',
|
|
17226
|
+
" continue",
|
|
17227
|
+
" fi",
|
|
17228
|
+
"",
|
|
17229
|
+
" local -a edit_args=()",
|
|
17230
|
+
" local stray",
|
|
17231
|
+
" for stray in $removing; do",
|
|
17232
|
+
' edit_args+=(--remove-label "$stray")',
|
|
17233
|
+
" done",
|
|
17234
|
+
' [[ -n "$adding" ]] && edit_args+=(--add-label "$adding")',
|
|
17235
|
+
' if [[ "${#edit_args[@]}" -eq 0 ]]; then',
|
|
17236
|
+
" continue",
|
|
17237
|
+
" fi",
|
|
17238
|
+
' if gh issue edit "$num" "${edit_args[@]}" >/dev/null 2>&1; then',
|
|
17239
|
+
' gh issue comment "$num" \\',
|
|
17240
|
+
' --body "Label invariant: phase label(s) ${phase_labels} require ${type_label}. Corrected \u2014 added: ${adding:-(none)}; removed: ${removing:-(none)}." >/dev/null 2>&1 || true',
|
|
17241
|
+
' echo "LABEL_CORRECTED #${num} added=\\"${adding}\\" removed=\\"${removing}\\""',
|
|
17242
|
+
" corrected_count=$((corrected_count + 1))",
|
|
17243
|
+
" else",
|
|
17244
|
+
' echo "LABEL_CORRECT_FAILED #${num} \u2014 label edit failed"',
|
|
17245
|
+
" fi",
|
|
17246
|
+
' done <<< "$issue_data"',
|
|
17247
|
+
"",
|
|
17248
|
+
" local violations_count=$((checked_count - ok_count))",
|
|
17249
|
+
' echo "LABEL_INVARIANT_DONE mode=${mode} checked=${checked_count} ok=${ok_count} violations=${violations_count} corrected=${corrected_count} flagged=${flagged_count}"',
|
|
17250
|
+
"}",
|
|
17251
|
+
"",
|
|
16354
17252
|
"cmd_scope() {",
|
|
16355
17253
|
' local issue_num="${1:-}"',
|
|
16356
17254
|
' if [[ -z "$issue_num" ]]; then',
|
|
@@ -16437,8 +17335,9 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
16437
17335
|
' maintenance) shift; cmd_maintenance "$@" ;;',
|
|
16438
17336
|
' prs) shift; cmd_prs "$@" ;;',
|
|
16439
17337
|
' scope) shift; cmd_scope "$@" ;;',
|
|
17338
|
+
' label-invariant) shift; cmd_label_invariant "$@" ;;',
|
|
16440
17339
|
" help|*)",
|
|
16441
|
-
' echo "Usage: check-blocked.sh <unblock|eligible|stale|orphaned|lease-reconcile|maintenance|prs|scope>"',
|
|
17340
|
+
' echo "Usage: check-blocked.sh <unblock|eligible|stale|orphaned|lease-reconcile|maintenance|prs|scope|label-invariant>"',
|
|
16442
17341
|
" exit 1",
|
|
16443
17342
|
" ;;",
|
|
16444
17343
|
"esac"
|
|
@@ -16447,7 +17346,7 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
16447
17346
|
function buildCheckBlockedProcedure(tiers, scopeGate = resolveScopeGate(), runRatio = resolveRunRatio()) {
|
|
16448
17347
|
return {
|
|
16449
17348
|
name: "check-blocked.sh",
|
|
16450
|
-
description: "Token-efficient issue triage script with subcommands: eligible, unblock, stale, orphaned, lease-reconcile, maintenance, prs, scope. Sorts eligible issues by priority desc \u2192 funnel tier asc \u2192 issue number asc, excluding issues carrying `status:needs-attention` or `status:deferred`; the scope subcommand classifies a single issue against the scope-gate thresholds; the unblock subcommand applies the `status:blocked` \u2192 `status:ready` label flip itself, posts the canned unblock comment, skips human-parked `status:deferred` candidates with a `SKIP_DEFERRED` line, and emits a single `TRIAGE_DONE unblocked=N still_blocked=M deferred_skipped=K` summary line; the lease-reconcile subcommand auto-reconciles stuck `review:fixing` PR leases \u2014 re-applying `review:needs-worker` on an orphaned lease (fix-list stale, no worker report) so the Phase B1 drain retries, and removing `review:needs-worker` on a consumed-but-uncleared wedge (branch HEAD advanced past the fix-list, marker still set, no worker report) so the reviewer confirm pass can merge, always posting an audit-trail note comment and emitting a single `LEASE_RECONCILE orphaned=N consumed_uncleared=M` summary line; the maintenance subcommand flags stale issues with `status:needs-attention` (never auto-resets to `status:ready`), counts orphan branches/PRs, folds in the lease-reconcile sweep, and emits a `MAINTENANCE_DONE flagged_stale=N flagged_blocked=M orphan_branches=A orphan_prs=B needs_attention_total=T` summary line followed by the `LEASE_RECONCILE` line.",
|
|
17349
|
+
description: "Token-efficient issue triage script with subcommands: eligible, unblock, stale, orphaned, lease-reconcile, maintenance, prs, scope, label-invariant. Sorts eligible issues by priority desc \u2192 funnel tier asc \u2192 issue number asc, excluding issues carrying `status:needs-attention` or `status:deferred`; the scope subcommand classifies a single issue against the scope-gate thresholds; the unblock subcommand applies the `status:blocked` \u2192 `status:ready` label flip itself, posts the canned unblock comment, skips human-parked `status:deferred` candidates with a `SKIP_DEFERRED` line, and emits a single `TRIAGE_DONE unblocked=N still_blocked=M deferred_skipped=K` summary line; the lease-reconcile subcommand auto-reconciles stuck `review:fixing` PR leases \u2014 re-applying `review:needs-worker` on an orphaned lease (fix-list stale, no worker report) so the Phase B1 drain retries, and removing `review:needs-worker` on a consumed-but-uncleared wedge (branch HEAD advanced past the fix-list, marker still set, no worker report) so the reviewer confirm pass can merge, always posting an audit-trail note comment and emitting a single `LEASE_RECONCILE orphaned=N consumed_uncleared=M` summary line; the maintenance subcommand flags stale issues with `status:needs-attention` (never auto-resets to `status:ready`), counts orphan branches/PRs, folds in the lease-reconcile sweep and the label-invariant auto-correction, and emits a `MAINTENANCE_DONE flagged_stale=N flagged_blocked=M orphan_branches=A orphan_prs=B needs_attention_total=T` summary line followed by the `LEASE_RECONCILE` and `LABEL_INVARIANT_DONE` lines; the label-invariant subcommand enforces the phase-label \u2192 `type:<bundle>` pairing derived from the canonical `BUNDLE_OWNERSHIP` map \u2014 report-only by default, auto-correcting with `--fix` (replacing a shadowing conventional-commit `type:*` rather than merely adding, so the issue keeps exactly one `type:*`) and flagging `status:needs-attention` when the pairing is ambiguous or collides with another bundle's type label, emitting a single `LABEL_INVARIANT_DONE mode=M checked=N ok=O violations=V corrected=C flagged=F` summary line.",
|
|
16451
17350
|
content: buildCheckBlockedScript(tiers, scopeGate, runRatio)
|
|
16452
17351
|
};
|
|
16453
17352
|
}
|
|
@@ -17163,32 +18062,36 @@ var orchestratorSubAgent = {
|
|
|
17163
18062
|
"## Phase D: Maintenance",
|
|
17164
18063
|
"",
|
|
17165
18064
|
"Run the bundled `check-blocked.sh maintenance` procedure and read",
|
|
17166
|
-
"**only** the
|
|
17167
|
-
"`LEASE_RECONCILE`. The procedure folds
|
|
17168
|
-
"orphan-detection, needs-attention summary,
|
|
17169
|
-
"auto-reconcile
|
|
17170
|
-
"into a single sweep \u2014
|
|
17171
|
-
"
|
|
17172
|
-
"
|
|
17173
|
-
"
|
|
17174
|
-
"
|
|
18065
|
+
"**only** the three summary lines it emits \u2014 `MAINTENANCE_DONE`,",
|
|
18066
|
+
"`LEASE_RECONCILE`, and `LABEL_INVARIANT_DONE`. The procedure folds",
|
|
18067
|
+
"the stale-detection, orphan-detection, needs-attention summary,",
|
|
18068
|
+
"PR-lease auto-reconcile, and phase-label invariant sweep that",
|
|
18069
|
+
"earlier revisions split across D1, D2, and D3 into a single sweep \u2014",
|
|
18070
|
+
"applying the `status:needs-attention` label and posting the canned",
|
|
18071
|
+
"flag comment for each stale / stale-blocked issue itself,",
|
|
18072
|
+
"reconciling stuck `review:fixing` PR leases itself, and correcting",
|
|
18073
|
+
"mislabeled pipeline issues itself, mirroring the discipline of",
|
|
18074
|
+
"Phase B (`pr-sweep.sh`) and Phase C (`check-blocked.sh unblock`).",
|
|
17175
18075
|
"",
|
|
17176
18076
|
"```bash",
|
|
17177
18077
|
".claude/procedures/check-blocked.sh maintenance",
|
|
17178
18078
|
"```",
|
|
17179
18079
|
"",
|
|
17180
|
-
"The script emits
|
|
18080
|
+
"The script emits three summary lines in this shape:",
|
|
17181
18081
|
"",
|
|
17182
18082
|
"```",
|
|
17183
18083
|
"MAINTENANCE_DONE flagged_stale=<N> flagged_blocked=<M> orphan_branches=<A> orphan_prs=<B> needs_attention_total=<T>",
|
|
17184
18084
|
"LEASE_RECONCILE orphaned=<N> consumed_uncleared=<M>",
|
|
18085
|
+
"LABEL_INVARIANT_DONE mode=fix checked=<N> ok=<O> violations=<V> corrected=<C> flagged=<F>",
|
|
17185
18086
|
"```",
|
|
17186
18087
|
"",
|
|
17187
18088
|
"Per-issue / per-orphan / per-PR informational lines (`FLAGGED_STALE",
|
|
17188
18089
|
"#N`, `FLAGGED_BLOCKED #N`, `STALE #N \u2014 \u2026`, `STALE_BLOCKED #N \u2014 \u2026`,",
|
|
17189
18090
|
"`ORPHAN_BRANCH \u2026`, `ORPHAN_PR #N \u2014 \u2026`, `FLAG_FAILED #N \u2014 \u2026`,",
|
|
17190
18091
|
"`LEASE_ORPHANED PR #N \u2014 \u2026`, `LEASE_CONSUMED_UNCLEARED PR #N \u2014 \u2026`,",
|
|
17191
|
-
"`LEASE_RECONCILE_FAILED PR #N \u2014 \u2026`
|
|
18092
|
+
"`LEASE_RECONCILE_FAILED PR #N \u2014 \u2026`, `LABEL_VIOLATION #N \u2014 \u2026`,",
|
|
18093
|
+
"`LABEL_CORRECTED #N \u2014 \u2026`, `LABEL_AMBIGUOUS #N \u2014 \u2026`,",
|
|
18094
|
+
"`LABEL_CONFLICT #N \u2014 \u2026`) are emitted for log visibility",
|
|
17192
18095
|
"but are **not** load-bearing for the orchestrator \u2014 partial failures",
|
|
17193
18096
|
"(one bad `gh` call) do not abort the sweep, they are simply omitted",
|
|
17194
18097
|
"from the counters.",
|
|
@@ -17228,9 +18131,24 @@ var orchestratorSubAgent = {
|
|
|
17228
18131
|
"is never silently force-released. The `review:fixing` lease itself",
|
|
17229
18132
|
"is always left for the reviewer confirm pass to release.",
|
|
17230
18133
|
"",
|
|
17231
|
-
"
|
|
17232
|
-
"
|
|
17233
|
-
"
|
|
18134
|
+
"**Phase-label invariant (`LABEL_INVARIANT_DONE`).** The sweep also",
|
|
18135
|
+
"enforces the phase-label \u2192 `type:<bundle>` pairing on every open",
|
|
18136
|
+
"issue, so a pipeline issue filed with a conventional-commit",
|
|
18137
|
+
"`type:*` (derived from its title prefix) cannot stay invisible to",
|
|
18138
|
+
'the `--label "type:<bundle>"` duplicate-check idiom or mis-tier in',
|
|
18139
|
+
"Phase E's funnel-tier sort. The script mutates the labels itself \u2014",
|
|
18140
|
+
"the orchestrator only reads the summary line. See the **Phase-label",
|
|
18141
|
+
"\u2192 `type:<bundle>` invariant** section in `CLAUDE.md` for the full",
|
|
18142
|
+
"correction policy; in short, a shadowing conventional-commit type",
|
|
18143
|
+
"label is **replaced** (never merely supplemented, so the issue",
|
|
18144
|
+
"keeps exactly one `type:*`), and an ambiguous or cross-bundle",
|
|
18145
|
+
"collision is **flagged** `status:needs-attention` rather than",
|
|
18146
|
+
"guessed at.",
|
|
18147
|
+
"",
|
|
18148
|
+
"Log all three summary lines and continue to Phase E regardless of",
|
|
18149
|
+
"outcomes \u2014 a non-zero `flagged_*`, `orphan_*`, `orphaned`,",
|
|
18150
|
+
"`consumed_uncleared`, `corrected`, or `flagged` count is",
|
|
18151
|
+
"informational, not a failure.",
|
|
17234
18152
|
"",
|
|
17235
18153
|
"## Phase E: Queue Scan",
|
|
17236
18154
|
"",
|
|
@@ -18232,6 +19150,7 @@ var ORCHESTRATOR_CONVENTIONS_PREAMBLE = [
|
|
|
18232
19150
|
"- Stale thresholds: 72h for in-progress, 168h for blocked",
|
|
18233
19151
|
"- Flagged issues get `status:needs-attention` \u2014 they are not auto-reset",
|
|
18234
19152
|
"- **Phase D auto-reconciles stuck PR leases.** The maintenance sweep's `check-blocked.sh maintenance` invocation folds in a PR-lease reconcile that unwedges `review:fixing` PRs the Phase B1 drain and the worker hand-off left stuck, emitting a `LEASE_RECONCILE orphaned=<N> consumed_uncleared=<M>` line alongside `MAINTENANCE_DONE`. An **orphaned lease** (`review:fixing` without `review:needs-worker`, fix-list older than the 72h stale threshold, no worker-report newer than the fix-list) is **re-armed** \u2014 the sweep re-applies `review:needs-worker` so the Phase B1 drain retries the delegation, leaving the lease untouched. A **consumed-but-uncleared wedge** (both labels present, branch HEAD newer than the fix-list, no worker-report \u2014 the worker pushed but skipped the hand-off) is **auto-reconciled** \u2014 the sweep removes `review:needs-worker` so the reviewer confirm pass can merge. Both paths post an audit-trail note comment; a lease is never silently force-released, and the `review:fixing` lease itself is always left for the reviewer confirm pass to release.",
|
|
19153
|
+
"- **Phase D enforces the phase-label \u2192 `type:<bundle>` invariant.** The same maintenance sweep folds in a label-invariant pass that auto-corrects open issues carrying a pipeline phase label without the matching `type:<bundle>` label, emitting a `LABEL_INVARIANT_DONE mode=fix checked=<N> ok=<O> violations=<V> corrected=<C> flagged=<F>` line alongside `MAINTENANCE_DONE`. The correction **replaces** a shadowing conventional-commit `type:*` rather than merely adding the bundle type, so the issue keeps exactly one `type:*` and Phase E's funnel-tier sort key stays deterministic. Ambiguous pairings and collisions with another bundle's `type:*` label are **flagged** `status:needs-attention` (additively) instead of guessed at. See the **Phase-label \u2192 `type:<bundle>` invariant** section below for the matcher table and the full correction policy.",
|
|
18235
19154
|
"",
|
|
18236
19155
|
"## Depth-0 invocation requirement",
|
|
18237
19156
|
"",
|
|
@@ -18247,6 +19166,8 @@ function buildOrchestratorConventionsContent(tiers, scopeGate = resolveScopeGate
|
|
|
18247
19166
|
"",
|
|
18248
19167
|
renderScopeGateSection(scopeGate, excludeBundles),
|
|
18249
19168
|
"",
|
|
19169
|
+
renderPhaseTypeInvariantSection(excludeBundles),
|
|
19170
|
+
"",
|
|
18250
19171
|
renderScheduledTasksSection(scheduledTasks),
|
|
18251
19172
|
"",
|
|
18252
19173
|
renderUnblockDependentsSection(unblockDependents)
|
|
@@ -31525,429 +32446,56 @@ function buildStandardsResearchBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults
|
|
|
31525
32446
|
}
|
|
31526
32447
|
var standardsResearchBundle = buildStandardsResearchBundle();
|
|
31527
32448
|
|
|
31528
|
-
// src/
|
|
31529
|
-
|
|
31530
|
-
|
|
31531
|
-
|
|
31532
|
-
// src/turbo/turbo-repo-task.ts
|
|
31533
|
-
var import_lib = require("projen/lib");
|
|
31534
|
-
var TurboRepoTask = class extends import_lib.Component {
|
|
31535
|
-
constructor(project, options) {
|
|
31536
|
-
super(project);
|
|
31537
|
-
this.project = project;
|
|
31538
|
-
this.name = options.name;
|
|
31539
|
-
this.dependsOn = options.dependsOn ?? [];
|
|
31540
|
-
this.env = options.env ?? [];
|
|
31541
|
-
this.passThroughEnv = options.passThroughEnv ?? [];
|
|
31542
|
-
this.outputs = options.outputs ?? [];
|
|
31543
|
-
this.cache = options.cache ?? true;
|
|
31544
|
-
this.inputs = [
|
|
31545
|
-
...options.inputs ?? [],
|
|
31546
|
-
// rerun if projen config changes
|
|
31547
|
-
".projen/**",
|
|
31548
|
-
// ignore mac files
|
|
31549
|
-
"!.DS_Store",
|
|
31550
|
-
"!**/.DS_Store"
|
|
31551
|
-
];
|
|
31552
|
-
this.outputLogs = options.outputLogs ?? "new-only";
|
|
31553
|
-
this.persistent = options.persistent ?? false;
|
|
31554
|
-
this.interactive = options.interactive ?? false;
|
|
31555
|
-
this.isActive = true;
|
|
31556
|
-
}
|
|
31557
|
-
taskConfig() {
|
|
31558
|
-
return {
|
|
31559
|
-
dependsOn: this.dependsOn,
|
|
31560
|
-
env: this.env,
|
|
31561
|
-
passThroughEnv: this.passThroughEnv,
|
|
31562
|
-
outputs: this.outputs,
|
|
31563
|
-
cache: this.cache,
|
|
31564
|
-
inputs: this.inputs,
|
|
31565
|
-
outputLogs: this.outputLogs,
|
|
31566
|
-
persistent: this.persistent,
|
|
31567
|
-
interactive: this.interactive
|
|
31568
|
-
};
|
|
31569
|
-
}
|
|
31570
|
-
};
|
|
31571
|
-
|
|
31572
|
-
// src/turbo/turbo-repo.ts
|
|
31573
|
-
var ROOT_TURBO_TASK_NAME = "turbo:build";
|
|
31574
|
-
var ROOT_CI_TASK_NAME = "build:all";
|
|
31575
|
-
var _TurboRepo = class _TurboRepo extends import_lib2.Component {
|
|
31576
|
-
constructor(project, options = {}) {
|
|
31577
|
-
super(project);
|
|
31578
|
-
this.project = project;
|
|
31579
|
-
/**
|
|
31580
|
-
* Sub-Tasks to run
|
|
31581
|
-
*/
|
|
31582
|
-
this.tasks = [];
|
|
31583
|
-
this.turboVersion = options.turboVersion ?? "catalog:";
|
|
31584
|
-
this.isRootProject = project === project.root;
|
|
31585
|
-
if (this.isRootProject) {
|
|
31586
|
-
project.addDevDeps(`turbo@${this.turboVersion}`);
|
|
31587
|
-
}
|
|
31588
|
-
project.gitignore.addPatterns("/.turbo");
|
|
31589
|
-
project.npmignore?.addPatterns("/.turbo/");
|
|
31590
|
-
this.extends = options.extends ?? (this.isRootProject ? [] : ["//"]);
|
|
31591
|
-
this.globalDependencies = options.globalDependencies ?? [];
|
|
31592
|
-
this.globalEnv = options.globalEnv ?? [];
|
|
31593
|
-
this.globalPassThroughEnv = options.globalPassThroughEnv ?? [];
|
|
31594
|
-
this.ui = options.ui ?? "stream";
|
|
31595
|
-
this.dangerouslyDisablePackageManagerCheck = options.dangerouslyDisablePackageManagerCheck ?? false;
|
|
31596
|
-
this.cacheDir = options.cacheDir ?? ".turbo/cache";
|
|
31597
|
-
this.daemon = options.daemon ?? true;
|
|
31598
|
-
this.envMode = options.envMode ?? "strict";
|
|
31599
|
-
this.runOptions = {
|
|
31600
|
-
...options.runOptions,
|
|
31601
|
-
summarize: options.runOptions?.summarize ?? true,
|
|
31602
|
-
concurrency: options.runOptions?.concurrency ?? 10
|
|
31603
|
-
};
|
|
31604
|
-
this.remoteCacheOptions = options.remoteCacheOptions;
|
|
31605
|
-
this.buildAllTaskEnvVars = options.buildAllTaskEnvVars ?? {};
|
|
31606
|
-
this.buildTask = new TurboRepoTask(this.project, {
|
|
31607
|
-
name: ROOT_TURBO_TASK_NAME,
|
|
31608
|
-
dependsOn: this.isRootProject ? [`^${ROOT_TURBO_TASK_NAME}`] : []
|
|
31609
|
-
});
|
|
31610
|
-
if (this.isRootProject) {
|
|
31611
|
-
this.buildAllTask = this.project.tasks.addTask(ROOT_CI_TASK_NAME, {
|
|
31612
|
-
description: "Root build followed by sub-project builds. Mimics the CI build process in one step."
|
|
31613
|
-
});
|
|
31614
|
-
this.buildAllTask.exec("turbo telemetry disable");
|
|
31615
|
-
if (this.buildAllTaskEnvVars) {
|
|
31616
|
-
Object.entries(this.buildAllTaskEnvVars).forEach(([name, value]) => {
|
|
31617
|
-
this.addGlobalEnvVar(name, value);
|
|
31618
|
-
});
|
|
31619
|
-
}
|
|
31620
|
-
if (!this.remoteCacheOptions) {
|
|
31621
|
-
this.buildAllTask.exec(
|
|
31622
|
-
`turbo ${ROOT_TURBO_TASK_NAME} ${this.renderRunArgs(false)}`
|
|
31623
|
-
);
|
|
31624
|
-
} else {
|
|
31625
|
-
this.buildAllTask.exec(
|
|
31626
|
-
`turbo ${ROOT_TURBO_TASK_NAME} ${this.renderRunArgs(true)}`,
|
|
31627
|
-
{
|
|
31628
|
-
condition: '[ ! -n "$CI" ]',
|
|
31629
|
-
env: {
|
|
31630
|
-
TURBO_ENDPOINT: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.endpointParamName} --query Parameter.Value --output text --profile ${this.remoteCacheOptions.profileName})`,
|
|
31631
|
-
TURBO_TOKEN: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.tokenParamName} --query Parameter.Value --output text --profile ${this.remoteCacheOptions.profileName})`
|
|
31632
|
-
}
|
|
31633
|
-
}
|
|
31634
|
-
);
|
|
31635
|
-
this.buildAllTask.exec(
|
|
31636
|
-
`turbo ${ROOT_TURBO_TASK_NAME} ${this.renderRunArgs(true)}`,
|
|
31637
|
-
{
|
|
31638
|
-
condition: '[ -n "$CI" ]',
|
|
31639
|
-
env: {
|
|
31640
|
-
TURBO_ENDPOINT: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.endpointParamName} --query Parameter.Value --output text)`,
|
|
31641
|
-
TURBO_TOKEN: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.tokenParamName} --query Parameter.Value --output text)`
|
|
31642
|
-
}
|
|
31643
|
-
}
|
|
31644
|
-
);
|
|
31645
|
-
}
|
|
31646
|
-
}
|
|
31647
|
-
if (!this.isRootProject) {
|
|
31648
|
-
this.preCompileTask = new TurboRepoTask(project, {
|
|
31649
|
-
name: options.preCompileTask?.name ?? "pre-compile",
|
|
31650
|
-
inputs: ["src/**"]
|
|
31651
|
-
});
|
|
31652
|
-
this.compileTask = new TurboRepoTask(project, {
|
|
31653
|
-
name: options.compileTask?.name ?? "compile",
|
|
31654
|
-
inputs: ["src/**"]
|
|
31655
|
-
});
|
|
31656
|
-
this.postCompileTask = new TurboRepoTask(project, {
|
|
31657
|
-
name: options.postCompileTask?.name ?? "post-compile",
|
|
31658
|
-
inputs: ["src/**"]
|
|
31659
|
-
});
|
|
31660
|
-
this.testTask = new TurboRepoTask(project, {
|
|
31661
|
-
name: options.testTask?.name ?? "test"
|
|
31662
|
-
});
|
|
31663
|
-
this.packageTask = new TurboRepoTask(project, {
|
|
31664
|
-
name: options.packageTask?.name ?? "package",
|
|
31665
|
-
inputs: [".npmignore"]
|
|
31666
|
-
});
|
|
31667
|
-
this.tasks.push(
|
|
31668
|
-
this.preCompileTask,
|
|
31669
|
-
this.compileTask,
|
|
31670
|
-
this.postCompileTask,
|
|
31671
|
-
this.testTask,
|
|
31672
|
-
this.packageTask
|
|
31673
|
-
);
|
|
31674
|
-
}
|
|
31675
|
-
}
|
|
31676
|
-
/**
|
|
31677
|
-
* Static method to discovert turbo in a project.
|
|
31678
|
-
*/
|
|
31679
|
-
static of(project) {
|
|
31680
|
-
const isDefined = (c) => c instanceof _TurboRepo;
|
|
31681
|
-
return project.components.find(isDefined);
|
|
31682
|
-
}
|
|
31683
|
-
/**
|
|
31684
|
-
* Render the `turbo run` CLI flag string for the `build:all` / `reset:all`
|
|
31685
|
-
* task commands from {@link runOptions}. With no `runOptions` configured the
|
|
31686
|
-
* output matches the historically hard-coded flags exactly.
|
|
31687
|
-
*
|
|
31688
|
-
* @param remote - when `true`, also emit the remote-cache flags
|
|
31689
|
-
* (`--cache=remote:rw` plus `--api` / `--token` / `--team` derived from
|
|
31690
|
-
* {@link remoteCacheOptions}). The remote-cache `build:all` variant passes
|
|
31691
|
-
* `true`; `reset:all` and the local `build:all` variant pass `false`.
|
|
31692
|
-
*/
|
|
31693
|
-
renderRunArgs(remote) {
|
|
31694
|
-
const run = this.runOptions;
|
|
31695
|
-
const args = [];
|
|
31696
|
-
if (run.summarize) {
|
|
31697
|
-
args.push("--summarize");
|
|
31698
|
-
}
|
|
31699
|
-
args.push(`--concurrency=${run.concurrency}`);
|
|
31700
|
-
if (run.force) {
|
|
31701
|
-
args.push("--force");
|
|
31702
|
-
}
|
|
31703
|
-
if (run.noCache) {
|
|
31704
|
-
args.push("--no-cache");
|
|
31705
|
-
}
|
|
31706
|
-
if (run.only) {
|
|
31707
|
-
args.push("--only");
|
|
31708
|
-
}
|
|
31709
|
-
if (run.affected) {
|
|
31710
|
-
args.push("--affected");
|
|
31711
|
-
}
|
|
31712
|
-
if (run.cacheWorkers !== void 0) {
|
|
31713
|
-
args.push(`--cache-workers=${run.cacheWorkers}`);
|
|
31714
|
-
}
|
|
31715
|
-
if (run.continueOn !== void 0) {
|
|
31716
|
-
args.push(`--continue=${run.continueOn}`);
|
|
31717
|
-
}
|
|
31718
|
-
if (run.frameworkInference !== void 0) {
|
|
31719
|
-
args.push(`--framework-inference=${run.frameworkInference}`);
|
|
31720
|
-
}
|
|
31721
|
-
for (const filter of run.filter ?? []) {
|
|
31722
|
-
args.push(`--filter=${filter}`);
|
|
31723
|
-
}
|
|
31724
|
-
if (run.outputLogs !== void 0) {
|
|
31725
|
-
args.push(`--output-logs=${run.outputLogs}`);
|
|
31726
|
-
}
|
|
31727
|
-
if (run.logOrder !== void 0) {
|
|
31728
|
-
args.push(`--log-order=${run.logOrder}`);
|
|
31729
|
-
}
|
|
31730
|
-
if (run.logPrefix !== void 0) {
|
|
31731
|
-
args.push(`--log-prefix=${run.logPrefix}`);
|
|
31732
|
-
}
|
|
31733
|
-
if (run.dryRun !== void 0) {
|
|
31734
|
-
args.push(`--dry-run=${run.dryRun}`);
|
|
31735
|
-
}
|
|
31736
|
-
const cache = run.cache ?? (remote ? "remote:rw" : void 0);
|
|
31737
|
-
if (cache !== void 0) {
|
|
31738
|
-
args.push(`--cache=${cache}`);
|
|
31739
|
-
}
|
|
31740
|
-
if (remote && this.remoteCacheOptions) {
|
|
31741
|
-
args.push(
|
|
31742
|
-
"--api=$TURBO_ENDPOINT",
|
|
31743
|
-
"--token=$TURBO_TOKEN",
|
|
31744
|
-
`--team=${this.remoteCacheOptions.teamName}`
|
|
31745
|
-
);
|
|
31746
|
-
}
|
|
31747
|
-
if (run.additionalArgs) {
|
|
31748
|
-
args.push(...run.additionalArgs);
|
|
31749
|
-
}
|
|
31750
|
-
return args.join(" ");
|
|
31751
|
-
}
|
|
31752
|
-
/**
|
|
31753
|
-
* Add an env var to the global env vars for all tasks.
|
|
31754
|
-
* This will also become an input for the build:all task cache at the root.
|
|
31755
|
-
*/
|
|
31756
|
-
addGlobalEnvVar(name, value) {
|
|
31757
|
-
this.buildAllTask?.env(name, value);
|
|
31758
|
-
if (this.isRootProject) {
|
|
31759
|
-
this.globalEnv.push(name);
|
|
31760
|
-
}
|
|
31761
|
-
}
|
|
31762
|
-
activateBranchNameEnvVar(options) {
|
|
31763
|
-
const value = '$([ -n "$GIT_BRANCH_NAME" ] && echo "$GIT_BRANCH_NAME" || git rev-parse --abbrev-ref HEAD)';
|
|
31764
|
-
if (options === void 0) {
|
|
31765
|
-
this.project.logger.warn(
|
|
31766
|
-
"TurboRepo.activateBranchNameEnvVar() with no arguments is deprecated. It writes GIT_BRANCH_NAME to the root `globalEnv`, which forces every task in the monorepo to miss cache on every branch switch. Pass `{ tasks: [...] }` and name only the tasks that actually consume the branch (e.g. CDK synth/package) to preserve cross-branch cache hits for everything else."
|
|
31767
|
-
);
|
|
31768
|
-
this.addGlobalEnvVar("GIT_BRANCH_NAME", value);
|
|
31769
|
-
return;
|
|
31770
|
-
}
|
|
31771
|
-
const knownTaskNames = this.tasks.map((task) => task.name);
|
|
31772
|
-
const unknown = options.tasks.filter(
|
|
31773
|
-
(name) => !knownTaskNames.includes(name)
|
|
31774
|
-
);
|
|
31775
|
-
if (unknown.length > 0) {
|
|
31776
|
-
throw new Error(
|
|
31777
|
-
`TurboRepo.activateBranchNameEnvVar: unknown task name(s) ${JSON.stringify(
|
|
31778
|
-
unknown
|
|
31779
|
-
)}. Known tasks on this TurboRepo: ${JSON.stringify(knownTaskNames)}.`
|
|
31780
|
-
);
|
|
31781
|
-
}
|
|
31782
|
-
for (const name of options.tasks) {
|
|
31783
|
-
const task = this.tasks.find((t) => t.name === name);
|
|
31784
|
-
if (task && !task.env.includes("GIT_BRANCH_NAME")) {
|
|
31785
|
-
task.env.push("GIT_BRANCH_NAME");
|
|
31786
|
-
}
|
|
31787
|
-
}
|
|
31788
|
-
}
|
|
31789
|
-
/**
|
|
31790
|
-
* Paths of all generated files in the project, deduped, for use as task
|
|
31791
|
-
* inputs so the compile cache invalidates when they change. Includes
|
|
31792
|
-
* projen-managed `FileBase` files plus generated-once `SampleFile` /
|
|
31793
|
-
* `SampleDir` — whose paths projen keeps private, so they are read defensively
|
|
31794
|
-
* and skipped if that internal shape ever changes. Computed at synth time so
|
|
31795
|
-
* files added after this component (e.g. a subclass's SampleFiles) are seen.
|
|
31796
|
-
*/
|
|
31797
|
-
generatedFileInputs() {
|
|
31798
|
-
const inputs = /* @__PURE__ */ new Set();
|
|
31799
|
-
for (const component of this.project.components) {
|
|
31800
|
-
if (component instanceof import_lib2.FileBase) {
|
|
31801
|
-
inputs.add(component.path);
|
|
31802
|
-
} else if (component instanceof import_lib2.SampleFile) {
|
|
31803
|
-
const filePath = component.filePath;
|
|
31804
|
-
if (typeof filePath === "string") {
|
|
31805
|
-
inputs.add(filePath);
|
|
31806
|
-
}
|
|
31807
|
-
} else if (component instanceof import_lib2.SampleDir) {
|
|
31808
|
-
const dir = component.dir;
|
|
31809
|
-
if (typeof dir === "string") {
|
|
31810
|
-
inputs.add(`${dir}/**`);
|
|
31811
|
-
}
|
|
31812
|
-
}
|
|
31813
|
-
}
|
|
31814
|
-
return Array.from(inputs);
|
|
31815
|
-
}
|
|
31816
|
-
preSynthesize() {
|
|
31817
|
-
let nextDependsOn = this.project.deps.all.filter((d) => d.version === "workspace:*").map((d) => [d.name, ROOT_TURBO_TASK_NAME].join("#"));
|
|
31818
|
-
if (!this.isRootProject) {
|
|
31819
|
-
[
|
|
31820
|
-
[this.project.preCompileTask, this.preCompileTask],
|
|
31821
|
-
[this.project.compileTask, this.compileTask],
|
|
31822
|
-
[this.project.postCompileTask, this.postCompileTask],
|
|
31823
|
-
[this.project.testTask, this.testTask],
|
|
31824
|
-
[this.project.packageTask, this.packageTask]
|
|
31825
|
-
].forEach(([pjTask, turboTask]) => {
|
|
31826
|
-
if (pjTask && turboTask && pjTask.steps.length > 0) {
|
|
31827
|
-
if (nextDependsOn.length > 0) {
|
|
31828
|
-
turboTask.dependsOn.push(...nextDependsOn);
|
|
31829
|
-
}
|
|
31830
|
-
nextDependsOn = [turboTask.name];
|
|
31831
|
-
} else {
|
|
31832
|
-
turboTask.isActive = false;
|
|
31833
|
-
}
|
|
31834
|
-
});
|
|
31835
|
-
this.buildTask.dependsOn.push(...nextDependsOn);
|
|
31836
|
-
}
|
|
31837
|
-
const generatedInputs = this.generatedFileInputs();
|
|
31838
|
-
const appendGeneratedInputs = (task) => {
|
|
31839
|
-
if (!task) {
|
|
31840
|
-
return;
|
|
31841
|
-
}
|
|
31842
|
-
for (const input of generatedInputs) {
|
|
31843
|
-
if (!task.inputs.includes(input)) {
|
|
31844
|
-
task.inputs.push(input);
|
|
31845
|
-
}
|
|
31846
|
-
}
|
|
31847
|
-
};
|
|
31848
|
-
if (this.isRootProject) {
|
|
31849
|
-
appendGeneratedInputs(this.buildTask);
|
|
31850
|
-
} else {
|
|
31851
|
-
appendGeneratedInputs(this.preCompileTask);
|
|
31852
|
-
appendGeneratedInputs(this.compileTask);
|
|
31853
|
-
appendGeneratedInputs(this.postCompileTask);
|
|
31854
|
-
}
|
|
31855
|
-
const fileName = "turbo.json";
|
|
31856
|
-
this.project.addPackageIgnore(fileName);
|
|
31857
|
-
new import_lib2.JsonFile(this.project, fileName, {
|
|
31858
|
-
obj: {
|
|
31859
|
-
extends: this.extends.length ? this.extends : void 0,
|
|
31860
|
-
globalDependencies: this.isRootProject && this.globalDependencies.length ? this.globalDependencies : void 0,
|
|
31861
|
-
globalEnv: this.isRootProject && this.globalEnv.length ? this.globalEnv : void 0,
|
|
31862
|
-
globalPassThroughEnv: this.isRootProject && this.globalPassThroughEnv.length ? this.globalPassThroughEnv : void 0,
|
|
31863
|
-
ui: this.isRootProject ? this.ui : void 0,
|
|
31864
|
-
dangerouslyDisablePackageManagerCheck: this.isRootProject ? this.dangerouslyDisablePackageManagerCheck : void 0,
|
|
31865
|
-
cacheDir: this.isRootProject ? this.cacheDir : void 0,
|
|
31866
|
-
envMode: this.isRootProject ? this.envMode : void 0,
|
|
31867
|
-
/**
|
|
31868
|
-
* All tasks
|
|
31869
|
-
*/
|
|
31870
|
-
tasks: this.tasks.filter((task) => task.isActive).reduce(
|
|
31871
|
-
(acc, task) => {
|
|
31872
|
-
acc[task.name] = {
|
|
31873
|
-
...task.taskConfig()
|
|
31874
|
-
};
|
|
31875
|
-
return acc;
|
|
31876
|
-
},
|
|
31877
|
-
{
|
|
31878
|
-
[this.buildTask.name]: { ...this.buildTask.taskConfig() }
|
|
31879
|
-
}
|
|
31880
|
-
)
|
|
31881
|
-
}
|
|
31882
|
-
});
|
|
31883
|
-
super.preSynthesize();
|
|
32449
|
+
// src/agent/bundles/turborepo.ts
|
|
32450
|
+
function renderCachingBullet(policy) {
|
|
32451
|
+
if (policy.remoteCacheEnabled && policy.awsProfileName) {
|
|
32452
|
+
return `- Uses remote caching (requires AWS credentials on the \`${policy.awsProfileName}\` profile)`;
|
|
31884
32453
|
}
|
|
31885
|
-
|
|
31886
|
-
|
|
32454
|
+
return "- Local caching only \u2014 no remote cache is configured, so no AWS credentials are required";
|
|
32455
|
+
}
|
|
32456
|
+
function buildTurborepoBundle(buildPolicy = DEFAULT_BUILD_POLICY) {
|
|
31887
32457
|
return {
|
|
31888
|
-
|
|
31889
|
-
|
|
31890
|
-
|
|
31891
|
-
|
|
31892
|
-
contents: import_workflows_model.JobPermission.WRITE,
|
|
31893
|
-
idToken: import_workflows_model.JobPermission.WRITE
|
|
31894
|
-
},
|
|
31895
|
-
preBuildSteps: [
|
|
32458
|
+
name: "turborepo",
|
|
32459
|
+
description: "Turborepo workspace rules and task pipeline conventions",
|
|
32460
|
+
appliesWhen: (project) => hasComponent(project, TurboRepo),
|
|
32461
|
+
rules: [
|
|
31896
32462
|
{
|
|
31897
|
-
name: "
|
|
31898
|
-
|
|
31899
|
-
|
|
31900
|
-
|
|
31901
|
-
|
|
31902
|
-
|
|
31903
|
-
|
|
32463
|
+
name: "turborepo-conventions",
|
|
32464
|
+
description: "Turborepo build system and task pipeline conventions",
|
|
32465
|
+
scope: AGENT_RULE_SCOPE.FILE_PATTERN,
|
|
32466
|
+
filePatterns: ["turbo.json", "package.json"],
|
|
32467
|
+
content: [
|
|
32468
|
+
"# Turborepo Conventions",
|
|
32469
|
+
"",
|
|
32470
|
+
"## Build System",
|
|
32471
|
+
"",
|
|
32472
|
+
"- **Build**: `pnpm build:all` (uses Turborepo)",
|
|
32473
|
+
"- **Test**: `pnpm test` or `pnpm test:watch`",
|
|
32474
|
+
"- **Lint**: `pnpm eslint`",
|
|
32475
|
+
"",
|
|
32476
|
+
"## Task Pipeline",
|
|
32477
|
+
"",
|
|
32478
|
+
renderCachingBullet(buildPolicy),
|
|
32479
|
+
"- Only rebuilds changed packages",
|
|
32480
|
+
"- Cache key based on file hashes and dependency graph",
|
|
32481
|
+
"- Configured in `turbo.json`",
|
|
32482
|
+
"",
|
|
32483
|
+
"## Workspace Rules",
|
|
32484
|
+
"",
|
|
32485
|
+
"- Source files: `src/` directory",
|
|
32486
|
+
"- Tests: Co-located with source files (`.spec.ts` or `.test.ts`)",
|
|
32487
|
+
"- Exports: Use `index.ts` files for clean public APIs",
|
|
32488
|
+
"- Configuration: Managed by Projen (edit `.projenrc.ts` or `projenrc/*.ts`)"
|
|
32489
|
+
].join("\n"),
|
|
32490
|
+
tags: ["workflow"]
|
|
31904
32491
|
}
|
|
31905
|
-
]
|
|
31906
|
-
|
|
31907
|
-
|
|
31908
|
-
var TurboRepo = _TurboRepo;
|
|
31909
|
-
|
|
31910
|
-
// src/agent/bundles/turborepo.ts
|
|
31911
|
-
var turborepoBundle = {
|
|
31912
|
-
name: "turborepo",
|
|
31913
|
-
description: "Turborepo workspace rules and task pipeline conventions",
|
|
31914
|
-
appliesWhen: (project) => hasComponent(project, TurboRepo),
|
|
31915
|
-
rules: [
|
|
31916
|
-
{
|
|
31917
|
-
name: "turborepo-conventions",
|
|
31918
|
-
description: "Turborepo build system and task pipeline conventions",
|
|
31919
|
-
scope: AGENT_RULE_SCOPE.FILE_PATTERN,
|
|
31920
|
-
filePatterns: ["turbo.json", "package.json"],
|
|
31921
|
-
content: [
|
|
31922
|
-
"# Turborepo Conventions",
|
|
31923
|
-
"",
|
|
31924
|
-
"## Build System",
|
|
31925
|
-
"",
|
|
31926
|
-
"- **Build**: `pnpm build:all` (uses Turborepo)",
|
|
31927
|
-
"- **Test**: `pnpm test` or `pnpm test:watch`",
|
|
31928
|
-
"- **Lint**: `pnpm eslint`",
|
|
31929
|
-
"",
|
|
31930
|
-
"## Task Pipeline",
|
|
31931
|
-
"",
|
|
31932
|
-
"- Uses remote caching (requires AWS credentials)",
|
|
31933
|
-
"- Only rebuilds changed packages",
|
|
31934
|
-
"- Cache key based on file hashes and dependency graph",
|
|
31935
|
-
"- Configured in `turbo.json`",
|
|
31936
|
-
"",
|
|
31937
|
-
"## Workspace Rules",
|
|
31938
|
-
"",
|
|
31939
|
-
"- Source files: `src/` directory",
|
|
31940
|
-
"- Tests: Co-located with source files (`.spec.ts` or `.test.ts`)",
|
|
31941
|
-
"- Exports: Use `index.ts` files for clean public APIs",
|
|
31942
|
-
"- Configuration: Managed by Projen (edit `.projenrc.ts` or `projenrc/*.ts`)"
|
|
31943
|
-
].join("\n"),
|
|
31944
|
-
tags: ["workflow"]
|
|
32492
|
+
],
|
|
32493
|
+
claudePermissions: {
|
|
32494
|
+
allow: ["Bash(npx turbo:*)"]
|
|
31945
32495
|
}
|
|
31946
|
-
|
|
31947
|
-
|
|
31948
|
-
|
|
31949
|
-
}
|
|
31950
|
-
};
|
|
32496
|
+
};
|
|
32497
|
+
}
|
|
32498
|
+
var turborepoBundle = buildTurborepoBundle();
|
|
31951
32499
|
|
|
31952
32500
|
// src/agent/bundles/typescript.ts
|
|
31953
32501
|
var typescriptBundle = {
|
|
@@ -33162,7 +33710,7 @@ function renderPriorityRulesSection(rules) {
|
|
|
33162
33710
|
}
|
|
33163
33711
|
|
|
33164
33712
|
// src/agent/bundles/index.ts
|
|
33165
|
-
function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, defaultAgentTier = AGENT_MODEL.BALANCED, bundleAgentTiers = /* @__PURE__ */ new Map(), prReviewPolicy = resolvePrReviewPolicy()) {
|
|
33713
|
+
function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, defaultAgentTier = AGENT_MODEL.BALANCED, bundleAgentTiers = /* @__PURE__ */ new Map(), prReviewPolicy = resolvePrReviewPolicy(), buildPolicy = DEFAULT_BUILD_POLICY) {
|
|
33166
33714
|
const tierFor = (bundle) => bundleAgentTiers.get(bundle) ?? defaultAgentTier;
|
|
33167
33715
|
return [
|
|
33168
33716
|
buildBaseBundle(paths),
|
|
@@ -33170,11 +33718,11 @@ function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAUL
|
|
|
33170
33718
|
typescriptBundle,
|
|
33171
33719
|
vitestBundle,
|
|
33172
33720
|
jestBundle,
|
|
33173
|
-
|
|
33721
|
+
buildTurborepoBundle(buildPolicy),
|
|
33174
33722
|
pnpmBundle,
|
|
33175
33723
|
awsCdkBundle,
|
|
33176
33724
|
projenBundle,
|
|
33177
|
-
|
|
33725
|
+
buildGithubWorkflowBundle(buildPolicy),
|
|
33178
33726
|
slackBundle,
|
|
33179
33727
|
buildMeetingAnalysisBundle(tierFor("meeting-analysis")),
|
|
33180
33728
|
agendaBundle,
|
|
@@ -34259,6 +34807,13 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
|
|
|
34259
34807
|
* their rendered rule content reflects any consumer override.
|
|
34260
34808
|
* Bundles that do not read agent paths are passed through as-is
|
|
34261
34809
|
* from their default const exports.
|
|
34810
|
+
*
|
|
34811
|
+
* The build policy is auto-detected from the project's `TurboRepo`
|
|
34812
|
+
* component here rather than configured, so build guidance in the
|
|
34813
|
+
* `github-workflow` and `turborepo` rules only claims an AWS
|
|
34814
|
+
* credential requirement when a remote cache actually exists. The
|
|
34815
|
+
* getter is lazy by design — `TurboRepo` must already be attached
|
|
34816
|
+
* to the project when the bundles are first read.
|
|
34262
34817
|
*/
|
|
34263
34818
|
get pathAwareBundles() {
|
|
34264
34819
|
if (!this.cachedBundles) {
|
|
@@ -34267,7 +34822,8 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
|
|
|
34267
34822
|
resolveIssueDefaults(this.options.issueDefaults),
|
|
34268
34823
|
resolveDefaultAgentTier(this.options),
|
|
34269
34824
|
resolveBundleAgentTiers(this.options),
|
|
34270
|
-
resolvePrReviewPolicy(this.options.prReviewPolicy)
|
|
34825
|
+
resolvePrReviewPolicy(this.options.prReviewPolicy),
|
|
34826
|
+
resolveBuildPolicy(this.project)
|
|
34271
34827
|
);
|
|
34272
34828
|
}
|
|
34273
34829
|
return this.cachedBundles;
|
|
@@ -40964,6 +41520,7 @@ export const collections = {
|
|
|
40964
41520
|
CDK_WATCH_DEFAULTS_BY_STAGE,
|
|
40965
41521
|
CLAUDE_RULE_TARGET,
|
|
40966
41522
|
COMPLETE_JOB_ID,
|
|
41523
|
+
CONVENTIONAL_COMMIT_TYPE_LABELS,
|
|
40967
41524
|
CdkCli,
|
|
40968
41525
|
DEFAULT_AC_THRESHOLDS,
|
|
40969
41526
|
DEFAULT_AGENT_PATHS,
|
|
@@ -40973,6 +41530,7 @@ export const collections = {
|
|
|
40973
41530
|
DEFAULT_API_EXTRACTOR_REPORT_FILENAME,
|
|
40974
41531
|
DEFAULT_API_EXTRACTOR_REPORT_FOLDER,
|
|
40975
41532
|
DEFAULT_AUDIT_REPORT_DIR,
|
|
41533
|
+
DEFAULT_BUILD_POLICY,
|
|
40976
41534
|
DEFAULT_BUNDLE_OVERRIDES,
|
|
40977
41535
|
DEFAULT_DECOMPOSITION_TEMPLATE,
|
|
40978
41536
|
DEFAULT_DISPATCH_MODEL,
|
|
@@ -41034,6 +41592,7 @@ export const collections = {
|
|
|
41034
41592
|
MONOREPO_LAYOUT,
|
|
41035
41593
|
MonorepoProject,
|
|
41036
41594
|
Nvmrc,
|
|
41595
|
+
PHASE_LABEL_TYPE_MAP,
|
|
41037
41596
|
PROD_DEPLOY_NAME,
|
|
41038
41597
|
PROGRESS_FILES_FORMAT_VALUES,
|
|
41039
41598
|
PnpmWorkspace,
|
|
@@ -41093,6 +41652,7 @@ export const collections = {
|
|
|
41093
41652
|
buildCompanyProfileBundle,
|
|
41094
41653
|
buildCustomerProfileBundle,
|
|
41095
41654
|
buildDocsSyncBundle,
|
|
41655
|
+
buildGithubWorkflowBundle,
|
|
41096
41656
|
buildIndustryDiscoveryBundle,
|
|
41097
41657
|
buildMaintenanceAuditBundle,
|
|
41098
41658
|
buildMeetingAnalysisBundle,
|
|
@@ -41107,6 +41667,7 @@ export const collections = {
|
|
|
41107
41667
|
buildResearchPipelineBundle,
|
|
41108
41668
|
buildSoftwareProfileBundle,
|
|
41109
41669
|
buildStandardsResearchBundle,
|
|
41670
|
+
buildTurborepoBundle,
|
|
41110
41671
|
buildUnblockDependentsProcedure,
|
|
41111
41672
|
bundleNameForWorkflowRule,
|
|
41112
41673
|
businessModelsBundle,
|
|
@@ -41192,6 +41753,8 @@ export const collections = {
|
|
|
41192
41753
|
renderIssueTemplatesStarterPage,
|
|
41193
41754
|
renderMeetingTypesSection,
|
|
41194
41755
|
renderNextRequirementIdProcedure,
|
|
41756
|
+
renderPhaseTypeInvariantSection,
|
|
41757
|
+
renderPhaseTypeInvariantShellHelpers,
|
|
41195
41758
|
renderPriorityRulesSection,
|
|
41196
41759
|
renderProgressFileName,
|
|
41197
41760
|
renderProgressFilePath,
|
|
@@ -41224,6 +41787,7 @@ export const collections = {
|
|
|
41224
41787
|
resolveAgentTiers,
|
|
41225
41788
|
resolveAstroProjectOutdir,
|
|
41226
41789
|
resolveAwsCdkProjectOutdir,
|
|
41790
|
+
resolveBuildPolicy,
|
|
41227
41791
|
resolveBundleAgentTiers,
|
|
41228
41792
|
resolveDefaultAgentTier,
|
|
41229
41793
|
resolveIssueDefaults,
|
|
@@ -41242,6 +41806,7 @@ export const collections = {
|
|
|
41242
41806
|
resolveSkillEvals,
|
|
41243
41807
|
resolveTemplateVariables,
|
|
41244
41808
|
resolveTemporalFraming,
|
|
41809
|
+
resolveTypeLabelForLabels,
|
|
41245
41810
|
resolveTypeScriptProjectOutdir,
|
|
41246
41811
|
resolveUnblockDependents,
|
|
41247
41812
|
runScan,
|
|
@@ -41251,6 +41816,7 @@ export const collections = {
|
|
|
41251
41816
|
stripToolArtifactTagsProcedure,
|
|
41252
41817
|
tsdocRecordToFindings,
|
|
41253
41818
|
turborepoBundle,
|
|
41819
|
+
typeLabelForPhaseLabel,
|
|
41254
41820
|
typescriptBundle,
|
|
41255
41821
|
upstreamConfigulatorDocsBundle,
|
|
41256
41822
|
validateAgentTierConfig,
|