mandrel 2.31.0 → 2.33.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 +12 -9
- package/.agents/docs/agentrc-reference.json +63 -58
- package/.agents/docs/configuration.md +275 -227
- package/.agents/docs/execution-reference.md +13 -14
- package/.agents/docs/quality-gates.md +195 -23
- package/.agents/instructions.md +2 -5
- package/.agents/rules/git-conventions-reference.md +27 -27
- package/.agents/rules/git-conventions.md +4 -2
- package/.agents/rules/known-tooling-behavior.md +66 -30
- package/.agents/rules/testing-standards.md +35 -71
- package/.agents/runtime-deps.json +2 -2
- package/.agents/schemas/agentrc.schema.json +1955 -1410
- 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 +16 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/audit-to-stories.js +160 -41
- 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 +159 -0
- package/.agents/scripts/check-lifecycle-lint.js +108 -124
- 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 +81 -35
- package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +185 -0
- 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/runners.js +38 -16
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +245 -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/dependency-parser.js +20 -7
- 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/findings/provenance-field.js +135 -0
- package/.agents/scripts/lib/findings/route-finding.js +57 -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-config-resolver.js +181 -0
- package/.agents/scripts/lib/knip-entry-sync.js +508 -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 +7 -26
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +69 -5
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +69 -13
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +49 -0
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +77 -50
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
- package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +116 -1
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-validator.js +38 -0
- 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/footer-block.js +97 -0
- package/.agents/scripts/lib/story-body/story-body.js +18 -81
- 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/wave-runner/footprint.js +306 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +198 -181
- 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/blocked-by-add.js +25 -10
- 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 +21 -7
- 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/stories-wave-tick.js +192 -9
- package/.agents/scripts/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +1 -11
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +28 -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 +30 -2
- package/.agents/workflows/helpers/deliver-story-reference.md +84 -16
- package/.agents/workflows/helpers/plan-reference.md +76 -0
- 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 +58 -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 +14 -5
- 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
|
@@ -112,17 +112,6 @@ export class ITicketingProvider {
|
|
|
112
112
|
throw new Error('Not implemented: getTicketDependencies');
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
/**
|
|
116
|
-
* Fetch recent comments across the repository.
|
|
117
|
-
* Useful for auditing and visualization of agent telemetry.
|
|
118
|
-
*
|
|
119
|
-
* @param {number} [limit=100]
|
|
120
|
-
* @returns {Promise<object[]>}
|
|
121
|
-
*/
|
|
122
|
-
async getRecentComments(_limit = 100) {
|
|
123
|
-
throw new Error('Not implemented: getRecentComments');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
115
|
/**
|
|
127
116
|
* Fetch all comments for a specific ticket.
|
|
128
117
|
*
|
|
@@ -137,42 +126,6 @@ export class ITicketingProvider {
|
|
|
137
126
|
// Write Operations
|
|
138
127
|
// ---------------------------------------------------------------------------
|
|
139
128
|
|
|
140
|
-
/**
|
|
141
|
-
* Create a child ticket within an Epic's structural hierarchy.
|
|
142
|
-
*
|
|
143
|
-
* @param {number} parentId - GitHub Issue number of the immediate structural parent (e.g. Epic or Story).
|
|
144
|
-
* @param {{
|
|
145
|
-
* epicId: number,
|
|
146
|
-
* title: string,
|
|
147
|
-
* body: string,
|
|
148
|
-
* labels: string[],
|
|
149
|
-
* dependencies: number[]
|
|
150
|
-
* }} ticketData - The ticket content and metadata.
|
|
151
|
-
* @returns {Promise<{ id: number, url: string }>}
|
|
152
|
-
*/
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Link an existing issue as a sub-issue of a parent.
|
|
156
|
-
*
|
|
157
|
-
* @param {number} parentId - GitHub Issue number of the parent.
|
|
158
|
-
* @param {number} childId - GitHub internal database ID of the sub-issue.
|
|
159
|
-
* @returns {Promise<void>}
|
|
160
|
-
*/
|
|
161
|
-
async addSubIssue(_parentId, _childId) {
|
|
162
|
-
throw new Error('Not implemented: addSubIssue');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Remove a sub-issue link from a parent.
|
|
167
|
-
*
|
|
168
|
-
* @param {number} parentId - GitHub Issue number of the parent.
|
|
169
|
-
* @param {number} childId - GitHub internal database ID of the sub-issue.
|
|
170
|
-
* @returns {Promise<void>}
|
|
171
|
-
*/
|
|
172
|
-
async removeSubIssue(_parentId, _childId) {
|
|
173
|
-
throw new Error('Not implemented: removeSubIssue');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
129
|
/**
|
|
177
130
|
* Mutate labels, body (tasklist checkboxes), and assignees on a ticket.
|
|
178
131
|
*
|
|
@@ -213,17 +166,6 @@ export class ITicketingProvider {
|
|
|
213
166
|
throw new Error('Not implemented: deleteComment');
|
|
214
167
|
}
|
|
215
168
|
|
|
216
|
-
/**
|
|
217
|
-
* Open a Pull Request linking the specified ticket.
|
|
218
|
-
*
|
|
219
|
-
* @param {string} branchName - The source branch for the PR.
|
|
220
|
-
* @param {number} ticketId - GitHub Issue number to link.
|
|
221
|
-
* @returns {Promise<{ number: number, url: string, htmlUrl: string }>}
|
|
222
|
-
*/
|
|
223
|
-
async createPullRequest(_branchName, _ticketId) {
|
|
224
|
-
throw new Error('Not implemented: createPullRequest');
|
|
225
|
-
}
|
|
226
|
-
|
|
227
169
|
// ---------------------------------------------------------------------------
|
|
228
170
|
// Setup Operations (used by bootstrap)
|
|
229
171
|
// ---------------------------------------------------------------------------
|
|
@@ -16,25 +16,25 @@
|
|
|
16
16
|
* @module lib/audit-baselines/staleness
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { execFileCapture } from '../child-exec.js';
|
|
20
20
|
import { KIND_SPECS } from './kinds.js';
|
|
21
21
|
import { ageInDays } from './read.js';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Run a git command under `cwd`, returning trimmed stdout, or `null` on any
|
|
25
|
-
* failure or empty result.
|
|
25
|
+
* failure or empty result. Buffer and shell policy come from the shared
|
|
26
|
+
* child-process surface ([`child-exec.js`](../child-exec.js)).
|
|
26
27
|
*
|
|
27
28
|
* @param {string[]} args
|
|
28
29
|
* @param {{ cwd: string, run?: Function }} io
|
|
29
30
|
* @returns {string | null}
|
|
30
31
|
*/
|
|
31
|
-
function git(args, { cwd, run
|
|
32
|
+
function git(args, { cwd, run }) {
|
|
32
33
|
try {
|
|
33
34
|
const stdout = String(
|
|
34
|
-
|
|
35
|
+
execFileCapture('git', args, {
|
|
36
|
+
run,
|
|
35
37
|
cwd,
|
|
36
|
-
encoding: 'utf8',
|
|
37
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
38
38
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
39
39
|
}),
|
|
40
40
|
).trim();
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* @module lib/audit-baselines/trend
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { execFileCapture } from '../child-exec.js';
|
|
20
20
|
import { trendRollupOf } from './kinds.js';
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -25,13 +25,13 @@ import { trendRollupOf } from './kinds.js';
|
|
|
25
25
|
* @param {{ cwd: string, relPath: string, limit: number, run?: Function }} args
|
|
26
26
|
* @returns {Array<{ sha: string, committedAt: string }>}
|
|
27
27
|
*/
|
|
28
|
-
function listCommits({ cwd, relPath, limit, run
|
|
28
|
+
function listCommits({ cwd, relPath, limit, run }) {
|
|
29
29
|
let stdout;
|
|
30
30
|
try {
|
|
31
|
-
stdout =
|
|
31
|
+
stdout = execFileCapture(
|
|
32
32
|
'git',
|
|
33
33
|
['log', `-n${limit}`, '--format=%H %cI', '--', relPath],
|
|
34
|
-
{
|
|
34
|
+
{ run, cwd, stdio: ['ignore', 'pipe', 'ignore'] },
|
|
35
35
|
);
|
|
36
36
|
} catch {
|
|
37
37
|
return [];
|
|
@@ -52,13 +52,12 @@ function listCommits({ cwd, relPath, limit, run = execFileSync }) {
|
|
|
52
52
|
* @param {{ cwd: string, kind: string, sha: string, relPath: string, run?: Function }} args
|
|
53
53
|
* @returns {object | null}
|
|
54
54
|
*/
|
|
55
|
-
function rollupAt({ cwd, kind, sha, relPath, run
|
|
55
|
+
function rollupAt({ cwd, kind, sha, relPath, run }) {
|
|
56
56
|
let stdout;
|
|
57
57
|
try {
|
|
58
|
-
stdout =
|
|
58
|
+
stdout = execFileCapture('git', ['show', `${sha}:${relPath}`], {
|
|
59
|
+
run,
|
|
59
60
|
cwd,
|
|
60
|
-
encoding: 'utf8',
|
|
61
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
62
61
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
63
62
|
});
|
|
64
63
|
} catch {
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
* @module lib/audit-baselines/weights
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { execFileSync } from 'node:child_process';
|
|
20
19
|
import fs from 'node:fs';
|
|
21
20
|
import path from 'node:path';
|
|
21
|
+
import { execFileCapture } from '../child-exec.js';
|
|
22
22
|
import { computeInDegree, resolveRepoGraph } from '../import-graph.js';
|
|
23
23
|
|
|
24
24
|
/** Neutral multiplier every degraded weight collapses to. */
|
|
@@ -47,10 +47,10 @@ function saturate(count, half) {
|
|
|
47
47
|
* `degraded` is true when git could not answer at all — not a git work
|
|
48
48
|
* tree, no commits yet, or the binary is unavailable.
|
|
49
49
|
*/
|
|
50
|
-
export function readChurn({ cwd, windowDays = 180, run
|
|
50
|
+
export function readChurn({ cwd, windowDays = 180, run }) {
|
|
51
51
|
let stdout;
|
|
52
52
|
try {
|
|
53
|
-
stdout =
|
|
53
|
+
stdout = execFileCapture(
|
|
54
54
|
'git',
|
|
55
55
|
[
|
|
56
56
|
'log',
|
|
@@ -60,9 +60,8 @@ export function readChurn({ cwd, windowDays = 180, run = execFileSync }) {
|
|
|
60
60
|
'--no-renames',
|
|
61
61
|
],
|
|
62
62
|
{
|
|
63
|
+
run,
|
|
63
64
|
cwd,
|
|
64
|
-
encoding: 'utf8',
|
|
65
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
66
65
|
// git narrates "not a git repository" on stderr; the degradation is
|
|
67
66
|
// reported in the envelope, not shouted at the operator.
|
|
68
67
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
@@ -41,7 +41,7 @@ import path from 'node:path';
|
|
|
41
41
|
import { AUDIT_LENSES } from '../audit-to-stories/audit-lenses.js';
|
|
42
42
|
import { getPaths, PROJECT_ROOT, resolveConfig } from '../config-resolver.js';
|
|
43
43
|
import { Logger } from '../Logger.js';
|
|
44
|
-
import { estimateTokens } from '../orchestration/
|
|
44
|
+
import { estimateTokens } from '../orchestration/spec-spill.js';
|
|
45
45
|
import {
|
|
46
46
|
changeSetLacksSiblingTest,
|
|
47
47
|
matchesAnyFilePattern,
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* structured contract.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
import path from 'node:path';
|
|
25
26
|
import { AGENT_LABELS, RISK_LABELS, TYPE_LABELS } from '../label-constants.js';
|
|
26
27
|
import { serialize } from '../story-body/story-body.js';
|
|
27
28
|
import { definesAuditLabel } from './audit-label-taxonomy.js';
|
|
@@ -116,10 +117,7 @@ function acceptanceCriteriaFromGroup(group) {
|
|
|
116
117
|
|
|
117
118
|
/**
|
|
118
119
|
* Resolve the `edges[]` sequencing anchored on this group. Each edge whose
|
|
119
|
-
* `fromGroupKey` matches this group's key contributes its `toGroupKey`.
|
|
120
|
-
* keys are the only stable identifier available at emit time — issues are not
|
|
121
|
-
* numbered yet — so the relationship is preserved as machine-readable keys the
|
|
122
|
-
* operator can resolve.
|
|
120
|
+
* `fromGroupKey` matches this group's key contributes its `toGroupKey`.
|
|
123
121
|
*
|
|
124
122
|
* @param {object} group
|
|
125
123
|
* @param {Array<{ fromGroupKey: string, toGroupKey: string }>} edges
|
|
@@ -135,22 +133,38 @@ function sequencingDepsForGroup(group, edges) {
|
|
|
135
133
|
}
|
|
136
134
|
|
|
137
135
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* issue refs (`blocked by #123`), which do not exist before the issues are
|
|
141
|
-
* opened; rendering the group-key sequencing as its own informational section
|
|
142
|
-
* keeps the signal in the body (not discarded — Story #4270) and survives
|
|
143
|
-
* `parse()` / `serialize()` round-tripping (it is preamble/extended content,
|
|
144
|
-
* not a structured section). Returns the empty string when there is no
|
|
145
|
-
* sequencing to surface.
|
|
136
|
+
* Resolve this group's sequencing to canonical `#N` issue refs, or `[]` when
|
|
137
|
+
* the caller has no issue numbers yet (Story #5044).
|
|
146
138
|
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
139
|
+
* Group keys are the only identifier that exists at *emit* time — the issues
|
|
140
|
+
* are not numbered — which is why this used to render as a prose
|
|
141
|
+
* `## Sequencing` block that nothing could act on. Standalone audit Stories
|
|
142
|
+
* therefore hardcoded `depends_on: []`, and their only actual serializer was
|
|
143
|
+
* an accident: their shared provenance footers collided under the delivery
|
|
144
|
+
* footprint guard. Narrowing that scrape removes the accident, so the ordering
|
|
145
|
+
* has to become real in the same change.
|
|
146
|
+
*
|
|
147
|
+
* The resolution is the two-pass shape `plan-persist` already uses: create every
|
|
148
|
+
* issue first, then re-render each body with the now-known numbers and mirror
|
|
149
|
+
* the same edges as native `blocked_by` relations. An edge whose target was not
|
|
150
|
+
* created (deduped against an existing Issue, suppressed by the ledger) simply
|
|
151
|
+
* drops — a `blocked by #undefined` would be worse than an absent edge.
|
|
152
|
+
*
|
|
153
|
+
* A **plain object**, deliberately, not a `Map`: the same map is handed to
|
|
154
|
+
* `applyBlockedByDependencies`, which indexes it with property access, so a
|
|
155
|
+
* `Map` there would silently resolve every lookup to `undefined`, skip every
|
|
156
|
+
* edge, and report success having written nothing. One shape, both halves.
|
|
157
|
+
*
|
|
158
|
+
* @param {string[]} deps Group keys this group depends on.
|
|
159
|
+
* @param {Record<string, number>|null} issueByGroupKey
|
|
160
|
+
* @returns {string[]} `#N` refs, in `deps` order.
|
|
149
161
|
*/
|
|
150
|
-
function
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
function dependencyRefs(deps, issueByGroupKey) {
|
|
163
|
+
if (!issueByGroupKey) return [];
|
|
164
|
+
return deps
|
|
165
|
+
.map((key) => issueByGroupKey[key])
|
|
166
|
+
.filter((n) => Number.isInteger(n) && n > 0)
|
|
167
|
+
.map((n) => `#${n}`);
|
|
154
168
|
}
|
|
155
169
|
|
|
156
170
|
function agentPromptsSection(group) {
|
|
@@ -162,6 +176,21 @@ function agentPromptsSection(group) {
|
|
|
162
176
|
return blocks.join('\n\n') || '_(no copy-pasteable prompts captured)_';
|
|
163
177
|
}
|
|
164
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Link each source audit report **once**.
|
|
181
|
+
*
|
|
182
|
+
* This used to render `- [\`path\`](path)` — the same
|
|
183
|
+
* `temp/audits/audit-<lens>-results.md` in the link text and again in the URL,
|
|
184
|
+
* byte-identical across every Story of a same-lens sweep. That doubled a token
|
|
185
|
+
* the delivery footprint guard scraped as edit intent, so a lens's whole cohort
|
|
186
|
+
* serialized on a report none of them would ever write to (Story #5044). The
|
|
187
|
+
* guard now ignores markdown-link URLs and temp-root paths, but rendering the
|
|
188
|
+
* path twice was never useful to a reader either: the file name is the label,
|
|
189
|
+
* the path is the target.
|
|
190
|
+
*
|
|
191
|
+
* @param {object} group
|
|
192
|
+
* @returns {string}
|
|
193
|
+
*/
|
|
165
194
|
function contextLinksFromGroup(group) {
|
|
166
195
|
const reports = uniq(
|
|
167
196
|
(group.findings ?? [])
|
|
@@ -169,7 +198,11 @@ function contextLinksFromGroup(group) {
|
|
|
169
198
|
.filter((s) => typeof s === 'string'),
|
|
170
199
|
);
|
|
171
200
|
if (reports.length === 0) return '_(no source audit reports captured)_';
|
|
172
|
-
|
|
201
|
+
// `path.basename` rather than `split('/')`: on win32 it splits on both
|
|
202
|
+
// separators, so an absolute Windows path yields the file name instead of
|
|
203
|
+
// the whole path — which would render the path twice in one link and
|
|
204
|
+
// re-create the very duplication this function exists to remove.
|
|
205
|
+
return reports.map((r) => `- [${path.basename(r)}](${r})`).join('\n');
|
|
173
206
|
}
|
|
174
207
|
|
|
175
208
|
function labelsForGroup(group) {
|
|
@@ -226,20 +259,27 @@ function assertLabelsInTaxonomy(labels) {
|
|
|
226
259
|
* — the dependency `edges[]` emitted by `groupFindings`. Edges anchored on
|
|
227
260
|
* this group are carried through to `depends_on[]`; omit when no sequencing
|
|
228
261
|
* is known.
|
|
229
|
-
* @
|
|
262
|
+
* @param {Record<string, number>|null} [params.issueByGroupKey]
|
|
263
|
+
* — group key → opened issue number. Supplied on the **second** pass, once
|
|
264
|
+
* the issues exist, so this group's edges render as canonical
|
|
265
|
+
* `blocked by #N` footers (Story #5044). Omit on the first pass.
|
|
266
|
+
* @returns {{ title: string, body: string, labels: string[], groupKey: string, dependsOn: string[] }}
|
|
267
|
+
* `groupKey` and `dependsOn` are the caller's handle on the second pass:
|
|
268
|
+
* they name this Story and the groups it must follow, so the caller can map
|
|
269
|
+
* both onto issue numbers without re-deriving the grouping.
|
|
230
270
|
*/
|
|
231
|
-
export function buildStoryBody({ group, edges = [] }) {
|
|
271
|
+
export function buildStoryBody({ group, edges = [], issueByGroupKey = null }) {
|
|
232
272
|
if (!group || !Array.isArray(group.findings)) {
|
|
233
273
|
throw new Error('buildStoryBody: group with findings[] is required');
|
|
234
274
|
}
|
|
235
275
|
const title = group.title;
|
|
276
|
+
const dependsOn = sequencingDepsForGroup(group, edges);
|
|
236
277
|
|
|
237
278
|
// Build the canonical StoryBody object from the audit group data. The
|
|
238
279
|
// acceptance + verify arrays are populated so the body clears the
|
|
239
|
-
// inline-contract bar; changes[] carries the file footprint.
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
// canonical depends_on footer.
|
|
280
|
+
// inline-contract bar; changes[] carries the file footprint. `depends_on`
|
|
281
|
+
// is empty on the first pass (the blockers have no issue numbers yet) and
|
|
282
|
+
// carries real `#N` refs on the second — see dependencyRefs.
|
|
243
283
|
const storyBody = {
|
|
244
284
|
goal: goalFromGroup(group),
|
|
245
285
|
changes: changesFromGroup(group),
|
|
@@ -248,21 +288,21 @@ export function buildStoryBody({ group, edges = [] }) {
|
|
|
248
288
|
references: [],
|
|
249
289
|
wide: null,
|
|
250
290
|
reason_to_exist: null,
|
|
251
|
-
depends_on:
|
|
252
|
-
estimated_test_files: null,
|
|
291
|
+
depends_on: dependencyRefs(dependsOn, issueByGroupKey),
|
|
253
292
|
};
|
|
254
293
|
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
294
|
+
// The `---` / `blocked by #N` footer is the canonical serializer's own, so
|
|
295
|
+
// the body round-trips through `parse()` and `/deliver`'s resolver reads the
|
|
296
|
+
// ordering from the same place it reads every other Story's.
|
|
297
|
+
const canonicalSections = serialize(storyBody, {
|
|
298
|
+
includeFooter: storyBody.depends_on.length > 0,
|
|
299
|
+
});
|
|
258
300
|
|
|
259
|
-
// Append audit-specific extended sections (
|
|
260
|
-
//
|
|
261
|
-
// shape.
|
|
301
|
+
// Append audit-specific extended sections (agent prompts, context links,
|
|
302
|
+
// provenance footers) that are not part of the canonical shape.
|
|
262
303
|
const body = [
|
|
263
304
|
canonicalSections,
|
|
264
305
|
'',
|
|
265
|
-
...(sequencing ? [sequencing] : []),
|
|
266
306
|
'## Agent Prompts',
|
|
267
307
|
'',
|
|
268
308
|
agentPromptsSection(group),
|
|
@@ -277,5 +317,11 @@ export function buildStoryBody({ group, edges = [] }) {
|
|
|
277
317
|
renderSemanticKeyFooter(group.findings),
|
|
278
318
|
].join('\n');
|
|
279
319
|
|
|
280
|
-
return {
|
|
320
|
+
return {
|
|
321
|
+
title,
|
|
322
|
+
body,
|
|
323
|
+
labels: labelsForGroup(group),
|
|
324
|
+
groupKey: group.groupKey,
|
|
325
|
+
dependsOn,
|
|
326
|
+
};
|
|
281
327
|
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/audit-to-stories/wire-dependencies.js — turn a standalone audit cohort's
|
|
3
|
+
* detected group edges into declared ordering, once the issues exist.
|
|
4
|
+
*
|
|
5
|
+
* `groupFindings` detects `edges[]` between finding groups, but at emit time
|
|
6
|
+
* the groups have no issue numbers, so the ordering could only be rendered as
|
|
7
|
+
* prose. Every standalone audit Story therefore shipped with `depends_on: []`,
|
|
8
|
+
* and what actually kept a cohort from being co-dispatched onto colliding
|
|
9
|
+
* branches was an **accident**: siblings shared the sweep-wide audit provenance
|
|
10
|
+
* footers `plan-persist` stamps, and the delivery footprint guard scraped
|
|
11
|
+
* path-shaped tokens out of them. Story #5044 narrows that scrape, which is why
|
|
12
|
+
* this module lands with it — removing the accidental serializer without giving
|
|
13
|
+
* the cohort a real one would leave it less ordered than before.
|
|
14
|
+
*
|
|
15
|
+
* The shape is `plan-persist`'s two-pass crossing (`plan-persist/story-ops.js`),
|
|
16
|
+
* because it is the same problem: **create every issue first, then mirror the
|
|
17
|
+
* edges**. Both halves are written:
|
|
18
|
+
*
|
|
19
|
+
* 1. The **body footer** (`---` / `blocked by #N`) — canonical, parsed by
|
|
20
|
+
* `/deliver`'s resolver, and the fallback when the dependencies API is
|
|
21
|
+
* unavailable.
|
|
22
|
+
* 2. The **native `blocked_by` relation** — visible in the GitHub UI,
|
|
23
|
+
* readable without parsing markdown, and settable by an operator later.
|
|
24
|
+
*
|
|
25
|
+
* Native mirroring is **non-fatal by design**, matching `plan-persist`: the
|
|
26
|
+
* footer has already been written by the time it runs, so a dependencies API
|
|
27
|
+
* that says no costs visibility, not ordering.
|
|
28
|
+
*
|
|
29
|
+
* @module lib/audit-to-stories/wire-dependencies
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { applyBlockedByDependencies } from '../../providers/github/blocked-by-add.js';
|
|
33
|
+
import { Logger } from '../Logger.js';
|
|
34
|
+
import { buildStoryBody } from './build-story-body.js';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Re-render each created Story's body with its blockers resolved to `#N`, and
|
|
38
|
+
* mirror the same edges as native `blocked_by` relations.
|
|
39
|
+
*
|
|
40
|
+
* Groups whose issue was not created — deduped against an existing Issue,
|
|
41
|
+
* suppressed by the ledger, or simply not in `issueByGroupKey` — are skipped
|
|
42
|
+
* rather than guessed at, and an edge pointing at one drops with them
|
|
43
|
+
* (`dependencyRefs` filters it). A `blocked by #undefined` would gate a Story
|
|
44
|
+
* on nothing forever, which is strictly worse than the un-ordered cohort this
|
|
45
|
+
* replaces.
|
|
46
|
+
*
|
|
47
|
+
* @param {object} args
|
|
48
|
+
* @param {Array<object>} args.groups The `create`-eligible groups, in
|
|
49
|
+
* the order their issues were opened.
|
|
50
|
+
* @param {Array<{ fromGroupKey: string, toGroupKey: string }>} [args.edges]
|
|
51
|
+
* @param {Record<string, number>} args.issueByGroupKey Group key → issue number.
|
|
52
|
+
* @param {(issueNumber: number, body: string) => Promise<unknown>} args.updateBody
|
|
53
|
+
* Persist a re-rendered body. Injected so the caller owns the provider call.
|
|
54
|
+
* @param {object|null} [args.provider] Provider for native edge mirroring.
|
|
55
|
+
* Omit (or pass one without the dependency ports) to write footers only.
|
|
56
|
+
* @returns {Promise<{
|
|
57
|
+
* storiesWired: number,
|
|
58
|
+
* bodiesUpdated: number,
|
|
59
|
+
* edgesDeclared: number,
|
|
60
|
+
* native: { edgesAdded: number, edgesSkipped: number, edgesFailed: number }|null
|
|
61
|
+
* }>}
|
|
62
|
+
*/
|
|
63
|
+
export async function wireAuditStoryEdges({
|
|
64
|
+
groups,
|
|
65
|
+
edges = [],
|
|
66
|
+
issueByGroupKey,
|
|
67
|
+
updateBody,
|
|
68
|
+
provider = null,
|
|
69
|
+
}) {
|
|
70
|
+
const wired = collectWiredStories({ groups, edges, issueByGroupKey });
|
|
71
|
+
if (wired.length === 0) {
|
|
72
|
+
return {
|
|
73
|
+
storiesWired: 0,
|
|
74
|
+
bodiesUpdated: 0,
|
|
75
|
+
edgesDeclared: 0,
|
|
76
|
+
native: null,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let bodiesUpdated = 0;
|
|
81
|
+
for (const story of wired) {
|
|
82
|
+
await updateBody(story.issueNumber, story.body);
|
|
83
|
+
bodiesUpdated++;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
storiesWired: wired.length,
|
|
88
|
+
bodiesUpdated,
|
|
89
|
+
edgesDeclared: wired.reduce((n, s) => n + s.blockerKeys.length, 0),
|
|
90
|
+
native: await mirrorNativeEdges({ provider, wired, issueByGroupKey }),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Re-render every group that both has an issue **and** has at least one blocker
|
|
96
|
+
* whose issue also exists.
|
|
97
|
+
*
|
|
98
|
+
* A group with no resolvable blocker is deliberately left alone rather than
|
|
99
|
+
* rewritten to an identical body: an issue-body update is a mutation and a
|
|
100
|
+
* notification, and doing it for a no-op edit is noise on every Story of every
|
|
101
|
+
* sweep.
|
|
102
|
+
*
|
|
103
|
+
* @param {object} args
|
|
104
|
+
* @returns {Array<{ groupKey: string, issueNumber: number, body: string, blockerKeys: string[] }>}
|
|
105
|
+
*/
|
|
106
|
+
function collectWiredStories({ groups, edges, issueByGroupKey }) {
|
|
107
|
+
const wired = [];
|
|
108
|
+
for (const group of groups ?? []) {
|
|
109
|
+
const issueNumber = issueByGroupKey?.[group?.groupKey];
|
|
110
|
+
if (!Number.isInteger(issueNumber)) continue;
|
|
111
|
+
const rendered = buildStoryBody({ group, edges, issueByGroupKey });
|
|
112
|
+
const blockerKeys = rendered.dependsOn.filter((key) =>
|
|
113
|
+
Number.isInteger(issueByGroupKey[key]),
|
|
114
|
+
);
|
|
115
|
+
if (blockerKeys.length === 0) continue;
|
|
116
|
+
wired.push({
|
|
117
|
+
groupKey: rendered.groupKey,
|
|
118
|
+
issueNumber,
|
|
119
|
+
body: rendered.body,
|
|
120
|
+
blockerKeys,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return wired;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Mirror the declared edges as native GitHub `blocked_by` relations.
|
|
128
|
+
*
|
|
129
|
+
* Two shape hazards this crossing inherits from `plan-persist`, both silent if
|
|
130
|
+
* missed: `applyBlockedByDependencies` indexes `slugToIssueNumber` with plain
|
|
131
|
+
* property access (so it must be a plain object, never a `Map` — a `Map` yields
|
|
132
|
+
* `undefined` for every lookup, skips every edge, and reports success having
|
|
133
|
+
* written nothing), and it reads `dependsOn`, not `depends_on`.
|
|
134
|
+
*
|
|
135
|
+
* @param {object} args
|
|
136
|
+
* @returns {Promise<{ edgesAdded: number, edgesSkipped: number, edgesFailed: number }|null>}
|
|
137
|
+
* `null` when there is no interface to mirror through.
|
|
138
|
+
*/
|
|
139
|
+
async function mirrorNativeEdges({ provider, wired, issueByGroupKey }) {
|
|
140
|
+
if (
|
|
141
|
+
typeof provider?.getDependencyWriteContext !== 'function' ||
|
|
142
|
+
typeof provider?.getTicket !== 'function'
|
|
143
|
+
) {
|
|
144
|
+
Logger.warn(
|
|
145
|
+
'[audit-to-stories] provider exposes no getDependencyWriteContext/getTicket — ' +
|
|
146
|
+
'skipping native blocked_by edges. Ordering survives in the ' +
|
|
147
|
+
'`blocked by #N` body footers just written.',
|
|
148
|
+
);
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const { gh, owner, repo } = provider.getDependencyWriteContext();
|
|
153
|
+
const summary = await applyBlockedByDependencies({
|
|
154
|
+
// The group key IS the slug here — it is the stable identifier both
|
|
155
|
+
// sides of an edge are keyed by — so `issueByGroupKey` is already the
|
|
156
|
+
// slug→number map the helper wants.
|
|
157
|
+
stories: wired.map((s) => ({
|
|
158
|
+
slug: s.groupKey,
|
|
159
|
+
dependsOn: s.blockerKeys,
|
|
160
|
+
})),
|
|
161
|
+
slugToIssueNumber: issueByGroupKey,
|
|
162
|
+
getTicket: (issueNumber) => provider.getTicket(issueNumber),
|
|
163
|
+
owner,
|
|
164
|
+
repo,
|
|
165
|
+
gh,
|
|
166
|
+
});
|
|
167
|
+
if (summary.edgesFailed > 0) {
|
|
168
|
+
Logger.warn(
|
|
169
|
+
`[audit-to-stories] ${summary.edgesFailed} native blocked_by edge(s) could ` +
|
|
170
|
+
'not be written. Ordering survives in the `blocked by #N` body footers.',
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
edgesAdded: summary.edgesAdded,
|
|
175
|
+
edgesSkipped: summary.edgesSkipped,
|
|
176
|
+
edgesFailed: summary.edgesFailed,
|
|
177
|
+
};
|
|
178
|
+
} catch (err) {
|
|
179
|
+
Logger.warn(
|
|
180
|
+
`[audit-to-stories] native blocked_by mirroring failed (${err.message}) — ` +
|
|
181
|
+
'ordering survives in the `blocked by #N` body footers.',
|
|
182
|
+
);
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|