@event4u/agent-config 9.15.0 → 9.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +74 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/agent-handoff.md +100 -48
- package/dist/agent-src/commands/commit.md +1 -1
- package/dist/agent-src/commands/condense.md +19 -64
- package/dist/agent-src/commands/evals/agent-handoff.json +13 -1
- package/dist/agent-src/commands/memory/promote.md +14 -1
- package/dist/agent-src/commands/mode.md +9 -5
- package/dist/agent-src/commands/optimize/deep.md +72 -11
- package/dist/agent-src/contexts/authority/commit-mechanics.md +5 -5
- package/dist/agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +1 -0
- package/dist/agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +6 -1
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +4 -1
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +27 -1
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +5 -2
- package/dist/agent-src/profiles/agency.yml +3 -3
- package/dist/agent-src/profiles/content_creator.yml +2 -2
- package/dist/agent-src/profiles/developer.yml +2 -2
- package/dist/agent-src/profiles/finance.yml +1 -1
- package/dist/agent-src/profiles/founder.yml +2 -2
- package/dist/agent-src/profiles/ops.yml +2 -2
- package/dist/agent-src/rules/architecture.md +2 -2
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/context-hygiene.md +3 -2
- package/dist/agent-src/rules/domain-safety-pii.md +1 -1
- package/dist/agent-src/rules/output-discipline.md +9 -2
- package/dist/agent-src/rules/token-budget-discipline.md +2 -2
- package/dist/agent-src/rules/user-interaction.md +3 -4
- package/dist/agent-src/skills/build-buy-partner/SKILL.md +1 -1
- package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +4 -4
- package/dist/agent-src/skills/copilot-config/SKILL.md +1 -1
- package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +7 -4
- package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
- package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
- package/dist/agent-src/skills/market-entry-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/persona-writing/SKILL.md +2 -2
- package/dist/agent-src/skills/readme-reviewer/SKILL.md +2 -1
- package/dist/agent-src/skills/rule-writing/SKILL.md +4 -2
- package/dist/agent-src/skills/skill-reviewer/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/SKILL.md +2 -2
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +3 -1
- package/dist/agent-src/skills/token-optimizer/SKILL.md +2 -2
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +6 -3
- package/dist/agent-src/templates/agent-settings.md +13 -15
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +6 -2
- package/dist/agent-src/templates/persona.md +2 -2
- package/dist/agent-src/templates/roadmaps.md +1 -1
- package/dist/agent-src/templates/rule.md +1 -1
- package/dist/agent-src/templates/skill.md +1 -1
- package/dist/cli/mcp/content.js +1 -1
- package/dist/cli/mcp/content.js.map +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-ZXXJLL5K.js → chunk-BSLUHV2F.js} +127 -1487
- package/dist/cli-delegate/{chunk-H574KOUH.js → chunk-IMSXPRVH.js} +24 -1
- package/dist/cli-delegate/chunk-VSYT7OHO.js +1382 -0
- package/dist/cli-delegate/chunk-XGA2NTU4.js +3727 -0
- package/dist/cli-delegate/cmd_conformance.js +4 -3
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +3 -2
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +3194 -0
- package/dist/cli-delegate/cmd_memory_get.js +35 -3754
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +3 -3
- package/dist/cli-delegate/cmd_upgrade.js +3 -3
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +3 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +31 -31
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +602 -507
- package/dist/install/install.mjs +23 -0
- package/dist/mcp/awesome-mcp-servers.row.md +1 -1
- package/dist/mcp/mcp-cloudflare-catalogue.json +2 -2
- package/dist/mcp/registry-manifest.json +4 -4
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +25 -3
- package/dist/scripts/_lib/agent_src.js +39 -6
- package/dist/scripts/_lib/agent_src.js.map +1 -1
- package/dist/server/routes/workspace.js +1 -1
- package/dist/server/routes/workspace.js.map +1 -1
- package/dist/server/schemas/settings.js +13 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/userMd/utils.js +1 -1
- package/dist/shared/userMd/utils.js.map +1 -1
- package/dist/ui/assets/index-DmR0mbk4.js +26 -0
- package/dist/ui/assets/index-DmR0mbk4.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/guidelines/abstraction-thresholds.md +53 -0
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -1
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +7 -0
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +2 -2
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +5 -3
- package/docs/guidelines/agent-infra/size-and-scope.md +8 -2
- package/docs/guidelines/component-oriented-and-oop-development.md +11 -6
- package/docs/guidelines/docs/readme-size-and-splitting.md +5 -2
- package/llms.txt +1 -0
- package/package.json +4 -4
- package/src/config/agent-settings.template.yml +25 -1
- package/src/config/budget-routing.json +7 -0
- package/src/config/evaluator-budgets.json +8 -11
- package/src/config/gate-coverage.yml +30 -0
- package/src/scripts/_cli/cmd_handoff.ts +405 -0
- package/src/scripts/_cli/handoff_generate.ts +453 -0
- package/src/scripts/_cli/handoff_sessions.ts +435 -0
- package/src/scripts/_dispatch.bash +9 -0
- package/src/scripts/_lib/agent_src.ts +36 -6
- package/src/scripts/_lib/release_material.ts +156 -0
- package/src/scripts/_lib/tier_budget_routing.ts +154 -28
- package/src/scripts/anchor_scoring_run.ts +1 -1
- package/src/scripts/bench_persona_placebo.ts +1 -1
- package/src/scripts/bench_quality_run.ts +1 -1
- package/src/scripts/check_cli_registry_budget_sync.ts +168 -0
- package/src/scripts/check_condensation.ts +2 -1
- package/src/scripts/check_finding_dispositions.ts +278 -0
- package/src/scripts/check_gate_coverage.ts +1 -1
- package/src/scripts/check_pack_size.ts +4 -3
- package/src/scripts/check_references.ts +1 -0
- package/src/scripts/check_release_highlights.ts +300 -0
- package/src/scripts/check_release_pr_shape.ts +4 -0
- package/src/scripts/check_release_surface_equality.ts +132 -0
- package/src/scripts/check_reply_consistency.ts +187 -138
- package/src/scripts/check_token_quality_golden.ts +1 -1
- package/src/scripts/cost/budget.mjs +18 -4
- package/src/scripts/handoff_context_hook.ts +148 -0
- package/src/scripts/hook_manifest.yaml +17 -7
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/lint_abstraction_thresholds.ts +325 -0
- package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
- package/src/scripts/lint_profile_personas.ts +153 -0
- package/src/scripts/lint_token_budget_discipline.ts +148 -0
- package/src/scripts/release.ts +264 -37
- package/src/scripts/release_drill.ts +491 -0
- package/src/scripts/routing_doctor.ts +76 -1
- package/src/scripts/schemas/command.schema.json +33 -0
- package/src/scripts/self_review_gate.ts +41 -3
- package/src/scripts/skill_linter.ts +3 -1
- package/src/scripts/smoke/router.sh +1 -1
- package/src/scripts/update_counts.ts +60 -12
- package/src/server/schemas/settings.ts +23 -0
- package/src/shared/userMd/utils.ts +1 -1
- package/dist/ui/assets/index-DJY_vQVn.js +0 -40
- package/dist/ui/assets/index-DJY_vQVn.js.map +0 -1
- package/dist/cli-delegate/{chunk-XX6TOPD7.js → chunk-WR6GTRHJ.js} +3 -3
package/src/scripts/release.ts
CHANGED
|
@@ -118,6 +118,12 @@ import {
|
|
|
118
118
|
perform_split,
|
|
119
119
|
plan_split,
|
|
120
120
|
} from './_lib/changelog_eras.js';
|
|
121
|
+
import {
|
|
122
|
+
extract_changelog_section,
|
|
123
|
+
pr_body_from_section,
|
|
124
|
+
release_notes_from_section,
|
|
125
|
+
tag_message_from_section,
|
|
126
|
+
} from './_lib/release_material.js';
|
|
121
127
|
|
|
122
128
|
// `__doc__.splitlines()[0]` in `_parse_args` — the argparse description. Kept
|
|
123
129
|
// as a referenceable constant so the first docstring line is preserved exactly.
|
|
@@ -517,6 +523,22 @@ interface RunResult {
|
|
|
517
523
|
* stderr so release preflight failures are diagnosable without re-running with
|
|
518
524
|
* a debugger.
|
|
519
525
|
*/
|
|
526
|
+
/**
|
|
527
|
+
* Drill/test seam — when set, every external command run() would spawn is
|
|
528
|
+
* answered by this function instead. The check/die/CalledProcessError
|
|
529
|
+
* semantics of run() still apply to the simulated result, so the drill
|
|
530
|
+
* exercises the REAL error paths of the orchestration (release_drill.ts).
|
|
531
|
+
*/
|
|
532
|
+
type ExecOverride = (args: readonly string[]) => {
|
|
533
|
+
status: number;
|
|
534
|
+
stdout: string;
|
|
535
|
+
stderr: string;
|
|
536
|
+
};
|
|
537
|
+
let _exec_override: ExecOverride | null = null;
|
|
538
|
+
function _set_exec_override(fn: ExecOverride | null): void {
|
|
539
|
+
_exec_override = fn;
|
|
540
|
+
}
|
|
541
|
+
|
|
520
542
|
function run(
|
|
521
543
|
args: readonly string[],
|
|
522
544
|
opts: { check?: boolean; capture?: boolean; cwd?: string | null } = {},
|
|
@@ -526,13 +548,15 @@ function run(
|
|
|
526
548
|
const cwd = opts.cwd ?? REPO_ROOT;
|
|
527
549
|
|
|
528
550
|
const [cmd, ...rest] = args;
|
|
529
|
-
const res =
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
551
|
+
const res = _exec_override
|
|
552
|
+
? { ..._exec_override(args), error: undefined as Error | undefined }
|
|
553
|
+
: spawnSync(cmd as string, rest, {
|
|
554
|
+
cwd,
|
|
555
|
+
encoding: 'utf-8',
|
|
556
|
+
// capture_output=True → pipe; else inherit so child writes straight to
|
|
557
|
+
// this process's stdout/stderr (text mode, matching subprocess text=True).
|
|
558
|
+
stdio: capture ? ['ignore', 'pipe', 'pipe'] : ['inherit', 'inherit', 'inherit'],
|
|
559
|
+
});
|
|
536
560
|
|
|
537
561
|
if (res.error) {
|
|
538
562
|
// FileNotFoundError analogue (ENOENT) and other spawn failures — Python
|
|
@@ -619,6 +643,31 @@ export function _failed_checks_report(names: readonly string[]): string {
|
|
|
619
643
|
'empty (docs/contracts/release-pr-gating.md § Mid-release fixes).',
|
|
620
644
|
);
|
|
621
645
|
}
|
|
646
|
+
if (names.includes('PR body equals CHANGELOG entry')) {
|
|
647
|
+
lines.push(
|
|
648
|
+
'',
|
|
649
|
+
'Surface equality: the PR body drifted from the CHANGELOG entry. A',
|
|
650
|
+
'resume run refreshes it from the branch head (release-truth Phase 1);',
|
|
651
|
+
'never hand-edit the PR body away from the changelog section.',
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
if (names.includes('Curated highlights plausible against the span')) {
|
|
655
|
+
lines.push(
|
|
656
|
+
'',
|
|
657
|
+
'Highlights: a curated `_none_` field contradicts the release span.',
|
|
658
|
+
'Fill the curated head in CHANGELOG.md on the release branch — the',
|
|
659
|
+
'gate output lists the deriving commits (release-truth Phase 2).',
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
if (names.includes('Blocking review findings dispositioned')) {
|
|
663
|
+
lines.push(
|
|
664
|
+
'',
|
|
665
|
+
'Dispositions: a blocking/high self-review finding has no committed',
|
|
666
|
+
'disposition. Ingest + adjudicate it in',
|
|
667
|
+
'agents/evidence/release-findings/<version>.json (release-truth Phase 3):',
|
|
668
|
+
' ./scripts-run src/scripts/check_finding_dispositions --ingest <findings.json>',
|
|
669
|
+
);
|
|
670
|
+
}
|
|
622
671
|
lines.push('', 'After fixing, resume with: task release -- --resume --yes', '');
|
|
623
672
|
return lines.join('\n');
|
|
624
673
|
}
|
|
@@ -634,22 +683,25 @@ export function _failed_checks_report(names: readonly string[]): string {
|
|
|
634
683
|
* A short grace period gives GitHub time to register workflow runs on a
|
|
635
684
|
* freshly-pushed branch.
|
|
636
685
|
*/
|
|
637
|
-
function watch_pr_checks(): void {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
//
|
|
686
|
+
function watch_pr_checks(branch: string): void {
|
|
687
|
+
if (_exec_override === null) {
|
|
688
|
+
// time.sleep(5) — blocking grace period for GitHub to register runs
|
|
689
|
+
// on a freshly-pushed branch. Skipped under the drill seam: there is
|
|
690
|
+
// no GitHub to wait for, and 5s per simulated wait adds up.
|
|
691
|
+
const until = Date.now() + 5000;
|
|
692
|
+
while (Date.now() < until) {
|
|
693
|
+
// busy-wait stand-in for time.sleep(5) without an event-loop yield.
|
|
694
|
+
}
|
|
642
695
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
696
|
+
// The branch is passed explicitly — `gh` inferring the PR from HEAD
|
|
697
|
+
// resolved to `main` mid-run once (2026-08-03, 9.16.0 resume) and the
|
|
698
|
+
// whole release died on "no pull requests found for branch main".
|
|
699
|
+
const proc = run(['gh', 'pr', 'checks', branch, '--watch'], {
|
|
700
|
+
check: false,
|
|
701
|
+
capture: true,
|
|
647
702
|
});
|
|
648
|
-
if (proc.error) {
|
|
649
|
-
throw proc.error;
|
|
650
|
-
}
|
|
651
703
|
const output = ((proc.stdout || '') + (proc.stderr || '')).trim();
|
|
652
|
-
const returncode = proc.
|
|
704
|
+
const returncode = proc.returncode;
|
|
653
705
|
if (returncode === 0) {
|
|
654
706
|
if (output) {
|
|
655
707
|
process.stdout.write(output + '\n');
|
|
@@ -667,10 +719,9 @@ function watch_pr_checks(): void {
|
|
|
667
719
|
if (output) {
|
|
668
720
|
process.stderr.write(output + '\n');
|
|
669
721
|
}
|
|
670
|
-
const summary =
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
722
|
+
const summary = run(['gh', 'pr', 'checks', branch, '--json', 'name,bucket'], {
|
|
723
|
+
check: false,
|
|
724
|
+
capture: true,
|
|
674
725
|
});
|
|
675
726
|
const report = _failed_checks_report(_failed_check_names(summary.stdout ?? ''));
|
|
676
727
|
if (report) {
|
|
@@ -679,6 +730,139 @@ function watch_pr_checks(): void {
|
|
|
679
730
|
die(`PR checks failed (exit ${returncode})`);
|
|
680
731
|
}
|
|
681
732
|
|
|
733
|
+
/**
|
|
734
|
+
* Push the release branch, integrating a remote that moved under us.
|
|
735
|
+
*
|
|
736
|
+
* A GitHub-side "Update branch" click (or a concurrent resume run) advances
|
|
737
|
+
* `origin/<branch>` while the local ref stays behind; a bare `git push` is
|
|
738
|
+
* then rejected with "fetch first" and the whole release run dies (measured
|
|
739
|
+
* 2026-08-03, 9.15.0). Same-branch commits are never ours to drop, so the
|
|
740
|
+
* recovery is merge-and-retry, never force-push.
|
|
741
|
+
*/
|
|
742
|
+
function push_release_branch(branch: string): void {
|
|
743
|
+
const first = run(['git', 'push', '-u', REMOTE, branch], { check: false });
|
|
744
|
+
if (first.returncode === 0) {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
process.stdout.write(`↻ ${REMOTE}/${branch} moved — integrating and retrying push\n`);
|
|
748
|
+
run(['git', 'fetch', REMOTE, branch]);
|
|
749
|
+
run(['git', 'merge', '--no-edit', `${REMOTE}/${branch}`]);
|
|
750
|
+
run(['git', 'push', '-u', REMOTE, branch]);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/** mergeStateStatus of the branch's PR ('' when the probe fails). */
|
|
754
|
+
function _pr_merge_state(branch: string): string {
|
|
755
|
+
const r = run(
|
|
756
|
+
['gh', 'pr', 'view', branch, '--json', 'mergeStateStatus', '--jq', '.mergeStateStatus'],
|
|
757
|
+
{
|
|
758
|
+
check: false,
|
|
759
|
+
capture: true,
|
|
760
|
+
},
|
|
761
|
+
);
|
|
762
|
+
return r.returncode === 0 ? r.stdout.trim() : '';
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Bound on update-and-retry rounds in merge_release_pr. Three is deliberate:
|
|
767
|
+
* main receiving unrelated merges three times inside one checks-wait window
|
|
768
|
+
* means the queue is hot and a human should decide, not a loop.
|
|
769
|
+
*/
|
|
770
|
+
const _MERGE_UPDATE_ROUNDS = 3;
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Merge the release PR, absorbing a moving base branch.
|
|
774
|
+
*
|
|
775
|
+
* Branch protection requires the head to be up to date with `main`, and
|
|
776
|
+
* `main` moves constantly (dependabot, unrelated merges) — so between the
|
|
777
|
+
* checks-wait and the merge the PR routinely flips to BEHIND and a bare
|
|
778
|
+
* `gh pr merge` dies with "the head branch is not up to date with the base
|
|
779
|
+
* branch" (measured 2026-08-03, 9.16.0). Recovery is mechanical and safe by
|
|
780
|
+
* construction: merging `origin/main` into `release/X.Y.Z` makes the fix
|
|
781
|
+
* files identical on both sides, so the release-PR diff stays inside the
|
|
782
|
+
* version-bump allowlist (docs/contracts/release-pr-gating.md § Mid-release
|
|
783
|
+
* fixes) — do it here instead of aborting the run.
|
|
784
|
+
*/
|
|
785
|
+
/**
|
|
786
|
+
* Re-derive the release-PR body from the CHANGELOG section at the current
|
|
787
|
+
* release-branch head and push it to the PR (release-truth Phase 1: one
|
|
788
|
+
* final source — late commits and maintainer edits on the branch must reach
|
|
789
|
+
* the PR body instead of leaving it frozen at plan time). Best-effort sync
|
|
790
|
+
* of the local branch first; a no-op when the derived body is unchanged.
|
|
791
|
+
*/
|
|
792
|
+
function _target_from_branch(branch: string): string {
|
|
793
|
+
return branch.replace(/^release\//u, '');
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function _refresh_pr_body_from_head(branch: string, target: string): void {
|
|
797
|
+
run(['git', 'pull', '--ff-only', REMOTE, branch], { check: false });
|
|
798
|
+
const changelog = fs.readFileSync(CHANGELOG, 'utf-8');
|
|
799
|
+
const section = extract_changelog_section(changelog, target);
|
|
800
|
+
if (!section) {
|
|
801
|
+
die(`CHANGELOG.md at ${branch} head carries no section for ${target} — cannot derive PR body`);
|
|
802
|
+
}
|
|
803
|
+
const capped = _cap_body(section!.body, GH_PR_BODY_LIMIT - 200, '`CHANGELOG.md` in this PR');
|
|
804
|
+
const body = pr_body_from_section(capped, target);
|
|
805
|
+
const live = run(['gh', 'pr', 'view', branch, '--json', 'body', '-q', '.body'], {
|
|
806
|
+
check: false,
|
|
807
|
+
capture: true,
|
|
808
|
+
});
|
|
809
|
+
if (live.returncode === 0 && live.stdout.replace(/\r\n/gu, '\n').trim() === body.trim()) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
run(['gh', 'pr', 'edit', branch, '--body', body]);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function merge_release_pr(branch: string, wait_for_checks: boolean): void {
|
|
816
|
+
if (git(['rev-parse', '--abbrev-ref', 'HEAD'], { capture: true }) !== branch) {
|
|
817
|
+
run(['git', 'checkout', branch]);
|
|
818
|
+
}
|
|
819
|
+
for (let round = 0; round <= _MERGE_UPDATE_ROUNDS; round++) {
|
|
820
|
+
// One final generation step at the final head: every merge attempt
|
|
821
|
+
// first re-derives the PR body from the branch-head CHANGELOG, so
|
|
822
|
+
// update rounds and maintainer edits can never desynchronize it.
|
|
823
|
+
_refresh_pr_body_from_head(branch, _target_from_branch(branch));
|
|
824
|
+
if (_pr_merge_state(branch) !== 'BEHIND') {
|
|
825
|
+
const merged = run(['gh', 'pr', 'merge', branch, '--merge', '--delete-branch'], {
|
|
826
|
+
check: false,
|
|
827
|
+
});
|
|
828
|
+
if (merged.returncode === 0) {
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
if (_pr_merge_state(branch) !== 'BEHIND') {
|
|
832
|
+
// Not the moving-base failure — surface it unchanged.
|
|
833
|
+
throw new CalledProcessError(merged.returncode, [
|
|
834
|
+
'gh',
|
|
835
|
+
'pr',
|
|
836
|
+
'merge',
|
|
837
|
+
branch,
|
|
838
|
+
'--merge',
|
|
839
|
+
'--delete-branch',
|
|
840
|
+
]);
|
|
841
|
+
}
|
|
842
|
+
// BEHIND appeared between the probe and the merge — fall through
|
|
843
|
+
// to the update below.
|
|
844
|
+
}
|
|
845
|
+
if (round === _MERGE_UPDATE_ROUNDS) {
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
process.stdout.write(
|
|
849
|
+
`↻ head behind ${MAIN_BRANCH} (update round ${round + 1}/${_MERGE_UPDATE_ROUNDS}) — ` +
|
|
850
|
+
`merging ${REMOTE}/${MAIN_BRANCH} into ${branch} and re-running checks\n`,
|
|
851
|
+
);
|
|
852
|
+
run(['git', 'fetch', REMOTE, MAIN_BRANCH]);
|
|
853
|
+
run(['git', 'merge', '--no-edit', `${REMOTE}/${MAIN_BRANCH}`]);
|
|
854
|
+
push_release_branch(branch);
|
|
855
|
+
if (wait_for_checks) {
|
|
856
|
+
watch_pr_checks(branch);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
die(
|
|
860
|
+
`release PR is still behind ${MAIN_BRANCH} after ${_MERGE_UPDATE_ROUNDS} update round(s) — ` +
|
|
861
|
+
`${MAIN_BRANCH} is moving faster than the checks complete. ` +
|
|
862
|
+
'Re-run when the queue quiets down: task release -- --resume --yes',
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
|
|
682
866
|
function have(bin: string): boolean {
|
|
683
867
|
const res = spawnSync('which', [bin], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
684
868
|
if (res.error) {
|
|
@@ -1596,27 +1780,37 @@ function execute(
|
|
|
1596
1780
|
_step(4, total, 'PR already merged — skip push');
|
|
1597
1781
|
} else {
|
|
1598
1782
|
// `git push -u` is naturally idempotent — it prints "Everything
|
|
1599
|
-
// up-to-date" when remote already matches.
|
|
1783
|
+
// up-to-date" when remote already matches. push_release_branch
|
|
1784
|
+
// additionally absorbs a remote that moved under us.
|
|
1600
1785
|
_step(4, total, `Push ${branch} to ${REMOTE}`);
|
|
1601
|
-
|
|
1786
|
+
push_release_branch(branch);
|
|
1602
1787
|
}
|
|
1603
1788
|
|
|
1604
1789
|
// ─── 5. PR ──────────────────────────────────────────────────────────────
|
|
1605
1790
|
if (pr_merged) {
|
|
1606
1791
|
_step(5, total, `PR #${pr_info!['number']} already merged — skip`);
|
|
1607
1792
|
} else if (resume && pr_state === 'OPEN') {
|
|
1608
|
-
|
|
1793
|
+
// One final source: a resumed run re-derives the body from the
|
|
1794
|
+
// branch-head CHANGELOG instead of leaving the plan-time snapshot —
|
|
1795
|
+
// this was the 9.14.0 test-count desync (resume re-rendered from a
|
|
1796
|
+
// later HEAD while the changelog kept the branch's entry).
|
|
1797
|
+
_step(5, total, `PR already open: ${pr_info!['url']} — refresh body from branch head`);
|
|
1798
|
+
_refresh_pr_body_from_head(branch, plan.target);
|
|
1609
1799
|
} else {
|
|
1610
1800
|
_step(5, total, 'Open pull request');
|
|
1801
|
+
// Derive the PR body from the CHANGELOG section step 2 just wrote —
|
|
1802
|
+
// the file is the single source; the in-memory plan is only its
|
|
1803
|
+
// author (release-truth Phase 1).
|
|
1804
|
+
const section = extract_changelog_section(fs.readFileSync(CHANGELOG, 'utf-8'), plan.target);
|
|
1805
|
+
if (!section) {
|
|
1806
|
+
die(`CHANGELOG.md carries no section for ${plan.target} — cannot derive PR body`);
|
|
1807
|
+
}
|
|
1611
1808
|
const pr_changelog = _cap_body(
|
|
1612
|
-
|
|
1809
|
+
section!.body,
|
|
1613
1810
|
GH_PR_BODY_LIMIT - 200, // leave room for the prefix + footer
|
|
1614
1811
|
'`CHANGELOG.md` in this PR',
|
|
1615
1812
|
);
|
|
1616
|
-
const pr_body =
|
|
1617
|
-
`Release ${plan.target}.\n\n` +
|
|
1618
|
-
`${pr_changelog}\n\n` +
|
|
1619
|
-
'Created by `scripts/release.py`.';
|
|
1813
|
+
const pr_body = pr_body_from_section(pr_changelog, plan.target);
|
|
1620
1814
|
run([
|
|
1621
1815
|
'gh',
|
|
1622
1816
|
'pr',
|
|
@@ -1637,7 +1831,7 @@ function execute(
|
|
|
1637
1831
|
_step(6, total, 'PR already merged — skip checks wait');
|
|
1638
1832
|
} else if (wait_for_checks) {
|
|
1639
1833
|
_step(6, total, 'Wait for PR checks');
|
|
1640
|
-
watch_pr_checks();
|
|
1834
|
+
watch_pr_checks(branch);
|
|
1641
1835
|
} else {
|
|
1642
1836
|
_step(6, total, 'Skip waiting for checks (--no-wait)');
|
|
1643
1837
|
}
|
|
@@ -1647,7 +1841,7 @@ function execute(
|
|
|
1647
1841
|
_step(7, total, `PR #${pr_info!['number']} already merged — skip`);
|
|
1648
1842
|
} else {
|
|
1649
1843
|
_step(7, total, 'Merge pull request (merge commit) and delete branch');
|
|
1650
|
-
|
|
1844
|
+
merge_release_pr(branch, wait_for_checks);
|
|
1651
1845
|
}
|
|
1652
1846
|
|
|
1653
1847
|
// ─── 8. tag main + push tag ─────────────────────────────────────────────
|
|
@@ -1667,8 +1861,21 @@ function execute(
|
|
|
1667
1861
|
run(['git', 'push', REMOTE, plan.target]);
|
|
1668
1862
|
}
|
|
1669
1863
|
} else {
|
|
1670
|
-
|
|
1671
|
-
|
|
1864
|
+
// Sequencing is load-bearing (release-truth Phase 1, council
|
|
1865
|
+
// 2026-08-03): merge FIRST (step 7), pull main (above), THEN derive
|
|
1866
|
+
// the tag message from the MERGED changelog — tagging before the
|
|
1867
|
+
// merge would read a section that does not exist yet. The annotated
|
|
1868
|
+
// tag replaces the previous lightweight one so tag metadata is a
|
|
1869
|
+
// fourth surface carrying the same single-source content.
|
|
1870
|
+
_step(8, total, `Tag merge commit (annotated, from merged CHANGELOG) and push ${plan.target}`);
|
|
1871
|
+
const merged = extract_changelog_section(fs.readFileSync(CHANGELOG, 'utf-8'), plan.target);
|
|
1872
|
+
if (!merged) {
|
|
1873
|
+
die(
|
|
1874
|
+
`CHANGELOG.md on ${MAIN_BRANCH} carries no section for ${plan.target} — ` +
|
|
1875
|
+
'refusing to tag a release whose changelog entry is missing',
|
|
1876
|
+
);
|
|
1877
|
+
}
|
|
1878
|
+
run(['git', 'tag', '-a', plan.target, '-m', tag_message_from_section(merged!.body, plan.target)]);
|
|
1672
1879
|
run(['git', 'push', REMOTE, plan.target]);
|
|
1673
1880
|
}
|
|
1674
1881
|
|
|
@@ -1683,8 +1890,19 @@ function execute(
|
|
|
1683
1890
|
? 'Create GitHub Release (tag push under GITHUB_TOKEN triggers nothing — dispatching next)'
|
|
1684
1891
|
: 'Create GitHub Release (triggers publish-npm on the tag)',
|
|
1685
1892
|
);
|
|
1893
|
+
// One final source: the notes come from the CHANGELOG as recorded AT
|
|
1894
|
+
// THE TAG — never from the plan-time render (whose scope can differ
|
|
1895
|
+
// from the merged content; that was the 9.14.0 desync).
|
|
1896
|
+
const tagged_changelog = git(['show', `${plan.target}:CHANGELOG.md`], { capture: true });
|
|
1897
|
+
const tagged_section = extract_changelog_section(tagged_changelog, plan.target);
|
|
1898
|
+
if (!tagged_section) {
|
|
1899
|
+
die(
|
|
1900
|
+
`CHANGELOG.md at tag ${plan.target} carries no section for ${plan.target} — ` +
|
|
1901
|
+
'refusing to publish release notes from a different source',
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1686
1904
|
const notes = _cap_body(
|
|
1687
|
-
|
|
1905
|
+
release_notes_from_section(tagged_section!.body, plan.target),
|
|
1688
1906
|
GH_RELEASE_NOTES_LIMIT,
|
|
1689
1907
|
'`CHANGELOG.md`',
|
|
1690
1908
|
);
|
|
@@ -2260,5 +2478,14 @@ export {
|
|
|
2260
2478
|
SEMVER_RE,
|
|
2261
2479
|
_RELEASE_BRANCH_RE,
|
|
2262
2480
|
MODULE_DOC_FIRST_LINE,
|
|
2481
|
+
// Drill seam (release_drill.ts) — the step machinery under a simulated
|
|
2482
|
+
// git/gh world, so orchestration bugs surface in vitest, not mid-release.
|
|
2483
|
+
execute,
|
|
2484
|
+
_set_exec_override,
|
|
2485
|
+
push_release_branch,
|
|
2486
|
+
merge_release_pr,
|
|
2487
|
+
watch_pr_checks,
|
|
2488
|
+
_pr_merge_state,
|
|
2489
|
+
_MERGE_UPDATE_ROUNDS,
|
|
2263
2490
|
};
|
|
2264
2491
|
export type { Args };
|