dev-loops 0.6.0 → 0.7.2

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.
Files changed (203) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/agents/dev-loop.md +8 -7
  3. package/.claude/agents/fixer.md +1 -0
  4. package/.claude/agents/refiner.md +2 -2
  5. package/.claude/agents/review.md +4 -3
  6. package/.claude/commands/{auto.md → loop-auto.md} +1 -1
  7. package/.claude/commands/loop-continue.md +15 -0
  8. package/.claude/commands/loop-enqueue.md +22 -0
  9. package/.claude/commands/loop-grill.md +17 -0
  10. package/.claude/commands/{info.md → loop-info.md} +2 -2
  11. package/.claude/commands/loop-queue-status.md +24 -0
  12. package/.claude/commands/{start-spike.md → loop-start-spike.md} +2 -2
  13. package/.claude/commands/{start.md → loop-start.md} +1 -1
  14. package/.claude/commands/{status.md → loop-status.md} +1 -1
  15. package/.claude/hooks/_bash-command-classify.mjs +333 -29
  16. package/.claude/hooks/_hook-decisions.mjs +138 -15
  17. package/.claude/hooks/pre-tool-use-bash-gate.mjs +36 -15
  18. package/.claude/skills/copilot-pr-followup/SKILL.md +60 -34
  19. package/.claude/skills/dev-loop/SKILL.md +16 -12
  20. package/.claude/skills/docs/acceptance-criteria-verification.md +8 -3
  21. package/.claude/skills/docs/anti-patterns.md +5 -3
  22. package/.claude/skills/docs/artifact-authority-contract.md +30 -11
  23. package/.claude/skills/docs/confirmation-rules.md +1 -0
  24. package/.claude/skills/docs/contract-style-guide.md +37 -0
  25. package/.claude/skills/docs/copilot-loop-operations.md +19 -15
  26. package/.claude/skills/docs/cross-harness-regression-contract.md +60 -0
  27. package/.claude/skills/docs/debt-remediation-contract.md +1 -1
  28. package/.claude/skills/docs/epic-tree-refinement-procedure.md +25 -22
  29. package/.claude/skills/docs/issue-intake-procedure.md +26 -19
  30. package/.claude/skills/docs/local-planning-flow.md +1 -1
  31. package/.claude/skills/docs/local-planning-worked-example.md +1 -1
  32. package/.claude/skills/docs/merge-preconditions.md +30 -12
  33. package/.claude/skills/docs/plan-file-contract.md +1 -1
  34. package/.claude/skills/docs/pr-lifecycle-contract.md +44 -35
  35. package/.claude/skills/docs/public-dev-loop-contract.md +60 -44
  36. package/.claude/skills/docs/retrospective-checkpoint-contract.md +106 -79
  37. package/.claude/skills/docs/spike-mode-contract.md +14 -7
  38. package/.claude/skills/docs/stop-conditions.md +30 -15
  39. package/.claude/skills/docs/tracker-first-loop-state.md +13 -8
  40. package/.claude/skills/docs/ui-e2e-scoping-step.md +32 -0
  41. package/.claude/skills/docs/validation-policy.md +4 -5
  42. package/.claude/skills/docs/workflow-handoff-contract.md +39 -0
  43. package/.claude/skills/local-implementation/SKILL.md +62 -145
  44. package/.claude/skills/loop-grill/SKILL.md +163 -0
  45. package/AGENTS.md +1 -0
  46. package/CHANGELOG.md +101 -0
  47. package/README.md +9 -9
  48. package/agents/dev-loop.agent.md +11 -10
  49. package/agents/developer.agent.md +1 -1
  50. package/agents/docs.agent.md +1 -1
  51. package/agents/fixer.agent.md +2 -1
  52. package/agents/quality.agent.md +1 -1
  53. package/agents/refiner.agent.md +3 -3
  54. package/agents/review.agent.md +5 -4
  55. package/cli/index.mjs +35 -42
  56. package/extension/README.md +4 -4
  57. package/extension/checks.ts +1 -5
  58. package/extension/harness-types.ts +1 -0
  59. package/extension/index.ts +6 -0
  60. package/extension/pi-extension-adapter.ts +2 -0
  61. package/extension/presentation.ts +7 -19
  62. package/package.json +12 -12
  63. package/scripts/_core-helpers.mjs +2 -0
  64. package/scripts/claude/headless-dev-loop.mjs +1 -1
  65. package/scripts/docs/validate-rule-ownership.mjs +442 -0
  66. package/scripts/docs/validate-state-machine-conformance.mjs +1092 -0
  67. package/scripts/github/_review-thread-mutations.mjs +5 -2
  68. package/scripts/github/capture-review-threads.mjs +2 -9
  69. package/scripts/github/comment-issue.mjs +2 -9
  70. package/scripts/github/create-label.mjs +133 -0
  71. package/scripts/github/create-pr.mjs +119 -10
  72. package/scripts/github/detect-checkpoint-evidence.mjs +259 -18
  73. package/scripts/github/detect-linked-issue-pr.mjs +22 -6
  74. package/scripts/github/edit-pr.mjs +259 -0
  75. package/scripts/github/fetch-ci-logs.mjs +2 -9
  76. package/scripts/github/list-issues.mjs +2 -9
  77. package/scripts/github/list-review-threads.mjs +277 -0
  78. package/scripts/github/manage-sub-issues.mjs +46 -10
  79. package/scripts/github/offer-human-handoff.mjs +8 -5
  80. package/scripts/github/post-gate-findings.mjs +20 -5
  81. package/scripts/github/probe-ci-status.mjs +8 -2
  82. package/scripts/github/probe-copilot-review.mjs +21 -14
  83. package/scripts/github/ready-for-review.mjs +26 -8
  84. package/scripts/github/reconcile-draft-gate.mjs +7 -3
  85. package/scripts/github/reply-resolve-review-thread.mjs +16 -5
  86. package/scripts/github/reply-resolve-review-threads.mjs +69 -7
  87. package/scripts/github/request-copilot-review.mjs +98 -26
  88. package/scripts/github/resolve-handoff-candidates.mjs +7 -3
  89. package/scripts/github/resolve-tracker-local-spec.mjs +9 -3
  90. package/scripts/github/stage-reviewer-draft.mjs +10 -2
  91. package/scripts/github/tick-verified-checkboxes.mjs +202 -0
  92. package/scripts/github/upsert-checkpoint-verdict.mjs +105 -15
  93. package/scripts/github/verify-briefing-prefixes.mjs +191 -0
  94. package/scripts/github/verify-fresh-review-context.mjs +226 -32
  95. package/scripts/github/view-pr.mjs +150 -0
  96. package/scripts/github/wait-pr-checks.mjs +171 -0
  97. package/scripts/github/write-gate-context.mjs +458 -65
  98. package/scripts/github/write-gate-findings-log.mjs +128 -2
  99. package/scripts/lib/jq-output.mjs +18 -0
  100. package/scripts/loop/_post-convergence-change.mjs +211 -0
  101. package/scripts/loop/_pr-runner-coordination.mjs +70 -0
  102. package/scripts/loop/_repo-root-resolver.mjs +47 -0
  103. package/scripts/loop/build-handoff-envelope.mjs +14 -4
  104. package/scripts/loop/check-retro-tooling.mjs +14 -3
  105. package/scripts/loop/checkpoint-contract.mjs +7 -4
  106. package/scripts/loop/cleanup-worktree.mjs +12 -3
  107. package/scripts/loop/conductor-monitor.mjs +12 -18
  108. package/scripts/loop/copilot-pr-handoff.mjs +162 -14
  109. package/scripts/loop/debt-remediate.mjs +24 -12
  110. package/scripts/loop/detect-change-scope.mjs +38 -9
  111. package/scripts/loop/detect-copilot-loop-state.mjs +34 -8
  112. package/scripts/loop/detect-copilot-session-activity.mjs +7 -3
  113. package/scripts/loop/detect-initial-copilot-pr-state.mjs +7 -3
  114. package/scripts/loop/detect-internal-only-pr.mjs +8 -2
  115. package/scripts/loop/detect-issue-refinement-artifact.mjs +6 -3
  116. package/scripts/loop/detect-pr-gate-coordination-state.mjs +184 -69
  117. package/scripts/loop/detect-reviewer-loop-state.mjs +12 -2
  118. package/scripts/loop/detect-tracker-first-loop-state.mjs +9 -1
  119. package/scripts/loop/detect-tracker-pr-state.mjs +10 -3
  120. package/scripts/loop/ensure-worktree.mjs +8 -3
  121. package/scripts/loop/info.mjs +12 -4
  122. package/scripts/loop/inspect-run-viewer/constants.mjs +4 -18
  123. package/scripts/loop/inspect-run-viewer/vendor/mermaid.min.js +3405 -0
  124. package/scripts/loop/inspect-run-viewer-ci-changes.mjs +18 -6
  125. package/scripts/loop/inspect-run-viewer.mjs +67 -4
  126. package/scripts/loop/inspect-run.mjs +8 -2
  127. package/scripts/loop/outer-loop.mjs +8 -4
  128. package/scripts/loop/pr-runner-coordination.mjs +2 -9
  129. package/scripts/loop/pre-commit-branch-guard.mjs +16 -10
  130. package/scripts/loop/pre-flight-gate.mjs +9 -9
  131. package/scripts/loop/pre-pr-ready-gate.mjs +8 -4
  132. package/scripts/loop/pre-write-remote-freshness-guard.mjs +13 -4
  133. package/scripts/loop/provision-worktree.mjs +74 -3
  134. package/scripts/loop/resolve-dev-loop-startup.mjs +216 -10
  135. package/scripts/loop/resolve-gate-dispatch.mjs +134 -0
  136. package/scripts/loop/resolve-pr-conflicts.mjs +14 -7
  137. package/scripts/loop/run-conductor-cycle.mjs +8 -2
  138. package/scripts/loop/run-queue.mjs +18 -9
  139. package/scripts/loop/run-refinement-audit.mjs +8 -9
  140. package/scripts/loop/run-watch-cycle.mjs +2 -9
  141. package/scripts/loop/sanctioned-commands.mjs +106 -0
  142. package/scripts/loop/steer-loop.mjs +29 -16
  143. package/scripts/loop/validate-pr-body-spec.mjs +223 -0
  144. package/scripts/loop/watch-initial-copilot-pr.mjs +8 -3
  145. package/scripts/pages/build-site.mjs +59 -8
  146. package/scripts/pages/build-state-atlas.mjs +443 -0
  147. package/scripts/projects/_resolve-project.mjs +1 -0
  148. package/scripts/projects/add-queue-item.mjs +60 -54
  149. package/scripts/projects/archive-done-items.mjs +49 -84
  150. package/scripts/projects/ensure-queue-board.mjs +10 -36
  151. package/scripts/projects/list-queue-items.mjs +59 -382
  152. package/scripts/projects/move-queue-item.mjs +21 -449
  153. package/scripts/projects/reconcile-queue.mjs +253 -0
  154. package/scripts/projects/reorder-queue-item.mjs +43 -68
  155. package/scripts/projects/resolve-active-board-item.mjs +108 -46
  156. package/scripts/projects/sync-item-status.mjs +15 -10
  157. package/scripts/refine/_refine-helpers.mjs +21 -5
  158. package/scripts/refine/exit-spike.mjs +18 -8
  159. package/scripts/refine/promote-plan.mjs +22 -16
  160. package/scripts/refine/prose-linkage-detector.mjs +1 -1
  161. package/scripts/refine/refine-plan-file.mjs +13 -4
  162. package/scripts/refine/refinement-completeness-checker.mjs +1 -1
  163. package/scripts/refine/scaffold-spike-file.mjs +4 -8
  164. package/scripts/refine/scope-boundary-cross-checker.mjs +1 -1
  165. package/scripts/refine/tree-integrity-validator.mjs +1 -1
  166. package/scripts/refine/validate-plan-file.mjs +1 -1
  167. package/scripts/refine/validate-spike-file.mjs +1 -1
  168. package/scripts/refine/verify.mjs +11 -6
  169. package/scripts/release/assert-core-dependency-version.mjs +123 -0
  170. package/scripts/release/extract-changelog-section.mjs +16 -2
  171. package/skills/copilot-pr-followup/SKILL.md +60 -34
  172. package/skills/dev-loop/SKILL.md +11 -7
  173. package/skills/docs/acceptance-criteria-verification.md +8 -3
  174. package/skills/docs/anti-patterns.md +5 -3
  175. package/skills/docs/artifact-authority-contract.md +30 -11
  176. package/skills/docs/confirmation-rules.md +1 -0
  177. package/skills/docs/contract-style-guide.md +37 -0
  178. package/skills/docs/copilot-loop-operations.md +19 -15
  179. package/skills/docs/cross-harness-regression-contract.md +60 -0
  180. package/skills/docs/debt-remediation-contract.md +1 -1
  181. package/skills/docs/epic-tree-refinement-procedure.md +25 -22
  182. package/skills/docs/issue-intake-procedure.md +26 -19
  183. package/skills/docs/local-planning-flow.md +1 -1
  184. package/skills/docs/local-planning-worked-example.md +1 -1
  185. package/skills/docs/merge-preconditions.md +30 -12
  186. package/skills/docs/plan-file-contract.md +1 -1
  187. package/skills/docs/pr-lifecycle-contract.md +44 -35
  188. package/skills/docs/public-dev-loop-contract.md +60 -44
  189. package/skills/docs/required-rules.json +149 -0
  190. package/skills/docs/retrospective-checkpoint-contract.md +106 -79
  191. package/skills/docs/spike-mode-contract.md +14 -7
  192. package/skills/docs/stop-conditions.md +30 -15
  193. package/skills/docs/tracker-first-loop-state.md +13 -8
  194. package/skills/docs/ui-e2e-scoping-step.md +32 -0
  195. package/skills/docs/validation-policy.md +4 -5
  196. package/skills/docs/workflow-handoff-contract.md +39 -0
  197. package/skills/local-implementation/SKILL.md +62 -145
  198. package/skills/loop-grill/SKILL.md +165 -0
  199. package/.claude/commands/continue.md +0 -15
  200. package/scripts/docs/validate-no-duplicate-rules.mjs +0 -250
  201. package/scripts/loop/conductor.mjs +0 -233
  202. package/scripts/loop/detect-stale-runner.mjs +0 -265
  203. package/scripts/loop/pre-push-main-guard.mjs +0 -117
@@ -2,6 +2,7 @@
2
2
  import { execFileSync } from "node:child_process";
3
3
  import process from "node:process";
4
4
  import { parseArgs } from "node:util";
5
+ import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
5
6
 
6
7
  const USAGE = `Usage: detect-change-scope.mjs [--base <ref>] [--head <ref>]
7
8
  Detect change scope from git diff for light-mode eligibility.
@@ -9,9 +10,13 @@ Options:
9
10
  --base <ref> Override base ref (default: HEAD~1)
10
11
  --head <ref> Override head ref; ignored unless --base is also set
11
12
  --help, -h Show this help
13
+
14
+ ${JQ_OUTPUT_USAGE}
15
+
12
16
  Exit codes:
13
17
  0 Success
14
18
  1 Error
19
+ 2 Invalid --jq filter
15
20
  `;
16
21
 
17
22
  function parseCliArgs(argv) {
@@ -21,6 +26,7 @@ function parseCliArgs(argv) {
21
26
  base: { type: "string" },
22
27
  head: { type: "string" },
23
28
  help: { type: "boolean", short: "h" },
29
+ ...JQ_OUTPUT_PARSE_OPTIONS,
24
30
  },
25
31
  allowPositionals: true,
26
32
  strict: false,
@@ -43,7 +49,9 @@ function parseCliArgs(argv) {
43
49
  }
44
50
  if (token.name === "head") {
45
51
  opts.head = token.value ?? null;
52
+ continue;
46
53
  }
54
+ if (matchJqOutputToken(token, opts)) continue;
47
55
  }
48
56
  }
49
57
  return opts;
@@ -67,7 +75,7 @@ export function parseGitDiffStat(output) {
67
75
  }
68
76
  return { filesChanged: fileCount, linesChanged: insertions + deletions };
69
77
  }
70
- function detectScope({ base, head } = {}) {
78
+ function detectScope({ base, head, cwd } = {}) {
71
79
  let diffArgs = ["diff", "--stat"];
72
80
  if (base && head) {
73
81
  diffArgs.push(`${base}..${head}`);
@@ -78,7 +86,7 @@ function detectScope({ base, head } = {}) {
78
86
  }
79
87
  let output;
80
88
  try {
81
- output = execFileSync("git", diffArgs, { encoding: "utf8", maxBuffer: 1_000_000 });
89
+ output = execFileSync("git", diffArgs, { encoding: "utf8", maxBuffer: 1_000_000, cwd: cwd || undefined });
82
90
  } catch (err) {
83
91
  return { ok: false, filesChanged: 0, linesChanged: 0, error: err instanceof Error ? err.message : String(err) };
84
92
  }
@@ -88,6 +96,27 @@ function detectScope({ base, head } = {}) {
88
96
  function isEligibleForLightMode(scope, threshold) {
89
97
  return scope.filesChanged <= threshold.maxFiles && scope.linesChanged <= threshold.maxLines;
90
98
  }
99
+ /**
100
+ * Detect change scope for the merge-base diff between `base` and `head` (the
101
+ * three-dot `base...head` diff git resolves against merge-base(base, head)).
102
+ *
103
+ * Fails CLOSED: a missing base/head, or any git failure, returns `{ ok: false }`
104
+ * so callers that gate on scope (e.g. the light-mode pre-merge acceptance) reject
105
+ * rather than silently treating an unmeasurable diff as under threshold. Reuses
106
+ * the same `parseGitDiffStat` scope resolution as `detectScope`.
107
+ */
108
+ function detectMergeBaseScope({ base, head, cwd } = {}) {
109
+ if (!base || !head) {
110
+ return { ok: false, filesChanged: 0, linesChanged: 0, error: "base and head are required for merge-base scope detection" };
111
+ }
112
+ let output;
113
+ try {
114
+ output = execFileSync("git", ["diff", "--stat", `${base}...${head}`], { encoding: "utf8", maxBuffer: 1_000_000, cwd: cwd || undefined });
115
+ } catch (err) {
116
+ return { ok: false, filesChanged: 0, linesChanged: 0, error: err instanceof Error ? err.message : String(err) };
117
+ }
118
+ return { ok: true, ...parseGitDiffStat(output) };
119
+ }
91
120
  async function main() {
92
121
  const opts = parseCliArgs(process.argv.slice(2));
93
122
  const scope = detectScope(opts);
@@ -108,12 +137,12 @@ async function main() {
108
137
  }
109
138
  } catch {
110
139
  }
111
- process.stdout.write(
112
- JSON.stringify({
113
- ...scope,
114
- eligibleForLightMode: eligible,
115
- threshold,
116
- }) + "\n"
140
+ // This tool always exits 0 on a parsed run (scope.ok:false only reflects a git
141
+ // diff failure inside the payload, never the process outcome) — force ok:true
142
+ // as emitResult's default so --jq/--silent compose without changing that.
143
+ process.exitCode = emitResult(
144
+ { ...scope, eligibleForLightMode: eligible, threshold },
145
+ { jq: opts.jq, silent: opts.silent, ok: true },
117
146
  );
118
147
  }
119
148
  const isDirectRun =
@@ -124,4 +153,4 @@ if (isDirectRun) {
124
153
  process.exitCode = 1;
125
154
  });
126
155
  }
127
- export { detectScope, isEligibleForLightMode };
156
+ export { detectScope, detectMergeBaseScope, isEligibleForLightMode };
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFile } from "node:fs/promises";
3
- import path from "node:path";
4
3
  import { parseArgs } from "node:util";
5
4
  import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
6
5
  import {
@@ -15,7 +14,7 @@ import {
15
14
  } from "../_core-helpers.mjs";
16
15
  import { fetchGithubReviewThreadsPayload } from "../github/capture-review-threads.mjs";
17
16
  import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
18
- import { loadDevLoopConfig, resolveRefinement } from "@dev-loops/core/config";
17
+ import { loadDevLoopConfig, resolveEffectiveCopilotRoundCap, resolveRefinement } from "@dev-loops/core/config";
19
18
  import {
20
19
  buildSnapshotFromPrFacts,
21
20
  interpretLoopState,
@@ -28,6 +27,8 @@ import {
28
27
  normalizeHeadScopedCommitStatus,
29
28
  normalizeHeadScopedCiContract,
30
29
  } from "@dev-loops/core/loop/copilot-ci-status";
30
+ import { resolveRepoRoot } from "./_repo-root-resolver.mjs";
31
+ import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
31
32
  const USAGE = `Usage:
32
33
  detect-copilot-loop-state.mjs --repo <owner/name> --pr <number>
33
34
  detect-copilot-loop-state.mjs --input <path>
@@ -42,8 +43,13 @@ Required (auto-detect mode):
42
43
  --pr <number> Pull request number
43
44
  Required (snapshot mode):
44
45
  --input <path> Path to snapshot JSON file
45
- Optional (auto-detect mode only):
46
- Optional (auto-detect mode only):
46
+ Optional (either mode):
47
+ --lightweight This PR is light-dispatched (#1210): compose the Copilot
48
+ round cap with localImplementation.lightMode.maxCopilotRounds
49
+ (default 1) via min(lightMode.maxCopilotRounds,
50
+ refinement.maxCopilotRounds) instead of using
51
+ refinement.maxCopilotRounds alone. refinement.maxCopilotRounds:
52
+ 0 still disables Copilot rounds even with --lightweight.
47
53
  Output (stdout, JSON):
48
54
  { "ok": true, "snapshot": {..., "copilotReviewRoundCount": N}, "state": "...", "allowedTransitions": [...], "nextAction": "...",
49
55
  "autoRerequestEligible": true|false, "sameHeadCleanConverged": true|false,
@@ -53,9 +59,11 @@ Error output (stderr, JSON):
53
59
  { "ok": false, "error": "...", "usage": "..." }
54
60
  gh/runtime failures:
55
61
  { "ok": false, "error": "..." }
62
+ ${JQ_OUTPUT_USAGE}
56
63
  Exit codes:
57
64
  0 Success
58
- 1 Argument error, gh failure, or indeterminate state`.trim();
65
+ 1 Argument error, gh failure, or indeterminate state
66
+ 2 Invalid --jq filter`.trim();
59
67
  const VALID_OVERRIDE_STATUSES = new Set(["requested", "already-requested", "unavailable", "none", "failed"]);
60
68
  const parseError = buildParseError(USAGE);
61
69
  export function parseDetectCliArgs(argv) {
@@ -64,6 +72,7 @@ export function parseDetectCliArgs(argv) {
64
72
  inputPath: undefined,
65
73
  repo: undefined,
66
74
  pr: undefined,
75
+ lightweight: false,
67
76
  };
68
77
  const { tokens } = parseArgs({
69
78
  args: [...argv],
@@ -72,6 +81,8 @@ export function parseDetectCliArgs(argv) {
72
81
  input: { type: "string" },
73
82
  repo: { type: "string" },
74
83
  pr: { type: "string" },
84
+ lightweight: { type: "boolean" },
85
+ ...JQ_OUTPUT_PARSE_OPTIONS,
75
86
  },
76
87
  allowPositionals: true,
77
88
  strict: false,
@@ -100,6 +111,11 @@ export function parseDetectCliArgs(argv) {
100
111
  options.pr = parsePrNumber(requireTokenValue(token, parseError), parseError);
101
112
  continue;
102
113
  }
114
+ if (token.name === "lightweight") {
115
+ options.lightweight = true;
116
+ continue;
117
+ }
118
+ if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
103
119
  throw parseError(`Unknown argument: ${token.rawName}`);
104
120
  }
105
121
  if (options.inputPath !== undefined) {
@@ -419,6 +435,7 @@ export async function runCli(
419
435
  argv = process.argv.slice(2),
420
436
  {
421
437
  stdout = process.stdout,
438
+ stderr = process.stderr,
422
439
  env = process.env,
423
440
  ghCommand = "gh",
424
441
  } = {},
@@ -445,13 +462,22 @@ export async function runCli(
445
462
  interpretationInput = snapshot;
446
463
  }
447
464
  let interpretation;
448
- const config = await loadDevLoopConfig({ repoRoot: path.resolve(process.cwd()) });
465
+ const config = await loadDevLoopConfig({ repoRoot: resolveRepoRoot(process.cwd()) });
449
466
  const refinementConfig = config.errors.length > 0
450
467
  ? resolveRefinement({ version: 1 })
451
468
  : resolveRefinement(config.config);
469
+ if (options.lightweight) {
470
+ // Compose (not replace) the round cap for light-dispatched PRs (#1210):
471
+ // min(lightMode.maxCopilotRounds ?? 1, refinement.maxCopilotRounds), so
472
+ // maxCopilotRounds: 0 still disables Copilot rounds everywhere.
473
+ refinementConfig.maxCopilotRounds = resolveEffectiveCopilotRoundCap(
474
+ config.errors.length > 0 ? { version: 1 } : config.config,
475
+ { lightweight: true },
476
+ );
477
+ }
452
478
  interpretation = interpretLoopState(interpretationInput, refinementConfig);
453
479
  const interpretationSummary = summarizeLoopInterpretation(interpretation);
454
- stdout.write(`${JSON.stringify({
480
+ process.exitCode = emitResult({
455
481
  ok: true,
456
482
  snapshot,
457
483
  state: interpretation.state,
@@ -461,7 +487,7 @@ export async function runCli(
461
487
  sameHeadCleanConverged: interpretation.sameHeadCleanConverged,
462
488
  loopDisposition: interpretationSummary.loopDisposition,
463
489
  terminal: interpretationSummary.terminal,
464
- })}\n`);
490
+ }, { jq: options.jq, silent: options.silent, stdout, stderr });
465
491
  }
466
492
  if (isDirectCliRun(import.meta.url)) {
467
493
  runCli().catch((error) => {
@@ -3,6 +3,7 @@ import { buildParseError, formatCliError, isDirectCliRun, parseJsonText } from "
3
3
  import { parsePositiveInteger, requireTokenValue, runChild } from "../_cli-primitives.mjs";
4
4
  import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
5
5
  import { parseArgs } from "node:util";
6
+ import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
6
7
  const USAGE = `Usage: detect-copilot-session-activity.mjs --repo <owner/name> --branch <name> [--limit <number>]
7
8
  Detect Copilot GitHub Actions session activity on a branch.
8
9
  Required:
@@ -32,7 +33,8 @@ Error output (stderr, JSON):
32
33
  Argument/usage errors:
33
34
  { "ok": false, "error": "...", "usage": "..." }
34
35
  gh/runtime failures:
35
- { "ok": false, "error": "..." }`.trim();
36
+ { "ok": false, "error": "..." }
37
+ ${JQ_OUTPUT_USAGE}`.trim();
36
38
  const DEFAULT_LIMIT = 20;
37
39
  const ACTIVE_RUN_STATUSES = new Set(["queued", "in_progress", "pending", "requested", "waiting"]);
38
40
  const COPILOT_RUN_NAME_PATTERNS = Object.freeze([
@@ -55,6 +57,7 @@ export function parseDetectCopilotSessionActivityCliArgs(argv) {
55
57
  repo: { type: "string" },
56
58
  branch: { type: "string" },
57
59
  limit: { type: "string" },
60
+ ...JQ_OUTPUT_PARSE_OPTIONS,
58
61
  },
59
62
  allowPositionals: true,
60
63
  strict: false,
@@ -83,6 +86,7 @@ export function parseDetectCopilotSessionActivityCliArgs(argv) {
83
86
  options.limit = parsePositiveInteger(requireTokenValue(token, parseError), "--limit", parseError);
84
87
  continue;
85
88
  }
89
+ if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
86
90
  throw parseError(`Unknown argument: ${token.rawName}`);
87
91
  }
88
92
  if (options.repo === undefined || options.branch === undefined || options.branch.length === 0) {
@@ -185,7 +189,7 @@ export async function detectCopilotSessionActivity({ repo, branch, limit = DEFAU
185
189
  }
186
190
  export async function runCli(
187
191
  argv = process.argv.slice(2),
188
- { stdout = process.stdout, env = process.env, ghCommand = "gh" } = {},
192
+ { stdout = process.stdout, stderr = process.stderr, env = process.env, ghCommand = "gh" } = {},
189
193
  ) {
190
194
  const options = parseDetectCopilotSessionActivityCliArgs(argv);
191
195
  if (options.help) {
@@ -193,7 +197,7 @@ export async function runCli(
193
197
  return;
194
198
  }
195
199
  const result = await detectCopilotSessionActivity(options, { env, ghCommand });
196
- stdout.write(`${JSON.stringify(result)}\n`);
200
+ process.exitCode = emitResult(result, { jq: options.jq, silent: options.silent, stdout, stderr });
197
201
  }
198
202
  if (isDirectCliRun(import.meta.url)) {
199
203
  runCli().catch((error) => {
@@ -5,6 +5,7 @@ import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
5
5
  import { detectLinkedIssuePr } from "../github/detect-linked-issue-pr.mjs";
6
6
  import { detectCopilotSessionActivity } from "./detect-copilot-session-activity.mjs";
7
7
  import { parseArgs } from "node:util";
8
+ import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
8
9
  const USAGE = `Usage: detect-initial-copilot-pr-state.mjs --repo <owner/name> --issue <number>
9
10
  Detect whether an assigned issue is still on the bootstrap-only Copilot draft PR
10
11
  or has moved into normal linked-PR follow-up.
@@ -43,7 +44,8 @@ Error output (stderr, JSON):
43
44
  Argument/usage errors:
44
45
  { "ok": false, "error": "...", "usage": "..." }
45
46
  gh/runtime failures:
46
- { "ok": false, "error": "..." }`.trim();
47
+ { "ok": false, "error": "..." }
48
+ ${JQ_OUTPUT_USAGE}`.trim();
47
49
  export const LINKED_PR_STATE = Object.freeze({
48
50
  NO_LINKED_PR: "no_linked_pr",
49
51
  PRIOR_LINKED_PR_CLOSED_UNMERGED: "prior_linked_pr_closed_unmerged",
@@ -91,6 +93,7 @@ export function parseDetectInitialCopilotPrStateCliArgs(argv) {
91
93
  help: { type: "boolean", short: "h" },
92
94
  repo: { type: "string" },
93
95
  issue: { type: "string" },
96
+ ...JQ_OUTPUT_PARSE_OPTIONS,
94
97
  },
95
98
  allowPositionals: true,
96
99
  strict: false,
@@ -115,6 +118,7 @@ export function parseDetectInitialCopilotPrStateCliArgs(argv) {
115
118
  options.issue = parseIssueNumber(requireTokenValue(token, parseError), parseError);
116
119
  continue;
117
120
  }
121
+ if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
118
122
  throw parseError(`Unknown argument: ${token.rawName}`);
119
123
  }
120
124
  if (options.repo === undefined || options.issue === undefined) {
@@ -313,7 +317,7 @@ export async function detectInitialCopilotPrState({ repo, issue }, { env = proce
313
317
  }
314
318
  export async function runCli(
315
319
  argv = process.argv.slice(2),
316
- { stdout = process.stdout, env = process.env, ghCommand = "gh" } = {},
320
+ { stdout = process.stdout, stderr = process.stderr, env = process.env, ghCommand = "gh" } = {},
317
321
  ) {
318
322
  const options = parseDetectInitialCopilotPrStateCliArgs(argv);
319
323
  if (options.help) {
@@ -324,7 +328,7 @@ export async function runCli(
324
328
  { repo: options.repo, issue: options.issue },
325
329
  { env, ghCommand },
326
330
  );
327
- stdout.write(`${JSON.stringify(result)}\n`);
331
+ process.exitCode = emitResult(result, { jq: options.jq, silent: options.silent, stdout, stderr });
328
332
  }
329
333
  if (isDirectCliRun(import.meta.url)) {
330
334
  runCli().catch((error) => {
@@ -6,6 +6,7 @@ import { buildParseError, formatCliError, isDirectCliRun } from "../_core-helper
6
6
  import { parsePrNumber, requireTokenValue, runChild } from "../_cli-primitives.mjs";
7
7
  import { parseRepoSlug } from "@dev-loops/core/github/repo-slug";
8
8
  import { parseArgs } from "node:util";
9
+ import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
9
10
 
10
11
  const USAGE = `Usage: detect-internal-only-pr.mjs --repo <owner/name> --pr <number> [--config <path>]
11
12
  Detect whether a PR only touches internal tooling files (scripts, docs, tests, config)
@@ -20,9 +21,11 @@ Optional:
20
21
  Output (stdout, JSON):
21
22
  { "ok": true, "internalOnly": true|false, "files": ["path1", "path2", ...],
22
23
  "reason": "...", "repo": "...", "pr": N }
24
+ ${JQ_OUTPUT_USAGE}
23
25
  Exit codes:
24
26
  0 Success
25
- 1 Argument error or gh failure`.trim();
27
+ 1 Argument error or gh failure
28
+ 2 Invalid --jq filter`.trim();
26
29
 
27
30
  const parseError = buildParseError(USAGE);
28
31
 
@@ -134,6 +137,7 @@ export function parseCliArgs(argv) {
134
137
  pr: { type: "string" },
135
138
  config: { type: "string" },
136
139
  "label-check": { type: "boolean" },
140
+ ...JQ_OUTPUT_PARSE_OPTIONS,
137
141
  },
138
142
  allowPositionals: true,
139
143
  strict: false,
@@ -166,6 +170,7 @@ export function parseCliArgs(argv) {
166
170
  options.labelCheck = true;
167
171
  continue;
168
172
  }
173
+ if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
169
174
  throw parseError(`Unknown argument: ${token.rawName}`);
170
175
  }
171
176
  if (options.repo === undefined || options.pr === undefined) {
@@ -265,6 +270,7 @@ export async function runCli(
265
270
  argv = process.argv.slice(2),
266
271
  {
267
272
  stdout = process.stdout,
273
+ stderr = process.stderr,
268
274
  env = process.env,
269
275
  ghCommand = "gh",
270
276
  } = {},
@@ -275,7 +281,7 @@ export async function runCli(
275
281
  return;
276
282
  }
277
283
  const result = await detectInternalOnly(options, { env, ghCommand });
278
- stdout.write(`${JSON.stringify(result)}\n`);
284
+ process.exitCode = emitResult(result, { jq: options.jq, silent: options.silent, stdout, stderr });
279
285
  }
280
286
 
281
287
  if (isDirectCliRun(import.meta.url)) {
@@ -8,6 +8,7 @@ import {
8
8
  detectIssueRefinementArtifact,
9
9
  REFINEMENT_SOURCE,
10
10
  } from "@dev-loops/core/loop/issue-refinement-artifact";
11
+ import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
11
12
  const USAGE = `Usage:
12
13
  detect-issue-refinement-artifact.mjs --repo <owner/name> --issue <number>
13
14
  detect-issue-refinement-artifact.mjs --input <path>
@@ -31,7 +32,8 @@ Success output (stdout, JSON):
31
32
  "reason": "..."
32
33
  }
33
34
  Error output (stderr, JSON):
34
- { "ok": false, "error": "...", "usage": "..." }`.trim();
35
+ { "ok": false, "error": "...", "usage": "..." }
36
+ ${JQ_OUTPUT_USAGE}`.trim();
35
37
  const parseError = buildParseError(USAGE);
36
38
  export function parseDetectIssueRefinementArtifactCliArgs(argv) {
37
39
  const options = {
@@ -47,6 +49,7 @@ export function parseDetectIssueRefinementArtifactCliArgs(argv) {
47
49
  repo: { type: "string" },
48
50
  issue: { type: "string" },
49
51
  input: { type: "string" },
52
+ ...JQ_OUTPUT_PARSE_OPTIONS,
50
53
  },
51
54
  allowPositionals: true,
52
55
  strict: false,
@@ -79,6 +82,7 @@ export function parseDetectIssueRefinementArtifactCliArgs(argv) {
79
82
  options.input = requireTokenValue(token, parseError).trim();
80
83
  continue;
81
84
  }
85
+ if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
82
86
  throw parseError(`Unknown argument: ${token.rawName}`);
83
87
  }
84
88
  const hasInput = typeof options.input === "string" && options.input.length > 0;
@@ -165,8 +169,7 @@ export async function runCli(
165
169
  }
166
170
  try {
167
171
  const result = await detectIssueRefinementArtifactFromOptions(options, { env, ghCommand });
168
- stdout.write(`${JSON.stringify(result)}\n`);
169
- return 0;
172
+ return emitResult(result, { jq: options.jq, silent: options.silent, stdout, stderr });
170
173
  } catch (error) {
171
174
  stderr.write(`${formatCliError(error)}\n`);
172
175
  return 1;