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,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/orchestration/lint-baseline-service.js
|
|
3
|
-
*
|
|
4
|
-
* Captures the lint baseline (see `lint-baseline.js capture`). Extracted
|
|
5
|
-
* from the inline implementation that used to live in the deleted
|
|
6
|
-
* `dispatch-engine.js` entry seam so the coordinator no longer reaches into
|
|
7
|
-
* `node:child_process` or `node:fs` directly. Tests can exercise the service with a stubbed exec
|
|
8
|
-
* adapter and no filesystem interaction.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import defaultFs from 'node:fs';
|
|
12
|
-
import path from 'node:path';
|
|
13
|
-
import { getBaselines, getPaths, PROJECT_ROOT } from '../config-resolver.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Shape of the exec adapter passed into {@link LintBaselineService}.
|
|
17
|
-
*
|
|
18
|
-
* Implementations may be sync (e.g. `execFileSync`) or async; the service
|
|
19
|
-
* always `await`s the return value so either works.
|
|
20
|
-
*
|
|
21
|
-
* @typedef {(file: string, args: string[], options?: object) => (void | Promise<void>)} LintBaselineExec
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Minimal logger shape consumed by {@link LintBaselineService}. Matches the
|
|
26
|
-
* surviving `lib/Logger.js` surface (single-argument message form).
|
|
27
|
-
*
|
|
28
|
-
* @typedef {object} LintBaselineLogger
|
|
29
|
-
* @property {(msg: string) => void} info
|
|
30
|
-
* @property {(msg: string) => void} warn
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Outcome of {@link LintBaselineService#capture}.
|
|
35
|
-
*
|
|
36
|
-
* @typedef {object} LintBaselineCaptureResult
|
|
37
|
-
* @property {boolean} skipped True when the baseline file already existed on disk.
|
|
38
|
-
* @property {boolean} [captured] True when the exec adapter ran and succeeded.
|
|
39
|
-
* @property {string} [error] Error message when the exec adapter threw (still non-fatal).
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
export class LintBaselineService {
|
|
43
|
-
/**
|
|
44
|
-
* @param {object} deps
|
|
45
|
-
* @param {LintBaselineExec} deps.exec Injected exec adapter — invoked with `(file, args, options)`.
|
|
46
|
-
* @param {LintBaselineLogger} deps.logger Logger for skip / capture / failure messages.
|
|
47
|
-
* @param {object} deps.settings `.agentrc.json` `settings` block.
|
|
48
|
-
* The baseline artifact path is resolved via
|
|
49
|
-
* `getBaselines(settings).lint.path` (default
|
|
50
|
-
* `baselines/lint.json`); operators override under
|
|
51
|
-
* `delivery.quality.gates.lint.baselinePath`.
|
|
52
|
-
* `scriptsRoot` is resolved via `getPaths(settings)`
|
|
53
|
-
* (lives at `project.paths.scriptsRoot` post-Epic #773 Story 9).
|
|
54
|
-
* @param {typeof import('node:fs')} [deps.fs] Optional `fs` module (defaults to `node:fs`). Kept injectable so unit tests can assert no real-disk access.
|
|
55
|
-
*/
|
|
56
|
-
constructor({ exec, logger, settings, fs = defaultFs }) {
|
|
57
|
-
this.exec = exec;
|
|
58
|
-
this.logger = logger;
|
|
59
|
-
this.settings = settings;
|
|
60
|
-
this.fs = fs;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Capture (or skip) the lint baseline for the given Epic branch.
|
|
65
|
-
*
|
|
66
|
-
* Behaviour:
|
|
67
|
-
* - If the baseline artifact already exists, log a skip and return
|
|
68
|
-
* `{ skipped: true }` — **no exec call is made**.
|
|
69
|
-
* - Otherwise invoke the injected exec adapter to run
|
|
70
|
-
* `node <scriptsRoot>/lint-baseline.js capture`.
|
|
71
|
-
* - Exec failures are logged at `warn` and swallowed — baseline capture
|
|
72
|
-
* is advisory and must never break the dispatch cycle.
|
|
73
|
-
*
|
|
74
|
-
* @param {string} epicBranch Epic branch name (used for logging only).
|
|
75
|
-
* @returns {Promise<LintBaselineCaptureResult>} Resolution of the capture attempt.
|
|
76
|
-
*/
|
|
77
|
-
async capture(epicBranch) {
|
|
78
|
-
const { settings, logger, exec, fs } = this;
|
|
79
|
-
// `settings` is the legacy-shim view (`{ paths, quality, ... }`). The
|
|
80
|
-
// post-reshape accessors read `project.paths` and `delivery.quality`;
|
|
81
|
-
// the shim's pointers are reference-equal to the canonical blocks, so
|
|
82
|
-
// re-wrap here.
|
|
83
|
-
const canonical = {
|
|
84
|
-
project: { paths: settings?.paths },
|
|
85
|
-
delivery: { quality: settings?.quality },
|
|
86
|
-
};
|
|
87
|
-
const lintBaselinePath = getBaselines(canonical).lint.path;
|
|
88
|
-
const absPath = path.resolve(PROJECT_ROOT, lintBaselinePath);
|
|
89
|
-
|
|
90
|
-
if (fs.existsSync(absPath)) {
|
|
91
|
-
logger.info('Lint baseline already exists, skipping capture.');
|
|
92
|
-
return { skipped: true };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
logger.info(`Capturing lint baseline on ${epicBranch}...`);
|
|
96
|
-
const scriptsRoot = getPaths(canonical).scriptsRoot;
|
|
97
|
-
try {
|
|
98
|
-
await exec(
|
|
99
|
-
'node',
|
|
100
|
-
[path.join(PROJECT_ROOT, scriptsRoot, 'lint-baseline.js'), 'capture'],
|
|
101
|
-
{
|
|
102
|
-
cwd: PROJECT_ROOT,
|
|
103
|
-
encoding: 'utf8',
|
|
104
|
-
stdio: 'inherit',
|
|
105
|
-
shell: false,
|
|
106
|
-
},
|
|
107
|
-
);
|
|
108
|
-
return { skipped: false, captured: true };
|
|
109
|
-
} catch (err) {
|
|
110
|
-
logger.warn(`Lint baseline capture failed (non-fatal): ${err.message}`);
|
|
111
|
-
return { skipped: false, captured: false, error: err.message };
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// .agents/scripts/lib/orchestration/pr-base-guard.js
|
|
2
|
-
/**
|
|
3
|
-
* pr-base-guard.js — refuse to open a pull request for a Story that still
|
|
4
|
-
* carries an Epic/parent footer (pre-v2 hierarchy). v2 Stories are
|
|
5
|
-
* standalone; Epic-attached bodies are a forward-cutover refusal, not a
|
|
6
|
-
* redirect onto `epic/<N>`.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { resolveStoryHierarchy } from '../story-lifecycle.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Inspect a Story's body for an Epic/parent reference and refuse any PR
|
|
13
|
-
* when one is present. Callers must re-plan Epic-attached tickets as v2
|
|
14
|
-
* Stories before delivering.
|
|
15
|
-
*
|
|
16
|
-
* @param {object} args
|
|
17
|
-
* @param {number} args.storyId — Story ticket id (used in the error
|
|
18
|
-
* message so the operator can grep the failing surface).
|
|
19
|
-
* @param {string|null|undefined} args.storyBody — raw Story body markdown.
|
|
20
|
-
* When falsy, the guard short-circuits (no body → nothing to enforce).
|
|
21
|
-
* @param {string} args.baseBranch — retained for call-site compatibility;
|
|
22
|
-
* unused once an Epic/parent footer is detected (any base is refused).
|
|
23
|
-
* @throws {Error} when the Story body declares an Epic or Parent footer.
|
|
24
|
-
*/
|
|
25
|
-
export function assertStoryPrBaseAllowed({
|
|
26
|
-
storyId,
|
|
27
|
-
storyBody,
|
|
28
|
-
baseBranch: _baseBranch,
|
|
29
|
-
}) {
|
|
30
|
-
if (!storyBody) return;
|
|
31
|
-
const { epicId } = resolveStoryHierarchy(storyBody);
|
|
32
|
-
if (epicId == null) return;
|
|
33
|
-
throw new Error(
|
|
34
|
-
`Story #${storyId} still declares Epic/Parent #${epicId}. ` +
|
|
35
|
-
'v2 delivery is Story-only — re-plan as a standalone Story (no Epic footer) before opening a PR.',
|
|
36
|
-
);
|
|
37
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* resolves-token.js — single source of truth for the `(resolves #N)`
|
|
3
|
-
* Story-merge integration marker.
|
|
4
|
-
*
|
|
5
|
-
* Background. The deleted `story-close.js` integrated each Story branch into its
|
|
6
|
-
* parent Epic branch with `git merge --no-ff` and a Conventional-Commit
|
|
7
|
-
* subject that carries a ` (resolves #<storyId>)` trailer. That trailer is
|
|
8
|
-
* the *durable* proof a Story landed on `epic/<id>`: the local/remote
|
|
9
|
-
* Story branch is deleted right after the merge, and a later rebase or
|
|
10
|
-
* force-push can drop the Story tip off the merged history, so several
|
|
11
|
-
* recovery and telemetry paths grep the Epic log for the trailer rather
|
|
12
|
-
* than relying on a branch ref.
|
|
13
|
-
*
|
|
14
|
-
* Before this module, six call sites each re-derived the marker inline —
|
|
15
|
-
* three subtly different regexes (`/\(resolves #(\d+)\)/i`,
|
|
16
|
-
* `/\(resolves\s+#\d+\)/i`) and three subtly different `git log --grep`
|
|
17
|
-
* argument shapes (plain `resolves #N`, and the boundary-safe
|
|
18
|
-
* `-E --grep=resolves #N( |\)|$)`). This module collapses them into one
|
|
19
|
-
* token vocabulary with no change to the *emitted* text, so in-flight
|
|
20
|
-
* peer closes that already wrote ` (resolves #N)` keep matching.
|
|
21
|
-
*
|
|
22
|
-
* Exports:
|
|
23
|
-
* - `resolvesToken(storyId)` — the ` (resolves #<id>)` suffix that
|
|
24
|
-
* merge-subject.js appends to the
|
|
25
|
-
* merge-commit subject.
|
|
26
|
-
* - `RESOLVES_TRAILER_RE` — anchored, capturing regex matching
|
|
27
|
-
* `(resolves #<digits>)` in a subject.
|
|
28
|
-
* - `parseResolvesStoryId(subject)`— parse the Story id out of a subject,
|
|
29
|
-
* or `null` when no trailer is present.
|
|
30
|
-
* - `resolvesGrepArgs(storyId)` — the boundary-safe `git log` argument
|
|
31
|
-
* list (`['-E', '--grep=...']`) that
|
|
32
|
-
* matches the trailer for exactly this
|
|
33
|
-
* Story id and not a longer id that
|
|
34
|
-
* shares the prefix.
|
|
35
|
-
* - `resolvesOrRefsGrepArgs(storyId)` — like `resolvesGrepArgs` but matches
|
|
36
|
-
* the fully-parenthesized `(resolves #N)`
|
|
37
|
-
* *or* `(refs #N)` integration marker, for
|
|
38
|
-
* the ref-independent already-merged scan
|
|
39
|
-
* in `story-close-recovery.js`.
|
|
40
|
-
*
|
|
41
|
-
* Pure module — no I/O, no git spawns. Callers own the spawn.
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Build the durable Story-merge marker suffix.
|
|
46
|
-
*
|
|
47
|
-
* The leading space is intentional: the marker is appended to a
|
|
48
|
-
* Conventional-Commit subject (`<type>(<scope>): <title>`) and the space
|
|
49
|
-
* separates the title from the trailer. This is the exact text peer
|
|
50
|
-
* closes already emit — do not change it without a coordinated cutover of
|
|
51
|
-
* every grep below.
|
|
52
|
-
*
|
|
53
|
-
* @param {number|string} storyId
|
|
54
|
-
* @returns {string} ` (resolves #<storyId>)`
|
|
55
|
-
*/
|
|
56
|
-
export function resolvesToken(storyId) {
|
|
57
|
-
return ` (resolves #${storyId})`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Match `(resolves #<digits>)` in a commit subject. Anchored to the
|
|
62
|
-
* parenthesized form because that is the canonical Story-merge trailer
|
|
63
|
-
* shape — a bare `resolves #N` mentioned in prose elsewhere in the
|
|
64
|
-
* message must not win over the trailer. Capturing group 1 is the
|
|
65
|
-
* Story id digits.
|
|
66
|
-
*
|
|
67
|
-
* Case-insensitive to tolerate a hand-authored `(Resolves #N)`; the
|
|
68
|
-
* runtime always emits lower-case.
|
|
69
|
-
*
|
|
70
|
-
* @type {RegExp}
|
|
71
|
-
*/
|
|
72
|
-
export const RESOLVES_TRAILER_RE = /\(resolves #(\d+)\)/i;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Parse the Story id out of a commit subject's `(resolves #N)` trailer.
|
|
76
|
-
* Returns the integer id, or `null` when the subject carries no trailer.
|
|
77
|
-
*
|
|
78
|
-
* @param {string} subject
|
|
79
|
-
* @returns {number|null}
|
|
80
|
-
*/
|
|
81
|
-
export function parseResolvesStoryId(subject) {
|
|
82
|
-
if (typeof subject !== 'string' || subject.length === 0) return null;
|
|
83
|
-
const m = RESOLVES_TRAILER_RE.exec(subject);
|
|
84
|
-
return m ? Number.parseInt(m[1], 10) : null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Build the boundary-safe `git log --grep` argument list that matches the
|
|
89
|
-
* `(resolves #<storyId>)` trailer for *exactly* this Story id.
|
|
90
|
-
*
|
|
91
|
-
* The trailing `( |\)|$)` alternation is what makes the match boundary
|
|
92
|
-
* safe: `resolves #12` must not match a subject that names `#1234`. The
|
|
93
|
-
* canonical merge subject closes the trailer with `)`, so the `\)` branch
|
|
94
|
-
* is the common hit; the ` ` and `$` branches tolerate non-canonical
|
|
95
|
-
* placements (trailer mid-subject, or unterminated at end-of-line).
|
|
96
|
-
*
|
|
97
|
-
* Returned as an argument array (not a string) so callers splat it
|
|
98
|
-
* straight into a `git log` spawn alongside their own ref / format flags.
|
|
99
|
-
* `-E` enables the POSIX-extended alternation.
|
|
100
|
-
*
|
|
101
|
-
* @param {number|string} storyId
|
|
102
|
-
* @returns {string[]} `['-E', '--grep=resolves #<storyId>( |\\)|$)']`
|
|
103
|
-
*/
|
|
104
|
-
export function resolvesGrepArgs(storyId) {
|
|
105
|
-
return ['-E', `--grep=resolves #${storyId}( |\\)|$)`];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Build the boundary-safe `git log --grep` argument list that matches the
|
|
110
|
-
* fully-parenthesized `(resolves #<storyId>)` *or* `(refs #<storyId>)`
|
|
111
|
-
* integration marker for *exactly* this Story id.
|
|
112
|
-
*
|
|
113
|
-
* Unlike {@link resolvesGrepArgs} (which keys off a trailing space / paren /
|
|
114
|
-
* end boundary), this matcher requires the canonical parenthesized form on
|
|
115
|
-
* both sides — `\(...\)` — because it is the ref-independent already-merged
|
|
116
|
-
* signal: when both Story refs are reaped, `story-close-recovery.js` scans
|
|
117
|
-
* the Epic history for the integration commit whose subject carries the
|
|
118
|
-
* trailer. The closing `\)` is the boundary that prevents `#3327` matching
|
|
119
|
-
* `(resolves #33270)`. `refs` is accepted alongside `resolves` because an
|
|
120
|
-
* agent-authored implementation commit references the Story via `(refs #N)`.
|
|
121
|
-
*
|
|
122
|
-
* @param {number|string} storyId
|
|
123
|
-
* @returns {string[]} `['-E', '--grep=\\((resolves|refs) #<storyId>\\)']`
|
|
124
|
-
*/
|
|
125
|
-
export function resolvesOrRefsGrepArgs(storyId) {
|
|
126
|
-
return ['-E', `--grep=\\((resolves|refs) #${storyId}\\)`];
|
|
127
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* spec-section-validator.js — Tech Spec post-authoring section gate.
|
|
3
|
-
*
|
|
4
|
-
* `/plan` Phase 7 authors a Tech Spec from documentation and the Epic body.
|
|
5
|
-
* Phase 8.3 (Holistic Consolidation) then reconciles the draft ticket array
|
|
6
|
-
* against the Tech Spec's `## Delivery Slicing` section, which the
|
|
7
|
-
* decompose-author skill uses as the capability-boundary anchor. When the
|
|
8
|
-
* authored Tech Spec omits that section, the consolidation pass runs against
|
|
9
|
-
* a void and produces groupings that reflect technical shape (cron jobs
|
|
10
|
-
* together) rather than capability boundaries — observed on Epic #18 in
|
|
11
|
-
* `dsj1984/athportal` (planned with v1.54.0).
|
|
12
|
-
*
|
|
13
|
-
* `validateSpecSections` parses a Tech Spec body for the required
|
|
14
|
-
* `## Delivery Slicing` heading (accepting the casing/wording variants
|
|
15
|
-
* below) and returns a deterministic `{ ok, missing[] }` envelope.
|
|
16
|
-
*
|
|
17
|
-
* Story #4403 (Finding 3): this gate used to be run by a standalone
|
|
18
|
-
* `epic-plan-spec-validate.js` CLI as a separate Phase 7.5 workflow step —
|
|
19
|
-
* but by the time the documented ordering ran it, the Phase 7 persist path's
|
|
20
|
-
* `cleanupPhaseTempFiles` had already deleted the temp `techspec.md` file the
|
|
21
|
-
* gate read, so the "blocking gate" could never actually block. The CLI is
|
|
22
|
-
* retired; `runSpecPhase` (`phases/run-spec-phase.js`) now calls
|
|
23
|
-
* `validateSpecSections` directly against the in-memory authored content as
|
|
24
|
-
* part of its input validation, before any GitHub mutation. This is the
|
|
25
|
-
* Phase 8-side counterpart to the retired Phase 6 Epic Clarity Gate —
|
|
26
|
-
* same detect-then-prompt pattern, one phase later, but a hard gate
|
|
27
|
-
* (fail closed) rather than an advisory rubric.
|
|
28
|
-
*
|
|
29
|
-
* `validateSpecSections` and `formatMissingSectionMessage` are pure, I/O-free
|
|
30
|
-
* helpers. `validateSpecFile` is a thin disk-reading convenience for callers
|
|
31
|
-
* (tests, ad-hoc tooling) that hold a path rather than in-memory content.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import { readFile } from 'node:fs/promises';
|
|
35
|
-
import { DELIVERY_SLICING_RE } from '../ticket-body-sections.js';
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* The single required Tech Spec section, with the heading variants the
|
|
39
|
-
* Architect persona may emit. Matched case-insensitively against any
|
|
40
|
-
* level-2 (`## `) heading.
|
|
41
|
-
*
|
|
42
|
-
* - `## Delivery Slicing` (canonical)
|
|
43
|
-
* - `## Delivery slicing` (variant casing)
|
|
44
|
-
* - `## DELIVERY SLICING` (variant casing)
|
|
45
|
-
* - `## Slicing` (shorthand)
|
|
46
|
-
*/
|
|
47
|
-
const REQUIRED_SECTIONS = Object.freeze([
|
|
48
|
-
{
|
|
49
|
-
name: 'Delivery Slicing',
|
|
50
|
-
/** Level-2 heading, allowing the `Delivery ` qualifier to be optional. */
|
|
51
|
-
re: DELIVERY_SLICING_RE,
|
|
52
|
-
},
|
|
53
|
-
]);
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Canonical names of the required sections, in document order. Exported so
|
|
57
|
-
* callers (CLI, tests, downstream tooling) can iterate without re-deriving
|
|
58
|
-
* the list.
|
|
59
|
-
*
|
|
60
|
-
* @type {readonly string[]}
|
|
61
|
-
*/
|
|
62
|
-
export const REQUIRED_SECTION_NAMES = Object.freeze(
|
|
63
|
-
REQUIRED_SECTIONS.map((s) => s.name),
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Validate a Tech Spec body for the required post-authoring sections.
|
|
68
|
-
*
|
|
69
|
-
* @param {{ body: string }} [args]
|
|
70
|
-
* @returns {{ ok: boolean, missing: string[], present: string[] }}
|
|
71
|
-
* `ok` is true when every required section heading is present. `missing`
|
|
72
|
-
* lists the canonical names of absent sections (empty when `ok`).
|
|
73
|
-
* `present` lists the canonical names that were found.
|
|
74
|
-
*/
|
|
75
|
-
export function validateSpecSections({ body } = {}) {
|
|
76
|
-
const source = typeof body === 'string' ? body : '';
|
|
77
|
-
|
|
78
|
-
const present = [];
|
|
79
|
-
const missing = [];
|
|
80
|
-
for (const section of REQUIRED_SECTIONS) {
|
|
81
|
-
if (section.re.test(source)) {
|
|
82
|
-
present.push(section.name);
|
|
83
|
-
} else {
|
|
84
|
-
missing.push(section.name);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return { ok: missing.length === 0, missing, present };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Validate an authored Tech Spec file for the required post-authoring
|
|
93
|
-
* sections. Thin wrapper around `validateSpecSections` that owns the file
|
|
94
|
-
* read so callers holding a path (rather than in-memory content) do not each
|
|
95
|
-
* re-implement the read-then-validate sequence.
|
|
96
|
-
*
|
|
97
|
-
* @param {{ techspecPath: string }} args
|
|
98
|
-
* @returns {Promise<{ ok: boolean, missing: string[], present: string[] }>}
|
|
99
|
-
*/
|
|
100
|
-
export async function validateSpecFile({ techspecPath }) {
|
|
101
|
-
const body = await readFile(techspecPath, 'utf8');
|
|
102
|
-
return validateSpecSections({ body });
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Build the operator-facing failure message for a missing-section result.
|
|
107
|
-
* Names each missing section and tells the operator whether to re-author the
|
|
108
|
-
* spec or add the section by hand before continuing.
|
|
109
|
-
*
|
|
110
|
-
* @param {{ techspecPath: string, missing: string[] }} args
|
|
111
|
-
* @returns {string}
|
|
112
|
-
*/
|
|
113
|
-
export function formatMissingSectionMessage({ techspecPath, missing }) {
|
|
114
|
-
const list = missing.map((name) => `## ${name}`).join(', ');
|
|
115
|
-
return [
|
|
116
|
-
`[spec-section-validator] Tech Spec is missing required section(s): ${list}`,
|
|
117
|
-
` Spec source: ${techspecPath}`,
|
|
118
|
-
'',
|
|
119
|
-
` Phase 8 (decomposition) reconciles the draft ticket array against the`,
|
|
120
|
-
` Tech Spec's "## Delivery Slicing" section — without it, the Phase 8.3`,
|
|
121
|
-
` consolidation pass has no capability-boundary anchor and groups by`,
|
|
122
|
-
` technical shape instead.`,
|
|
123
|
-
'',
|
|
124
|
-
' To continue, do ONE of the following and re-run the Phase 7 persist step:',
|
|
125
|
-
` 1. Re-author the Tech Spec (re-run the Phase 7 spec-author step) so it`,
|
|
126
|
-
` emits a "## Delivery Slicing" section, OR`,
|
|
127
|
-
` 2. Add a "## Delivery Slicing" section to the Tech Spec by hand,`,
|
|
128
|
-
` describing the capability boundaries the work should be sliced along.`,
|
|
129
|
-
].join('\n');
|
|
130
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Best-effort blocked-close result helper shared by Story-scope review.
|
|
3
|
-
*
|
|
4
|
-
* @module lib/orchestration/story-close/emit-blocked
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Logger } from '../../Logger.js';
|
|
8
|
-
import { emitTerseResult } from '../../observability/terse-result.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Best-effort `story.blocked` lifecycle emit. The bus is optional and emit
|
|
12
|
-
* failures are swallowed so the blocked result remains the caller-visible
|
|
13
|
-
* outcome.
|
|
14
|
-
*/
|
|
15
|
-
async function emitStoryBlockedSafe({ bus, storyId, reason, logger }) {
|
|
16
|
-
if (!bus) return;
|
|
17
|
-
try {
|
|
18
|
-
await bus.emit('story.blocked', {
|
|
19
|
-
storyId: Number(storyId),
|
|
20
|
-
reason: String(reason),
|
|
21
|
-
});
|
|
22
|
-
} catch (err) {
|
|
23
|
-
logger?.warn?.(
|
|
24
|
-
`[story-close] story.blocked emit failed for #${storyId} (swallowed): ${err?.message ?? err}`,
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @param {object} args
|
|
31
|
-
* @returns {Promise<object>}
|
|
32
|
-
*/
|
|
33
|
-
export async function emitBlockedCloseResult({
|
|
34
|
-
storyId,
|
|
35
|
-
phase,
|
|
36
|
-
reason,
|
|
37
|
-
extra = {},
|
|
38
|
-
bus = null,
|
|
39
|
-
progress,
|
|
40
|
-
blockedMessage,
|
|
41
|
-
logger = Logger,
|
|
42
|
-
}) {
|
|
43
|
-
const result = { success: false, status: 'blocked', phase, reason, ...extra };
|
|
44
|
-
await emitStoryBlockedSafe({ bus, storyId, reason, logger });
|
|
45
|
-
// Story #4685 — full detail to a temp log; single summary line in its place.
|
|
46
|
-
emitTerseResult({
|
|
47
|
-
label: 'STORY CLOSE RESULT',
|
|
48
|
-
result,
|
|
49
|
-
scope: storyId,
|
|
50
|
-
summary: { storyId, status: 'blocked', phase, reason },
|
|
51
|
-
log: logger,
|
|
52
|
-
});
|
|
53
|
-
progress('BLOCKED', blockedMessage);
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* phases/code-review.js — Story-scope code-review phase
|
|
3
|
-
* (Story #2840, Epic #2815 — Pluggable Code Review + Story-Level Review).
|
|
4
|
-
*
|
|
5
|
-
* Sits between the close-validation gate chain and merge in the deleted
|
|
6
|
-
* pre-v2 Epic close path (`runStoryCloseLocked` / `locked-pipeline.js`,
|
|
7
|
-
* merge target `epic/<id>`). The v2 `/deliver` path
|
|
8
|
-
* (`single-story-close.js`) reviews `main`…`story-<id>` instead. The
|
|
9
|
-
* configured ReviewProvider runs against the supplied base…head diff. The
|
|
10
|
-
* unified `verification-results`
|
|
11
|
-
* structured comment is posted to the Story issue (default
|
|
12
|
-
* `commentTargetId === ticketId` inside `runCodeReview`). Outcomes:
|
|
13
|
-
*
|
|
14
|
-
* - clean / non-critical findings → `{ blocked: null }`; the pipeline
|
|
15
|
-
* proceeds to merge.
|
|
16
|
-
* - critical findings → `{ blocked: <envelope> }`; the
|
|
17
|
-
* pipeline short-circuits, the Story is not merged, and the CLI
|
|
18
|
-
* exits non-zero via `exitCode: 1` on the envelope.
|
|
19
|
-
* - adapter throw / wiring failure → `{ blocked: null }`; the close
|
|
20
|
-
* proceeds because the review surface is advisory for transport
|
|
21
|
-
* failures (the same posture refresh.js takes). A warn is logged.
|
|
22
|
-
*
|
|
23
|
-
* Bus contract: `runCodeReview` only emits lifecycle events for
|
|
24
|
-
* `scope: 'epic'` (the `code-review.end` schema requires `epicId`
|
|
25
|
-
* and the ledger only spans Epic lifecycles — see Story #2839 lock-in
|
|
26
|
-
* in `code-review.js`). The Story-scope path here therefore does not
|
|
27
|
-
* forward the bus, and `story.blocked` is emitted separately on the
|
|
28
|
-
* critical-halt path so the Epic-scoped lifecycle ledger still sees
|
|
29
|
-
* the Story drop out.
|
|
30
|
-
*
|
|
31
|
-
* The shared spine both close paths call `runCodeReview` through
|
|
32
|
-
* (`runStoryReviewCore`, Story #3653) and the shift-left local-lens pass
|
|
33
|
-
* (Epic #4405) live in `review-core.js` and `local-lens-review.js`
|
|
34
|
-
* respectively (extracted by Story #4603). This module is the Epic-attached
|
|
35
|
-
* phase entry point: it owns the advisory error posture and the
|
|
36
|
-
* critical-halt → blocked-envelope translation, and nothing else.
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
import { Logger } from '../../../Logger.js';
|
|
40
|
-
import { runCodeReview } from '../../code-review.js';
|
|
41
|
-
import { summarizeDegradations } from '../../review-providers/degraded-gates.js';
|
|
42
|
-
import { emitBlockedCloseResult } from '../emit-blocked.js';
|
|
43
|
-
import { runLocalLensReview } from './local-lens-review.js';
|
|
44
|
-
import { runStoryReviewCore } from './review-core.js';
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Read a review envelope's severity counts, tolerating the partial envelope a
|
|
48
|
-
* misbehaving provider adapter can return. Pure.
|
|
49
|
-
*
|
|
50
|
-
* @param {object|null|undefined} reviewResult
|
|
51
|
-
* @returns {{ critical: number, high: number, medium: number, suggestion: number }}
|
|
52
|
-
*/
|
|
53
|
-
function resolveSeverity(reviewResult) {
|
|
54
|
-
return (
|
|
55
|
-
reviewResult?.severity ?? {
|
|
56
|
-
critical: 0,
|
|
57
|
-
high: 0,
|
|
58
|
-
medium: 0,
|
|
59
|
-
suggestion: 0,
|
|
60
|
-
}
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Collect the extra fields for the code-review-critical blocked envelope. Pure.
|
|
66
|
-
*
|
|
67
|
-
* @param {{ storyId: number, reviewResult: object }} args
|
|
68
|
-
* @returns {object}
|
|
69
|
-
*/
|
|
70
|
-
function buildCodeReviewBlockedExtra({ storyId, reviewResult }) {
|
|
71
|
-
return {
|
|
72
|
-
storyId,
|
|
73
|
-
blockerReason: reviewResult?.blockerReason ?? null,
|
|
74
|
-
severity: resolveSeverity(reviewResult),
|
|
75
|
-
posted: reviewResult?.posted ?? false,
|
|
76
|
-
exitCode: 1,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Render the operator-facing one-line summary of a completed (non-halting)
|
|
82
|
-
* review. Pure.
|
|
83
|
-
*
|
|
84
|
-
* @param {object} reviewResult
|
|
85
|
-
* @returns {string}
|
|
86
|
-
*/
|
|
87
|
-
function formatReviewSummary(reviewResult) {
|
|
88
|
-
const { high, medium, suggestion } = resolveSeverity(reviewResult);
|
|
89
|
-
const posted = reviewResult?.posted ?? false;
|
|
90
|
-
// Story #4839 — `degraded` is always stated: an absent degradation line must
|
|
91
|
-
// never be the way an operator concludes that every gate ran.
|
|
92
|
-
const degraded = summarizeDegradations(reviewResult?.degradations);
|
|
93
|
-
return `Review complete — high=${high} medium=${medium} suggestion=${suggestion} degraded=${degraded} (posted=${posted}).`;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Run the shared review spine, absorbing an adapter / wiring failure into this
|
|
98
|
-
* phase's advisory posture: the review is best-effort when the provider cannot
|
|
99
|
-
* complete, and the gates already vouched for the diff at this point.
|
|
100
|
-
*
|
|
101
|
-
* @param {object} args Spine arguments (see {@link runStoryReviewCore}).
|
|
102
|
-
* @returns {Promise<object|null>} The review envelope, or `null` when the
|
|
103
|
-
* review threw and the close should proceed unblocked.
|
|
104
|
-
*/
|
|
105
|
-
async function invokeReviewCore(args) {
|
|
106
|
-
try {
|
|
107
|
-
return await runStoryReviewCore(args);
|
|
108
|
-
} catch (err) {
|
|
109
|
-
Logger.warn?.(
|
|
110
|
-
`[story-close] ⚠️ code-review phase failed (continuing without blocker): ${err?.message ?? err}`,
|
|
111
|
-
);
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Translate a halting (critical-findings) review into the blocked envelope the
|
|
118
|
-
* caller returns verbatim, emitting `story.blocked` onto the bus on the way.
|
|
119
|
-
*
|
|
120
|
-
* @param {{
|
|
121
|
-
* storyId: number,
|
|
122
|
-
* reviewResult: object,
|
|
123
|
-
* bus: { emit: Function }|null,
|
|
124
|
-
* progress: (tag: string, msg: string) => void,
|
|
125
|
-
* }} args
|
|
126
|
-
* @returns {Promise<object>} The blocked envelope.
|
|
127
|
-
*/
|
|
128
|
-
async function emitCriticalBlock({ storyId, reviewResult, bus, progress }) {
|
|
129
|
-
const { critical } = resolveSeverity(reviewResult);
|
|
130
|
-
return emitBlockedCloseResult({
|
|
131
|
-
storyId,
|
|
132
|
-
phase: 'closing',
|
|
133
|
-
reason: 'code-review-critical',
|
|
134
|
-
extra: buildCodeReviewBlockedExtra({ storyId, reviewResult }),
|
|
135
|
-
bus,
|
|
136
|
-
progress,
|
|
137
|
-
blockedMessage: `Story #${storyId} blocked: code-review reported ${critical} critical blocker(s).`,
|
|
138
|
-
logger: Logger,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Run a Story-scope code review against the supplied base…head diff
|
|
144
|
-
* (v2: `main`…`story-<id>` via `single-story-close.js`; pre-v2 Epic
|
|
145
|
-
* close: `epic/<id>`…`story-<id>`) and post the structured
|
|
146
|
-
* `code-review` comment to the Story issue. Returns `{ blocked }` where
|
|
147
|
-
* `blocked` is either `null` (caller proceeds to open/merge the PR) or the
|
|
148
|
-
* blocked-envelope (caller returns it verbatim and the CLI exits 1).
|
|
149
|
-
*
|
|
150
|
-
* Review depth is derived inside `runCodeReview` from this Story's own base…head
|
|
151
|
-
* diff — a narrow change touching a registered sensitive path earns `deep`, a
|
|
152
|
-
* small change touching none gets `light`, a wide diff earns `deep` on size, and
|
|
153
|
-
* an unenumerable diff resolves `standard` (Story #4542). Depth is input-only:
|
|
154
|
-
* it never changes `{ blocked }` or the posted comment.
|
|
155
|
-
*
|
|
156
|
-
* @param {{
|
|
157
|
-
* storyId: number|string,
|
|
158
|
-
* baseBranch: string,
|
|
159
|
-
* storyBranch: string,
|
|
160
|
-
* provider: object,
|
|
161
|
-
* bus: { emit: Function }|null,
|
|
162
|
-
* progress: (tag: string, msg: string) => void,
|
|
163
|
-
* runCodeReviewFn?: typeof runCodeReview,
|
|
164
|
-
* runLocalLensReviewFn?: typeof runLocalLensReview,
|
|
165
|
-
* }} args
|
|
166
|
-
* @returns {Promise<{ blocked: object|null, localLensReview?: object }>}
|
|
167
|
-
* `localLensReview` carries the Story-scope local-lens pass outcome
|
|
168
|
-
* (Epic #4405, Story #4409) when the review completed; it is absent only when
|
|
169
|
-
* the whole review phase threw (advisory failure).
|
|
170
|
-
*/
|
|
171
|
-
export async function runStoryCodeReview({
|
|
172
|
-
storyId,
|
|
173
|
-
baseBranch,
|
|
174
|
-
storyBranch,
|
|
175
|
-
provider,
|
|
176
|
-
bus,
|
|
177
|
-
progress,
|
|
178
|
-
runCodeReviewFn = runCodeReview,
|
|
179
|
-
runLocalLensReviewFn = runLocalLensReview,
|
|
180
|
-
}) {
|
|
181
|
-
const storyIdNum = Number(storyId);
|
|
182
|
-
progress(
|
|
183
|
-
'CODE-REVIEW',
|
|
184
|
-
`Running Story-scope review (${baseBranch}…${storyBranch})...`,
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
const reviewResult = await invokeReviewCore({
|
|
188
|
-
storyId: storyIdNum,
|
|
189
|
-
baseRef: baseBranch,
|
|
190
|
-
headRef: storyBranch,
|
|
191
|
-
provider,
|
|
192
|
-
progress,
|
|
193
|
-
runCodeReviewFn,
|
|
194
|
-
runLocalLensReviewFn,
|
|
195
|
-
});
|
|
196
|
-
if (reviewResult === null) return { blocked: null };
|
|
197
|
-
|
|
198
|
-
const localLensReview = reviewResult.localLensReview;
|
|
199
|
-
if (reviewResult.halted) {
|
|
200
|
-
const blocked = await emitCriticalBlock({
|
|
201
|
-
storyId: storyIdNum,
|
|
202
|
-
reviewResult,
|
|
203
|
-
bus,
|
|
204
|
-
progress,
|
|
205
|
-
});
|
|
206
|
-
return { blocked, localLensReview };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
progress('CODE-REVIEW', formatReviewSummary(reviewResult));
|
|
210
|
-
return { blocked: null, localLensReview };
|
|
211
|
-
}
|