@dev-loops/core 1.0.1 → 1.0.2-pre.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 +4 -2
- package/src/claude/hook-decisions.mjs +144 -20
- package/src/config/extension-defaults.yaml +14 -9
- package/src/github/comment-id-guard.mjs +38 -0
- package/src/github/gh.mjs +49 -0
- package/src/loop/commit-msg-guard.mjs +1 -1
- package/src/loop/gate-carry-forward.mjs +45 -21
- package/src/loop/gate-evidence-reconcile.mjs +75 -0
- package/src/loop/gate-fanin.mjs +34 -13
- package/src/loop/handoff-envelope.mjs +2 -2
- package/src/loop/issue-refinement-artifact.mjs +358 -99
- package/src/loop/pr-gate-coordination.mjs +13 -13
- package/src/loop/queue-board-sync.mjs +1 -28
- package/src/loop/review-dispatch-plan.mjs +1 -0
- package/src/loop/spec-authority.mjs +759 -0
- package/src/loop/ui-e2e-scoping.mjs +1 -0
- package/src/loop/worktree-guard.mjs +55 -0
- package/src/projects/list-queue-items.mjs +1 -30
- package/src/projects/move-queue-item.mjs +1 -30
- package/src/security/secret-scan.mjs +13 -1
package/src/loop/gate-fanin.mjs
CHANGED
|
@@ -287,8 +287,8 @@ export function severityRank(severity) {
|
|
|
287
287
|
/**
|
|
288
288
|
* A zero-initialized severity→count map, one key per SEVERITY_ORDER entry, in
|
|
289
289
|
* SEVERITY_ORDER's order. The shared starting point every severity tally in
|
|
290
|
-
* this codebase (consolidateFanin's own `bySeverity`,
|
|
291
|
-
*
|
|
290
|
+
* this codebase (consolidateFanin's own `bySeverity`,
|
|
291
|
+
* reconcile-draft-gate.mjs's no-findings placeholder) used
|
|
292
292
|
* to hand-roll separately via `Object.fromEntries(SEVERITY_ORDER.map((s) =>
|
|
293
293
|
* [s, 0]))` — one copy here means a severity added to SEVERITY_ORDER is
|
|
294
294
|
* zero-initialized everywhere at once.
|
|
@@ -305,10 +305,9 @@ export function zeroSeverityCounts() {
|
|
|
305
305
|
* finding whose normalized severity is not a recognized SEVERITY_ORDER member
|
|
306
306
|
* is silently excluded from the tally rather than inflating an unknown key —
|
|
307
307
|
* every routed call site here counts already-validated findings in practice
|
|
308
|
-
* (consolidateFanin validates every result's severity before this runs
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
* unvalidated severities. `findings` and its entries are NOT nullish-tolerant:
|
|
308
|
+
* (consolidateFanin validates every result's severity before this runs), so
|
|
309
|
+
* this guard is a defensive floor against future drift, not an escape hatch
|
|
310
|
+
* for accepting unvalidated severities. `findings` and its entries are NOT nullish-tolerant:
|
|
312
311
|
* a nullish `findings` argument throws (not iterable), and a nullish
|
|
313
312
|
* individual entry throws reading `.severity` — no routed caller passes
|
|
314
313
|
* either shape, so a caller that does gets a loud failure instead of a
|
|
@@ -342,6 +341,30 @@ export function normalizeSeverityCounts(counts) {
|
|
|
342
341
|
return normalized;
|
|
343
342
|
}
|
|
344
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Resolve a finding's effective file path from EITHER shape the shared floor
|
|
346
|
+
* accepts: `file` (singular string, hand-authored / future-producer ledgers) or
|
|
347
|
+
* `files[0]` (the array shape consolidate-fanin / write-gate-findings-log emit).
|
|
348
|
+
* The ONE resolver `hasLocatableShape` and every downstream consumer keys on,
|
|
349
|
+
* so a consumer reading `finding.files[0]` directly can never crash on a
|
|
350
|
+
* singular-`file` finding that already passed the floor. The value is TRIMMED
|
|
351
|
+
* and a whitespace-only `file` is treated as ABSENT (it falls back to
|
|
352
|
+
* `files[0]`, not shadows it), so the resolved path both matches the trimmed
|
|
353
|
+
* commentable-line-set keys and is a valid GitHub review-comment `path` even
|
|
354
|
+
* for an untrimmed hand-authored ledger entry (`readGateFindingsLedger` trims
|
|
355
|
+
* `files[]` but not a singular `file`). Returns `undefined` when neither shape
|
|
356
|
+
* names a non-blank string path.
|
|
357
|
+
* @param {{ file?: unknown, files?: unknown }} finding
|
|
358
|
+
* @returns {string|undefined}
|
|
359
|
+
*/
|
|
360
|
+
export function resolveFindingFile(finding) {
|
|
361
|
+
if (typeof finding?.file === "string" && finding.file.trim().length > 0) return finding.file.trim();
|
|
362
|
+
if (Array.isArray(finding?.files) && typeof finding.files[0] === "string" && finding.files[0].trim().length > 0) {
|
|
363
|
+
return finding.files[0].trim();
|
|
364
|
+
}
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
367
|
+
|
|
345
368
|
/**
|
|
346
369
|
* A finding is LOCATABLE-SHAPED when it names a real file (via `file` or
|
|
347
370
|
* `files[0]`) and a positive-integer `line` — the ONE shared shape check
|
|
@@ -358,9 +381,7 @@ export function normalizeSeverityCounts(counts) {
|
|
|
358
381
|
* @returns {boolean}
|
|
359
382
|
*/
|
|
360
383
|
export function hasLocatableShape(finding) {
|
|
361
|
-
const file =
|
|
362
|
-
? finding.file
|
|
363
|
-
: (Array.isArray(finding?.files) ? finding.files[0] : undefined);
|
|
384
|
+
const file = resolveFindingFile(finding);
|
|
364
385
|
return typeof file === "string" && file.trim().length > 0
|
|
365
386
|
&& Number.isInteger(finding?.line) && /** @type {number} */ (finding.line) >= 1;
|
|
366
387
|
}
|
|
@@ -696,7 +717,7 @@ export function fanoutReviewerPairingError(perAngle, resolvedGroups = null) {
|
|
|
696
717
|
|
|
697
718
|
/**
|
|
698
719
|
* Base angle name for a delta-suffixed re-review entry (`<angle>-delta-at-...`,
|
|
699
|
-
* e.g. `pr-checklist-
|
|
720
|
+
* e.g. `pr-checklist-delta-at-current-head`): a re-review scoped to only
|
|
700
721
|
* the current head's delta still counts toward its base angle for both
|
|
701
722
|
* mandatory-angle coverage and pool-membership checks.
|
|
702
723
|
*
|
|
@@ -742,12 +763,12 @@ export function checkFanoutAngleCoverage(recordedAngles, { mandatoryAngles = [],
|
|
|
742
763
|
|
|
743
764
|
/**
|
|
744
765
|
* Angles the fan-in itself mandates and may synthesize (consolidate-fanin's
|
|
745
|
-
* `--pr-checklist
|
|
766
|
+
* `--pr-checklist clean` upsert) without them appearing in any gate's
|
|
746
767
|
* configured `angles` pool. Always legal in the foreign-angle check above —
|
|
747
768
|
* requiring every consumer repo to also list them per-gate would make the two
|
|
748
769
|
* tools contradict the shared contract they implement.
|
|
749
770
|
*/
|
|
750
|
-
export const FANIN_SYNTHETIC_ANGLES = Object.freeze(["pr-checklist
|
|
771
|
+
export const FANIN_SYNTHETIC_ANGLES = Object.freeze(["pr-checklist"]);
|
|
751
772
|
|
|
752
773
|
/**
|
|
753
774
|
* Validate a round's RESOLVED angle set — the full angle list the round
|
|
@@ -813,7 +834,7 @@ export function checkResolvedAngleEvidence(resolvedAngles, { recordedAngles, car
|
|
|
813
834
|
export const DEFAULT_MAX_FANOUT_REVIEWERS = 8;
|
|
814
835
|
|
|
815
836
|
// Every sanctioned angle name is a short, hand-authored slug (e.g.
|
|
816
|
-
// "contradiction-lens", "pr-checklist
|
|
837
|
+
// "contradiction-lens", "pr-checklist"); nothing legitimate ever
|
|
817
838
|
// approaches this length. Bounding it here, at the trust boundary this
|
|
818
839
|
// function already owns, fails a pathological artifact closed as malformed —
|
|
819
840
|
// the same place every other angle-result defect is caught — instead of
|
|
@@ -123,7 +123,7 @@ register(INTERNAL_DEV_LOOP_STRATEGY.FINAL_APPROVAL, "default", {
|
|
|
123
123
|
register(INTERNAL_DEV_LOOP_STRATEGY.LOCAL_IMPLEMENTATION, "default", {
|
|
124
124
|
criteria: [
|
|
125
125
|
{ id: "phase-ac", must: "All phase acceptance criteria from the active phase doc are satisfied.", severity: "required" },
|
|
126
|
-
{ id: "verify-green", must: "`
|
|
126
|
+
{ id: "verify-green", must: "`bun run verify` passes with no failures.", severity: "required" },
|
|
127
127
|
],
|
|
128
128
|
evidence: ["commands-run", "validation-output", "changed-files"],
|
|
129
129
|
maxFinalizationTurns: 6,
|
|
@@ -138,7 +138,7 @@ register(INTERNAL_DEV_LOOP_STRATEGY.LOCAL_IMPLEMENTATION, "default", {
|
|
|
138
138
|
register(INTERNAL_DEV_LOOP_STRATEGY.LOCAL_IMPLEMENTATION, "spike", {
|
|
139
139
|
criteria: [
|
|
140
140
|
{ id: "spike-recorded", must: "The spike exploration and its recommendation are recorded (spike file + summary).", severity: "required" },
|
|
141
|
-
{ id: "verify-green", must: "`
|
|
141
|
+
{ id: "verify-green", must: "`bun run verify` passes with no failures.", severity: "required" },
|
|
142
142
|
],
|
|
143
143
|
evidence: ["commands-run", "validation-output", "changed-files"],
|
|
144
144
|
maxFinalizationTurns: 6,
|