Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/next/fixed-issue-4185.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Claim and planning tasks no longer risk pushing work directly to their default branch.
71 changes: 54 additions & 17 deletions server/services/taskPromptDefaults.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,10 @@ describe('taskPromptDefaults integrity snapshot', () => {
// declared the reviewer unavailable, and merged its PR on a self-review. Every
// claim/plan prompt that enumerates the CLI reviewers must name the binary.
it.each([
['plan-task', 13],
['claim-issue', 11],
['claim-issue-gitlab', 10],
['claim-issue-jira', 8],
['plan-task', 14],
['claim-issue', 12],
['claim-issue-gitlab', 11],
['claim-issue-jira', 9],
])('%s v%d names the antigravity reviewer\'s `agy` binary, preserving the pre-`agy` default', (key, version) => {
const current = DEFAULT_TASK_PROMPTS[key];
expect(PROMPT_VERSIONS[key]).toBe(version);
Expand All @@ -245,6 +245,38 @@ describe('taskPromptDefaults integrity snapshot', () => {
expect(preAgy).not.toBe(current);
});

// A branch created from a remote default-branch ref normally inherits that
// ref as its upstream. The claim flows later derive their push destination
// from the branch config, so that inherited upstream could send claim work
// directly to the default branch instead of its PR branch. Keep these four
// commands untracked until their explicit `git push -u` phase establishes
// the correct upstream. dependency-updates intentionally differs: it starts
// from the bot PR head, where tracking the existing PR branch is correct.
it.each([
'plan-task',
'claim-issue',
'claim-issue-gitlab',
'claim-issue-jira',
])('%s creates a no-track claim worktree and preserves the outgoing default', (key) => {
const current = DEFAULT_TASK_PROMPTS[key];
const worktreeCommands = current.match(/^git(?: -C \{repoPath\})? worktree add\b.*$/gm) || [];

expect(worktreeCommands).toHaveLength(1);
expect(worktreeCommands.every((command) => command.includes('--no-track'))).toBe(true);

const outgoing = PREVIOUS_DEFAULT_PROMPTS[key].at(-1);
expect(outgoing).toMatch(/\bworktree add -b\b/);
expect(outgoing).not.toContain('--no-track');
expect(outgoing).not.toBe(current);
});

it('keeps dependency-update worktrees tracking their PR head', () => {
const current = DEFAULT_TASK_PROMPTS['dependency-updates'];

expect(current).toContain('worktree add -b dep-{appName}-pr-<n>');
expect(current).not.toContain('--no-track');
});

// Changelog instructions defer to the convention the repo documents rather
// than prescribing an append to `.changelog/NEXT.md`. PortOS (and any repo
// that adopts the same shape) collects per-branch fragments so parallel
Expand Down Expand Up @@ -304,7 +336,7 @@ describe('taskPromptDefaults integrity snapshot', () => {
it.each([
['claim-issue', 'gh issue close', 'gh issue edit "${NUM}" --add-label needs-input'],
['claim-issue-gitlab', 'glab issue close', 'glab issue update "${NUM}" --label needs-input'],
])('%s converges every Phase-3 release, preserving the outgoing default', (key, closeCommand, parkCommand) => {
])('%s converges every Phase-3 release, preserving the pre-convergence default', (key, closeCommand, parkCommand) => {
const current = DEFAULT_TASK_PROMPTS[key];
const phase3 = phaseSection(current, 3);
// The already-fixed/superseded branch CLOSES rather than releasing open…
Expand All @@ -321,21 +353,23 @@ describe('taskPromptDefaults integrity snapshot', () => {
// the issue open and unlabeled — it must be gone, not merely qualified.
expect(phase3).not.toContain('If ANY of these are true, release the claim and re-pick');

// The outgoing default is preserved verbatim so installs holding it are
// recognized and auto-upgraded rather than read as user customizations.
const outgoing = PREVIOUS_DEFAULT_PROMPTS[key][PREVIOUS_DEFAULT_PROMPTS[key].length - 1];
const outgoingPhase3 = phaseSection(outgoing, 3);
expect(outgoingPhase3).toContain('If ANY of these are true, release the claim and re-pick');
expect(outgoingPhase3).not.toContain(closeCommand);
expect(outgoing).not.toBe(current);
// Later prompt revisions append their own outgoing defaults, so identify
// the pre-convergence body by its Phase-3 behavior rather than array slot.
const preConvergence = PREVIOUS_DEFAULT_PROMPTS[key].findLast(
(body) => phaseSection(body, 3).includes('If ANY of these are true, release the claim and re-pick'),
);
expect(preConvergence).toBeDefined();
const preConvergencePhase3 = phaseSection(preConvergence, 3);
expect(preConvergencePhase3).not.toContain(closeCommand);
expect(preConvergence).not.toBe(current);
});

// JIRA has no labels, so its converging vocabulary is status: an already-fixed
// ticket goes to Done/Closed, and a stale-reference ticket parks on a held
// status behind a Review Hub todo. Transitioning back to a not-started status
// is the JIRA shape of the same bug — Phase 1's not-started-only filter
// re-picks it immediately.
it('claim-issue-jira converges every Phase-3 release, preserving the outgoing default', () => {
it('claim-issue-jira converges every Phase-3 release, preserving the pre-convergence default', () => {
const current = DEFAULT_TASK_PROMPTS['claim-issue-jira'];
const phase3 = phaseSection(current, 3);
expect(phase3).toContain('CONVERGING status');
Expand All @@ -348,10 +382,13 @@ describe('taskPromptDefaults integrity snapshot', () => {
expect(phase3).toContain('NOT back to a not-started status');
expect(phase3).not.toContain('If ANY of these are true, release the claim and re-pick');

const previous = PREVIOUS_DEFAULT_PROMPTS['claim-issue-jira'];
const outgoing = previous[previous.length - 1];
expect(phaseSection(outgoing, 3)).toContain('transition the ticket back to its not-started status');
expect(outgoing).not.toBe(current);
// Newer prompt revisions append another outgoing default, so retain this
// historical assertion by its Phase-3 behavior rather than its array slot.
const preConvergence = PREVIOUS_DEFAULT_PROMPTS['claim-issue-jira'].findLast(
(body) => phaseSection(body, 3).includes('transition the ticket back to its not-started status'),
);
expect(preConvergence).toBeDefined();
expect(preConvergence).not.toBe(current);
});

// release-check READS the changelog rather than writing it, so its fix is the
Expand Down
28 changes: 16 additions & 12 deletions server/services/taskPromptDefaults/integrity.snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
"mobile-responsive": "16d8e7f63a673a2de48994c0ac08ef3d",
"ux": "e03d03fc7c16faa5b5db93be2896b9ad",
"feature-ideas": "d3282f16da29efe2d53595b3b34778bc",
"plan-task": "bbc5495a02102fd470d2a429eedfbe76",
"claim-issue": "d5465bd5d6fa1cd979746b99a881dba9",
"claim-issue-gitlab": "8e8f795bb75e996b80992883983a3c08",
"claim-issue-jira": "927766326e492e4c9c2566534f465512",
"plan-task": "4df36bed4974ff41f369c86828e7f94f",
"claim-issue": "0ffd94e6c7966e5ce457effea63f71d3",
"claim-issue-gitlab": "da0a8fbb33ac274a3b2efd968e302a1a",
"claim-issue-jira": "364ecf5feb38ec1f7ada50a7bcbf3199",
"code-reviewer-review": "1cd13ee5142d130f95cc390098d0f278",
"code-reviewer-implement": "ab2c3e16d54e195bdf856574b7702042",
"error-handling": "9afba2e99b90a8d5771c75e7f0615d96",
Expand All @@ -36,10 +36,10 @@
},
"PROMPT_VERSIONS": {
"feature-ideas": 10,
"plan-task": 13,
"claim-issue": 11,
"claim-issue-gitlab": 10,
"claim-issue-jira": 8,
"plan-task": 14,
"claim-issue": 12,
"claim-issue-gitlab": 11,
"claim-issue-jira": 9,
"pr-reviewer": 3,
"code-reviewer-a": 1,
"code-reviewer-b": 1,
Expand Down Expand Up @@ -82,7 +82,8 @@
"2189019880ee12a8ba4d7975e7afc646",
"dced853944c2cad8fc0c151dea71301a",
"8f5b69fd9336b17184e7f7989f97def5",
"808140457aff9ff344ddac426e0022ed"
"808140457aff9ff344ddac426e0022ed",
"bbc5495a02102fd470d2a429eedfbe76"
],
"pr-reviewer": [
"9ceeed08f238b3787fc1201a0ce8e023",
Expand All @@ -100,7 +101,8 @@
"3f5cc58943bc08cbbc00f836660ecdfc",
"97a3994ce4da2d2743bdffec110a7482",
"e3d1a2e884a2a49c306a586089f4f376",
"367c22d2587212c9c391f50d146948e8"
"367c22d2587212c9c391f50d146948e8",
"927766326e492e4c9c2566534f465512"
],
"claim-issue-gitlab": [
"271ad9c6efa9dce424103711292fd055",
Expand All @@ -111,7 +113,8 @@
"c9888390df157b72741aafb9f2f9d8e4",
"d7b44882f4d47ae44d39989e29e5fe2b",
"4f323dddadb5c7f0047e35914ba80529",
"12d78a91126213f7c10e9a6725cdb464"
"12d78a91126213f7c10e9a6725cdb464",
"8e8f795bb75e996b80992883983a3c08"
],
"claim-issue": [
"89308f712f99ad8134224639cd99f135",
Expand All @@ -123,7 +126,8 @@
"e9039e0ee911636db2927fe239280f35",
"dc985be3422c9fdee07d92a059666f0a",
"ed113d11f477bf1b50cd5a48364b9da9",
"9b90d29048e85c9db640b1db643a08c8"
"9b90d29048e85c9db640b1db643a08c8",
"d5465bd5d6fa1cd979746b99a881dba9"
],
"security": [
"d0f54c1fbe62d067b1a23ee0a9f3528c"
Expand Down
Loading