@dev-loops/core 1.0.0 → 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.
@@ -3,6 +3,11 @@ import { findBlockingTitleMarkers } from "./pr-title-markers.mjs";
3
3
  import { evaluateUiE2eScoping } from "./ui-e2e-scoping.mjs";
4
4
  import { evaluateUiDesignerReviewScoping } from "./ui-designer-review-scoping.mjs";
5
5
  import { trimmedOrNull } from "./normalize.mjs";
6
+ import {
7
+ MALFORMED_AC_DOD_MATRIX_FINDING,
8
+ MISSING_AC_DOD_MATRIX_FINDING,
9
+ MISSING_EXPLICIT_NON_GOALS_FINDING,
10
+ } from "./issue-refinement-artifact.mjs";
6
11
 
7
12
  export const PR_CHECKPOINT = Object.freeze({
8
13
  DRAFT_REVIEW: "draft_review",
@@ -241,15 +246,35 @@ function normalizeRefinementArtifactStatus(value) {
241
246
  // their own validation-failure reason from the detector; that reason must
242
247
  // replace the "linked issue" wording, which does not apply when the PR is the
243
248
  // spec-of-record and no linked issue was ever expected.
249
+ // #1951 matrix-floor vocabulary for the draft-gate blocked reason: which piece
250
+ // of the refinement floor is missing per finding — the SAME finding taxonomy
251
+ // the enqueue gate's guidance (decideEnqueueRefinementGate) and the detector
252
+ // (detectIssueRefinementArtifact) use, so the draft gate (the unconditional
253
+ // backstop for that floor) cannot drift from it. A checklist-only or
254
+ // matrix-malformed linked issue names the actually-missing/invalid artifact so
255
+ // the fix is not misdirected.
256
+ const REFINEMENT_MISSING_ARM_BY_FINDING = Object.freeze({
257
+ [MISSING_AC_DOD_MATRIX_FINDING]: "an AC→DoD mapping matrix (a two-column table mapping each acceptance-criterion outcome to its required completion evidence)",
258
+ [MALFORMED_AC_DOD_MATRIX_FINDING]: "a valid AC→DoD mapping matrix (the existing table is empty or identifier-only/tautological)",
259
+ [MISSING_EXPLICIT_NON_GOALS_FINDING]: "an explicit Non-goals section",
260
+ });
261
+
244
262
  function formatRefinementBlockedReason(linkedIssue, status, refinementArtifact) {
245
263
  const specSource = refinementArtifact?.specSource;
246
264
  if (specSource != null && specSource !== REFINEMENT_ARTIFACT_SPEC_SOURCE.LINKED_ISSUE && typeof refinementArtifact?.reason === "string" && refinementArtifact.reason.length > 0) {
247
265
  return `The draft gate cannot complete: ${refinementArtifact.reason} finding=${REFINEMENT_ARTIFACT_FINDING}`;
248
266
  }
267
+ const finding = typeof refinementArtifact?.finding === "string" && refinementArtifact.finding.length > 0
268
+ ? refinementArtifact.finding
269
+ : REFINEMENT_ARTIFACT_FINDING;
270
+ const missingArm = REFINEMENT_MISSING_ARM_BY_FINDING[finding];
271
+ if (missingArm !== undefined) {
272
+ return `Linked issue #${linkedIssue} has an incomplete refinement matrix — it is missing ${missingArm}. Add it to the issue body to complete the AC→DoD mapping matrix + Non-goals refinement floor, then re-open the draft PR. finding=${finding}`;
273
+ }
249
274
  if (linkedIssue !== null && Number.isInteger(linkedIssue)) {
250
- return `Linked issue #${linkedIssue} has no refinement artifact (Acceptance criteria / DoD / linked refinement doc). Run refinement first, add ACs/DoD to the issue, then re-open the draft PR. finding=${REFINEMENT_ARTIFACT_FINDING}`;
275
+ return `Linked issue #${linkedIssue} has no refinement artifact (no AC→DoD mapping matrix, or a resolvable linked refinement doc). Refine the issue to the authoritative AC→DoD mapping matrix plus an explicit Non-goals section — or link a refinement doc (tmp/refinement/*.md), a complete artifact on its own — then re-open the draft PR. finding=${REFINEMENT_ARTIFACT_FINDING}`;
251
276
  }
252
- return `The draft gate cannot complete: the linked issue has no detectable refinement artifact (Acceptance criteria / DoD / linked refinement doc). finding=${REFINEMENT_ARTIFACT_FINDING}`;
277
+ return `The draft gate cannot complete: the linked issue has no detectable refinement artifact (no AC→DoD mapping matrix, or a resolvable linked refinement doc). finding=${REFINEMENT_ARTIFACT_FINDING}`;
253
278
  }
254
279
 
255
280
  // #1472: describes the CI state a round-cap-reached fallback branch actually
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { parse as parseYaml } from "yaml";
4
4
  import { main as moveQueueItemMain } from "../projects/move-queue-item.mjs";
5
- import { ghGraphql } from "../github/gh.mjs";
5
+ import { ghGraphql, resolveOwner } from "../github/gh.mjs";
6
6
 
7
7
  const DEFAULT_NON_SUCCESS_COLUMN = "Backlog";
8
8
 
@@ -172,9 +172,8 @@ function readDevloopsSettings(repoRoot) {
172
172
  try {
173
173
  const raw = readFileSync(base + ext, "utf8");
174
174
  const settings = ext === ".json" ? JSON.parse(raw) : parseYaml(raw);
175
- // `tracker` (issue #1408, the tracker-agnostic seam) is surfaced
176
- // alongside `queue` so loadBoardConfig can prefer tracker.board over
177
- // the deprecated queue.board without a second file read.
175
+ // `tracker` (issue #1408, the tracker-agnostic seam) is surfaced so
176
+ // loadBoardConfig can read tracker.board directly.
178
177
  return { settings: settings?.queue ?? null, tracker: settings?.tracker ?? null };
179
178
  } catch (err) {
180
179
  if (err?.code === "ENOENT") {
@@ -204,18 +203,15 @@ function boardSelector(board) {
204
203
  }
205
204
 
206
205
  export function loadBoardConfig(repoRoot) {
207
- const { settings: queue, tracker, error } = readDevloopsSettings(repoRoot);
206
+ const { tracker, error } = readDevloopsSettings(repoRoot);
208
207
  if (error) {
209
208
  return { enabled: false, reason: `config read/parse error: ${error}` };
210
209
  }
211
- // tracker.board (canonical) takes priority over the deprecated queue.board
212
- // (issue #1408) — see resolveTrackerBoard in ../config/config.mjs for the
213
- // equivalent resolution against the validated, loaded config.
210
+ // tracker.board (canonical board key, issue #1408) — see resolveTrackerBoard
211
+ // in ../config/config.mjs for the equivalent resolution against the
212
+ // validated, loaded config.
214
213
  const trackerBoard = boardSelector(tracker?.board);
215
214
  if (trackerBoard) return trackerBoard;
216
- if (!queue) return { enabled: false };
217
- const queueBoard = boardSelector(queue.board);
218
- if (queueBoard) return queueBoard;
219
215
  return { enabled: false };
220
216
  }
221
217
 
@@ -281,18 +277,6 @@ export function loadStateColumnMap(repoRoot) {
281
277
 
282
278
  // ── Minimal project lookup (read-only, no create/repair) ────────────────
283
279
 
284
- const GET_USER_ID = [
285
- "query($login:String!) {",
286
- " user(login:$login) { id }",
287
- "}"
288
- ].join("\n");
289
-
290
- const GET_ORG_ID = [
291
- "query($login:String!) {",
292
- " organization(login:$login) { id }",
293
- "}"
294
- ].join("\n");
295
-
296
280
  const LIST_USER_PROJECTS = [
297
281
  "query($login:String!, $after:String) {",
298
282
  " user(login:$login) {",
@@ -315,21 +299,6 @@ const LIST_ORG_PROJECTS = [
315
299
  "}"
316
300
  ].join("\n");
317
301
 
318
- async function resolveOwner(login, env, runChild) {
319
- const userPayload = await ghGraphql(GET_USER_ID, { login }, env, runChild);
320
- if (userPayload?.data?.user?.id) {
321
- return { id: userPayload.data.user.id, kind: "user" };
322
- }
323
- const orgPayload = await ghGraphql(GET_ORG_ID, { login }, env, runChild);
324
- if (orgPayload?.data?.organization?.id) {
325
- return { id: orgPayload.data.organization.id, kind: "org" };
326
- }
327
- throw Object.assign(
328
- new Error(`Could not resolve owner ID for "${login}"`),
329
- { code: "NO_USER_ID" },
330
- );
331
- }
332
-
333
302
  async function listAllProjects(login, kind, env, runChild) {
334
303
  const query = kind === "org" ? LIST_ORG_PROJECTS : LIST_USER_PROJECTS;
335
304
  const projects = [];
@@ -869,6 +869,7 @@ export function verifyPromptLeadingAlignment({ promptLeading, prefixBytes, prefi
869
869
  */
870
870
  export const DEFAULT_DIFF_EXCLUDE_GLOBS = Object.freeze([
871
871
  // Lockfiles.
872
+ "bun.lock", "**/bun.lock",
872
873
  "package-lock.json", "**/package-lock.json",
873
874
  "npm-shrinkwrap.json", "**/npm-shrinkwrap.json",
874
875
  "yarn.lock", "**/yarn.lock",