@renzrollon/interlock 1.5.0 → 1.6.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/.claude-plugin/plugin.json +12 -1
- package/CHANGELOG.md +104 -0
- package/README.md +7 -8
- package/bin/interlock +1655 -29
- package/hooks/mod.mjs +21 -20
- package/hooks/notify-option.mjs +69 -0
- package/hooks/preflight.mjs +6 -42
- package/lib/conformance.mjs +75 -0
- package/lib/coverage.mjs +387 -0
- package/lib/doctor.mjs +17 -13
- package/lib/drift.mjs +36 -12
- package/lib/findings.mjs +40 -3
- package/lib/graph/extract/openspec.mjs +6 -15
- package/lib/limits.mjs +78 -1
- package/lib/merge-confidence.mjs +1302 -0
- package/lib/merge-evidence.mjs +527 -0
- package/lib/notify.mjs +10 -20
- package/lib/outcomes.mjs +26 -5
- package/lib/pipeline.mjs +380 -0
- package/lib/prompts/dimensions.mjs +24 -2
- package/lib/prompts/domain-context.mjs +271 -0
- package/lib/prompts/handoff.mjs +32 -1
- package/lib/prompts/index.mjs +57 -0
- package/lib/prompts/remediate.mjs +29 -5
- package/lib/prompts/review.mjs +21 -7
- package/lib/prompts/schemas.mjs +2 -0
- package/lib/ready.mjs +13 -1
- package/lib/receipt.mjs +72 -2
- package/lib/review-core.mjs +224 -21
- package/lib/risk.mjs +8 -0
- package/lib/run-log.mjs +44 -0
- package/lib/run.mjs +859 -27
- package/lib/spec-audit.mjs +232 -0
- package/lib/spec-coverage.mjs +105 -0
- package/lib/spec-text.mjs +257 -0
- package/lib/verify.mjs +17 -4
- package/package.json +1 -1
- package/shared/SESSION-SHAPES.md +1 -1
- package/shared/TEST-PROFILE.md +39 -2
- package/skills/mr/SKILL.md +161 -11
- package/skills/review-code/REVIEW.template.md +24 -6
- package/skills/review-code/SKILL.md +16 -6
- package/skills/review-code/dimensions/domain.md +21 -0
package/lib/limits.mjs
CHANGED
|
@@ -141,6 +141,18 @@ export const LIMITS = {
|
|
|
141
141
|
*/
|
|
142
142
|
maxReviewPolicyBytes: 65_536,
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Character budget for the living-requirement text one review prompt carries
|
|
146
|
+
* in its `domain` dimension. Whole requirement blocks are added in capability
|
|
147
|
+
* then spec order until the next would exceed this; every block left out is
|
|
148
|
+
* counted in the text and named in the result, never cut mid-requirement. It
|
|
149
|
+
* bounds a prompt, not a merge decision, so it lives here and not in
|
|
150
|
+
* `MERGE_BANDS`. Read by `assembleDomainContext` in
|
|
151
|
+
* `lib/prompts/domain-context.mjs`.
|
|
152
|
+
* See judge-against-base-and-living-specs design.md D4.
|
|
153
|
+
*/
|
|
154
|
+
maxDomainSpecChars: 12_000,
|
|
155
|
+
|
|
144
156
|
/**
|
|
145
157
|
* Milliseconds a close's push to the ntfy relay may take before it is
|
|
146
158
|
* abandoned as a failed push. A hanging network call must not stall the
|
|
@@ -512,6 +524,54 @@ export const SOLO = {
|
|
|
512
524
|
maxTasks: 20
|
|
513
525
|
}
|
|
514
526
|
|
|
527
|
+
/**
|
|
528
|
+
* The thresholds the merge verdict applies (`interlock merge-confidence`,
|
|
529
|
+
* `lib/merge-confidence.mjs`), published here so no skill, prompt or document
|
|
530
|
+
* restates one as the rule — the reason this module exists.
|
|
531
|
+
*
|
|
532
|
+
* A separate export from LIMITS, like LANE_CAPS and EVAL_CAPS, for two reasons.
|
|
533
|
+
* `ceiling` is a table, not a positive-integer loop count, and the LIMITS
|
|
534
|
+
* invariant holds every entry to that shape. And these are not loop caps: the
|
|
535
|
+
* verdict is the worst band over every signal, and these numbers only decide
|
|
536
|
+
* which band one signal reads, or where inside its band's range a score lands.
|
|
537
|
+
*
|
|
538
|
+
* `ceiling` is the score each band may reach; a band's floor is one above the
|
|
539
|
+
* next-worse band's ceiling, and red's floor is 0. `unobserved` has no row: it
|
|
540
|
+
* takes amber's range, as it takes amber's rank for the verdict, so a fourth
|
|
541
|
+
* number would be one nobody reads. The domain-spec text cap the epic lists is
|
|
542
|
+
* deliberately absent: its reader lands with the `domain` review dimension, and a
|
|
543
|
+
* printed cap with no reader fails the cap-authority sweep.
|
|
544
|
+
*
|
|
545
|
+
* Read by `lib/merge-confidence.mjs`, which cites each key by name in the reason
|
|
546
|
+
* it applies it in; printed by `interlock limits` and emitted as `mergeBands` by
|
|
547
|
+
* `--json`. The cap-authority sweep covers the group, `ceiling` counting as one
|
|
548
|
+
* cap on the terms `LANE_CAPS.byTier` does.
|
|
549
|
+
*/
|
|
550
|
+
export const MERGE_BANDS = Object.freeze({
|
|
551
|
+
ceiling: Object.freeze({ green: 100, amber: 79, red: 39 }),
|
|
552
|
+
|
|
553
|
+
/** Surviving review warnings at or under this read green; one more is amber. */
|
|
554
|
+
maxWarningsForMerge: 2,
|
|
555
|
+
|
|
556
|
+
/** Skeptic verdicts a surviving blocker or warning needs before it counts as adjudicated. */
|
|
557
|
+
minVerdictsPerFinding: 2,
|
|
558
|
+
|
|
559
|
+
/** Points the project coverage total may fall below the base total and still read green. */
|
|
560
|
+
maxCoverageDeclinePts: 1,
|
|
561
|
+
|
|
562
|
+
/** Changed-line coverage, in percent, a change with no baseline must reach to read green. */
|
|
563
|
+
patchCoverageFloorPct: 80,
|
|
564
|
+
|
|
565
|
+
/** Changed lines below which coverage is not judged at all; a change of exactly this many is. */
|
|
566
|
+
minChangedLinesForCoverage: 20,
|
|
567
|
+
|
|
568
|
+
/** Changed lines, added plus deleted, above which the diff shape is amber. */
|
|
569
|
+
maxChangedLinesForMerge: 400,
|
|
570
|
+
|
|
571
|
+
/** Changed files above which the diff shape is amber. */
|
|
572
|
+
maxChangedFilesForMerge: 20
|
|
573
|
+
})
|
|
574
|
+
|
|
515
575
|
/**
|
|
516
576
|
* Caps for the corpus reader (`interlock report`).
|
|
517
577
|
*
|
|
@@ -573,6 +633,7 @@ export function formatLimits({ runtimeObserved } = {}) {
|
|
|
573
633
|
['verify preview budget (chars)', LIMITS.verifyPreviewChars],
|
|
574
634
|
['wave handoff budget (chars, per task)', LIMITS.maxHandoffChars],
|
|
575
635
|
['review policy scan cap (bytes)', LIMITS.maxReviewPolicyBytes],
|
|
636
|
+
['domain spec text budget (chars)', LIMITS.maxDomainSpecChars],
|
|
576
637
|
['push timeout (ms)', LIMITS.notifyTimeoutMs],
|
|
577
638
|
['resume card list rows (per list)', LIMITS.resumeCardListRows],
|
|
578
639
|
['launch ledger max age (ms)', LIMITS.launchLedgerMaxAgeMs],
|
|
@@ -623,6 +684,20 @@ export function formatLimits({ runtimeObserved } = {}) {
|
|
|
623
684
|
.map(tier => [`cache write multiplier: ${tier} (x input)`, MODEL_PRICES.cacheMultipliers.write[tier]])
|
|
624
685
|
]
|
|
625
686
|
const reportRows = [['report trajectory scan cap', REPORT_CAPS.maxRunsScanned]]
|
|
687
|
+
// One row per ceiling, then one per threshold. `unobserved` prints beside
|
|
688
|
+
// amber because it takes amber's range; it has no number of its own.
|
|
689
|
+
const mergeRows = [
|
|
690
|
+
['merge band ceiling: green', MERGE_BANDS.ceiling.green],
|
|
691
|
+
['merge band ceiling: amber (also unobserved)', MERGE_BANDS.ceiling.amber],
|
|
692
|
+
['merge band ceiling: red', MERGE_BANDS.ceiling.red],
|
|
693
|
+
['merge: max surviving warnings', MERGE_BANDS.maxWarningsForMerge],
|
|
694
|
+
['merge: min skeptic verdicts per finding', MERGE_BANDS.minVerdictsPerFinding],
|
|
695
|
+
['merge: max coverage decline (points)', MERGE_BANDS.maxCoverageDeclinePts],
|
|
696
|
+
['merge: patch coverage floor (%, no baseline)', MERGE_BANDS.patchCoverageFloorPct],
|
|
697
|
+
['merge: min changed lines to judge coverage', MERGE_BANDS.minChangedLinesForCoverage],
|
|
698
|
+
['merge: max changed lines', MERGE_BANDS.maxChangedLinesForMerge],
|
|
699
|
+
['merge: max changed files', MERGE_BANDS.maxChangedFilesForMerge]
|
|
700
|
+
]
|
|
626
701
|
// One row per tier, rather than a rendered object: an operator reading this
|
|
627
702
|
// wants to know what bounds the lane in front of them, and a tier is how they
|
|
628
703
|
// find it. The cohesion ceiling and the solo envelope print beside them because
|
|
@@ -650,7 +725,7 @@ export function formatLimits({ runtimeObserved } = {}) {
|
|
|
650
725
|
['effort: review skeptic step', EFFORT.skeptic]
|
|
651
726
|
]
|
|
652
727
|
const width = Math.max(
|
|
653
|
-
...[...rows, ...laneRows, ...evalRows, ...priceRows, ...reportRows, ...effortRows].map(
|
|
728
|
+
...[...rows, ...laneRows, ...mergeRows, ...evalRows, ...priceRows, ...reportRows, ...effortRows].map(
|
|
654
729
|
([label]) => label.length
|
|
655
730
|
)
|
|
656
731
|
)
|
|
@@ -660,6 +735,8 @@ export function formatLimits({ runtimeObserved } = {}) {
|
|
|
660
735
|
'\n\n' +
|
|
661
736
|
laneRows.map(fmt).join('\n') +
|
|
662
737
|
'\n\n' +
|
|
738
|
+
mergeRows.map(fmt).join('\n') +
|
|
739
|
+
'\n\n' +
|
|
663
740
|
evalRows.map(fmt).join('\n') +
|
|
664
741
|
'\n\n' +
|
|
665
742
|
priceRows.map(fmt).join('\n') +
|