@codedrifters/configulator 0.0.402 → 0.0.404

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.mjs CHANGED
@@ -4326,6 +4326,35 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
4326
4326
  "schema and consumer example. `status:deferred` is a valid",
4327
4327
  "override value reserved for this exact use case.",
4328
4328
  "",
4329
+ "### The `status:deferred` invariant",
4330
+ "",
4331
+ "**No agent, procedure, or sweep ever removes `status:deferred`.",
4332
+ "Only a human promotes a deferred issue.**",
4333
+ "",
4334
+ "`status:deferred` records a deliberate human decision to park",
4335
+ "work. Automation may read it, count it, and log it \u2014 but the",
4336
+ "label is cleared exclusively by a human (or by a scheduled task",
4337
+ "a human configured for that purpose). Concretely:",
4338
+ "",
4339
+ "- **Unblock sweeps skip it.** Both `check-blocked.sh unblock`",
4340
+ " and `unblock-dependents.sh` leave a `status:deferred` +",
4341
+ " `status:blocked` issue blocked when its dependencies close,",
4342
+ " emitting a `SKIP_DEFERRED #<n>` line instead of flipping to",
4343
+ " `status:ready`. Dependency resolution is not a promotion",
4344
+ " signal for parked work.",
4345
+ "- **Dispatch scans exclude it.** `check-blocked.sh eligible`",
4346
+ " drops any issue carrying `status:deferred`, so a stray",
4347
+ " `status:ready` + `status:deferred` pairing never reaches a",
4348
+ " worker.",
4349
+ "- **No sweep writes it either.** Automation adds",
4350
+ " `status:deferred` only at filing time via the configured",
4351
+ " `issueDefaults` \u2014 never as a runtime triage decision.",
4352
+ "",
4353
+ "An issue that legitimately needs to leave the parked backlog is",
4354
+ "promoted by a human removing `status:deferred` and setting the",
4355
+ "appropriate base status. Any automation that removes the label",
4356
+ "is a bug.",
4357
+ "",
4329
4358
  "### Blocking Rules",
4330
4359
  "",
4331
4360
  "Two rules force `status:blocked` \u2014 both are non-negotiable:",
@@ -5527,6 +5556,409 @@ function buildBcmWriterBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAU
5527
5556
  }
5528
5557
  var bcmWriterBundle = buildBcmWriterBundle();
5529
5558
 
5559
+ // src/turbo/turbo-repo.ts
5560
+ import {
5561
+ Component as Component2,
5562
+ FileBase,
5563
+ JsonFile,
5564
+ SampleDir,
5565
+ SampleFile
5566
+ } from "projen/lib";
5567
+ import { JobPermission } from "projen/lib/github/workflows-model";
5568
+
5569
+ // src/turbo/turbo-repo-task.ts
5570
+ import { Component } from "projen/lib";
5571
+ var TurboRepoTask = class extends Component {
5572
+ constructor(project, options) {
5573
+ super(project);
5574
+ this.project = project;
5575
+ this.name = options.name;
5576
+ this.dependsOn = options.dependsOn ?? [];
5577
+ this.env = options.env ?? [];
5578
+ this.passThroughEnv = options.passThroughEnv ?? [];
5579
+ this.outputs = options.outputs ?? [];
5580
+ this.cache = options.cache ?? true;
5581
+ this.inputs = [
5582
+ ...options.inputs ?? [],
5583
+ // rerun if projen config changes
5584
+ ".projen/**",
5585
+ // ignore mac files
5586
+ "!.DS_Store",
5587
+ "!**/.DS_Store"
5588
+ ];
5589
+ this.outputLogs = options.outputLogs ?? "new-only";
5590
+ this.persistent = options.persistent ?? false;
5591
+ this.interactive = options.interactive ?? false;
5592
+ this.isActive = true;
5593
+ }
5594
+ taskConfig() {
5595
+ return {
5596
+ dependsOn: this.dependsOn,
5597
+ env: this.env,
5598
+ passThroughEnv: this.passThroughEnv,
5599
+ outputs: this.outputs,
5600
+ cache: this.cache,
5601
+ inputs: this.inputs,
5602
+ outputLogs: this.outputLogs,
5603
+ persistent: this.persistent,
5604
+ interactive: this.interactive
5605
+ };
5606
+ }
5607
+ };
5608
+
5609
+ // src/turbo/turbo-repo.ts
5610
+ var ROOT_TURBO_TASK_NAME = "turbo:build";
5611
+ var ROOT_CI_TASK_NAME = "build:all";
5612
+ var _TurboRepo = class _TurboRepo extends Component2 {
5613
+ constructor(project, options = {}) {
5614
+ super(project);
5615
+ this.project = project;
5616
+ /**
5617
+ * Sub-Tasks to run
5618
+ */
5619
+ this.tasks = [];
5620
+ this.turboVersion = options.turboVersion ?? "catalog:";
5621
+ this.isRootProject = project === project.root;
5622
+ if (this.isRootProject) {
5623
+ project.addDevDeps(`turbo@${this.turboVersion}`);
5624
+ }
5625
+ project.gitignore.addPatterns("/.turbo");
5626
+ project.npmignore?.addPatterns("/.turbo/");
5627
+ this.extends = options.extends ?? (this.isRootProject ? [] : ["//"]);
5628
+ this.globalDependencies = options.globalDependencies ?? [];
5629
+ this.globalEnv = options.globalEnv ?? [];
5630
+ this.globalPassThroughEnv = options.globalPassThroughEnv ?? [];
5631
+ this.ui = options.ui ?? "stream";
5632
+ this.dangerouslyDisablePackageManagerCheck = options.dangerouslyDisablePackageManagerCheck ?? false;
5633
+ this.cacheDir = options.cacheDir ?? ".turbo/cache";
5634
+ this.daemon = options.daemon ?? true;
5635
+ this.envMode = options.envMode ?? "strict";
5636
+ this.runOptions = {
5637
+ ...options.runOptions,
5638
+ summarize: options.runOptions?.summarize ?? true,
5639
+ concurrency: options.runOptions?.concurrency ?? 10
5640
+ };
5641
+ this.remoteCacheOptions = options.remoteCacheOptions;
5642
+ this.buildAllTaskEnvVars = options.buildAllTaskEnvVars ?? {};
5643
+ this.buildTask = new TurboRepoTask(this.project, {
5644
+ name: ROOT_TURBO_TASK_NAME,
5645
+ dependsOn: this.isRootProject ? [`^${ROOT_TURBO_TASK_NAME}`] : []
5646
+ });
5647
+ if (this.isRootProject) {
5648
+ this.buildAllTask = this.project.tasks.addTask(ROOT_CI_TASK_NAME, {
5649
+ description: "Root build followed by sub-project builds. Mimics the CI build process in one step."
5650
+ });
5651
+ this.buildAllTask.exec("turbo telemetry disable");
5652
+ if (this.buildAllTaskEnvVars) {
5653
+ Object.entries(this.buildAllTaskEnvVars).forEach(([name, value]) => {
5654
+ this.addGlobalEnvVar(name, value);
5655
+ });
5656
+ }
5657
+ if (!this.remoteCacheOptions) {
5658
+ this.buildAllTask.exec(
5659
+ `turbo ${ROOT_TURBO_TASK_NAME} ${this.renderRunArgs(false)}`
5660
+ );
5661
+ } else {
5662
+ this.buildAllTask.exec(
5663
+ `turbo ${ROOT_TURBO_TASK_NAME} ${this.renderRunArgs(true)}`,
5664
+ {
5665
+ condition: '[ ! -n "$CI" ]',
5666
+ env: {
5667
+ TURBO_ENDPOINT: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.endpointParamName} --query Parameter.Value --output text --profile ${this.remoteCacheOptions.profileName})`,
5668
+ TURBO_TOKEN: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.tokenParamName} --query Parameter.Value --output text --profile ${this.remoteCacheOptions.profileName})`
5669
+ }
5670
+ }
5671
+ );
5672
+ this.buildAllTask.exec(
5673
+ `turbo ${ROOT_TURBO_TASK_NAME} ${this.renderRunArgs(true)}`,
5674
+ {
5675
+ condition: '[ -n "$CI" ]',
5676
+ env: {
5677
+ TURBO_ENDPOINT: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.endpointParamName} --query Parameter.Value --output text)`,
5678
+ TURBO_TOKEN: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.tokenParamName} --query Parameter.Value --output text)`
5679
+ }
5680
+ }
5681
+ );
5682
+ }
5683
+ }
5684
+ if (!this.isRootProject) {
5685
+ this.preCompileTask = new TurboRepoTask(project, {
5686
+ name: options.preCompileTask?.name ?? "pre-compile",
5687
+ inputs: ["src/**"]
5688
+ });
5689
+ this.compileTask = new TurboRepoTask(project, {
5690
+ name: options.compileTask?.name ?? "compile",
5691
+ inputs: ["src/**"]
5692
+ });
5693
+ this.postCompileTask = new TurboRepoTask(project, {
5694
+ name: options.postCompileTask?.name ?? "post-compile",
5695
+ inputs: ["src/**"]
5696
+ });
5697
+ this.testTask = new TurboRepoTask(project, {
5698
+ name: options.testTask?.name ?? "test"
5699
+ });
5700
+ this.packageTask = new TurboRepoTask(project, {
5701
+ name: options.packageTask?.name ?? "package",
5702
+ inputs: [".npmignore"]
5703
+ });
5704
+ this.tasks.push(
5705
+ this.preCompileTask,
5706
+ this.compileTask,
5707
+ this.postCompileTask,
5708
+ this.testTask,
5709
+ this.packageTask
5710
+ );
5711
+ }
5712
+ }
5713
+ /**
5714
+ * Static method to discovert turbo in a project.
5715
+ */
5716
+ static of(project) {
5717
+ const isDefined = (c) => c instanceof _TurboRepo;
5718
+ return project.components.find(isDefined);
5719
+ }
5720
+ /**
5721
+ * Render the `turbo run` CLI flag string for the `build:all` / `reset:all`
5722
+ * task commands from {@link runOptions}. With no `runOptions` configured the
5723
+ * output matches the historically hard-coded flags exactly.
5724
+ *
5725
+ * @param remote - when `true`, also emit the remote-cache flags
5726
+ * (`--cache=remote:rw` plus `--api` / `--token` / `--team` derived from
5727
+ * {@link remoteCacheOptions}). The remote-cache `build:all` variant passes
5728
+ * `true`; `reset:all` and the local `build:all` variant pass `false`.
5729
+ */
5730
+ renderRunArgs(remote) {
5731
+ const run = this.runOptions;
5732
+ const args = [];
5733
+ if (run.summarize) {
5734
+ args.push("--summarize");
5735
+ }
5736
+ args.push(`--concurrency=${run.concurrency}`);
5737
+ if (run.force) {
5738
+ args.push("--force");
5739
+ }
5740
+ if (run.noCache) {
5741
+ args.push("--no-cache");
5742
+ }
5743
+ if (run.only) {
5744
+ args.push("--only");
5745
+ }
5746
+ if (run.affected) {
5747
+ args.push("--affected");
5748
+ }
5749
+ if (run.cacheWorkers !== void 0) {
5750
+ args.push(`--cache-workers=${run.cacheWorkers}`);
5751
+ }
5752
+ if (run.continueOn !== void 0) {
5753
+ args.push(`--continue=${run.continueOn}`);
5754
+ }
5755
+ if (run.frameworkInference !== void 0) {
5756
+ args.push(`--framework-inference=${run.frameworkInference}`);
5757
+ }
5758
+ for (const filter of run.filter ?? []) {
5759
+ args.push(`--filter=${filter}`);
5760
+ }
5761
+ if (run.outputLogs !== void 0) {
5762
+ args.push(`--output-logs=${run.outputLogs}`);
5763
+ }
5764
+ if (run.logOrder !== void 0) {
5765
+ args.push(`--log-order=${run.logOrder}`);
5766
+ }
5767
+ if (run.logPrefix !== void 0) {
5768
+ args.push(`--log-prefix=${run.logPrefix}`);
5769
+ }
5770
+ if (run.dryRun !== void 0) {
5771
+ args.push(`--dry-run=${run.dryRun}`);
5772
+ }
5773
+ const cache = run.cache ?? (remote ? "remote:rw" : void 0);
5774
+ if (cache !== void 0) {
5775
+ args.push(`--cache=${cache}`);
5776
+ }
5777
+ if (remote && this.remoteCacheOptions) {
5778
+ args.push(
5779
+ "--api=$TURBO_ENDPOINT",
5780
+ "--token=$TURBO_TOKEN",
5781
+ `--team=${this.remoteCacheOptions.teamName}`
5782
+ );
5783
+ }
5784
+ if (run.additionalArgs) {
5785
+ args.push(...run.additionalArgs);
5786
+ }
5787
+ return args.join(" ");
5788
+ }
5789
+ /**
5790
+ * Add an env var to the global env vars for all tasks.
5791
+ * This will also become an input for the build:all task cache at the root.
5792
+ */
5793
+ addGlobalEnvVar(name, value) {
5794
+ this.buildAllTask?.env(name, value);
5795
+ if (this.isRootProject) {
5796
+ this.globalEnv.push(name);
5797
+ }
5798
+ }
5799
+ activateBranchNameEnvVar(options) {
5800
+ const value = '$([ -n "$GIT_BRANCH_NAME" ] && echo "$GIT_BRANCH_NAME" || git rev-parse --abbrev-ref HEAD)';
5801
+ if (options === void 0) {
5802
+ this.project.logger.warn(
5803
+ "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."
5804
+ );
5805
+ this.addGlobalEnvVar("GIT_BRANCH_NAME", value);
5806
+ return;
5807
+ }
5808
+ const knownTaskNames = this.tasks.map((task) => task.name);
5809
+ const unknown = options.tasks.filter(
5810
+ (name) => !knownTaskNames.includes(name)
5811
+ );
5812
+ if (unknown.length > 0) {
5813
+ throw new Error(
5814
+ `TurboRepo.activateBranchNameEnvVar: unknown task name(s) ${JSON.stringify(
5815
+ unknown
5816
+ )}. Known tasks on this TurboRepo: ${JSON.stringify(knownTaskNames)}.`
5817
+ );
5818
+ }
5819
+ for (const name of options.tasks) {
5820
+ const task = this.tasks.find((t) => t.name === name);
5821
+ if (task && !task.env.includes("GIT_BRANCH_NAME")) {
5822
+ task.env.push("GIT_BRANCH_NAME");
5823
+ }
5824
+ }
5825
+ }
5826
+ /**
5827
+ * Paths of all generated files in the project, deduped, for use as task
5828
+ * inputs so the compile cache invalidates when they change. Includes
5829
+ * projen-managed `FileBase` files plus generated-once `SampleFile` /
5830
+ * `SampleDir` — whose paths projen keeps private, so they are read defensively
5831
+ * and skipped if that internal shape ever changes. Computed at synth time so
5832
+ * files added after this component (e.g. a subclass's SampleFiles) are seen.
5833
+ */
5834
+ generatedFileInputs() {
5835
+ const inputs = /* @__PURE__ */ new Set();
5836
+ for (const component of this.project.components) {
5837
+ if (component instanceof FileBase) {
5838
+ inputs.add(component.path);
5839
+ } else if (component instanceof SampleFile) {
5840
+ const filePath = component.filePath;
5841
+ if (typeof filePath === "string") {
5842
+ inputs.add(filePath);
5843
+ }
5844
+ } else if (component instanceof SampleDir) {
5845
+ const dir = component.dir;
5846
+ if (typeof dir === "string") {
5847
+ inputs.add(`${dir}/**`);
5848
+ }
5849
+ }
5850
+ }
5851
+ return Array.from(inputs);
5852
+ }
5853
+ preSynthesize() {
5854
+ let nextDependsOn = this.project.deps.all.filter((d) => d.version === "workspace:*").map((d) => [d.name, ROOT_TURBO_TASK_NAME].join("#"));
5855
+ if (!this.isRootProject) {
5856
+ [
5857
+ [this.project.preCompileTask, this.preCompileTask],
5858
+ [this.project.compileTask, this.compileTask],
5859
+ [this.project.postCompileTask, this.postCompileTask],
5860
+ [this.project.testTask, this.testTask],
5861
+ [this.project.packageTask, this.packageTask]
5862
+ ].forEach(([pjTask, turboTask]) => {
5863
+ if (pjTask && turboTask && pjTask.steps.length > 0) {
5864
+ if (nextDependsOn.length > 0) {
5865
+ turboTask.dependsOn.push(...nextDependsOn);
5866
+ }
5867
+ nextDependsOn = [turboTask.name];
5868
+ } else {
5869
+ turboTask.isActive = false;
5870
+ }
5871
+ });
5872
+ this.buildTask.dependsOn.push(...nextDependsOn);
5873
+ }
5874
+ const generatedInputs = this.generatedFileInputs();
5875
+ const appendGeneratedInputs = (task) => {
5876
+ if (!task) {
5877
+ return;
5878
+ }
5879
+ for (const input of generatedInputs) {
5880
+ if (!task.inputs.includes(input)) {
5881
+ task.inputs.push(input);
5882
+ }
5883
+ }
5884
+ };
5885
+ if (this.isRootProject) {
5886
+ appendGeneratedInputs(this.buildTask);
5887
+ } else {
5888
+ appendGeneratedInputs(this.preCompileTask);
5889
+ appendGeneratedInputs(this.compileTask);
5890
+ appendGeneratedInputs(this.postCompileTask);
5891
+ }
5892
+ const fileName = "turbo.json";
5893
+ this.project.addPackageIgnore(fileName);
5894
+ new JsonFile(this.project, fileName, {
5895
+ obj: {
5896
+ extends: this.extends.length ? this.extends : void 0,
5897
+ globalDependencies: this.isRootProject && this.globalDependencies.length ? this.globalDependencies : void 0,
5898
+ globalEnv: this.isRootProject && this.globalEnv.length ? this.globalEnv : void 0,
5899
+ globalPassThroughEnv: this.isRootProject && this.globalPassThroughEnv.length ? this.globalPassThroughEnv : void 0,
5900
+ ui: this.isRootProject ? this.ui : void 0,
5901
+ dangerouslyDisablePackageManagerCheck: this.isRootProject ? this.dangerouslyDisablePackageManagerCheck : void 0,
5902
+ cacheDir: this.isRootProject ? this.cacheDir : void 0,
5903
+ envMode: this.isRootProject ? this.envMode : void 0,
5904
+ /**
5905
+ * All tasks
5906
+ */
5907
+ tasks: this.tasks.filter((task) => task.isActive).reduce(
5908
+ (acc, task) => {
5909
+ acc[task.name] = {
5910
+ ...task.taskConfig()
5911
+ };
5912
+ return acc;
5913
+ },
5914
+ {
5915
+ [this.buildTask.name]: { ...this.buildTask.taskConfig() }
5916
+ }
5917
+ )
5918
+ }
5919
+ });
5920
+ super.preSynthesize();
5921
+ }
5922
+ };
5923
+ _TurboRepo.buildWorkflowOptions = (remoteCacheOptions) => {
5924
+ return {
5925
+ env: {
5926
+ GIT_BRANCH_NAME: "${{ github.head_ref || github.ref_name }}"
5927
+ },
5928
+ permissions: {
5929
+ contents: JobPermission.WRITE,
5930
+ idToken: JobPermission.WRITE
5931
+ },
5932
+ preBuildSteps: [
5933
+ {
5934
+ name: "AWS Creds for SSM",
5935
+ uses: "aws-actions/configure-aws-credentials@v6",
5936
+ with: {
5937
+ ["role-to-assume"]: remoteCacheOptions.oidcRole,
5938
+ ["aws-region"]: "us-east-1",
5939
+ ["role-duration-seconds"]: "900"
5940
+ }
5941
+ }
5942
+ ]
5943
+ };
5944
+ };
5945
+ var TurboRepo = _TurboRepo;
5946
+
5947
+ // src/agent/bundles/build-policy.ts
5948
+ var DEFAULT_BUILD_POLICY = {
5949
+ remoteCacheEnabled: false
5950
+ };
5951
+ function resolveBuildPolicy(project) {
5952
+ const remoteCacheOptions = TurboRepo.of(project)?.remoteCacheOptions;
5953
+ if (!remoteCacheOptions) {
5954
+ return DEFAULT_BUILD_POLICY;
5955
+ }
5956
+ return {
5957
+ remoteCacheEnabled: true,
5958
+ awsProfileName: remoteCacheOptions.profileName
5959
+ };
5960
+ }
5961
+
5530
5962
  // src/agent/bundles/business-models.ts
5531
5963
  var TEMPLATE_CANVAS = `---
5532
5964
  title: "Business Model: <Segment Name>"
@@ -10465,188 +10897,210 @@ var cleanMergedBranchesSkill = {
10465
10897
  }
10466
10898
  ]
10467
10899
  };
10468
- var githubWorkflowBundle = {
10469
- name: "github-workflow",
10470
- description: "GitHub issue and PR workflow automation patterns",
10471
- appliesWhen: (project) => hasComponent(project, GitHub),
10472
- rules: [
10473
- {
10474
- name: "issue-workflow",
10475
- description: "Automated workflow for starting work on a GitHub issue",
10476
- scope: AGENT_RULE_SCOPE.ALWAYS,
10477
- content: [
10478
- "# Issue Workflow",
10479
- "",
10480
- '## "Work on issue X" Automation',
10481
- "",
10482
- "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."
10483
- ].join("\n"),
10484
- tags: ["workflow"]
10485
- },
10486
- {
10487
- name: "create-issue-workflow",
10488
- description: "Automated workflow for creating a new GitHub issue",
10489
- // ALWAYS scope: users invoke "create an issue" from any
10490
- // context, not only when editing agent / skill / bundle source.
10491
- // Consumers that want to narrow the load can override via
10492
- // `agentConfig.additionalRulePaths` or `excludeRules`.
10493
- scope: AGENT_RULE_SCOPE.ALWAYS,
10494
- content: [
10495
- "# Create Issue Workflow",
10496
- "",
10497
- '## "Create an issue" Automation',
10498
- "",
10499
- "When the user says **create an issue** (or similar), follow these steps exactly:",
10500
- "",
10501
- "1. **Determine the issue type prefix** from the user's description:",
10502
- " - `epic:` \u2014 Large initiatives spanning multiple child issues",
10503
- " - `feat:` \u2014 New features or functionality",
10504
- " - `fix:` \u2014 Bug fixes",
10505
- " - `chore:` \u2014 Maintenance: deps, tooling, config",
10506
- " - `docs:` \u2014 Documentation-only work",
10507
- " - `refactor:` \u2014 Code restructure, no behavior change",
10508
- " - `release:` \u2014 Release preparation, version bumps",
10509
- " - `hotfix:` \u2014 Urgent production fixes",
10510
- " - If unclear, ask the user which type applies",
10511
- "2. **Compose the issue title** in the format: `<type>: <short description>`",
10512
- "3. **Determine the GitHub issue type** based on the prefix:",
10513
- " - `epic:` \u2192 Epic",
10514
- " - `feat:` \u2192 Feature",
10515
- " - `fix:` \u2192 Bug",
10516
- " - `chore:`, `docs:`, `refactor:`, `release:`, `hotfix:` \u2192 Task",
10517
- "4. **Identify prerequisite issues** \u2014 if the user mentions dependencies or blockers, include a **Dependencies** section in the body with `Depends on: #<issue-number>`",
10518
- "5. **Determine labels** \u2014 every issue must be created with the following labels:",
10519
- " - **`type:*`** \u2014 derived from the issue title prefix:",
10520
- " - `epic:` \u2192 `type:feat`",
10521
- " - `feat:` \u2192 `type:feat`",
10522
- " - `fix:` \u2192 `type:fix`",
10523
- " - `chore:` \u2192 `type:chore`",
10524
- " - `docs:` \u2192 `type:docs`",
10525
- " - `refactor:` \u2192 `type:refactor`",
10526
- " - `release:` \u2192 `type:release`",
10527
- " - `hotfix:` \u2192 `type:hotfix`",
10528
- ' - **`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`',
10529
- " - **`status:ready`** \u2014 always add unless the issue has dependencies or blockers, in which case use `status:blocked`",
10530
- "6. **Create the issue** using `gh issue create`:",
10531
- " - `--title '<type>: <description>'`",
10532
- " - `--body '<issue body>'`",
10533
- " - `--label '<type-label>' --label '<priority-label>' --label '<status-label>'`",
10534
- "7. **Set the GitHub issue type** by invoking the `set-issue-type.sh` helper (shipped with this bundle):",
10535
- "",
10536
- " ```sh",
10537
- " .claude/procedures/set-issue-type.sh <issue-number> <Feature|Task|Epic|Bug>",
10538
- " ```",
10539
- "",
10540
- " 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.",
10541
- "",
10542
- " **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:",
10543
- "",
10544
- " ```sh",
10545
- " 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>",
10546
- " 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>",
10547
- " ```",
10548
- "",
10549
- "### Issue Body Template",
10550
- "",
10551
- "```markdown",
10552
- "## Summary",
10553
- "",
10554
- "<1-3 sentences describing the issue>",
10555
- "",
10556
- "## Details",
10557
- "",
10558
- "<Detailed description, acceptance criteria, or reproduction steps as appropriate>",
10559
- "",
10560
- "## Dependencies",
10561
- "",
10562
- "Depends on: #<issue-number> (if any, otherwise omit this section)",
10563
- "```",
10564
- "",
10565
- "### Important",
10566
- "",
10567
- "- Always use the conventional prefix in the issue title",
10568
- "- Always assign the correct GitHub issue type via the `set-issue-type.sh` helper (step 7) \u2014 never via `gh issue create --type`",
10569
- "- Always include `type:*`, `priority:*`, and `status:*` labels",
10570
- "- If the user does not specify a type, ask before creating the issue",
10571
- "- If the priority cannot be inferred from the description, ask the user before creating the issue",
10572
- "- Keep titles concise and descriptive"
10573
- ].join("\n"),
10574
- tags: ["workflow"]
10575
- },
10576
- {
10577
- name: "pr-workflow",
10578
- description: "Automated workflow for opening a pull request",
10579
- scope: AGENT_RULE_SCOPE.ALWAYS,
10580
- content: [
10581
- "# PR Workflow",
10582
- "",
10583
- '## "Open a PR" Automation',
10584
- "",
10585
- "When the user says **open a PR** (or similar), follow these steps exactly:",
10586
- "",
10587
- "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.",
10588
- "2. **Run the full monorepo build** \u2014 run `pnpm build:all` to compile, lint, and test all packages (mirrors the CI pipeline). This command requires the user to be authenticated to AWS on the prod account used for Turborepo remote caching (`readonlyaccess-prod-525259625215-us-east-1` profile). If the command fails due to AWS credentials, ask the user to authenticate first. 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.",
10589
- "3. **Check for uncommitted changes** \u2014 if any exist, commit them with a conventional commit message",
10590
- "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",
10591
- "5. **Push the branch** to origin: `git push -u origin <branch>`",
10592
- "6. **Create the PR** using `gh pr create`:",
10593
- " - **Title**: use a conventional commit style title (e.g., `feat(scope): short description`)",
10594
- " - **Body**: include `Closes #<issue-number>` (derived from the branch name) and a brief summary of changes",
10595
- "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.",
10596
- "",
10597
- "### PR Body Template",
10598
- "",
10599
- "```markdown",
10600
- "## Summary",
10601
- "",
10602
- "<1-3 bullet points describing what changed and why>",
10603
- "",
10604
- "Closes #<issue-number>",
10605
- "",
10606
- "## Test Plan",
10607
- "",
10608
- "- [ ] Tests pass locally",
10609
- "- [ ] Relevant changes have been reviewed",
10610
- "```",
10611
- "",
10612
- "### Important",
10613
- "",
10614
- "- Always derive the issue number from the branch name (e.g., `feat/42-add-login` \u2192 `#42`)",
10615
- "- Use conventional commit format for the PR title",
10616
- "- Delegate merge to the `pr-reviewer` sub-agent \u2014 do not merge manually and do not enable auto-merge directly"
10617
- ].join("\n"),
10618
- tags: ["workflow"]
10619
- },
10620
- {
10621
- name: "branch-cleanup",
10622
- 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).",
10623
- scope: AGENT_RULE_SCOPE.ALWAYS,
10624
- content: [
10625
- "# Branch Cleanup",
10626
- "",
10627
- "Local branches accumulate after every merged PR. In squash-merge",
10628
- "repositories `git branch -d` refuses to delete them because the",
10629
- "commit hash on the base differs, even when the branch content is",
10630
- "fully merged. The `github-workflow` bundle ships two affordances",
10631
- "that use content-equality (not commit-graph reachability) to",
10632
- "identify branches safe to force-delete:",
10633
- "",
10634
- "- `/clean-merged-branches` \u2014 interactive slash-command skill that",
10635
- " classifies every local branch, prompts for confirmation, then",
10636
- " runs `git branch -D` on the confirmed list. See",
10637
- " `.claude/skills/clean-merged-branches/SKILL.md` for usage,",
10638
- " output format, and the squash-merge verification algorithm.",
10639
- "- `.claude/procedures/clean-merged-branches.sh` \u2014 analysis-only",
10640
- " procedure for non-interactive agent use (orchestrator,",
10641
- " maintenance-audit). NEVER deletes \u2014 only reports `MERGED` /",
10642
- " `UNMERGED` / `EMPTY` / `SKIP_WORKTREE` lines."
10643
- ].join("\n"),
10644
- tags: ["workflow"]
10645
- }
10646
- ],
10647
- skills: [cleanMergedBranchesSkill],
10648
- procedures: [setIssueTypeProcedure, cleanMergedBranchesProcedure]
10649
- };
10900
+ function renderPrWorkflowBuildStep(policy) {
10901
+ const lines = [
10902
+ "2. **Run the build \u2014 scoped to what changed.**",
10903
+ "",
10904
+ " - **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.",
10905
+ " - **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."
10906
+ ];
10907
+ if (policy.remoteCacheEnabled && policy.awsProfileName) {
10908
+ lines.push(
10909
+ "",
10910
+ ` \`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.`
10911
+ );
10912
+ }
10913
+ lines.push(
10914
+ "",
10915
+ " **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."
10916
+ );
10917
+ return lines;
10918
+ }
10919
+ function buildGithubWorkflowBundle(buildPolicy = DEFAULT_BUILD_POLICY) {
10920
+ return {
10921
+ name: "github-workflow",
10922
+ description: "GitHub issue and PR workflow automation patterns",
10923
+ appliesWhen: (project) => hasComponent(project, GitHub),
10924
+ rules: [
10925
+ {
10926
+ name: "issue-workflow",
10927
+ description: "Automated workflow for starting work on a GitHub issue",
10928
+ scope: AGENT_RULE_SCOPE.ALWAYS,
10929
+ content: [
10930
+ "# Issue Workflow",
10931
+ "",
10932
+ '## "Work on issue X" Automation',
10933
+ "",
10934
+ "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."
10935
+ ].join("\n"),
10936
+ tags: ["workflow"]
10937
+ },
10938
+ {
10939
+ name: "create-issue-workflow",
10940
+ description: "Automated workflow for creating a new GitHub issue",
10941
+ // ALWAYS scope: users invoke "create an issue" from any
10942
+ // context, not only when editing agent / skill / bundle source.
10943
+ // Consumers that want to narrow the load can override via
10944
+ // `agentConfig.additionalRulePaths` or `excludeRules`.
10945
+ scope: AGENT_RULE_SCOPE.ALWAYS,
10946
+ content: [
10947
+ "# Create Issue Workflow",
10948
+ "",
10949
+ '## "Create an issue" Automation',
10950
+ "",
10951
+ "When the user says **create an issue** (or similar), follow these steps exactly:",
10952
+ "",
10953
+ "1. **Determine the issue type prefix** from the user's description:",
10954
+ " - `epic:` \u2014 Large initiatives spanning multiple child issues",
10955
+ " - `feat:` \u2014 New features or functionality",
10956
+ " - `fix:` \u2014 Bug fixes",
10957
+ " - `chore:` \u2014 Maintenance: deps, tooling, config",
10958
+ " - `docs:` \u2014 Documentation-only work",
10959
+ " - `refactor:` \u2014 Code restructure, no behavior change",
10960
+ " - `release:` \u2014 Release preparation, version bumps",
10961
+ " - `hotfix:` \u2014 Urgent production fixes",
10962
+ " - If unclear, ask the user which type applies",
10963
+ "2. **Compose the issue title** in the format: `<type>: <short description>`",
10964
+ "3. **Determine the GitHub issue type** based on the prefix:",
10965
+ " - `epic:` \u2192 Epic",
10966
+ " - `feat:` \u2192 Feature",
10967
+ " - `fix:` \u2192 Bug",
10968
+ " - `chore:`, `docs:`, `refactor:`, `release:`, `hotfix:` \u2192 Task",
10969
+ "4. **Identify prerequisite issues** \u2014 if the user mentions dependencies or blockers, include a **Dependencies** section in the body with `Depends on: #<issue-number>`",
10970
+ "5. **Determine labels** \u2014 every issue must be created with the following labels:",
10971
+ " - **`type:*`** \u2014 derived from the issue title prefix:",
10972
+ " - `epic:` \u2192 `type:feat`",
10973
+ " - `feat:` \u2192 `type:feat`",
10974
+ " - `fix:` \u2192 `type:fix`",
10975
+ " - `chore:` \u2192 `type:chore`",
10976
+ " - `docs:` \u2192 `type:docs`",
10977
+ " - `refactor:` \u2192 `type:refactor`",
10978
+ " - `release:` \u2192 `type:release`",
10979
+ " - `hotfix:` \u2192 `type:hotfix`",
10980
+ ' - **`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`',
10981
+ " - **`status:ready`** \u2014 always add unless the issue has dependencies or blockers, in which case use `status:blocked`",
10982
+ "6. **Create the issue** using `gh issue create`:",
10983
+ " - `--title '<type>: <description>'`",
10984
+ " - `--body '<issue body>'`",
10985
+ " - `--label '<type-label>' --label '<priority-label>' --label '<status-label>'`",
10986
+ "7. **Set the GitHub issue type** by invoking the `set-issue-type.sh` helper (shipped with this bundle):",
10987
+ "",
10988
+ " ```sh",
10989
+ " .claude/procedures/set-issue-type.sh <issue-number> <Feature|Task|Epic|Bug>",
10990
+ " ```",
10991
+ "",
10992
+ " 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.",
10993
+ "",
10994
+ " **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:",
10995
+ "",
10996
+ " ```sh",
10997
+ " 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>",
10998
+ " 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>",
10999
+ " ```",
11000
+ "",
11001
+ "### Issue Body Template",
11002
+ "",
11003
+ "```markdown",
11004
+ "## Summary",
11005
+ "",
11006
+ "<1-3 sentences describing the issue>",
11007
+ "",
11008
+ "## Details",
11009
+ "",
11010
+ "<Detailed description, acceptance criteria, or reproduction steps as appropriate>",
11011
+ "",
11012
+ "## Dependencies",
11013
+ "",
11014
+ "Depends on: #<issue-number> (if any, otherwise omit this section)",
11015
+ "```",
11016
+ "",
11017
+ "### Important",
11018
+ "",
11019
+ "- Always use the conventional prefix in the issue title",
11020
+ "- Always assign the correct GitHub issue type via the `set-issue-type.sh` helper (step 7) \u2014 never via `gh issue create --type`",
11021
+ "- Always include `type:*`, `priority:*`, and `status:*` labels",
11022
+ "- If the user does not specify a type, ask before creating the issue",
11023
+ "- If the priority cannot be inferred from the description, ask the user before creating the issue",
11024
+ "- Keep titles concise and descriptive"
11025
+ ].join("\n"),
11026
+ tags: ["workflow"]
11027
+ },
11028
+ {
11029
+ name: "pr-workflow",
11030
+ description: "Automated workflow for opening a pull request",
11031
+ scope: AGENT_RULE_SCOPE.ALWAYS,
11032
+ content: [
11033
+ "# PR Workflow",
11034
+ "",
11035
+ '## "Open a PR" Automation',
11036
+ "",
11037
+ "When the user says **open a PR** (or similar), follow these steps exactly:",
11038
+ "",
11039
+ "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.",
11040
+ ...renderPrWorkflowBuildStep(buildPolicy),
11041
+ "3. **Check for uncommitted changes** \u2014 if any exist, commit them with a conventional commit message",
11042
+ "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",
11043
+ "5. **Push the branch** to origin: `git push -u origin <branch>`",
11044
+ "6. **Create the PR** using `gh pr create`:",
11045
+ " - **Title**: use a conventional commit style title (e.g., `feat(scope): short description`)",
11046
+ " - **Body**: include `Closes #<issue-number>` (derived from the branch name) and a brief summary of changes",
11047
+ "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.",
11048
+ "",
11049
+ "### PR Body Template",
11050
+ "",
11051
+ "```markdown",
11052
+ "## Summary",
11053
+ "",
11054
+ "<1-3 bullet points describing what changed and why>",
11055
+ "",
11056
+ "Closes #<issue-number>",
11057
+ "",
11058
+ "## Test Plan",
11059
+ "",
11060
+ "- [ ] Tests pass locally",
11061
+ "- [ ] Relevant changes have been reviewed",
11062
+ "```",
11063
+ "",
11064
+ "### Important",
11065
+ "",
11066
+ "- Always derive the issue number from the branch name (e.g., `feat/42-add-login` \u2192 `#42`)",
11067
+ "- Use conventional commit format for the PR title",
11068
+ "- Delegate merge to the `pr-reviewer` sub-agent \u2014 do not merge manually and do not enable auto-merge directly"
11069
+ ].join("\n"),
11070
+ tags: ["workflow"]
11071
+ },
11072
+ {
11073
+ name: "branch-cleanup",
11074
+ 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).",
11075
+ scope: AGENT_RULE_SCOPE.ALWAYS,
11076
+ content: [
11077
+ "# Branch Cleanup",
11078
+ "",
11079
+ "Local branches accumulate after every merged PR. In squash-merge",
11080
+ "repositories `git branch -d` refuses to delete them because the",
11081
+ "commit hash on the base differs, even when the branch content is",
11082
+ "fully merged. The `github-workflow` bundle ships two affordances",
11083
+ "that use content-equality (not commit-graph reachability) to",
11084
+ "identify branches safe to force-delete:",
11085
+ "",
11086
+ "- `/clean-merged-branches` \u2014 interactive slash-command skill that",
11087
+ " classifies every local branch, prompts for confirmation, then",
11088
+ " runs `git branch -D` on the confirmed list. See",
11089
+ " `.claude/skills/clean-merged-branches/SKILL.md` for usage,",
11090
+ " output format, and the squash-merge verification algorithm.",
11091
+ "- `.claude/procedures/clean-merged-branches.sh` \u2014 analysis-only",
11092
+ " procedure for non-interactive agent use (orchestrator,",
11093
+ " maintenance-audit). NEVER deletes \u2014 only reports `MERGED` /",
11094
+ " `UNMERGED` / `EMPTY` / `SKIP_WORKTREE` lines."
11095
+ ].join("\n"),
11096
+ tags: ["workflow"]
11097
+ }
11098
+ ],
11099
+ skills: [cleanMergedBranchesSkill],
11100
+ procedures: [setIssueTypeProcedure, cleanMergedBranchesProcedure]
11101
+ };
11102
+ }
11103
+ var githubWorkflowBundle = buildGithubWorkflowBundle();
10650
11104
 
10651
11105
  // src/agent/bundles/industry-discovery.ts
10652
11106
  function buildIndustryDiscoveryAnalystSubAgent(paths, issueDefaults) {
@@ -14818,13 +15272,16 @@ function renderUnblockDependentsSection(ud) {
14818
15272
  "",
14819
15273
  "1. Searches open issues for `Depends on: #<closed>` references in",
14820
15274
  " their body.",
14821
- "2. For each match, re-reads every dependency listed across",
15275
+ "2. Skips any match carrying `status:deferred`, emitting a",
15276
+ " `SKIP_DEFERRED` line. A human-parked issue is never promoted by",
15277
+ " a sweep, no matter how its dependencies resolve.",
15278
+ "3. For each remaining match, re-reads every dependency listed across",
14822
15279
  " **all** `Depends on:` lines in the body (bodies routinely list",
14823
15280
  " one dependency per line) and checks whether they are all closed.",
14824
- "3. If **all** dependencies are closed, flips the dependent from",
15281
+ "4. If **all** dependencies are closed, flips the dependent from",
14825
15282
  " `status:blocked` to `status:ready` and posts a one-line comment",
14826
15283
  " citing the resolving issue.",
14827
- "4. If **some** dependencies are still open, leaves the dependent",
15284
+ "5. If **some** dependencies are still open, leaves the dependent",
14828
15285
  " as `status:blocked` and optionally posts a partial-unblock",
14829
15286
  " comment (see the **Partial unblock behaviour** subsection",
14830
15287
  " below).",
@@ -14858,6 +15315,7 @@ function renderUnblockDependentsSection(ud) {
14858
15315
  'UNBLOCKED #<dep> \u2014 all deps closed (<closed-list>) \u2014 "<title>"',
14859
15316
  'UNBLOCK_FAILED #<dep> \u2014 label flip failed (all deps closed: <closed-list>) \u2014 "<title>"',
14860
15317
  'STILL_BLOCKED #<dep> \u2014 waiting on <open-list> \u2014 "<title>"',
15318
+ 'SKIP_DEFERRED #<dep> \u2014 status:deferred; human-parked, not promoting \u2014 "<title>"',
14861
15319
  "NO_DEPENDENTS #<closed>",
14862
15320
  "```",
14863
15321
  "",
@@ -14867,7 +15325,10 @@ function renderUnblockDependentsSection(ud) {
14867
15325
  "error, permissions, or the dependent went stale between the",
14868
15326
  "search and the edit) \u2014 a human needs to retry or flip the label",
14869
15327
  "manually. `STILL_BLOCKED` lines mean the dependent stays blocked",
14870
- "pending other open deps. `NO_DEPENDENTS` means no open issue",
15328
+ "pending other open deps. `SKIP_DEFERRED` lines mean the dependent",
15329
+ "carries `status:deferred` and was deliberately left blocked even",
15330
+ "though its dependencies resolved \u2014 the sweep never promotes a",
15331
+ "human-parked issue. `NO_DEPENDENTS` means no open issue",
14871
15332
  "references the just-closed issue in a `Depends on:` line.",
14872
15333
  "",
14873
15334
  "### Partial unblock behaviour",
@@ -15018,7 +15479,7 @@ function renderUnblockDependentsScript(ud) {
15018
15479
  "# `status:blocked` \u2192 `status:ready` directly on the overflow.",
15019
15480
  'dependents=$(gh issue list --label "status:blocked" --state open \\',
15020
15481
  ' --search "in:body \\"Depends on:\\"" \\',
15021
- ' --json number,title,body --limit 100 2>/dev/null || echo "[]")',
15482
+ ' --json number,title,body,labels --limit 100 2>/dev/null || echo "[]")',
15022
15483
  "",
15023
15484
  `count=$(echo "$dependents" | jq 'length')`,
15024
15485
  'if [[ "$count" -eq 0 ]]; then',
@@ -15026,6 +15487,18 @@ function renderUnblockDependentsScript(ud) {
15026
15487
  " exit 0",
15027
15488
  "fi",
15028
15489
  "",
15490
+ "# Human-parked candidates (`status:deferred`). Collected jq-side into a",
15491
+ "# space-delimited lookup string rather than threaded through the",
15492
+ "# tab-separated records below: an extra TSV field would either sit after",
15493
+ "# dep_line (displacing the mandatory-last optional field) or before it",
15494
+ "# (reintroducing the #884 IFS tab-collapse bug). The skip itself happens",
15495
+ "# inside the loop, after the closed-issue reference test, so an unrelated",
15496
+ "# deferred issue in the search window never emits a line. No agent or",
15497
+ "# sweep ever removes `status:deferred` \u2014 only a human promotes it.",
15498
+ `deferred_set=" $(echo "$dependents" | jq -r '`,
15499
+ ' .[] | select(.labels | map(.name) | index("status:deferred")) | .number',
15500
+ `' | tr '\\n' ' ')"`,
15501
+ "",
15029
15502
  "# Union every `Depends on:` line per candidate (a body may list one",
15030
15503
  "# dependency per line) into a single space-joined dep string and stream",
15031
15504
  "# as tab-separated number / title / dep-line tuples.",
@@ -15061,6 +15534,14 @@ function renderUnblockDependentsScript(ud) {
15061
15534
  "",
15062
15535
  " found_any=true",
15063
15536
  "",
15537
+ " # Deliberately-parked dependents never get promoted by a sweep, even",
15538
+ " # when every dependency has closed. Only a human clears",
15539
+ " # `status:deferred`.",
15540
+ ' if [[ "$deferred_set" == *" ${num} "* ]]; then',
15541
+ ' echo "SKIP_DEFERRED #${num} \u2014 status:deferred; human-parked, not promoting \u2014 \\"${title}\\""',
15542
+ " continue",
15543
+ " fi",
15544
+ "",
15064
15545
  " # Re-check every dep in the list. Fully-closed \u2192 unblock.",
15065
15546
  " all_closed=true",
15066
15547
  ' open_deps=""',
@@ -15250,15 +15731,26 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15250
15731
  " # position.",
15251
15732
  " local issues",
15252
15733
  ' issues=$(gh issue list --label "status:blocked" --state open \\',
15253
- ' --json number,body --limit 1000 2>/dev/null || echo "[]")',
15734
+ ' --json number,body,labels --limit 1000 2>/dev/null || echo "[]")',
15254
15735
  "",
15255
15736
  " local count",
15256
15737
  ` count=$(echo "$issues" | jq 'length')`,
15257
15738
  ' if [[ "$count" -eq 0 ]]; then',
15258
- ' echo "TRIAGE_DONE unblocked=0 still_blocked=0"',
15739
+ ' echo "TRIAGE_DONE unblocked=0 still_blocked=0 deferred_skipped=0"',
15259
15740
  " return 0",
15260
15741
  " fi",
15261
15742
  "",
15743
+ " # Human-parked candidates (`status:deferred`). Collected jq-side into",
15744
+ " # a space-delimited lookup string rather than threaded through the",
15745
+ " # tab-separated records below: an extra TSV field would either sit",
15746
+ " # after dep_line (displacing the mandatory-last optional field) or",
15747
+ " # before it (reintroducing the #884 IFS tab-collapse bug). No agent",
15748
+ " # or sweep ever removes `status:deferred` \u2014 only a human promotes it.",
15749
+ " local deferred_set",
15750
+ ` deferred_set=" $(echo "$issues" | jq -r '`,
15751
+ ' .[] | select(.labels | map(.name) | index("status:deferred")) | .number',
15752
+ ` ' | tr '\\n' ' ')"`,
15753
+ "",
15262
15754
  " local issue_data",
15263
15755
  ` issue_data=$(echo "$issues" | jq -r '`,
15264
15756
  " .[] |",
@@ -15271,10 +15763,20 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15271
15763
  " # mirroring unblock-dependents.sh.",
15272
15764
  " local unblocked_count=0",
15273
15765
  " local still_blocked_count=0",
15766
+ " local deferred_skipped_count=0",
15274
15767
  "",
15275
15768
  " while IFS=$'\\t' read -r num dep_line; do",
15276
15769
  ' [[ -z "$num" ]] && continue',
15277
15770
  "",
15771
+ " # Deliberately-parked issues never get promoted by a sweep, even",
15772
+ " # when every dependency has closed. Only a human clears",
15773
+ " # `status:deferred`.",
15774
+ ' if [[ "$deferred_set" == *" ${num} "* ]]; then',
15775
+ ' echo "SKIP_DEFERRED #${num} \u2014 status:deferred; human-parked, not promoting"',
15776
+ " deferred_skipped_count=$((deferred_skipped_count + 1))",
15777
+ " continue",
15778
+ " fi",
15779
+ "",
15278
15780
  ' if [[ -z "$dep_line" ]]; then',
15279
15781
  ' echo "BLOCKED #${num} \u2014 no Depends on field found"',
15280
15782
  " still_blocked_count=$((still_blocked_count + 1))",
@@ -15335,7 +15837,7 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15335
15837
  ' done <<< "$issue_data"',
15336
15838
  "",
15337
15839
  " # Single summary line consumed by the orchestrator.",
15338
- ' echo "TRIAGE_DONE unblocked=${unblocked_count} still_blocked=${still_blocked_count}"',
15840
+ ' echo "TRIAGE_DONE unblocked=${unblocked_count} still_blocked=${still_blocked_count} deferred_skipped=${deferred_skipped_count}"',
15339
15841
  "}",
15340
15842
  "",
15341
15843
  "cmd_eligible() {",
@@ -15359,11 +15861,26 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15359
15861
  " continue",
15360
15862
  " fi",
15361
15863
  "",
15864
+ " # Dispatch exclusions. `gh issue list` has no negative-label filter",
15865
+ " # for a label-filtered (non-search) listing, so both exclusions run",
15866
+ " # jq-side over the labels already fetched above \u2014 no new TSV field",
15867
+ " # is needed, and the mandatory-last dep_line ordering is untouched.",
15868
+ " # - `status:needs-attention` is additive (it no longer replaces the",
15869
+ " # base status), so a scope-gate-flagged issue keeps `status:ready`",
15870
+ " # and would re-surface in every scan until a human clears the flag",
15871
+ " # (#901). It is never dispatchable, so drop it from the scan.",
15872
+ " # - `status:deferred` is human-parked: captured for provenance and",
15873
+ " # never auto-dispatched. `status:ready` + `status:deferred` is",
15874
+ " # reachable (e.g. after an unblock or a manual edit), so exclude",
15875
+ " # it here as the dispatch-side half of the #887 guard.",
15362
15876
  " local issue_data",
15363
15877
  ` issue_data=$(echo "$issues" | jq -r '`,
15364
15878
  " .[] |",
15879
+ " (.labels | map(.name)) as $names |",
15880
+ ' select($names | index("status:needs-attention") | not) |',
15881
+ ' select($names | index("status:deferred") | not) |',
15365
15882
  ' (.body | split("\\n") | map(select(test("Depends on:"; "i"))) | join(" ")) as $dep_line |',
15366
- ' (.labels | map(.name) | map(select(startswith("type:"))) | .[0] // "") as $type_label |',
15883
+ ' ($names | map(select(startswith("type:"))) | .[0] // "") as $type_label |',
15367
15884
  ' "\\(.number)\\t\\(.title)\\t\\($type_label)\\t\\($dep_line)"',
15368
15885
  " ')",
15369
15886
  "",
@@ -16007,7 +16524,7 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
16007
16524
  function buildCheckBlockedProcedure(tiers, scopeGate = resolveScopeGate(), runRatio = resolveRunRatio()) {
16008
16525
  return {
16009
16526
  name: "check-blocked.sh",
16010
- 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; 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, and emits a single `TRIAGE_DONE unblocked=N still_blocked=M` 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.",
16527
+ 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.",
16011
16528
  content: buildCheckBlockedScript(tiers, scopeGate, runRatio)
16012
16529
  };
16013
16530
  }
@@ -16689,14 +17206,20 @@ var orchestratorSubAgent = {
16689
17206
  "The script emits one summary line in this shape:",
16690
17207
  "",
16691
17208
  "```",
16692
- "TRIAGE_DONE unblocked=<N> still_blocked=<M>",
17209
+ "TRIAGE_DONE unblocked=<N> still_blocked=<M> deferred_skipped=<K>",
16693
17210
  "```",
16694
17211
  "",
16695
17212
  "Per-issue informational lines (`UNBLOCKED #N`, `UNBLOCK_FAILED #N`,",
16696
- "`STILL_BLOCKED #N`, `BLOCKED #N \u2014 no Depends on field found`) are",
16697
- "emitted for log visibility but are **not** load-bearing for the",
16698
- "orchestrator \u2014 partial failures (one bad `gh` call) do not abort",
16699
- "the sweep, they are simply counted toward `still_blocked`.",
17213
+ "`STILL_BLOCKED #N`, `SKIP_DEFERRED #N`, `BLOCKED #N \u2014 no Depends on",
17214
+ "field found`) are emitted for log visibility but are **not**",
17215
+ "load-bearing for the orchestrator \u2014 partial failures (one bad `gh`",
17216
+ "call) do not abort the sweep, they are simply counted toward",
17217
+ "`still_blocked`.",
17218
+ "",
17219
+ "`deferred_skipped` counts candidates the sweep deliberately left",
17220
+ "blocked because they carry `status:deferred`. Those issues are",
17221
+ "human-parked: no agent or sweep ever removes `status:deferred`, so",
17222
+ "a non-zero count is expected steady-state noise, not a failure.",
16700
17223
  "",
16701
17224
  "This phase is the **fallback safety net** for agent-driven",
16702
17225
  "unblocking. Every agent that applies `status:done` already runs",
@@ -17876,9 +18399,11 @@ var checkBlockedCommand = {
17876
18399
  "reference, applies the `status:blocked` \u2192 `status:ready` label flip",
17877
18400
  "itself, posts the canned `Dependencies resolved \u2014 unblocking.`",
17878
18401
  "comment, and emits a single",
17879
- "`TRIAGE_DONE unblocked=<N> still_blocked=<M>` summary line. Per-issue",
17880
- "informational lines (`UNBLOCKED`, `UNBLOCK_FAILED`, `STILL_BLOCKED`)",
17881
- "are also emitted for log visibility.",
18402
+ "`TRIAGE_DONE unblocked=<N> still_blocked=<M> deferred_skipped=<K>`",
18403
+ "summary line. Candidates carrying `status:deferred` are skipped",
18404
+ "rather than promoted \u2014 only a human clears that label. Per-issue",
18405
+ "informational lines (`UNBLOCKED`, `UNBLOCK_FAILED`, `STILL_BLOCKED`,",
18406
+ "`SKIP_DEFERRED`) are also emitted for log visibility.",
17882
18407
  "",
17883
18408
  "Summarise the output (the `TRIAGE_DONE` counts and the first few",
17884
18409
  "per-issue lines) \u2014 do **not** apply additional label flips yourself;",
@@ -18886,7 +19411,7 @@ var peopleProfileBundle = buildPeopleProfileBundle();
18886
19411
 
18887
19412
  // src/pnpm/pnpm-workspace.ts
18888
19413
  import { relative } from "path";
18889
- import { Component, YamlFile } from "projen";
19414
+ import { Component as Component3, YamlFile } from "projen";
18890
19415
  var MINIMUM_RELEASE_AGE = {
18891
19416
  ZERO_DAYS: 0,
18892
19417
  ONE_HOUR: 60,
@@ -18900,7 +19425,7 @@ var MINIMUM_RELEASE_AGE = {
18900
19425
  SIX_DAYS: 8640,
18901
19426
  ONE_WEEK: 10080
18902
19427
  };
18903
- var PnpmWorkspace = class _PnpmWorkspace extends Component {
19428
+ var PnpmWorkspace = class _PnpmWorkspace extends Component3 {
18904
19429
  /**
18905
19430
  * Get the pnpm workspace component of a project. If it does not exist,
18906
19431
  * return undefined.
@@ -28465,7 +28990,7 @@ function buildResearchPipelineBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults
28465
28990
  var researchPipelineBundle = buildResearchPipelineBundle();
28466
28991
 
28467
28992
  // src/projects/project-metadata.ts
28468
- import { Component as Component2 } from "projen";
28993
+ import { Component as Component4 } from "projen";
28469
28994
  import { NodeProject } from "projen/lib/javascript";
28470
28995
  var GITHUB_HTTPS_RE = /(?:https?:\/\/|git\+https:\/\/)github\.com\/([^/]+)\/([^/.]+)(?:\.git)?/;
28471
28996
  var GITHUB_SSH_RE = /git@github\.com:([^/]+)\/([^/.]+)(?:\.git)?/;
@@ -28480,7 +29005,7 @@ function parseGitHubUrl(url) {
28480
29005
  }
28481
29006
  return { owner: void 0, name: void 0 };
28482
29007
  }
28483
- var ProjectMetadata = class _ProjectMetadata extends Component2 {
29008
+ var ProjectMetadata = class _ProjectMetadata extends Component4 {
28484
29009
  /**
28485
29010
  * Returns the ProjectMetadata instance for a project. Walks up the parent
28486
29011
  * chain so sub-projects resolve the metadata declared on a root
@@ -31077,435 +31602,56 @@ function buildStandardsResearchBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults
31077
31602
  }
31078
31603
  var standardsResearchBundle = buildStandardsResearchBundle();
31079
31604
 
31080
- // src/turbo/turbo-repo.ts
31081
- import {
31082
- Component as Component4,
31083
- FileBase,
31084
- JsonFile,
31085
- SampleDir,
31086
- SampleFile
31087
- } from "projen/lib";
31088
- import { JobPermission } from "projen/lib/github/workflows-model";
31089
-
31090
- // src/turbo/turbo-repo-task.ts
31091
- import { Component as Component3 } from "projen/lib";
31092
- var TurboRepoTask = class extends Component3 {
31093
- constructor(project, options) {
31094
- super(project);
31095
- this.project = project;
31096
- this.name = options.name;
31097
- this.dependsOn = options.dependsOn ?? [];
31098
- this.env = options.env ?? [];
31099
- this.passThroughEnv = options.passThroughEnv ?? [];
31100
- this.outputs = options.outputs ?? [];
31101
- this.cache = options.cache ?? true;
31102
- this.inputs = [
31103
- ...options.inputs ?? [],
31104
- // rerun if projen config changes
31105
- ".projen/**",
31106
- // ignore mac files
31107
- "!.DS_Store",
31108
- "!**/.DS_Store"
31109
- ];
31110
- this.outputLogs = options.outputLogs ?? "new-only";
31111
- this.persistent = options.persistent ?? false;
31112
- this.interactive = options.interactive ?? false;
31113
- this.isActive = true;
31114
- }
31115
- taskConfig() {
31116
- return {
31117
- dependsOn: this.dependsOn,
31118
- env: this.env,
31119
- passThroughEnv: this.passThroughEnv,
31120
- outputs: this.outputs,
31121
- cache: this.cache,
31122
- inputs: this.inputs,
31123
- outputLogs: this.outputLogs,
31124
- persistent: this.persistent,
31125
- interactive: this.interactive
31126
- };
31127
- }
31128
- };
31129
-
31130
- // src/turbo/turbo-repo.ts
31131
- var ROOT_TURBO_TASK_NAME = "turbo:build";
31132
- var ROOT_CI_TASK_NAME = "build:all";
31133
- var _TurboRepo = class _TurboRepo extends Component4 {
31134
- constructor(project, options = {}) {
31135
- super(project);
31136
- this.project = project;
31137
- /**
31138
- * Sub-Tasks to run
31139
- */
31140
- this.tasks = [];
31141
- this.turboVersion = options.turboVersion ?? "catalog:";
31142
- this.isRootProject = project === project.root;
31143
- if (this.isRootProject) {
31144
- project.addDevDeps(`turbo@${this.turboVersion}`);
31145
- }
31146
- project.gitignore.addPatterns("/.turbo");
31147
- project.npmignore?.addPatterns("/.turbo/");
31148
- this.extends = options.extends ?? (this.isRootProject ? [] : ["//"]);
31149
- this.globalDependencies = options.globalDependencies ?? [];
31150
- this.globalEnv = options.globalEnv ?? [];
31151
- this.globalPassThroughEnv = options.globalPassThroughEnv ?? [];
31152
- this.ui = options.ui ?? "stream";
31153
- this.dangerouslyDisablePackageManagerCheck = options.dangerouslyDisablePackageManagerCheck ?? false;
31154
- this.cacheDir = options.cacheDir ?? ".turbo/cache";
31155
- this.daemon = options.daemon ?? true;
31156
- this.envMode = options.envMode ?? "strict";
31157
- this.runOptions = {
31158
- ...options.runOptions,
31159
- summarize: options.runOptions?.summarize ?? true,
31160
- concurrency: options.runOptions?.concurrency ?? 10
31161
- };
31162
- this.remoteCacheOptions = options.remoteCacheOptions;
31163
- this.buildAllTaskEnvVars = options.buildAllTaskEnvVars ?? {};
31164
- this.buildTask = new TurboRepoTask(this.project, {
31165
- name: ROOT_TURBO_TASK_NAME,
31166
- dependsOn: this.isRootProject ? [`^${ROOT_TURBO_TASK_NAME}`] : []
31167
- });
31168
- if (this.isRootProject) {
31169
- this.buildAllTask = this.project.tasks.addTask(ROOT_CI_TASK_NAME, {
31170
- description: "Root build followed by sub-project builds. Mimics the CI build process in one step."
31171
- });
31172
- this.buildAllTask.exec("turbo telemetry disable");
31173
- if (this.buildAllTaskEnvVars) {
31174
- Object.entries(this.buildAllTaskEnvVars).forEach(([name, value]) => {
31175
- this.addGlobalEnvVar(name, value);
31176
- });
31177
- }
31178
- if (!this.remoteCacheOptions) {
31179
- this.buildAllTask.exec(
31180
- `turbo ${ROOT_TURBO_TASK_NAME} ${this.renderRunArgs(false)}`
31181
- );
31182
- } else {
31183
- this.buildAllTask.exec(
31184
- `turbo ${ROOT_TURBO_TASK_NAME} ${this.renderRunArgs(true)}`,
31185
- {
31186
- condition: '[ ! -n "$CI" ]',
31187
- env: {
31188
- TURBO_ENDPOINT: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.endpointParamName} --query Parameter.Value --output text --profile ${this.remoteCacheOptions.profileName})`,
31189
- TURBO_TOKEN: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.tokenParamName} --query Parameter.Value --output text --profile ${this.remoteCacheOptions.profileName})`
31190
- }
31191
- }
31192
- );
31193
- this.buildAllTask.exec(
31194
- `turbo ${ROOT_TURBO_TASK_NAME} ${this.renderRunArgs(true)}`,
31195
- {
31196
- condition: '[ -n "$CI" ]',
31197
- env: {
31198
- TURBO_ENDPOINT: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.endpointParamName} --query Parameter.Value --output text)`,
31199
- TURBO_TOKEN: `$(aws ssm get-parameter --name ${this.remoteCacheOptions.tokenParamName} --query Parameter.Value --output text)`
31200
- }
31201
- }
31202
- );
31203
- }
31204
- }
31205
- if (!this.isRootProject) {
31206
- this.preCompileTask = new TurboRepoTask(project, {
31207
- name: options.preCompileTask?.name ?? "pre-compile",
31208
- inputs: ["src/**"]
31209
- });
31210
- this.compileTask = new TurboRepoTask(project, {
31211
- name: options.compileTask?.name ?? "compile",
31212
- inputs: ["src/**"]
31213
- });
31214
- this.postCompileTask = new TurboRepoTask(project, {
31215
- name: options.postCompileTask?.name ?? "post-compile",
31216
- inputs: ["src/**"]
31217
- });
31218
- this.testTask = new TurboRepoTask(project, {
31219
- name: options.testTask?.name ?? "test"
31220
- });
31221
- this.packageTask = new TurboRepoTask(project, {
31222
- name: options.packageTask?.name ?? "package",
31223
- inputs: [".npmignore"]
31224
- });
31225
- this.tasks.push(
31226
- this.preCompileTask,
31227
- this.compileTask,
31228
- this.postCompileTask,
31229
- this.testTask,
31230
- this.packageTask
31231
- );
31232
- }
31233
- }
31234
- /**
31235
- * Static method to discovert turbo in a project.
31236
- */
31237
- static of(project) {
31238
- const isDefined = (c) => c instanceof _TurboRepo;
31239
- return project.components.find(isDefined);
31240
- }
31241
- /**
31242
- * Render the `turbo run` CLI flag string for the `build:all` / `reset:all`
31243
- * task commands from {@link runOptions}. With no `runOptions` configured the
31244
- * output matches the historically hard-coded flags exactly.
31245
- *
31246
- * @param remote - when `true`, also emit the remote-cache flags
31247
- * (`--cache=remote:rw` plus `--api` / `--token` / `--team` derived from
31248
- * {@link remoteCacheOptions}). The remote-cache `build:all` variant passes
31249
- * `true`; `reset:all` and the local `build:all` variant pass `false`.
31250
- */
31251
- renderRunArgs(remote) {
31252
- const run = this.runOptions;
31253
- const args = [];
31254
- if (run.summarize) {
31255
- args.push("--summarize");
31256
- }
31257
- args.push(`--concurrency=${run.concurrency}`);
31258
- if (run.force) {
31259
- args.push("--force");
31260
- }
31261
- if (run.noCache) {
31262
- args.push("--no-cache");
31263
- }
31264
- if (run.only) {
31265
- args.push("--only");
31266
- }
31267
- if (run.affected) {
31268
- args.push("--affected");
31269
- }
31270
- if (run.cacheWorkers !== void 0) {
31271
- args.push(`--cache-workers=${run.cacheWorkers}`);
31272
- }
31273
- if (run.continueOn !== void 0) {
31274
- args.push(`--continue=${run.continueOn}`);
31275
- }
31276
- if (run.frameworkInference !== void 0) {
31277
- args.push(`--framework-inference=${run.frameworkInference}`);
31278
- }
31279
- for (const filter of run.filter ?? []) {
31280
- args.push(`--filter=${filter}`);
31281
- }
31282
- if (run.outputLogs !== void 0) {
31283
- args.push(`--output-logs=${run.outputLogs}`);
31284
- }
31285
- if (run.logOrder !== void 0) {
31286
- args.push(`--log-order=${run.logOrder}`);
31287
- }
31288
- if (run.logPrefix !== void 0) {
31289
- args.push(`--log-prefix=${run.logPrefix}`);
31290
- }
31291
- if (run.dryRun !== void 0) {
31292
- args.push(`--dry-run=${run.dryRun}`);
31293
- }
31294
- const cache = run.cache ?? (remote ? "remote:rw" : void 0);
31295
- if (cache !== void 0) {
31296
- args.push(`--cache=${cache}`);
31297
- }
31298
- if (remote && this.remoteCacheOptions) {
31299
- args.push(
31300
- "--api=$TURBO_ENDPOINT",
31301
- "--token=$TURBO_TOKEN",
31302
- `--team=${this.remoteCacheOptions.teamName}`
31303
- );
31304
- }
31305
- if (run.additionalArgs) {
31306
- args.push(...run.additionalArgs);
31307
- }
31308
- return args.join(" ");
31309
- }
31310
- /**
31311
- * Add an env var to the global env vars for all tasks.
31312
- * This will also become an input for the build:all task cache at the root.
31313
- */
31314
- addGlobalEnvVar(name, value) {
31315
- this.buildAllTask?.env(name, value);
31316
- if (this.isRootProject) {
31317
- this.globalEnv.push(name);
31318
- }
31319
- }
31320
- activateBranchNameEnvVar(options) {
31321
- const value = '$([ -n "$GIT_BRANCH_NAME" ] && echo "$GIT_BRANCH_NAME" || git rev-parse --abbrev-ref HEAD)';
31322
- if (options === void 0) {
31323
- this.project.logger.warn(
31324
- "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."
31325
- );
31326
- this.addGlobalEnvVar("GIT_BRANCH_NAME", value);
31327
- return;
31328
- }
31329
- const knownTaskNames = this.tasks.map((task) => task.name);
31330
- const unknown = options.tasks.filter(
31331
- (name) => !knownTaskNames.includes(name)
31332
- );
31333
- if (unknown.length > 0) {
31334
- throw new Error(
31335
- `TurboRepo.activateBranchNameEnvVar: unknown task name(s) ${JSON.stringify(
31336
- unknown
31337
- )}. Known tasks on this TurboRepo: ${JSON.stringify(knownTaskNames)}.`
31338
- );
31339
- }
31340
- for (const name of options.tasks) {
31341
- const task = this.tasks.find((t) => t.name === name);
31342
- if (task && !task.env.includes("GIT_BRANCH_NAME")) {
31343
- task.env.push("GIT_BRANCH_NAME");
31344
- }
31345
- }
31346
- }
31347
- /**
31348
- * Paths of all generated files in the project, deduped, for use as task
31349
- * inputs so the compile cache invalidates when they change. Includes
31350
- * projen-managed `FileBase` files plus generated-once `SampleFile` /
31351
- * `SampleDir` — whose paths projen keeps private, so they are read defensively
31352
- * and skipped if that internal shape ever changes. Computed at synth time so
31353
- * files added after this component (e.g. a subclass's SampleFiles) are seen.
31354
- */
31355
- generatedFileInputs() {
31356
- const inputs = /* @__PURE__ */ new Set();
31357
- for (const component of this.project.components) {
31358
- if (component instanceof FileBase) {
31359
- inputs.add(component.path);
31360
- } else if (component instanceof SampleFile) {
31361
- const filePath = component.filePath;
31362
- if (typeof filePath === "string") {
31363
- inputs.add(filePath);
31364
- }
31365
- } else if (component instanceof SampleDir) {
31366
- const dir = component.dir;
31367
- if (typeof dir === "string") {
31368
- inputs.add(`${dir}/**`);
31369
- }
31370
- }
31371
- }
31372
- return Array.from(inputs);
31373
- }
31374
- preSynthesize() {
31375
- let nextDependsOn = this.project.deps.all.filter((d) => d.version === "workspace:*").map((d) => [d.name, ROOT_TURBO_TASK_NAME].join("#"));
31376
- if (!this.isRootProject) {
31377
- [
31378
- [this.project.preCompileTask, this.preCompileTask],
31379
- [this.project.compileTask, this.compileTask],
31380
- [this.project.postCompileTask, this.postCompileTask],
31381
- [this.project.testTask, this.testTask],
31382
- [this.project.packageTask, this.packageTask]
31383
- ].forEach(([pjTask, turboTask]) => {
31384
- if (pjTask && turboTask && pjTask.steps.length > 0) {
31385
- if (nextDependsOn.length > 0) {
31386
- turboTask.dependsOn.push(...nextDependsOn);
31387
- }
31388
- nextDependsOn = [turboTask.name];
31389
- } else {
31390
- turboTask.isActive = false;
31391
- }
31392
- });
31393
- this.buildTask.dependsOn.push(...nextDependsOn);
31394
- }
31395
- const generatedInputs = this.generatedFileInputs();
31396
- const appendGeneratedInputs = (task) => {
31397
- if (!task) {
31398
- return;
31399
- }
31400
- for (const input of generatedInputs) {
31401
- if (!task.inputs.includes(input)) {
31402
- task.inputs.push(input);
31403
- }
31404
- }
31405
- };
31406
- if (this.isRootProject) {
31407
- appendGeneratedInputs(this.buildTask);
31408
- } else {
31409
- appendGeneratedInputs(this.preCompileTask);
31410
- appendGeneratedInputs(this.compileTask);
31411
- appendGeneratedInputs(this.postCompileTask);
31412
- }
31413
- const fileName = "turbo.json";
31414
- this.project.addPackageIgnore(fileName);
31415
- new JsonFile(this.project, fileName, {
31416
- obj: {
31417
- extends: this.extends.length ? this.extends : void 0,
31418
- globalDependencies: this.isRootProject && this.globalDependencies.length ? this.globalDependencies : void 0,
31419
- globalEnv: this.isRootProject && this.globalEnv.length ? this.globalEnv : void 0,
31420
- globalPassThroughEnv: this.isRootProject && this.globalPassThroughEnv.length ? this.globalPassThroughEnv : void 0,
31421
- ui: this.isRootProject ? this.ui : void 0,
31422
- dangerouslyDisablePackageManagerCheck: this.isRootProject ? this.dangerouslyDisablePackageManagerCheck : void 0,
31423
- cacheDir: this.isRootProject ? this.cacheDir : void 0,
31424
- envMode: this.isRootProject ? this.envMode : void 0,
31425
- /**
31426
- * All tasks
31427
- */
31428
- tasks: this.tasks.filter((task) => task.isActive).reduce(
31429
- (acc, task) => {
31430
- acc[task.name] = {
31431
- ...task.taskConfig()
31432
- };
31433
- return acc;
31434
- },
31435
- {
31436
- [this.buildTask.name]: { ...this.buildTask.taskConfig() }
31437
- }
31438
- )
31439
- }
31440
- });
31441
- super.preSynthesize();
31605
+ // src/agent/bundles/turborepo.ts
31606
+ function renderCachingBullet(policy) {
31607
+ if (policy.remoteCacheEnabled && policy.awsProfileName) {
31608
+ return `- Uses remote caching (requires AWS credentials on the \`${policy.awsProfileName}\` profile)`;
31442
31609
  }
31443
- };
31444
- _TurboRepo.buildWorkflowOptions = (remoteCacheOptions) => {
31610
+ return "- Local caching only \u2014 no remote cache is configured, so no AWS credentials are required";
31611
+ }
31612
+ function buildTurborepoBundle(buildPolicy = DEFAULT_BUILD_POLICY) {
31445
31613
  return {
31446
- env: {
31447
- GIT_BRANCH_NAME: "${{ github.head_ref || github.ref_name }}"
31448
- },
31449
- permissions: {
31450
- contents: JobPermission.WRITE,
31451
- idToken: JobPermission.WRITE
31452
- },
31453
- preBuildSteps: [
31614
+ name: "turborepo",
31615
+ description: "Turborepo workspace rules and task pipeline conventions",
31616
+ appliesWhen: (project) => hasComponent(project, TurboRepo),
31617
+ rules: [
31454
31618
  {
31455
- name: "AWS Creds for SSM",
31456
- uses: "aws-actions/configure-aws-credentials@v6",
31457
- with: {
31458
- ["role-to-assume"]: remoteCacheOptions.oidcRole,
31459
- ["aws-region"]: "us-east-1",
31460
- ["role-duration-seconds"]: "900"
31461
- }
31619
+ name: "turborepo-conventions",
31620
+ description: "Turborepo build system and task pipeline conventions",
31621
+ scope: AGENT_RULE_SCOPE.FILE_PATTERN,
31622
+ filePatterns: ["turbo.json", "package.json"],
31623
+ content: [
31624
+ "# Turborepo Conventions",
31625
+ "",
31626
+ "## Build System",
31627
+ "",
31628
+ "- **Build**: `pnpm build:all` (uses Turborepo)",
31629
+ "- **Test**: `pnpm test` or `pnpm test:watch`",
31630
+ "- **Lint**: `pnpm eslint`",
31631
+ "",
31632
+ "## Task Pipeline",
31633
+ "",
31634
+ renderCachingBullet(buildPolicy),
31635
+ "- Only rebuilds changed packages",
31636
+ "- Cache key based on file hashes and dependency graph",
31637
+ "- Configured in `turbo.json`",
31638
+ "",
31639
+ "## Workspace Rules",
31640
+ "",
31641
+ "- Source files: `src/` directory",
31642
+ "- Tests: Co-located with source files (`.spec.ts` or `.test.ts`)",
31643
+ "- Exports: Use `index.ts` files for clean public APIs",
31644
+ "- Configuration: Managed by Projen (edit `.projenrc.ts` or `projenrc/*.ts`)"
31645
+ ].join("\n"),
31646
+ tags: ["workflow"]
31462
31647
  }
31463
- ]
31464
- };
31465
- };
31466
- var TurboRepo = _TurboRepo;
31467
-
31468
- // src/agent/bundles/turborepo.ts
31469
- var turborepoBundle = {
31470
- name: "turborepo",
31471
- description: "Turborepo workspace rules and task pipeline conventions",
31472
- appliesWhen: (project) => hasComponent(project, TurboRepo),
31473
- rules: [
31474
- {
31475
- name: "turborepo-conventions",
31476
- description: "Turborepo build system and task pipeline conventions",
31477
- scope: AGENT_RULE_SCOPE.FILE_PATTERN,
31478
- filePatterns: ["turbo.json", "package.json"],
31479
- content: [
31480
- "# Turborepo Conventions",
31481
- "",
31482
- "## Build System",
31483
- "",
31484
- "- **Build**: `pnpm build:all` (uses Turborepo)",
31485
- "- **Test**: `pnpm test` or `pnpm test:watch`",
31486
- "- **Lint**: `pnpm eslint`",
31487
- "",
31488
- "## Task Pipeline",
31489
- "",
31490
- "- Uses remote caching (requires AWS credentials)",
31491
- "- Only rebuilds changed packages",
31492
- "- Cache key based on file hashes and dependency graph",
31493
- "- Configured in `turbo.json`",
31494
- "",
31495
- "## Workspace Rules",
31496
- "",
31497
- "- Source files: `src/` directory",
31498
- "- Tests: Co-located with source files (`.spec.ts` or `.test.ts`)",
31499
- "- Exports: Use `index.ts` files for clean public APIs",
31500
- "- Configuration: Managed by Projen (edit `.projenrc.ts` or `projenrc/*.ts`)"
31501
- ].join("\n"),
31502
- tags: ["workflow"]
31648
+ ],
31649
+ claudePermissions: {
31650
+ allow: ["Bash(npx turbo:*)"]
31503
31651
  }
31504
- ],
31505
- claudePermissions: {
31506
- allow: ["Bash(npx turbo:*)"]
31507
- }
31508
- };
31652
+ };
31653
+ }
31654
+ var turborepoBundle = buildTurborepoBundle();
31509
31655
 
31510
31656
  // src/agent/bundles/typescript.ts
31511
31657
  var typescriptBundle = {
@@ -32720,7 +32866,7 @@ function renderPriorityRulesSection(rules) {
32720
32866
  }
32721
32867
 
32722
32868
  // src/agent/bundles/index.ts
32723
- function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS, defaultAgentTier = AGENT_MODEL.BALANCED, bundleAgentTiers = /* @__PURE__ */ new Map(), prReviewPolicy = resolvePrReviewPolicy()) {
32869
+ 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) {
32724
32870
  const tierFor = (bundle) => bundleAgentTiers.get(bundle) ?? defaultAgentTier;
32725
32871
  return [
32726
32872
  buildBaseBundle(paths),
@@ -32728,11 +32874,11 @@ function buildBuiltInBundles(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAUL
32728
32874
  typescriptBundle,
32729
32875
  vitestBundle,
32730
32876
  jestBundle,
32731
- turborepoBundle,
32877
+ buildTurborepoBundle(buildPolicy),
32732
32878
  pnpmBundle,
32733
32879
  awsCdkBundle,
32734
32880
  projenBundle,
32735
- githubWorkflowBundle,
32881
+ buildGithubWorkflowBundle(buildPolicy),
32736
32882
  slackBundle,
32737
32883
  buildMeetingAnalysisBundle(tierFor("meeting-analysis")),
32738
32884
  agendaBundle,
@@ -33817,6 +33963,13 @@ var AgentConfig = class _AgentConfig extends Component8 {
33817
33963
  * their rendered rule content reflects any consumer override.
33818
33964
  * Bundles that do not read agent paths are passed through as-is
33819
33965
  * from their default const exports.
33966
+ *
33967
+ * The build policy is auto-detected from the project's `TurboRepo`
33968
+ * component here rather than configured, so build guidance in the
33969
+ * `github-workflow` and `turborepo` rules only claims an AWS
33970
+ * credential requirement when a remote cache actually exists. The
33971
+ * getter is lazy by design — `TurboRepo` must already be attached
33972
+ * to the project when the bundles are first read.
33820
33973
  */
33821
33974
  get pathAwareBundles() {
33822
33975
  if (!this.cachedBundles) {
@@ -33825,7 +33978,8 @@ var AgentConfig = class _AgentConfig extends Component8 {
33825
33978
  resolveIssueDefaults(this.options.issueDefaults),
33826
33979
  resolveDefaultAgentTier(this.options),
33827
33980
  resolveBundleAgentTiers(this.options),
33828
- resolvePrReviewPolicy(this.options.prReviewPolicy)
33981
+ resolvePrReviewPolicy(this.options.prReviewPolicy),
33982
+ resolveBuildPolicy(this.project)
33829
33983
  );
33830
33984
  }
33831
33985
  return this.cachedBundles;
@@ -40543,6 +40697,7 @@ export {
40543
40697
  DEFAULT_API_EXTRACTOR_REPORT_FILENAME,
40544
40698
  DEFAULT_API_EXTRACTOR_REPORT_FOLDER,
40545
40699
  DEFAULT_AUDIT_REPORT_DIR,
40700
+ DEFAULT_BUILD_POLICY,
40546
40701
  DEFAULT_BUNDLE_OVERRIDES,
40547
40702
  DEFAULT_DECOMPOSITION_TEMPLATE,
40548
40703
  DEFAULT_DISPATCH_MODEL,
@@ -40663,6 +40818,7 @@ export {
40663
40818
  buildCompanyProfileBundle,
40664
40819
  buildCustomerProfileBundle,
40665
40820
  buildDocsSyncBundle,
40821
+ buildGithubWorkflowBundle,
40666
40822
  buildIndustryDiscoveryBundle,
40667
40823
  buildMaintenanceAuditBundle,
40668
40824
  buildMeetingAnalysisBundle,
@@ -40677,6 +40833,7 @@ export {
40677
40833
  buildResearchPipelineBundle,
40678
40834
  buildSoftwareProfileBundle,
40679
40835
  buildStandardsResearchBundle,
40836
+ buildTurborepoBundle,
40680
40837
  buildUnblockDependentsProcedure,
40681
40838
  bundleNameForWorkflowRule,
40682
40839
  businessModelsBundle,
@@ -40794,6 +40951,7 @@ export {
40794
40951
  resolveAgentTiers,
40795
40952
  resolveAstroProjectOutdir,
40796
40953
  resolveAwsCdkProjectOutdir,
40954
+ resolveBuildPolicy,
40797
40955
  resolveBundleAgentTiers,
40798
40956
  resolveDefaultAgentTier,
40799
40957
  resolveIssueDefaults,