dev-loops 0.5.0 → 0.7.1
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/.claude/.claude-plugin/plugin.json +1 -1
- package/.claude/agents/dev-loop.md +2 -1
- package/.claude/agents/review.md +2 -1
- package/.claude/commands/loop-auto.md +7 -0
- package/.claude/commands/loop-continue.md +15 -0
- package/.claude/commands/loop-enqueue.md +22 -0
- package/.claude/commands/loop-grill.md +17 -0
- package/.claude/commands/loop-info.md +7 -0
- package/.claude/commands/loop-queue-status.md +24 -0
- package/.claude/commands/loop-start-spike.md +16 -0
- package/.claude/commands/loop-start.md +7 -0
- package/.claude/commands/loop-status.md +6 -0
- package/.claude/hooks/_bash-command-classify.mjs +333 -29
- package/.claude/hooks/_hook-decisions.mjs +138 -15
- package/.claude/hooks/_run-context.mjs +11 -4
- package/.claude/hooks/pre-tool-use-bash-gate.mjs +36 -15
- package/.claude/skills/copilot-pr-followup/SKILL.md +24 -12
- package/.claude/skills/dev-loop/SKILL.md +19 -10
- package/.claude/skills/docs/acceptance-criteria-verification.md +6 -2
- package/.claude/skills/docs/anti-patterns.md +2 -1
- package/.claude/skills/docs/artifact-authority-contract.md +22 -4
- package/.claude/skills/docs/copilot-loop-operations.md +1 -1
- package/.claude/skills/docs/cross-harness-regression-contract.md +60 -0
- package/.claude/skills/docs/issue-intake-procedure.md +1 -0
- package/.claude/skills/docs/local-planning-flow.md +1 -1
- package/.claude/skills/docs/local-planning-worked-example.md +1 -1
- package/.claude/skills/docs/merge-preconditions.md +17 -1
- package/.claude/skills/docs/plan-file-contract.md +1 -1
- package/.claude/skills/docs/public-dev-loop-contract.md +1 -1
- package/.claude/skills/docs/release-runbook.md +45 -0
- package/.claude/skills/docs/retrospective-checkpoint-contract.md +90 -76
- package/.claude/skills/docs/ui-e2e-scoping-step.md +134 -0
- package/.claude/skills/docs/workflow-handoff-contract.md +39 -0
- package/.claude/skills/local-implementation/SKILL.md +26 -15
- package/.claude/skills/loop-grill/SKILL.md +163 -0
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +125 -0
- package/README.md +29 -10
- package/agents/dev-loop.agent.md +11 -3
- package/agents/developer.agent.md +1 -1
- package/agents/docs.agent.md +1 -1
- package/agents/fixer.agent.md +1 -1
- package/agents/quality.agent.md +1 -1
- package/agents/refiner.agent.md +1 -1
- package/agents/review.agent.md +3 -2
- package/cli/index.mjs +35 -40
- package/extension/README.md +4 -4
- package/extension/checks.ts +1 -5
- package/extension/harness-types.ts +1 -0
- package/extension/index.ts +16 -1
- package/extension/pi-extension-adapter.ts +2 -0
- package/extension/presentation.ts +16 -13
- package/lib/dev-loops-core.mjs +141 -0
- package/package.json +10 -8
- package/scripts/claude/generate-claude-assets.mjs +15 -1
- package/scripts/github/capture-review-threads.mjs +2 -9
- package/scripts/github/comment-issue.mjs +174 -0
- package/scripts/github/create-label.mjs +133 -0
- package/scripts/github/detect-checkpoint-evidence.mjs +170 -14
- package/scripts/github/detect-linked-issue-pr.mjs +22 -6
- package/scripts/github/edit-pr.mjs +259 -0
- package/scripts/github/fetch-ci-logs.mjs +208 -0
- package/scripts/github/list-issues.mjs +184 -0
- package/scripts/github/manage-sub-issues.mjs +46 -10
- package/scripts/github/offer-human-handoff.mjs +8 -5
- package/scripts/github/post-gate-findings.mjs +14 -2
- package/scripts/github/probe-ci-status.mjs +8 -2
- package/scripts/github/probe-copilot-review.mjs +21 -14
- package/scripts/github/ready-for-review.mjs +26 -8
- package/scripts/github/reconcile-draft-gate.mjs +7 -3
- package/scripts/github/reply-resolve-review-thread.mjs +16 -5
- package/scripts/github/reply-resolve-review-threads.mjs +69 -7
- package/scripts/github/request-copilot-review.mjs +8 -2
- package/scripts/github/resolve-handoff-candidates.mjs +7 -3
- package/scripts/github/resolve-tracker-local-spec.mjs +9 -3
- package/scripts/github/stage-reviewer-draft.mjs +10 -2
- package/scripts/github/tick-verified-checkboxes.mjs +202 -0
- package/scripts/github/upsert-checkpoint-verdict.mjs +31 -10
- package/scripts/github/verify-fresh-review-context.mjs +150 -31
- package/scripts/github/view-pr.mjs +150 -0
- package/scripts/github/write-gate-context.mjs +248 -61
- package/scripts/github/write-gate-findings-log.mjs +75 -1
- package/scripts/lib/jq-output.mjs +18 -0
- package/scripts/loop/_handoff-contract.mjs +1 -0
- package/scripts/loop/_post-convergence-change.mjs +211 -0
- package/scripts/loop/_pr-runner-coordination.mjs +70 -0
- package/scripts/loop/_repo-root-resolver.mjs +47 -0
- package/scripts/loop/build-handoff-envelope.mjs +14 -4
- package/scripts/loop/check-retro-tooling.mjs +14 -3
- package/scripts/loop/checkpoint-contract.mjs +7 -4
- package/scripts/loop/cleanup-worktree.mjs +12 -3
- package/scripts/loop/conductor-monitor.mjs +12 -18
- package/scripts/loop/copilot-pr-handoff.mjs +135 -14
- package/scripts/loop/debt-remediate.mjs +24 -12
- package/scripts/loop/detect-change-scope.mjs +36 -7
- package/scripts/loop/detect-copilot-loop-state.mjs +11 -5
- package/scripts/loop/detect-copilot-session-activity.mjs +7 -3
- package/scripts/loop/detect-initial-copilot-pr-state.mjs +7 -3
- package/scripts/loop/detect-internal-only-pr.mjs +8 -2
- package/scripts/loop/detect-issue-refinement-artifact.mjs +6 -3
- package/scripts/loop/detect-pr-gate-coordination-state.mjs +179 -66
- package/scripts/loop/detect-reviewer-loop-state.mjs +12 -2
- package/scripts/loop/detect-tracker-first-loop-state.mjs +9 -1
- package/scripts/loop/detect-tracker-pr-state.mjs +10 -3
- package/scripts/loop/ensure-worktree.mjs +8 -3
- package/scripts/loop/info.mjs +12 -4
- package/scripts/loop/inspect-run-viewer/constants.mjs +4 -18
- package/scripts/loop/inspect-run-viewer/vendor/mermaid.min.js +3405 -0
- package/scripts/loop/inspect-run-viewer-ci-changes.mjs +18 -6
- package/scripts/loop/inspect-run-viewer.mjs +67 -4
- package/scripts/loop/inspect-run.mjs +8 -2
- package/scripts/loop/outer-loop.mjs +8 -4
- package/scripts/loop/pr-runner-coordination.mjs +2 -9
- package/scripts/loop/pre-commit-branch-guard.mjs +16 -10
- package/scripts/loop/pre-flight-gate.mjs +9 -9
- package/scripts/loop/pre-pr-ready-gate.mjs +8 -4
- package/scripts/loop/pre-write-remote-freshness-guard.mjs +13 -4
- package/scripts/loop/provision-worktree.mjs +74 -3
- package/scripts/loop/resolve-dev-loop-startup.mjs +76 -7
- package/scripts/loop/resolve-gate-dispatch.mjs +134 -0
- package/scripts/loop/resolve-pr-conflicts.mjs +14 -7
- package/scripts/loop/run-conductor-cycle.mjs +13 -2
- package/scripts/loop/run-queue.mjs +18 -9
- package/scripts/loop/run-refinement-audit.mjs +8 -9
- package/scripts/loop/run-watch-cycle.mjs +2 -9
- package/scripts/loop/sanctioned-commands.mjs +104 -0
- package/scripts/loop/steer-loop.mjs +29 -16
- package/scripts/loop/validate-pr-body-spec.mjs +207 -0
- package/scripts/loop/watch-initial-copilot-pr.mjs +8 -3
- package/scripts/pages/build-site.mjs +59 -8
- package/scripts/pages/build-state-atlas.mjs +441 -0
- package/scripts/projects/_resolve-project.mjs +148 -0
- package/scripts/projects/add-queue-item.mjs +60 -54
- package/scripts/projects/archive-done-items.mjs +49 -84
- package/scripts/projects/ensure-queue-board.mjs +10 -36
- package/scripts/projects/list-queue-items.mjs +116 -65
- package/scripts/projects/move-queue-item.mjs +28 -49
- package/scripts/projects/reconcile-queue.mjs +253 -0
- package/scripts/projects/reorder-queue-item.mjs +41 -47
- package/scripts/projects/resolve-active-board-item.mjs +255 -0
- package/scripts/projects/sync-item-status.mjs +15 -10
- package/scripts/refine/_refine-helpers.mjs +21 -5
- package/scripts/refine/exit-spike.mjs +18 -8
- package/scripts/refine/promote-plan.mjs +22 -16
- package/scripts/refine/prose-linkage-detector.mjs +1 -1
- package/scripts/refine/refine-plan-file.mjs +13 -4
- package/scripts/refine/refinement-completeness-checker.mjs +1 -1
- package/scripts/refine/scaffold-spike-file.mjs +179 -0
- package/scripts/refine/scope-boundary-cross-checker.mjs +1 -1
- package/scripts/refine/tree-integrity-validator.mjs +1 -1
- package/scripts/refine/validate-plan-file.mjs +1 -1
- package/scripts/refine/validate-spike-file.mjs +1 -1
- package/scripts/refine/verify.mjs +11 -6
- package/scripts/release/assert-core-dependency-version.mjs +123 -0
- package/scripts/release/extract-changelog-section.mjs +125 -0
- package/skills/copilot-pr-followup/SKILL.md +24 -12
- package/skills/dev-loop/SKILL.md +22 -6
- package/skills/docs/acceptance-criteria-verification.md +6 -2
- package/skills/docs/anti-patterns.md +2 -1
- package/skills/docs/artifact-authority-contract.md +22 -4
- package/skills/docs/copilot-loop-operations.md +1 -1
- package/skills/docs/cross-harness-regression-contract.md +60 -0
- package/skills/docs/issue-intake-procedure.md +1 -0
- package/skills/docs/local-planning-flow.md +1 -1
- package/skills/docs/local-planning-worked-example.md +1 -1
- package/skills/docs/merge-preconditions.md +17 -1
- package/skills/docs/plan-file-contract.md +1 -1
- package/skills/docs/public-dev-loop-contract.md +1 -1
- package/skills/docs/release-runbook.md +45 -0
- package/skills/docs/retrospective-checkpoint-contract.md +90 -76
- package/skills/docs/ui-e2e-scoping-step.md +134 -0
- package/skills/docs/workflow-handoff-contract.md +39 -0
- package/skills/local-implementation/SKILL.md +26 -15
- package/skills/loop-grill/SKILL.md +165 -0
- package/scripts/loop/conductor.mjs +0 -233
- package/scripts/loop/detect-stale-runner.mjs +0 -265
- package/scripts/loop/pre-push-main-guard.mjs +0 -117
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
writeLinkedPrNumber,
|
|
18
18
|
PLAN_FILE_PROMOTE_ACTION,
|
|
19
19
|
} from "@dev-loops/core/loop/plan-file-promote-contract";
|
|
20
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
20
21
|
|
|
21
22
|
const CREATE_PR_PATH = fileURLToPath(new URL("../github/create-pr.mjs", import.meta.url));
|
|
22
23
|
|
|
@@ -42,9 +43,14 @@ Optional:
|
|
|
42
43
|
--branch <name> Branch name to commit the plan on (default: derived from the plan path)
|
|
43
44
|
--json Machine-readable JSON output
|
|
44
45
|
--help Show this help
|
|
46
|
+
|
|
47
|
+
${JQ_OUTPUT_USAGE}
|
|
48
|
+
(--jq/--silent only apply together with --json; the default text output is unaffected.)
|
|
49
|
+
|
|
45
50
|
Exit codes:
|
|
46
51
|
0 Promotion succeeded (or idempotent no-op for an already-linked plan)
|
|
47
|
-
1 Argument error, or fail-closed (plan not ready / git / PR-create failure); no issue ever created
|
|
52
|
+
1 Argument error, or fail-closed (plan not ready / git / PR-create failure); no issue ever created
|
|
53
|
+
2 Invalid --jq filter`.trim();
|
|
48
54
|
|
|
49
55
|
const parseError = buildParseError(USAGE);
|
|
50
56
|
|
|
@@ -57,6 +63,7 @@ export function parsePromotePlanCliArgs(argv) {
|
|
|
57
63
|
base: { type: "string" },
|
|
58
64
|
branch: { type: "string" },
|
|
59
65
|
json: { type: "boolean" },
|
|
66
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
60
67
|
},
|
|
61
68
|
allowPositionals: true,
|
|
62
69
|
strict: false,
|
|
@@ -88,6 +95,7 @@ export function parsePromotePlanCliArgs(argv) {
|
|
|
88
95
|
options.json = true;
|
|
89
96
|
continue;
|
|
90
97
|
}
|
|
98
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
91
99
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
92
100
|
}
|
|
93
101
|
if (typeof options.planFile !== "string" || options.planFile.trim().length === 0) {
|
|
@@ -123,11 +131,14 @@ function parsePrNumberFromGhOutput(text) {
|
|
|
123
131
|
return match ? Number.parseInt(match[1], 10) : null;
|
|
124
132
|
}
|
|
125
133
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
134
|
+
// Sets process.exitCode from `summary.ok` (0/1), or the shared --jq/--silent
|
|
135
|
+
// contract's exit code (0/1/2) when --json + --jq/--silent are in play.
|
|
136
|
+
function emit(stdout, options, summary, humanLines) {
|
|
137
|
+
if (options.json) {
|
|
138
|
+
process.exitCode = emitResult(summary, { jq: options.jq, silent: options.silent, stdout });
|
|
129
139
|
} else {
|
|
130
140
|
stdout.write(`${humanLines.join("\n")}\n`);
|
|
141
|
+
process.exitCode = summary.ok ? 0 : 1;
|
|
131
142
|
}
|
|
132
143
|
}
|
|
133
144
|
|
|
@@ -161,8 +172,7 @@ export async function runCli(argv = process.argv.slice(2), {
|
|
|
161
172
|
if (!decision.ok) {
|
|
162
173
|
// Fail closed: no git, no gh, no issue — surface the reason and stop.
|
|
163
174
|
const summary = { ok: false, reason: decision.reason, planFileIntakeState: decision.planFileIntakeState ?? null };
|
|
164
|
-
emit(stdout, options
|
|
165
|
-
process.exitCode = 1;
|
|
175
|
+
emit(stdout, options, summary, [`promote-plan: FAIL (${decision.reason})`]);
|
|
166
176
|
return summary;
|
|
167
177
|
}
|
|
168
178
|
|
|
@@ -174,7 +184,7 @@ export async function runCli(argv = process.argv.slice(2), {
|
|
|
174
184
|
planFile: planPath,
|
|
175
185
|
prNumber: decision.existingPrNumber,
|
|
176
186
|
};
|
|
177
|
-
emit(stdout, options
|
|
187
|
+
emit(stdout, options, summary, [
|
|
178
188
|
"promote-plan: PASS (already promoted)",
|
|
179
189
|
` plan: ${planPath}`,
|
|
180
190
|
` pr: #${decision.existingPrNumber} (existing; opened nothing)`,
|
|
@@ -207,8 +217,7 @@ export async function runCli(argv = process.argv.slice(2), {
|
|
|
207
217
|
|
|
208
218
|
const fail = (reason, detail) => {
|
|
209
219
|
const summary = { ok: false, reason, detail: detail ?? null, planFile: planPath };
|
|
210
|
-
emit(stdout, options
|
|
211
|
-
process.exitCode = 1;
|
|
220
|
+
emit(stdout, options, summary, [`promote-plan: FAIL (${reason})${detail ? `: ${detail}` : ""}`]);
|
|
212
221
|
return summary;
|
|
213
222
|
};
|
|
214
223
|
|
|
@@ -294,11 +303,10 @@ export async function runCli(argv = process.argv.slice(2), {
|
|
|
294
303
|
branch,
|
|
295
304
|
recovery: `gh pr create failed, so no PR was opened, but the plan is committed and branch ${branch} is pushed. Re-run promote-plan to recover: the commit step is idempotent (skips when nothing is staged) and the push is idempotent, so a clean re-run will retry opening the PR.`,
|
|
296
305
|
};
|
|
297
|
-
emit(stdout, options
|
|
306
|
+
emit(stdout, options, summary, [
|
|
298
307
|
`promote-plan: FAIL (pr_create_failed)${detail ? `: ${detail}` : ""}`,
|
|
299
308
|
` branch: ${branch} (committed and pushed; no PR opened — re-run promote-plan to recover, it is idempotent and will retry opening the PR)`,
|
|
300
309
|
]);
|
|
301
|
-
process.exitCode = 1;
|
|
302
310
|
return summary;
|
|
303
311
|
}
|
|
304
312
|
const prNumber = parsePrNumberFromGhOutput(prCreate.stdout);
|
|
@@ -315,11 +323,10 @@ export async function runCli(argv = process.argv.slice(2), {
|
|
|
315
323
|
branch,
|
|
316
324
|
recovery: `A draft PR was opened on branch ${branch} but its number could not be parsed from gh output. Find it with: gh pr list --head "${branch}" --json number,url. Then record the link with: node scripts/refine/promote-plan.mjs is idempotent only once the front-matter prNumber is set, so add it manually (prNumber: <n>) and commit ${planDocRelPath}.`,
|
|
317
325
|
};
|
|
318
|
-
emit(stdout, options
|
|
326
|
+
emit(stdout, options, summary, [
|
|
319
327
|
`promote-plan: FAIL (pr_number_unparseable)`,
|
|
320
328
|
` branch: ${branch} (a draft PR is open; its number could not be parsed — recover via 'gh pr list --head "${branch}"')`,
|
|
321
329
|
]);
|
|
322
|
-
process.exitCode = 1;
|
|
323
330
|
return summary;
|
|
324
331
|
}
|
|
325
332
|
|
|
@@ -340,11 +347,10 @@ export async function runCli(argv = process.argv.slice(2), {
|
|
|
340
347
|
prNumber,
|
|
341
348
|
recovery: `PR #${prNumber} is open but the plan->PR link commit failed. ${planDocRelPath} now carries prNumber: ${prNumber} in its front-matter; record the link with: git add -- "${planDocRelPath}" && git commit -m "docs(plan): link to PR #${prNumber}" && git push.`,
|
|
342
349
|
};
|
|
343
|
-
emit(stdout, options
|
|
350
|
+
emit(stdout, options, summary, [
|
|
344
351
|
`promote-plan: FAIL (${reason})${detail ? `: ${detail}` : ""}`,
|
|
345
352
|
` pr: #${prNumber} (open; plan->PR link NOT committed — recover manually)`,
|
|
346
353
|
]);
|
|
347
|
-
process.exitCode = 1;
|
|
348
354
|
return summary;
|
|
349
355
|
};
|
|
350
356
|
const linkedMarkdown = writeLinkedPrNumber(markdownText, prNumber);
|
|
@@ -370,7 +376,7 @@ export async function runCli(argv = process.argv.slice(2), {
|
|
|
370
376
|
branch,
|
|
371
377
|
prNumber,
|
|
372
378
|
};
|
|
373
|
-
emit(stdout, options
|
|
379
|
+
emit(stdout, options, summary, [
|
|
374
380
|
"promote-plan: PASS",
|
|
375
381
|
` plan: ${planPath}`,
|
|
376
382
|
` branch: ${branch}`,
|
|
@@ -80,7 +80,7 @@ export async function runCli(argv = process.argv.slice(2), { stdout = process.st
|
|
|
80
80
|
}
|
|
81
81
|
const tree = await loadTreeFromInput(options.input);
|
|
82
82
|
const result = runProseLinkageDetector(tree);
|
|
83
|
-
writeCheckerOutput(result, { stdout, json: options.json });
|
|
83
|
+
process.exitCode = writeCheckerOutput(result, { stdout, json: options.json, jq: options.jq, silent: options.silent });
|
|
84
84
|
return result;
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "@dev-loops/core/loop/plan-file-refine-contract";
|
|
14
14
|
import { PLAN_FILE_REFINEMENT_SECTIONS } from "@dev-loops/core/loop/plan-file-intake-contract";
|
|
15
15
|
import { classifyDocsGrillFinding } from "../loop/docs-grill-contract.mjs";
|
|
16
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
16
17
|
|
|
17
18
|
const USAGE = `Usage:
|
|
18
19
|
refine-plan-file.mjs --plan-file <path> --payload <path> [--json]
|
|
@@ -31,9 +32,14 @@ Required:
|
|
|
31
32
|
Optional:
|
|
32
33
|
--json Machine-readable JSON output
|
|
33
34
|
--help Show this help
|
|
35
|
+
|
|
36
|
+
${JQ_OUTPUT_USAGE}
|
|
37
|
+
(--jq/--silent only apply together with --json; the default text output is unaffected.)
|
|
38
|
+
|
|
34
39
|
Exit codes:
|
|
35
40
|
0 Refinement succeeded; plan written in place; stop for local human review
|
|
36
|
-
1 Argument error, or fail-closed refine/grill/ambiguous state (no write)
|
|
41
|
+
1 Argument error, or fail-closed refine/grill/ambiguous state (no write)
|
|
42
|
+
2 Invalid --jq filter`.trim();
|
|
37
43
|
|
|
38
44
|
const parseError = buildParseError(USAGE);
|
|
39
45
|
|
|
@@ -45,6 +51,7 @@ export function parseRefinePlanFileCliArgs(argv) {
|
|
|
45
51
|
"plan-file": { type: "string" },
|
|
46
52
|
payload: { type: "string" },
|
|
47
53
|
json: { type: "boolean" },
|
|
54
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
48
55
|
},
|
|
49
56
|
allowPositionals: true,
|
|
50
57
|
strict: false,
|
|
@@ -72,6 +79,7 @@ export function parseRefinePlanFileCliArgs(argv) {
|
|
|
72
79
|
options.json = true;
|
|
73
80
|
continue;
|
|
74
81
|
}
|
|
82
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
75
83
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
76
84
|
}
|
|
77
85
|
if (typeof options.planFile !== "string" || options.planFile.trim().length === 0) {
|
|
@@ -123,11 +131,12 @@ export async function runCli(argv = process.argv.slice(2), { stdout = process.st
|
|
|
123
131
|
if (!result.ok) {
|
|
124
132
|
// Fail closed: surface the reason, do not write, do not advance, do not promote.
|
|
125
133
|
if (options.json) {
|
|
126
|
-
|
|
134
|
+
const failurePayload = { ok: false, reason: result.reason, planFileIntakeState: result.planFileIntakeState ?? null };
|
|
135
|
+
process.exitCode = emitResult(failurePayload, { jq: options.jq, silent: options.silent, stdout });
|
|
127
136
|
} else {
|
|
128
137
|
stdout.write(`refine-plan-file: FAIL (${result.reason})\n`);
|
|
138
|
+
process.exitCode = 1;
|
|
129
139
|
}
|
|
130
|
-
process.exitCode = 1;
|
|
131
140
|
return result;
|
|
132
141
|
}
|
|
133
142
|
|
|
@@ -142,7 +151,7 @@ export async function runCli(argv = process.argv.slice(2), { stdout = process.st
|
|
|
142
151
|
grillDispositions: result.grillDispositions,
|
|
143
152
|
};
|
|
144
153
|
if (options.json) {
|
|
145
|
-
|
|
154
|
+
process.exitCode = emitResult(summary, { jq: options.jq, silent: options.silent, stdout });
|
|
146
155
|
} else {
|
|
147
156
|
stdout.write(
|
|
148
157
|
[
|
|
@@ -76,7 +76,7 @@ export async function runCli(argv = process.argv.slice(2), { stdout = process.st
|
|
|
76
76
|
}
|
|
77
77
|
const tree = await loadTreeFromInput(options.input);
|
|
78
78
|
const result = runRefinementCompletenessChecker(tree);
|
|
79
|
-
writeCheckerOutput(result, { stdout, json: options.json });
|
|
79
|
+
process.exitCode = writeCheckerOutput(result, { stdout, json: options.json, jq: options.jq, silent: options.silent });
|
|
80
80
|
return result;
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Scaffold a STARTABLE spike findings artifact from an inline question (#988 P2).
|
|
3
|
+
//
|
|
4
|
+
// `/start-spike <question>` needs a findings artifact that passes
|
|
5
|
+
// `validateSpikeExplorationSections` (non-empty Question/Approach/Findings) so
|
|
6
|
+
// `resolve-dev-loop-startup --spike <path>` accepts it. This is the only new
|
|
7
|
+
// piece behind /start-spike: a pure section builder + a thin file writer. The
|
|
8
|
+
// Recommendation is left for the spike to fill in (the exit marker), matching
|
|
9
|
+
// the spike-mode contract — so this scaffolds an in-progress spike, not a
|
|
10
|
+
// ready-for-exit one. No new spike behavior; the intake/gate/exit machinery is
|
|
11
|
+
// the shipped #964/#965/#966 surface.
|
|
12
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { parseArgs } from "node:util";
|
|
15
|
+
|
|
16
|
+
import { formatCliError, isDirectCliRun } from "../_core-helpers.mjs";
|
|
17
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
18
|
+
import { SPIKE_FILE_EXPLORATION_SECTIONS } from "./validate-spike-file.mjs";
|
|
19
|
+
|
|
20
|
+
// Placeholder bodies the operator fills in during the spike. Non-empty so the
|
|
21
|
+
// exploration scaffold validates; explicitly marked TBD so they are obviously
|
|
22
|
+
// stubs, not findings. (`Recommendation` is intentionally omitted — it is the
|
|
23
|
+
// exit marker, written when the spike concludes.)
|
|
24
|
+
const PLACEHOLDER = "TBD — filled in during the spike.";
|
|
25
|
+
|
|
26
|
+
const USAGE = `Usage:
|
|
27
|
+
scaffold-spike-file.mjs --question <text> --out <path>
|
|
28
|
+
|
|
29
|
+
Scaffold a startable spike findings artifact from an inline question. Writes a
|
|
30
|
+
file carrying the exploration scaffold (## Question filled from --question,
|
|
31
|
+
## Approach/## Findings stubbed) so \`resolve-dev-loop-startup --spike <path>\`
|
|
32
|
+
accepts it. The ## Recommendation is left for the spike to fill in.
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
--question <text> Required. The question the spike investigates.
|
|
36
|
+
--out <path> Required. Where to write the findings artifact.
|
|
37
|
+
--help, -h Show this help.
|
|
38
|
+
|
|
39
|
+
Output (stdout):
|
|
40
|
+
JSON: { ok: true, path: "<abs>", question: "<text>" }
|
|
41
|
+
|
|
42
|
+
${JQ_OUTPUT_USAGE}
|
|
43
|
+
`.trim();
|
|
44
|
+
|
|
45
|
+
// Pure builder: question text -> a findings-artifact markdown body carrying the
|
|
46
|
+
// exploration scaffold. Question gets the operator's text; Approach/Findings get
|
|
47
|
+
// non-empty placeholder bodies so the exploration scaffold validates.
|
|
48
|
+
export function buildSpikeScaffold(question) {
|
|
49
|
+
const trimmed = String(question ?? "").trim();
|
|
50
|
+
if (trimmed.length === 0) {
|
|
51
|
+
throw Object.assign(new Error("--question must be non-empty"), { code: "INVALID_ARGS" });
|
|
52
|
+
}
|
|
53
|
+
const bodies = { Question: trimmed, Approach: PLACEHOLDER, Findings: PLACEHOLDER };
|
|
54
|
+
const sections = SPIKE_FILE_EXPLORATION_SECTIONS.map(
|
|
55
|
+
(heading) => `## ${heading}\n\n${bodies[heading]}\n`,
|
|
56
|
+
);
|
|
57
|
+
return `# Spike\n\n${sections.join("\n")}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseCliArgs(argv) {
|
|
61
|
+
const parseError = (message) => Object.assign(new Error(message), { usage: USAGE, code: "INVALID_ARGS" });
|
|
62
|
+
const requireValue = (token, message) => {
|
|
63
|
+
const v = token.value;
|
|
64
|
+
if (typeof v !== "string" || v.length === 0 || v.startsWith("-")) {
|
|
65
|
+
throw parseError(message);
|
|
66
|
+
}
|
|
67
|
+
return v;
|
|
68
|
+
};
|
|
69
|
+
// The question is free text embedded in the artifact body, never forwarded as
|
|
70
|
+
// a CLI option — so unlike --out/--jq it may legitimately begin with `-`
|
|
71
|
+
// (e.g. "- should we ...?"). Only require a non-empty value, matching the
|
|
72
|
+
// core free-text parser; leave the leading-`-` guard to the path/filter flags.
|
|
73
|
+
const requireQuestion = (token, message) => {
|
|
74
|
+
const v = token.value;
|
|
75
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
76
|
+
throw parseError(message);
|
|
77
|
+
}
|
|
78
|
+
return v;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const args = {};
|
|
82
|
+
const { tokens } = parseArgs({
|
|
83
|
+
args: [...argv],
|
|
84
|
+
options: {
|
|
85
|
+
question: { type: "string" },
|
|
86
|
+
out: { type: "string" },
|
|
87
|
+
json: { type: "boolean" },
|
|
88
|
+
help: { type: "boolean", short: "h" },
|
|
89
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
90
|
+
},
|
|
91
|
+
allowPositionals: true,
|
|
92
|
+
strict: false,
|
|
93
|
+
tokens: true,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
for (const token of tokens) {
|
|
97
|
+
if (token.kind === "positional") {
|
|
98
|
+
throw parseError(`Unexpected argument: ${token.value}`);
|
|
99
|
+
}
|
|
100
|
+
if (token.kind !== "option") {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
switch (token.name) {
|
|
104
|
+
case "help":
|
|
105
|
+
args.help = true;
|
|
106
|
+
break;
|
|
107
|
+
case "json":
|
|
108
|
+
// Output is JSON by default; accepted as a no-op for callers that pass it.
|
|
109
|
+
break;
|
|
110
|
+
case "question":
|
|
111
|
+
args.question = requireQuestion(token, "--question requires a value");
|
|
112
|
+
break;
|
|
113
|
+
case "out":
|
|
114
|
+
args.out = requireValue(token, "--out requires a value (path)");
|
|
115
|
+
break;
|
|
116
|
+
default: {
|
|
117
|
+
if (matchJqOutputToken(token, args, (t) => requireValue(t, "--jq requires a filter"))) break;
|
|
118
|
+
throw parseError(`Unknown flag: ${token.rawName}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return args;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function main(args, { writeFileImpl = writeFile, mkdirImpl = mkdir } = {}) {
|
|
126
|
+
if (!args.question) {
|
|
127
|
+
throw Object.assign(new Error("--question is required"), { usage: USAGE, code: "INVALID_ARGS" });
|
|
128
|
+
}
|
|
129
|
+
if (!args.out) {
|
|
130
|
+
throw Object.assign(new Error("--out is required"), { usage: USAGE, code: "INVALID_ARGS" });
|
|
131
|
+
}
|
|
132
|
+
const body = buildSpikeScaffold(args.question);
|
|
133
|
+
const outPath = path.resolve(args.out);
|
|
134
|
+
await mkdirImpl(path.dirname(outPath), { recursive: true });
|
|
135
|
+
// Fail closed on an existing file: the slash-command picks a fresh path and
|
|
136
|
+
// must never clobber a spike already in progress. `wx` makes write+exclusive
|
|
137
|
+
// atomic, so a concurrent create still loses (no overwrite).
|
|
138
|
+
try {
|
|
139
|
+
await writeFileImpl(outPath, body, { encoding: "utf8", flag: "wx" });
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (err?.code === "EEXIST") {
|
|
142
|
+
throw Object.assign(new Error(`--out already exists, refusing to overwrite: ${outPath}`), {
|
|
143
|
+
usage: USAGE,
|
|
144
|
+
code: "INVALID_ARGS",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
return { ok: true, path: outPath, question: String(args.question).trim() };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function runCli(argv, { stdout = process.stdout, stderr = process.stderr } = {}) {
|
|
153
|
+
let args;
|
|
154
|
+
try {
|
|
155
|
+
args = parseCliArgs(argv);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
stderr.write(`${formatCliError(err)}\n`);
|
|
158
|
+
process.exitCode = 1;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (args.help) {
|
|
162
|
+
stdout.write(`${USAGE}\n`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const result = await main(args);
|
|
167
|
+
process.exitCode = emitResult(result, { jq: args.jq, silent: args.silent, stdout, stderr });
|
|
168
|
+
} catch (err) {
|
|
169
|
+
stderr.write(`${formatCliError(err)}\n`);
|
|
170
|
+
process.exitCode = err.code === "INVALID_ARGS" ? 1 : 2;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (isDirectCliRun(import.meta.url)) {
|
|
175
|
+
runCli(process.argv.slice(2)).catch((error) => {
|
|
176
|
+
process.stderr.write(`${formatCliError(error)}\n`);
|
|
177
|
+
process.exitCode = 2;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
@@ -151,7 +151,7 @@ export async function runCli(argv = process.argv.slice(2), { stdout = process.st
|
|
|
151
151
|
}
|
|
152
152
|
const tree = await loadTreeFromInput(options.input);
|
|
153
153
|
const result = runScopeBoundaryCrossChecker(tree);
|
|
154
|
-
writeCheckerOutput(result, { stdout, json: options.json });
|
|
154
|
+
process.exitCode = writeCheckerOutput(result, { stdout, json: options.json, jq: options.jq, silent: options.silent });
|
|
155
155
|
return result;
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -199,7 +199,7 @@ export async function runCli(argv = process.argv.slice(2), { stdout = process.st
|
|
|
199
199
|
}
|
|
200
200
|
const tree = await loadTreeFromInput(options.input);
|
|
201
201
|
const result = runTreeIntegrityValidator(tree);
|
|
202
|
-
writeCheckerOutput(result, { stdout, json: options.json });
|
|
202
|
+
process.exitCode = writeCheckerOutput(result, { stdout, json: options.json, jq: options.jq, silent: options.silent });
|
|
203
203
|
return result;
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -52,7 +52,7 @@ export async function runCli(argv = process.argv.slice(2), { stdout = process.st
|
|
|
52
52
|
}
|
|
53
53
|
const markdownText = await readFile(options.input, "utf8");
|
|
54
54
|
const result = validatePlanFile(markdownText);
|
|
55
|
-
writeCheckerOutput(result, { stdout, json: options.json });
|
|
55
|
+
process.exitCode = writeCheckerOutput(result, { stdout, json: options.json, jq: options.jq, silent: options.silent });
|
|
56
56
|
return result;
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -75,7 +75,7 @@ export async function runCli(argv = process.argv.slice(2), { stdout = process.st
|
|
|
75
75
|
}
|
|
76
76
|
const markdownText = await readFile(options.input, "utf8");
|
|
77
77
|
const result = validateSpikeFile(markdownText);
|
|
78
|
-
writeCheckerOutput(result, { stdout, json: options.json });
|
|
78
|
+
process.exitCode = writeCheckerOutput(result, { stdout, json: options.json, jq: options.jq, silent: options.silent });
|
|
79
79
|
return result;
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -9,6 +9,7 @@ import { runProseLinkageDetector } from "./prose-linkage-detector.mjs";
|
|
|
9
9
|
import { runScopeBoundaryCrossChecker } from "./scope-boundary-cross-checker.mjs";
|
|
10
10
|
import { runRefinementCompletenessChecker } from "./refinement-completeness-checker.mjs";
|
|
11
11
|
import { runTreeIntegrityValidator } from "./tree-integrity-validator.mjs";
|
|
12
|
+
import { JQ_OUTPUT_PARSE_OPTIONS, JQ_OUTPUT_USAGE, emitResult, matchJqOutputToken } from "../lib/jq-output.mjs";
|
|
12
13
|
|
|
13
14
|
const USAGE = `Usage:
|
|
14
15
|
dev-loops refine verify --issue <number> [--repo <owner/name>] [--json]
|
|
@@ -26,7 +27,10 @@ Required (exactly one mode):
|
|
|
26
27
|
Optional:
|
|
27
28
|
--repo <owner/name> Repository slug for online mode
|
|
28
29
|
--json Machine-readable JSON output (default: human-readable summary)
|
|
29
|
-
--help Show this help
|
|
30
|
+
--help Show this help
|
|
31
|
+
|
|
32
|
+
${JQ_OUTPUT_USAGE}
|
|
33
|
+
(--jq/--silent only apply together with --json; the default text output is unaffected.)`.trim();
|
|
30
34
|
|
|
31
35
|
const parseError = buildParseError(USAGE);
|
|
32
36
|
|
|
@@ -39,6 +43,7 @@ export function parseRefineVerifyCliArgs(argv) {
|
|
|
39
43
|
repo: { type: "string" },
|
|
40
44
|
input: { type: "string" },
|
|
41
45
|
json: { type: "boolean" },
|
|
46
|
+
...JQ_OUTPUT_PARSE_OPTIONS,
|
|
42
47
|
},
|
|
43
48
|
allowPositionals: true,
|
|
44
49
|
strict: false,
|
|
@@ -79,6 +84,7 @@ export function parseRefineVerifyCliArgs(argv) {
|
|
|
79
84
|
options.json = true;
|
|
80
85
|
continue;
|
|
81
86
|
}
|
|
87
|
+
if (matchJqOutputToken(token, options, (t) => requireTokenValue(t, parseError))) continue;
|
|
82
88
|
throw parseError(`Unknown argument: ${token.rawName}`);
|
|
83
89
|
}
|
|
84
90
|
|
|
@@ -177,13 +183,12 @@ export async function runCli(
|
|
|
177
183
|
};
|
|
178
184
|
|
|
179
185
|
if (options.json) {
|
|
180
|
-
|
|
186
|
+
process.exitCode = emitResult(payload, { jq: options.jq, silent: options.silent, stdout });
|
|
181
187
|
} else {
|
|
182
188
|
writeHumanOutput(result, tree, { stdout });
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
process.exitCode = 1;
|
|
189
|
+
if (!result.ok) {
|
|
190
|
+
process.exitCode = 1;
|
|
191
|
+
}
|
|
187
192
|
}
|
|
188
193
|
return payload;
|
|
189
194
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Release-time guard: fail closed when the published `dev-loops` package's
|
|
4
|
+
* `@dev-loops/core` dependency does not resolve to the same major.minor as the
|
|
5
|
+
* version being released. This is the check that would have caught #1033
|
|
6
|
+
* (`dev-loops@0.6.2` shipping `@dev-loops/core@^0.2.6`).
|
|
7
|
+
*
|
|
8
|
+
* Runs in release.yml BEFORE the GitHub Release is created, so a mismatched
|
|
9
|
+
* manifest never becomes a release (and never fires npm-publish). release.yml
|
|
10
|
+
* has no `npm ci`, so this script imports only `node:` builtins — a workspace
|
|
11
|
+
* import here would ERR_MODULE_NOT_FOUND and skip the guard entirely (same
|
|
12
|
+
* constraint as extract-changelog-section.mjs).
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* node scripts/release/assert-core-dependency-version.mjs [--release-version <v>] [--manifest <path>]
|
|
16
|
+
*
|
|
17
|
+
* --release-version defaults to the manifest `version`. Pass the tag-derived
|
|
18
|
+
* version in release.yml so a manifest that forgot its own bump is also caught.
|
|
19
|
+
* Exits 0 on lockstep, 1 on mismatch, 2 on usage/parse errors.
|
|
20
|
+
*/
|
|
21
|
+
import { realpathSync } from "node:fs";
|
|
22
|
+
import { readFile } from "node:fs/promises";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
|
|
25
|
+
const CORE_DEP = "@dev-loops/core";
|
|
26
|
+
|
|
27
|
+
function isDirectCliRun(importMetaUrl, argv1 = process.argv[1]) {
|
|
28
|
+
if (typeof argv1 !== "string" || argv1.length === 0) return false;
|
|
29
|
+
try {
|
|
30
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(importMetaUrl));
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extract the `major.minor` token from a semver version or npm range.
|
|
38
|
+
* Handles leading range operators (^, ~, >=, >, <=, <, =, v) and compound
|
|
39
|
+
* ranges by taking the first `<digits>.<digits>` occurrence.
|
|
40
|
+
* @param {string} spec
|
|
41
|
+
* @returns {string} e.g. "0.6"
|
|
42
|
+
*/
|
|
43
|
+
export function extractMajorMinor(spec) {
|
|
44
|
+
const match = String(spec).match(/(\d+)\.(\d+)/);
|
|
45
|
+
if (!match) {
|
|
46
|
+
throw new Error(`cannot parse a major.minor version from "${spec}"`);
|
|
47
|
+
}
|
|
48
|
+
return `${match[1]}.${match[2]}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Assert the `@dev-loops/core` dependency range is in lockstep (same
|
|
53
|
+
* major.minor) with the release version. Throws on mismatch (fail closed).
|
|
54
|
+
* @param {{releaseVersion: string, coreRange: string}} input
|
|
55
|
+
* @returns {{releaseVersion: string, coreRange: string, majorMinor: string}}
|
|
56
|
+
*/
|
|
57
|
+
export function assertCoreDependencyInLockstep({ releaseVersion, coreRange } = {}) {
|
|
58
|
+
if (!releaseVersion) throw new Error("releaseVersion is required");
|
|
59
|
+
if (!coreRange) {
|
|
60
|
+
throw new Error(`root package must declare a "${CORE_DEP}" dependency`);
|
|
61
|
+
}
|
|
62
|
+
const releaseMajorMinor = extractMajorMinor(releaseVersion);
|
|
63
|
+
const coreMajorMinor = extractMajorMinor(coreRange);
|
|
64
|
+
if (releaseMajorMinor !== coreMajorMinor) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`${CORE_DEP} dependency "${coreRange}" (major.minor ${coreMajorMinor}) does not match ` +
|
|
67
|
+
`the release version ${releaseVersion} (major.minor ${releaseMajorMinor}). ` +
|
|
68
|
+
`Bump the ${CORE_DEP} range to ^${releaseMajorMinor}.0 before releasing (root cause of #1033).`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return { releaseVersion, coreRange, majorMinor: releaseMajorMinor };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function usageError(message) {
|
|
75
|
+
const err = new Error(message);
|
|
76
|
+
err.usage = true;
|
|
77
|
+
return err;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseArgs(argv) {
|
|
81
|
+
const out = { manifest: "package.json", releaseVersion: null };
|
|
82
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
83
|
+
const arg = argv[i];
|
|
84
|
+
if (arg === "--release-version" || arg === "--manifest") {
|
|
85
|
+
const value = argv[++i];
|
|
86
|
+
// Fail closed like extract-changelog-section.mjs: a flag missing its value
|
|
87
|
+
// is a usage error (exit 2), not a silent fallback that defeats the guard.
|
|
88
|
+
if (value === undefined) throw usageError(`${arg} requires a value`);
|
|
89
|
+
if (arg === "--release-version") out.releaseVersion = value;
|
|
90
|
+
else out.manifest = value;
|
|
91
|
+
} else {
|
|
92
|
+
throw usageError(`unknown argument: ${arg}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function main(argv) {
|
|
99
|
+
const { manifest, releaseVersion } = parseArgs(argv);
|
|
100
|
+
// An unreadable or invalid manifest is a usage/parse error (exit 2), not a
|
|
101
|
+
// lockstep mismatch (exit 1) — matches the header contract and extract-changelog-section.mjs.
|
|
102
|
+
let pkg;
|
|
103
|
+
try {
|
|
104
|
+
pkg = JSON.parse(await readFile(manifest, "utf8"));
|
|
105
|
+
} catch (err) {
|
|
106
|
+
throw usageError(`cannot read or parse manifest "${manifest}": ${err.message}`);
|
|
107
|
+
}
|
|
108
|
+
const coreRange = pkg.dependencies?.[CORE_DEP];
|
|
109
|
+
const result = assertCoreDependencyInLockstep({
|
|
110
|
+
releaseVersion: releaseVersion ?? pkg.version,
|
|
111
|
+
coreRange,
|
|
112
|
+
});
|
|
113
|
+
process.stdout.write(
|
|
114
|
+
`${CORE_DEP} ${result.coreRange} is in lockstep with release ${result.releaseVersion} (major.minor ${result.majorMinor}).\n`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (isDirectCliRun(import.meta.url)) {
|
|
119
|
+
main(process.argv.slice(2)).catch((err) => {
|
|
120
|
+
process.stderr.write(`::error::${err.message}\n`);
|
|
121
|
+
process.exit(err.usage ? 2 : 1);
|
|
122
|
+
});
|
|
123
|
+
}
|