@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.
@@ -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`, consolidate-fanin.mjs's
291
- * `buildAngleMarker`, reconcile-draft-gate.mjs's no-findings placeholder) used
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
- * buildAngleMarker tallies consolidateFanin's own output), so this guard is a
310
- * defensive floor against future drift, not an escape hatch for accepting
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 = typeof finding?.file === "string"
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-matrix-delta-at-current-head`): a re-review scoped to only
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-matrix clean` upsert) without them appearing in any gate's
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-matrix"]);
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-matrix"); nothing legitimate ever
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: "`npm run verify` passes with no failures.", severity: "required" },
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: "`npm run verify` passes with no failures.", severity: "required" },
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,