mandrel 2.30.0 → 2.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +13 -17
- package/.agents/agents/acceptance-critic.md +1 -2
- package/.agents/docs/SDLC.md +4 -4
- package/.agents/docs/agentrc-reference.json +61 -57
- package/.agents/docs/configuration.md +274 -227
- package/.agents/docs/execution-reference.md +13 -14
- package/.agents/docs/quality-gates.md +195 -23
- package/.agents/instructions.md +2 -5
- package/.agents/rules/ci-remediation.md +44 -1
- package/.agents/rules/git-conventions-reference.md +27 -27
- package/.agents/rules/git-conventions.md +4 -2
- package/.agents/rules/known-tooling-behavior.md +66 -30
- package/.agents/rules/testing-standards.md +35 -71
- package/.agents/runtime-deps.json +0 -1
- package/.agents/schemas/agentrc.schema.json +1939 -1400
- package/.agents/schemas/lifecycle/README.md +21 -14
- package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/scripts/README.md +7 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/bootstrap.js +28 -26
- package/.agents/scripts/check-baseline-drift.js +73 -13
- package/.agents/scripts/check-baseline-scope.js +362 -0
- package/.agents/scripts/check-dead-exports.js +9 -1
- package/.agents/scripts/check-gherkin-corpus.js +508 -0
- package/.agents/scripts/check-knip-entries.js +136 -0
- package/.agents/scripts/check-lifecycle-lint.js +36 -112
- package/.agents/scripts/check-schema-references.js +1 -1
- package/.agents/scripts/diagnose-friction.js +7 -4
- package/.agents/scripts/generate-config-docs.js +263 -171
- package/.agents/scripts/install-matrix-assert.js +0 -1
- package/.agents/scripts/lib/ITicketingProvider.js +0 -58
- package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
- package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
- package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
- package/.agents/scripts/lib/baselines/envelope.js +41 -60
- package/.agents/scripts/lib/baselines/git-base.js +30 -37
- package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
- package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
- package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
- package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
- package/.agents/scripts/lib/bdd-step-index.js +326 -0
- package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
- package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
- package/.agents/scripts/lib/checks/loop-health.js +9 -37
- package/.agents/scripts/lib/child-exec.js +193 -0
- package/.agents/scripts/lib/cli/standard-args.js +1 -1
- package/.agents/scripts/lib/cli-args.js +64 -0
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/close-validation/runner.js +3 -3
- package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
- package/.agents/scripts/lib/config/commands.js +3 -5
- package/.agents/scripts/lib/config/explain.js +5 -7
- package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
- package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
- package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
- package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
- package/.agents/scripts/lib/config/gates/index.js +5 -2
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
- package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
- package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
- package/.agents/scripts/lib/config/gates/shared.js +58 -9
- package/.agents/scripts/lib/config/github.js +0 -1
- package/.agents/scripts/lib/config/limits.js +3 -48
- package/.agents/scripts/lib/config/qa.js +105 -0
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
- package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
- package/.agents/scripts/lib/config-settings-schema.js +386 -39
- package/.agents/scripts/lib/crap-baseline-join.js +126 -9
- package/.agents/scripts/lib/crap-utils.js +84 -520
- package/.agents/scripts/lib/dead-exports-knip.js +79 -10
- package/.agents/scripts/lib/degraded-mode.js +2 -2
- package/.agents/scripts/lib/doc-tiers.js +3 -3
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
- package/.agents/scripts/lib/fs-walk.js +52 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/git-utils.js +16 -36
- package/.agents/scripts/lib/knip-entry-sync.js +469 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
- package/.agents/scripts/lib/observability/signal-validator.js +5 -85
- package/.agents/scripts/lib/observability/signals-writer.js +19 -62
- package/.agents/scripts/lib/observability/source-classifier.js +5 -7
- package/.agents/scripts/lib/observability/terse-result.js +3 -3
- package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
- package/.agents/scripts/lib/orchestration/code-review.js +2 -2
- package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
- package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
- package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
- package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +17 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +65 -25
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
- package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
- package/.agents/scripts/lib/signals/detectors/common.js +1 -1
- package/.agents/scripts/lib/signals/index.js +8 -6
- package/.agents/scripts/lib/signals/schema.js +20 -25
- package/.agents/scripts/lib/signals/write.js +8 -8
- package/.agents/scripts/lib/story-body/story-body.js +12 -59
- package/.agents/scripts/lib/temp-retention.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
- package/.agents/scripts/lib/ticket-body-sections.js +4 -5
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
- package/.agents/scripts/lib/worktree-manager.js +2 -3
- package/.agents/scripts/lint-label-vocabulary.js +2 -24
- package/.agents/scripts/pr-watch-with-update.js +7 -5
- package/.agents/scripts/providers/github/cache.js +2 -2
- package/.agents/scripts/providers/github/comments.js +6 -28
- package/.agents/scripts/providers/github/compose.js +0 -15
- package/.agents/scripts/providers/github/errors.js +10 -27
- package/.agents/scripts/providers/github/request-helpers.js +1 -2
- package/.agents/scripts/providers/github/sub-issues.js +10 -218
- package/.agents/scripts/providers/github.js +4 -7
- package/.agents/scripts/prune-baseline-orphans.js +181 -0
- package/.agents/scripts/resolve-stories.js +0 -2
- package/.agents/scripts/run-lint.js +61 -61
- package/.agents/scripts/run-test-profile.js +6 -6
- package/.agents/scripts/run-verify.js +48 -30
- package/.agents/scripts/single-story-close.js +20 -0
- package/.agents/scripts/single-story-init.js +12 -35
- package/.agents/scripts/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright/SKILL.md +48 -0
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +2 -2
- package/.agents/workflows/helpers/audit-lens-core.md +11 -12
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
- package/.agents/workflows/helpers/code-review.md +3 -8
- package/.agents/workflows/helpers/deliver-reference.md +2 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
- package/.agents/workflows/mandrel-update.md +10 -10
- package/.agents/workflows/qa-assist.md +15 -20
- package/.agents/workflows/qa-explore.md +9 -8
- package/README.md +1 -1
- package/docs/CHANGELOG.md +49 -0
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
- package/package.json +12 -3
- package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
- package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
- package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
- package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
- package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
- package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
- package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
- package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
- package/.agents/scripts/check-gherkin-placeholders.js +0 -663
- package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
- package/.agents/scripts/lib/audit-suite/cli.js +0 -64
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
- package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
- package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
- package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
- package/.agents/scripts/lib/crap-baseline-index.js +0 -46
- package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
- package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
- package/.agents/scripts/lib/mutation/config-detector.js +0 -119
- package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
- package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
- package/.agents/scripts/lib/observability/active-story-env.js +0 -170
- package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
- package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
- package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
- package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
- package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
- package/.agents/scripts/lib/planning-corpus.js +0 -37
- package/.agents/scripts/lib/qa/coverage-report.js +0 -181
- package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
- package/.agents/scripts/lib/signals/detectors/index.js +0 -14
- package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
- package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
- package/.agents/scripts/lib/signals/read.js +0 -268
- package/.agents/scripts/lib/signals/span-tree.js +0 -291
- package/.agents/scripts/lib/story-lifecycle.js +0 -194
- package/.agents/scripts/lib/story-plan.js +0 -379
- package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
- package/.agents/scripts/lib/util/phase-timer.js +0 -163
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
- package/.agents/scripts/lint-baseline.js +0 -507
- package/.agents/scripts/providers/github/prs.js +0 -103
- package/.agents/scripts/signals-view.js +0 -309
- package/.agents/scripts/story-plan.js +0 -370
- package/.agents/scripts/sync-branch-from-base.js +0 -149
- package/.agents/scripts/validate-docs-freshness.js +0 -314
- package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
- package/.agents/workflows/helpers/signals.md +0 -112
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Append-only signals
|
|
2
|
+
* Append-only signals writer (Epic #1030 Story #1041).
|
|
3
3
|
*
|
|
4
4
|
* Centralizes the per-(epic, story) NDJSON streams under
|
|
5
|
-
* `temp/run-<id>/stories/story-<sid>/signals.ndjson
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* `temp/run-<id>/stories/story-<sid>/signals.ndjson`. Every live emitter
|
|
6
|
+
* funnels through this writer so the on-disk shape stays under one schema
|
|
7
|
+
* and one set of robustness guarantees. The sibling `traces.ndjson` stream
|
|
8
|
+
* and its `appendTrace` entry point went in Story #5003 with the tool-trace
|
|
9
|
+
* hook that was its only producer.
|
|
9
10
|
*
|
|
10
11
|
* Robustness contract (Tech Spec #1032 §observability):
|
|
11
12
|
* - **Best-effort.** Every entry point swallows fs / JSON failures
|
|
12
13
|
* after logging via `Logger.warn`. Observability MUST NOT take down
|
|
13
14
|
* the runner — a failed write is a missing signal, not a halted
|
|
14
15
|
* wave.
|
|
15
|
-
* - **No buffering.** Each `appendSignal`
|
|
16
|
-
*
|
|
17
|
-
* The Tech Spec explicitly forbids in-process buffering:
|
|
16
|
+
* - **No buffering.** Each `appendSignal` opens the target file, writes
|
|
17
|
+
* one newline-terminated JSON line, and closes.
|
|
18
|
+
* The Tech Spec explicitly forbids in-process buffering: emitters
|
|
18
19
|
* fire from inside per-Story sub-agents that may exit abruptly, and
|
|
19
20
|
* a buffered tail would silently disappear on `process.exit`.
|
|
20
21
|
* - **Lazy directory creation.** The first write to a fresh Story
|
|
@@ -42,23 +43,11 @@ import {
|
|
|
42
43
|
STANDALONE_DIRNAME,
|
|
43
44
|
STORIES_DIRNAME,
|
|
44
45
|
signalsFile,
|
|
45
|
-
storyTempDir,
|
|
46
46
|
} from '../config/temp-paths.js';
|
|
47
47
|
import { Logger } from '../Logger.js';
|
|
48
|
-
import {
|
|
48
|
+
import { validateSignal } from './signal-validator.js';
|
|
49
49
|
import { classifySignalSource } from './source-classifier.js';
|
|
50
50
|
|
|
51
|
-
const TRACES_BASENAME = 'traces.ndjson';
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Async traces-file path (kept private — consumers thread through
|
|
55
|
-
* `appendTrace`). Mirrors `signalsFile` but with the `traces.ndjson`
|
|
56
|
-
* sibling so the analyzer can scan signals and traces independently.
|
|
57
|
-
*/
|
|
58
|
-
function tracesFile(eid, sid, config) {
|
|
59
|
-
return path.join(storyTempDir(eid, sid, config), TRACES_BASENAME);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
51
|
/**
|
|
63
52
|
* Best-effort decoration of a signal record with a `source` field
|
|
64
53
|
* (`"framework"` or `"consumer"`) produced by `classifyPathSource`.
|
|
@@ -111,21 +100,23 @@ function tagSignalSource(signal) {
|
|
|
111
100
|
/**
|
|
112
101
|
* Validate a record against the canonical `signal-event.schema.json`
|
|
113
102
|
* before it is appended. On failure the record is **dropped** (never
|
|
114
|
-
* appended)
|
|
115
|
-
* reject tally is incremented under the Epic temp tree. Never throws —
|
|
103
|
+
* appended) and a `Logger.warn` names the violating field. Never throws —
|
|
116
104
|
* the writer's best-effort contract is preserved.
|
|
117
105
|
*
|
|
106
|
+
* Story #5003 removed the persisted per-Epic reject tally this used to
|
|
107
|
+
* increment: v2 Stories are standalone (`epicId` is always null), so the
|
|
108
|
+
* tally was never written and its only reader could never see one.
|
|
109
|
+
*
|
|
118
110
|
* @param {unknown} record
|
|
119
|
-
* @param {
|
|
120
|
-
* @returns {
|
|
111
|
+
* @param {string} label
|
|
112
|
+
* @returns {boolean} true when the record is valid (safe to append).
|
|
121
113
|
*/
|
|
122
|
-
|
|
114
|
+
function validateOrDrop(record, label) {
|
|
123
115
|
const { valid, violatingField, message } = validateSignal(record);
|
|
124
116
|
if (valid) return true;
|
|
125
117
|
Logger.warn(
|
|
126
118
|
`signals-writer: dropping schema-invalid ${label} record — violating field '${violatingField}' (${message}).`,
|
|
127
119
|
);
|
|
128
|
-
await recordSignalReject({ epicId, config, field: violatingField });
|
|
129
120
|
return false;
|
|
130
121
|
}
|
|
131
122
|
|
|
@@ -189,44 +180,10 @@ export async function appendSignal(args) {
|
|
|
189
180
|
return false;
|
|
190
181
|
}
|
|
191
182
|
const tagged = tagSignalSource(signal);
|
|
192
|
-
|
|
193
|
-
epicId: Number.isInteger(epicId) ? epicId : null,
|
|
194
|
-
config,
|
|
195
|
-
label: 'signal',
|
|
196
|
-
});
|
|
197
|
-
if (!ok) return false;
|
|
183
|
+
if (!validateOrDrop(tagged, 'signal')) return false;
|
|
198
184
|
return appendOne(target, tagged);
|
|
199
185
|
}
|
|
200
186
|
|
|
201
|
-
/**
|
|
202
|
-
* Append one trace record to `temp/run-<id>/stories/story-<sid>/traces.ndjson`.
|
|
203
|
-
* Same robustness contract as `appendSignal` — never throws.
|
|
204
|
-
*
|
|
205
|
-
* @param {{ epicId: number, storyId: number, trace: unknown, config?: object }} args
|
|
206
|
-
* @returns {Promise<boolean>}
|
|
207
|
-
*/
|
|
208
|
-
export async function appendTrace(args) {
|
|
209
|
-
const { epicId, storyId, trace, config } = args ?? {};
|
|
210
|
-
let target;
|
|
211
|
-
try {
|
|
212
|
-
target = tracesFile(epicId, storyId, config);
|
|
213
|
-
} catch (err) {
|
|
214
|
-
Logger.warn(
|
|
215
|
-
`signals-writer: invalid epicId/storyId for appendTrace: ${
|
|
216
|
-
err instanceof Error ? err.message : String(err)
|
|
217
|
-
}`,
|
|
218
|
-
);
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
const ok = await validateOrDrop(trace, {
|
|
222
|
-
epicId: Number.isInteger(epicId) ? epicId : null,
|
|
223
|
-
config,
|
|
224
|
-
label: 'trace',
|
|
225
|
-
});
|
|
226
|
-
if (!ok) return false;
|
|
227
|
-
return appendOne(target, trace);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
187
|
/**
|
|
231
188
|
* Stream any NDJSON `target` file line by line, invoking
|
|
232
189
|
* `cb(parsed, lineNumber)` for each successfully parsed JSON line.
|
|
@@ -88,13 +88,14 @@ const FRAMEWORK_SCRIPT_BASENAMES = Object.freeze([
|
|
|
88
88
|
'check-action-pinning.js',
|
|
89
89
|
'check-arch-cycles.js',
|
|
90
90
|
'check-baseline-drift.js',
|
|
91
|
+
'check-baseline-scope.js',
|
|
91
92
|
'check-baselines.js',
|
|
92
93
|
'check-context-budget.js',
|
|
93
94
|
'check-cyclomatic.js',
|
|
94
95
|
'check-dead-exports.js',
|
|
95
96
|
'check-doc-links.js',
|
|
96
|
-
'check-gherkin-
|
|
97
|
-
'check-
|
|
97
|
+
'check-gherkin-corpus.js',
|
|
98
|
+
'check-knip-entries.js',
|
|
98
99
|
'check-lifecycle-lint.js',
|
|
99
100
|
'check-schema-references.js',
|
|
100
101
|
'check-test-temp-hygiene.js',
|
|
@@ -117,7 +118,6 @@ const FRAMEWORK_SCRIPT_BASENAMES = Object.freeze([
|
|
|
117
118
|
'generate-workflows-doc.js',
|
|
118
119
|
'git-cleanup.js',
|
|
119
120
|
'install-matrix-assert.js',
|
|
120
|
-
'lint-baseline.js',
|
|
121
121
|
'lint-issue-body.js',
|
|
122
122
|
'lint-label-vocabulary.js',
|
|
123
123
|
'mandrel-update-preflight.js',
|
|
@@ -130,6 +130,7 @@ const FRAMEWORK_SCRIPT_BASENAMES = Object.freeze([
|
|
|
130
130
|
'post-structured-comment.js',
|
|
131
131
|
'pr-watch-with-update.js',
|
|
132
132
|
'provision-git-hooks.js',
|
|
133
|
+
'prune-baseline-orphans.js',
|
|
133
134
|
'quality-preview.js',
|
|
134
135
|
'quality-watch.js',
|
|
135
136
|
'resolve-doc-tiers.js',
|
|
@@ -140,24 +141,21 @@ const FRAMEWORK_SCRIPT_BASENAMES = Object.freeze([
|
|
|
140
141
|
'run-test-profile.js',
|
|
141
142
|
'run-tests.js',
|
|
142
143
|
'run-verify.js',
|
|
143
|
-
'signals-view.js',
|
|
144
144
|
'single-story-close.js',
|
|
145
145
|
'single-story-confirm-merge.js',
|
|
146
146
|
'single-story-init.js',
|
|
147
147
|
'stories-wave-tick.js',
|
|
148
|
-
'story-plan.js',
|
|
149
148
|
'sync-agentrc.js',
|
|
150
|
-
'sync-branch-from-base.js',
|
|
151
149
|
'sync-claude-agents.js',
|
|
152
150
|
'sync-claude-commands.js',
|
|
153
151
|
'test-isolate.js',
|
|
154
152
|
'test-wrapper.js',
|
|
155
153
|
'update-coverage-baseline.js',
|
|
156
154
|
'update-crap-baseline.js',
|
|
155
|
+
'update-dead-exports-baseline.js',
|
|
157
156
|
'update-duplication-baseline.js',
|
|
158
157
|
'update-maintainability-baseline.js',
|
|
159
158
|
'update-ticket-state.js',
|
|
160
|
-
'validate-docs-freshness.js',
|
|
161
159
|
'validate-skills.js',
|
|
162
160
|
]);
|
|
163
161
|
|
|
@@ -8,9 +8,9 @@ import { Logger } from '../Logger.js';
|
|
|
8
8
|
* Terse hot-path result emission (Story #4685).
|
|
9
9
|
*
|
|
10
10
|
* The orchestration CLIs an agent invokes on every delivery turn
|
|
11
|
-
* (`single-story-init`, `single-story-close`, `single-story-confirm-merge
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* (`single-story-init`, `single-story-close`, `single-story-confirm-merge`)
|
|
12
|
+
* historically dumped their whole result object to stdout as pretty-printed
|
|
13
|
+
* JSON:
|
|
14
14
|
*
|
|
15
15
|
* --- STORY CLOSE RESULT ---
|
|
16
16
|
* { ... every field, 2-space indented ... }
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* behind-recovery.js — the one bounded `BEHIND` → `gh pr update-branch`
|
|
3
|
+
* recovery decision (Story #5006).
|
|
4
|
+
*
|
|
5
|
+
* Two loops watch an open PR and both must fast-forward it when GitHub
|
|
6
|
+
* reports `mergeStateStatus: BEHIND`:
|
|
7
|
+
*
|
|
8
|
+
* - the CI-watch loop (`pr-watch.js#watchPrToTerminal`),
|
|
9
|
+
* which probes with a synchronous `gh pr view` spawn and re-enters its
|
|
10
|
+
* poll loop after each update; and
|
|
11
|
+
* - the merge wait (`single-story-close/phases/confirm-merge.js`), which
|
|
12
|
+
* probes with an async `gh` facade and simply re-reads on the next tick.
|
|
13
|
+
*
|
|
14
|
+
* They had two copies of the same three-way decision — *is it BEHIND, is
|
|
15
|
+
* there update budget left, did the update land* — differing only in
|
|
16
|
+
* incidental ordering, which is exactly the shape that drifts. This module
|
|
17
|
+
* owns the decision; the callers keep their own probe source, their own
|
|
18
|
+
* timing model, and their own operator-facing wording (supplied as the
|
|
19
|
+
* `on*` callbacks).
|
|
20
|
+
*
|
|
21
|
+
* Deliberately NOT owned here: probing `mergeStateStatus`, counting the
|
|
22
|
+
* budget across iterations, and deciding what a caller does next with the
|
|
23
|
+
* verdict. The helper is a single bounded step, not a loop.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The `gh pr view --json mergeStateStatus` value that licenses a recovery.
|
|
28
|
+
* Module-private: both call sites reach it through {@link applyBehindUpdate},
|
|
29
|
+
* so exporting it would only add a symbol the reachability ratchet scores
|
|
30
|
+
* dead.
|
|
31
|
+
*/
|
|
32
|
+
const BEHIND_MERGE_STATE = 'BEHIND';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Normalise an `updateBranch()` settlement into `{ ok, detail }`.
|
|
36
|
+
*
|
|
37
|
+
* Both callers' invokers signal failure differently — the merge wait's `gh`
|
|
38
|
+
* facade **throws**, the watcher's `spawnSync` port **resolves** a non-zero
|
|
39
|
+
* status — so both shapes are accepted: a throw is a failure, and so is a
|
|
40
|
+
* resolved object carrying `ok === false`. Anything else resolved (including
|
|
41
|
+
* `undefined`) is a success, which is what a facade that only throws on
|
|
42
|
+
* failure returns.
|
|
43
|
+
*
|
|
44
|
+
* @param {() => Promise<unknown>} updateBranch
|
|
45
|
+
* @returns {Promise<{ ok: boolean, detail: string }>}
|
|
46
|
+
*/
|
|
47
|
+
async function settleUpdate(updateBranch) {
|
|
48
|
+
try {
|
|
49
|
+
const result = await updateBranch();
|
|
50
|
+
if (result && typeof result === 'object' && result.ok === false) {
|
|
51
|
+
return { ok: false, detail: String(result.detail ?? 'update-failed') };
|
|
52
|
+
}
|
|
53
|
+
return { ok: true, detail: '' };
|
|
54
|
+
} catch (err) {
|
|
55
|
+
return { ok: false, detail: String(err?.message ?? err) };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Apply at most one bounded `gh pr update-branch` to a PR that is BEHIND its
|
|
61
|
+
* base.
|
|
62
|
+
*
|
|
63
|
+
* The order of the two guards is load-bearing: **not-BEHIND is checked
|
|
64
|
+
* first**, so the budget-spent callback fires only for a PR that actually
|
|
65
|
+
* wanted an update. Reversing them would announce an exhausted budget on
|
|
66
|
+
* every poll of a perfectly up-to-date PR.
|
|
67
|
+
*
|
|
68
|
+
* @param {object} args
|
|
69
|
+
* @param {string|undefined|null} args.mergeStateStatus As probed by the
|
|
70
|
+
* caller. Anything other than `BEHIND` (including a degraded/unknown probe)
|
|
71
|
+
* is a no-op — an unreadable merge state must never license a write.
|
|
72
|
+
* @param {number} [args.updatesUsed=0] Updates already applied this wait.
|
|
73
|
+
* @param {number} [args.maxUpdates=0] Hard cap on updates per wait.
|
|
74
|
+
* @param {() => Promise<unknown>} args.updateBranch Invoker that issues the
|
|
75
|
+
* fast-forward. See {@link settleUpdate} for the accepted settlements.
|
|
76
|
+
* @param {(args: { maxUpdates: number }) => void} [args.onBudgetSpent]
|
|
77
|
+
* @param {(args: { updatesUsed: number, maxUpdates: number }) => void} [args.onUpdated]
|
|
78
|
+
* @param {(detail: string) => void} [args.onUpdateFailed]
|
|
79
|
+
* @returns {Promise<{
|
|
80
|
+
* attempted: boolean,
|
|
81
|
+
* updated: boolean,
|
|
82
|
+
* outcome: 'not-behind'|'budget-spent'|'updated'|'update-failed',
|
|
83
|
+
* }>}
|
|
84
|
+
* `attempted` is true whenever the invoker ran — a failed update still
|
|
85
|
+
* counts as an attempt (the merge wait treats it as a consumed tick and
|
|
86
|
+
* re-reads the real state next poll). `updated` is true only when the
|
|
87
|
+
* fast-forward actually landed, which is the signal a caller needs before
|
|
88
|
+
* invalidating a terminal check outcome and re-polling.
|
|
89
|
+
*/
|
|
90
|
+
export async function applyBehindUpdate({
|
|
91
|
+
mergeStateStatus,
|
|
92
|
+
updatesUsed = 0,
|
|
93
|
+
maxUpdates = 0,
|
|
94
|
+
updateBranch,
|
|
95
|
+
onBudgetSpent,
|
|
96
|
+
onUpdated,
|
|
97
|
+
onUpdateFailed,
|
|
98
|
+
}) {
|
|
99
|
+
if (mergeStateStatus !== BEHIND_MERGE_STATE) {
|
|
100
|
+
return { attempted: false, updated: false, outcome: 'not-behind' };
|
|
101
|
+
}
|
|
102
|
+
if (updatesUsed >= maxUpdates) {
|
|
103
|
+
onBudgetSpent?.({ maxUpdates });
|
|
104
|
+
return { attempted: false, updated: false, outcome: 'budget-spent' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const settled = await settleUpdate(updateBranch);
|
|
108
|
+
if (!settled.ok) {
|
|
109
|
+
onUpdateFailed?.(settled.detail);
|
|
110
|
+
return { attempted: true, updated: false, outcome: 'update-failed' };
|
|
111
|
+
}
|
|
112
|
+
onUpdated?.({ updatesUsed, maxUpdates });
|
|
113
|
+
return { attempted: true, updated: true, outcome: 'updated' };
|
|
114
|
+
}
|
|
@@ -28,12 +28,11 @@
|
|
|
28
28
|
* ## The load-bearing invariant (M4-B acceptance floor — DO NOT VIOLATE)
|
|
29
29
|
*
|
|
30
30
|
* Risk-routing chooses fresh-vs-inline **PER CLUSTER**. It NEVER changes the
|
|
31
|
-
* cluster COUNT
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* decision for that one cluster; it has no way to add or remove clusters.
|
|
31
|
+
* cluster COUNT — the caller owns clustering and hands this module a cluster
|
|
32
|
+
* index. A low-risk Story still gets one verdict per cluster — just possibly
|
|
33
|
+
* authored inline instead of by a fresh sub-agent. This module takes the
|
|
34
|
+
* cluster index as an INPUT and returns a decision for that one cluster; it
|
|
35
|
+
* has no way to add or remove clusters.
|
|
37
36
|
*
|
|
38
37
|
* ## One verdict-owner per cluster (Story #4723)
|
|
39
38
|
*
|
|
@@ -127,8 +126,8 @@ function normalizeCeremonyProfile(value) {
|
|
|
127
126
|
* 2·stride, …) is forced fresh, yielding ≈`r` of clusters fresh. `r <= 0`
|
|
128
127
|
* disables the floor (no cluster forced); `r >= 1` forces every cluster.
|
|
129
128
|
*
|
|
130
|
-
* @param {number} clusterIndex Zero-based cluster position (from the
|
|
131
|
-
*
|
|
129
|
+
* @param {number} clusterIndex Zero-based cluster position (from the
|
|
130
|
+
* caller-owned fan-out — an INPUT, never mutated here).
|
|
132
131
|
* @param {number} rate Sampling rate, already clamped into [0, 1] by
|
|
133
132
|
* `getDeliveryRouting`.
|
|
134
133
|
* @returns {boolean} `true` when the floor forces this cluster fresh.
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
* an unrecognized check state.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
-
import { spawnSync } from 'node:child_process';
|
|
36
35
|
import {
|
|
37
36
|
existsSync,
|
|
38
37
|
mkdirSync,
|
|
@@ -41,6 +40,7 @@ import {
|
|
|
41
40
|
writeFileSync,
|
|
42
41
|
} from 'node:fs';
|
|
43
42
|
import path from 'node:path';
|
|
43
|
+
import { spawnChild } from '../child-exec.js';
|
|
44
44
|
import { resolveConfig } from '../config-resolver.js';
|
|
45
45
|
import { Logger } from '../Logger.js';
|
|
46
46
|
import { createProvider } from '../provider-factory.js';
|
|
@@ -119,14 +119,13 @@ function ciDigestPaths({ storyId = null, tempRoot, cwd }) {
|
|
|
119
119
|
* returns an empty tail when the run id is unknown or `gh` errors.
|
|
120
120
|
* Injected into `writeCiDigest` so tests never shell out.
|
|
121
121
|
*/
|
|
122
|
-
function ghRunLogTail({ runId, cwd, spawnFn
|
|
122
|
+
function ghRunLogTail({ runId, cwd, spawnFn, maxLines = 40 }) {
|
|
123
123
|
if (!runId) return '';
|
|
124
|
-
const result =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
});
|
|
124
|
+
const result = spawnChild(
|
|
125
|
+
'gh',
|
|
126
|
+
['run', 'view', String(runId), '--log-failed'],
|
|
127
|
+
{ run: spawnFn, cwd },
|
|
128
|
+
);
|
|
130
129
|
const out = (result.stdout ?? '').trim();
|
|
131
130
|
if (out.length === 0) return '';
|
|
132
131
|
const lines = out.split('\n');
|
|
@@ -142,17 +141,12 @@ function ghRunLogTail({ runId, cwd, spawnFn = spawnSync, maxLines = 40 }) {
|
|
|
142
141
|
*
|
|
143
142
|
* @returns {{ runId: string|null, url: string|null }}
|
|
144
143
|
*/
|
|
145
|
-
function resolveFailingCheckRun({
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const result = spawnFn('gh', ['pr', 'checks', prRef, '--json', 'name,link'], {
|
|
152
|
-
cwd,
|
|
153
|
-
encoding: 'utf-8',
|
|
154
|
-
shell: false,
|
|
155
|
-
});
|
|
144
|
+
function resolveFailingCheckRun({ prRef, checkName, cwd, spawnFn }) {
|
|
145
|
+
const result = spawnChild(
|
|
146
|
+
'gh',
|
|
147
|
+
['pr', 'checks', prRef, '--json', 'name,link'],
|
|
148
|
+
{ run: spawnFn, cwd },
|
|
149
|
+
);
|
|
156
150
|
try {
|
|
157
151
|
const parsed = JSON.parse((result.stdout ?? '').trim() || '[]');
|
|
158
152
|
const entry = Array.isArray(parsed)
|
|
@@ -172,15 +166,18 @@ function resolveFailingCheckRun({
|
|
|
172
166
|
* `null` when `gh` fails or the payload is unparseable; callers treat an
|
|
173
167
|
* unresolvable head as unverifiable, never as "changed".
|
|
174
168
|
*
|
|
175
|
-
* @param {{ prRef: string, cwd: string, spawnFn?:
|
|
169
|
+
* @param {{ prRef: string, cwd: string, spawnFn?: Function }} opts
|
|
176
170
|
* @returns {string|null}
|
|
177
171
|
*/
|
|
178
|
-
export function resolvePrHeadSha({ prRef, cwd, spawnFn
|
|
179
|
-
const result =
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
172
|
+
export function resolvePrHeadSha({ prRef, cwd, spawnFn }) {
|
|
173
|
+
const result = spawnChild(
|
|
174
|
+
'gh',
|
|
175
|
+
['pr', 'view', prRef, '--json', 'headRefOid'],
|
|
176
|
+
{
|
|
177
|
+
run: spawnFn,
|
|
178
|
+
cwd,
|
|
179
|
+
},
|
|
180
|
+
);
|
|
184
181
|
if ((result?.status ?? 1) !== 0) return null;
|
|
185
182
|
try {
|
|
186
183
|
const parsed = JSON.parse((result.stdout ?? '').trim() || '{}');
|
|
@@ -204,18 +201,17 @@ const NOT_ARMED = /not enabled|isn't enabled|is not set|no auto-?merge/i;
|
|
|
204
201
|
* Disarm GitHub native auto-merge for the PR — the race-free response to the
|
|
205
202
|
* first red. Never throws.
|
|
206
203
|
*
|
|
207
|
-
* @param {{ prRef: string, cwd: string, spawnFn?:
|
|
204
|
+
* @param {{ prRef: string, cwd: string, spawnFn?: Function }} opts
|
|
208
205
|
* @returns {{ disarmed: boolean, alreadyUnarmed: boolean, detail: string }}
|
|
209
206
|
* `disarmed` is true when the PR is (now) un-armed; `alreadyUnarmed`
|
|
210
207
|
* distinguishes "there was nothing armed" from an executed disarm.
|
|
211
208
|
*/
|
|
212
|
-
export function disarmAutoMerge({ prRef, cwd, spawnFn
|
|
209
|
+
export function disarmAutoMerge({ prRef, cwd, spawnFn }) {
|
|
213
210
|
let result;
|
|
214
211
|
try {
|
|
215
|
-
result =
|
|
212
|
+
result = spawnChild('gh', ['pr', 'merge', prRef, '--disable-auto'], {
|
|
213
|
+
run: spawnFn,
|
|
216
214
|
cwd,
|
|
217
|
-
encoding: 'utf-8',
|
|
218
|
-
shell: false,
|
|
219
215
|
});
|
|
220
216
|
} catch (err) {
|
|
221
217
|
return {
|
|
@@ -362,6 +358,9 @@ function renderDigestMarkdown(digest, failures) {
|
|
|
362
358
|
* @param {string} opts.prRef
|
|
363
359
|
* @param {Function} [opts.checkRunFn]
|
|
364
360
|
* @param {Function} [opts.logTailFn]
|
|
361
|
+
* @param {Function} [opts.spawnFn] Child runner handed to the two default
|
|
362
|
+
* `gh` probes, so their real bodies can be exercised without shelling out.
|
|
363
|
+
* Ignored when `checkRunFn` / `logTailFn` are replaced outright.
|
|
365
364
|
* @returns {{ jsonPath: string, mdPath: string } | null}
|
|
366
365
|
*/
|
|
367
366
|
export function writeCiDigest({
|
|
@@ -374,12 +373,14 @@ export function writeCiDigest({
|
|
|
374
373
|
prRef,
|
|
375
374
|
checkRunFn = resolveFailingCheckRun,
|
|
376
375
|
logTailFn = ghRunLogTail,
|
|
376
|
+
spawnFn,
|
|
377
377
|
}) {
|
|
378
378
|
const paths = ciDigestPaths({ storyId, tempRoot, cwd });
|
|
379
379
|
if (!paths) return null;
|
|
380
380
|
const primary = failures[0] ?? { name: 'unknown', outcome: 'failure' };
|
|
381
|
-
const checkRun =
|
|
382
|
-
|
|
381
|
+
const checkRun =
|
|
382
|
+
checkRunFn({ prRef, checkName: primary.name, cwd, spawnFn }) ?? {};
|
|
383
|
+
const logTail = logTailFn({ runId: checkRun.runId, cwd, spawnFn });
|
|
383
384
|
const previous = readCiDigest({ storyId, tempRoot, cwd });
|
|
384
385
|
const digest = {
|
|
385
386
|
storyId: paths.scope.id,
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
*
|
|
16
16
|
* v2.0.0 removed the Epic tier. The Epic-scope envelope and the
|
|
17
17
|
* Epic-scoped `code-review.start` / `.end` lifecycle emits (whose schema
|
|
18
|
-
* requires `epicId`) went with it; Story scope is the only scope
|
|
19
|
-
*
|
|
18
|
+
* requires `epicId`) went with it; Story scope is the only scope. Story #5024
|
|
19
|
+
* deleted those schemas along with the bus that was their only publish path.
|
|
20
20
|
*
|
|
21
21
|
* Public API:
|
|
22
22
|
* - `runCodeReview({ ticketId, headRef, provider, logger, ... })` →
|