mandrel 2.0.0 → 2.2.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 +59 -28
- package/.agents/agents/acceptance-critic.md +20 -9
- package/.agents/agents/story-worker.md +45 -48
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +60 -46
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +33 -57
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +17 -19
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +10 -0
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
- package/.agents/schemas/agentrc.schema.json +35 -46
- package/.agents/schemas/audit-rules.json +59 -1
- package/.agents/schemas/audit-rules.schema.json +33 -1
- package/.agents/schemas/lifecycle/README.md +1 -2
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
- package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
- package/.agents/schemas/signal-event.schema.json +3 -3
- package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +24 -68
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/bootstrap.js +3 -3
- package/.agents/scripts/check-dead-exports.js +43 -104
- package/.agents/scripts/check-doc-links.js +2 -2
- package/.agents/scripts/check-lifecycle-lint.js +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- package/.agents/scripts/deliver-recover.js +122 -0
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/evidence-gate.js +20 -50
- package/.agents/scripts/generate-skills-index.js +17 -1
- package/.agents/scripts/generate-workflows-doc.js +4 -4
- package/.agents/scripts/lib/ITicketingProvider.js +1 -19
- package/.agents/scripts/lib/audit-suite/selector.js +323 -23
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
- package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
- package/.agents/scripts/lib/checks/index.js +1 -1
- package/.agents/scripts/lib/checks/loop-health.js +12 -11
- package/.agents/scripts/lib/checks/state.js +17 -248
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
- package/.agents/scripts/lib/cli-args.js +23 -2
- package/.agents/scripts/lib/close-validation/gates.js +13 -13
- package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
- package/.agents/scripts/lib/close-validation/runner.js +13 -21
- package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
- package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
- package/.agents/scripts/lib/config/delivery-routing.js +7 -6
- package/.agents/scripts/lib/config/explain.js +10 -16
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +15 -25
- package/.agents/scripts/lib/config/quality.js +11 -14
- package/.agents/scripts/lib/config/runners.js +8 -21
- package/.agents/scripts/lib/config/temp-paths.js +18 -56
- package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
- package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
- package/.agents/scripts/lib/config-settings-schema.js +48 -22
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- package/.agents/scripts/lib/duplicate-search.js +38 -7
- package/.agents/scripts/lib/findings/promote-finding.js +23 -14
- package/.agents/scripts/lib/format-generated-json.js +97 -0
- package/.agents/scripts/lib/framework-version.js +19 -189
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
- package/.agents/scripts/lib/git-utils.js +0 -14
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +0 -15
- package/.agents/scripts/lib/label-taxonomy.js +1 -12
- package/.agents/scripts/lib/observability/active-story-env.js +42 -163
- package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
- package/.agents/scripts/lib/observability/signal-validator.js +4 -4
- package/.agents/scripts/lib/observability/signals-writer.js +6 -82
- package/.agents/scripts/lib/observability/source-classifier.js +2 -2
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
- package/.agents/scripts/lib/orchestration/change-set.js +103 -0
- package/.agents/scripts/lib/orchestration/code-review.js +70 -191
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
- package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
- package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
- package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
- package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
- package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
- package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
- package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
- package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
- package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
- package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
- package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
- package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
- package/.agents/scripts/lib/planning-corpus.js +12 -286
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
- package/.agents/scripts/lib/signals/index.js +4 -17
- package/.agents/scripts/lib/signals/read.js +35 -35
- package/.agents/scripts/lib/signals/schema.js +8 -11
- package/.agents/scripts/lib/signals/span-tree.js +7 -7
- package/.agents/scripts/lib/signals/write.js +0 -1
- package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
- package/.agents/scripts/lib/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +8 -7
- package/.agents/scripts/lib/story-body/story-body.js +81 -13
- package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/ticket-body-sections.js +0 -14
- package/.agents/scripts/lib/validation-evidence.js +31 -59
- package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- package/.agents/scripts/plan-context.js +38 -7
- package/.agents/scripts/plan-critics.js +203 -0
- package/.agents/scripts/plan-persist.js +145 -35
- package/.agents/scripts/plan-run-epilogue.js +83 -38
- package/.agents/scripts/post-structured-comment.js +0 -38
- package/.agents/scripts/pr-watch-with-update.js +43 -22
- package/.agents/scripts/providers/github/compose.js +0 -1
- package/.agents/scripts/providers/github/errors.js +0 -19
- package/.agents/scripts/providers/github/issues.js +1 -11
- package/.agents/scripts/providers/github/mappers.js +5 -0
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -153
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/quality-preview.js +13 -6
- package/.agents/scripts/resolve-stories.js +236 -0
- package/.agents/scripts/run-coverage.js +4 -1
- package/.agents/scripts/run-lint.js +2 -2
- package/.agents/scripts/run-verify.js +31 -2
- package/.agents/scripts/signals-view.js +9 -10
- package/.agents/scripts/single-story-close.js +173 -18
- package/.agents/scripts/single-story-confirm-merge.js +288 -15
- package/.agents/scripts/single-story-init.js +6 -10
- package/.agents/scripts/stories-wave-tick.js +380 -53
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/update-ticket-state.js +8 -50
- package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
- package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
- package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
- package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
- package/.agents/skills/core/scope-triage/SKILL.md +3 -0
- package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
- package/.agents/skills/core/security-and-hardening/reference.md +375 -0
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
- package/.agents/workflows/audit-architecture.md +3 -4
- package/.agents/workflows/audit-clean-code.md +4 -4
- package/.agents/workflows/audit-documentation.md +4 -5
- package/.agents/workflows/audit-lighthouse.md +8 -0
- package/.agents/workflows/audit-navigability.md +10 -0
- package/.agents/workflows/audit-performance.md +2 -3
- package/.agents/workflows/audit-quality.md +8 -9
- package/.agents/workflows/audit-security.md +1 -2
- package/.agents/workflows/audit-seo.md +10 -0
- package/.agents/workflows/audit-ux-ui.md +7 -0
- package/.agents/workflows/deliver.md +133 -45
- package/.agents/workflows/git-cleanup.md +2 -2
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
- package/.agents/workflows/helpers/code-review.md +14 -12
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +209 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +239 -19
- package/.agents/workflows/qa-assist.md +6 -6
- package/.agents/workflows/qa-explore.md +3 -3
- package/.agents/workflows/qa-run.md +1 -5
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +62 -0
- package/lib/cli/registry.js +262 -19
- package/lib/cli/sync-agents.js +157 -0
- package/lib/cli/sync-commands.js +115 -6
- package/lib/cli/sync.js +168 -6
- package/lib/cli/update.js +105 -8
- package/lib/cli/version-helpers.js +131 -0
- package/lib/migrations/README.md +7 -5
- package/lib/migrations/index.js +17 -9
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
- package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
- package/package.json +2 -2
- package/.agents/schemas/epic-perf-report.schema.json +0 -89
- package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
- package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
- package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
- package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -53
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
- package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
- package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
- package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
- package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
- package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
- package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
- package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
- package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
- package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
- package/.agents/scripts/resolve-plan-run.js +0 -117
- package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
|
@@ -22,10 +22,6 @@ import fs from 'node:fs/promises';
|
|
|
22
22
|
import { parseArgs } from 'node:util';
|
|
23
23
|
import { runAsCli } from './lib/cli-utils.js';
|
|
24
24
|
import { resolveConfig } from './lib/config-resolver.js';
|
|
25
|
-
import {
|
|
26
|
-
outboxPathFor,
|
|
27
|
-
postCommentOrBuffer,
|
|
28
|
-
} from './lib/orchestration/bookkeeping-outbox.js';
|
|
29
25
|
import {
|
|
30
26
|
assertValidStructuredCommentType,
|
|
31
27
|
upsertStructuredComment,
|
|
@@ -41,11 +37,6 @@ Flags:
|
|
|
41
37
|
retro, epic-run-state, wave-0-start) (required).
|
|
42
38
|
--body-file Path to a file containing the markdown body (required).
|
|
43
39
|
--provider Provider name (default: inferred from .agentrc.json github block).
|
|
44
|
-
--buffer Headless mode: buffer this upsert to the per-Epic bookkeeping
|
|
45
|
-
outbox instead of posting live. Requires --epic. Drained to
|
|
46
|
-
GitHub once at finalize by bookkeeping-reconcile.js (Epic
|
|
47
|
-
#4476). Omit for attended runs (posts live, unchanged).
|
|
48
|
-
--epic <id> Epic id owning the outbox (required with --buffer).
|
|
49
40
|
--help Show this message.
|
|
50
41
|
`;
|
|
51
42
|
|
|
@@ -73,8 +64,6 @@ export function parseArgv(argv) {
|
|
|
73
64
|
marker: { type: 'string' },
|
|
74
65
|
'body-file': { type: 'string' },
|
|
75
66
|
provider: { type: 'string' },
|
|
76
|
-
buffer: { type: 'boolean' },
|
|
77
|
-
epic: { type: 'string' },
|
|
78
67
|
help: { type: 'boolean' },
|
|
79
68
|
},
|
|
80
69
|
strict: false,
|
|
@@ -126,33 +115,6 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
126
115
|
: config;
|
|
127
116
|
const provider = createProvider(effectiveConfig);
|
|
128
117
|
|
|
129
|
-
// Headless buffering (Epic #4476 M5): route the upsert to the per-Epic
|
|
130
|
-
// outbox instead of a live GitHub round-trip. Requires --epic to locate the
|
|
131
|
-
// outbox; finalize's bookkeeping-reconcile.js drains it once. Attended runs
|
|
132
|
-
// (no --buffer) are unchanged.
|
|
133
|
-
if (values.buffer) {
|
|
134
|
-
const epicId = Number.parseInt(values.epic ?? '', 10);
|
|
135
|
-
if (!Number.isInteger(epicId) || epicId <= 0) {
|
|
136
|
-
process.stderr.write(
|
|
137
|
-
'[post-structured-comment] --buffer requires --epic <id>.\n',
|
|
138
|
-
);
|
|
139
|
-
process.exit(2);
|
|
140
|
-
}
|
|
141
|
-
assertValidStructuredCommentType(values.marker);
|
|
142
|
-
const { buffered } = await postCommentOrBuffer({
|
|
143
|
-
provider,
|
|
144
|
-
ticketId,
|
|
145
|
-
marker: values.marker,
|
|
146
|
-
body,
|
|
147
|
-
headless: true,
|
|
148
|
-
outboxPath: outboxPathFor(epicId, config),
|
|
149
|
-
});
|
|
150
|
-
process.stdout.write(
|
|
151
|
-
`${JSON.stringify({ success: true, ticketId, type: values.marker, buffered })}\n`,
|
|
152
|
-
);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
118
|
const envelope = await runPostStructuredComment({
|
|
157
119
|
ticketId,
|
|
158
120
|
type: values.marker,
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* pr-watch-with-update.js — the single CI-watch mechanism for
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `gh pr checks --watch` from the single-Story path so both drive this
|
|
7
|
-
* one CLI.
|
|
3
|
+
* pr-watch-with-update.js — the single CI-watch mechanism for the Story
|
|
4
|
+
* delivery path (`helpers/deliver-story.md` Step 4). Story #4358 retired
|
|
5
|
+
* the bare `gh pr checks --watch` so every caller drives this one CLI.
|
|
8
6
|
*
|
|
9
7
|
* Polls the PR's required checks to a terminal state and auto-recovers
|
|
10
8
|
* from `mergeStateStatus: BEHIND` (via bounded `gh pr update-branch`
|
|
@@ -17,10 +15,13 @@
|
|
|
17
15
|
* - GREEN — every required check terminal + green → exit 0.
|
|
18
16
|
* - RED — one or more required checks genuinely failed → exit 1
|
|
19
17
|
* IMMEDIATELY, consuming no resume budget. On red the CLI
|
|
20
|
-
* writes `temp/
|
|
18
|
+
* writes `temp/story-<id>-ci-digest.{json,md}` (failing check
|
|
21
19
|
* name, run id, a `gh run view --log-failed` tail, and a
|
|
22
20
|
* coarse classification) and prints the red-green
|
|
23
|
-
* remediation handoff.
|
|
21
|
+
* remediation handoff. The digest is scoped by filename, so
|
|
22
|
+
* it requires `--story` (Story #4539: the digest was
|
|
23
|
+
* Epic-scoped and therefore never written on the only
|
|
24
|
+
* delivery path v2 has).
|
|
24
25
|
* - STILL-RUNNING — the poll cap fired with checks still pending and
|
|
25
26
|
* none failed; the watcher re-armed up to
|
|
26
27
|
* `delivery.ci.watch.maxResumes` times, then returned a
|
|
@@ -35,8 +36,8 @@
|
|
|
35
36
|
* CLI flags override config; config overrides the framework fallback.
|
|
36
37
|
*
|
|
37
38
|
* Usage:
|
|
38
|
-
* node .agents/scripts/pr-watch-with-update.js --pr <n>
|
|
39
|
-
* [--
|
|
39
|
+
* node .agents/scripts/pr-watch-with-update.js --pr <n> --story <id>
|
|
40
|
+
* [--repo owner/repo] [--max-updates N] [--poll-interval-ms MS]
|
|
40
41
|
* [--max-polls N] [--max-resumes N]
|
|
41
42
|
*/
|
|
42
43
|
import { spawnSync } from 'node:child_process';
|
|
@@ -161,13 +162,33 @@ function resolveRunId({ prRef, checkName, cwd, spawnFn = spawnSync }) {
|
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Resolve which ticket the digest is keyed to. Story #4539: the digest used
|
|
167
|
+
* to be Epic-scoped by filename and returned `null` without an epic id — so
|
|
168
|
+
* on the v2 Story path (which has no Epic and invokes the watch with `--pr`
|
|
169
|
+
* alone) a red check wrote no digest at all, despite the module header
|
|
170
|
+
* advertising one. v2.0.0 removed the Epic tier; Story scope is the only
|
|
171
|
+
* scope.
|
|
172
|
+
*
|
|
173
|
+
* @param {{ storyId?: number|string|null }} opts
|
|
174
|
+
* @returns {{ kind: 'story', id: number } | null}
|
|
175
|
+
*/
|
|
176
|
+
export function resolveDigestScope({ storyId = null } = {}) {
|
|
177
|
+
if (storyId == null || String(storyId).length === 0) return null;
|
|
178
|
+
const parsed = Number.parseInt(String(storyId), 10);
|
|
179
|
+
return Number.isInteger(parsed) && parsed > 0
|
|
180
|
+
? { kind: 'story', id: parsed }
|
|
181
|
+
: null;
|
|
182
|
+
}
|
|
183
|
+
|
|
164
184
|
/**
|
|
165
185
|
* Write the CI failure digest (`.json` + `.md`) for a red watch. Returns
|
|
166
|
-
* the two paths written
|
|
167
|
-
* digest is
|
|
186
|
+
* the two paths written, or `null` when no story id was supplied (the
|
|
187
|
+
* digest is scoped by filename and has nothing to key on). Exported for
|
|
188
|
+
* tests.
|
|
168
189
|
*
|
|
169
190
|
* @param {object} opts
|
|
170
|
-
* @param {number|string|null} opts.
|
|
191
|
+
* @param {number|string|null} [opts.storyId] The v2 delivery scope.
|
|
171
192
|
* @param {number} opts.prNumber
|
|
172
193
|
* @param {Array<{name:string, outcome:string}>} opts.failures
|
|
173
194
|
* @param {string} opts.tempRoot
|
|
@@ -178,7 +199,7 @@ function resolveRunId({ prRef, checkName, cwd, spawnFn = spawnSync }) {
|
|
|
178
199
|
* @returns {{ jsonPath: string, mdPath: string } | null}
|
|
179
200
|
*/
|
|
180
201
|
export function writeCiDigest({
|
|
181
|
-
|
|
202
|
+
storyId = null,
|
|
182
203
|
prNumber,
|
|
183
204
|
failures,
|
|
184
205
|
tempRoot,
|
|
@@ -187,13 +208,14 @@ export function writeCiDigest({
|
|
|
187
208
|
runIdFn = resolveRunId,
|
|
188
209
|
logTailFn = ghRunLogTail,
|
|
189
210
|
}) {
|
|
190
|
-
|
|
211
|
+
const scope = resolveDigestScope({ storyId });
|
|
212
|
+
if (!scope) return null;
|
|
191
213
|
const primary = failures[0] ?? { name: 'unknown', outcome: 'failure' };
|
|
192
214
|
const runId = runIdFn({ prRef, checkName: primary.name, cwd });
|
|
193
215
|
const logTail = logTailFn({ runId, cwd });
|
|
194
216
|
const classification = classifyFailure(primary.name);
|
|
195
217
|
const digest = {
|
|
196
|
-
|
|
218
|
+
storyId: scope.id,
|
|
197
219
|
prNumber,
|
|
198
220
|
failingCheck: primary.name,
|
|
199
221
|
failingOutcome: primary.outcome,
|
|
@@ -205,12 +227,12 @@ export function writeCiDigest({
|
|
|
205
227
|
};
|
|
206
228
|
const dir = path.isAbsolute(tempRoot) ? tempRoot : path.join(cwd, tempRoot);
|
|
207
229
|
mkdirSync(dir, { recursive: true });
|
|
208
|
-
const base =
|
|
230
|
+
const base = `${scope.kind}-${scope.id}-ci-digest`;
|
|
209
231
|
const jsonPath = path.join(dir, `${base}.json`);
|
|
210
232
|
const mdPath = path.join(dir, `${base}.md`);
|
|
211
233
|
writeFileSync(jsonPath, `${JSON.stringify(digest, null, 2)}\n`);
|
|
212
234
|
const md = [
|
|
213
|
-
`# CI failure digest —
|
|
235
|
+
`# CI failure digest — Story #${scope.id} (PR #${prNumber})`,
|
|
214
236
|
'',
|
|
215
237
|
`- **Failing check:** \`${digest.failingCheck}\` (${digest.failingOutcome})`,
|
|
216
238
|
`- **Run id:** ${runId ?? 'unresolved'}`,
|
|
@@ -247,7 +269,6 @@ export function writeCiDigest({
|
|
|
247
269
|
* @param {object} opts
|
|
248
270
|
* @param {number} opts.prNumber
|
|
249
271
|
* @param {string|null} [opts.repo]
|
|
250
|
-
* @param {number|string|null} [opts.epicId] Epic id for the red-path digest.
|
|
251
272
|
* @param {number|string} [opts.maxUpdates]
|
|
252
273
|
* @param {number|string} [opts.pollIntervalMs]
|
|
253
274
|
* @param {number|string} [opts.maxPolls]
|
|
@@ -266,7 +287,7 @@ export function writeCiDigest({
|
|
|
266
287
|
export async function runPrWatch({
|
|
267
288
|
prNumber,
|
|
268
289
|
repo = null,
|
|
269
|
-
|
|
290
|
+
storyId = null,
|
|
270
291
|
maxUpdates,
|
|
271
292
|
pollIntervalMs,
|
|
272
293
|
maxPolls,
|
|
@@ -377,7 +398,7 @@ export async function runPrWatch({
|
|
|
377
398
|
let digestPaths = null;
|
|
378
399
|
try {
|
|
379
400
|
digestPaths = writeDigestFn({
|
|
380
|
-
|
|
401
|
+
storyId,
|
|
381
402
|
prNumber,
|
|
382
403
|
failures,
|
|
383
404
|
tempRoot: effectiveTempRoot,
|
|
@@ -415,7 +436,7 @@ async function main() {
|
|
|
415
436
|
options: {
|
|
416
437
|
pr: { type: 'string' },
|
|
417
438
|
repo: { type: 'string' },
|
|
418
|
-
|
|
439
|
+
story: { type: 'string' },
|
|
419
440
|
'max-updates': { type: 'string' },
|
|
420
441
|
'poll-interval-ms': { type: 'string' },
|
|
421
442
|
'max-polls': { type: 'string' },
|
|
@@ -426,7 +447,7 @@ async function main() {
|
|
|
426
447
|
return runPrWatch({
|
|
427
448
|
prNumber: Number.parseInt(values.pr ?? '', 10),
|
|
428
449
|
repo: values.repo ?? null,
|
|
429
|
-
|
|
450
|
+
storyId: values.story ?? null,
|
|
430
451
|
maxUpdates: values['max-updates'],
|
|
431
452
|
pollIntervalMs: values['poll-interval-ms'],
|
|
432
453
|
maxPolls: values['max-polls'],
|
|
@@ -272,22 +272,3 @@ export const REMOVE_SUB_ISSUE_MUTATION = `
|
|
|
272
272
|
subIssue { number }
|
|
273
273
|
}
|
|
274
274
|
}`;
|
|
275
|
-
|
|
276
|
-
// Story #2982 — native parent lookup for the cascade upward walk. The
|
|
277
|
-
// inverse of `SUB_ISSUES_QUERY`: given a child Issue node, fetch the
|
|
278
|
-
// `parent` field exposed by the Sub-Issues feature so `cascadeCompletion`
|
|
279
|
-
// can resolve a parent even when the child body lacks the `parent: #N`
|
|
280
|
-
// orchestrator footer.
|
|
281
|
-
export const PARENT_ISSUE_QUERY = `query($id: ID!) {
|
|
282
|
-
node(id: $id) {
|
|
283
|
-
... on Issue {
|
|
284
|
-
parent {
|
|
285
|
-
number
|
|
286
|
-
databaseId
|
|
287
|
-
id
|
|
288
|
-
title
|
|
289
|
-
state
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}`;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Owns the remaining read-side surface that did not belong to any of the
|
|
5
5
|
* earlier six gateways: the raw GraphQL shim (`graphql` / `_ghGraphql`),
|
|
6
|
-
* epic
|
|
6
|
+
* epic reads (`getEpic`), repository-wide label scans
|
|
7
7
|
* (`listIssuesByLabel`), branch existence probes (`branchExists`), and the
|
|
8
8
|
* three-strategy sub-ticket aggregator (`getSubTickets`).
|
|
9
9
|
*
|
|
@@ -151,16 +151,6 @@ export class IssuesGateway {
|
|
|
151
151
|
}));
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
/**
|
|
155
|
-
* Historical Epic enumeration surface. Stage 5 removed the Epic type from
|
|
156
|
-
* the taxonomy; callers that have not yet collapsed their planning corpus
|
|
157
|
-
* reads receive an empty list rather than querying a deleted label.
|
|
158
|
-
*/
|
|
159
|
-
/* node:coverage ignore next */
|
|
160
|
-
async getEpics(_filters = {}) {
|
|
161
|
-
return [];
|
|
162
|
-
}
|
|
163
|
-
|
|
164
154
|
/**
|
|
165
155
|
* @field-manifest /repos/{owner}/{repo}/issues/{n}: number, id, node_id,
|
|
166
156
|
* title, body, labels, state
|
|
@@ -35,6 +35,11 @@ export function issueToTicket(issue) {
|
|
|
35
35
|
labelSet: new Set(labels),
|
|
36
36
|
assignees: (issue.assignees ?? []).map((a) => a.login),
|
|
37
37
|
state: issue.state,
|
|
38
|
+
// `completed` | `not_planned` | `reopened` | null. `state` alone cannot
|
|
39
|
+
// tell a Story that landed from one closed as superseded — both read
|
|
40
|
+
// `closed` — so the close path needs this to avoid reporting a
|
|
41
|
+
// never-merged Story as landed.
|
|
42
|
+
stateReason: issue.state_reason ?? null,
|
|
38
43
|
};
|
|
39
44
|
}
|
|
40
45
|
|
|
@@ -30,7 +30,6 @@ import { concurrentMap } from '../../lib/util/concurrent-map.js';
|
|
|
30
30
|
import {
|
|
31
31
|
ADD_SUB_ISSUE_MUTATION,
|
|
32
32
|
classifyGithubError as defaultClassifyGithubError,
|
|
33
|
-
PARENT_ISSUE_QUERY,
|
|
34
33
|
REMOVE_SUB_ISSUE_MUTATION,
|
|
35
34
|
SUB_ISSUES_QUERY,
|
|
36
35
|
withTransientRetry,
|
|
@@ -134,52 +133,6 @@ export class SubIssueGateway {
|
|
|
134
133
|
return childIds;
|
|
135
134
|
}
|
|
136
135
|
|
|
137
|
-
/**
|
|
138
|
-
* Strategy 2 — native GitHub Sub-Issues, inverse direction. Given a
|
|
139
|
-
* child Issue node, return the parent's issue number (or `null` when
|
|
140
|
-
* the child has no native parent link, or the feature is disabled on
|
|
141
|
-
* this repo). Story #2982: used by `cascadeCompletion` as a third
|
|
142
|
-
* fallback so a Story whose body lost the `parent: #N` orchestrator
|
|
143
|
-
* footer still cascades upward to its true Sub-Issue parent.
|
|
144
|
-
*
|
|
145
|
-
* @param {string} childNodeId GraphQL node ID of the child Issue.
|
|
146
|
-
* @param {number} childNumber Issue number, for log context only.
|
|
147
|
-
* @returns {Promise<number|null>}
|
|
148
|
-
*/
|
|
149
|
-
async getNativeParent(childNodeId, childNumber) {
|
|
150
|
-
if (!childNodeId) return null;
|
|
151
|
-
try {
|
|
152
|
-
const data = await withTransientRetry(
|
|
153
|
-
() =>
|
|
154
|
-
this._ghGraphql(
|
|
155
|
-
PARENT_ISSUE_QUERY,
|
|
156
|
-
{ id: childNodeId },
|
|
157
|
-
{ headers: { 'GraphQL-Features': 'sub_issues' } },
|
|
158
|
-
),
|
|
159
|
-
{
|
|
160
|
-
label: `getNativeParent child=#${childNumber}`,
|
|
161
|
-
classify: this._classify,
|
|
162
|
-
onRetry: defaultRetryWarn,
|
|
163
|
-
},
|
|
164
|
-
);
|
|
165
|
-
const parent = data?.node?.parent;
|
|
166
|
-
if (!parent || typeof parent.number !== 'number') return null;
|
|
167
|
-
return parent.number;
|
|
168
|
-
} catch (err) {
|
|
169
|
-
const category = this._classify(err);
|
|
170
|
-
if (category === 'feature-disabled') {
|
|
171
|
-
Logger.warn(
|
|
172
|
-
`[GitHubProvider] sub-issues parent lookup unavailable (child #${childNumber})`,
|
|
173
|
-
);
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
Logger.error(
|
|
177
|
-
`[GitHubProvider] native parent lookup failed (child #${childNumber}, category=${category}): ${err.message}`,
|
|
178
|
-
);
|
|
179
|
-
throw err;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
136
|
/**
|
|
184
137
|
* Establish the native sub-issue link between `parentNumber` and the
|
|
185
138
|
* child identified by `childNodeId`. Retries on transient errors with
|
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
*
|
|
9
9
|
* The gateway is constructed with a `{ gh, owner, repo, hooks }` object so
|
|
10
10
|
* cross-gateway concerns can be threaded in without bloating the constructor
|
|
11
|
-
* signature.
|
|
12
|
-
*
|
|
13
|
-
* link a freshly-created child as a native sub-issue) and `addItemToProject`
|
|
14
|
-
* (from the projects-v2 shim, used by `createTicket` to add the new issue
|
|
15
|
-
* to the configured Project V2).
|
|
11
|
+
* signature. The `addItemToProject` hook (from the projects-v2 shim) adds a
|
|
12
|
+
* newly-created issue to the configured Project V2.
|
|
16
13
|
*
|
|
17
|
-
*
|
|
14
|
+
* Story #4545 deleted `createTicket` and its `composeStoryBody` helper — the
|
|
15
|
+
* Epic-hierarchy write surface. It composed the exact `Epic: #N` footer that
|
|
16
|
+
* `pr-base-guard.js` hard-refuses at delivery, so the framework retained the
|
|
17
|
+
* ability to generate work it would then reject; it had no production caller
|
|
18
|
+
* (`/plan` persists through the bare `createIssue` below).
|
|
19
|
+
*
|
|
20
|
+
* Public surface: `GitHubProvider.createIssue / getTicket /
|
|
18
21
|
* getTickets / updateTicket / getTicketDependencies / primeTicketCache /
|
|
19
22
|
* invalidateTicket` all delegate to the same-named methods on this class.
|
|
20
23
|
*
|
|
@@ -22,9 +25,7 @@
|
|
|
22
25
|
*/
|
|
23
26
|
|
|
24
27
|
import { parseBlockedBy, parseBlocks } from '../../lib/dependency-parser.js';
|
|
25
|
-
import { stampFrameworkVersion } from '../../lib/framework-version.js';
|
|
26
28
|
import { Logger } from '../../lib/Logger.js';
|
|
27
|
-
import { TYPE_LABELS } from '../../lib/label-constants.js';
|
|
28
29
|
import { addIssueToBoard } from './board-add.js';
|
|
29
30
|
import { createInlineTicketCache } from './cache.js';
|
|
30
31
|
import { withTransientRetry } from './errors.js';
|
|
@@ -43,70 +44,6 @@ import {
|
|
|
43
44
|
*/
|
|
44
45
|
const SEARCH_PAGE_CAP = 10;
|
|
45
46
|
|
|
46
|
-
/**
|
|
47
|
-
* Compose the final markdown body for a created ticket. Under the 2-tier
|
|
48
|
-
* hierarchy (Epic → Story), `body` is always a string supplied
|
|
49
|
-
* by the caller (the decomposer, the spec planner, or the reconciler-apply
|
|
50
|
-
* engine). This helper appends the canonical orchestrator footer
|
|
51
|
-
* (`parent: #<n>` / `Epic: #<m>` / `blocked by #<x>`) byte-stable with the
|
|
52
|
-
* format consumers (manifest, close-gate, dispatcher) parse.
|
|
53
|
-
*
|
|
54
|
-
* Story #3186 — inlined here when the Task-tier body-renderer helpers
|
|
55
|
-
* were retired. The structured-body (object) path is gone: Stories carry
|
|
56
|
-
* inline `acceptance[]` / `verify[]`
|
|
57
|
-
* arrays on the Story body authored by the decomposer; there is no
|
|
58
|
-
* server-side rendering of a four-section payload at create time.
|
|
59
|
-
*
|
|
60
|
-
* Story #4382 — this is also where a Story body is stamped, once, with the
|
|
61
|
-
* running Mandrel framework version and authoring date (hidden `mandrel_version`
|
|
62
|
-
* / `authored_at` meta field + a visible `> 🏷️ Authored with Mandrel …`
|
|
63
|
-
* marker) via {@link stampFrameworkVersion}. The stamp is immutable: a body
|
|
64
|
-
* that already carries a version (e.g. a reconciler re-create) is preserved
|
|
65
|
-
* verbatim. The `stamp` override exists for deterministic tests; production
|
|
66
|
-
* callers omit it so the running version and today's date are used.
|
|
67
|
-
*
|
|
68
|
-
* @param {{
|
|
69
|
-
* body: string,
|
|
70
|
-
* parentId: number,
|
|
71
|
-
* epicId?: number,
|
|
72
|
-
* dependencies?: number[],
|
|
73
|
-
* stamp?: { version?: string, authoredAt?: string } | false,
|
|
74
|
-
* }} opts
|
|
75
|
-
* @returns {string}
|
|
76
|
-
*
|
|
77
|
-
* Story #3958 — this is the single owner of the `blocked by #N` footer.
|
|
78
|
-
* Callers (the reconciler-apply create path, the spec planner) pass
|
|
79
|
-
* resolved dependency issue numbers via `dependencies` and supply a body
|
|
80
|
-
* WITHOUT any pre-appended footer. A caller that also appends a
|
|
81
|
-
* `blocked by` block to `body` would double every dependency line.
|
|
82
|
-
*/
|
|
83
|
-
export function composeStoryBody({
|
|
84
|
-
body,
|
|
85
|
-
parentId,
|
|
86
|
-
epicId,
|
|
87
|
-
dependencies = [],
|
|
88
|
-
stamp,
|
|
89
|
-
}) {
|
|
90
|
-
const rawHead = typeof body === 'string' ? body : '';
|
|
91
|
-
// `stamp === false` → footer-only recomposition: the caller (the reconciler
|
|
92
|
-
// UPDATE/diff path) owns stamp preservation itself and must NOT introduce a
|
|
93
|
-
// fresh authoring stamp, which would churn or bump the version on every
|
|
94
|
-
// reconcile. Every other call is a create — stamp once (immutably).
|
|
95
|
-
const head =
|
|
96
|
-
stamp === false ? rawHead : stampFrameworkVersion(rawHead, stamp ?? {});
|
|
97
|
-
const lines = ['---', `parent: #${parentId}`];
|
|
98
|
-
if (epicId !== undefined && epicId !== null) {
|
|
99
|
-
lines.push(`Epic: #${epicId}`);
|
|
100
|
-
}
|
|
101
|
-
if (dependencies.length > 0) {
|
|
102
|
-
lines.push('');
|
|
103
|
-
for (const dep of dependencies) {
|
|
104
|
-
lines.push(`blocked by #${dep}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return `${head}\n\n${lines.join('\n')}`;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
47
|
export class TicketGateway {
|
|
111
48
|
/**
|
|
112
49
|
* @param {{
|
|
@@ -114,7 +51,6 @@ export class TicketGateway {
|
|
|
114
51
|
* owner: string,
|
|
115
52
|
* repo: string,
|
|
116
53
|
* hooks?: {
|
|
117
|
-
* addSubIssue?: (parentNumber: number, childNodeId: string) => Promise<unknown>,
|
|
118
54
|
* addItemToProject?: (nodeId: string) => Promise<unknown>,
|
|
119
55
|
* getProjectNumber?: () => number|null,
|
|
120
56
|
* },
|
|
@@ -314,81 +250,10 @@ export class TicketGateway {
|
|
|
314
250
|
// ---------------------------------------------------------------------------
|
|
315
251
|
|
|
316
252
|
/**
|
|
317
|
-
* Create a
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
* After the POST, opportunistically link the child as a native sub-issue
|
|
322
|
-
* (via the `addSubIssue` hook — retried internally on the sub-issue
|
|
323
|
-
* gateway) and add it to the configured Project V2 (best-effort —
|
|
324
|
-
* failures warn but do not fail the create).
|
|
325
|
-
*
|
|
326
|
-
* @field-manifest POST /repos/{owner}/{repo}/issues: number, id, node_id,
|
|
327
|
-
* html_url
|
|
328
|
-
*/
|
|
329
|
-
/* node:coverage ignore next */
|
|
330
|
-
async createTicket(parentId, ticketData) {
|
|
331
|
-
const epicId = ticketData.epicId || parentId;
|
|
332
|
-
const renderedBody = composeStoryBody({
|
|
333
|
-
body: ticketData.body ?? '',
|
|
334
|
-
parentId,
|
|
335
|
-
epicId,
|
|
336
|
-
dependencies: ticketData.dependencies ?? [],
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
// Inject TYPE_LABELS.STORY so a spec that omits the labels array cannot
|
|
340
|
-
// produce an unlabeled, undispatchable Story. Dedupe to avoid duplicates
|
|
341
|
-
// when the caller already carries the label.
|
|
342
|
-
const callerLabels = ticketData.labels ?? [];
|
|
343
|
-
const labels = callerLabels.includes(TYPE_LABELS.STORY)
|
|
344
|
-
? callerLabels
|
|
345
|
-
: [TYPE_LABELS.STORY, ...callerLabels];
|
|
346
|
-
const result = await this._gh.api({
|
|
347
|
-
method: 'POST',
|
|
348
|
-
endpoint: `/repos/${this.owner}/${this.repo}/issues`,
|
|
349
|
-
body: {
|
|
350
|
-
title: ticketData.title,
|
|
351
|
-
body: renderedBody,
|
|
352
|
-
labels,
|
|
353
|
-
},
|
|
354
|
-
});
|
|
355
|
-
const issue = parseApiJson(result);
|
|
356
|
-
this._listCache.clear();
|
|
357
|
-
|
|
358
|
-
let subIssueLinked = false;
|
|
359
|
-
let subIssueError = null;
|
|
360
|
-
try {
|
|
361
|
-
if (typeof this._hooks.addSubIssue === 'function') {
|
|
362
|
-
await this._hooks.addSubIssue(parentId, issue.node_id);
|
|
363
|
-
subIssueLinked = true;
|
|
364
|
-
}
|
|
365
|
-
} catch (err) {
|
|
366
|
-
subIssueError = err;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
await addIssueToBoard({
|
|
370
|
-
nodeId: issue.node_id,
|
|
371
|
-
issueNumber: issue.number,
|
|
372
|
-
getProjectNumber: this._hooks.getProjectNumber,
|
|
373
|
-
addItemToProject: this._hooks.addItemToProject,
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
return {
|
|
377
|
-
id: issue.number,
|
|
378
|
-
internalId: issue.id,
|
|
379
|
-
nodeId: issue.node_id,
|
|
380
|
-
url: issue.html_url,
|
|
381
|
-
subIssueLinked,
|
|
382
|
-
subIssueError,
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Create a **bare** issue — no `parent: #N` footer composition and no
|
|
388
|
-
* sub-issue link. Serves the standalone create paths that bypass
|
|
389
|
-
* `createTicket`'s Story-shaped body rendering: the `/plan`
|
|
390
|
-
* persist step and the `/plan` Phase 4 Epic open
|
|
391
|
-
* (`openEpicFromOnePager`'s `createIssue` port).
|
|
253
|
+
* Create a **bare** issue — no footer composition and no sub-issue link.
|
|
254
|
+
* Since Story #4545 this is the *only* create path: `/plan` persist and the
|
|
255
|
+
* `/plan` Phase 4 Epic open (`openEpicFromOnePager`'s `createIssue` port)
|
|
256
|
+
* both route through it.
|
|
392
257
|
*
|
|
393
258
|
* After the POST, the new issue is added to the configured Project V2
|
|
394
259
|
* board via the shared `addIssueToBoard` helper (Story #3822) —
|
|
@@ -396,6 +261,17 @@ export class TicketGateway {
|
|
|
396
261
|
* so board membership never depends on GitHub's "Auto-add to project"
|
|
397
262
|
* built-in workflow.
|
|
398
263
|
*
|
|
264
|
+
* The POST is wrapped in `withTransientRetry` (Story #4541) so it absorbs
|
|
265
|
+
* the same 502/429/ECONNRESET blips the read surfaces already do. It used
|
|
266
|
+
* to post bare, which made a single transient failure at story *k* of *N*
|
|
267
|
+
* abort `/plan` persist with `1..k-1` already live on the tracker.
|
|
268
|
+
*
|
|
269
|
+
* Retry alone is not sufficient for that failure mode — a POST whose
|
|
270
|
+
* response is lost would double-create on the retry — so the caller
|
|
271
|
+
* (`plan-persist`'s `createStoryIssues`) carries the idempotency half via
|
|
272
|
+
* a plan fingerprint it looks up before creating. Retry narrows the
|
|
273
|
+
* window; the fingerprint closes it.
|
|
274
|
+
*
|
|
399
275
|
* @field-manifest POST /repos/{owner}/{repo}/issues: number, id, node_id,
|
|
400
276
|
* html_url
|
|
401
277
|
*
|
|
@@ -410,11 +286,15 @@ export class TicketGateway {
|
|
|
410
286
|
* }>}
|
|
411
287
|
*/
|
|
412
288
|
async createIssue({ title, body, labels = [] }) {
|
|
413
|
-
const result = await
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
289
|
+
const result = await withTransientRetry(
|
|
290
|
+
() =>
|
|
291
|
+
this._gh.api({
|
|
292
|
+
method: 'POST',
|
|
293
|
+
endpoint: `/repos/${this.owner}/${this.repo}/issues`,
|
|
294
|
+
body: { title, body, labels },
|
|
295
|
+
}),
|
|
296
|
+
{ label: `createIssue "${title}"`, onRetry: defaultRetryWarn },
|
|
297
|
+
);
|
|
418
298
|
const issue = parseApiJson(result);
|
|
419
299
|
this._listCache.clear();
|
|
420
300
|
|
|
@@ -80,9 +80,23 @@ export class GitHubProvider extends ITicketingProvider {
|
|
|
80
80
|
return this._memoizedToken;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
/**
|
|
84
|
+
* The identifiers a native dependency-edge writer needs, handed over as an
|
|
85
|
+
* explicit interface (Story #4544).
|
|
86
|
+
*
|
|
87
|
+
* `providers/github/blocked-by-add.js` talks to the dependencies REST API
|
|
88
|
+
* directly, so it needs the `gh` facade plus `owner`/`repo` — none of which
|
|
89
|
+
* the `ITicketingProvider` surface exposes. Its caller lives in the
|
|
90
|
+
* orchestration layer (`plan-persist/story-ops.js`), and the alternative was
|
|
91
|
+
* for that caller to reach through `provider._gh` — a private-by-convention
|
|
92
|
+
* field — from outside this module. Naming the hand-off here keeps the
|
|
93
|
+
* coupling declared and greppable instead of incidental: the provider
|
|
94
|
+
* decides what it lends out, and the field stays private.
|
|
95
|
+
*
|
|
96
|
+
* @returns {{ gh: object, owner: string, repo: string }}
|
|
97
|
+
*/
|
|
98
|
+
getDependencyWriteContext() {
|
|
99
|
+
return { gh: this._gh, owner: this.owner, repo: this.repo };
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
static isInsufficientScopes(err) {
|
|
@@ -100,7 +114,6 @@ const DELEGATIONS = [
|
|
|
100
114
|
['graphql', 'issues.ghGraphql'],
|
|
101
115
|
['searchIssues', 'issues.searchIssues'],
|
|
102
116
|
['listIssuesByLabel', 'issues.listIssuesByLabel'],
|
|
103
|
-
['getEpics', 'issues.getEpics'],
|
|
104
117
|
['getEpic', 'issues.getEpic'],
|
|
105
118
|
['branchExists', 'issues.branchExists'],
|
|
106
119
|
['getSubTickets', 'issues.getSubTickets'],
|
|
@@ -108,12 +121,10 @@ const DELEGATIONS = [
|
|
|
108
121
|
['getTickets', 'tickets.getTickets'],
|
|
109
122
|
['getTicket', 'tickets.getTicket'],
|
|
110
123
|
['getTicketDependencies', 'tickets.getTicketDependencies'],
|
|
111
|
-
['createTicket', 'tickets.createTicket'],
|
|
112
124
|
['createIssue', 'tickets.createIssue'],
|
|
113
125
|
['updateTicket', 'tickets.updateTicket'],
|
|
114
126
|
['_applyLabelMutations', 'tickets._applyLabelMutations'],
|
|
115
127
|
['_getNativeSubIssues', 'subIssues.getNativeSubIssues'],
|
|
116
|
-
['_getNativeParent', 'subIssues.getNativeParent'],
|
|
117
128
|
['addSubIssue', 'subIssues.addSubIssue'],
|
|
118
129
|
['removeSubIssue', 'subIssues.removeSubIssue'],
|
|
119
130
|
['reconcileSubIssueLinks', 'subIssues.reconcileSubIssueLinks'],
|
|
@@ -35,18 +35,25 @@ import {
|
|
|
35
35
|
* present without a value. Returns `null` when the flag is absent so callers
|
|
36
36
|
* can fall through to the gate scripts' own diff defaults.
|
|
37
37
|
*
|
|
38
|
+
* **Last occurrence wins** (Story #4603). `npm run <alias> -- --changed-since <base>`
|
|
39
|
+
* appends the operator's flag *after* any flag baked into the npm script, so a
|
|
40
|
+
* first-wins scan silently discarded the operator's base and compared against
|
|
41
|
+
* the script's hardcoded one instead — reporting a false green for a branch the
|
|
42
|
+
* gate had never actually scored. Last-wins matches the convention every
|
|
43
|
+
* mainstream CLI parser follows for repeated scalar flags, and makes the
|
|
44
|
+
* npm-alias passthrough behave the way its callers already assume.
|
|
45
|
+
*
|
|
38
46
|
* @param {string[]} argv
|
|
39
47
|
* @returns {string | null}
|
|
40
48
|
*/
|
|
41
49
|
export function parseChangedSinceArg(argv) {
|
|
50
|
+
let resolved = null;
|
|
42
51
|
for (let i = 0; i < argv.length; i += 1) {
|
|
43
|
-
if (argv[i]
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return 'HEAD';
|
|
47
|
-
}
|
|
52
|
+
if (argv[i] !== '--changed-since') continue;
|
|
53
|
+
const next = argv[i + 1];
|
|
54
|
+
resolved = next && !next.startsWith('--') ? next : 'HEAD';
|
|
48
55
|
}
|
|
49
|
-
return
|
|
56
|
+
return resolved;
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
/**
|