@dev-loops/core 1.0.1 → 1.0.2-slim.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/package.json +5 -2
- package/src/analysis/diff-analyzer.mjs +85 -137
- package/src/claude/asset-generation.mjs +7 -7
- package/src/claude/hook-decisions.mjs +167 -50
- package/src/config/config.mjs +388 -787
- package/src/config/extension-defaults.yaml +14 -9
- package/src/github/comment-id-guard.mjs +39 -1
- package/src/github/copilot-helpers.mjs +90 -158
- package/src/github/gh.mjs +49 -0
- package/src/loop/bash-command-classify.mjs +34 -49
- package/src/loop/commit-msg-guard.mjs +1 -1
- package/src/loop/conductor-routing.mjs +15 -23
- package/src/loop/copilot-loop-state.mjs +46 -94
- package/src/loop/gate-carry-forward.mjs +46 -22
- package/src/loop/gate-evidence-reconcile.mjs +75 -0
- package/src/loop/gate-fanin.mjs +266 -435
- package/src/loop/handoff-envelope.mjs +21 -21
- package/src/loop/issue-refinement-artifact.mjs +449 -284
- package/src/loop/lifecycle-state.mjs +10 -21
- package/src/loop/pr-gate-coordination.mjs +49 -49
- package/src/loop/queue-board-sync.mjs +16 -82
- package/src/loop/review-dispatch-plan.mjs +61 -122
- package/src/loop/review-lineage.mjs +19 -44
- package/src/loop/spec-authority.mjs +729 -0
- package/src/loop/steering.mjs +16 -68
- package/src/loop/ui-e2e-scoping.mjs +1 -0
- package/src/loop/worktree-guard.mjs +55 -0
- package/src/projects/list-queue-items.mjs +16 -175
- package/src/projects/move-queue-item.mjs +16 -171
- package/src/projects/projects-access.mjs +202 -0
- package/src/security/secret-scan.mjs +13 -1
|
@@ -1,39 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* review-dispatch-plan.mjs — cache-aware review dispatch
|
|
3
|
-
* fingerprinting
|
|
2
|
+
* review-dispatch-plan.mjs — cache-aware review dispatch: harness capability
|
|
3
|
+
* model, request-prefix fingerprinting, stable/volatile request separation,
|
|
4
|
+
* and the deterministic per-gate-round dispatch-plan builder.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* (breakpointControl / barrierSignal / cacheTtlControl / usageTelemetry).
|
|
11
|
-
* Opaque capabilities are represented as such and are NEVER described as
|
|
12
|
-
* verified cache hits.
|
|
13
|
-
* 2. Request-prefix fingerprinting — a deterministic sha256 over every
|
|
14
|
-
* cache-relevant value the dev-loops layer observes or controls (concrete
|
|
15
|
-
* model, tool definitions/order, system/project/agent instructions,
|
|
16
|
-
* thinking/tool-choice settings, content-block boundaries, shared artifact
|
|
17
|
-
* bytes, and breakpoint/TTL intent). Values owned opaquely by a harness are
|
|
18
|
-
* represented as a placeholder, never assumed identical.
|
|
19
|
-
* 3. Stable/volatile request separation — physically separates stable handoff
|
|
20
|
-
* content from volatile `gateState` at the request/artifact boundary, so a
|
|
21
|
-
* provider-visible cache boundary sits after the stable materialized
|
|
22
|
-
* briefing block and before the late volatile tail + angle suffix.
|
|
23
|
-
* 4. Dispatch-plan builder — one deterministic per-gate-round artifact that
|
|
24
|
-
* records the complete cache-relevant request shape without duplicating
|
|
25
|
-
* briefing content (Section A). `buildAngleRequestGroups` partitions a
|
|
26
|
-
* caller's angle -> concrete-model resolutions into that plan's
|
|
27
|
-
* `requestGroups` shape, bucketing angles with no override into an
|
|
28
|
-
* explicit "inherit" key rather than merging them into a concrete group.
|
|
29
|
-
* 5. Primer-form default — deterministic default by harness capability
|
|
30
|
-
* (Section C/D): first-output-observable harnesses may let a lead reviewer
|
|
31
|
-
* prime; completion-only harnesses default to a short dedicated primer
|
|
32
|
-
* unless an adequate TTL is explicit; multiple concrete models partition
|
|
33
|
-
* into one primer group per model/request-prefix.
|
|
34
|
-
*
|
|
35
|
-
* This module is pure and offline: no GitHub, no harness, no clock. All runtime
|
|
36
|
-
* execution adapters consume it; it never executes a reviewer itself.
|
|
6
|
+
* Pure and offline: no GitHub, no harness, no clock; runtime execution
|
|
7
|
+
* adapters consume it, it never executes a reviewer itself. A capability a
|
|
8
|
+
* harness owns opaquely (breakpoints/TTL/telemetry it cannot observe) is
|
|
9
|
+
* always represented as opaque/unavailable rather than assumed identical —
|
|
10
|
+
* this module never claims a verified cache hit it cannot prove.
|
|
37
11
|
*/
|
|
38
12
|
import { createHash } from "node:crypto";
|
|
39
13
|
|
|
@@ -198,20 +172,16 @@ function isPlainObject(value) {
|
|
|
198
172
|
}
|
|
199
173
|
|
|
200
174
|
/**
|
|
201
|
-
* Recursively sort object keys for
|
|
202
|
-
*
|
|
203
|
-
* content-block boundaries).
|
|
175
|
+
* Recursively sort object keys for byte-deterministic serialization (array
|
|
176
|
+
* order is preserved — order is itself cache-relevant).
|
|
204
177
|
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* `
|
|
211
|
-
*
|
|
212
|
-
* accumulator is null-prototype so an own `__proto__` key (a realistic shape
|
|
213
|
-
* for JSON.parse'd input) is kept as a plain data property instead of
|
|
214
|
-
* vanishing into the prototype chain.
|
|
178
|
+
* Fails closed rather than silently colliding distinct inputs onto one
|
|
179
|
+
* fingerprint: a non-finite number is rejected rather than collapsed to
|
|
180
|
+
* `null`, a non-plain object (Date/Map/Set/...) is rejected rather than
|
|
181
|
+
* treated as keyless, and undefined/function/symbol/bigint are rejected
|
|
182
|
+
* rather than dropped or crash-serialized. The accumulator is null-prototype
|
|
183
|
+
* so an own `__proto__` key stays a plain data property instead of vanishing
|
|
184
|
+
* into the prototype chain.
|
|
215
185
|
* @param {*} value
|
|
216
186
|
* @param {string} [keyPath] — dotted path to `value`, for the error message
|
|
217
187
|
* @returns {*}
|
|
@@ -241,24 +211,16 @@ function stableStringify(value, keyPath = "$") {
|
|
|
241
211
|
}
|
|
242
212
|
|
|
243
213
|
/**
|
|
244
|
-
* Fingerprint the complete observable request prefix (Section A)
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* should be passed as `null`-free opaque markers (see `opaqueMarker`).
|
|
214
|
+
* Fingerprint the complete observable request prefix (Section A): concrete
|
|
215
|
+
* model, tool definitions/order, system/project/agent instructions,
|
|
216
|
+
* thinking/tool-choice settings, content-block boundaries, shared artifact
|
|
217
|
+
* bytes, and breakpoint/TTL intent. Values owned opaquely by a harness should
|
|
218
|
+
* be passed as `null`-free opaque markers (see `opaqueMarker`).
|
|
250
219
|
*
|
|
251
220
|
* @param {object} input
|
|
252
|
-
* @param {string} input.model - concrete model id
|
|
253
|
-
* @param {string[]
|
|
254
|
-
*
|
|
255
|
-
* @param {object|string} [input.settings] - thinking/tool-choice settings.
|
|
256
|
-
* @param {object[]} [input.contentBlocks] - content-block boundaries + shared bytes.
|
|
257
|
-
* @param {string} [input.sharedArtifact] - shared artifact reference (path) or bytes.
|
|
258
|
-
* @param {string} [input.cacheBoundary] - e.g. `after_shared_prefix`.
|
|
259
|
-
* @param {string} [input.ttlIntent] - one of TTL_INTENT_VALUES.
|
|
260
|
-
* @param {string[]} [input.angleSuffix] - angle-specific suffix (excluded from the
|
|
261
|
-
* STABLE prefix fingerprint; included here only when invasive).
|
|
221
|
+
* @param {string} input.model - non-empty concrete model id.
|
|
222
|
+
* @param {string[]} [input.angleSuffix] - excluded from the STABLE prefix
|
|
223
|
+
* fingerprint elsewhere, but folded in here when present.
|
|
262
224
|
* @returns {{ fingerprint: string, canonical: object }}
|
|
263
225
|
*/
|
|
264
226
|
export function fingerprintRequestPrefix(input) {
|
|
@@ -543,30 +505,20 @@ export const INHERIT_MODEL_KEY = "inherit";
|
|
|
543
505
|
* input. Angles resolving to the same concrete model id share one group;
|
|
544
506
|
* angles with no override (`model: null`/`undefined`) form their own explicit
|
|
545
507
|
* {@link INHERIT_MODEL_KEY} bucket, never merged with a concrete id. An angle
|
|
546
|
-
* listed twice with two DIFFERENT models
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
* collide with the reserved bucket key and become indistinguishable from
|
|
550
|
-
* genuine no-override.
|
|
508
|
+
* listed twice with two DIFFERENT models throws (it cannot belong to two
|
|
509
|
+
* request groups); a concrete model literally named {@link INHERIT_MODEL_KEY}
|
|
510
|
+
* also throws, since it would otherwise collide with the reserved bucket key.
|
|
551
511
|
*
|
|
552
512
|
* Each group's `requestPrefixFingerprint` is computed via
|
|
553
513
|
* {@link fingerprintRequestPrefix} over every cache-relevant input this layer
|
|
554
|
-
* observes for that group
|
|
555
|
-
*
|
|
556
|
-
* declared cache boundary/TTL intent) — changing only the angle set within a
|
|
557
|
-
* bucket never changes its fingerprint.
|
|
514
|
+
* observes for that group — changing only the angle set within a bucket never
|
|
515
|
+
* changes its fingerprint.
|
|
558
516
|
*
|
|
559
517
|
* @param {object} input
|
|
560
518
|
* @param {Array<{angle: string, model: string|null}>} input.angleModels
|
|
561
|
-
* @
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
* @param {object} [input.settings] — thinking/tool-choice settings
|
|
565
|
-
* @param {string[]} [input.blockBoundaries] — content-block boundary markers, in order
|
|
566
|
-
* @param {string} [input.cacheBoundary]
|
|
567
|
-
* @param {string} [input.ttlIntent] — one of TTL_INTENT_VALUES
|
|
568
|
-
* @returns {RequestGroup[]} sorted by model (code-unit order, never localeCompare — ICU-dependent
|
|
569
|
-
* sorting could order the same two model ids differently across runtimes); angles sorted within a group.
|
|
519
|
+
* @returns {RequestGroup[]} sorted by model (code-unit order, never
|
|
520
|
+
* localeCompare — ICU-dependent sorting could order the same two model ids
|
|
521
|
+
* differently across runtimes); angles sorted within a group.
|
|
570
522
|
*/
|
|
571
523
|
export function buildAngleRequestGroups({
|
|
572
524
|
angleModels,
|
|
@@ -730,11 +682,11 @@ export function partitionPrimerGroups(requestGroups, capabilities = {}) {
|
|
|
730
682
|
}
|
|
731
683
|
|
|
732
684
|
/* ------------------------------------------------------------------ *
|
|
733
|
-
* 6. Dispatch-prompt layout alignment
|
|
685
|
+
* 6. Dispatch-prompt layout alignment
|
|
734
686
|
* ------------------------------------------------------------------ */
|
|
735
687
|
|
|
736
|
-
// Leading-bytes capture cap for a dispatched reviewer prompt (
|
|
737
|
-
// record-dispatch-prompt-layout.mjs). Sized comfortably above
|
|
688
|
+
// Leading-bytes capture cap for a dispatched reviewer prompt (the
|
|
689
|
+
// record-dispatch-prompt-layout.mjs cap). Sized comfortably above
|
|
738
690
|
// write-gate-context.mjs's BRIEFING_PREFIX_INLINE_DIFF_CAP_BYTES (200 KiB) so
|
|
739
691
|
// a full byte-for-byte alignment check never runs out of captured bytes for
|
|
740
692
|
// an inline-mode round.
|
|
@@ -765,33 +717,26 @@ export function renderBriefingPointerLine(prefixPath) {
|
|
|
765
717
|
/**
|
|
766
718
|
* Deterministically compose a full reviewer prompt: the round's
|
|
767
719
|
* byte-identical invariant prefix INLINED as the leading bytes, followed by
|
|
768
|
-
* the
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
* exists to catch).
|
|
720
|
+
* the round-invariant volatile tail, followed by the per-group angle suffix.
|
|
721
|
+
* This is the ONE function every reviewer prompt on the canonical
|
|
722
|
+
* fan-out path is built from — never a hand-assembled per-group preamble that
|
|
723
|
+
* leads with dynamic prose ahead of the prefix (the "angle-first" failure
|
|
724
|
+
* mode `verifyPromptLeadingAlignment` exists to catch).
|
|
774
725
|
*
|
|
775
|
-
* Byte-identical-prefix-across-groups
|
|
776
|
-
* two calls sharing
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
* `angleSuffix` — the property AC1 requires, provable by construction rather
|
|
780
|
-
* than by review.
|
|
726
|
+
* Byte-identical-prefix-across-groups follows from the arguments alone: any
|
|
727
|
+
* two calls sharing `prefixBytes`/`volatileBytes` (true for every dispatch
|
|
728
|
+
* unit of one round) produce prompts whose leading span is identical
|
|
729
|
+
* regardless of `angleSuffix` — provable by construction, not by review.
|
|
781
730
|
*
|
|
782
731
|
* Pure and offline: takes already-read bytes, never reads a file itself (the
|
|
783
|
-
* CLI wrapper, `compose-reviewer-prompt.mjs`, owns I/O and the
|
|
784
|
-
*
|
|
785
|
-
*
|
|
732
|
+
* CLI wrapper, `compose-reviewer-prompt.mjs`, owns I/O and the capture that
|
|
733
|
+
* makes the composed prompt's layout binding on
|
|
734
|
+
* `verify-dispatch-prompt-layout.mjs`).
|
|
786
735
|
*
|
|
787
736
|
* @param {object} input
|
|
788
|
-
* @param {string} input.prefixBytes — the round's invariant-prefix bytes
|
|
789
|
-
*
|
|
790
|
-
* @param {string}
|
|
791
|
-
* (`<gate>-<headSha>.briefing-volatile.txt`); absent/non-string treated as
|
|
792
|
-
* "" (best-effort — a round that never wrote one still composes).
|
|
793
|
-
* @param {string} input.angleSuffix — the per-group/angle-specific prompt
|
|
794
|
-
* text, non-empty (an empty suffix would compose a prompt naming no work).
|
|
737
|
+
* @param {string} input.prefixBytes — non-empty, the round's invariant-prefix bytes.
|
|
738
|
+
* @param {string} [input.volatileBytes] — round's volatile-tail bytes; absent treated as "".
|
|
739
|
+
* @param {string} input.angleSuffix — non-empty per-group angle-specific prompt text.
|
|
795
740
|
* @returns {string} the exact full reviewer prompt text.
|
|
796
741
|
*/
|
|
797
742
|
export function composeReviewerPromptText({ prefixBytes, volatileBytes, angleSuffix } = {}) {
|
|
@@ -807,26 +752,19 @@ export function composeReviewerPromptText({ prefixBytes, volatileBytes, angleSuf
|
|
|
807
752
|
|
|
808
753
|
/**
|
|
809
754
|
* Decide whether a dispatched reviewer prompt's LEADING bytes are
|
|
810
|
-
* cache-aligned (GATE-EXEC-BRIEFING-PREFIX layout
|
|
755
|
+
* cache-aligned (GATE-EXEC-BRIEFING-PREFIX layout): either the
|
|
811
756
|
* prompt's leading bytes are byte-identical to the round's invariant prefix
|
|
812
757
|
* (inline mode), or the prompt leads with the byte-identical pointer line
|
|
813
758
|
* naming the round's invariant-prefix path (pointer-seeding mode), with any
|
|
814
|
-
* angle-specific text strictly AFTER it. An angle-first prompt
|
|
815
|
-
*
|
|
816
|
-
* REJECTED — this is the mechanical proof the prose-only rule lacked.
|
|
759
|
+
* angle-specific text strictly AFTER it. An angle-first prompt matches
|
|
760
|
+
* neither and is REJECTED.
|
|
817
761
|
*
|
|
818
|
-
* Pure and offline: takes
|
|
819
|
-
* read prefix bytes/path, never reads a file itself (the CLI wrapper owns
|
|
820
|
-
* I/O), so this is directly unit-testable with in-memory strings.
|
|
762
|
+
* Pure and offline: takes already-captured bytes, never reads a file itself.
|
|
821
763
|
*
|
|
822
764
|
* @param {object} input
|
|
823
|
-
* @param {string} input.promptLeading —
|
|
824
|
-
*
|
|
825
|
-
* @param {string} input.
|
|
826
|
-
* invariant-prefix content (the `<gate>-<headSha>.briefing-prefix.txt`
|
|
827
|
-
* bytes).
|
|
828
|
-
* @param {string} input.prefixPath — the path used to render this round's
|
|
829
|
-
* pointer line (must be the SAME path every reviewer was pointed at).
|
|
765
|
+
* @param {string} input.promptLeading — captured leading bytes of the actual reviewer prompt.
|
|
766
|
+
* @param {string} input.prefixBytes — the round's recorded invariant-prefix bytes.
|
|
767
|
+
* @param {string} input.prefixPath — the path used to render this round's pointer line.
|
|
830
768
|
* @returns {{ aligned: boolean, mode: "inline"|"pointer"|null, reason: string|null }}
|
|
831
769
|
*/
|
|
832
770
|
export function verifyPromptLeadingAlignment({ promptLeading, prefixBytes, prefixPath } = {}) {
|
|
@@ -848,7 +786,7 @@ export function verifyPromptLeadingAlignment({ promptLeading, prefixBytes, prefi
|
|
|
848
786
|
}
|
|
849
787
|
|
|
850
788
|
/* ------------------------------------------------------------------ *
|
|
851
|
-
* 7. Diff filtering for the shared per-head block
|
|
789
|
+
* 7. Diff filtering for the shared per-head block
|
|
852
790
|
* ------------------------------------------------------------------ */
|
|
853
791
|
|
|
854
792
|
/**
|
|
@@ -869,6 +807,7 @@ export function verifyPromptLeadingAlignment({ promptLeading, prefixBytes, prefi
|
|
|
869
807
|
*/
|
|
870
808
|
export const DEFAULT_DIFF_EXCLUDE_GLOBS = Object.freeze([
|
|
871
809
|
// Lockfiles.
|
|
810
|
+
"bun.lock", "**/bun.lock",
|
|
872
811
|
"package-lock.json", "**/package-lock.json",
|
|
873
812
|
"npm-shrinkwrap.json", "**/npm-shrinkwrap.json",
|
|
874
813
|
"yarn.lock", "**/yarn.lock",
|
|
@@ -982,7 +921,7 @@ function extractDiffBlockPath(blockLines) {
|
|
|
982
921
|
|
|
983
922
|
/**
|
|
984
923
|
* Filter a unified diff (`git diff` output) down to the files that should be
|
|
985
|
-
* INLINED into a reviewer prompt's shared per-head block
|
|
924
|
+
* INLINED into a reviewer prompt's shared per-head block:
|
|
986
925
|
* lockfiles, generated/vendored trees, and any caller-configured
|
|
987
926
|
* `excludeGlobs` are dropped whole-file (header + all hunks), every other
|
|
988
927
|
* file's block passes through byte-for-byte unchanged. Excluding a file here
|
|
@@ -1,37 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* composition (issue #1468 slice 5).
|
|
2
|
+
* Additive review-lineage base + per-fix-round delta composition.
|
|
4
3
|
*
|
|
5
|
-
* A
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the whole context.
|
|
9
|
-
*
|
|
10
|
-
* Artifact model (Section E of the #1468 spec):
|
|
11
|
-
*
|
|
12
|
-
* review-lineage-base
|
|
13
|
-
* lineage identity + gate + stable contracts/instructions + original
|
|
14
|
-
* review target + original full diff.
|
|
15
|
-
*
|
|
16
|
-
* round-N-delta
|
|
17
|
-
* exact base/reviewed SHAs + the fix diff + validation evidence + an
|
|
18
|
-
* independent findings verification checklist.
|
|
4
|
+
* A stable review-lineage base plus deterministic per-round delta artifacts let
|
|
5
|
+
* round 2+ append only what changed instead of rebuilding a full head-specific
|
|
6
|
+
* briefing.
|
|
19
7
|
*
|
|
20
8
|
* Composition contract:
|
|
21
|
-
*
|
|
22
|
-
* round-N request = [lineage base][delta 1][delta 2]...[delta N][angle suffix]
|
|
9
|
+
* round-N request = [lineage base][delta 1]...[delta N][angle suffix]
|
|
23
10
|
*
|
|
24
11
|
* Composition is append-only and byte-deterministic: the composed request is
|
|
25
12
|
* the ordered concatenation of the lineage base and the individual delta
|
|
26
|
-
* artifacts, never a parse/reserialize of the full PR context
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* "does not rebuild the full PR context" test asserts.
|
|
13
|
+
* artifacts, never a parse/reserialize of the full PR context. Round N+1 appends
|
|
14
|
+
* exactly one new delta segment; every prior segment is byte-identical (same
|
|
15
|
+
* ref + same hash).
|
|
30
16
|
*
|
|
31
|
-
* Carry-forward
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* stays in gate-carry-forward.mjs) and it never fabricates a verdict.
|
|
17
|
+
* Carry-forward provenance (original reviewer + prior head of a carried clean
|
|
18
|
+
* angle) is preserved in the composed request but not decided here (that stays
|
|
19
|
+
* in gate-carry-forward.mjs); a verdict is never fabricated.
|
|
35
20
|
*
|
|
36
21
|
* This module is pure and offline: no GitHub, no harness, no clock.
|
|
37
22
|
*/
|
|
@@ -226,26 +211,16 @@ export const ANGLE_SUFFIX_SLOT = "angleSuffix";
|
|
|
226
211
|
|
|
227
212
|
/**
|
|
228
213
|
* Compose the round-N request as an append-only ordered segment list:
|
|
229
|
-
*
|
|
230
214
|
* [lineage base][delta 1]...[delta N][angle suffix]
|
|
231
215
|
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
* base + prior deltas) rather than rebuilding the full PR context, and this
|
|
237
|
-
* function's contract + tests pin that reuse property.
|
|
238
|
-
*
|
|
239
|
-
* Segments before the new delta are byte-identical to the prior round's
|
|
240
|
-
* (same `ref` + same `hash`), which is the mechanical proof of append-only
|
|
241
|
-
* composition (AC-2: does not rebuild the full PR context as a replacement
|
|
242
|
-
* block).
|
|
216
|
+
* Returned `segments` carry individual artifact bytes + hashes; a consumer
|
|
217
|
+
* renders the request by concatenating segment bytes IN ORDER. Round N+1 appends
|
|
218
|
+
* exactly one new delta segment and every earlier segment is byte-identical to
|
|
219
|
+
* the prior round's (same `ref` + same `hash`).
|
|
243
220
|
*
|
|
244
|
-
* `carriedAngles` provenance is preserved unchanged
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* fabricated. A carried clean angle keeps exactly the original reviewer and
|
|
248
|
-
* prior head it was recorded with.
|
|
221
|
+
* `carriedAngles` provenance is preserved unchanged: each entry
|
|
222
|
+
* { angle, originalReviewer, priorHead } is folded into the composed hash so a
|
|
223
|
+
* carried angle's provenance is pinned, never fabricated.
|
|
249
224
|
*
|
|
250
225
|
* @param {object} input
|
|
251
226
|
* @param {object} input.lineageBase - a valid review-lineage-base artifact.
|
|
@@ -384,7 +359,7 @@ export function renderComposedRequest(composed) {
|
|
|
384
359
|
}
|
|
385
360
|
|
|
386
361
|
/* ------------------------------------------------------------------ *
|
|
387
|
-
* Compaction / rebase policy
|
|
362
|
+
* Compaction / rebase policy
|
|
388
363
|
* ------------------------------------------------------------------ */
|
|
389
364
|
|
|
390
365
|
/**
|