mandrel 2.31.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/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/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-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 +1 -11
- 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 +42 -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
|
@@ -393,12 +393,13 @@ async function emitBlockedFriction(ticketId, fromState, newState, opts) {
|
|
|
393
393
|
* `agent::done` at story-close (after the merge lands), not when the
|
|
394
394
|
* last Task commit landed on the still-unmerged Story branch. The
|
|
395
395
|
* parameter is preserved for callers that still suppress cascade
|
|
396
|
-
* explicitly (
|
|
396
|
+
* explicitly (`single-story-init.js`, and `bulk.js` where the upward
|
|
397
|
+
* recursion is driven by hand instead).
|
|
397
398
|
*
|
|
398
399
|
* `ticketSnapshot` (Story #1795 / Epic #1788) is an optional pre-fetched
|
|
399
|
-
* ticket object. When the caller already holds the ticket
|
|
400
|
-
*
|
|
401
|
-
*
|
|
400
|
+
* ticket object. When the caller already holds the ticket — e.g. it
|
|
401
|
+
* hydrated the ticket to inspect its labels first — passing the
|
|
402
|
+
* snapshot eliminates the two `getTicket` round-trips that
|
|
402
403
|
* `transitionTicketState` would otherwise issue — one for the notify
|
|
403
404
|
* `fromState` lookup and one inside `provider.updateTicket`'s label
|
|
404
405
|
* merge path. Backwards compatible: when omitted, behaviour is unchanged.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* verify-tier-repair.js — repair-before-judging for the `verify[]` tier suffix
|
|
3
|
+
* (Story #5005).
|
|
4
|
+
*
|
|
5
|
+
* The tier suffix (`… (unit)`) is a deterministic, mechanically-derivable
|
|
6
|
+
* formality. `task-body-validator.js` already computed the corrected entry in
|
|
7
|
+
* order to print it as a "Suggested fix" — then rejected the plan anyway and
|
|
8
|
+
* charged the author a full re-drafting round to paste that exact string back.
|
|
9
|
+
* This module applies the inference the validator already trusts, so the hard
|
|
10
|
+
* error is reserved for the entries only the author can resolve.
|
|
11
|
+
*
|
|
12
|
+
* It lives beside the validator rather than inside it because the validator's
|
|
13
|
+
* job is to *judge*: mixing a mutating repair pass into a module of pure
|
|
14
|
+
* collectors muddies both. `persist-helpers.js#validateTickets` calls this
|
|
15
|
+
* first, then `validateTaskBodies`.
|
|
16
|
+
*
|
|
17
|
+
* @module lib/orchestration/verify-tier-repair
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { suggestVerifyFix } from '../story-body/body-format-lints.js';
|
|
21
|
+
import {
|
|
22
|
+
parse as parseStoryBody,
|
|
23
|
+
serialize as serializeStoryBody,
|
|
24
|
+
} from '../story-body/story-body.js';
|
|
25
|
+
import { VERIFY_TIER_VALUES } from './task-body-validator.js';
|
|
26
|
+
|
|
27
|
+
/** A parenthesised tier drawn from the canonical vocabulary, at end of entry. */
|
|
28
|
+
const VERIFY_TIER_RE = new RegExp(
|
|
29
|
+
`\\((?:${VERIFY_TIER_VALUES.join('|')})\\)\\s*$`,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Rewrite one `verify[]` list, appending the inferable testing tier to any
|
|
34
|
+
* entry missing one. Returns `null` when nothing changed, so callers can keep
|
|
35
|
+
* an already-compliant list byte-identical (and skip re-serializing the body
|
|
36
|
+
* it came from).
|
|
37
|
+
*
|
|
38
|
+
* @param {unknown} rawVerify
|
|
39
|
+
* @returns {string[]|null} The corrected list, or `null` when no fix applied.
|
|
40
|
+
*/
|
|
41
|
+
function repairVerifyList(rawVerify) {
|
|
42
|
+
if (!Array.isArray(rawVerify)) return null;
|
|
43
|
+
let changed = false;
|
|
44
|
+
const next = rawVerify.map((entry) => {
|
|
45
|
+
if (typeof entry !== 'string') return entry;
|
|
46
|
+
if (entry.startsWith('manual:') || VERIFY_TIER_RE.test(entry)) return entry;
|
|
47
|
+
const fix = suggestVerifyFix(entry);
|
|
48
|
+
if (fix === null) return entry;
|
|
49
|
+
changed = true;
|
|
50
|
+
return fix;
|
|
51
|
+
});
|
|
52
|
+
return changed ? next : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Repair the `## Verify` section of a serialized (string) body, in place on
|
|
57
|
+
* the ticket. Only re-serializes when a fix actually applied, so a compliant
|
|
58
|
+
* body is never round-tripped through `parse → serialize`.
|
|
59
|
+
*
|
|
60
|
+
* Leaving the body untouched while the top level was repaired would be worse
|
|
61
|
+
* than not repairing at all: `syncContractFieldFromTopLevel` fails closed on a
|
|
62
|
+
* body section that disagrees with its top-level array, so both sides must be
|
|
63
|
+
* repaired by the same rule or neither.
|
|
64
|
+
*
|
|
65
|
+
* @param {object} ticket
|
|
66
|
+
*/
|
|
67
|
+
function repairStringBodyVerify(ticket) {
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = parseStoryBody(ticket.body).body;
|
|
71
|
+
} catch {
|
|
72
|
+
// An unparseable body is already a hard error in `validateTaskBodyShape`;
|
|
73
|
+
// there is nothing to repair and nothing to report twice.
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const repaired = repairVerifyList(parsed.verify);
|
|
77
|
+
if (repaired === null) return;
|
|
78
|
+
ticket.body = serializeStoryBody({ ...parsed, verify: repaired });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Auto-append the testing tier to `verify[]` entries that omit one, wherever
|
|
83
|
+
* `suggestVerifyFix` can infer it from the command. An entry whose tier cannot
|
|
84
|
+
* be inferred is left untouched and still hard-errors downstream.
|
|
85
|
+
*
|
|
86
|
+
* Mutates `tickets` in place (the persist pipeline threads this same array on
|
|
87
|
+
* to assembly) and returns it. Already-compliant tickets are untouched, so
|
|
88
|
+
* their persisted output stays byte-identical. Total — a non-array argument
|
|
89
|
+
* and non-Story tickets are no-ops.
|
|
90
|
+
*
|
|
91
|
+
* @param {object[]} tickets
|
|
92
|
+
* @returns {object[]} `tickets`
|
|
93
|
+
*/
|
|
94
|
+
export function normalizeVerifyTiers(tickets) {
|
|
95
|
+
for (const ticket of Array.isArray(tickets) ? tickets : []) {
|
|
96
|
+
if (!ticket || ticket.type !== 'story' || ticket.body == null) continue;
|
|
97
|
+
const topLevel = repairVerifyList(ticket.verify);
|
|
98
|
+
if (topLevel !== null) ticket.verify = topLevel;
|
|
99
|
+
if (typeof ticket.body === 'string') {
|
|
100
|
+
repairStringBodyVerify(ticket);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const bodyLevel = repairVerifyList(ticket.body.verify);
|
|
104
|
+
if (bodyLevel !== null) ticket.body.verify = bodyLevel;
|
|
105
|
+
}
|
|
106
|
+
return tickets;
|
|
107
|
+
}
|
|
@@ -34,17 +34,12 @@
|
|
|
34
34
|
// operator-facing string explaining the verdict (always populated,
|
|
35
35
|
// including for present tiers).
|
|
36
36
|
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// }
|
|
41
|
-
//
|
|
42
|
-
// Maps each acceptance criterion to its per-tier `coverageVerdict`, giving
|
|
43
|
-
// the AC × test-tier matrix the markdown report (lib/qa/coverage-report.js)
|
|
44
|
-
// renders.
|
|
37
|
+
// Story #5008 removed the `acceptanceMatrix` feeder and the markdown report it
|
|
38
|
+
// fed: the QA workflows read the per-tier verdict and author the missing-test
|
|
39
|
+
// prose directly, so the AC x tier matrix was a round-trip with no reader.
|
|
45
40
|
|
|
46
|
-
/** The three test tiers, in pyramid order (base
|
|
47
|
-
|
|
41
|
+
/** The three test tiers, in pyramid order (base -> top). */
|
|
42
|
+
const TIERS = Object.freeze(['unit', 'contract', 'acceptance']);
|
|
48
43
|
|
|
49
44
|
const PRESENT = 'present';
|
|
50
45
|
const ABSENT = 'absent';
|
|
@@ -217,80 +212,3 @@ export function coverageVerdict(surface = {}) {
|
|
|
217
212
|
|
|
218
213
|
return verdict;
|
|
219
214
|
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Normalize a single criterion descriptor into `{id, label, surface}`. A
|
|
223
|
-
* descriptor may carry `id`, `label` (falls back to id), and either a nested
|
|
224
|
-
* `surface` or a flat `{symbol, tests}` shape.
|
|
225
|
-
*/
|
|
226
|
-
function normalizeOne(entry, index) {
|
|
227
|
-
const e = entry && typeof entry === 'object' ? entry : {};
|
|
228
|
-
const id =
|
|
229
|
-
typeof e.id === 'string' && e.id.trim() !== ''
|
|
230
|
-
? e.id.trim()
|
|
231
|
-
: `AC-${index + 1}`;
|
|
232
|
-
const label =
|
|
233
|
-
typeof e.label === 'string' && e.label.trim() !== '' ? e.label.trim() : id;
|
|
234
|
-
|
|
235
|
-
let surface;
|
|
236
|
-
if (e.surface && typeof e.surface === 'object') {
|
|
237
|
-
surface = e.surface;
|
|
238
|
-
} else if (Array.isArray(e.tests) || typeof e.symbol === 'string') {
|
|
239
|
-
surface = { symbol: e.symbol, tests: e.tests };
|
|
240
|
-
} else {
|
|
241
|
-
surface = {};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return { id, label, surface };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Normalize the criteria input for {@link acceptanceMatrix}. Accepts either an
|
|
249
|
-
* array of criterion descriptors or a plain object keyed by criterion id whose
|
|
250
|
-
* values are surfaces (or `{surface}` wrappers). Returns a normalized array of
|
|
251
|
-
* `{id, label, surface}`.
|
|
252
|
-
*
|
|
253
|
-
* @param {Array<object>|object} criteria
|
|
254
|
-
* @returns {Array<{id:string,label:string,surface:object}>}
|
|
255
|
-
*/
|
|
256
|
-
function normalizeCriteria(criteria) {
|
|
257
|
-
if (Array.isArray(criteria)) {
|
|
258
|
-
return criteria.map((entry, index) => normalizeOne(entry, index));
|
|
259
|
-
}
|
|
260
|
-
if (criteria && typeof criteria === 'object') {
|
|
261
|
-
return Object.entries(criteria).map(([id, value], index) =>
|
|
262
|
-
normalizeOne(
|
|
263
|
-
value && typeof value === 'object' && !Array.isArray(value)
|
|
264
|
-
? { id, ...value }
|
|
265
|
-
: { id, surface: { tests: value } },
|
|
266
|
-
index,
|
|
267
|
-
),
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
throw new TypeError(
|
|
271
|
-
'acceptanceMatrix: criteria must be an array or an object',
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Build the AC × test-tier matrix: for each acceptance criterion, the per-tier
|
|
277
|
-
* {@link coverageVerdict}. This is the structured shape the markdown report
|
|
278
|
-
* (`lib/qa/coverage-report.js`) renders into a table.
|
|
279
|
-
*
|
|
280
|
-
* @param {Array<{id?:string,label?:string,surface?:object,symbol?:string,tests?:Array}>|Record<string,object>} criteria
|
|
281
|
-
* Acceptance criteria, either as an array of descriptors or an object keyed
|
|
282
|
-
* by criterion id. Each descriptor names a surface (nested `surface`, or a
|
|
283
|
-
* flat `{symbol, tests}`).
|
|
284
|
-
* @returns {{tiers: ReadonlyArray<string>,
|
|
285
|
-
* rows: Array<{id:string,label:string,
|
|
286
|
-
* verdict:ReturnType<typeof coverageVerdict>}>}}
|
|
287
|
-
*/
|
|
288
|
-
export function acceptanceMatrix(criteria) {
|
|
289
|
-
const normalized = normalizeCriteria(criteria);
|
|
290
|
-
const rows = normalized.map(({ id, label, surface }) => ({
|
|
291
|
-
id,
|
|
292
|
-
label,
|
|
293
|
-
verdict: coverageVerdict(surface),
|
|
294
|
-
}));
|
|
295
|
-
return { tiers: TIERS, rows };
|
|
296
|
-
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* detectors/common.js — shared helpers for the signals layer.
|
|
3
3
|
*
|
|
4
4
|
* Hoisted out of the detector modules (retry, rework) plus
|
|
5
|
-
* `signals/read.js
|
|
5
|
+
* the since-deleted `signals/read.js`, and `signals/schema.js`, all of which shipped
|
|
6
6
|
* byte-equivalent copies of these predicates. See Story #2464.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `lib/signals/` barrel.
|
|
3
3
|
*
|
|
4
|
-
* Consumers do `import {
|
|
4
|
+
* Consumers do `import { schema, appendSignal } from './lib/signals/index.js'`
|
|
5
5
|
* (or `import * as signals from './lib/signals/index.js'`) so a future
|
|
6
6
|
* shape migration only has to touch this file.
|
|
7
7
|
*
|
|
@@ -9,18 +9,20 @@
|
|
|
9
9
|
* - Epic #1181 / Story #1438 / Task #1459 — initial barrel with `read`
|
|
10
10
|
* + `schema`; `buildSpanTree` was a throwing placeholder.
|
|
11
11
|
* - Epic #1181 / Story #1440 / Task #1461 — placeholder replaced by
|
|
12
|
-
* the real
|
|
12
|
+
* the real span-tree builder.
|
|
13
13
|
* - Story #1476 — `appendSignal` / `appendTrace` re-exported here
|
|
14
14
|
* via `./write.js` so the gate scripts (and any
|
|
15
15
|
* new code) converge on `lib/signals/` instead of importing the
|
|
16
16
|
* writer directly from `lib/observability/`.
|
|
17
|
+
* - Story #5003 — the reader half (`read`, `buildSpanTree`) went with the
|
|
18
|
+
* debug viewer that consumed it: it walked a `run-<id>/` layout no v2
|
|
19
|
+
* writer populates. `appendTrace` went with the trace hook, its only
|
|
20
|
+
* producer. What remains is the live write surface.
|
|
17
21
|
*
|
|
18
22
|
* @module lib/signals
|
|
19
23
|
*/
|
|
20
24
|
|
|
21
|
-
import { read } from './read.js';
|
|
22
25
|
import * as schema from './schema.js';
|
|
23
|
-
import {
|
|
24
|
-
import { appendSignal, appendTrace, forEachLine } from './write.js';
|
|
26
|
+
import { appendSignal, forEachLine } from './write.js';
|
|
25
27
|
|
|
26
|
-
export { appendSignal,
|
|
28
|
+
export { appendSignal, forEachLine, schema };
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Signals schema — single source of truth for event-kind constants and
|
|
3
3
|
* shape guards (Epic #1181 / Story #1438 / Task #1458).
|
|
4
4
|
*
|
|
5
|
-
* Both writers (`lib/observability/signals-writer.js` callers) and
|
|
6
|
-
*
|
|
7
|
-
* import their field names from this module so the
|
|
8
|
-
* stays under one schema.
|
|
5
|
+
* Both writers (`lib/observability/signals-writer.js` callers) and readers
|
|
6
|
+
* (its `forEachLine` / `forEachSignalStreamLine` walkers, and the retro's
|
|
7
|
+
* gather-signals phase) import their field names from this module so the
|
|
8
|
+
* on-disk NDJSON shape stays under one schema.
|
|
9
9
|
*
|
|
10
10
|
* Pure module: importing has **no I/O side effects**. The exported
|
|
11
11
|
* `EVENT_KINDS` object is `Object.freeze`'d so consumers can use it as a
|
|
@@ -14,30 +14,25 @@
|
|
|
14
14
|
* ## Event-kind enumeration
|
|
15
15
|
*
|
|
16
16
|
* The set below is the audited union of `kind:` literals emitted via
|
|
17
|
-
* `signals-writer.appendSignal`
|
|
18
|
-
*
|
|
17
|
+
* `signals-writer.appendSignal` across the codebase as of Epic #1181
|
|
18
|
+
* (audit-snapshot 2026-05-11):
|
|
19
19
|
*
|
|
20
20
|
* Signals-writer `appendSignal` call sites:
|
|
21
21
|
* - `friction` — quality-gate / runtime friction (diagnose-friction.js,
|
|
22
22
|
* lib/gates/friction.js)
|
|
23
23
|
* - `acceptance-eval` — acceptance-eval.js per-criterion terminus signal.
|
|
24
|
-
* - `wave-start` / `wave-complete` — wave-window anchors
|
|
25
|
-
*
|
|
26
|
-
* - `wave-end` — span-tree pairing anchor (retained for span-tree)
|
|
24
|
+
* - `wave-start` / `wave-complete` — wave-window anchors.
|
|
25
|
+
* - `wave-end` — wave-window pairing anchor.
|
|
27
26
|
* - `state-transition` — notification-derived window anchor.
|
|
28
27
|
*
|
|
29
28
|
* The `dispatched` kind was deleted in the Epic #4406 signal-contract
|
|
30
29
|
* cutover — it had no live emitter and no consumer.
|
|
31
30
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* emitted by `lib/signals/detectors/*`; `hotspot`, `churn`, `idle` remain
|
|
38
|
-
* pinned in the enum for the aggregator's kind-count rollup even though no
|
|
39
|
-
* live detector emits them):
|
|
40
|
-
* - `hotspot`, `rework`, `churn`, `idle`, `retry`
|
|
31
|
+
* Producerless kinds, pinned in the enum so a re-introduction needs no schema
|
|
32
|
+
* bump. Story #5003 deleted the last emitters of `trace` (the tool-trace hook
|
|
33
|
+
* and its `appendTrace` writer limb) and of `rework` / `retry` (the detector
|
|
34
|
+
* pair the hook fed); `hotspot`, `churn`, `idle` never had one:
|
|
35
|
+
* - `trace`, `hotspot`, `rework`, `churn`, `idle`, `retry`
|
|
41
36
|
*
|
|
42
37
|
* ## Common envelope (canonical — Epic #4406 / Story #4413)
|
|
43
38
|
*
|
|
@@ -68,18 +63,18 @@ import { isPositiveInt } from './detectors/common.js';
|
|
|
68
63
|
|
|
69
64
|
/**
|
|
70
65
|
* Frozen enumeration of every event kind currently emitted by
|
|
71
|
-
* `signals-writer.appendSignal
|
|
72
|
-
*
|
|
66
|
+
* `signals-writer.appendSignal`, plus the producerless kinds a future Story
|
|
67
|
+
* may re-introduce an emitter for.
|
|
73
68
|
*
|
|
74
69
|
* Field names are kebab-cased to match the on-disk literals.
|
|
75
70
|
*/
|
|
76
71
|
export const EVENT_KINDS = Object.freeze({
|
|
77
72
|
FRICTION: 'friction',
|
|
78
73
|
TRACE: 'trace',
|
|
79
|
-
// Wave-window forensics signals
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
74
|
+
// Wave-window forensics signals. Story #4545 deleted the `waveParallelism`
|
|
75
|
+
// consumer (perf-aggregator) with the execution-analysis surface, and
|
|
76
|
+
// Story #5003 deleted the span-tree that paired them; the kinds stay
|
|
77
|
+
// pinned so a re-introduced window reader needs no schema bump.
|
|
83
78
|
WAVE_START: 'wave-start',
|
|
84
79
|
WAVE_END: 'wave-end',
|
|
85
80
|
WAVE_COMPLETE: 'wave-complete',
|
|
@@ -156,7 +151,7 @@ function isTimestamp(v) {
|
|
|
156
151
|
* recognised `kind`. Canonical keys only — the legacy `timestamp` / `epic`
|
|
157
152
|
* aliases were deleted in the Epic #4406 cutover.
|
|
158
153
|
*
|
|
159
|
-
* Returns true when the envelope is well-formed. Used by
|
|
154
|
+
* Returns true when the envelope is well-formed. Used by the stream readers
|
|
160
155
|
* to discard malformed lines before yielding them to the consumer.
|
|
161
156
|
*
|
|
162
157
|
* @param {unknown} evt
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `lib/signals/write.js` — thin re-export of the signals
|
|
2
|
+
* `lib/signals/write.js` — thin re-export of the signals writer.
|
|
3
3
|
*
|
|
4
4
|
* The real implementation lives at `../observability/signals-writer.js`
|
|
5
5
|
* (Epic #1030 / Story #1041). This module exists so callers can converge
|
|
6
|
-
* on `lib/signals/` for
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* from
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* on `lib/signals/` for the write surface. New code should
|
|
7
|
+
* `import { appendSignal } from './lib/signals/index.js'` (or
|
|
8
|
+
* `'./lib/signals/write.js'` for a narrower import); legacy direct imports
|
|
9
|
+
* from `lib/observability/` keep working unchanged.
|
|
10
|
+
*
|
|
11
|
+
* `appendTrace` was removed in Story #5003 together with the tool-trace
|
|
12
|
+
* hook — its only producer — and the viewer that was its only reader.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
export {
|
|
15
16
|
appendSignal,
|
|
16
|
-
appendTrace,
|
|
17
17
|
forEachLine,
|
|
18
18
|
} from '../observability/signals-writer.js';
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
* wide: { reason } | null,// declared-wide footprint (optional)
|
|
22
22
|
* reason_to_exist: string | null, // one-sentence cohesion reason (optional)
|
|
23
23
|
* depends_on: string[], // blocker story slugs or #ids
|
|
24
|
-
* estimated_test_files: number | null, // absent → null (informational)
|
|
25
24
|
* }
|
|
26
25
|
* ```
|
|
27
26
|
*
|
|
@@ -79,7 +78,6 @@ import { suggestPathEntryFix } from './body-format-lints.js';
|
|
|
79
78
|
* @property {{ reason: string }|null} wide - Declared-wide footprint (reason), or null.
|
|
80
79
|
* @property {string|null} reason_to_exist - One-sentence cohesion reason ("why this Story exists"), or null.
|
|
81
80
|
* @property {string[]} depends_on - Blocking story slugs / issue refs.
|
|
82
|
-
* @property {number|null} estimated_test_files - Test surface count or null.
|
|
83
81
|
* @property {string|null} mandrel_version - Framework version stamped at authoring, or null.
|
|
84
82
|
* @property {string|null} authored_at - Authoring date (YYYY-MM-DD) stamped at authoring, or null.
|
|
85
83
|
*/
|
|
@@ -334,7 +332,7 @@ const META_BLOCK_RE = /<!--\s*meta:\s*([\s\S]*?)\s*-->/;
|
|
|
334
332
|
const META_OBJECT_RE = /<!--\s*meta:\s*(\{[\s\S]*?\})\s*-->/;
|
|
335
333
|
|
|
336
334
|
/**
|
|
337
|
-
* Extract the `wide` / `
|
|
335
|
+
* Extract the `wide` / `reason_to_exist` fields from the trailing
|
|
338
336
|
* `<!-- meta: {...} -->` comment block written by {@link serialize}. Returns
|
|
339
337
|
* canonical-shaped values (null when absent or malformed) so the parser
|
|
340
338
|
* round-trips the meta block faithfully.
|
|
@@ -350,13 +348,12 @@ const META_OBJECT_RE = /<!--\s*meta:\s*(\{[\s\S]*?\})\s*-->/;
|
|
|
350
348
|
* rather than dropping or re-deriving it.
|
|
351
349
|
*
|
|
352
350
|
* @param {string} markdown
|
|
353
|
-
* @returns {{ wide: { reason: string }|null, reason_to_exist: string|null,
|
|
351
|
+
* @returns {{ wide: { reason: string }|null, reason_to_exist: string|null, mandrel_version: string|null, authored_at: string|null }}
|
|
354
352
|
*/
|
|
355
353
|
function extractMeta(markdown) {
|
|
356
354
|
const result = {
|
|
357
355
|
wide: null,
|
|
358
356
|
reason_to_exist: null,
|
|
359
|
-
estimated_test_files: null,
|
|
360
357
|
mandrel_version: null,
|
|
361
358
|
authored_at: null,
|
|
362
359
|
};
|
|
@@ -377,9 +374,6 @@ function extractMeta(markdown) {
|
|
|
377
374
|
|
|
378
375
|
result.wide = normalizeWide(parsed.wide);
|
|
379
376
|
result.reason_to_exist = normalizeReasonToExist(parsed.reason_to_exist);
|
|
380
|
-
if (typeof parsed.estimated_test_files === 'number') {
|
|
381
|
-
result.estimated_test_files = parsed.estimated_test_files;
|
|
382
|
-
}
|
|
383
377
|
if (
|
|
384
378
|
typeof parsed.mandrel_version === 'string' &&
|
|
385
379
|
parsed.mandrel_version.trim()
|
|
@@ -579,7 +573,6 @@ function isMachineMarkerLine(line) {
|
|
|
579
573
|
function parseUnstructuredBody(input, preamble, footer) {
|
|
580
574
|
const warnings = [
|
|
581
575
|
'unstructured-body: no structured sections found; returning minimal body from preamble text.',
|
|
582
|
-
'test-surface-unestimated: estimated_test_files not present.',
|
|
583
576
|
];
|
|
584
577
|
const body = {
|
|
585
578
|
goal: preamble || input.trim(),
|
|
@@ -593,7 +586,6 @@ function parseUnstructuredBody(input, preamble, footer) {
|
|
|
593
586
|
wide: null,
|
|
594
587
|
reason_to_exist: null,
|
|
595
588
|
depends_on: extractBlockedBy(footer),
|
|
596
|
-
estimated_test_files: null,
|
|
597
589
|
mandrel_version: null,
|
|
598
590
|
authored_at: null,
|
|
599
591
|
};
|
|
@@ -688,11 +680,6 @@ function parseTextListSection(lines) {
|
|
|
688
680
|
* `result.warnings` to detect legacy path entries that should be
|
|
689
681
|
* migrated.
|
|
690
682
|
*
|
|
691
|
-
* Informational finding emitted on `result.warnings`:
|
|
692
|
-
* - `test-surface-unestimated` — when `estimated_test_files` is absent
|
|
693
|
-
* from both a structured body and the markdown. Callers that care about
|
|
694
|
-
* test-surface coverage SHOULD surface this to the operator.
|
|
695
|
-
*
|
|
696
683
|
* @param {string|object} input - Markdown string or already-structured body object.
|
|
697
684
|
* @returns {ParseResult}
|
|
698
685
|
* @throws {StoryBodyParseError} When the body is structurally unrecoverable.
|
|
@@ -765,20 +752,16 @@ export function parse(input) {
|
|
|
765
752
|
const non_goals = parseTextListSection(sections.get('non_goals') ?? []);
|
|
766
753
|
const dependsOn = extractBlockedBy(footer);
|
|
767
754
|
|
|
768
|
-
// --- Recover wide /
|
|
755
|
+
// --- Recover wide / reason_to_exist / provenance from the meta block ---
|
|
769
756
|
// serialize() writes these into a trailing `<!-- meta: {...} -->` comment
|
|
770
757
|
// so round-trips preserve them. Absent meta block → canonical null defaults.
|
|
758
|
+
// Unknown keys are ignored, so a body carrying a retired meta field parses
|
|
759
|
+
// clean and simply drops it.
|
|
771
760
|
const meta = extractMeta(input);
|
|
772
|
-
const estimated_test_files = meta.estimated_test_files;
|
|
773
761
|
const wide = meta.wide;
|
|
774
762
|
const reason_to_exist = meta.reason_to_exist;
|
|
775
763
|
const mandrel_version = meta.mandrel_version;
|
|
776
764
|
const authored_at = meta.authored_at;
|
|
777
|
-
if (estimated_test_files === null) {
|
|
778
|
-
warnings.push(
|
|
779
|
-
'test-surface-unestimated: estimated_test_files not present.',
|
|
780
|
-
);
|
|
781
|
-
}
|
|
782
765
|
|
|
783
766
|
const body = {
|
|
784
767
|
goal,
|
|
@@ -792,7 +775,6 @@ export function parse(input) {
|
|
|
792
775
|
wide,
|
|
793
776
|
reason_to_exist,
|
|
794
777
|
depends_on: dependsOn,
|
|
795
|
-
estimated_test_files,
|
|
796
778
|
mandrel_version,
|
|
797
779
|
authored_at,
|
|
798
780
|
};
|
|
@@ -834,13 +816,6 @@ function parseStructuredObject(obj) {
|
|
|
834
816
|
body[name] = STRUCTURED_FIELD_NORMALIZERS[kind](obj[name], warnings);
|
|
835
817
|
}
|
|
836
818
|
|
|
837
|
-
// estimated_test_files is the one warning-coupled scalar: absent (== null)
|
|
838
|
-
// warns; a non-number, non-null value stays null silently.
|
|
839
|
-
body.estimated_test_files = normalizeEstimatedTestFiles(
|
|
840
|
-
obj.estimated_test_files,
|
|
841
|
-
warnings,
|
|
842
|
-
);
|
|
843
|
-
|
|
844
819
|
// Provenance stamp (preserved verbatim; never re-derived here).
|
|
845
820
|
body.mandrel_version = normalizeProvenanceString(obj.mandrel_version);
|
|
846
821
|
body.authored_at = normalizeProvenanceString(obj.authored_at);
|
|
@@ -917,25 +892,6 @@ const STRUCTURED_FIELD_NORMALIZERS = {
|
|
|
917
892
|
reasonToExist: (raw) => normalizeReasonToExist(raw),
|
|
918
893
|
};
|
|
919
894
|
|
|
920
|
-
/**
|
|
921
|
-
* Normalize `estimated_test_files`: a number passes through; an absent
|
|
922
|
-
* (`null`/`undefined`) value warns `test-surface-unestimated`; any other
|
|
923
|
-
* value stays `null` silently.
|
|
924
|
-
*
|
|
925
|
-
* @param {unknown} raw
|
|
926
|
-
* @param {string[]} warnings
|
|
927
|
-
* @returns {number|null}
|
|
928
|
-
*/
|
|
929
|
-
function normalizeEstimatedTestFiles(raw, warnings) {
|
|
930
|
-
if (typeof raw === 'number') return raw;
|
|
931
|
-
if (raw == null) {
|
|
932
|
-
warnings.push(
|
|
933
|
-
'test-surface-unestimated: estimated_test_files not present.',
|
|
934
|
-
);
|
|
935
|
-
}
|
|
936
|
-
return null;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
895
|
/**
|
|
940
896
|
* Normalize a provenance stamp field (`mandrel_version` / `authored_at`) to
|
|
941
897
|
* a non-empty trimmed string or `null`.
|
|
@@ -1066,11 +1022,11 @@ const SERIALIZE_SECTIONS = [
|
|
|
1066
1022
|
|
|
1067
1023
|
/**
|
|
1068
1024
|
* Build the trailing `<!-- meta: {...} -->` block carrying the fields that
|
|
1069
|
-
* have no human-readable section (`wide`, `reason_to_exist
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
1025
|
+
* have no human-readable section (`wide`, `reason_to_exist`). Returns the
|
|
1026
|
+
* empty string when no meta field is present so {@link serialize} appends
|
|
1027
|
+
* nothing.
|
|
1072
1028
|
*
|
|
1073
|
-
* Key insertion order (`wide` → `reason_to_exist` →
|
|
1029
|
+
* Key insertion order (`wide` → `reason_to_exist` →
|
|
1074
1030
|
* `mandrel_version` → `authored_at`) is load-bearing: it fixes the serialized
|
|
1075
1031
|
* JSON byte sequence the parser's meta round-trip and the unit suite assert
|
|
1076
1032
|
* against. The provenance stamp keys are appended **last** so every
|
|
@@ -1089,9 +1045,6 @@ function serializeMetaBlock(body) {
|
|
|
1089
1045
|
if (reasonToExist !== null) {
|
|
1090
1046
|
metaFields.reason_to_exist = reasonToExist;
|
|
1091
1047
|
}
|
|
1092
|
-
if (typeof body.estimated_test_files === 'number') {
|
|
1093
|
-
metaFields.estimated_test_files = body.estimated_test_files;
|
|
1094
|
-
}
|
|
1095
1048
|
if (typeof body.mandrel_version === 'string' && body.mandrel_version.trim()) {
|
|
1096
1049
|
metaFields.mandrel_version = body.mandrel_version.trim();
|
|
1097
1050
|
}
|
|
@@ -1153,9 +1106,9 @@ function serializeFooter(body, opts) {
|
|
|
1153
1106
|
* `## Goal`, `## Slicing`, `## Spec`, `## Changes`, `## Acceptance`,
|
|
1154
1107
|
* `## Verify`, `## References`, `## Non-Goals` (each omitted when empty).
|
|
1155
1108
|
*
|
|
1156
|
-
* `wide
|
|
1157
|
-
*
|
|
1158
|
-
*
|
|
1109
|
+
* `wide` and `reason_to_exist` are emitted as a fenced `<!-- meta -->`
|
|
1110
|
+
* comment block so round-trips preserve them without polluting the
|
|
1111
|
+
* human-readable body.
|
|
1159
1112
|
*
|
|
1160
1113
|
* @param {StoryBody} body
|
|
1161
1114
|
* @param {SerializeOptions} [opts]
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* ## Keep-class
|
|
32
32
|
*
|
|
33
33
|
* `signals.ndjson` is the artifact whose value *starts* when the run ends —
|
|
34
|
-
* `
|
|
34
|
+
* `acceptance-eval` and the loop-health check both read it
|
|
35
35
|
* long after the Story merged. It is excluded twice over: it is not in the
|
|
36
36
|
* evidence basename allowlist, and {@link KEEP_BASENAMES} is re-checked at
|
|
37
37
|
* the deletion site. Defence in depth is warranted for the one file whose
|