mandrel 2.55.0 → 2.57.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 -34
- package/.agents/docs/agentrc-reference.json +4 -30
- package/.agents/docs/configuration.md +11 -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/rules/ci-remediation.md +39 -21
- package/.agents/schemas/agentrc.schema.json +28 -185
- package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +107 -17
- package/.agents/scripts/audit-to-stories.js +222 -75
- 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/file-ci-gap.js +306 -0
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +25 -1
- package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +40 -52
- package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +5 -1
- package/.agents/scripts/lib/audit-to-stories/issue-corpus.js +162 -0
- package/.agents/scripts/lib/audit-to-stories/issues-file.js +121 -0
- package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/ledger-record.js +126 -0
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +11 -0
- 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 +49 -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/feedback-loop/graduator-core.js +53 -13
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +71 -25
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +18 -25
- package/.agents/scripts/lib/{audit-to-stories/ledger.js → findings/audit-ledger.js} +131 -24
- package/.agents/scripts/lib/findings/route-finding.js +38 -0
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/github/framework-repo.js +148 -2
- package/.agents/scripts/lib/label-constants.js +6 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
- package/.agents/scripts/lib/observability/source-classifier.js +2 -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/ci-gap-intake.js +605 -0
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +13 -8
- 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/plan-context.js +181 -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/audit-provenance.js +197 -0
- package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +300 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +131 -168
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +133 -299
- 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 +30 -139
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
- package/.agents/scripts/lib/orchestration/run-epilogue.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
- package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +32 -20
- 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 +17 -237
- package/.agents/scripts/lib/templates/decomposer-prompts.js +84 -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 +266 -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/pr-watch-with-update.js +3 -2
- 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/audit-to-stories.md +63 -27
- 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 +41 -57
- 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 +56 -62
- package/.agents/workflows/helpers/deliver-story.md +9 -13
- package/.agents/workflows/helpers/plan-reference.md +132 -196
- package/.agents/workflows/mandrel-plan.md +28 -41
- package/.agents/workflows/memory-consolidate.md +9 -13
- package/docs/CHANGELOG.md +33 -0
- 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
|
@@ -6,20 +6,11 @@ import { gitSpawn } from '../git-utils.js';
|
|
|
6
6
|
import { Logger } from '../Logger.js';
|
|
7
7
|
import { validateStoryFileAssumptions } from './file-assumptions.js';
|
|
8
8
|
import { isExternalDependencyRef } from './plan-persist/external-deps.js';
|
|
9
|
-
import { computeSpecBudgetFindings } from './spec-budget.js';
|
|
10
9
|
import {
|
|
11
10
|
assertStoryBodiesParse,
|
|
12
11
|
parseStoryBodyOrThrow,
|
|
13
12
|
} from './story-body-gate.js';
|
|
14
|
-
import {
|
|
15
|
-
CONFLICT_KINDS,
|
|
16
|
-
computeConflictFindings,
|
|
17
|
-
renderHardConflictError,
|
|
18
|
-
} from './ticket-validator-conflicts.js';
|
|
19
|
-
import {
|
|
20
|
-
computeSizingFindings,
|
|
21
|
-
renderHardFindingError,
|
|
22
|
-
} from './ticket-validator-sizing.js';
|
|
13
|
+
import { computeConflictFindings } from './ticket-validator-conflicts.js';
|
|
23
14
|
|
|
24
15
|
/**
|
|
25
16
|
* Regex matching code-asset paths the freshness gate cares about. The three
|
|
@@ -230,10 +221,13 @@ function makeMemoizedGitRunner(runner) {
|
|
|
230
221
|
}
|
|
231
222
|
|
|
232
223
|
/**
|
|
233
|
-
*
|
|
234
|
-
* `baseBranchRef
|
|
235
|
-
*
|
|
236
|
-
*
|
|
224
|
+
* Check that every code-asset path referenced by a Story body or AC exists at
|
|
225
|
+
* `baseBranchRef`, and report the ones that do not. A missing path usually
|
|
226
|
+
* means the planner named a file it is about to create without declaring it,
|
|
227
|
+
* or a stale reference — worth saying, not worth refusing on: Story #5312
|
|
228
|
+
* demoted this gate from a throw to the **warning list** the dry-run prints,
|
|
229
|
+
* because the paths a goal or acceptance line names are prose the deliverer
|
|
230
|
+
* reads against the real tree, never a contract the validator can hold it to.
|
|
237
231
|
*
|
|
238
232
|
* Only Stories are scanned — they are the implementation unit; the Epic
|
|
239
233
|
* carries narrative copy, not implementation paths.
|
|
@@ -243,7 +237,7 @@ function makeMemoizedGitRunner(runner) {
|
|
|
243
237
|
* @param {string} opts.baseBranchRef - Ref to probe (e.g. 'main' or 'origin/main').
|
|
244
238
|
* @param {Function} [opts.gitRunner] - Probe override (testing seam).
|
|
245
239
|
* @param {string} [opts.cwd] - Repo cwd (forwarded to default runner).
|
|
246
|
-
* @
|
|
240
|
+
* @returns {string[]} One warning line per missing reference, empty when clean.
|
|
247
241
|
*/
|
|
248
242
|
export function validateAcFreshness({
|
|
249
243
|
tickets,
|
|
@@ -286,12 +280,7 @@ export function validateAcFreshness({
|
|
|
286
280
|
}
|
|
287
281
|
}
|
|
288
282
|
}
|
|
289
|
-
|
|
290
|
-
const lines = misses.map((m) => renderMissLine(m)).join('\n');
|
|
291
|
-
throw new ValidationError(
|
|
292
|
-
`Cross-Validation Failed: ${misses.length} Story reference(s) name files that do not exist at ${baseBranchRef}:\n${lines}\n\nEither declare the path in body.changes (signals net-new) or correct the reference.`,
|
|
293
|
-
{ misses, baseBranchRef },
|
|
294
|
-
);
|
|
283
|
+
return misses.map((m) => renderMissLine(m, baseBranchRef));
|
|
295
284
|
}
|
|
296
285
|
|
|
297
286
|
/**
|
|
@@ -406,40 +395,35 @@ export function validateAcceptanceSubjectPrefix({ tickets }) {
|
|
|
406
395
|
}
|
|
407
396
|
|
|
408
397
|
/**
|
|
409
|
-
* Render one missing-path
|
|
410
|
-
*
|
|
398
|
+
* Render one missing-path warning with a remediation hint pointing at the
|
|
399
|
+
* Story's `changes[]`. For `tests/**` paths we suggest the explicit
|
|
411
400
|
* "add the test file" verb; for everything else we emit a generic hint
|
|
412
401
|
* since the planner knows whether the path is net-new or a typo.
|
|
413
402
|
*/
|
|
414
|
-
function renderMissLine({ slug, path }) {
|
|
403
|
+
function renderMissLine({ slug, path }, baseBranchRef) {
|
|
415
404
|
const verb = path.startsWith('tests/') ? 'add test file' : 'create';
|
|
416
|
-
return `
|
|
405
|
+
return `Story "${slug}" references ${path}, which does not exist at ${baseBranchRef} — if net-new, declare {"path":"${path}","assumption":"creates"} in changes[] (${verb}); otherwise fix the typo or stale reference.`;
|
|
417
406
|
}
|
|
418
407
|
|
|
419
408
|
/**
|
|
420
409
|
* Validates the generated ticket hierarchy and handles lifting cross-story dependencies.
|
|
421
410
|
*
|
|
422
|
-
* The returned tickets array carries
|
|
423
|
-
* - `findings` —
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
* - `
|
|
428
|
-
* `
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
* violations occur.
|
|
411
|
+
* The returned tickets array carries extra non-array properties:
|
|
412
|
+
* - `findings` — the advisory cross-Story conflict findings.
|
|
413
|
+
* - `errors` — human-readable strings, one per hard refusal: a `deletes`
|
|
414
|
+
* naming a path absent at base (prefixed `File assumption mismatch:`).
|
|
415
|
+
* The hierarchy/cycle/parse checks continue to throw.
|
|
416
|
+
* - `warnings` — the demoted footprint probes (Story #5312): a `creates`
|
|
417
|
+
* or `refactors-existing` mismatch, a goal/acceptance/verify path absent
|
|
418
|
+
* at base. Listed by the dry-run; the persist proceeds.
|
|
419
|
+
* - `normalizations` — the `refactors-existing`→`creates` rewrites applied.
|
|
432
420
|
*
|
|
433
421
|
* @param {object[]} tickets - Array of ticket objects parsed from LLM output.
|
|
434
422
|
* @param {object} [opts]
|
|
435
|
-
* @param {string} [opts.baseBranchRef] - When set, runs
|
|
423
|
+
* @param {string} [opts.baseBranchRef] - When set, runs the base-branch probes against this ref.
|
|
436
424
|
* @param {Function} [opts.gitRunner] - Optional git probe override.
|
|
437
|
-
* @param {string} [opts.cwd] - Repo cwd (forwarded to the
|
|
438
|
-
* @
|
|
439
|
-
* @param {object} [opts.conflictPolicy] - Severity controls for cross-Story conflict findings.
|
|
440
|
-
* @param {boolean} [opts.conflictPolicy.failOnSharedEditors=false] - Upgrade `shared-editor` findings to `hard`.
|
|
441
|
-
* @param {boolean} [opts.conflictPolicy.requireExplicitCrossStoryDeps=false] - Upgrade `implicit-cross-story-dep` findings to `hard`.
|
|
442
|
-
* @returns {object[] & { findings: object[], errors: string[] }} Validated tickets with normalized dependencies and attached sizing + conflict findings.
|
|
425
|
+
* @param {string} [opts.cwd] - Repo cwd (forwarded to the probes).
|
|
426
|
+
* @returns {object[] & { findings: object[], errors: string[], warnings: string[], normalizations: object[] }}
|
|
443
427
|
*/
|
|
444
428
|
/**
|
|
445
429
|
* Internal helpers extracted from `validateAndNormalizeTickets` so each
|
|
@@ -603,13 +587,12 @@ function assertAcyclic(slugAdjacency) {
|
|
|
603
587
|
|
|
604
588
|
function attachFindingsAndErrors(
|
|
605
589
|
tickets,
|
|
606
|
-
findings,
|
|
607
|
-
errors,
|
|
608
|
-
normalizations = [],
|
|
590
|
+
{ findings, errors, warnings, normalizations },
|
|
609
591
|
) {
|
|
610
592
|
for (const [key, value] of [
|
|
611
593
|
['findings', findings],
|
|
612
594
|
['errors', errors],
|
|
595
|
+
['warnings', warnings],
|
|
613
596
|
// Story #5265: the auto-normalizations the assumption gate applied. They
|
|
614
597
|
// used to end at a `Logger.warn` and die with the process, so persist's
|
|
615
598
|
// emitted result reported a plan whose declarations it had silently
|
|
@@ -660,25 +643,26 @@ export function validateAndNormalizeTickets(tickets, opts = {}) {
|
|
|
660
643
|
? makeMemoizedGitRunner(opts.gitRunner ?? defaultGitRunner)
|
|
661
644
|
: null;
|
|
662
645
|
|
|
663
|
-
|
|
664
|
-
//
|
|
665
|
-
//
|
|
666
|
-
//
|
|
646
|
+
const warnings = [];
|
|
647
|
+
// Story #5312: a goal / acceptance / verify path absent at base is a
|
|
648
|
+
// warning the dry-run lists, not a refusal. Skipped when the caller omits
|
|
649
|
+
// `baseBranchRef` so unit tests keep their semantics; production
|
|
650
|
+
// call-sites always pass it.
|
|
667
651
|
if (opts.baseBranchRef) {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
652
|
+
warnings.push(
|
|
653
|
+
...validateAcFreshness({
|
|
654
|
+
tickets,
|
|
655
|
+
baseBranchRef: opts.baseBranchRef,
|
|
656
|
+
gitRunner: sharedGitRunner,
|
|
657
|
+
cwd: opts.cwd,
|
|
658
|
+
}),
|
|
659
|
+
);
|
|
674
660
|
}
|
|
675
661
|
|
|
676
662
|
// Story #2636 — Phase 8 path-assumption gate. Cross-check every Story's
|
|
677
663
|
// declared `{ path, assumption }` against the actual state of the base
|
|
678
|
-
// branch
|
|
679
|
-
// envelope
|
|
680
|
-
// unit tests keep their semantics; production call-sites always pass
|
|
681
|
-
// it.
|
|
664
|
+
// branch. A `deletes` on an absent path batches into the validator's
|
|
665
|
+
// errors envelope; every other mismatch is a warning (Story #5312).
|
|
682
666
|
let assumptionErrors = [];
|
|
683
667
|
let assumptionNormalizations = [];
|
|
684
668
|
if (opts.baseBranchRef) {
|
|
@@ -688,71 +672,23 @@ export function validateAndNormalizeTickets(tickets, opts = {}) {
|
|
|
688
672
|
gitRunner: sharedGitRunner,
|
|
689
673
|
cwd: opts.cwd,
|
|
690
674
|
});
|
|
691
|
-
|
|
692
|
-
// warning is self-explanatory; everything else on the warnings channel
|
|
693
|
-
// is a legacy-shape deprecation nudge.
|
|
694
|
-
const normalizationWarnings = new Set(
|
|
695
|
-
(assumptionReport.normalizations ?? []).map((n) => n.path),
|
|
696
|
-
);
|
|
697
|
-
for (const warning of assumptionReport.warnings) {
|
|
698
|
-
const isNormalization = warning.includes('auto-normalized to "creates"');
|
|
699
|
-
Logger.warn(
|
|
700
|
-
`[ticket-validator] ${isNormalization ? 'assumption-normalized' : 'assumption-deprecation'}: ${warning}`,
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
if (normalizationWarnings.size > 0) {
|
|
704
|
-
Logger.warn(
|
|
705
|
-
`[ticket-validator] ${normalizationWarnings.size} refactors-existing ` +
|
|
706
|
-
'declaration(s) on base-untracked path(s) auto-normalized to ' +
|
|
707
|
-
'"creates" — the gate proceeds; update the plan declarations at ' +
|
|
708
|
-
'the next amend.',
|
|
709
|
-
);
|
|
710
|
-
}
|
|
675
|
+
warnings.push(...assumptionReport.warnings);
|
|
711
676
|
assumptionErrors = assumptionReport.errors;
|
|
712
677
|
assumptionNormalizations = assumptionReport.normalizations ?? [];
|
|
713
678
|
}
|
|
714
679
|
|
|
715
|
-
const sizingFindings = computeSizingFindings({
|
|
716
|
-
stories,
|
|
717
|
-
capacity: opts.modelCapacity,
|
|
718
|
-
});
|
|
719
680
|
// Cross-Story path-conflict pass observes the story-level depends_on
|
|
720
|
-
// graph.
|
|
721
|
-
// the
|
|
722
|
-
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
681
|
+
// graph. Every finding is advisory; the persist surfaces them and the
|
|
682
|
+
// plan summary renders the shared-editor class beside the wave table.
|
|
683
|
+
const findings = computeConflictFindings({ stories });
|
|
684
|
+
const errors = assumptionErrors.map((e) => `File assumption mismatch: ${e}`);
|
|
685
|
+
|
|
686
|
+
attachFindingsAndErrors(tickets, {
|
|
687
|
+
findings,
|
|
688
|
+
errors,
|
|
689
|
+
warnings,
|
|
690
|
+
normalizations: assumptionNormalizations,
|
|
726
691
|
});
|
|
727
|
-
// Advisory `## Spec` word-budget pass (Story #4723) — soft findings only,
|
|
728
|
-
// never promoted to `errors[]`, so an over-budget Spec cannot fail the
|
|
729
|
-
// persist. Runs after `assertStoryBodiesParse`, so string bodies parse.
|
|
730
|
-
// This pass computes but does not report: the sole production caller always
|
|
731
|
-
// runs the persist soft-finding surface, which reports every soft kind
|
|
732
|
-
// uniformly. Warning here too made `spec-word-budget` the only kind logged
|
|
733
|
-
// twice per run (Story #4907).
|
|
734
|
-
const specBudgetFindings = computeSpecBudgetFindings({ stories });
|
|
735
|
-
const findings = [
|
|
736
|
-
...sizingFindings,
|
|
737
|
-
...conflictFindings,
|
|
738
|
-
...specBudgetFindings,
|
|
739
|
-
];
|
|
740
|
-
const errors = findings
|
|
741
|
-
.filter((f) => f.severity === 'hard')
|
|
742
|
-
.map((f) =>
|
|
743
|
-
CONFLICT_KINDS.has(f.kind)
|
|
744
|
-
? renderHardConflictError(f)
|
|
745
|
-
: renderHardFindingError(f),
|
|
746
|
-
);
|
|
747
|
-
// Append per-Story path-assumption mismatches (Story #2636) to the
|
|
748
|
-
// hard-error list. The decompose loop already gates on
|
|
749
|
-
// `errors.length > 0` to trigger a re-prompt, so the new check
|
|
750
|
-
// participates in the same loop without bespoke wiring.
|
|
751
|
-
for (const e of assumptionErrors) {
|
|
752
|
-
errors.push(`File assumption mismatch: ${e}`);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
attachFindingsAndErrors(tickets, findings, errors, assumptionNormalizations);
|
|
756
692
|
return tickets;
|
|
757
693
|
}
|
|
758
694
|
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
* the entry as credited instead of telling the caller to spawn it.
|
|
13
13
|
*
|
|
14
14
|
* It only ever *reads*. Nothing here writes a capture stamp or an evidence
|
|
15
|
-
* record — an entry that is not covered by a fresh stamp
|
|
16
|
-
* `
|
|
15
|
+
* record — an entry that is not covered by a fresh stamp or a credited
|
|
16
|
+
* `test` evidence record (Story #5313: a green bare `npm test` deposits one)
|
|
17
|
+
* is reported `spawn: true` and runs for real, so the credit can never
|
|
18
|
+
* manufacture a pass.
|
|
17
19
|
*
|
|
18
20
|
* @see .agents/scripts/lib/coverage-capture.js (`isCoverageFresh`)
|
|
19
21
|
* @see .agents/scripts/lib/validation-evidence.js (`shouldSkip`)
|
|
@@ -23,7 +25,11 @@ import { getQuality, resolveConfig } from '../config-resolver.js';
|
|
|
23
25
|
import { isCoverageFresh } from '../coverage-capture.js';
|
|
24
26
|
import { gitSpawn } from '../git-utils.js';
|
|
25
27
|
import { hasNpmScript, readPackageScripts } from '../npm-scripts.js';
|
|
26
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
hashCommandConfig,
|
|
30
|
+
shouldSkip,
|
|
31
|
+
treeFingerprint,
|
|
32
|
+
} from '../validation-evidence.js';
|
|
27
33
|
|
|
28
34
|
/**
|
|
29
35
|
* The shape a `verify[]` array is supposed to have, stated once so the
|
|
@@ -169,6 +175,7 @@ export function resolveVerifyCredit(
|
|
|
169
175
|
isCoverageFreshImpl = isCoverageFresh,
|
|
170
176
|
shouldSkipImpl = shouldSkip,
|
|
171
177
|
hashCommandConfigImpl = hashCommandConfig,
|
|
178
|
+
treeFingerprintImpl = treeFingerprint,
|
|
172
179
|
gitSpawnFn = gitSpawn,
|
|
173
180
|
} = deps;
|
|
174
181
|
|
|
@@ -189,43 +196,81 @@ export function resolveVerifyCredit(
|
|
|
189
196
|
? 'capture'
|
|
190
197
|
: 'evidence';
|
|
191
198
|
|
|
199
|
+
let stampReason = null;
|
|
192
200
|
if (mode === 'capture') {
|
|
193
201
|
const freshness = isCoverageFreshImpl({
|
|
194
202
|
coveragePath: crap.coveragePath,
|
|
195
203
|
targetDirs: crap.targetDirs,
|
|
196
204
|
cwd: worktree,
|
|
197
205
|
});
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
+
if (freshness?.fresh === true) {
|
|
207
|
+
return {
|
|
208
|
+
...scoped,
|
|
209
|
+
mode,
|
|
210
|
+
credited: true,
|
|
211
|
+
spawn: false,
|
|
212
|
+
reason: 'capture-stamp-fresh',
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
stampReason = freshness?.reason ?? 'unknown';
|
|
206
216
|
}
|
|
207
217
|
|
|
218
|
+
// Story #5313 — a green bare `npm test` deposits the `test` evidence record
|
|
219
|
+
// close reads, so a stale (or absent) capture stamp is not the last word:
|
|
220
|
+
// the evidence keyspace is consulted in both modes before spawning. When it
|
|
221
|
+
// credits nothing either, capture mode reports the stamp's own reason.
|
|
222
|
+
const verdict = readTestEvidence({
|
|
223
|
+
storyId,
|
|
224
|
+
worktree,
|
|
225
|
+
cwd,
|
|
226
|
+
gitSpawnFn,
|
|
227
|
+
shouldSkipImpl,
|
|
228
|
+
hashCommandConfigImpl,
|
|
229
|
+
treeFingerprintImpl,
|
|
230
|
+
});
|
|
231
|
+
const credited = verdict.skip === true;
|
|
232
|
+
return {
|
|
233
|
+
...scoped,
|
|
234
|
+
mode,
|
|
235
|
+
credited,
|
|
236
|
+
spawn: !credited,
|
|
237
|
+
reason: credited ? verdict.reason : (stampReason ?? verdict.reason),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Consult the `test` evidence record for the worktree's HEAD — the record a
|
|
243
|
+
* green bare `npm test` deposits (Story #5313) and `evidence-gate.js` wrote
|
|
244
|
+
* before it. Total: an unreadable HEAD is `no-head`, never a credit.
|
|
245
|
+
*
|
|
246
|
+
* @param {{ storyId: number|string, worktree: string, cwd: string, gitSpawnFn: Function, shouldSkipImpl: Function, hashCommandConfigImpl: Function, treeFingerprintImpl: Function }} args
|
|
247
|
+
* @returns {{ skip: boolean, reason: string }}
|
|
248
|
+
*/
|
|
249
|
+
function readTestEvidence({
|
|
250
|
+
storyId,
|
|
251
|
+
worktree,
|
|
252
|
+
cwd,
|
|
253
|
+
gitSpawnFn,
|
|
254
|
+
shouldSkipImpl,
|
|
255
|
+
hashCommandConfigImpl,
|
|
256
|
+
treeFingerprintImpl,
|
|
257
|
+
}) {
|
|
208
258
|
const headSha = readHeadSha(worktree, gitSpawnFn);
|
|
209
|
-
if (!headSha) {
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
const [cmd, ...args] = command.split(/\s+/).filter(Boolean);
|
|
213
|
-
const verdict = shouldSkipImpl(
|
|
259
|
+
if (!headSha) return { skip: false, reason: 'no-head' };
|
|
260
|
+
return shouldSkipImpl(
|
|
214
261
|
{
|
|
215
262
|
storyId,
|
|
216
263
|
gateName: 'test',
|
|
217
264
|
currentSha: headSha,
|
|
218
|
-
configHash: hashCommandConfigImpl({
|
|
265
|
+
configHash: hashCommandConfigImpl({
|
|
266
|
+
cmd: 'npm',
|
|
267
|
+
args: ['test'],
|
|
268
|
+
cwd: worktree,
|
|
269
|
+
}),
|
|
270
|
+
inputFingerprint: treeFingerprintImpl(worktree, gitSpawnFn),
|
|
219
271
|
},
|
|
220
272
|
{ cwd, standalone: true },
|
|
221
273
|
);
|
|
222
|
-
return {
|
|
223
|
-
...scoped,
|
|
224
|
-
mode,
|
|
225
|
-
credited: verdict.skip === true,
|
|
226
|
-
spawn: verdict.skip !== true,
|
|
227
|
-
reason: verdict.reason,
|
|
228
|
-
};
|
|
229
274
|
}
|
|
230
275
|
|
|
231
276
|
/**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* inference a failing dry-run surfaces (Story #4684).
|
|
5
5
|
*
|
|
6
6
|
* The problem this closes: deterministic format rules (structured `## Changes`
|
|
7
|
-
* bullet shape,
|
|
7
|
+
* bullet shape, non-empty sections) used to be discovered only as persist
|
|
8
8
|
* dry-run failures — every miss cost a full re-author round-trip at
|
|
9
9
|
* resident-context prices. This module is the single home for two remedies:
|
|
10
10
|
*
|
|
@@ -13,11 +13,16 @@
|
|
|
13
13
|
* them into the story-author system prompt so the first draft is
|
|
14
14
|
* lint-clean by construction; a test enumerates the registry against the
|
|
15
15
|
* rendered prompt (Story #4684 AC-1).
|
|
16
|
-
* 2. `suggestPathEntryFix`
|
|
17
|
-
*
|
|
18
|
-
* (`collectChangesErrors`
|
|
19
|
-
*
|
|
20
|
-
* (
|
|
16
|
+
* 2. `suggestPathEntryFix` — the mechanical rewrite. `story-body.js`
|
|
17
|
+
* (`parsePathEntry`) and `task-body-validator.js`
|
|
18
|
+
* (`collectChangesErrors`) call it so a failing lint emits the corrected
|
|
19
|
+
* form ready to paste rather than a bare reject (AC-2), and the persist
|
|
20
|
+
* dry-run applies the same salvage for real (Story #5312,
|
|
21
|
+
* `plan-persist/changes-repair.js`).
|
|
22
|
+
*
|
|
23
|
+
* Story #5312 deleted the `verify-tier-suffix` / `verify-manual-reason` lints
|
|
24
|
+
* with the tier suffix itself: a `verify[]` entry is any command, and the
|
|
25
|
+
* `manual:<reason>` escape is gone with the rule it escaped.
|
|
21
26
|
*
|
|
22
27
|
* Import hygiene: this module imports only the cycle-free
|
|
23
28
|
* `file-assumption-enum.js` leaf. It must NOT import `story-body.js` or
|
|
@@ -27,20 +32,6 @@
|
|
|
27
32
|
|
|
28
33
|
import { FILE_ASSUMPTION_VALUES } from '../orchestration/file-assumption-enum.js';
|
|
29
34
|
|
|
30
|
-
/**
|
|
31
|
-
* Canonical testing-tier vocabulary an inferred verify suffix may name. Kept in
|
|
32
|
-
* sync with `task-body-validator.js`'s `VERIFY_TIER_VALUES` by a test rather
|
|
33
|
-
* than an import (importing that module here would create a cycle).
|
|
34
|
-
*
|
|
35
|
-
* @type {readonly ['unit','contract','e2e','validate']}
|
|
36
|
-
*/
|
|
37
|
-
const INFERABLE_VERIFY_TIERS = Object.freeze([
|
|
38
|
-
'unit',
|
|
39
|
-
'contract',
|
|
40
|
-
'e2e',
|
|
41
|
-
'validate',
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
35
|
/**
|
|
45
36
|
* The default assumption a mechanical `## Changes` auto-fix proposes. Most
|
|
46
37
|
* bare-path bullets an author drops are in-place edits, so `refactors-existing`
|
|
@@ -54,48 +45,6 @@ const DEFAULT_SUGGESTED_ASSUMPTION = 'refactors-existing';
|
|
|
54
45
|
// a false positive only produces an unhelpful (still-valid) fix-it string.
|
|
55
46
|
const PATH_LIKE_RE = /[\w@*-]*[/.][\w@./*-]+/;
|
|
56
47
|
|
|
57
|
-
/**
|
|
58
|
-
* Infer the testing tier a bare `verify[]` command implies, when it is
|
|
59
|
-
* unambiguous. Returns `null` when no confident inference is possible (the
|
|
60
|
-
* author must then choose the tier themselves — the lint still fires, just
|
|
61
|
-
* without a fix-it).
|
|
62
|
-
*
|
|
63
|
-
* @param {unknown} command
|
|
64
|
-
* @returns {'unit'|'contract'|'e2e'|'validate'|null}
|
|
65
|
-
*/
|
|
66
|
-
function inferVerifyTier(command) {
|
|
67
|
-
if (typeof command !== 'string') return null;
|
|
68
|
-
const c = command.toLowerCase();
|
|
69
|
-
if (/\bvalidate\b/.test(c)) return 'validate';
|
|
70
|
-
if (/playwright|\.spec\.|\be2e\b/.test(c)) return 'e2e';
|
|
71
|
-
if (/\.test\.|node --test|node:test|\bvitest\b|\bjest\b/.test(c)) {
|
|
72
|
-
return 'unit';
|
|
73
|
-
}
|
|
74
|
-
if (/\bcontract\b/.test(c)) return 'contract';
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Propose the corrected form of a `verify[]` entry that is missing its tier
|
|
80
|
-
* suffix, when the tier is inferable from the command. Returns `null` when the
|
|
81
|
-
* entry is a `manual:` escape, is empty, or the tier cannot be inferred.
|
|
82
|
-
*
|
|
83
|
-
* @param {unknown} entry
|
|
84
|
-
* @returns {string|null} e.g. `"npm run validate (validate)"`.
|
|
85
|
-
*/
|
|
86
|
-
export function suggestVerifyFix(entry) {
|
|
87
|
-
if (typeof entry !== 'string') return null;
|
|
88
|
-
const trimmed = entry.trim();
|
|
89
|
-
if (trimmed === '' || trimmed.startsWith('manual:')) return null;
|
|
90
|
-
const tier = inferVerifyTier(trimmed);
|
|
91
|
-
if (tier === null) return null;
|
|
92
|
-
// Drop any trailing (…) — a wrong/partial tier suffix — before appending the
|
|
93
|
-
// inferred one, so `npm test (smoke)` becomes `npm test (unit)` not a double.
|
|
94
|
-
const base = trimmed.replace(/\s*\([^)]*\)\s*$/, '').trim();
|
|
95
|
-
if (base === '') return null;
|
|
96
|
-
return `${base} (${tier})`;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
48
|
/**
|
|
100
49
|
* Propose the canonical `{ path, assumption }` object form for a `## Changes` /
|
|
101
50
|
* `## References` bullet an author wrote as a bare path string (or a humanized
|
|
@@ -140,9 +89,8 @@ export function suggestPathEntryFix(raw) {
|
|
|
140
89
|
/**
|
|
141
90
|
* The enumerated deterministic lints that can reject an authored Story body at
|
|
142
91
|
* persist time. Each carries a concrete example so the story-author prompt can
|
|
143
|
-
* state the requirement example-first (Story #4684 AC-1). The
|
|
144
|
-
*
|
|
145
|
-
* form (AC-2).
|
|
92
|
+
* state the requirement example-first (Story #4684 AC-1). The one `autoFixable`
|
|
93
|
+
* lint is the mechanical rewrite the dry-run applies (Story #5312).
|
|
146
94
|
*
|
|
147
95
|
* @type {ReadonlyArray<BodyFormatLint>}
|
|
148
96
|
*/
|
|
@@ -179,29 +127,11 @@ export const BODY_FORMAT_LINTS = Object.freeze([
|
|
|
179
127
|
goodExample: '- {"path": "src/app.js", "assumption": "creates"}',
|
|
180
128
|
autoFixable: false,
|
|
181
129
|
},
|
|
182
|
-
{
|
|
183
|
-
id: 'verify-tier-suffix',
|
|
184
|
-
summary:
|
|
185
|
-
'Every `verify[]` entry MUST end with a tier in parentheses — one of ' +
|
|
186
|
-
`(${INFERABLE_VERIFY_TIERS.join(' | ')}) — or be a \`manual:<reason>\` escape.`,
|
|
187
|
-
badExample: 'npm test -- src/app.test.js',
|
|
188
|
-
goodExample: 'npm test -- src/app.test.js (unit)',
|
|
189
|
-
autoFixable: true,
|
|
190
|
-
},
|
|
191
130
|
{
|
|
192
131
|
id: 'verify-non-empty',
|
|
193
|
-
summary:
|
|
194
|
-
'A Story MUST list at least one `verify[]` entry (use `manual:<reason>` only when truly unverifiable in isolation).',
|
|
132
|
+
summary: 'A Story MUST list at least one `verify[]` entry.',
|
|
195
133
|
badExample: '"verify": []',
|
|
196
|
-
goodExample: '"verify": ["npm run validate
|
|
197
|
-
autoFixable: false,
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
id: 'verify-manual-reason',
|
|
201
|
-
summary:
|
|
202
|
-
'A `manual:` verify entry MUST carry a reason after the colon; a bare `manual:` is rejected.',
|
|
203
|
-
badExample: 'manual:',
|
|
204
|
-
goodExample: 'manual: copy-only edit an auditor eyeballs',
|
|
134
|
+
goodExample: '"verify": ["npm run validate"]',
|
|
205
135
|
autoFixable: false,
|
|
206
136
|
},
|
|
207
137
|
{
|