mandrel 2.64.0 → 2.66.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/.agents/agents/acceptance-critic.md +8 -7
- package/.agents/agents/auditor.md +20 -20
- package/.agents/agents/plan-critic.md +8 -7
- package/.agents/agents/story-worker.md +7 -7
- package/.agents/audit-checklists/quality.md +3 -0
- package/.agents/docs/agentrc-reference.json +1 -9
- package/.agents/docs/configuration.md +8 -7
- package/.agents/docs/execution-reference.md +27 -5
- package/.agents/instructions.md +10 -12
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +3 -2
- package/.agents/rules/git-conventions-reference.md +12 -3
- package/.agents/rules/git-conventions.md +9 -7
- package/.agents/rules/testing-standards.md +8 -7
- package/.agents/runtime-deps.json +1 -1
- package/.agents/schemas/agentrc.schema.json +6 -13
- package/.agents/schemas/audit-rules.schema.json +1 -1
- package/.agents/schemas/story-deliver-terminal.schema.json +5 -0
- package/.agents/scripts/bootstrap.js +102 -91
- package/.agents/scripts/check-context-budget.js +1 -1
- package/.agents/scripts/lib/ITicketingProvider.js +1 -3
- package/.agents/scripts/lib/audit-suite/findings.js +1 -17
- package/.agents/scripts/lib/audit-suite/frontmatter.js +0 -28
- package/.agents/scripts/lib/audit-suite/index.js +0 -6
- package/.agents/scripts/lib/audit-suite/selector.js +0 -31
- package/.agents/scripts/lib/baselines/duplication-scanner.js +17 -7
- package/.agents/scripts/lib/bootstrap/agents-md-fold.js +156 -0
- package/.agents/scripts/lib/bootstrap/commit-push.js +1 -1
- package/.agents/scripts/lib/bootstrap/manifest.js +2 -2
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +91 -107
- package/.agents/scripts/lib/cli/standard-args.js +60 -76
- package/.agents/scripts/lib/cli-args.js +26 -0
- package/.agents/scripts/lib/config/gates/shared.js +3 -3
- package/.agents/scripts/lib/config/review-chain-default.js +13 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-quality.js +11 -13
- package/.agents/scripts/lib/doc-tiers.js +25 -6
- package/.agents/scripts/lib/feedback-loop/graduate-steps.js +205 -0
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +47 -782
- package/.agents/scripts/lib/feedback-loop/graduator-gh.js +449 -0
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/observability/close-telemetry.js +330 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +0 -72
- package/.agents/scripts/lib/observability/runtime-friction.js +2 -0
- package/.agents/scripts/lib/observability/signal-validator.js +17 -5
- package/.agents/scripts/lib/orchestration/code-review.js +33 -6
- package/.agents/scripts/lib/orchestration/epic-rollup.js +29 -12
- package/.agents/scripts/lib/orchestration/merge-block-class.js +20 -4
- package/.agents/scripts/lib/orchestration/merge-poll.js +41 -22
- package/.agents/scripts/lib/orchestration/plan-metrics.js +76 -63
- package/.agents/scripts/lib/orchestration/required-checks.js +147 -0
- package/.agents/scripts/lib/orchestration/review-providers/code-review.js +203 -0
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +29 -4
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +3 -2
- package/.agents/scripts/lib/orchestration/run-epilogue.js +6 -0
- package/.agents/scripts/lib/orchestration/single-story-close/failed-terminal.js +1 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +2 -12
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +370 -268
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +21 -7
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +112 -82
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +4 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +393 -313
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +12 -87
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +3 -0
- package/.agents/scripts/lib/orchestration/ticket-validator.js +19 -36
- package/.agents/scripts/lib/signals/detectors/common.js +63 -51
- package/.agents/scripts/lib/templates/decomposer-prompts.js +5 -24
- package/.agents/scripts/lib/transpile.js +28 -3
- package/.agents/scripts/providers/github/issues.js +14 -23
- package/.agents/scripts/single-story-close.js +10 -2
- package/.agents/scripts/single-story-confirm-merge.js +267 -238
- package/.agents/scripts/sync-claude-agents.js +1 -1
- package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -2
- package/.agents/workflows/audit-architecture.md +5 -4
- package/.agents/workflows/audit-documentation.md +5 -5
- package/.agents/workflows/audit-performance.md +10 -10
- package/.agents/workflows/audit-quality.md +42 -7
- package/.agents/workflows/helpers/acceptance-self-eval.md +9 -9
- package/.agents/workflows/helpers/audit-lens-core.md +30 -57
- package/.agents/workflows/helpers/code-review.md +15 -38
- package/.agents/workflows/helpers/deliver-digest.md +2 -2
- package/.agents/workflows/helpers/deliver-reference.md +7 -3
- package/.agents/workflows/helpers/deliver-story.md +9 -1
- package/.agents/workflows/helpers/parallel-tooling.md +16 -18
- package/.agents/workflows/helpers/plan-reference.md +9 -8
- package/.agents/workflows/mandrel-deliver.md +3 -2
- package/.agents/workflows/mandrel-plan.md +11 -7
- package/.agents/workflows/mandrel-update.md +5 -3
- package/docs/CHANGELOG.md +57 -0
- package/lib/cli/claude-code-version.js +73 -0
- package/lib/cli/doctor.js +2 -2
- package/lib/cli/guarded-sync.js +87 -0
- package/lib/cli/registry.js +9 -0
- package/lib/cli/sync-agents.js +9 -92
- package/lib/cli/sync-commands.js +9 -101
- package/lib/cli/uninstall.js +37 -9
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.65.0-fold-claude-md-into-agents-md.js +38 -0
- package/package.json +3 -2
- package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +0 -99
- package/.agents/scripts/lib/audit-suite/runner.js +0 -205
- package/.agents/scripts/lib/audit-suite/substitutions.js +0 -96
- package/.agents/scripts/lib/audit-suite/workflow-loader.js +0 -37
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +0 -234
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
* @see .agents/workflows/helpers/deliver-story.md
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import
|
|
20
|
-
import {
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { parseStandardCliArgs } from './lib/cli/standard-args.js';
|
|
21
|
+
import { parseSprintArgs, parseSprintArgsTolerant } from './lib/cli-args.js';
|
|
21
22
|
import { runAsCli } from './lib/cli-utils.js';
|
|
22
23
|
import { resolveConfig } from './lib/config-resolver.js';
|
|
23
24
|
import { formatCliError } from './lib/error-redactor.js';
|
|
@@ -30,7 +31,7 @@ import { MERGED_FLIP_FAILED_BLOCK_CLASS } from './lib/orchestration/lifecycle/em
|
|
|
30
31
|
import { MERGE_WAIT_GH_TIMEOUT_MS } from './lib/orchestration/merge-poll.js';
|
|
31
32
|
import { parsePrNumber } from './lib/orchestration/single-story-close/phases/code-review.js';
|
|
32
33
|
import { runConfirmMergePhase as defaultRunConfirmMergePhase } from './lib/orchestration/single-story-close/phases/confirm-merge.js';
|
|
33
|
-
import {
|
|
34
|
+
import { assertNoRetiredFlags } from './lib/orchestration/single-story-close/phases/options.js';
|
|
34
35
|
import { runPostLandTail } from './lib/orchestration/single-story-close/phases/post-land.js';
|
|
35
36
|
import {
|
|
36
37
|
buildTerminalEnvelope,
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
NEXT_COMMANDS,
|
|
40
41
|
terminalFromWaitOutcome,
|
|
41
42
|
} from './lib/orchestration/story-deliver-terminal.js';
|
|
43
|
+
import { PROJECT_ROOT } from './lib/project-root.js';
|
|
42
44
|
import { createProvider } from './lib/provider-factory.js';
|
|
43
45
|
import { confirmStoryMerged } from './lib/single-story/confirm-merge.js';
|
|
44
46
|
|
|
@@ -67,56 +69,42 @@ const USAGE =
|
|
|
67
69
|
' over delivery.mergeWatch.maxWaitSeconds and the async\n' +
|
|
68
70
|
' probe-window cap; only meaningful with --wait)';
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
options: { pr: { type: 'string' } },
|
|
78
|
-
strict: false,
|
|
79
|
-
});
|
|
80
|
-
return values.pr;
|
|
81
|
-
} catch {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
72
|
+
const CONFIRM_MERGE_FLAGS = Object.freeze({
|
|
73
|
+
pr: { type: 'string' },
|
|
74
|
+
// Resume the bounded wait; without it the CLI probes once.
|
|
75
|
+
wait: { type: 'boolean' },
|
|
76
|
+
'max-wait-seconds': { type: 'string' },
|
|
77
|
+
cwd: { type: 'string' },
|
|
78
|
+
});
|
|
85
79
|
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
function readWaitFlag() {
|
|
91
|
-
try {
|
|
92
|
-
const { values } = parseArgs({
|
|
93
|
-
args: process.argv.slice(2),
|
|
94
|
-
options: { wait: { type: 'boolean', default: false } },
|
|
95
|
-
strict: false,
|
|
96
|
-
});
|
|
97
|
-
return values.wait === true;
|
|
98
|
-
} catch {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
80
|
+
/** @returns {number|undefined} */
|
|
81
|
+
function positiveIntOrUndefined(value) {
|
|
82
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
83
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
101
84
|
}
|
|
102
85
|
|
|
103
86
|
/**
|
|
104
|
-
*
|
|
87
|
+
* The close's vocabulary is validated first, so a malformed close flag fails
|
|
88
|
+
* `init` with the close's own message.
|
|
105
89
|
*
|
|
106
|
-
* @
|
|
90
|
+
* @param {string[]} fullArgv
|
|
91
|
+
* @returns {object}
|
|
107
92
|
*/
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
93
|
+
function parseConfirmMergeArgv(fullArgv) {
|
|
94
|
+
const argv = fullArgv.slice(2);
|
|
95
|
+
assertNoRetiredFlags(argv);
|
|
96
|
+
parseSprintArgs(fullArgv);
|
|
97
|
+
const { values } = parseStandardCliArgs({
|
|
98
|
+
argv,
|
|
99
|
+
extras: CONFIRM_MERGE_FLAGS,
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
storyId: values.storyId,
|
|
103
|
+
cwd: values.cwd,
|
|
104
|
+
pr: values.pr,
|
|
105
|
+
wait: values.wait,
|
|
106
|
+
maxWaitSeconds: positiveIntOrUndefined(values.maxWaitSeconds),
|
|
107
|
+
};
|
|
120
108
|
}
|
|
121
109
|
|
|
122
110
|
/**
|
|
@@ -164,199 +152,175 @@ async function logConfirmResult(result, terminal, config) {
|
|
|
164
152
|
return { success: terminal.status !== 'failed', result, terminal };
|
|
165
153
|
}
|
|
166
154
|
|
|
155
|
+
/** @returns {'MERGED'|'CLOSED'|'OPEN'} */
|
|
156
|
+
function confirmPrState(confirmation) {
|
|
157
|
+
if (confirmation.merged) return 'MERGED';
|
|
158
|
+
return confirmation.reason === 'pr-not-merged' ? 'CLOSED' : 'OPEN';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** @returns {{ number: number, state: string }|null} */
|
|
162
|
+
function confirmPr(prNumber, confirmation) {
|
|
163
|
+
if (!Number.isInteger(prNumber) || prNumber <= 0) return null;
|
|
164
|
+
return { number: prNumber, state: confirmPrState(confirmation) };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function confirmBlocked(blockClass, reason, nextCommand) {
|
|
168
|
+
return {
|
|
169
|
+
status: 'blocked',
|
|
170
|
+
phase: 'confirm-merge',
|
|
171
|
+
blocked: { blockClass, reason, frictionCommentId: null },
|
|
172
|
+
nextCommand,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
167
176
|
/**
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* a human); otherwise pending.
|
|
177
|
+
* done/noop → landed; flip-failed → blocked (re-run this command);
|
|
178
|
+
* pr-not-merged → blocked (needs a human); otherwise pending.
|
|
171
179
|
*/
|
|
172
|
-
function
|
|
173
|
-
storyId,
|
|
174
|
-
storyBranch,
|
|
175
|
-
baseBranch,
|
|
176
|
-
prNumber,
|
|
177
|
-
confirmation,
|
|
178
|
-
tail,
|
|
179
|
-
elapsedSeconds,
|
|
180
|
-
}) {
|
|
181
|
-
const prState = confirmation.merged
|
|
182
|
-
? 'MERGED'
|
|
183
|
-
: confirmation.reason === 'pr-not-merged'
|
|
184
|
-
? 'CLOSED'
|
|
185
|
-
: 'OPEN';
|
|
186
|
-
const pr =
|
|
187
|
-
Number.isInteger(prNumber) && prNumber > 0
|
|
188
|
-
? { number: prNumber, state: prState }
|
|
189
|
-
: null;
|
|
190
|
-
const common = { storyId, storyBranch, baseBranch, pr, elapsedSeconds };
|
|
191
|
-
|
|
180
|
+
function confirmOutcomeFields({ storyId, confirmation, tail }) {
|
|
192
181
|
if (confirmation.action === 'done' || confirmation.action === 'noop') {
|
|
193
|
-
return
|
|
194
|
-
...common,
|
|
182
|
+
return {
|
|
195
183
|
status: 'landed',
|
|
196
184
|
phase: tail ? 'post-land' : 'done',
|
|
197
185
|
tail,
|
|
198
186
|
nextCommand: null,
|
|
199
|
-
}
|
|
187
|
+
};
|
|
200
188
|
}
|
|
201
189
|
if (confirmation.action === 'flip-failed') {
|
|
202
|
-
return
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
blockClass: MERGED_FLIP_FAILED_BLOCK_CLASS,
|
|
208
|
-
reason:
|
|
209
|
-
'merge confirmed but the agent::closing → agent::done label write failed',
|
|
210
|
-
frictionCommentId: null,
|
|
211
|
-
},
|
|
212
|
-
nextCommand: NEXT_COMMANDS.confirmMerge(storyId),
|
|
213
|
-
});
|
|
190
|
+
return confirmBlocked(
|
|
191
|
+
MERGED_FLIP_FAILED_BLOCK_CLASS,
|
|
192
|
+
'merge confirmed but the agent::closing → agent::done label write failed',
|
|
193
|
+
NEXT_COMMANDS.confirmMerge(storyId),
|
|
194
|
+
);
|
|
214
195
|
}
|
|
215
196
|
if (confirmation.reason === 'pr-not-merged') {
|
|
216
|
-
return
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
blockClass: 'api-race-other',
|
|
222
|
-
reason: 'the PR was closed without merging (state=CLOSED)',
|
|
223
|
-
frictionCommentId: null,
|
|
224
|
-
},
|
|
225
|
-
nextCommand: NEXT_COMMANDS.recover(storyId),
|
|
226
|
-
});
|
|
197
|
+
return confirmBlocked(
|
|
198
|
+
'api-race-other',
|
|
199
|
+
'the PR was closed without merging (state=CLOSED)',
|
|
200
|
+
NEXT_COMMANDS.recover(storyId),
|
|
201
|
+
);
|
|
227
202
|
}
|
|
228
|
-
return
|
|
229
|
-
...common,
|
|
203
|
+
return {
|
|
230
204
|
status: 'pending',
|
|
231
205
|
phase: 'confirm-merge',
|
|
232
206
|
nextCommand:
|
|
233
207
|
confirmation.reason === 'no-pr'
|
|
234
208
|
? NEXT_COMMANDS.recover(storyId)
|
|
235
209
|
: NEXT_COMMANDS.confirmMerge(storyId),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function buildConfirmTerminal({
|
|
214
|
+
storyId,
|
|
215
|
+
storyBranch,
|
|
216
|
+
baseBranch,
|
|
217
|
+
prNumber,
|
|
218
|
+
confirmation,
|
|
219
|
+
tail,
|
|
220
|
+
elapsedSeconds,
|
|
221
|
+
}) {
|
|
222
|
+
return buildTerminalEnvelope({
|
|
223
|
+
storyId,
|
|
224
|
+
storyBranch,
|
|
225
|
+
baseBranch,
|
|
226
|
+
pr: confirmPr(prNumber, confirmation),
|
|
227
|
+
elapsedSeconds,
|
|
228
|
+
...confirmOutcomeFields({ storyId, confirmation, tail }),
|
|
236
229
|
});
|
|
237
230
|
}
|
|
238
231
|
|
|
239
|
-
async function resolveConfirmPrNumber({
|
|
240
|
-
|
|
241
|
-
let prNumber = Number.parseInt(String(rawPr ?? ''), 10);
|
|
232
|
+
async function resolveConfirmPrNumber({ pr, storyBranch, gh }) {
|
|
233
|
+
let prNumber = Number.parseInt(String(pr ?? ''), 10);
|
|
242
234
|
if (!Number.isInteger(prNumber) || prNumber <= 0) {
|
|
243
235
|
prNumber = await resolvePrNumber({ storyBranch, gh });
|
|
244
236
|
}
|
|
245
237
|
return Number.isInteger(prNumber) && prNumber > 0 ? prNumber : null;
|
|
246
238
|
}
|
|
247
239
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
pr: prParam,
|
|
252
|
-
wait: waitParam,
|
|
253
|
-
maxWaitSeconds: maxWaitSecondsParam,
|
|
254
|
-
injectedProvider,
|
|
255
|
-
injectedConfig,
|
|
256
|
-
injectedGh,
|
|
257
|
-
injectedNotify,
|
|
258
|
-
injectedReadPrMergeState,
|
|
259
|
-
runConfirmMergePhaseFn = defaultRunConfirmMergePhase,
|
|
260
|
-
} = {}) {
|
|
261
|
-
const { storyId, cwd } = parseCloseOptions({
|
|
262
|
-
storyIdParam,
|
|
263
|
-
cwdParam,
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
if (!storyId) {
|
|
267
|
-
throw new Error(USAGE);
|
|
268
|
-
}
|
|
269
|
-
const wait = waitParam ?? readWaitFlag();
|
|
270
|
-
const maxWaitSeconds = maxWaitSecondsParam ?? readMaxWaitSecondsFlag();
|
|
271
|
-
|
|
272
|
-
const startedAtMs = Date.now();
|
|
273
|
-
const config = injectedConfig || resolveConfig({ cwd });
|
|
274
|
-
const provider = injectedProvider || createProvider(config);
|
|
275
|
-
const gh = injectedGh ?? defaultGh;
|
|
276
|
-
const storyBranch = getStoryBranch(storyId);
|
|
277
|
-
const baseBranch = config.project?.baseBranch ?? 'main';
|
|
240
|
+
function elapsedSeconds(ctx) {
|
|
241
|
+
return Math.round((Date.now() - ctx.startedAtMs) / 1000);
|
|
242
|
+
}
|
|
278
243
|
|
|
279
|
-
|
|
244
|
+
async function confirmWithoutPr(ctx) {
|
|
245
|
+
progress(
|
|
246
|
+
'CONFIRM',
|
|
247
|
+
`⚠️ No PR found for ${ctx.storyBranch}; cannot confirm merge. Story stays at agent::closing.`,
|
|
248
|
+
);
|
|
249
|
+
const noPr = {
|
|
250
|
+
storyId: ctx.storyId,
|
|
251
|
+
standalone: true,
|
|
252
|
+
action: 'pending',
|
|
253
|
+
reason: 'no-pr',
|
|
254
|
+
merged: false,
|
|
255
|
+
};
|
|
256
|
+
return await logConfirmResult(
|
|
257
|
+
noPr,
|
|
258
|
+
buildConfirmTerminal({
|
|
259
|
+
storyId: ctx.storyId,
|
|
260
|
+
storyBranch: ctx.storyBranch,
|
|
261
|
+
baseBranch: ctx.baseBranch,
|
|
262
|
+
prNumber: null,
|
|
263
|
+
confirmation: noPr,
|
|
264
|
+
tail: null,
|
|
265
|
+
elapsedSeconds: elapsedSeconds(ctx),
|
|
266
|
+
}),
|
|
267
|
+
ctx.config,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
280
270
|
|
|
281
|
-
|
|
282
|
-
|
|
271
|
+
/**
|
|
272
|
+
* `--wait` runs the SAME phase as close so the cumulative budget give-up
|
|
273
|
+
* (the only path to `merge.unlanded` / `agent::blocked`) is reachable from a
|
|
274
|
+
* resume. The budget is anchored at the PR's `createdAt`, so resuming does
|
|
275
|
+
* not restart the clock.
|
|
276
|
+
*/
|
|
277
|
+
async function resumeMergeWait(ctx) {
|
|
278
|
+
const { storyId, storyBranch, baseBranch, prNumber } = ctx;
|
|
279
|
+
const waitOutcome = await ctx.runConfirmMergePhaseFn({
|
|
280
|
+
cwd: ctx.cwd,
|
|
281
|
+
storyId,
|
|
282
|
+
storyBranch,
|
|
283
|
+
baseBranch,
|
|
284
|
+
prNumber,
|
|
285
|
+
prUrl: `${storyBranch} PR #${prNumber}`,
|
|
286
|
+
// The close already armed it.
|
|
287
|
+
autoMergeEnabled: true,
|
|
288
|
+
maxWaitSeconds: ctx.maxWaitSeconds,
|
|
289
|
+
provider: ctx.provider,
|
|
290
|
+
config: ctx.config,
|
|
291
|
+
progress,
|
|
292
|
+
injectedGh: ctx.gh,
|
|
293
|
+
injectedNotify: ctx.injectedNotify,
|
|
294
|
+
readPrMergeStateFn: ctx.injectedReadPrMergeState,
|
|
295
|
+
});
|
|
296
|
+
const terminal = terminalFromWaitOutcome({
|
|
297
|
+
waitOutcome,
|
|
298
|
+
storyId,
|
|
283
299
|
storyBranch,
|
|
284
|
-
|
|
300
|
+
baseBranch,
|
|
301
|
+
prNumber,
|
|
302
|
+
prUrl: null,
|
|
303
|
+
autoMergeEnabled: true,
|
|
304
|
+
// This CLI runs no close gates.
|
|
305
|
+
gates: undefined,
|
|
306
|
+
elapsedSeconds: elapsedSeconds(ctx),
|
|
285
307
|
});
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
'CONFIRM',
|
|
289
|
-
`⚠️ No PR found for ${storyBranch}; cannot confirm merge. Story stays at agent::closing.`,
|
|
290
|
-
);
|
|
291
|
-
const noPr = {
|
|
308
|
+
return await logConfirmResult(
|
|
309
|
+
{
|
|
292
310
|
storyId,
|
|
293
311
|
standalone: true,
|
|
294
|
-
action:
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
storyBranch,
|
|
303
|
-
baseBranch,
|
|
304
|
-
prNumber: null,
|
|
305
|
-
confirmation: noPr,
|
|
306
|
-
tail: null,
|
|
307
|
-
elapsedSeconds: Math.round((Date.now() - startedAtMs) / 1000),
|
|
308
|
-
}),
|
|
309
|
-
config,
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// `--wait` runs the SAME phase as close so the cumulative budget give-up
|
|
314
|
-
// (the only path to `merge.unlanded` / `agent::blocked`) is reachable from a
|
|
315
|
-
// resume. The budget is anchored at the PR's `createdAt`, so resuming does
|
|
316
|
-
// not restart the clock.
|
|
317
|
-
if (wait) {
|
|
318
|
-
const waitOutcome = await runConfirmMergePhaseFn({
|
|
319
|
-
cwd,
|
|
320
|
-
storyId,
|
|
321
|
-
storyBranch,
|
|
322
|
-
baseBranch,
|
|
323
|
-
prNumber,
|
|
324
|
-
prUrl: `${storyBranch} PR #${prNumber}`,
|
|
325
|
-
// The close already armed it.
|
|
326
|
-
autoMergeEnabled: true,
|
|
327
|
-
maxWaitSeconds,
|
|
328
|
-
provider,
|
|
329
|
-
config,
|
|
330
|
-
progress,
|
|
331
|
-
injectedGh: gh,
|
|
332
|
-
injectedNotify,
|
|
333
|
-
readPrMergeStateFn: injectedReadPrMergeState,
|
|
334
|
-
});
|
|
335
|
-
const terminal = terminalFromWaitOutcome({
|
|
336
|
-
waitOutcome,
|
|
337
|
-
storyId,
|
|
338
|
-
storyBranch,
|
|
339
|
-
baseBranch,
|
|
340
|
-
prNumber,
|
|
341
|
-
prUrl: null,
|
|
342
|
-
autoMergeEnabled: true,
|
|
343
|
-
// This CLI runs no close gates.
|
|
344
|
-
gates: undefined,
|
|
345
|
-
elapsedSeconds: Math.round((Date.now() - startedAtMs) / 1000),
|
|
346
|
-
});
|
|
347
|
-
return await logConfirmResult(
|
|
348
|
-
{
|
|
349
|
-
storyId,
|
|
350
|
-
standalone: true,
|
|
351
|
-
action: waitOutcome.terminal,
|
|
352
|
-
resumed: true,
|
|
353
|
-
tail: waitOutcome.tail ?? null,
|
|
354
|
-
},
|
|
355
|
-
terminal,
|
|
356
|
-
config,
|
|
357
|
-
);
|
|
358
|
-
}
|
|
312
|
+
action: waitOutcome.terminal,
|
|
313
|
+
resumed: true,
|
|
314
|
+
tail: waitOutcome.tail ?? null,
|
|
315
|
+
},
|
|
316
|
+
terminal,
|
|
317
|
+
ctx.config,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
359
320
|
|
|
321
|
+
async function probeMergeOnce(ctx) {
|
|
322
|
+
const { storyId, storyBranch, baseBranch, prNumber, cwd, provider, config } =
|
|
323
|
+
ctx;
|
|
360
324
|
const confirmation = await confirmStoryMerged({
|
|
361
325
|
provider,
|
|
362
326
|
storyId,
|
|
@@ -365,9 +329,9 @@ export async function runConfirmMerge({
|
|
|
365
329
|
cwd,
|
|
366
330
|
config,
|
|
367
331
|
progress,
|
|
368
|
-
injectedGh: gh,
|
|
369
|
-
injectedNotify,
|
|
370
|
-
readPrMergeStateFn: injectedReadPrMergeState,
|
|
332
|
+
injectedGh: ctx.gh,
|
|
333
|
+
injectedNotify: ctx.injectedNotify,
|
|
334
|
+
readPrMergeStateFn: ctx.injectedReadPrMergeState,
|
|
371
335
|
});
|
|
372
336
|
|
|
373
337
|
// The same shared land tail close runs. Gated on `merged`, not
|
|
@@ -393,7 +357,7 @@ export async function runConfirmMerge({
|
|
|
393
357
|
prNumber,
|
|
394
358
|
confirmation,
|
|
395
359
|
tail,
|
|
396
|
-
elapsedSeconds:
|
|
360
|
+
elapsedSeconds: elapsedSeconds(ctx),
|
|
397
361
|
});
|
|
398
362
|
if (confirmation.action === 'done') {
|
|
399
363
|
progress('DONE', `✅ Story #${storyId} → agent::done (merged).`);
|
|
@@ -405,43 +369,108 @@ export async function runConfirmMerge({
|
|
|
405
369
|
);
|
|
406
370
|
}
|
|
407
371
|
|
|
372
|
+
/** Reads no argv: `main()` parses it and passes the values in. */
|
|
373
|
+
export async function runConfirmMerge({
|
|
374
|
+
storyId,
|
|
375
|
+
cwd,
|
|
376
|
+
pr,
|
|
377
|
+
wait = false,
|
|
378
|
+
maxWaitSeconds,
|
|
379
|
+
injectedProvider,
|
|
380
|
+
injectedConfig,
|
|
381
|
+
injectedGh,
|
|
382
|
+
injectedNotify,
|
|
383
|
+
injectedReadPrMergeState,
|
|
384
|
+
runConfirmMergePhaseFn = defaultRunConfirmMergePhase,
|
|
385
|
+
} = {}) {
|
|
386
|
+
if (!storyId) {
|
|
387
|
+
throw new Error(USAGE);
|
|
388
|
+
}
|
|
389
|
+
const startedAtMs = Date.now();
|
|
390
|
+
const resolvedCwd = path.resolve(cwd ?? PROJECT_ROOT);
|
|
391
|
+
const config = injectedConfig || resolveConfig({ cwd: resolvedCwd });
|
|
392
|
+
const storyBranch = getStoryBranch(storyId);
|
|
393
|
+
const ctx = {
|
|
394
|
+
storyId,
|
|
395
|
+
cwd: resolvedCwd,
|
|
396
|
+
startedAtMs,
|
|
397
|
+
config,
|
|
398
|
+
provider: injectedProvider || createProvider(config),
|
|
399
|
+
gh: injectedGh ?? defaultGh,
|
|
400
|
+
storyBranch,
|
|
401
|
+
baseBranch: config.project?.baseBranch ?? 'main',
|
|
402
|
+
maxWaitSeconds,
|
|
403
|
+
injectedNotify,
|
|
404
|
+
injectedReadPrMergeState,
|
|
405
|
+
runConfirmMergePhaseFn,
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
progress('INIT', `Confirming merge for standalone Story #${storyId}...`);
|
|
409
|
+
|
|
410
|
+
const prNumber = await resolveConfirmPrNumber({
|
|
411
|
+
pr,
|
|
412
|
+
storyBranch,
|
|
413
|
+
gh: ctx.gh,
|
|
414
|
+
});
|
|
415
|
+
if (prNumber == null) return await confirmWithoutPr(ctx);
|
|
416
|
+
const withPr = { ...ctx, prNumber };
|
|
417
|
+
return wait ? await resumeMergeWait(withPr) : await probeMergeOnce(withPr);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* A throw still emits a `failed` envelope: it is the landing surface's
|
|
422
|
+
* contract.
|
|
423
|
+
*
|
|
424
|
+
* @returns {Promise<number>}
|
|
425
|
+
*/
|
|
426
|
+
async function failWithEnvelope(err, phase, fullArgv) {
|
|
427
|
+
// Tolerant parse: a strict one would re-throw an argv rejection here.
|
|
428
|
+
const { args, error: argvError } = parseSprintArgsTolerant(fullArgv);
|
|
429
|
+
const storyId = Number(args.storyId);
|
|
430
|
+
// No story id: nothing to report an envelope about.
|
|
431
|
+
if (!Number.isInteger(storyId) || storyId <= 0) throw err;
|
|
432
|
+
const terminal = buildTerminalEnvelope({
|
|
433
|
+
storyId,
|
|
434
|
+
status: 'failed',
|
|
435
|
+
// An argv rejection precedes every phase.
|
|
436
|
+
phase: argvError ? 'init' : phase,
|
|
437
|
+
failure: { reason: String(err?.message ?? err) },
|
|
438
|
+
nextCommand: NEXT_COMMANDS.recover(storyId),
|
|
439
|
+
elapsedSeconds: 0,
|
|
440
|
+
});
|
|
441
|
+
Logger.error(
|
|
442
|
+
`[single-story-confirm-merge] Fatal error: ${formatCliError(err)}`,
|
|
443
|
+
);
|
|
444
|
+
emitTerminalEnvelope(terminal);
|
|
445
|
+
await emitTerminalFriction({ envelope: terminal, tool: CLI_SOURCE });
|
|
446
|
+
return exitCodeForTerminal(terminal);
|
|
447
|
+
}
|
|
448
|
+
|
|
408
449
|
/**
|
|
409
|
-
* Exit code comes from the
|
|
410
|
-
*
|
|
411
|
-
*
|
|
450
|
+
* Exit code comes from the envelope's status; `runAsCli` answers `--help`.
|
|
451
|
+
*
|
|
452
|
+
* @param {string[]} fullArgv
|
|
453
|
+
* @returns {Promise<number>}
|
|
412
454
|
*/
|
|
413
|
-
async function
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
455
|
+
export async function runConfirmMergeCli(fullArgv) {
|
|
456
|
+
let options;
|
|
457
|
+
try {
|
|
458
|
+
options = parseConfirmMergeArgv(fullArgv);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
return await failWithEnvelope(err, 'init', fullArgv);
|
|
417
461
|
}
|
|
418
462
|
try {
|
|
419
|
-
const outcome = await runConfirmMerge();
|
|
463
|
+
const outcome = await runConfirmMerge(options);
|
|
420
464
|
return exitCodeForTerminal(outcome?.terminal ?? { status: 'failed' });
|
|
421
465
|
} catch (err) {
|
|
422
|
-
|
|
423
|
-
const { args, error: argvError } = parseSprintArgsTolerant();
|
|
424
|
-
const storyId = Number(args.storyId);
|
|
425
|
-
// No story id: nothing to report an envelope about.
|
|
426
|
-
if (!Number.isInteger(storyId) || storyId <= 0) throw err;
|
|
427
|
-
const terminal = buildTerminalEnvelope({
|
|
428
|
-
storyId,
|
|
429
|
-
status: 'failed',
|
|
430
|
-
// An argv rejection precedes every phase.
|
|
431
|
-
phase: argvError ? 'init' : 'confirm-merge',
|
|
432
|
-
failure: { reason: String(err?.message ?? err) },
|
|
433
|
-
nextCommand: NEXT_COMMANDS.recover(storyId),
|
|
434
|
-
elapsedSeconds: 0,
|
|
435
|
-
});
|
|
436
|
-
Logger.error(
|
|
437
|
-
`[single-story-confirm-merge] Fatal error: ${formatCliError(err)}`,
|
|
438
|
-
);
|
|
439
|
-
emitTerminalEnvelope(terminal);
|
|
440
|
-
await emitTerminalFriction({ envelope: terminal, tool: CLI_SOURCE });
|
|
441
|
-
return exitCodeForTerminal(terminal);
|
|
466
|
+
return await failWithEnvelope(err, 'confirm-merge', fullArgv);
|
|
442
467
|
}
|
|
443
468
|
}
|
|
444
469
|
|
|
470
|
+
function main() {
|
|
471
|
+
return runConfirmMergeCli(process.argv);
|
|
472
|
+
}
|
|
473
|
+
|
|
445
474
|
runAsCli(import.meta.url, main, {
|
|
446
475
|
source: CLI_SOURCE,
|
|
447
476
|
propagateExitCode: true,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Project `.agents/agents/` and `.agents/local/agents/` into a flat
|
|
5
5
|
* `.claude/agents/` tree — the sibling of `sync-claude-commands.js`, with the
|
|
6
6
|
* same payload-wins shadowing and orphan-reap. A role agent runs on its own
|
|
7
|
-
* system prompt (no
|
|
7
|
+
* system prompt (no entry-doc `@`-import closure), which is the point of routing to it.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
// cli-opt-out: top-level-await script with no main() function — runAsCli wraps an async main, which doesn't apply here.
|
|
@@ -22,12 +22,12 @@ description:
|
|
|
22
22
|
|
|
23
23
|
## Activation
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
Invoked directly by the operator (`idea-refine` / `ideate`), ahead of
|
|
26
|
+
planning — it is not a step of
|
|
27
|
+
[`/mandrel-plan`](../../../workflows/mandrel-plan.md), whose Gate #1 stops only
|
|
28
|
+
for a HITL unknown or a duplicate. The skill sharpens freeform intent into the
|
|
29
|
+
canonical planning sections, so a saved one-pager feeds
|
|
30
|
+
`/mandrel-plan <path>` (seed-file mode) directly.
|
|
31
31
|
|
|
32
32
|
## Detailed Instructions
|
|
33
33
|
|
|
@@ -109,8 +109,7 @@ not a soft preference.
|
|
|
109
109
|
convenience to work around. Confirm authenticated state with a
|
|
110
110
|
`take_snapshot` before driving.
|
|
111
111
|
- **No headless fallback.** The chrome-devtools MCP surface is a host-provided
|
|
112
|
-
runtime dependency. If it is unavailable,
|
|
113
|
-
— never fall back to the retired headless BDD runner.
|
|
112
|
+
runtime dependency. If it is unavailable, report a clear error and stop.
|
|
114
113
|
|
|
115
114
|
## 4. Mode — Known-Scenario Sweep (`/qa-run`)
|
|
116
115
|
|
|
@@ -23,10 +23,11 @@ Per the core's Scope interpretation:
|
|
|
23
23
|
|
|
24
24
|
## Execution strategy
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Sequential inline execution is the fallback (see the
|
|
26
|
+
Dispatch this lens as one `subagent_type: auditor` call. Fan its dimensions
|
|
27
|
+
out across parallel `auditor` subagents (parallel-tooling Rule 3), merging
|
|
28
|
+
under the self-cross-check, only when the operator explicitly asks for
|
|
29
|
+
per-dimension fan-out. Sequential inline execution is the fallback (see the
|
|
30
|
+
core's Execution strategy).
|
|
30
31
|
|
|
31
32
|
## Step 0: Tool-first detection (mandatory — run before any LLM dimension)
|
|
32
33
|
|
|
@@ -62,11 +62,11 @@ with the config-driven target set above.
|
|
|
62
62
|
|
|
63
63
|
## Execution strategy
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Execution strategy).
|
|
65
|
+
Dispatch this lens as one `subagent_type: auditor` call. Fan its per-doc /
|
|
66
|
+
per-dimension verification out across parallel `auditor` subagents
|
|
67
|
+
(parallel-tooling Rule 3), merging under the self-cross-check, only when the
|
|
68
|
+
operator explicitly asks for per-dimension fan-out. Sequential inline
|
|
69
|
+
execution is the fallback (see the core's Execution strategy).
|
|
70
70
|
|
|
71
71
|
## Step 1: Deterministic Signal First
|
|
72
72
|
|