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
|
@@ -3,7 +3,10 @@ import { detectCycle } from '../Graph.js';
|
|
|
3
3
|
import { gitSpawn } from '../git-utils.js';
|
|
4
4
|
|
|
5
5
|
import { Logger } from '../Logger.js';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
parse as parseStoryBody,
|
|
8
|
+
StoryBodyParseError,
|
|
9
|
+
} from '../story-body/story-body.js';
|
|
7
10
|
import { validateStoryFileAssumptions } from './file-assumptions.js';
|
|
8
11
|
import {
|
|
9
12
|
computeConflictFindings,
|
|
@@ -43,6 +46,95 @@ function collectPathsFromText(text, paths) {
|
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Parse a Story's serialized markdown body, translating a
|
|
51
|
+
* `StoryBodyParseError` into a `ValidationError` that names the offending
|
|
52
|
+
* **section** and **entry** (Story #4541).
|
|
53
|
+
*
|
|
54
|
+
* `StoryBodyParseError` already carries `field` (the section the parser was
|
|
55
|
+
* reading) and `raw` (the entry text that failed); this lifts both into an
|
|
56
|
+
* operator-legible message and a structured `violation` payload so an
|
|
57
|
+
* authoring loop can point at the exact bullet instead of re-deriving it
|
|
58
|
+
* from a downstream freshness miss.
|
|
59
|
+
*
|
|
60
|
+
* @param {object} story Story whose `body` is a non-empty markdown string.
|
|
61
|
+
* @returns {object} The structured body.
|
|
62
|
+
* @throws {ValidationError} `code: 'story-body-unparseable'`.
|
|
63
|
+
*/
|
|
64
|
+
function parseStoryBodyOrThrow(story) {
|
|
65
|
+
try {
|
|
66
|
+
return parseStoryBody(story.body).body;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (!(err instanceof StoryBodyParseError)) throw err;
|
|
69
|
+
const slug = story.slug ?? '<unknown>';
|
|
70
|
+
const section = err.field ?? 'body';
|
|
71
|
+
const entry = err.raw ?? null;
|
|
72
|
+
const entryLine = entry === null ? '' : `\n entry: ${entry}`;
|
|
73
|
+
const violation = { slug, section, entry, reason: err.message };
|
|
74
|
+
const error = new ValidationError(
|
|
75
|
+
`Cross-Validation Failed: Story "${slug}" has an unparseable body — ` +
|
|
76
|
+
`the ## ${section} section could not be read: ${err.message}` +
|
|
77
|
+
`${entryLine}\n\nFix the offending entry; this is a malformed body, ` +
|
|
78
|
+
'not a stale path reference.',
|
|
79
|
+
{ violations: [violation] },
|
|
80
|
+
);
|
|
81
|
+
error.code = 'story-body-unparseable';
|
|
82
|
+
error.violations = [violation];
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Refuse the plan when any Story's serialized body cannot be parsed, before
|
|
89
|
+
* either git-probe gate runs (Story #4541). Ordering matters: the freshness
|
|
90
|
+
* gate consults `body.changes` for its net-new whitelist, so an unparseable
|
|
91
|
+
* body used to reach the operator as a freshness miss naming declared paths.
|
|
92
|
+
*
|
|
93
|
+
* @param {{ tickets: object[] }} opts
|
|
94
|
+
* @throws {ValidationError} `code: 'story-body-unparseable'` on the first
|
|
95
|
+
* offending Story.
|
|
96
|
+
*/
|
|
97
|
+
function assertStoryBodiesParse({ tickets }) {
|
|
98
|
+
for (const story of (tickets ?? []).filter((t) => t.type === 'story')) {
|
|
99
|
+
if (typeof story.body !== 'string' || story.body.trim().length === 0) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
parseStoryBodyOrThrow(story);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resolve every acceptance line a Story declares, across both authoring
|
|
108
|
+
* shapes (Story #4541).
|
|
109
|
+
*
|
|
110
|
+
* The canonical shape is a **serialized string body** with the criteria at
|
|
111
|
+
* the ticket's **top level** — the machine contract persist syncs into the
|
|
112
|
+
* body. `validateAcceptanceSubjectPrefix` used to read `body.acceptance` on
|
|
113
|
+
* an object body only, so on every real plan it scanned nothing and the gate
|
|
114
|
+
* silently passed. Union both sources (deduplicated) so the gate fires on
|
|
115
|
+
* whichever surface the author used.
|
|
116
|
+
*
|
|
117
|
+
* @param {object} story
|
|
118
|
+
* @returns {string[]}
|
|
119
|
+
*/
|
|
120
|
+
function resolveAcceptanceLines(story) {
|
|
121
|
+
const lines = new Set();
|
|
122
|
+
if (Array.isArray(story?.acceptance)) {
|
|
123
|
+
for (const item of story.acceptance) lines.add(String(item ?? ''));
|
|
124
|
+
}
|
|
125
|
+
const body = story?.body;
|
|
126
|
+
let bodyAcceptance = null;
|
|
127
|
+
if (typeof body === 'string' && body.trim().length > 0) {
|
|
128
|
+
bodyAcceptance = parseStoryBodyOrThrow(story).acceptance;
|
|
129
|
+
} else if (body !== null && typeof body === 'object') {
|
|
130
|
+
bodyAcceptance = body.acceptance;
|
|
131
|
+
}
|
|
132
|
+
if (Array.isArray(bodyAcceptance)) {
|
|
133
|
+
for (const item of bodyAcceptance) lines.add(String(item ?? ''));
|
|
134
|
+
}
|
|
135
|
+
return [...lines];
|
|
136
|
+
}
|
|
137
|
+
|
|
46
138
|
function collectTaskPathReferences(task) {
|
|
47
139
|
const paths = new Set();
|
|
48
140
|
const body = task.body;
|
|
@@ -82,7 +174,7 @@ function collectTaskPathReferences(task) {
|
|
|
82
174
|
* path out of the prose.
|
|
83
175
|
* 3. **Object form** — `{ path: "<path>", assumption: "creates" | ... }`,
|
|
84
176
|
* introduced by Story #2636 as the canonical declaration shape and
|
|
85
|
-
* documented in `
|
|
177
|
+
* documented in `lib/templates/decomposer-prompts.js`. The path is
|
|
86
178
|
* trusted verbatim.
|
|
87
179
|
*
|
|
88
180
|
* Only `body.changes` (and `body.references`) is consulted —
|
|
@@ -100,15 +192,16 @@ function collectTaskChangesPaths(task) {
|
|
|
100
192
|
// arrays before scanning. Without this, a string body causes the
|
|
101
193
|
// object-form branch below to fall through on every item, leaving the
|
|
102
194
|
// freshness gate blind to declared paths.
|
|
195
|
+
//
|
|
196
|
+
// Story #4541: a parse failure is NOT swallowed here. Swallowing it
|
|
197
|
+
// returned an empty whitelist, so a single malformed `## Changes` entry
|
|
198
|
+
// surfaced downstream as "files do not exist at main" naming the very
|
|
199
|
+
// paths the Story *had* declared — a misdiagnosis that cost two authoring
|
|
200
|
+
// round-trips. `assertStoryBodiesParse` runs before the freshness gate and
|
|
201
|
+
// owns that failure with a named error; the throw here is the same error
|
|
202
|
+
// for any caller that drives `validateAcFreshness` directly.
|
|
103
203
|
if (typeof body === 'string' && body.trim().length > 0) {
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
parsed = parseStoryBody(body).body;
|
|
107
|
-
} catch {
|
|
108
|
-
// Unparseable body — no paths to whitelist; the freshness gate will
|
|
109
|
-
// catch any real references in the text scan below.
|
|
110
|
-
return paths;
|
|
111
|
-
}
|
|
204
|
+
const parsed = parseStoryBodyOrThrow(task);
|
|
112
205
|
for (const arrName of ['changes', 'references']) {
|
|
113
206
|
const arr = parsed[arrName];
|
|
114
207
|
if (!Array.isArray(arr)) continue;
|
|
@@ -307,11 +400,16 @@ const SUBJECT_PREFIX_RE = /Commit subject begins with ['"`]([^'"`]+):['"`]/g;
|
|
|
307
400
|
* the form `baseline-refresh` is rejected because no Conventional-Commits
|
|
308
401
|
* type starts with that token.
|
|
309
402
|
*
|
|
310
|
-
* Only
|
|
403
|
+
* Only acceptance criteria are scanned; `body.goal` / `body.verify` /
|
|
311
404
|
* `body.changes` are not commit-subject prescriptions by convention and
|
|
312
405
|
* scanning them would surface false positives from prose that happens to
|
|
313
406
|
* quote a forbidden prefix while explaining why it's forbidden.
|
|
314
407
|
*
|
|
408
|
+
* Both authoring shapes are covered (Story #4541): the canonical top-level
|
|
409
|
+
* `acceptance[]` on a serialized string body, and the pre-serialize
|
|
410
|
+
* `body.acceptance[]` object shape. Scanning only the latter made the gate
|
|
411
|
+
* inert on every real plan.
|
|
412
|
+
*
|
|
315
413
|
* @param {object} opts
|
|
316
414
|
* @param {object[]} opts.tickets - Validated ticket hierarchy.
|
|
317
415
|
* @throws {ValidationError} when one or more Story acceptance items
|
|
@@ -324,11 +422,7 @@ export function validateAcceptanceSubjectPrefix({ tickets }) {
|
|
|
324
422
|
const violations = [];
|
|
325
423
|
const stories = (tickets ?? []).filter((t) => t.type === 'story');
|
|
326
424
|
for (const story of stories) {
|
|
327
|
-
const
|
|
328
|
-
if (body === null || typeof body !== 'object') continue;
|
|
329
|
-
if (!Array.isArray(body.acceptance)) continue;
|
|
330
|
-
for (const item of body.acceptance) {
|
|
331
|
-
const line = String(item ?? '');
|
|
425
|
+
for (const line of resolveAcceptanceLines(story)) {
|
|
332
426
|
// Reset the global regex between iterations.
|
|
333
427
|
SUBJECT_PREFIX_RE.lastIndex = 0;
|
|
334
428
|
let match = SUBJECT_PREFIX_RE.exec(line);
|
|
@@ -381,8 +475,8 @@ function renderMissLine({ slug, path }) {
|
|
|
381
475
|
* The returned tickets array carries two extra non-array properties:
|
|
382
476
|
* - `findings` — structured sizing findings (hard + soft) keyed by the
|
|
383
477
|
* three-layer sizing model. The bounded re-decomposition loop in
|
|
384
|
-
*
|
|
385
|
-
*
|
|
478
|
+
* `/plan` reads `findings.filter(f => f.severity === 'hard')` to decide
|
|
479
|
+
* whether to re-prompt.
|
|
386
480
|
* - `errors` — human-readable strings, one per hard finding. Non-empty
|
|
387
481
|
* `errors[]` is the AC-visible "block normalization" signal; the legacy
|
|
388
482
|
* hierarchy/cycle/freshness checks continue to throw, so callers that
|
|
@@ -540,6 +634,14 @@ export function validateAndNormalizeTickets(tickets, opts = {}) {
|
|
|
540
634
|
|
|
541
635
|
assertAcyclic(slugAdjacency);
|
|
542
636
|
|
|
637
|
+
// Story #4541 — refuse an unparseable Story body up front, with a named
|
|
638
|
+
// error pointing at the offending section + entry. Must precede both the
|
|
639
|
+
// subject-prefix scan and the freshness gate: each parses the body, and
|
|
640
|
+
// the freshness gate's net-new whitelist comes from `body.changes`, so a
|
|
641
|
+
// malformed body used to surface as a stale-path miss naming the paths the
|
|
642
|
+
// Story had legitimately declared.
|
|
643
|
+
assertStoryBodiesParse({ tickets });
|
|
644
|
+
|
|
543
645
|
// Reject any Task acceptance item that prescribes a non-Conventional-Commits
|
|
544
646
|
// subject prefix (e.g. legacy "Commit subject begins with 'baseline-refresh:'"
|
|
545
647
|
// from pre-Epic-#2501 planner output). Runs before the freshness gate so
|
|
@@ -648,6 +750,7 @@ export function validateAndNormalizeTickets(tickets, opts = {}) {
|
|
|
648
750
|
|
|
649
751
|
// Internal helpers exposed for unit tests; not part of the public surface.
|
|
650
752
|
export const _internal = {
|
|
753
|
+
assertStoryBodiesParse,
|
|
651
754
|
indexTicketsBySlug,
|
|
652
755
|
assertAllTicketsAreStories,
|
|
653
756
|
assertEveryStoryHasInlineContract,
|
|
@@ -357,7 +357,7 @@ async function processCascadeParentLocked(
|
|
|
357
357
|
* unset so the module-level {@link Logger} is used.
|
|
358
358
|
* @returns {Promise<{ cascadedTo: number[], failed: Array<{ parentId: number, error: string }> }>}
|
|
359
359
|
*/
|
|
360
|
-
|
|
360
|
+
async function cascadeCompletion(provider, ticketId, opts = {}) {
|
|
361
361
|
const ticket = await provider.getTicket(ticketId);
|
|
362
362
|
|
|
363
363
|
// Determine if this ticket is agent::done
|
|
@@ -365,43 +365,14 @@ export async function cascadeCompletion(provider, ticketId, opts = {}) {
|
|
|
365
365
|
return { cascadedTo: [], failed: [] };
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
+
// Story #4545 — one strategy, not three. The `parent: #N` body footer and
|
|
369
|
+
// the native sub-issue link were both written by `createTicket`, the
|
|
370
|
+
// Epic-hierarchy write surface deleted in the same Story; with no writer,
|
|
371
|
+
// the body regex could only ever miss and the native lookup could only
|
|
372
|
+
// ever spend an API call to learn the same. The operator-settable `blocks`
|
|
373
|
+
// annotation is the one parent edge that can still exist.
|
|
368
374
|
const { blocks: parentIds } = await provider.getTicketDependencies(ticketId);
|
|
369
|
-
|
|
370
|
-
// Fallback: parse `parent: #NNN` from the body when `blocks` syntax isn't used (C-5).
|
|
371
|
-
let parsedParents = parentIds;
|
|
372
|
-
if (!parsedParents || parsedParents.length === 0) {
|
|
373
|
-
const parentMatch = ticket.body
|
|
374
|
-
? [...ticket.body.matchAll(/parent:\s*#(\d+)/gi)]
|
|
375
|
-
: [];
|
|
376
|
-
parsedParents = parentMatch.map((m) => Number.parseInt(m[1], 10));
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// Story #2982 — third fallback: GitHub's native Sub-Issues API. The
|
|
380
|
-
// resume reconciler can strip the `parent: #N` orchestrator footer
|
|
381
|
-
// from a Story body (see Issue 2 in #2982); without the body marker
|
|
382
|
-
// the cascade silently returned `{ cascadedTo: [], failed: [] }` and
|
|
383
|
-
// left intermediate parent tickets stranded OPEN. The native link is
|
|
384
|
-
// independent of body text, so consult it when the first two
|
|
385
|
-
// strategies came back empty.
|
|
386
|
-
if (
|
|
387
|
-
parsedParents.length === 0 &&
|
|
388
|
-
typeof provider._getNativeParent === 'function' &&
|
|
389
|
-
ticket.nodeId
|
|
390
|
-
) {
|
|
391
|
-
try {
|
|
392
|
-
const nativeParent = await provider._getNativeParent(
|
|
393
|
-
ticket.nodeId,
|
|
394
|
-
ticketId,
|
|
395
|
-
);
|
|
396
|
-
if (typeof nativeParent === 'number') {
|
|
397
|
-
parsedParents = [nativeParent];
|
|
398
|
-
}
|
|
399
|
-
} catch (err) {
|
|
400
|
-
Logger.warn(
|
|
401
|
-
`[cascadeCompletion] native parent lookup failed for #${ticketId}: ${err.message}`,
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
375
|
+
const parsedParents = Array.isArray(parentIds) ? parentIds : [];
|
|
405
376
|
|
|
406
377
|
if (parsedParents.length === 0) {
|
|
407
378
|
return { cascadedTo: [], failed: [] };
|
|
@@ -537,22 +508,18 @@ export async function cascadeParentState(provider, ticketId, opts = {}) {
|
|
|
537
508
|
}
|
|
538
509
|
|
|
539
510
|
/**
|
|
540
|
-
* Resolve the parent issue ids for a ticket
|
|
541
|
-
* annotations
|
|
542
|
-
*
|
|
511
|
+
* Resolve the parent issue ids for a ticket from its `blocks:` dependency
|
|
512
|
+
* annotations. Mirrors the resolution path used by {@link cascadeCompletion}
|
|
513
|
+
* — see there for why the `parent: #NNN` body fallback is gone (Story #4545).
|
|
543
514
|
*
|
|
544
515
|
* @param {import('../../ITicketingProvider.js').ITicketingProvider} provider
|
|
545
|
-
* @param {object}
|
|
516
|
+
* @param {object} _ticket
|
|
546
517
|
* @param {number} ticketId
|
|
547
518
|
* @returns {Promise<number[]>}
|
|
548
519
|
*/
|
|
549
|
-
async function resolveParentIds(provider,
|
|
520
|
+
async function resolveParentIds(provider, _ticket, ticketId) {
|
|
550
521
|
const { blocks: parentIds } = await provider.getTicketDependencies(ticketId);
|
|
551
|
-
|
|
552
|
-
const parentMatch = ticket?.body
|
|
553
|
-
? [...ticket.body.matchAll(/parent:\s*#(\d+)/gi)]
|
|
554
|
-
: [];
|
|
555
|
-
return parentMatch.map((m) => Number.parseInt(m[1], 10));
|
|
522
|
+
return Array.isArray(parentIds) ? parentIds : [];
|
|
556
523
|
}
|
|
557
524
|
|
|
558
525
|
/**
|
|
@@ -82,27 +82,13 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
82
82
|
'epic-run-progress',
|
|
83
83
|
'epic-plan-state',
|
|
84
84
|
'parked-follow-ons',
|
|
85
|
-
// Story #566 — per-phase wall-clock summary posted by single-story-close.js
|
|
86
|
-
// and consumed by analyze-execution / perf tooling to surface median /
|
|
87
|
-
// p95 phase timings across completed stories.
|
|
85
|
+
// Story #566 — per-phase wall-clock summary posted by single-story-close.js.
|
|
88
86
|
'phase-timings',
|
|
89
87
|
// Story #831 — story-init upserts a `story-init` comment that
|
|
90
88
|
// surfaces `dependenciesInstalled` (and the underlying installStatus) so
|
|
91
89
|
// downstream workflow steps don't have to infer install state from
|
|
92
90
|
// node_modules presence.
|
|
93
91
|
'story-init',
|
|
94
|
-
// Story #908 — /deliver upserts a `story-run-progress` snapshot
|
|
95
|
-
// on each Story per Task transition. The /deliver aggregator and
|
|
96
|
-
// `analyze-execution.js` both read this comment to derive
|
|
97
|
-
// Story-level state without re-fetching ticket labels.
|
|
98
|
-
'story-run-progress',
|
|
99
|
-
// Story #1123 — analyze-execution.js upserts perf summaries at close
|
|
100
|
-
// time. Story-mode posts `story-perf-summary` on each Story; Epic-mode
|
|
101
|
-
// posts `epic-perf-report` on the Epic. Both replace the legacy
|
|
102
|
-
// per-Task `friction` fan-out and the standalone `phase-timings`
|
|
103
|
-
// surface (Epic #1030).
|
|
104
|
-
'story-perf-summary',
|
|
105
|
-
'epic-perf-report',
|
|
106
92
|
// Story #2128 — Phase 6 Epic Clarity Gate (CLI retired). Historical
|
|
107
93
|
// `clarity-gate-update` comments may still exist on older tickets.
|
|
108
94
|
'clarity-gate-update',
|
|
@@ -151,19 +137,13 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
151
137
|
// the documented remediation actually executable
|
|
152
138
|
// (assertValidStructuredCommentType would otherwise throw).
|
|
153
139
|
'wave-stall',
|
|
154
|
-
// Story #
|
|
155
|
-
// comment
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
//
|
|
161
|
-
// comment on the Epic at lease-acquire time, recording the claiming
|
|
162
|
-
// operator and the claim timestamp. `/plan` emits no
|
|
163
|
-
// `story.heartbeat`, so this claim-time is the liveness signal that
|
|
164
|
-
// makes the documented `--steal` contract decidable: a foreign claim
|
|
165
|
-
// older than the lease TTL is reclaimed automatically; a fresh one
|
|
166
|
-
// refuses with the claim age. One entry per Epic; re-acquires upsert
|
|
140
|
+
// Story #4019 — the plan-tier lease guard upserts a `plan-lease`
|
|
141
|
+
// comment at lease-acquire time, recording the claiming operator and the
|
|
142
|
+
// claim timestamp. Nothing emits a per-run liveness beat, so this
|
|
143
|
+
// claim-time is the only age signal a reader has when reasoning about the
|
|
144
|
+
// documented `--steal` contract. The guard itself fails closed regardless
|
|
145
|
+
// (it anchors liveness to `now`), so a stranded claim is cleared with
|
|
146
|
+
// `--steal`, not by TTL expiry. One entry per ticket; re-acquires upsert
|
|
167
147
|
// in place.
|
|
168
148
|
'plan-lease',
|
|
169
149
|
// Story #4415 (Epic #4406) — the feedback-loop graduators
|
|
@@ -183,6 +163,13 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
183
163
|
// v2 Stage 3 — flat Story persist checkpoint on every created Story
|
|
184
164
|
// (replaces epic-plan-state for new plans). plan-summary stays primary-only.
|
|
185
165
|
'story-plan-state',
|
|
166
|
+
// Story #4535 — `plan-persist.js` upserts a `superseded-by` comment on
|
|
167
|
+
// each `/plan --tickets` source issue at persist time, naming the single
|
|
168
|
+
// Story that claims it (plus any per-supersede note the plan authored),
|
|
169
|
+
// immediately before closing the issue as `not_planned`. Keying off this
|
|
170
|
+
// marker rather than a bare `postComment` is what makes a re-run
|
|
171
|
+
// non-double-commenting. One entry per source issue.
|
|
172
|
+
'superseded-by',
|
|
186
173
|
]);
|
|
187
174
|
|
|
188
175
|
export const WAVE_TYPE_PATTERN = WAVE_MARKER_RE;
|
|
@@ -268,8 +255,8 @@ export function structuredCommentMarker(type, attrs = null) {
|
|
|
268
255
|
* recent `findStructuredComment` / `upsertStructuredComment` call.
|
|
269
256
|
* Story #1795 — every Epic run owns its process for the duration of
|
|
270
257
|
* the run, so a single seed-then-reuse window saves one
|
|
271
|
-
* `getTicketComments` per repeat upsert on the hot path (
|
|
272
|
-
* `
|
|
258
|
+
* `getTicketComments` per repeat upsert on the hot path (`story-init`,
|
|
259
|
+
* `verification-results`, etc).
|
|
273
260
|
*
|
|
274
261
|
* Lifecycle:
|
|
275
262
|
* - First call to `findStructuredComment(provider, t, type, attrs)`
|
|
@@ -355,8 +342,8 @@ export function structuredCommentCacheKey(ticketId, type, attrs) {
|
|
|
355
342
|
* array returned by the most recent `provider.getTicketComments(ticketId)`
|
|
356
343
|
* call. Story #2465 — `findStructuredComment` is invoked back-to-back for
|
|
357
344
|
* different `type` discriminators against the same ticket (e.g.
|
|
358
|
-
* `story-
|
|
359
|
-
*
|
|
345
|
+
* `story-init` + `verification-results` + a `friction` probe during a
|
|
346
|
+
* single close). Without this cache each lookup pays a full
|
|
360
347
|
* pagination round-trip even when the prior call already fetched the
|
|
361
348
|
* same comments. The structured-comment-id cache short-circuits *repeat*
|
|
362
349
|
* lookups for the same `(type, attrs)` tuple but does not help across
|
|
@@ -37,6 +37,10 @@ import {
|
|
|
37
37
|
eventSeverity,
|
|
38
38
|
renderTransitionMessage,
|
|
39
39
|
} from '../../notifications/notifier.js';
|
|
40
|
+
import {
|
|
41
|
+
emitRuntimeFriction,
|
|
42
|
+
RUNTIME_FRICTION_CATEGORIES,
|
|
43
|
+
} from '../../observability/runtime-friction.js';
|
|
40
44
|
import { ColumnSync } from '../column-sync.js';
|
|
41
45
|
import {
|
|
42
46
|
ALL_STATES,
|
|
@@ -279,6 +283,46 @@ function dispatchTransitionNotification(args) {
|
|
|
279
283
|
});
|
|
280
284
|
}
|
|
281
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Emit a runtime-derived `friction` signal when a ticket parks at
|
|
288
|
+
* `agent::blocked` (Story #4578). No-op for every other target state.
|
|
289
|
+
*
|
|
290
|
+
* Why here: `agent::blocked` is the single runtime HITL pause point
|
|
291
|
+
* (`.agents/instructions.md` § 1.J), and this is its canonical mutator — so
|
|
292
|
+
* one hook catches every block regardless of which path drove it (the
|
|
293
|
+
* `merge.unlanded` and `merge.flip-failed` paths in
|
|
294
|
+
* `single-story-close/phases/confirm-merge.js`, the review-block path, an
|
|
295
|
+
* operator's `update-ticket-state.js`, a Story worker giving up). Before
|
|
296
|
+
* this, a block was only ever a *label* — it left no trace in the friction
|
|
297
|
+
* stream the retro reads, so a run could park a worker and still produce a
|
|
298
|
+
* zero-signal roll-up.
|
|
299
|
+
*
|
|
300
|
+
* This is also why the terminal-envelope hook deliberately skips `blocked`
|
|
301
|
+
* (see `frictionForTerminal`): the two would otherwise count one incident
|
|
302
|
+
* twice.
|
|
303
|
+
*
|
|
304
|
+
* Best-effort and awaited: `emitRuntimeFriction` swallows its own failures
|
|
305
|
+
* and resolves `false`, so this can neither throw nor block the transition.
|
|
306
|
+
* It is awaited rather than fire-and-forget because CLI entry points exit
|
|
307
|
+
* via `process.exit` as soon as `main` resolves (`cli-utils.runAsCli` with
|
|
308
|
+
* `propagateExitCode`), which would discard a still-pending append.
|
|
309
|
+
*
|
|
310
|
+
* @param {number} ticketId
|
|
311
|
+
* @param {string} newState
|
|
312
|
+
* @param {{ config?: object }} opts
|
|
313
|
+
* @returns {Promise<void>}
|
|
314
|
+
*/
|
|
315
|
+
async function emitBlockedFriction(ticketId, newState, opts) {
|
|
316
|
+
if (newState !== STATE_LABELS.BLOCKED) return;
|
|
317
|
+
await emitRuntimeFriction({
|
|
318
|
+
storyId: ticketId,
|
|
319
|
+
category: RUNTIME_FRICTION_CATEGORIES.STORY_BLOCKED,
|
|
320
|
+
tool: 'transitionTicketState',
|
|
321
|
+
details: { toState: newState },
|
|
322
|
+
config: opts?.config,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
282
326
|
/**
|
|
283
327
|
* Transitions a ticket's label to the new state.
|
|
284
328
|
* Removes other agent:: state labels.
|
|
@@ -361,6 +405,10 @@ export async function transitionTicketState(
|
|
|
361
405
|
_ticketSnapshot: ticketSnapshot,
|
|
362
406
|
});
|
|
363
407
|
|
|
408
|
+
// Story #4578 — derive a friction signal from the block, at the point the
|
|
409
|
+
// runtime already knows. Best-effort; never blocks the transition.
|
|
410
|
+
await emitBlockedFriction(ticketId, newState, opts);
|
|
411
|
+
|
|
364
412
|
// Story #2548 — mirror the new state onto the Projects v2 Status
|
|
365
413
|
// column. Best-effort; never blocks the transition.
|
|
366
414
|
// Story #3645 — thread the DIP seam so callers can inject a stub.
|
|
@@ -460,14 +508,25 @@ export async function toggleTasklistCheckbox(
|
|
|
460
508
|
/**
|
|
461
509
|
* Post a structured comment to a ticket.
|
|
462
510
|
*
|
|
511
|
+
* Returns whatever the provider's `postComment` resolved to (Story #4543).
|
|
512
|
+
* Previously the result was swallowed, which made it impossible for a caller
|
|
513
|
+
* to reference the comment it had just written — the terminal envelope's
|
|
514
|
+
* `blocked.frictionCommentId` pointer needs exactly that, so an operator can
|
|
515
|
+
* be sent straight to the remediation instead of told to go find it. Callers
|
|
516
|
+
* that don't need the id simply ignore the return, so this is additive.
|
|
517
|
+
*
|
|
518
|
+
* The shape is provider-defined and may be `undefined` for providers that do
|
|
519
|
+
* not surface one; callers must treat the id as best-effort.
|
|
520
|
+
*
|
|
463
521
|
* @param {import('../../ITicketingProvider.js').ITicketingProvider} provider
|
|
464
522
|
* @param {number} ticketId
|
|
465
523
|
* @param {'progress'|'friction'|'notification'} type
|
|
466
524
|
* @param {string} payload
|
|
525
|
+
* @returns {Promise<unknown>} The provider's `postComment` result.
|
|
467
526
|
*/
|
|
468
527
|
export async function postStructuredComment(provider, ticketId, type, payload) {
|
|
469
528
|
assertValidStructuredCommentType(type);
|
|
470
|
-
await provider.postComment(ticketId, {
|
|
529
|
+
const posted = await provider.postComment(ticketId, {
|
|
471
530
|
type,
|
|
472
531
|
body: payload,
|
|
473
532
|
});
|
|
@@ -475,4 +534,5 @@ export async function postStructuredComment(provider, ticketId, type, payload) {
|
|
|
475
534
|
// `findStructuredComment` against this ticket re-fetches and sees the
|
|
476
535
|
// freshly-posted comment.
|
|
477
536
|
invalidateRawCommentsCache(provider, ticketId);
|
|
537
|
+
return posted;
|
|
478
538
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* plan-phase-cleanup.js — Post-phase temp-file cleanup for `/plan`.
|
|
3
3
|
*
|
|
4
|
-
* The spec and decompose phases write several
|
|
5
|
-
* the per-
|
|
6
|
-
* `temp/
|
|
4
|
+
* The spec and decompose phases write several run-scoped temp files under
|
|
5
|
+
* the per-run tree (`temp/run-<id>/planner-context.json`,
|
|
6
|
+
* `temp/run-<id>/techspec.md`, etc. — see `lib/config/temp-paths.js`). The
|
|
7
7
|
* workflow .md previously told the operator to `Remove-Item` those files by
|
|
8
8
|
* name at the end of each phase, which rots: adding a new temp file in the
|
|
9
9
|
* script required a synchronized markdown edit, and missed edits left
|
|
@@ -19,20 +19,20 @@
|
|
|
19
19
|
* never sinks a successful phase.
|
|
20
20
|
*
|
|
21
21
|
* Migration note (Epic #1030 Story #1040): the legacy flat layout
|
|
22
|
-
* (`temp/planner-context-
|
|
23
|
-
* resolver now delegates to `
|
|
24
|
-
* `lib/config/temp-paths.js`, which yields `temp/
|
|
22
|
+
* (`temp/planner-context-for-<id>.json` etc.) has been retired. The
|
|
23
|
+
* resolver now delegates to `runArtifactPath` from
|
|
24
|
+
* `lib/config/temp-paths.js`, which yields `temp/run-<id>/<basename>`
|
|
25
25
|
* under the configured `tempRoot`.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import fs from 'node:fs/promises';
|
|
29
29
|
import path from 'node:path';
|
|
30
|
-
import {
|
|
30
|
+
import { runArtifactPath } from './config/temp-paths.js';
|
|
31
31
|
import { PROJECT_ROOT, resolveConfig } from './config-resolver.js';
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Map of phase → artifact basenames (no path components). The per-
|
|
35
|
-
* directory prefix is supplied by `
|
|
34
|
+
* Map of phase → artifact basenames (no path components). The per-run
|
|
35
|
+
* directory prefix is supplied by `runArtifactPath` at resolution time.
|
|
36
36
|
*/
|
|
37
37
|
export const PHASE_TEMP_BASENAMES = Object.freeze({
|
|
38
38
|
spec: Object.freeze([
|
|
@@ -45,15 +45,13 @@ export const PHASE_TEMP_BASENAMES = Object.freeze({
|
|
|
45
45
|
// plan-phase artifact and deletes them ONLY at terminal success (after
|
|
46
46
|
// the `agent::ready` flip), fixing the mid-pipeline deletion defect where
|
|
47
47
|
// per-phase cleanup removed artifacts a `--force`/`--resume` re-persist
|
|
48
|
-
// was still entitled to reuse.
|
|
49
|
-
// the split-phase cleanup never owned it, which orphaned it in temp/.
|
|
48
|
+
// was still entitled to reuse.
|
|
50
49
|
// `plan-metrics.json` stays deliberately excluded (PR1) — the ledger
|
|
51
50
|
// must survive cleanup so the whole plan run is visible in one stream.
|
|
52
51
|
persist: Object.freeze([
|
|
53
52
|
'planner-context.json',
|
|
54
53
|
'techspec.md',
|
|
55
54
|
'acceptance-spec.md',
|
|
56
|
-
'risk-verdict.json',
|
|
57
55
|
'decomposer-context.json',
|
|
58
56
|
'tickets.json',
|
|
59
57
|
]),
|
|
@@ -84,8 +82,8 @@ export function resolvePhaseTempPaths(phase, epicId, repoRoot = PROJECT_ROOT) {
|
|
|
84
82
|
config = undefined;
|
|
85
83
|
}
|
|
86
84
|
return basenames.map((basename) => {
|
|
87
|
-
const rel =
|
|
88
|
-
// `
|
|
85
|
+
const rel = runArtifactPath(epicId, basename, config);
|
|
86
|
+
// `runArtifactPath` yields a path under `tempRoot` (relative when
|
|
89
87
|
// tempRoot is itself relative — the framework default). Rebase
|
|
90
88
|
// relative paths against `repoRoot` so callers always receive an
|
|
91
89
|
// absolute path — matches the pre-migration contract that
|