@opengsd/gsd-core 1.19.0 → 1.20.1
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/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-code-fixer.compact.md +5 -5
- package/agents/gsd-code-fixer.md +9 -4
- package/agents/gsd-code-reviewer.compact.md +49 -8
- package/agents/gsd-code-reviewer.md +53 -14
- package/agents/gsd-executor.md +8 -4
- package/bin/install.js +36 -40
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/new-project.md +2 -2
- package/commands/gsd/update.md +5 -2
- package/gsd-core/bin/gsd-tools.cjs +177 -93
- package/gsd-core/bin/lib/capability-lifecycle.cjs +1 -12
- package/gsd-core/bin/lib/capability-registry.cjs +284 -93
- package/gsd-core/bin/lib/capability-validator.cjs +15 -0
- package/gsd-core/bin/lib/commands.cjs +46 -17
- package/gsd-core/bin/lib/config-loader.cjs +3 -3
- package/gsd-core/bin/lib/core-utils.cjs +11 -0
- package/gsd-core/bin/lib/frontmatter.cjs +21 -3
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
- package/gsd-core/bin/lib/gate-context-drift.cjs +1 -1
- package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
- package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
- package/gsd-core/bin/lib/git-info-exclude.cjs +1 -6
- package/gsd-core/bin/lib/gsd-tool-dir.cjs +134 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +13 -9
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -3
- package/gsd-core/bin/lib/init.cjs +181 -68
- package/gsd-core/bin/lib/install-engine.cjs +9 -9
- package/gsd-core/bin/lib/install-manifest-record.cjs +86 -0
- package/gsd-core/bin/lib/install-profiles.cjs +4 -2
- package/gsd-core/bin/lib/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +34 -1
- package/gsd-core/bin/lib/markdown-table.cjs +23 -0
- package/gsd-core/bin/lib/mcp-server.cjs +8 -5
- package/gsd-core/bin/lib/milestone.cjs +18 -50
- package/gsd-core/bin/lib/phase-entry-grammar.cjs +119 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
- package/gsd-core/bin/lib/phase-id.cjs +627 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -74
- package/gsd-core/bin/lib/phase-locator.cjs +328 -19
- package/gsd-core/bin/lib/phase.cjs +3039 -666
- package/gsd-core/bin/lib/planning-document.cjs +26 -5
- package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
- package/gsd-core/bin/lib/planning-snapshot.cjs +12 -7
- package/gsd-core/bin/lib/planning-workspace.cjs +3 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +23 -2
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +759 -144
- package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +8 -5
- package/gsd-core/bin/lib/roadmap.cjs +358 -106
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -130
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +6 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +36 -9
- package/gsd-core/bin/lib/smart-entry.cjs +22 -11
- package/gsd-core/bin/lib/state-contract.cjs +11 -7
- package/gsd-core/bin/lib/state-document.cjs +332 -23
- package/gsd-core/bin/lib/state-transition.cjs +51 -15
- package/gsd-core/bin/lib/state.cjs +499 -180
- package/gsd-core/bin/lib/template.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +3 -2
- package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
- package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
- package/gsd-core/bin/lib/verify.cjs +2 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +27 -21
- package/gsd-core/bin/lib/worktree-base-ref.cjs +32 -2
- package/gsd-core/bin/verify-reapply-patches.cjs +411 -10
- package/gsd-core/references/cicd-review-patterns.md +45 -0
- package/gsd-core/references/necessity-review.md +48 -0
- package/gsd-core/workflows/code-review-fix.md +46 -5
- package/gsd-core/workflows/code-review.md +3 -0
- package/gsd-core/workflows/execute-phase.md +1 -1
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
- package/gsd-core/workflows/plan-phase.md +1 -1
- package/gsd-core/workflows/pr-branch.md +2 -0
- package/gsd-core/workflows/reapply-patches.md +104 -0
- package/gsd-core/workflows/review.md +54 -141
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +57 -9
- package/gsd-core/workflows/ship.md +12 -0
- package/hooks/dist/gsd-agent-isolation-guard.js +1 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +1 -1
- package/hooks/dist/gsd-graphify-update.sh +6 -0
- package/hooks/dist/gsd-statusline.js +3 -2
- package/hooks/dist/gsd-windsurf-pre-write.js +4 -4
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/gsd-worktree-path-guard.js +4 -4
- package/hooks/dist/lib/git-probe.js +16 -0
- package/hooks/dist/lib/isolation-sentinel.js +77 -2
- package/hooks/gsd-agent-isolation-guard.js +1 -1
- package/hooks/gsd-cursor-subagent-start.js +1 -1
- package/hooks/gsd-graphify-update.sh +6 -0
- package/hooks/gsd-statusline.js +3 -2
- package/hooks/gsd-windsurf-pre-write.js +4 -4
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/gsd-worktree-path-guard.js +4 -4
- package/hooks/lib/git-probe.js +16 -0
- package/hooks/lib/isolation-sentinel.js +77 -2
- package/package.json +3 -3
- package/pi/package-agents/gsd-code-fixer.md +9 -4
- package/pi/package-agents/gsd-code-reviewer.md +53 -14
- package/pi/package-agents/gsd-executor.md +8 -4
- package/pi/package-agents/manifest.json +3 -3
- package/pi/package-skills/gsd-new-milestone/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-project/SKILL.md +1 -1
- package/pi/package-skills/gsd-update/SKILL.md +4 -1
- package/scripts/changeset/lint.cjs +28 -3
- package/scripts/ci-next-health.cjs +246 -23
- package/scripts/docs-guard-registry.cjs +2 -0
- package/scripts/gen-features.cjs +14 -2
- package/scripts/lib/drift-scan.cjs +27 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +10 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +15 -0
- package/scripts/lint-phase-enumeration-drift.cjs +8 -4
- package/scripts/lint-roadmap-progress-drift.cjs +421 -0
- package/scripts/merge-commit-pr.cjs +186 -0
- package/scripts/mutation-matrix.cjs +23 -1
- package/scripts/release-ancestry.cjs +86 -0
- package/scripts/release-pipeline-pr.cjs +92 -0
- package/scripts/require-issue-link-policy.cjs +18 -9
- package/scripts/sync-next-version.cjs +48 -7
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-new-project/SKILL.md +1 -1
- package/skills/gsd-update/SKILL.md +5 -2
- package/vscode/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Release ancestry check (#5894): `main` must be an ancestor of `next`.
|
|
5
|
+
*
|
|
6
|
+
* auto-backmerge.yml keeps that true by landing a merge commit of main into
|
|
7
|
+
* next after every release. When the back-merge is squashed (#5730), main and
|
|
8
|
+
* next stop sharing history and the next release -> main merge conflicts in
|
|
9
|
+
* every version field. release.yml runs this before cutting or finalizing a
|
|
10
|
+
* release so the break surfaces there, with the fix, instead of as a 91-file
|
|
11
|
+
* conflict on the merge-back PR.
|
|
12
|
+
*
|
|
13
|
+
* Usage: node scripts/release-ancestry.cjs [--ancestor origin/main] [--descendant origin/next]
|
|
14
|
+
* Exits 0 when the ancestor is reachable from the descendant, else 1.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { execFileSync } = require('node:child_process');
|
|
18
|
+
const { runMain } = require('./lib/cli-exit.cjs');
|
|
19
|
+
|
|
20
|
+
const ANCESTRY_VERDICT = Object.freeze({
|
|
21
|
+
ANCESTOR: 'ancestor',
|
|
22
|
+
DIVERGED: 'diverged',
|
|
23
|
+
UNKNOWN: 'unknown',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const GIT_TIMEOUT_MS = 15000;
|
|
27
|
+
const REF_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
28
|
+
|
|
29
|
+
/** Default git runner: returns { status } for `git <args>`. */
|
|
30
|
+
function defaultGit(args) {
|
|
31
|
+
try {
|
|
32
|
+
execFileSync('git', args, { stdio: 'ignore', timeout: GIT_TIMEOUT_MS });
|
|
33
|
+
return { status: 0 };
|
|
34
|
+
} catch (err) {
|
|
35
|
+
return { status: typeof err.status === 'number' ? err.status : null };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* `git merge-base --is-ancestor` exits 0 (ancestor), 1 (not), or another code
|
|
41
|
+
* (an error, e.g. an unknown ref). Only 0 and 1 are verdicts; anything else is
|
|
42
|
+
* UNKNOWN, and the CLI treats UNKNOWN as a failure.
|
|
43
|
+
*/
|
|
44
|
+
function checkAncestry({ ancestor, descendant, git = defaultGit }) {
|
|
45
|
+
const { status } = git(['merge-base', '--is-ancestor', ancestor, descendant]);
|
|
46
|
+
if (status === 0) return ANCESTRY_VERDICT.ANCESTOR;
|
|
47
|
+
if (status === 1) return ANCESTRY_VERDICT.DIVERGED;
|
|
48
|
+
return ANCESTRY_VERDICT.UNKNOWN;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseArgs(argv) {
|
|
52
|
+
const out = { ancestor: 'origin/main', descendant: 'origin/next' };
|
|
53
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
54
|
+
const flag = argv[i];
|
|
55
|
+
const value = argv[i + 1];
|
|
56
|
+
if ((flag === '--ancestor' || flag === '--descendant') && typeof value === 'string' && REF_RE.test(value)) {
|
|
57
|
+
out[flag.slice(2)] = value;
|
|
58
|
+
i += 1;
|
|
59
|
+
} else {
|
|
60
|
+
return { error: `unexpected argument: ${flag}` };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function main(argv = process.argv.slice(2), { git = defaultGit, stdout = process.stdout, stderr = process.stderr } = {}) {
|
|
67
|
+
const args = parseArgs(argv);
|
|
68
|
+
if (args.error) {
|
|
69
|
+
stderr.write(`::error::${args.error}\n`);
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
const verdict = checkAncestry({ ancestor: args.ancestor, descendant: args.descendant, git });
|
|
73
|
+
stdout.write(`${JSON.stringify({ verdict, ancestor: args.ancestor, descendant: args.descendant })}\n`);
|
|
74
|
+
if (verdict === ANCESTRY_VERDICT.ANCESTOR) return 0;
|
|
75
|
+
stderr.write(verdict === ANCESTRY_VERDICT.DIVERGED
|
|
76
|
+
? `::error::${args.ancestor} is not an ancestor of ${args.descendant}, so the next release -> main merge will conflict (#5894). `
|
|
77
|
+
+ 'Land a back-merge of main into next as a merge commit first: push chore/backmerge-main-to-next-<short sha of main> '
|
|
78
|
+
+ '(next plus `git merge -s ours --no-ff origin/main`), open it against next, and merge it with '
|
|
79
|
+
+ '`node scripts/merge-commit-pr.cjs <PR>` as a repo admin. Never send it through the merge queue.\n'
|
|
80
|
+
: `::error::could not read the ancestry of ${args.ancestor} and ${args.descendant} (git failed). Fetch both refs and retry.\n`);
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (require.main === module) runMain(main);
|
|
85
|
+
|
|
86
|
+
module.exports = { ANCESTRY_VERDICT, GIT_TIMEOUT_MS, checkAncestry, parseArgs, main };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The release pipeline's own back-merge PRs (#5890).
|
|
5
|
+
*
|
|
6
|
+
* The pipeline opens three kinds of PR, and none of them has an issue to link:
|
|
7
|
+
*
|
|
8
|
+
* kind head branch base opened by
|
|
9
|
+
* release-merge release/X.Y.0 | hotfix/X.Y.Z (Z>0) main release.yml "merge release back to main"
|
|
10
|
+
* next-version-sync chore/sync-next-version-<version> next scripts/sync-next-version.cjs
|
|
11
|
+
* main-to-next-backmerge chore/backmerge-main-to-next-<sha> next auto-backmerge.yml (#1389)
|
|
12
|
+
*
|
|
13
|
+
* Callers, each with its trust model:
|
|
14
|
+
*
|
|
15
|
+
* - scripts/require-issue-link-policy.cjs (`Issue link required`) and
|
|
16
|
+
* .github/workflows/pr-title-validator.yml (`validate-title`) run on
|
|
17
|
+
* pull_request_target and load this module from a BASE-branch checkout,
|
|
18
|
+
* so a PR cannot edit the predicate that judges it.
|
|
19
|
+
* - scripts/changeset/lint.cjs (changeset-lint, #5903) runs on pull_request
|
|
20
|
+
* and executes PR-tree code, so the base-checkout guarantee does not apply
|
|
21
|
+
* (a PR could already edit lint.cjs itself). The fork guard is what blocks
|
|
22
|
+
* forgery: sameRepo is derived from the GitHub event payload.
|
|
23
|
+
*
|
|
24
|
+
* What each caller exempts:
|
|
25
|
+
*
|
|
26
|
+
* - `Issue link required`: the closing-reference requirement.
|
|
27
|
+
* - `validate-title`: only the missing-issue-ref verdict; the title's type
|
|
28
|
+
* prefix (`bad-prefix`) is still enforced on these PRs.
|
|
29
|
+
* - changeset-lint: the fragment requirement; a malformed fragment or `pr:`
|
|
30
|
+
* drift still fails.
|
|
31
|
+
*
|
|
32
|
+
* Anti-forgery (#1389): a branch name alone is attacker-chosen — a fork can
|
|
33
|
+
* name its branch anything. The exemption therefore also requires
|
|
34
|
+
* `sameRepo === true` (head.repo.full_name == the base repository), which only
|
|
35
|
+
* someone with push access to this repository can satisfy, and the exact base
|
|
36
|
+
* branch each kind targets.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
const { isReleaseVersion, SYNC_BRANCH_PREFIX } = require('./sync-next-version.cjs');
|
|
40
|
+
|
|
41
|
+
const RELEASE_PIPELINE_PR_KIND = Object.freeze({
|
|
42
|
+
RELEASE_MERGE: 'release-merge',
|
|
43
|
+
NEXT_VERSION_SYNC: 'next-version-sync',
|
|
44
|
+
MAIN_TO_NEXT_BACKMERGE: 'main-to-next-backmerge',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// release.yml validate-version: X.Y.0 cuts release/X.Y.0; X.Y.Z with Z>0 cuts
|
|
48
|
+
// hotfix/X.Y.Z. Same no-leading-zero grammar as its `grep -qE` checks.
|
|
49
|
+
const RELEASE_BRANCH_RE = /^release\/(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.0$/;
|
|
50
|
+
const HOTFIX_BRANCH_RE = /^hotfix\/(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.[1-9][0-9]*$/;
|
|
51
|
+
|
|
52
|
+
// auto-backmerge.yml: BR="chore/backmerge-main-to-next-${SHORT_SHA}" (#1389),
|
|
53
|
+
// SHORT_SHA=$(git rev-parse --short origin/main): 7 to 40 lowercase hex.
|
|
54
|
+
const BACKMERGE_BRANCH_PREFIX = 'chore/backmerge-main-to-next-';
|
|
55
|
+
const BACKMERGE_SHA_RE = /^[0-9a-f]{7,40}$/;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Classify a PR as one of the release pipeline's own PRs.
|
|
59
|
+
*
|
|
60
|
+
* @param {{ headRef?: unknown, baseRef?: unknown, sameRepo?: unknown }} [pr]
|
|
61
|
+
* @returns {string|null} a RELEASE_PIPELINE_PR_KIND value, or null when the PR
|
|
62
|
+
* is not a release-pipeline PR (including every fork PR).
|
|
63
|
+
*/
|
|
64
|
+
function releasePipelinePrKind({ headRef, baseRef, sameRepo } = {}) {
|
|
65
|
+
if (sameRepo !== true) return null;
|
|
66
|
+
if (typeof headRef !== 'string' || typeof baseRef !== 'string') return null;
|
|
67
|
+
|
|
68
|
+
if (baseRef === 'main' && (RELEASE_BRANCH_RE.test(headRef) || HOTFIX_BRANCH_RE.test(headRef))) {
|
|
69
|
+
return RELEASE_PIPELINE_PR_KIND.RELEASE_MERGE;
|
|
70
|
+
}
|
|
71
|
+
if (baseRef === 'next' && headRef.startsWith(SYNC_BRANCH_PREFIX)
|
|
72
|
+
&& isReleaseVersion(headRef.slice(SYNC_BRANCH_PREFIX.length))) {
|
|
73
|
+
return RELEASE_PIPELINE_PR_KIND.NEXT_VERSION_SYNC;
|
|
74
|
+
}
|
|
75
|
+
if (baseRef === 'next' && headRef.startsWith(BACKMERGE_BRANCH_PREFIX)
|
|
76
|
+
&& BACKMERGE_SHA_RE.test(headRef.slice(BACKMERGE_BRANCH_PREFIX.length))) {
|
|
77
|
+
return RELEASE_PIPELINE_PR_KIND.MAIN_TO_NEXT_BACKMERGE;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** True iff the PR is one of the release pipeline's own PRs (see releasePipelinePrKind). */
|
|
83
|
+
function isReleasePipelinePr(pr) {
|
|
84
|
+
return releasePipelinePrKind(pr) !== null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
RELEASE_PIPELINE_PR_KIND,
|
|
89
|
+
BACKMERGE_BRANCH_PREFIX,
|
|
90
|
+
releasePipelinePrKind,
|
|
91
|
+
isReleasePipelinePr,
|
|
92
|
+
};
|
|
@@ -37,10 +37,14 @@ const ISSUE_LINK_REASON = Object.freeze({
|
|
|
37
37
|
FAIL_FILE_LIST_INCOMPLETE: 'fail_file_list_incomplete',
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
// #1389
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
|
|
40
|
+
// #1389 / #5890: the release pipeline's own back-merge PRs (release/hotfix ->
|
|
41
|
+
// main, chore/sync-next-version-* and chore/backmerge-main-to-next-* -> next)
|
|
42
|
+
// are opened by CI, never by a human or a fork, and have no issue to link, so
|
|
43
|
+
// they are exempt outright. The predicate lives in release-pipeline-pr.cjs —
|
|
44
|
+
// shared with pr-title-validator.yml so the two gates agree — and requires
|
|
45
|
+
// `sameRepo === true` plus the exact base branch, so a fork cannot forge it
|
|
46
|
+
// by naming its branch.
|
|
47
|
+
const { releasePipelinePrKind, BACKMERGE_BRANCH_PREFIX } = require('./release-pipeline-pr.cjs');
|
|
44
48
|
|
|
45
49
|
// #4196: Dependabot has no mechanism to link a PR it opens to a repo issue —
|
|
46
50
|
// its alerts live in the Security tab, not as issues, so there is nothing
|
|
@@ -135,7 +139,7 @@ function allPathsAreTestsOrDocs(changedFiles) {
|
|
|
135
139
|
});
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
function evaluateIssueLink({ prBody, headRef, sameRepo, authorLogin, changedFiles, changedFilesTotal }) {
|
|
142
|
+
function evaluateIssueLink({ prBody, headRef, baseRef, sameRepo, authorLogin, changedFiles, changedFilesTotal }) {
|
|
139
143
|
if (authorLogin === DEPENDABOT_LOGIN) {
|
|
140
144
|
return { ok: true, reason: ISSUE_LINK_REASON.OK_DEPENDABOT_EXEMPT };
|
|
141
145
|
}
|
|
@@ -144,10 +148,14 @@ function evaluateIssueLink({ prBody, headRef, sameRepo, authorLogin, changedFile
|
|
|
144
148
|
return { ok: true, reason: ISSUE_LINK_REASON.OK_CLOSING_KEYWORD };
|
|
145
149
|
}
|
|
146
150
|
|
|
147
|
-
// #1389: `sameRepo === true` is required, not just the branch
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
// #1389: `sameRepo === true` is required, not just the branch name — a fork
|
|
152
|
+
// PR could otherwise name its branch to forge this exemption (#5890 adds the
|
|
153
|
+
// base branch and the release/sync shapes; see release-pipeline-pr.cjs).
|
|
154
|
+
const releaseKind = releasePipelinePrKind({ headRef, baseRef, sameRepo });
|
|
155
|
+
if (releaseKind) {
|
|
156
|
+
// `kind` names which release-pipeline PR this is (the reason keeps its
|
|
157
|
+
// #1389 name so existing consumers of the enum are unchanged).
|
|
158
|
+
return { ok: true, reason: ISSUE_LINK_REASON.OK_BACKMERGE_EXEMPT, kind: releaseKind };
|
|
151
159
|
}
|
|
152
160
|
|
|
153
161
|
if (!hasFollowUpReference(prBody)) {
|
|
@@ -175,6 +183,7 @@ function main() {
|
|
|
175
183
|
const result = evaluateIssueLink({
|
|
176
184
|
prBody: process.env.PR_BODY || '',
|
|
177
185
|
headRef: process.env.HEAD_REF || '',
|
|
186
|
+
baseRef: process.env.BASE_REF || '',
|
|
178
187
|
sameRepo: process.env.SAME_REPO === 'true',
|
|
179
188
|
authorLogin: process.env.PR_AUTHOR_LOGIN || '',
|
|
180
189
|
changedFiles,
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* release type (rc / hotfix / final). See issue #1104.
|
|
10
10
|
*
|
|
11
11
|
* Modes:
|
|
12
|
-
* default — open + admin-merge a `chore: sync next version` PR
|
|
12
|
+
* default — open + admin-merge a `chore: sync next version` PR, or enqueue it
|
|
13
|
+
* when the base requires the merge queue (#5746) (used by
|
|
13
14
|
* release.yml's rc job, which has no next-targeting PR of its
|
|
14
15
|
* own). Idempotent: a no-op when `next` is already at the target.
|
|
15
16
|
* --in-place — set the version (+ manifests, via the npm `version` hook) in
|
|
@@ -29,6 +30,15 @@ function isReleaseVersion(v) {
|
|
|
29
30
|
return typeof v === 'string' && RELEASE_VERSION.test(v);
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
// The branch syncViaPr opens. scripts/release-pipeline-pr.cjs exempts exactly
|
|
34
|
+
// this shape from the issue-link and PR-title gates (#5890).
|
|
35
|
+
const SYNC_BRANCH_PREFIX = 'chore/sync-next-version-';
|
|
36
|
+
|
|
37
|
+
/** The head branch of the sync PR for `version`. */
|
|
38
|
+
function syncBranchName(version) {
|
|
39
|
+
return `${SYNC_BRANCH_PREFIX}${version}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
32
42
|
/** Read the `version` field out of a package.json text blob. Throws on a missing/non-string version. */
|
|
33
43
|
function versionFromPackageJson(text) {
|
|
34
44
|
const parsed = JSON.parse(text);
|
|
@@ -52,7 +62,19 @@ function applyVersion(version, { run = defaultRun } = {}) {
|
|
|
52
62
|
run('npm', ['version', version, '--no-git-tag-version', '--allow-same-version'], { timeout: 60000 });
|
|
53
63
|
}
|
|
54
64
|
|
|
55
|
-
|
|
65
|
+
// What `gh pr merge --admin` says when the base branch is behind a merge-queue ruleset.
|
|
66
|
+
const MERGE_QUEUE_REJECTION_RE = /merge queue/i;
|
|
67
|
+
|
|
68
|
+
// stdin ignored, stdout and stderr captured so a failed `gh` call carries its own text.
|
|
69
|
+
const PIPED_STDERR = ['ignore', 'pipe', 'pipe'];
|
|
70
|
+
|
|
71
|
+
/** Piped stderr is not visible on its own: write it out, then rethrow. */
|
|
72
|
+
function rethrowWithStderr(err) {
|
|
73
|
+
if (err && err.stderr) process.stderr.write(String(err.stderr));
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Full PR-based sync to the `next` branch. Returns 'noop' | 'synced' | 'queued' (enqueued, not yet merged). */
|
|
56
78
|
function syncViaPr(version, { run = defaultRun } = {}) {
|
|
57
79
|
if (!isReleaseVersion(version)) {
|
|
58
80
|
throw new Error(`refusing to sync next to invalid/non-release version '${version}'`);
|
|
@@ -63,7 +85,7 @@ function syncViaPr(version, { run = defaultRun } = {}) {
|
|
|
63
85
|
process.stdout.write(`next already at ${version} — nothing to sync\n`);
|
|
64
86
|
return 'noop';
|
|
65
87
|
}
|
|
66
|
-
const branch =
|
|
88
|
+
const branch = syncBranchName(version);
|
|
67
89
|
run('git', ['checkout', '-B', branch, 'origin/next'], { timeout: 15000 });
|
|
68
90
|
applyVersion(version, { run });
|
|
69
91
|
run('git', ['add', '-u'], { timeout: 15000 });
|
|
@@ -100,11 +122,30 @@ function syncViaPr(version, { run = defaultRun } = {}) {
|
|
|
100
122
|
/* labels are best-effort; admin-merge below still lands the PR */
|
|
101
123
|
}
|
|
102
124
|
try {
|
|
103
|
-
|
|
125
|
+
// stderr is piped, not inherited: with the default 'inherit' the GitHub text goes
|
|
126
|
+
// to the terminal and err.stderr is null, so the classification below would only
|
|
127
|
+
// ever see "Command failed: gh pr merge ..." (#5746).
|
|
128
|
+
run('gh', ['pr', 'merge', '--admin', '--merge', prNum], { timeout: 60000, stdio: PIPED_STDERR });
|
|
104
129
|
} catch (err) {
|
|
105
|
-
// tolerate "already merged"; rethrow anything else
|
|
106
130
|
const msg = String((err && (err.stderr || err.message)) || '');
|
|
107
|
-
if (
|
|
131
|
+
if (/already merged/i.test(msg)) {
|
|
132
|
+
// a PR that is already merged must never be enqueued
|
|
133
|
+
} else if (MERGE_QUEUE_REJECTION_RE.test(msg)) {
|
|
134
|
+
// #5746: `next` is governed by a merge-queue ruleset, which --admin cannot bypass.
|
|
135
|
+
// Enqueue instead; the queue chooses the merge strategy, so no --merge here. A
|
|
136
|
+
// failed enqueue is a real failure: its text is surfaced and it propagates.
|
|
137
|
+
try {
|
|
138
|
+
run('gh', ['pr', 'merge', '--auto', prNum], { timeout: 60000, stdio: PIPED_STDERR });
|
|
139
|
+
} catch (enqueueErr) {
|
|
140
|
+
rethrowWithStderr(enqueueErr);
|
|
141
|
+
}
|
|
142
|
+
process.stdout.write(`queued sync of next -> ${version} via PR #${prNum}\n`);
|
|
143
|
+
return 'queued';
|
|
144
|
+
} else if (!/not mergeable|Merged/i.test(msg)) {
|
|
145
|
+
// the older tolerance, kept after the queue rule so a queue rejection that happens
|
|
146
|
+
// to mention "mergeable" is not mistaken for success
|
|
147
|
+
rethrowWithStderr(err);
|
|
148
|
+
}
|
|
108
149
|
}
|
|
109
150
|
process.stdout.write(`synced next -> ${version} via PR #${prNum}\n`);
|
|
110
151
|
return 'synced';
|
|
@@ -130,4 +171,4 @@ if (require.main === module) {
|
|
|
130
171
|
}
|
|
131
172
|
}
|
|
132
173
|
|
|
133
|
-
module.exports = { isReleaseVersion, versionFromPackageJson, applyVersion, syncViaPr, main };
|
|
174
|
+
module.exports = { isReleaseVersion, versionFromPackageJson, applyVersion, syncViaPr, main, defaultRun, SYNC_BRANCH_PREFIX, syncBranchName };
|
|
@@ -28,7 +28,7 @@ Brownfield equivalent of new-project. Project exists, PROJECT.md has history. Ga
|
|
|
28
28
|
- `.planning/ROADMAP.md` — phase structure (continues numbering)
|
|
29
29
|
- `.planning/STATE.md` — reset for new milestone
|
|
30
30
|
|
|
31
|
-
**After:** `/gsd-plan-phase [N]` to
|
|
31
|
+
**After:** Run `/gsd-discuss-phase [N]` to clarify the approach (or `/gsd-plan-phase [N]` to plan directly); then `/gsd-execute-phase [N]` to execute.
|
|
32
32
|
</objective>
|
|
33
33
|
|
|
34
34
|
<execution_context>
|
|
@@ -36,7 +36,7 @@ Initialize a new project through unified flow: questioning → research (optiona
|
|
|
36
36
|
- `.planning/ROADMAP.md` — phase structure
|
|
37
37
|
- `.planning/STATE.md` — project memory
|
|
38
38
|
|
|
39
|
-
**After this command:** Run `/gsd-plan-phase 1` to
|
|
39
|
+
**After this command:** Run `/gsd-discuss-phase 1` to clarify the approach (or `/gsd-plan-phase 1` to plan directly); then `/gsd-execute-phase 1` to execute.
|
|
40
40
|
</objective>
|
|
41
41
|
|
|
42
42
|
<execution_context>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gsd-update
|
|
3
3
|
description: "Update GSD to latest version with changelog display"
|
|
4
|
-
argument-hint: "[--sync | --reapply | --next | --rc]"
|
|
4
|
+
argument-hint: "[--sync | --reapply | --next | --rc] [--interactive=exceptions|all]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Write
|
|
@@ -36,14 +36,17 @@ Routes to the update workflow which handles:
|
|
|
36
36
|
<flags>
|
|
37
37
|
- **--sync**: Sync managed GSD skills across runtime roots so multi-runtime users stay aligned after an update. Runs the sync-skills workflow (--from, --to, --dry-run, --apply flags supported).
|
|
38
38
|
- **--reapply**: Reapply local modifications after a GSD update. Uses three-way comparison (pristine baseline, user-modified backup, newly installed version) to merge user customizations back. Runs the reapply-patches workflow.
|
|
39
|
+
- **--interactive[=exceptions|all]** (with `--reapply` only): show each user edit next to the stock text with a recommended disposition and wait for a keep / retire / rewrite decision per edit. `all` shows every edit; `exceptions` (bare `--interactive`) shows only edits upstream adopted, partly adopted, reworded around, or orphaned, and keeps the rest. Without it, `--reapply` behaves exactly as before.
|
|
39
40
|
- **--next** (alias **--rc**): Target the `@next` RC dist-tag instead of `@latest` so you can install or refresh a release candidate (e.g. `1.4.0-rc.1`) through the normal update flow — scope/runtime detection, changelog preview, custom-file backup, and cache clearing all still apply. Omitting it keeps targeting `@latest` (no change). See ADR #660 for the RC channel.
|
|
40
41
|
- **(no flag)**: Standard update — check for new version, show changelog, install.
|
|
41
42
|
</flags>
|
|
42
43
|
|
|
43
44
|
<process>
|
|
45
|
+
If the arguments contain `--interactive` (bare or with a value) but not `--reapply`, STOP with: `--interactive applies only to --reapply (for example: /gsd-update --reapply --interactive=all)`. Do nothing else.
|
|
46
|
+
|
|
44
47
|
Parse the first token of the `<arguments>` block:
|
|
45
48
|
- If it is `--sync`: strip the flag, execute the sync-skills workflow (passing remaining args for --from/--to/--dry-run/--apply).
|
|
46
|
-
- If it is `--reapply`: strip the flag, execute the reapply-patches workflow.
|
|
49
|
+
- If it is `--reapply`: strip the flag, execute the reapply-patches workflow, passing the remaining arguments (for example `--interactive=all`) to it.
|
|
47
50
|
- Otherwise (including `--next` / `--rc`): execute the update workflow end-to-end, passing the `<arguments>` block through so the workflow's parse_update_channel step can select the release channel.
|
|
48
51
|
|
|
49
52
|
</process>
|
package/vscode/package.json
CHANGED