mandrel 2.31.0 → 2.32.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/README.md +13 -17
- package/.agents/agents/acceptance-critic.md +1 -2
- package/.agents/docs/SDLC.md +4 -4
- package/.agents/docs/agentrc-reference.json +61 -57
- package/.agents/docs/configuration.md +274 -227
- package/.agents/docs/execution-reference.md +13 -14
- package/.agents/docs/quality-gates.md +195 -23
- package/.agents/instructions.md +2 -5
- package/.agents/rules/git-conventions-reference.md +27 -27
- package/.agents/rules/git-conventions.md +4 -2
- package/.agents/rules/known-tooling-behavior.md +66 -30
- package/.agents/rules/testing-standards.md +35 -71
- package/.agents/runtime-deps.json +0 -1
- package/.agents/schemas/agentrc.schema.json +1939 -1400
- package/.agents/schemas/lifecycle/README.md +21 -14
- package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/scripts/README.md +7 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/bootstrap.js +28 -26
- package/.agents/scripts/check-baseline-drift.js +73 -13
- package/.agents/scripts/check-baseline-scope.js +362 -0
- package/.agents/scripts/check-dead-exports.js +9 -1
- package/.agents/scripts/check-gherkin-corpus.js +508 -0
- package/.agents/scripts/check-knip-entries.js +136 -0
- package/.agents/scripts/check-lifecycle-lint.js +36 -112
- package/.agents/scripts/check-schema-references.js +1 -1
- package/.agents/scripts/diagnose-friction.js +7 -4
- package/.agents/scripts/generate-config-docs.js +263 -171
- package/.agents/scripts/install-matrix-assert.js +0 -1
- package/.agents/scripts/lib/ITicketingProvider.js +0 -58
- package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
- package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
- package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
- package/.agents/scripts/lib/baselines/envelope.js +41 -60
- package/.agents/scripts/lib/baselines/git-base.js +30 -37
- package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
- package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
- package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
- package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
- package/.agents/scripts/lib/bdd-step-index.js +326 -0
- package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
- package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
- package/.agents/scripts/lib/checks/loop-health.js +9 -37
- package/.agents/scripts/lib/child-exec.js +193 -0
- package/.agents/scripts/lib/cli/standard-args.js +1 -1
- package/.agents/scripts/lib/cli-args.js +64 -0
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/close-validation/runner.js +3 -3
- package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
- package/.agents/scripts/lib/config/commands.js +3 -5
- package/.agents/scripts/lib/config/explain.js +5 -7
- package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
- package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
- package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
- package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
- package/.agents/scripts/lib/config/gates/index.js +5 -2
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
- package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
- package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
- package/.agents/scripts/lib/config/gates/shared.js +58 -9
- package/.agents/scripts/lib/config/github.js +0 -1
- package/.agents/scripts/lib/config/limits.js +3 -48
- package/.agents/scripts/lib/config/qa.js +105 -0
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
- package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
- package/.agents/scripts/lib/config-settings-schema.js +386 -39
- package/.agents/scripts/lib/crap-baseline-join.js +126 -9
- package/.agents/scripts/lib/crap-utils.js +84 -520
- package/.agents/scripts/lib/dead-exports-knip.js +79 -10
- package/.agents/scripts/lib/degraded-mode.js +2 -2
- package/.agents/scripts/lib/doc-tiers.js +3 -3
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
- package/.agents/scripts/lib/fs-walk.js +52 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/git-utils.js +16 -36
- package/.agents/scripts/lib/knip-entry-sync.js +469 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
- package/.agents/scripts/lib/observability/signal-validator.js +5 -85
- package/.agents/scripts/lib/observability/signals-writer.js +19 -62
- package/.agents/scripts/lib/observability/source-classifier.js +5 -7
- package/.agents/scripts/lib/observability/terse-result.js +3 -3
- package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
- package/.agents/scripts/lib/orchestration/code-review.js +2 -2
- package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
- package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
- package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
- package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
- package/.agents/scripts/lib/signals/detectors/common.js +1 -1
- package/.agents/scripts/lib/signals/index.js +8 -6
- package/.agents/scripts/lib/signals/schema.js +20 -25
- package/.agents/scripts/lib/signals/write.js +8 -8
- package/.agents/scripts/lib/story-body/story-body.js +12 -59
- package/.agents/scripts/lib/temp-retention.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
- package/.agents/scripts/lib/ticket-body-sections.js +4 -5
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
- package/.agents/scripts/lib/worktree-manager.js +2 -3
- package/.agents/scripts/lint-label-vocabulary.js +2 -24
- package/.agents/scripts/pr-watch-with-update.js +7 -5
- package/.agents/scripts/providers/github/cache.js +2 -2
- package/.agents/scripts/providers/github/comments.js +6 -28
- package/.agents/scripts/providers/github/compose.js +0 -15
- package/.agents/scripts/providers/github/errors.js +10 -27
- package/.agents/scripts/providers/github/request-helpers.js +1 -2
- package/.agents/scripts/providers/github/sub-issues.js +10 -218
- package/.agents/scripts/providers/github.js +4 -7
- package/.agents/scripts/prune-baseline-orphans.js +181 -0
- package/.agents/scripts/resolve-stories.js +0 -2
- package/.agents/scripts/run-lint.js +61 -61
- package/.agents/scripts/run-test-profile.js +6 -6
- package/.agents/scripts/run-verify.js +48 -30
- package/.agents/scripts/single-story-close.js +20 -0
- package/.agents/scripts/single-story-init.js +12 -35
- package/.agents/scripts/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +1 -11
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +2 -2
- package/.agents/workflows/helpers/audit-lens-core.md +11 -12
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
- package/.agents/workflows/helpers/code-review.md +3 -8
- package/.agents/workflows/helpers/deliver-reference.md +2 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
- package/.agents/workflows/mandrel-update.md +10 -10
- package/.agents/workflows/qa-assist.md +15 -20
- package/.agents/workflows/qa-explore.md +9 -8
- package/README.md +1 -1
- package/docs/CHANGELOG.md +42 -0
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
- package/package.json +12 -3
- package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
- package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
- package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
- package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
- package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
- package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
- package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
- package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
- package/.agents/scripts/check-gherkin-placeholders.js +0 -663
- package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
- package/.agents/scripts/lib/audit-suite/cli.js +0 -64
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
- package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
- package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
- package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
- package/.agents/scripts/lib/crap-baseline-index.js +0 -46
- package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
- package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
- package/.agents/scripts/lib/mutation/config-detector.js +0 -119
- package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
- package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
- package/.agents/scripts/lib/observability/active-story-env.js +0 -170
- package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
- package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
- package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
- package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
- package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
- package/.agents/scripts/lib/planning-corpus.js +0 -37
- package/.agents/scripts/lib/qa/coverage-report.js +0 -181
- package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
- package/.agents/scripts/lib/signals/detectors/index.js +0 -14
- package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
- package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
- package/.agents/scripts/lib/signals/read.js +0 -268
- package/.agents/scripts/lib/signals/span-tree.js +0 -291
- package/.agents/scripts/lib/story-lifecycle.js +0 -194
- package/.agents/scripts/lib/story-plan.js +0 -379
- package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
- package/.agents/scripts/lib/util/phase-timer.js +0 -163
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
- package/.agents/scripts/lint-baseline.js +0 -507
- package/.agents/scripts/providers/github/prs.js +0 -103
- package/.agents/scripts/signals-view.js +0 -309
- package/.agents/scripts/story-plan.js +0 -370
- package/.agents/scripts/sync-branch-from-base.js +0 -149
- package/.agents/scripts/validate-docs-freshness.js +0 -314
- package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
- package/.agents/workflows/helpers/signals.md +0 -112
|
@@ -16,13 +16,9 @@
|
|
|
16
16
|
* actually reaped and every close said it did. Best-effort means the close
|
|
17
17
|
* still succeeds on refusal; it does not mean the close may claim an
|
|
18
18
|
* outcome it never checked.
|
|
19
|
-
*
|
|
20
|
-
* Also clears the trace-hook env vars so subsequent tooling falls back to
|
|
21
|
-
* the no-op branch instead of pointing at a (now-reaped) worktree.
|
|
22
19
|
*/
|
|
23
20
|
|
|
24
21
|
import { Logger } from '../../../Logger.js';
|
|
25
|
-
import { clearActiveStoryEnv } from '../../../observability/active-story-env.js';
|
|
26
22
|
import { WorktreeManager as DefaultWorktreeManager } from '../../../worktree-manager.js';
|
|
27
23
|
|
|
28
24
|
/**
|
|
@@ -92,15 +88,5 @@ export async function reapWorktreePhase({
|
|
|
92
88
|
}
|
|
93
89
|
}
|
|
94
90
|
|
|
95
|
-
// Clear the trace-hook env vars so subsequent tooling falls back to the
|
|
96
|
-
// no-op branch instead of pointing at a (now-reaped) worktree.
|
|
97
|
-
try {
|
|
98
|
-
clearActiveStoryEnv({
|
|
99
|
-
logger: { warn: (m) => progress('ENV', `⚠️ ${m}`) },
|
|
100
|
-
});
|
|
101
|
-
} catch {
|
|
102
|
-
// Non-fatal.
|
|
103
|
-
}
|
|
104
|
-
|
|
105
91
|
return worktreeReaped;
|
|
106
92
|
}
|
|
@@ -29,6 +29,7 @@ import { parseCloseOptions, resolveWaitForMerge } from './phases/options.js';
|
|
|
29
29
|
import { ensurePullRequestWith } from './phases/pull-request.js';
|
|
30
30
|
import { pushStoryBranch } from './phases/push.js';
|
|
31
31
|
import { handleCriticalReviewBlock } from './phases/review-block.js';
|
|
32
|
+
import { handleOverriddenReviewBlock } from './phases/review-override.js';
|
|
32
33
|
import { reapWorktreePhase } from './phases/worktree-reap.js';
|
|
33
34
|
import { runWrongTreeGuardPhase } from './phases/wrong-tree-guard.js';
|
|
34
35
|
|
|
@@ -207,6 +208,8 @@ async function openAndReviewPr({
|
|
|
207
208
|
storyBranch,
|
|
208
209
|
baseBranch,
|
|
209
210
|
provider,
|
|
211
|
+
config,
|
|
212
|
+
overrideReviewBlock,
|
|
210
213
|
injectedGh,
|
|
211
214
|
injectedRunCodeReview,
|
|
212
215
|
setPhase = () => {},
|
|
@@ -222,6 +225,8 @@ async function openAndReviewPr({
|
|
|
222
225
|
cwd,
|
|
223
226
|
storyId,
|
|
224
227
|
storyTitle: story.title,
|
|
228
|
+
// Scanned for a declared `BREAKING CHANGE:` footer — see pull-request.js.
|
|
229
|
+
storyBody: story.body,
|
|
225
230
|
storyBranch,
|
|
226
231
|
baseBranch,
|
|
227
232
|
gh: injectedGh,
|
|
@@ -249,6 +254,27 @@ async function openAndReviewPr({
|
|
|
249
254
|
});
|
|
250
255
|
if (reviewOutcome.halted) {
|
|
251
256
|
const criticalCount = reviewOutcome.severity?.critical ?? 0;
|
|
257
|
+
// The sanctioned override. Checked BEFORE the blocked
|
|
258
|
+
// transition so an overridden run never touches `agent::blocked`: the
|
|
259
|
+
// Story is proceeding, and parking it would make the label lie for the
|
|
260
|
+
// rest of the close.
|
|
261
|
+
if (overrideReviewBlock) {
|
|
262
|
+
const override = await handleOverriddenReviewBlock({
|
|
263
|
+
provider,
|
|
264
|
+
storyId,
|
|
265
|
+
prUrl,
|
|
266
|
+
prNumber,
|
|
267
|
+
criticalCount,
|
|
268
|
+
reason: overrideReviewBlock,
|
|
269
|
+
config,
|
|
270
|
+
});
|
|
271
|
+
return {
|
|
272
|
+
prUrl,
|
|
273
|
+
prNumber,
|
|
274
|
+
alreadyMerged: false,
|
|
275
|
+
reviewOverride: override,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
252
278
|
await handleCriticalReviewBlock({
|
|
253
279
|
provider,
|
|
254
280
|
storyId,
|
|
@@ -257,10 +283,12 @@ async function openAndReviewPr({
|
|
|
257
283
|
});
|
|
258
284
|
throw new Error(
|
|
259
285
|
`[single-story-close] Story-scope review reported ${criticalCount} critical blocker(s) on PR ${prUrl}. ` +
|
|
260
|
-
'Auto-merge was not enabled. Remediate the findings posted to the PR and re-run `/deliver`.'
|
|
286
|
+
'Auto-merge was not enabled. Remediate the findings posted to the PR and re-run `/deliver`. ' +
|
|
287
|
+
'If you have reviewed a finding and judged it wrong, re-run with ' +
|
|
288
|
+
'`--override-review-block "<reason>"` rather than merging by hand.',
|
|
261
289
|
);
|
|
262
290
|
}
|
|
263
|
-
return { prUrl, prNumber, alreadyMerged: false };
|
|
291
|
+
return { prUrl, prNumber, alreadyMerged: false, reviewOverride: null };
|
|
264
292
|
}
|
|
265
293
|
|
|
266
294
|
async function releaseLease({
|
|
@@ -296,11 +324,9 @@ async function releaseLease({
|
|
|
296
324
|
* `runBaseSyncPhase`, and a critical-blocker review halt in
|
|
297
325
|
* `openAndReviewPr`) throw before the clean-close lease release at the
|
|
298
326
|
* tail of `runSingleStoryClose`, stranding the operator's lease
|
|
299
|
-
* indefinitely. The standalone lease
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* configured TTL), so a stranded claim is cleared only by `--steal` or
|
|
303
|
-
* de-assignment. That fail-closed-refuses a different operator who picks up
|
|
327
|
+
* indefinitely. The standalone lease has **no** TTL to expire by (Story
|
|
328
|
+
* #5006 deleted it): `acquireLease` refuses any foreign assignee outright,
|
|
329
|
+
* so a stranded claim is cleared only by `--steal` or de-assignment. That fail-closed-refuses a different operator who picks up
|
|
304
330
|
* the blocked Story — exactly the hand-off case. Releasing here closes
|
|
305
331
|
* that gap.
|
|
306
332
|
*
|
|
@@ -383,6 +409,7 @@ export async function runSingleStoryClose({
|
|
|
383
409
|
noWaitForMerge: noWaitForMergeParam,
|
|
384
410
|
maxWaitSeconds: maxWaitSecondsParam,
|
|
385
411
|
mergeWatchMode: mergeWatchModeParam,
|
|
412
|
+
overrideReviewBlock: overrideReviewBlockParam,
|
|
386
413
|
injectedProvider,
|
|
387
414
|
injectedConfig,
|
|
388
415
|
injectedNotify,
|
|
@@ -402,10 +429,11 @@ export async function runSingleStoryClose({
|
|
|
402
429
|
noWaitForMergeParam,
|
|
403
430
|
maxWaitSecondsParam,
|
|
404
431
|
mergeWatchModeParam,
|
|
432
|
+
overrideReviewBlockParam,
|
|
405
433
|
});
|
|
406
434
|
if (!options.storyId) {
|
|
407
435
|
throw new Error(
|
|
408
|
-
'Usage: node single-story-close.js --story <STORY_ID> [--cwd <main-repo>] [--skip-validation] [--skip-sync] [--no-auto-merge] [--wait-merge|--no-wait-merge] [--max-wait-seconds <n>] [--merge-watch-mode <sync|async>]',
|
|
436
|
+
'Usage: node single-story-close.js --story <STORY_ID> [--cwd <main-repo>] [--skip-validation] [--skip-sync] [--no-auto-merge] [--wait-merge|--no-wait-merge] [--max-wait-seconds <n>] [--merge-watch-mode <sync|async>] [--override-review-block <reason>]',
|
|
409
437
|
);
|
|
410
438
|
}
|
|
411
439
|
|
|
@@ -730,22 +758,25 @@ async function runClosePipeline({
|
|
|
730
758
|
leaseArgs,
|
|
731
759
|
);
|
|
732
760
|
|
|
733
|
-
const { prUrl, prNumber, alreadyMerged } =
|
|
734
|
-
(
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
761
|
+
const { prUrl, prNumber, alreadyMerged, reviewOverride } =
|
|
762
|
+
await releaseLeaseOnBlock(
|
|
763
|
+
() =>
|
|
764
|
+
openAndReviewPr({
|
|
765
|
+
cwd: options.cwd,
|
|
766
|
+
worktreePath,
|
|
767
|
+
story,
|
|
768
|
+
storyId: options.storyId,
|
|
769
|
+
storyBranch,
|
|
770
|
+
baseBranch,
|
|
771
|
+
provider,
|
|
772
|
+
config,
|
|
773
|
+
overrideReviewBlock: options.overrideReviewBlock,
|
|
774
|
+
injectedGh,
|
|
775
|
+
injectedRunCodeReview,
|
|
776
|
+
setPhase,
|
|
777
|
+
}),
|
|
778
|
+
leaseArgs,
|
|
779
|
+
);
|
|
749
780
|
// Reap the per-Story worktree BEFORE the arm (Story #4681). Arming runs
|
|
750
781
|
// `gh pr merge --auto --squash --delete-branch`, which — against an
|
|
751
782
|
// already-mergeable PR — merges immediately and then shells out to local
|
|
@@ -843,7 +874,10 @@ async function runClosePipeline({
|
|
|
843
874
|
gates: {
|
|
844
875
|
validation: options.skipValidation ? 'skipped' : 'passed',
|
|
845
876
|
baseSync: options.skipSync ? 'skipped' : 'passed',
|
|
846
|
-
|
|
877
|
+
// An overridden blocker reports `overridden`, never
|
|
878
|
+
// `passed`. The review DID fail; a human authorized shipping anyway, and
|
|
879
|
+
// the envelope is the machine-readable trail that says so.
|
|
880
|
+
codeReview: reviewOverride ? 'overridden' : 'passed',
|
|
847
881
|
},
|
|
848
882
|
};
|
|
849
883
|
|
|
@@ -24,18 +24,17 @@
|
|
|
24
24
|
* operator handle from config and delegates assignee mutation to the pure
|
|
25
25
|
* `ticket-lease.js` primitive.
|
|
26
26
|
*
|
|
27
|
-
* **Fail-closed
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* raced into automatically. Unclaimed and self-held tickets still proceed.
|
|
27
|
+
* **Fail-closed (audit #3513, hard-wired by Story #5006).** The lease
|
|
28
|
+
* primitive used to reclaim a foreign claim whose heartbeat was older than a
|
|
29
|
+
* TTL — and with no heartbeat source, *every* foreign claim looked stale, so
|
|
30
|
+
* the guard silently reclaimed any foreign assignee and was inert as a
|
|
31
|
+
* concurrency guard. The guard worked around that by anchoring the heartbeat
|
|
32
|
+
* to `now`; Story #5006 deleted the TTL outright, so the fail-closed
|
|
33
|
+
* behaviour is now the primitive's own: a foreign assignee refuses the take
|
|
34
|
+
* (naming the current owner) unless the operator passes `--steal` to forcibly
|
|
35
|
+
* transfer it. A genuinely abandoned claim is cleared by hand (or `--steal`)
|
|
36
|
+
* rather than raced into automatically. Unclaimed and self-held tickets still
|
|
37
|
+
* proceed.
|
|
39
38
|
*/
|
|
40
39
|
|
|
41
40
|
import {
|
|
@@ -75,21 +74,19 @@ export function resolveOperator(config) {
|
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
/**
|
|
78
|
-
* Acquire (or re-affirm
|
|
77
|
+
* Acquire (or re-affirm) the Story lease for the standalone path.
|
|
79
78
|
*
|
|
80
|
-
* **Fail-closed:**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* transfer it. Unclaimed and self-held tickets proceed without a write.
|
|
79
|
+
* **Fail-closed:** a foreign assignee refuses the take — the guard throws
|
|
80
|
+
* naming the current owner so the operator can coordinate. Pass
|
|
81
|
+
* `steal: true` (`--steal`) to forcibly transfer it. Unclaimed and self-held
|
|
82
|
+
* tickets proceed without a write.
|
|
85
83
|
*
|
|
86
84
|
* @param {object} opts
|
|
87
85
|
* @param {object} opts.provider Ticketing provider (getTicket/updateTicket).
|
|
88
86
|
* @param {number} opts.storyId Story ticket to claim.
|
|
89
|
-
* @param {object} opts.config Resolved config (operator handle
|
|
87
|
+
* @param {object} opts.config Resolved config (operator handle).
|
|
90
88
|
* @param {string} [opts.operator] Override the resolved operator (tests).
|
|
91
89
|
* @param {boolean} [opts.steal=false] Forcibly transfer a foreign claim.
|
|
92
|
-
* @param {number} [opts.now] Injectable clock (epoch ms) for tests.
|
|
93
90
|
* @returns {Promise<{ acquired: boolean, owner: string, previousOwner: string|null, reason: string }>}
|
|
94
91
|
* @throws {Error} When a foreign claim refuses the acquire (no `steal`).
|
|
95
92
|
*/
|
|
@@ -99,27 +96,19 @@ export async function acquireStoryLease({
|
|
|
99
96
|
config,
|
|
100
97
|
operator,
|
|
101
98
|
steal = false,
|
|
102
|
-
now,
|
|
103
99
|
}) {
|
|
104
100
|
const owner = operator ?? resolveOperator(config);
|
|
105
|
-
// Fail closed: with no live-heartbeat source on the standalone path, the
|
|
106
|
-
// shared kernel anchors `heartbeatAt` to the same `now` the primitive
|
|
107
|
-
// evaluates against, so `isClaimLive` returns true for any foreign owner
|
|
108
|
-
// and `acquireLease` refuses unless `steal` is set.
|
|
109
101
|
return acquireLeaseFailClosed({
|
|
110
102
|
provider,
|
|
111
103
|
ticketId: storyId,
|
|
112
104
|
operator: owner,
|
|
113
105
|
steal,
|
|
114
|
-
config,
|
|
115
|
-
now,
|
|
116
|
-
anchorHeartbeatToNow: true,
|
|
117
106
|
renderRefusal: (result) =>
|
|
118
107
|
`single-story lease: Story #${storyId} is currently held by @${result.owner}. ` +
|
|
119
108
|
'Another /deliver run owns this Story. Coordinate with that ' +
|
|
120
109
|
'operator, or re-run with --steal to forcibly transfer the claim once you ' +
|
|
121
|
-
'have confirmed the other run is dead. (
|
|
122
|
-
'
|
|
110
|
+
'have confirmed the other run is dead. (A foreign assignee always ' +
|
|
111
|
+
'blocks unless stolen.)',
|
|
123
112
|
});
|
|
124
113
|
}
|
|
125
114
|
|
|
@@ -142,5 +131,5 @@ export async function releaseStoryLease({
|
|
|
142
131
|
operator,
|
|
143
132
|
}) {
|
|
144
133
|
const owner = operator ?? resolveOperator(config);
|
|
145
|
-
return releaseLease({ provider, ticketId: storyId, operator: owner
|
|
134
|
+
return releaseLease({ provider, ticketId: storyId, operator: owner });
|
|
146
135
|
}
|
|
@@ -6,13 +6,27 @@
|
|
|
6
6
|
* Spec is treated as a sizing smell (the Story should be split or the Spec
|
|
7
7
|
* tightened), not as a reason to write temporary product docs.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* chars/token)
|
|
9
|
+
* This module also **owns** the §2 FinOps token estimator
|
|
10
|
+
* ({@link estimateTokens}, ~4 chars/token) — the one shared approximation every
|
|
11
|
+
* fixed framework ceiling speaks in (the Spec budget below, the plan-time
|
|
12
|
+
* sizing ceilings in `ticket-validator-sizing.js`, and the audit checklist
|
|
13
|
+
* threading budget). It used to live in a `context-envelope.js` SDK whose
|
|
14
|
+
* remaining surface had no live caller; the estimator is all that survived.
|
|
11
15
|
*
|
|
12
16
|
* @module lib/orchestration/spec-spill
|
|
13
17
|
*/
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Rough token estimate: ~4 characters per token. Deliberately cheap and
|
|
21
|
+
* deterministic — every ceiling that quotes "tokens" is quoting this number,
|
|
22
|
+
* so the approximation matters far less than every caller sharing one.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} text
|
|
25
|
+
* @returns {number}
|
|
26
|
+
*/
|
|
27
|
+
export function estimateTokens(text) {
|
|
28
|
+
return Math.ceil(String(text ?? '').length / 4);
|
|
29
|
+
}
|
|
16
30
|
|
|
17
31
|
/**
|
|
18
32
|
* Soft budget (estimated tokens) for an inline `## Spec`. ~1500 tokens ≈ 6KB —
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* phases/review-core.js — the shared Story-scope review spine.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from `phases/code-review.js` (Story #4603)
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* v2 path
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Extracted from the Epic-era `phases/code-review.js` (Story #4603) so the
|
|
5
|
+
* spine belonged to neither close path. `runStoryReviewCore` is the one
|
|
6
|
+
* implementation `runCodeReview` is called through; since Story #5006 retired
|
|
7
|
+
* the Epic-attached phase, its sole caller is the v2 standalone path
|
|
8
|
+
* (`single-story-close/phases/code-review.js#runStoryScopeReview`). It stays
|
|
9
|
+
* here — the shared-spine contract (Story #3653) is what keeps the maker-blind
|
|
10
|
+
* review invocation out of a phase file.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { countChangedLines } from '../../../audit-suite/index.js';
|
|
@@ -425,7 +425,8 @@ export function persistTerminalEnvelope(
|
|
|
425
425
|
* machine-readable contract — every invocation emits exactly ONE, and a
|
|
426
426
|
* headless caller parses it out of stdout to decide what happened.
|
|
427
427
|
* `Logger.info` is level-gated, so under the documented
|
|
428
|
-
* `AGENT_LOG_LEVEL=silent` (§
|
|
428
|
+
* `AGENT_LOG_LEVEL=silent` (execution-reference § Log-level control) the
|
|
429
|
+
* envelope silently vanished and the
|
|
429
430
|
* caller got a bare exit code: precisely the "no envelope at all" outcome
|
|
430
431
|
* Story #4543 exists to remove. A contract payload must not be suppressible
|
|
431
432
|
* by a verbosity knob.
|
|
@@ -45,7 +45,10 @@
|
|
|
45
45
|
* `body.verify` entries must either name a testing tier in parentheses
|
|
46
46
|
* drawn from `VERIFY_TIER_VALUES` (e.g. `npm run test (unit)`) or be the
|
|
47
47
|
* literal `manual:<reason>` escape hatch when the Story is genuinely
|
|
48
|
-
* unverifiable in isolation.
|
|
48
|
+
* unverifiable in isolation. `verify-tier-repair.js#normalizeVerifyTiers` runs
|
|
49
|
+
* first on the persist path (Story #5005) and **repairs** the entries whose
|
|
50
|
+
* tier `suggestVerifyFix` can infer, so the hard error below is reserved for
|
|
51
|
+
* the entries only the author can resolve.
|
|
49
52
|
*
|
|
50
53
|
* The errors are batched and surfaced as a single thrown Error so the
|
|
51
54
|
* planner can see every offending slug in one pass instead of fixing one
|
|
@@ -1,57 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ticket-lease.js — assignee-as-lease primitive (Story #3480, Epic #3457).
|
|
3
3
|
*
|
|
4
|
-
* The workflow-guards Feature (#3478) needs a way for one operator to take
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* compared against a configured TTL.
|
|
4
|
+
* The workflow-guards Feature (#3478) needs a way for one operator to take an
|
|
5
|
+
* exclusive claim on a ticket so two concurrent runs do not both drive the
|
|
6
|
+
* same Story. Rather than invent a new state column, the lease rides the
|
|
7
|
+
* ticket's existing **assignees** surface: the single assignee *is* the lease
|
|
8
|
+
* owner.
|
|
10
9
|
*
|
|
11
|
-
* **
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
* `
|
|
16
|
-
* which
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
10
|
+
* **A foreign claim is a refusal, full stop (Story #5006).** The lease shipped
|
|
11
|
+
* with a TTL: a claim whose owner's last heartbeat was older than
|
|
12
|
+
* `delivery.lease.ttlMs` counted as stale and was silently reclaimed. The
|
|
13
|
+
* `story.heartbeat` emitter that would have fed it was structurally inert (it
|
|
14
|
+
* demanded an `epicId >= 1` that v2, which has no Epics, never supplies) and
|
|
15
|
+
* was deleted, after which every guard pinned `heartbeatAt` to `now` so the
|
|
16
|
+
* liveness test always answered "live" — the TTL, the heartbeat parameter and
|
|
17
|
+
* the reclaim branch were an elaborate way of writing `true`. They are gone.
|
|
18
|
+
* A stranded claim is cleared with `--steal`, which is now the only way past
|
|
19
|
+
* a foreign owner.
|
|
21
20
|
*
|
|
22
|
-
* The
|
|
21
|
+
* The two exported operations are deliberately thin and provider-agnostic:
|
|
23
22
|
*
|
|
24
23
|
* - `acquireLease` — claim an unassigned ticket, re-affirm a self-held
|
|
25
|
-
* claim,
|
|
26
|
-
*
|
|
27
|
-
* `steal: true` — forcibly transfer a *live* foreign
|
|
28
|
-
* claim.
|
|
24
|
+
* claim, or — with `steal: true` — forcibly transfer a
|
|
25
|
+
* foreign claim.
|
|
29
26
|
* - `releaseLease` — clear the assignment, but only when the operator
|
|
30
27
|
* still holds it (a no-op once the ticket was
|
|
31
28
|
* reassigned elsewhere, so a late release never steals
|
|
32
29
|
* a claim back from whoever took over).
|
|
33
|
-
* - `describeLease` — a read-only snapshot of the current claim and its
|
|
34
|
-
* liveness, used by callers (and tests) to reason about
|
|
35
|
-
* a ticket without mutating it.
|
|
36
30
|
*
|
|
37
31
|
* Provider contract (a subset of `ITicketingProvider`):
|
|
38
32
|
* - `getTicket(id)` → `{ assignees: string[], ... }`
|
|
39
33
|
* - `updateTicket(id, { assignees })` writes the assignee list.
|
|
40
|
-
*
|
|
41
|
-
* Liveness seam: callers supply the owner's last-heartbeat epoch-ms via the
|
|
42
|
-
* `heartbeatAt` option (a number, or `null`/`undefined` when no heartbeat has
|
|
43
|
-
* ever been recorded for the current owner). Threading the timestamp in keeps
|
|
44
|
-
* this module pure and trivially unit-testable — it does not read any ledger
|
|
45
|
-
* itself. A claim with no heartbeat is treated as stale (reclaimable) by
|
|
46
|
-
* `isClaimLive`; note the live guards never take that branch, per the
|
|
47
|
-
* fail-closed anchoring described above.
|
|
48
|
-
*
|
|
49
|
-
* `now` is injectable (epoch ms) for deterministic tests; it defaults to
|
|
50
|
-
* `Date.now()`.
|
|
51
34
|
*/
|
|
52
35
|
|
|
53
|
-
import { resolveLeaseTtlMs } from '../config/limits.js';
|
|
54
|
-
|
|
55
36
|
/**
|
|
56
37
|
* The shipped, non-personal operator-identity placeholder (and its bare,
|
|
57
38
|
* post-normalise form). The committed `.agentrc.json` and the distributed
|
|
@@ -94,35 +75,6 @@ export function normalizeOperatorHandle(raw) {
|
|
|
94
75
|
return trimmed;
|
|
95
76
|
}
|
|
96
77
|
|
|
97
|
-
/**
|
|
98
|
-
* Decide whether a foreign claim is still "live" given the owner's last
|
|
99
|
-
* heartbeat and the configured TTL. A claim is live when a heartbeat exists
|
|
100
|
-
* and is no older than `ttlMs`. A missing heartbeat (`null`/`undefined`) or a
|
|
101
|
-
* heartbeat older than the TTL is stale and therefore reclaimable.
|
|
102
|
-
*
|
|
103
|
-
* The `heartbeatAt` seam is retained, but there is no longer any in-repo
|
|
104
|
-
* heartbeat *source*: the `story.heartbeat` emitter was structurally inert
|
|
105
|
-
* (it required an `epicId >= 1` that v2 never sets) and was deleted along
|
|
106
|
-
* with the ledger reader that scanned for it. Every live caller reaches this
|
|
107
|
-
* through `lease-guard-shared.acquireLeaseFailClosed` with
|
|
108
|
-
* `anchorHeartbeatToNow`, which pins `heartbeatAt` to `now` so ANY foreign
|
|
109
|
-
* claim reads live and the guard fails closed — a stranded claim is cleared
|
|
110
|
-
* with `--steal`, not by TTL expiry. The parameter stays because that
|
|
111
|
-
* anchoring is expressed through it.
|
|
112
|
-
*
|
|
113
|
-
* @param {object} args
|
|
114
|
-
* @param {number|null|undefined} args.heartbeatAt Owner's last heartbeat (epoch ms).
|
|
115
|
-
* @param {number} args.ttlMs Lease TTL in milliseconds.
|
|
116
|
-
* @param {number} args.now Current time (epoch ms).
|
|
117
|
-
* @returns {boolean}
|
|
118
|
-
*/
|
|
119
|
-
export function isClaimLive({ heartbeatAt, ttlMs, now }) {
|
|
120
|
-
if (typeof heartbeatAt !== 'number' || !Number.isFinite(heartbeatAt)) {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
return now - heartbeatAt <= ttlMs;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
78
|
/**
|
|
127
79
|
* Normalise the assignee list into a single current owner. The lease model is
|
|
128
80
|
* single-holder: the first assignee is authoritative. Returns `null` for an
|
|
@@ -141,10 +93,10 @@ export function currentOwner(assignees) {
|
|
|
141
93
|
*
|
|
142
94
|
* @param {string} op
|
|
143
95
|
* @param {object} opts
|
|
144
|
-
* @returns {{ provider: object, ticketId: number, operator: string
|
|
96
|
+
* @returns {{ provider: object, ticketId: number, operator: string }}
|
|
145
97
|
*/
|
|
146
98
|
function normaliseOpts(op, opts) {
|
|
147
|
-
const { provider, ticketId, operator
|
|
99
|
+
const { provider, ticketId, operator } = opts ?? {};
|
|
148
100
|
|
|
149
101
|
if (!provider || typeof provider.getTicket !== 'function') {
|
|
150
102
|
throw new Error(`${op}: provider with getTicket/updateTicket is required`);
|
|
@@ -156,45 +108,7 @@ function normaliseOpts(op, opts) {
|
|
|
156
108
|
throw new Error(`${op}: operator must be a non-empty string`);
|
|
157
109
|
}
|
|
158
110
|
|
|
159
|
-
|
|
160
|
-
const resolvedNow =
|
|
161
|
-
typeof now === 'number' && Number.isFinite(now) ? now : Date.now();
|
|
162
|
-
|
|
163
|
-
return { provider, ticketId, operator, ttlMs, now: resolvedNow };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Read-only snapshot of the current lease on a ticket. Never mutates.
|
|
168
|
-
*
|
|
169
|
-
* @param {object} opts
|
|
170
|
-
* @param {object} opts.provider Ticketing provider (getTicket).
|
|
171
|
-
* @param {number} opts.ticketId Ticket to inspect.
|
|
172
|
-
* @param {string} opts.operator The operator asking.
|
|
173
|
-
* @param {number|null} [opts.heartbeatAt] Owner's last heartbeat (epoch ms).
|
|
174
|
-
* @param {object} [opts.config] Resolved config (for TTL default).
|
|
175
|
-
* @param {number} [opts.ttlMs] Explicit TTL override (epoch ms).
|
|
176
|
-
* @param {number} [opts.now] Injectable clock (epoch ms).
|
|
177
|
-
* @returns {Promise<{
|
|
178
|
-
* ticketId: number,
|
|
179
|
-
* owner: string|null,
|
|
180
|
-
* heldByOperator: boolean,
|
|
181
|
-
* live: boolean,
|
|
182
|
-
* ttlMs: number,
|
|
183
|
-
* }>}
|
|
184
|
-
*/
|
|
185
|
-
export async function describeLease(opts) {
|
|
186
|
-
const { provider, ticketId, operator, ttlMs, now } = normaliseOpts(
|
|
187
|
-
'describeLease',
|
|
188
|
-
opts,
|
|
189
|
-
);
|
|
190
|
-
const ticket = await provider.getTicket(ticketId);
|
|
191
|
-
const owner = currentOwner(ticket?.assignees);
|
|
192
|
-
const heldByOperator = owner === operator;
|
|
193
|
-
const live =
|
|
194
|
-
owner === null
|
|
195
|
-
? false
|
|
196
|
-
: isClaimLive({ heartbeatAt: opts.heartbeatAt, ttlMs, now });
|
|
197
|
-
return { ticketId, owner, heldByOperator, live, ttlMs };
|
|
111
|
+
return { provider, ticketId, operator };
|
|
198
112
|
}
|
|
199
113
|
|
|
200
114
|
/**
|
|
@@ -205,10 +119,8 @@ export async function describeLease(opts) {
|
|
|
205
119
|
* `reason: 'unclaimed'`.
|
|
206
120
|
* - Operator already holds it → no write, `acquired: true`,
|
|
207
121
|
* `reason: 'already-held'`.
|
|
208
|
-
* -
|
|
122
|
+
* - Foreign claim, no steal → no write, `acquired: false`,
|
|
209
123
|
* `owner: <foreign>`, `reason: 'held'`.
|
|
210
|
-
* - Stale foreign claim → reassign operator, `acquired: true`,
|
|
211
|
-
* `reason: 'reclaimed'`.
|
|
212
124
|
* - Foreign claim + `steal:true` → reassign operator, `acquired: true`,
|
|
213
125
|
* `reason: 'stolen'`.
|
|
214
126
|
* - Lost a write race → a foreign login co-assigned between our
|
|
@@ -226,23 +138,16 @@ export async function describeLease(opts) {
|
|
|
226
138
|
* @param {object} opts.provider Ticketing provider.
|
|
227
139
|
* @param {number} opts.ticketId Ticket to claim.
|
|
228
140
|
* @param {string} opts.operator Operator acquiring the lease.
|
|
229
|
-
* @param {
|
|
230
|
-
* @param {boolean} [opts.steal=false] Transfer a live foreign claim.
|
|
231
|
-
* @param {object} [opts.config] Resolved config (TTL default).
|
|
232
|
-
* @param {number} [opts.ttlMs] Explicit TTL override.
|
|
233
|
-
* @param {number} [opts.now] Injectable clock.
|
|
141
|
+
* @param {boolean} [opts.steal=false] Transfer a foreign claim.
|
|
234
142
|
* @returns {Promise<{
|
|
235
143
|
* acquired: boolean,
|
|
236
144
|
* owner: string,
|
|
237
145
|
* previousOwner: string|null,
|
|
238
|
-
* reason: 'unclaimed'|'already-held'|'
|
|
146
|
+
* reason: 'unclaimed'|'already-held'|'stolen'|'held'|'lost-race',
|
|
239
147
|
* }>}
|
|
240
148
|
*/
|
|
241
149
|
export async function acquireLease(opts) {
|
|
242
|
-
const { provider, ticketId, operator
|
|
243
|
-
'acquireLease',
|
|
244
|
-
opts,
|
|
245
|
-
);
|
|
150
|
+
const { provider, ticketId, operator } = normaliseOpts('acquireLease', opts);
|
|
246
151
|
const steal = opts.steal === true;
|
|
247
152
|
|
|
248
153
|
const ticket = await provider.getTicket(ticketId);
|
|
@@ -269,9 +174,8 @@ export async function acquireLease(opts) {
|
|
|
269
174
|
};
|
|
270
175
|
}
|
|
271
176
|
|
|
272
|
-
// Foreign claim —
|
|
273
|
-
|
|
274
|
-
if (live && !steal) {
|
|
177
|
+
// Foreign claim — refuse unless the operator explicitly steals it.
|
|
178
|
+
if (!steal) {
|
|
275
179
|
return {
|
|
276
180
|
acquired: false,
|
|
277
181
|
owner,
|
|
@@ -285,7 +189,7 @@ export async function acquireLease(opts) {
|
|
|
285
189
|
ticketId,
|
|
286
190
|
operator,
|
|
287
191
|
previousOwner: owner,
|
|
288
|
-
reason:
|
|
192
|
+
reason: 'stolen',
|
|
289
193
|
});
|
|
290
194
|
}
|
|
291
195
|
|
|
@@ -359,9 +263,6 @@ async function claimAndVerify({
|
|
|
359
263
|
* @param {object} opts.provider Ticketing provider.
|
|
360
264
|
* @param {number} opts.ticketId Ticket to release.
|
|
361
265
|
* @param {string} opts.operator Operator releasing the lease.
|
|
362
|
-
* @param {object} [opts.config] Resolved config (TTL default).
|
|
363
|
-
* @param {number} [opts.ttlMs] Explicit TTL override.
|
|
364
|
-
* @param {number} [opts.now] Injectable clock.
|
|
365
266
|
* @returns {Promise<{
|
|
366
267
|
* released: boolean,
|
|
367
268
|
* owner: string|null,
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { parse as parseStoryBody } from '../story-body/story-body.js';
|
|
32
|
-
import { estimateTokens } from './
|
|
32
|
+
import { estimateTokens } from './spec-spill.js';
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Normalize a Story's `body` to the structured object the sizing layers
|
|
@@ -146,14 +146,14 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
146
146
|
// `--steal`, not by TTL expiry. One entry per ticket; re-acquires upsert
|
|
147
147
|
// in place.
|
|
148
148
|
'plan-lease',
|
|
149
|
-
// Story #4415 (Epic #4406) — the feedback-loop
|
|
150
|
-
// (`
|
|
151
|
-
// `cross-repo-deferred` comment
|
|
149
|
+
// Story #4415 (Epic #4406) — the feedback-loop graduator
|
|
150
|
+
// (`retro-proposals-graduator.js`) upserts a
|
|
151
|
+
// `cross-repo-deferred` comment listing findings that route
|
|
152
152
|
// to a different repository and were therefore not filed here. Replaces
|
|
153
153
|
// the prior log-line-only trace so the deferral survives the finalize
|
|
154
154
|
// run as a durable, operator-visible record. Discriminated by a
|
|
155
|
-
// `graduator="
|
|
156
|
-
//
|
|
155
|
+
// `graduator="<name>"` attr so independent graduators do not clobber each
|
|
156
|
+
// other's comment; re-runs upsert in place.
|
|
157
157
|
'cross-repo-deferred',
|
|
158
158
|
// Epic #4474 (PR3) / v2 Stage 3 — `plan-persist.js` upserts a single
|
|
159
159
|
// `plan-summary` comment on the primary Story at terminal persist
|