mandrel 2.56.0 → 2.58.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/agents/plan-critic.md +13 -18
- package/.agents/agents/story-worker.md +25 -33
- package/.agents/docs/agentrc-reference.json +0 -30
- package/.agents/docs/configuration.md +8 -28
- package/.agents/docs/execution-reference.md +5 -5
- package/.agents/docs/quality-gates.md +8 -7
- package/.agents/instructions.md +9 -10
- package/.agents/schemas/agentrc.schema.json +9 -185
- package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +107 -17
- package/.agents/scripts/ceremony-derive.js +191 -0
- package/.agents/scripts/check-context-budget.js +28 -33
- package/.agents/scripts/check-cyclomatic.js +4 -3
- package/.agents/scripts/deliver-light.js +31 -94
- package/.agents/scripts/evidence-gate.js +17 -1
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
- package/.agents/scripts/lib/baselines/coverage-updater-cli.js +110 -0
- package/.agents/scripts/lib/baselines/crap-preview-scan.js +25 -0
- package/.agents/scripts/lib/baselines/crap-updater-cli.js +223 -0
- package/.agents/scripts/lib/bdd-scenario-budget.js +21 -3
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -1
- package/.agents/scripts/lib/close-validation/gates.js +52 -1
- package/.agents/scripts/lib/config/acceptance-eval.js +25 -57
- package/.agents/scripts/lib/config/delivery-routing.js +7 -33
- package/.agents/scripts/lib/config/explain.js +0 -19
- package/.agents/scripts/lib/config/limits.js +18 -78
- package/.agents/scripts/lib/config/quality.js +6 -3
- package/.agents/scripts/lib/config/runners.js +3 -2
- package/.agents/scripts/lib/config-settings-schema-delivery.js +15 -68
- package/.agents/scripts/lib/config-settings-schema-quality.js +0 -14
- package/.agents/scripts/lib/config-settings-schema.js +16 -143
- package/.agents/scripts/lib/crap-engine.js +35 -4
- package/.agents/scripts/lib/crap-utils.js +17 -1
- package/.agents/scripts/lib/cyclomatic-ceiling.js +19 -7
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +5 -4
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +19 -73
- package/.agents/scripts/lib/orchestration/code-review.js +7 -3
- package/.agents/scripts/lib/orchestration/complexity-gate.js +46 -212
- package/.agents/scripts/lib/orchestration/file-assumptions.js +32 -17
- package/.agents/scripts/lib/orchestration/light-escalation.js +3 -3
- package/.agents/scripts/lib/orchestration/light-suitability.js +66 -233
- package/.agents/scripts/lib/orchestration/pinned-identifier-lint.js +137 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +189 -387
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +42 -153
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +14 -70
- package/.agents/scripts/lib/orchestration/plan-persist/acceptance-handle-repair.js +107 -0
- package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +305 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +138 -170
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +128 -297
- package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +55 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -65
- package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +22 -35
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +36 -135
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
- package/.agents/scripts/lib/orchestration/review-base-ref.js +138 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +37 -5
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +6 -1
- package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
- package/.agents/scripts/lib/orchestration/task-body-validator.js +10 -63
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +33 -539
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +21 -414
- package/.agents/scripts/lib/orchestration/ticket-validator.js +54 -118
- package/.agents/scripts/lib/orchestration/verify-credit.js +69 -24
- package/.agents/scripts/lib/story-body/body-format-lints.js +15 -85
- package/.agents/scripts/lib/story-body/story-body.js +54 -240
- package/.agents/scripts/lib/templates/decomposer-prompts.js +133 -121
- package/.agents/scripts/lib/test-isolate/cli-options.js +93 -0
- package/.agents/scripts/lib/test-isolate/progress-log.js +45 -0
- package/.agents/scripts/lib/test-isolate/render-report.js +97 -0
- package/.agents/scripts/lib/test-isolate/run-isolate.js +87 -0
- package/.agents/scripts/lib/test-run-credit.js +277 -0
- package/.agents/scripts/lib/wave-runner/footprint.js +48 -358
- package/.agents/scripts/lib/wave-runner/ready-set.js +6 -5
- package/.agents/scripts/lib/workers/crap-worker.js +32 -41
- package/.agents/scripts/plan-context.js +7 -9
- package/.agents/scripts/plan-critics.js +28 -54
- package/.agents/scripts/plan-persist.js +25 -68
- package/.agents/scripts/quality-preview.js +51 -0
- package/.agents/scripts/run-tests.js +12 -0
- package/.agents/scripts/stories-wave-tick.js +23 -45
- package/.agents/scripts/test-isolate.js +13 -180
- package/.agents/scripts/update-coverage-baseline.js +25 -70
- package/.agents/scripts/update-crap-baseline.js +19 -123
- package/.agents/skills/core/scope-triage/SKILL.md +3 -3
- package/.agents/workflows/audit-clean-code.md +4 -3
- package/.agents/workflows/helpers/acceptance-self-eval.md +41 -41
- package/.agents/workflows/helpers/code-quality-guardrails.md +4 -4
- package/.agents/workflows/helpers/code-review.md +2 -3
- package/.agents/workflows/helpers/deliver-digest.md +46 -55
- package/.agents/workflows/helpers/deliver-light.md +40 -105
- package/.agents/workflows/helpers/deliver-reference.md +1 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +54 -55
- package/.agents/workflows/helpers/deliver-story.md +10 -13
- package/.agents/workflows/helpers/plan-reference.md +163 -221
- package/.agents/workflows/mandrel-plan.md +31 -40
- package/.agents/workflows/memory-consolidate.md +9 -13
- package/docs/CHANGELOG.md +36 -0
- package/lib/cli/registry.js +98 -2
- package/lib/migrations/index.js +4 -0
- package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +45 -0
- package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +59 -0
- package/package.json +1 -1
- package/.agents/scripts/lib/framework-version.js +0 -39
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +0 -223
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +0 -97
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +0 -26
- package/.agents/scripts/lib/orchestration/spec-budget.js +0 -89
- package/.agents/scripts/lib/orchestration/spec-spill.js +0 -74
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +0 -107
|
@@ -1,124 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* plan-critic-conditions.js —
|
|
3
|
-
* the
|
|
2
|
+
* plan-critic-conditions.js — the pre-mortem critic's dispatch decision for
|
|
3
|
+
* the `/mandrel-plan` critic CLI (Epic #4474 PR6, design §4; narrowed to one
|
|
4
|
+
* arm by Story #5312).
|
|
4
5
|
*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* computes those decisions deterministically so the workflow never judges
|
|
11
|
-
* its own dispatch conditions:
|
|
6
|
+
* The critic is a fresh-context sub-agent dispatch, and the dispatch is
|
|
7
|
+
* **conditional**: the dominant plan cost is turns × standing context, and an
|
|
8
|
+
* unconditional critic pays a full sub-agent spawn even when it provably has
|
|
9
|
+
* nothing to find. This module computes the decision deterministically so
|
|
10
|
+
* the workflow never judges its own dispatch condition.
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* table). A fail-open precondition (missing/unparseable table) on a
|
|
18
|
-
* small draft is NOT a confirmed divergence — it skips, because a
|
|
19
|
-
* ≤-threshold draft is small enough for gate #2's single-view review
|
|
20
|
-
* to catch a distorted shape without a dedicated sub-agent.
|
|
21
|
-
* - **Pre-mortem**: dispatch when the ticket count is at least half
|
|
22
|
-
* of `maxTickets`, OR any configured `planning.riskHeuristics` phrase
|
|
23
|
-
* matches the plan text (case-insensitive substring), OR the
|
|
24
|
-
* **external-dependency probe** (Story #4700) finds an out-of-repo marker
|
|
25
|
-
* in the plan text. Story #4542 removed the authored-risk-verdict condition
|
|
26
|
-
* along with the verdict itself; every surviving condition reads the plan's
|
|
27
|
-
* own observable text and shape rather than a self-assessment.
|
|
28
|
-
*
|
|
29
|
-
* The external-dependency probe (Story #4700) is what gives the default N=1
|
|
30
|
-
* path a cheap viability check: on that path the size condition is unreachable
|
|
31
|
-
* (`count*2 >= maxTickets` never holds at one ticket) and a repo whose resolved
|
|
32
|
-
* `planning.riskHeuristics` is empty has no phrase to match, so a plan-time
|
|
33
|
-
* discoverable blocker — a scoped package the plan names that no manifest
|
|
34
|
-
* declares, a cross-repo reference, an external service prerequisite — reached
|
|
12
|
+
* One trigger survives: the **external-dependency probe** (Story #4700),
|
|
13
|
+
* which finds an out-of-repo marker in the plan text — a scoped package the
|
|
14
|
+
* plan names that no manifest declares, a cross-repo reference, an external
|
|
15
|
+
* service prerequisite — so a plan-time discoverable blocker does not reach
|
|
35
16
|
* delivery unquestioned (the swarm-os #757 shape). The probe is deliberately
|
|
36
|
-
* **conservative**: it matches only explicit markers (npm scoped-package
|
|
37
|
-
* `github.com/<owner>/<repo>` URLs, prerequisite-keyword-anchored
|
|
38
|
-
* never NLP guesswork
|
|
39
|
-
*
|
|
17
|
+
* **conservative**: it matches only explicit markers (npm scoped-package
|
|
18
|
+
* specs, `github.com/<owner>/<repo>` URLs, prerequisite-keyword-anchored
|
|
19
|
+
* endpoints), never NLP guesswork.
|
|
20
|
+
*
|
|
21
|
+
* Story #5312 deleted the two triggers that sat beside it — the ticket count
|
|
22
|
+
* reaching half a `maxTickets` budget, and a `planning.riskHeuristics` phrase
|
|
23
|
+
* matching the plan text — with the constants they read. The count trigger
|
|
24
|
+
* was unreachable at the default N=1; the phrase list was empty in every
|
|
25
|
+
* consumer that resolved it. The consolidation critic went with them: its
|
|
26
|
+
* one deterministic input was a `## Delivery Slicing` table no Story carries.
|
|
40
27
|
*
|
|
41
|
-
* Under-firing risk (design PR6 note): the persist validators are
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
28
|
+
* Under-firing risk (design PR6 note): the persist validators are unchanged
|
|
29
|
+
* hard gates; every skip decision this module produces is logged to the
|
|
30
|
+
* plan-metrics ledger (`appendCriticSkip`) by the caller so under-firing is
|
|
31
|
+
* auditable.
|
|
45
32
|
*
|
|
46
33
|
* Pure, synchronous, no I/O. The single caller is `plan-critics-evaluate.js`,
|
|
47
|
-
* driven by the `plan-critics.js` CLI
|
|
48
|
-
* Persist
|
|
49
|
-
* resolved config.
|
|
50
|
-
*/
|
|
51
|
-
|
|
52
|
-
import { evaluateConsolidationPrecondition } from './consolidation-precondition.js';
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Draft-story count above which the consolidation critic fires even
|
|
56
|
-
* without a confirmed slicing divergence (#4474 PR6: "> 5 stories").
|
|
34
|
+
* driven by the `plan-critics.js` CLI the operator runs between Author and
|
|
35
|
+
* Persist when they want the critic; the CLI owns reading the authored
|
|
36
|
+
* artifacts and the resolved config.
|
|
57
37
|
*/
|
|
58
|
-
export const CONSOLIDATION_STORY_THRESHOLD = 5;
|
|
59
38
|
|
|
60
39
|
/**
|
|
61
40
|
* @typedef {Object} CriticDispatchDecision
|
|
62
|
-
* @property {'
|
|
41
|
+
* @property {'pre-mortem'} critic
|
|
63
42
|
* @property {boolean} dispatch
|
|
64
43
|
* @property {string[]} reasons Why the critic fires — or why it is safe to
|
|
65
44
|
* skip. Never empty: a skip's reasons are the audit trail the
|
|
66
45
|
* plan-metrics ledger records.
|
|
67
46
|
*/
|
|
68
47
|
|
|
69
|
-
/**
|
|
70
|
-
* Decide the consolidation dispatch: precondition AND size/divergence.
|
|
71
|
-
*
|
|
72
|
-
* @param {object} input
|
|
73
|
-
* @param {object[]} input.draftStories - The draft `tickets.json` array
|
|
74
|
-
* (raw Story objects with top-level `slug` / `depends_on` / `body`).
|
|
75
|
-
* @param {string} input.specText - The text carrying the `## Delivery
|
|
76
|
-
* Slicing` table. At author time this is the authored `techspec.md`
|
|
77
|
-
* content (the Epic body carries the same folded section post-persist).
|
|
78
|
-
* @returns {CriticDispatchDecision}
|
|
79
|
-
*/
|
|
80
|
-
export function evaluateConsolidationDispatch({ draftStories, specText }) {
|
|
81
|
-
const precondition = evaluateConsolidationPrecondition({
|
|
82
|
-
draftStories,
|
|
83
|
-
epicBody: specText,
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
if (!precondition.dispatch) {
|
|
87
|
-
return {
|
|
88
|
-
critic: 'consolidation',
|
|
89
|
-
dispatch: false,
|
|
90
|
-
reasons: precondition.reasons,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const storyCount = draftStories.length;
|
|
95
|
-
const oversized = storyCount > CONSOLIDATION_STORY_THRESHOLD;
|
|
96
|
-
const diverges = precondition.cause === 'divergence';
|
|
97
|
-
|
|
98
|
-
if (!oversized && !diverges) {
|
|
99
|
-
return {
|
|
100
|
-
critic: 'consolidation',
|
|
101
|
-
dispatch: false,
|
|
102
|
-
reasons: [
|
|
103
|
-
`Draft has ${storyCount} story(ies) (≤ ${CONSOLIDATION_STORY_THRESHOLD}) and no confirmed Delivery Slicing divergence — gate #2's single-view review covers a draft this small.`,
|
|
104
|
-
...precondition.reasons,
|
|
105
|
-
],
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const reasons = [];
|
|
110
|
-
if (diverges) reasons.push(...precondition.reasons);
|
|
111
|
-
if (oversized) {
|
|
112
|
-
reasons.push(
|
|
113
|
-
`Draft has ${storyCount} stories (> ${CONSOLIDATION_STORY_THRESHOLD}) — large enough that a distorted shape can hide from the gate #2 single view.`,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
if (!diverges && precondition.cause === 'fail-open') {
|
|
117
|
-
reasons.push(...precondition.reasons);
|
|
118
|
-
}
|
|
119
|
-
return { critic: 'consolidation', dispatch: true, reasons };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
48
|
/**
|
|
123
49
|
* Explicit npm scoped-package marker: `@scope/name`. Requires the leading `@`
|
|
124
50
|
* and an interior `/`, so bare GitHub handles (`@dsj1984`) and the
|
|
@@ -215,8 +141,8 @@ function matchExternalServicePrereqs(planText) {
|
|
|
215
141
|
/**
|
|
216
142
|
* The external-dependency probe (Story #4700): a conservative, marker-only
|
|
217
143
|
* scan of the draft plan text for artifacts outside the current repo that the
|
|
218
|
-
* plan depends on. A match is the pre-mortem's
|
|
219
|
-
*
|
|
144
|
+
* plan depends on. A match is the pre-mortem's dispatch condition; a no-match
|
|
145
|
+
* plan skips the critic.
|
|
220
146
|
*
|
|
221
147
|
* @param {object} input
|
|
222
148
|
* @param {string} [input.planText] - Concatenated plan text (tech spec +
|
|
@@ -262,19 +188,12 @@ export function evaluateExternalDependencyProbe({
|
|
|
262
188
|
}
|
|
263
189
|
|
|
264
190
|
/**
|
|
265
|
-
* Decide the pre-mortem dispatch:
|
|
266
|
-
*
|
|
191
|
+
* Decide the pre-mortem dispatch: an external-dependency probe match
|
|
192
|
+
* (Story #4700) — the one deterministic trigger left after Story #5312.
|
|
267
193
|
*
|
|
268
194
|
* @param {object} input
|
|
269
|
-
* @param {
|
|
270
|
-
*
|
|
271
|
-
* @param {number} input.maxTickets - The reviewability budget
|
|
272
|
-
* (`getLimits(config).maxTickets`).
|
|
273
|
-
* @param {string[]} [input.riskHeuristics] - `planning.riskHeuristics`
|
|
274
|
-
* phrases from the resolved config.
|
|
275
|
-
* @param {string} [input.planText] - Concatenated plan text the heuristics and
|
|
276
|
-
* the external-dependency probe match against (tech spec + serialized
|
|
277
|
-
* tickets).
|
|
195
|
+
* @param {string} [input.planText] - Concatenated plan text the probe
|
|
196
|
+
* matches against (tech spec + serialized tickets).
|
|
278
197
|
* @param {string[]} [input.knownPackages] - Package specifiers the repo's own
|
|
279
198
|
* manifests declare (own name + dependency maps + workspace package names),
|
|
280
199
|
* passed to the external-dependency probe.
|
|
@@ -284,58 +203,28 @@ export function evaluateExternalDependencyProbe({
|
|
|
284
203
|
* @returns {CriticDispatchDecision}
|
|
285
204
|
*/
|
|
286
205
|
export function evaluatePremortemDispatch({
|
|
287
|
-
ticketCount,
|
|
288
|
-
maxTickets,
|
|
289
|
-
riskHeuristics = [],
|
|
290
206
|
planText = '',
|
|
291
207
|
knownPackages = [],
|
|
292
208
|
ownerRepo = null,
|
|
293
209
|
}) {
|
|
294
|
-
if (!Number.isInteger(maxTickets) || maxTickets <= 0) {
|
|
295
|
-
throw new TypeError(
|
|
296
|
-
'evaluatePremortemDispatch: maxTickets must be a positive integer',
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
const reasons = [];
|
|
300
|
-
|
|
301
|
-
const count = Number.isInteger(ticketCount) ? ticketCount : 0;
|
|
302
|
-
if (count * 2 >= maxTickets) {
|
|
303
|
-
reasons.push(
|
|
304
|
-
`Ticket count ${count} is at least half the reviewability budget (maxTickets ${maxTickets}).`,
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const haystack = String(planText).toLowerCase();
|
|
309
|
-
const matched = riskHeuristics.filter(
|
|
310
|
-
(phrase) =>
|
|
311
|
-
typeof phrase === 'string' &&
|
|
312
|
-
phrase.trim().length > 0 &&
|
|
313
|
-
haystack.includes(phrase.trim().toLowerCase()),
|
|
314
|
-
);
|
|
315
|
-
if (matched.length > 0) {
|
|
316
|
-
reasons.push(
|
|
317
|
-
`planning.riskHeuristics match(es) in the plan text: ${matched.map((p) => `"${p.trim()}"`).join(', ')}.`,
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
210
|
const externalDeps = evaluateExternalDependencyProbe({
|
|
322
211
|
planText,
|
|
323
212
|
knownPackages,
|
|
324
213
|
ownerRepo,
|
|
325
214
|
});
|
|
326
215
|
if (externalDeps.matched) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
216
|
+
return {
|
|
217
|
+
critic: 'pre-mortem',
|
|
218
|
+
dispatch: true,
|
|
219
|
+
reasons: externalDeps.reasons,
|
|
220
|
+
};
|
|
332
221
|
}
|
|
333
222
|
|
|
334
223
|
return {
|
|
335
224
|
critic: 'pre-mortem',
|
|
336
225
|
dispatch: false,
|
|
337
226
|
reasons: [
|
|
338
|
-
|
|
227
|
+
'The external-dependency probe found no out-of-repo markers in the plan text.',
|
|
339
228
|
],
|
|
340
229
|
};
|
|
341
230
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* plan-critics-evaluate.js — shared critic-dispatch evaluation for the
|
|
3
|
-
* collapsed /mandrel-plan flow (#4496 fix 6).
|
|
3
|
+
* collapsed /mandrel-plan flow (#4496 fix 6; pre-mortem only since Story #5312).
|
|
4
4
|
*
|
|
5
|
-
* One consumer: the `plan-critics.js` CLI, which
|
|
6
|
-
* Author and Persist
|
|
7
|
-
* module, prints the verdict as JSON, and records
|
|
8
|
-
* plan-metrics ledger; the workflow dispatches a fresh-context
|
|
9
|
-
* sub-agent on a `dispatch: true` verdict and folds the findings into
|
|
10
|
-
* re-author round before persist.
|
|
5
|
+
* One consumer: the `plan-critics.js` CLI, which the operator runs between
|
|
6
|
+
* Author and Persist when they want the pre-mortem. The CLI loads the draft
|
|
7
|
+
* artifacts, calls this module, prints the verdict as JSON, and records a
|
|
8
|
+
* skip on the plan-metrics ledger; the workflow dispatches a fresh-context
|
|
9
|
+
* critic sub-agent on a `dispatch: true` verdict and folds the findings into
|
|
10
|
+
* a re-author round before persist.
|
|
11
11
|
*
|
|
12
12
|
* Story #4592 moved that evaluation here from `run-plan-persist.js`, which
|
|
13
13
|
* ran it after authoring was finished and immediately before
|
|
@@ -21,26 +21,7 @@
|
|
|
21
21
|
* @module lib/orchestration/plan-critics-evaluate
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
evaluateConsolidationDispatch,
|
|
27
|
-
evaluatePremortemDispatch,
|
|
28
|
-
} from './plan-critic-conditions.js';
|
|
29
|
-
import { evaluateTextHygiene } from './plan-text-hygiene.js';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Resolve the planning risk heuristics list from the canonical config
|
|
33
|
-
* block (same resolution `plan-context.js` and the decompose context use).
|
|
34
|
-
*
|
|
35
|
-
* @param {object} config
|
|
36
|
-
* @returns {string[]}
|
|
37
|
-
*/
|
|
38
|
-
function resolveRiskHeuristics(config = {}) {
|
|
39
|
-
if (Array.isArray(config.planning?.riskHeuristics)) {
|
|
40
|
-
return config.planning.riskHeuristics;
|
|
41
|
-
}
|
|
42
|
-
return [];
|
|
43
|
-
}
|
|
24
|
+
import { evaluatePremortemDispatch } from './plan-critic-conditions.js';
|
|
44
25
|
|
|
45
26
|
/**
|
|
46
27
|
* Resolve the `{ owner, repo }` the external-dependency probe's cross-repo arm
|
|
@@ -57,24 +38,11 @@ function resolveOwnerRepo(config = {}) {
|
|
|
57
38
|
}
|
|
58
39
|
|
|
59
40
|
/**
|
|
60
|
-
* Evaluate the
|
|
61
|
-
* the
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* deterministic precondition + size/divergence conditions.
|
|
66
|
-
* - Pre-mortem: ticket count at least half `maxTickets`, OR any
|
|
67
|
-
* `planning.riskHeuristics` phrase matching the plan text, OR the
|
|
68
|
-
* external-dependency probe (Story #4700) matching an out-of-repo marker
|
|
69
|
-
* — a scoped package absent from `knownPackages`, a cross-repo
|
|
70
|
-
* `github.com/<owner>/<repo>` reference, or a named external service
|
|
71
|
-
* prerequisite. Story #4542 retired its authored-risk-level condition with
|
|
72
|
-
* the verdict itself.
|
|
73
|
-
* - Text hygiene (Story #4599, advisory-only): deterministic body lints
|
|
74
|
-
* (dangling-citation / open-question / slicing-mass) over the draft
|
|
75
|
-
* stories. It has no `dispatch` semantics and spawns nothing — its
|
|
76
|
-
* `findings[]` are re-author-round input, and the consolidation /
|
|
77
|
-
* premortem dispatch verdicts are untouched by it.
|
|
41
|
+
* Evaluate the pre-mortem critic's dispatch condition over the authored
|
|
42
|
+
* planning artifacts: the external-dependency probe (Story #4700) matching an
|
|
43
|
+
* out-of-repo marker — a scoped package absent from `knownPackages`, a
|
|
44
|
+
* cross-repo `github.com/<owner>/<repo>` reference, or a named external
|
|
45
|
+
* service prerequisite.
|
|
78
46
|
*
|
|
79
47
|
* @param {{
|
|
80
48
|
* techSpecContent: string,
|
|
@@ -88,9 +56,7 @@ function resolveOwnerRepo(config = {}) {
|
|
|
88
56
|
* that gathers them; this module stays pure. Empty when unresolved, which
|
|
89
57
|
* only widens what the probe treats as external.
|
|
90
58
|
* @returns {{
|
|
91
|
-
* consolidation: { critic: string, dispatch: boolean, reasons: string[] },
|
|
92
59
|
* premortem: { critic: string, dispatch: boolean, reasons: string[] },
|
|
93
|
-
* textHygiene: { critic: string, findings: Array<object> },
|
|
94
60
|
* }}
|
|
95
61
|
*/
|
|
96
62
|
export function evaluatePlanCritics({
|
|
@@ -100,24 +66,7 @@ export function evaluatePlanCritics({
|
|
|
100
66
|
knownPackages = [],
|
|
101
67
|
}) {
|
|
102
68
|
const ticketList = Array.isArray(tickets) ? tickets : null;
|
|
103
|
-
const consolidation =
|
|
104
|
-
ticketList === null
|
|
105
|
-
? {
|
|
106
|
-
critic: 'consolidation',
|
|
107
|
-
dispatch: false,
|
|
108
|
-
reasons: [
|
|
109
|
-
'single-delivery shape — no draft tickets exist to consolidate.',
|
|
110
|
-
],
|
|
111
|
-
}
|
|
112
|
-
: evaluateConsolidationDispatch({
|
|
113
|
-
draftStories: ticketList,
|
|
114
|
-
specText: techSpecContent,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
69
|
const premortem = evaluatePremortemDispatch({
|
|
118
|
-
ticketCount: ticketList?.length ?? 0,
|
|
119
|
-
maxTickets: getLimits(config).maxTickets,
|
|
120
|
-
riskHeuristics: resolveRiskHeuristics(config),
|
|
121
70
|
planText: [
|
|
122
71
|
techSpecContent ?? '',
|
|
123
72
|
ticketList ? JSON.stringify(ticketList) : '',
|
|
@@ -126,10 +75,5 @@ export function evaluatePlanCritics({
|
|
|
126
75
|
ownerRepo: resolveOwnerRepo(config),
|
|
127
76
|
});
|
|
128
77
|
|
|
129
|
-
|
|
130
|
-
critic: 'text-hygiene',
|
|
131
|
-
findings: evaluateTextHygiene({ draftStories: ticketList }).findings,
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
return { consolidation, premortem, textHygiene };
|
|
78
|
+
return { premortem };
|
|
135
79
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* acceptance-handle-repair.js — repair-before-judging for the `AC-<n>:`
|
|
3
|
+
* presentation handle on authored acceptance items (Story #5323).
|
|
4
|
+
*
|
|
5
|
+
* The handle belongs to the body renderer, which numbers each checkbox from
|
|
6
|
+
* its position in `acceptance[]` (`story-body.js`). An author planning from
|
|
7
|
+
* an existing ticket reads that rendered body as a template and carries the
|
|
8
|
+
* handle forward, so the persisted checkbox reads `- [ ] AC-1: AC-1: …`; a
|
|
9
|
+
* lettered handle copied out of a hand-edited source (`AC-14a:`) misnumbers
|
|
10
|
+
* the rest of the list against its own text.
|
|
11
|
+
*
|
|
12
|
+
* The correction is mechanical and total — strip the handle the renderer
|
|
13
|
+
* will re-apply — so this module applies it and **reports** it, exactly as
|
|
14
|
+
* `changes-repair.js` does for the `{ path, assumption }` formality. Charging
|
|
15
|
+
* the author a re-draft round to paste back text the strip already produced
|
|
16
|
+
* buys nothing.
|
|
17
|
+
*
|
|
18
|
+
* It lives beside the validator rather than inside it for the same reason
|
|
19
|
+
* `changes-repair.js` does: the validator's job is to judge, and mixing a
|
|
20
|
+
* mutating repair pass into a module of pure collectors muddies both.
|
|
21
|
+
* `persist-helpers.js#validateTickets` calls this first, then the validators.
|
|
22
|
+
*
|
|
23
|
+
* @module lib/orchestration/plan-persist/acceptance-handle-repair
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { stripAcceptanceHandle } from '../../story-body/story-body.js';
|
|
27
|
+
import { renderChangeRepair } from './changes-repair.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Strip the handle off one surface's `acceptance[]`, recording each distinct
|
|
31
|
+
* strip on `repairs`.
|
|
32
|
+
*
|
|
33
|
+
* @param {object} surface An object that may carry an `acceptance` array.
|
|
34
|
+
* @param {string} slug
|
|
35
|
+
* @param {Set<string>} reported Items already reported for this ticket.
|
|
36
|
+
* @param {object[]} repairs Accumulator, mutated.
|
|
37
|
+
* @returns {void}
|
|
38
|
+
*/
|
|
39
|
+
function repairSurface(surface, slug, reported, repairs) {
|
|
40
|
+
if (!Array.isArray(surface.acceptance)) return;
|
|
41
|
+
surface.acceptance = surface.acceptance.map((item) => {
|
|
42
|
+
const { text, stripped } = stripAcceptanceHandle(item);
|
|
43
|
+
if (!stripped) return item;
|
|
44
|
+
const from = String(item ?? '');
|
|
45
|
+
if (!reported.has(from)) {
|
|
46
|
+
reported.add(from);
|
|
47
|
+
repairs.push({ kind: 'acceptance-handle', slug, from, to: text });
|
|
48
|
+
}
|
|
49
|
+
return text;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Strip the presentation `AC-<n>:` handle off every authored `acceptance[]`
|
|
55
|
+
* item, on both surfaces that can carry one: the ticket's top-level array
|
|
56
|
+
* (the machine contract) and a structured body's.
|
|
57
|
+
*
|
|
58
|
+
* A serialized **string** body needs no pass of its own — `parse()` strips
|
|
59
|
+
* the handle with the same grammar this does, so the two surfaces converge
|
|
60
|
+
* on one list whichever carried the handle, and the contract sync (which
|
|
61
|
+
* fails closed on a disagreement) sees them agree.
|
|
62
|
+
*
|
|
63
|
+
* Mutates `tickets` in place — the persist pipeline threads this same array
|
|
64
|
+
* on to assembly. Total: a non-array argument and non-Story tickets are
|
|
65
|
+
* no-ops.
|
|
66
|
+
*
|
|
67
|
+
* @param {object[]} tickets
|
|
68
|
+
* @returns {Array<{ kind: 'acceptance-handle', slug: string, from: string, to: string }>}
|
|
69
|
+
*/
|
|
70
|
+
export function normalizeAcceptanceHandles(tickets) {
|
|
71
|
+
const repairs = [];
|
|
72
|
+
for (const ticket of Array.isArray(tickets) ? tickets : []) {
|
|
73
|
+
if (!ticket || typeof ticket !== 'object' || ticket.type !== 'story') {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const slug = ticket.slug ?? ticket.title ?? '<unknown>';
|
|
77
|
+
// A ticket normally carries the same list on both surfaces, so report
|
|
78
|
+
// each distinct item once — the operator reads one correction, not two.
|
|
79
|
+
const reported = new Set();
|
|
80
|
+
repairSurface(ticket, slug, reported, repairs);
|
|
81
|
+
const body = ticket.body;
|
|
82
|
+
if (body && typeof body === 'object') {
|
|
83
|
+
repairSurface(body, slug, reported, repairs);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return repairs;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Render one entry of the dry-run's mixed repair list.
|
|
91
|
+
*
|
|
92
|
+
* The list carries two kinds — a `changes[]` entry repaired by probing base,
|
|
93
|
+
* and an `acceptance[]` item whose handle was normalised off — and both are
|
|
94
|
+
* mechanical corrections the run applied on the author's behalf, so both
|
|
95
|
+
* belong on the one list the operator reads. This module owns the dispatch
|
|
96
|
+
* because it owns the newer kind: it renders its own line and delegates
|
|
97
|
+
* every other kind to `changes-repair.js`, so neither producer has to know
|
|
98
|
+
* the other's shape.
|
|
99
|
+
*
|
|
100
|
+
* @param {{ kind?: string, slug: string, from: string, to?: string }} repair
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
export function renderRepair(repair) {
|
|
104
|
+
if (repair.kind !== 'acceptance-handle') return renderChangeRepair(repair);
|
|
105
|
+
const { slug, from, to } = repair;
|
|
106
|
+
return `Story "${slug}": acceptance[] item "${from}" carried an AC-<n> handle — normalised to "${to}"; the body renderer numbers the checkboxes.`;
|
|
107
|
+
}
|