@dev-loops/core 0.2.1 → 0.2.3
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dev-loops/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared deterministic support package for dev-loop skills, repo-local scripts, and GitHub automation.",
|
|
6
6
|
"exports": {
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
"./cli/helpers": "./src/cli/helpers.mjs",
|
|
9
9
|
"./cli/primitives": "./src/cli/primitives.mjs",
|
|
10
10
|
"./cli/retry-wrapper": "./src/cli/retry-wrapper.mjs",
|
|
11
|
-
"./cli/subcommand-runner": "./src/cli/subcommand-runner.mjs",
|
|
12
11
|
"./claude/asset-generation": "./src/claude/asset-generation.mjs",
|
|
13
12
|
"./claude/headless-entry": "./src/claude/headless-entry.mjs",
|
|
14
13
|
"./claude/hook-decisions": "./src/claude/hook-decisions.mjs",
|
package/src/config/config.mjs
CHANGED
|
@@ -28,7 +28,7 @@ const ModelsConfig = z.strictObject({
|
|
|
28
28
|
const RefinementConfig = z.strictObject({
|
|
29
29
|
fanOut: z.number().int().min(1).max(10),
|
|
30
30
|
mode: z.enum(["parallel", "sequential"]),
|
|
31
|
-
maxCopilotRounds: z.number().int().
|
|
31
|
+
maxCopilotRounds: z.number().int().nonnegative().default(5),
|
|
32
32
|
stopOnLowSignal: z.boolean().default(false),
|
|
33
33
|
lowSignalRoundThreshold: z.number().int().nonnegative().default(3),
|
|
34
34
|
lowSignalMaxComments: z.number().int().nonnegative().default(2),
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* This module is intentionally pure and side-effect free.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { RUN_ID_MARKERS } from "./run-context.mjs";
|
|
26
|
+
import { RUN_ID_MARKERS, isClaudeHarness } from "./run-context.mjs";
|
|
27
27
|
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
29
29
|
// Constants
|
|
@@ -54,6 +54,39 @@ export const ASYNC_START_STATUS = Object.freeze({
|
|
|
54
54
|
REJECTED: "rejected",
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the effective async-start mode for the current harness.
|
|
59
|
+
*
|
|
60
|
+
* The async-start contract is configurable via `workflow.asyncStartMode`
|
|
61
|
+
* (`required` | `allowed`) — see defaults.yaml. It exists to stop the Pi
|
|
62
|
+
* harness from running the loop as a detached, uninspectable background
|
|
63
|
+
* process. Claude Code's Agent tool has no detached-process variant (each
|
|
64
|
+
* subagent run is visible and inspectable), so under the Claude harness a
|
|
65
|
+
* *recognized* mode is relaxed to `allowed` at runtime. An unrecognized
|
|
66
|
+
* (e.g. typo'd) `configuredMode` is returned verbatim — not relaxed — so
|
|
67
|
+
* `validateAsyncStartContext` still rejects it and the config error surfaces
|
|
68
|
+
* even under Claude. Pi behavior is unchanged: outside Claude the configured
|
|
69
|
+
* mode is always returned verbatim.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} configuredMode - Mode from workflow config; normally
|
|
72
|
+
* `"required"` | `"allowed"`, but any value is accepted and an unrecognized
|
|
73
|
+
* one is passed through unchanged for downstream validation.
|
|
74
|
+
* @param {Record<string, string|undefined>} [env]
|
|
75
|
+
* @returns {string} The effective mode (`"allowed"` when a recognized mode is
|
|
76
|
+
* relaxed under Claude; otherwise `configuredMode` verbatim).
|
|
77
|
+
*/
|
|
78
|
+
export function resolveEffectiveAsyncStartMode(configuredMode, env = process.env) {
|
|
79
|
+
// Only relax a recognized mode. An unrecognized configuredMode must pass through
|
|
80
|
+
// verbatim so validateAsyncStartContext still rejects it (surfacing the config
|
|
81
|
+
// error) rather than having the Claude relaxation silently mask a typo'd value.
|
|
82
|
+
const isRecognizedMode =
|
|
83
|
+
configuredMode === ASYNC_START_MODE.REQUIRED || configuredMode === ASYNC_START_MODE.ALLOWED;
|
|
84
|
+
if (isRecognizedMode && isClaudeHarness(env)) {
|
|
85
|
+
return ASYNC_START_MODE.ALLOWED;
|
|
86
|
+
}
|
|
87
|
+
return configuredMode;
|
|
88
|
+
}
|
|
89
|
+
|
|
57
90
|
// ---------------------------------------------------------------------------
|
|
58
91
|
// Validation
|
|
59
92
|
// ---------------------------------------------------------------------------
|
|
@@ -447,6 +447,12 @@ export function shouldGuardCopilotReviewRequest({
|
|
|
447
447
|
if (!gateBoundariesRequiringCopilotFormalRequest.has(gateBoundary)) {
|
|
448
448
|
return false;
|
|
449
449
|
}
|
|
450
|
+
// Copilot review disabled for the repo (maxCopilotRounds: 0): never force a
|
|
451
|
+
// formal request — the loop runs draft_gate → pre_approval with the local
|
|
452
|
+
// harness only. See evaluatePrGateCoordination (internal_only routing).
|
|
453
|
+
if (maxCopilotRounds === 0) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
450
456
|
if (copilotReviewRequestStatus !== "none") {
|
|
451
457
|
return false;
|
|
452
458
|
}
|
|
@@ -477,9 +483,14 @@ export function evaluatePrGateCoordination(input = {}) {
|
|
|
477
483
|
const prClosed = input.prClosed === true;
|
|
478
484
|
const prMerged = input.prMerged === true;
|
|
479
485
|
const sameHeadCleanConverged = input.sameHeadCleanConverged === true;
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
486
|
+
// maxCopilotRounds: 0 disables the external Copilot review gate entirely
|
|
487
|
+
// (for repos without Copilot / local-harness-only review). It reuses the
|
|
488
|
+
// existing internal_only routing — skip the Copilot cycle, go straight to
|
|
489
|
+
// pre_approval — so no separate skip path is needed.
|
|
490
|
+
const copilotReviewDisabled = input.maxCopilotRounds === 0;
|
|
491
|
+
const reviewMode = copilotReviewDisabled
|
|
492
|
+
? "internal_only"
|
|
493
|
+
: (typeof input.reviewMode === "string" ? input.reviewMode.trim().toLowerCase() : null);
|
|
483
494
|
const mergeStateStatus = normalizeMergeStateStatus(input.mergeStateStatus);
|
|
484
495
|
const conflictFiles = normalizeConflictFiles(input.conflictFiles);
|
|
485
496
|
const ciStatus = normalizeCiStatus(input.ciStatus);
|
|
@@ -813,7 +824,9 @@ export function evaluatePrGateCoordination(input = {}) {
|
|
|
813
824
|
allowedNextActions,
|
|
814
825
|
forbiddenActions,
|
|
815
826
|
nextAction: PR_CHECKPOINT_ACTION.AWAIT_FINAL_HUMAN_APPROVAL,
|
|
816
|
-
reason:
|
|
827
|
+
reason: copilotReviewDisabled
|
|
828
|
+
? "Copilot review is disabled for this repo (maxCopilotRounds: 0); with clean draft_gate evidence and current-head clean pre_approval_gate, the PR is ready for final human approval."
|
|
829
|
+
: "This is an explicitly internal-only PR with clean draft_gate evidence and current-head clean pre_approval_gate, so it is ready for final human approval.",
|
|
817
830
|
mergeStateStatus,
|
|
818
831
|
conflictFiles,
|
|
819
832
|
refinementArtifact,
|
|
@@ -835,7 +848,9 @@ export function evaluatePrGateCoordination(input = {}) {
|
|
|
835
848
|
allowedNextActions,
|
|
836
849
|
forbiddenActions,
|
|
837
850
|
nextAction: PR_CHECKPOINT_ACTION.RUN_PRE_APPROVAL_GATE,
|
|
838
|
-
reason:
|
|
851
|
+
reason: copilotReviewDisabled
|
|
852
|
+
? "Copilot review is disabled for this repo (maxCopilotRounds: 0), so `pre_approval_gate` is the next legal boundary instead of an external Copilot review cycle."
|
|
853
|
+
: "This is an explicitly internal-only PR, so `pre_approval_gate` is the next legal boundary instead of an external Copilot review cycle.",
|
|
839
854
|
mergeStateStatus,
|
|
840
855
|
conflictFiles,
|
|
841
856
|
refinementArtifact,
|
package/src/loop/run-context.mjs
CHANGED
|
@@ -34,6 +34,27 @@ export const PI_RUN_ID_ALIAS_VAR = "PI_SUBAGENT_RUN_ID";
|
|
|
34
34
|
/** State-file name (under `.pi/`, consistent with existing dev-loop checkpoint files). */
|
|
35
35
|
export const RUN_CONTEXT_FILENAME = "dev-loop-run-context.json";
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Env var Claude Code sets in every tool/subagent shell it spawns.
|
|
39
|
+
* Used as the harness signal — see `isClaudeHarness`.
|
|
40
|
+
*/
|
|
41
|
+
export const CLAUDE_HARNESS_MARKER = "CLAUDECODE";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* True when running under the Claude Code harness.
|
|
45
|
+
*
|
|
46
|
+
* Claude Code sets `CLAUDECODE=1` in the environment of every Bash tool and
|
|
47
|
+
* subagent it spawns. This is the harness-detection seam used to relax
|
|
48
|
+
* Pi-specific runtime contracts (e.g. the async-start contract) that do not
|
|
49
|
+
* apply to Claude's execution model.
|
|
50
|
+
*
|
|
51
|
+
* @param {Record<string, string|undefined>} [env]
|
|
52
|
+
* @returns {boolean}
|
|
53
|
+
*/
|
|
54
|
+
export function isClaudeHarness(env = process.env) {
|
|
55
|
+
return env?.[CLAUDE_HARNESS_MARKER] === "1";
|
|
56
|
+
}
|
|
57
|
+
|
|
37
58
|
/**
|
|
38
59
|
* Resolve the active run id from the environment, neutral marker first.
|
|
39
60
|
*
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared subcommand runner for standardizing CLI script boilerplate.
|
|
3
|
-
* Extracted per issue #548 Phase 3.
|
|
4
|
-
*
|
|
5
|
-
* Replaces per-script: USAGE string, parseError, arg-parsing loop,
|
|
6
|
-
* removed-flags handling, and direct-invocation boilerplate.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { buildParseError, isDirectCliRun } from "./helpers.mjs";
|
|
10
|
-
import { requireOptionValue, parsePrNumber, parseIssueNumber,
|
|
11
|
-
parsePositiveInteger, parseNonNegativeInteger } from "./primitives.mjs";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Option descriptor for defineSubcommand.
|
|
15
|
-
*
|
|
16
|
-
* @typedef {Object} CliOption
|
|
17
|
-
* @property {string} flag - e.g. "--repo"
|
|
18
|
-
* @property {string} [key] - override output key name; defaults to dashed→camelCase (e.g. "--head-sha" → "headSha")
|
|
19
|
-
* @property {string} [valueName] - human-readable value name for usage (ignored for boolean flags)
|
|
20
|
-
* @property {string} [description] - help text
|
|
21
|
-
* @property {"string"|"number"|"boolean"|"pr"|"issue"|"positiveInt"|"nonNegativeInt"} [type] - default "string"
|
|
22
|
-
* @property {boolean} [required] - default false
|
|
23
|
-
* @property {*} [default] - default value
|
|
24
|
-
* @property {string[]} [choices] - allowed values
|
|
25
|
-
* @property {string[]} [removedAliases] - flags that should be rejected with a message
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
/** Convert a dashed flag name (e.g. "--head-sha") to camelCase (e.g. "headSha"). */
|
|
29
|
-
function dashedToCamel(flag) {
|
|
30
|
-
const stem = flag.replace(/^--/, "");
|
|
31
|
-
return stem.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Return the parsed output key for an option. Respects opt.key override. */
|
|
35
|
-
function optionKey(opt) {
|
|
36
|
-
if (opt.key) return opt.key;
|
|
37
|
-
return dashedToCamel(opt.flag);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Render a single option's usage fragment for the generated help text. */
|
|
41
|
-
function optionUsageFragment(opt) {
|
|
42
|
-
if (opt.type === "boolean") return opt.flag;
|
|
43
|
-
const vn = opt.valueName || opt.flag.replace(/^--/, "").replace(/-/g, "_").toUpperCase();
|
|
44
|
-
return `${opt.flag} <${vn}>`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Define a subcommand with auto-generated usage, arg parsing, and help.
|
|
49
|
-
*
|
|
50
|
-
* @param {Object} def
|
|
51
|
-
* @param {string} def.name - subcommand name for usage
|
|
52
|
-
* @param {string} def.description - one-line description
|
|
53
|
-
* @param {string} [def.longDescription] - extended help text
|
|
54
|
-
* @param {CliOption[]} def.options - option descriptors
|
|
55
|
-
* @param {Function} def.run - async (parsed, { args, stdout, stderr }) => exitCode
|
|
56
|
-
* @param {Object} [def.extraUsage] - extra usage lines
|
|
57
|
-
* @param {Object} [def.outputSchema] - stdout JSON schema description
|
|
58
|
-
* @returns {{ parseArgs, runAsScript, usage, parseError }}
|
|
59
|
-
*/
|
|
60
|
-
export function defineSubcommand(def) {
|
|
61
|
-
const {
|
|
62
|
-
name,
|
|
63
|
-
description,
|
|
64
|
-
longDescription = "",
|
|
65
|
-
options = [],
|
|
66
|
-
run,
|
|
67
|
-
extraUsage = {},
|
|
68
|
-
outputSchema = null,
|
|
69
|
-
} = def;
|
|
70
|
-
|
|
71
|
-
// Auto-build usage string
|
|
72
|
-
const requiredOpts = options.filter((o) => o.required);
|
|
73
|
-
const optionalOpts = options.filter((o) => !o.required);
|
|
74
|
-
|
|
75
|
-
const usageLines = [`Usage: dev-loops ${name}`];
|
|
76
|
-
for (const opt of requiredOpts) {
|
|
77
|
-
usageLines.push(` ${optionUsageFragment(opt)}`);
|
|
78
|
-
}
|
|
79
|
-
if (optionalOpts.length > 0) {
|
|
80
|
-
const optStrs = optionalOpts.map((o) => optionUsageFragment(o));
|
|
81
|
-
usageLines.push(` [${optStrs.join("] [")}]`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (description) usageLines.push("", description);
|
|
85
|
-
if (longDescription) usageLines.push("", longDescription);
|
|
86
|
-
|
|
87
|
-
if (requiredOpts.length > 0) {
|
|
88
|
-
usageLines.push("", "Required:");
|
|
89
|
-
for (const opt of requiredOpts) {
|
|
90
|
-
usageLines.push(` ${optionUsageFragment(opt)}${opt.description ? ` ${opt.description}` : ""}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (optionalOpts.length > 0) {
|
|
95
|
-
usageLines.push("", "Optional:");
|
|
96
|
-
for (const opt of optionalOpts) {
|
|
97
|
-
usageLines.push(` ${optionUsageFragment(opt)}${opt.description ? ` ${opt.description}` : ""}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (extraUsage.before) usageLines.splice(1, 0, ...extraUsage.before);
|
|
102
|
-
if (extraUsage.after) usageLines.push(...extraUsage.after);
|
|
103
|
-
|
|
104
|
-
if (outputSchema) {
|
|
105
|
-
usageLines.push("", "Output (stdout, JSON):", JSON.stringify(outputSchema, null, 2));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const usage = usageLines.join("\n");
|
|
109
|
-
const parseError = buildParseError(usage);
|
|
110
|
-
|
|
111
|
-
// Build removed-flags set
|
|
112
|
-
const removedFlags = new Set();
|
|
113
|
-
for (const opt of options) {
|
|
114
|
-
if (opt.removedAliases) {
|
|
115
|
-
for (const alias of opt.removedAliases) removedFlags.add(alias);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function parseValue(raw, opt) {
|
|
120
|
-
if (raw === undefined) return opt.default;
|
|
121
|
-
switch (opt.type) {
|
|
122
|
-
case "number": case "positiveInt": case "nonNegativeInt": {
|
|
123
|
-
if (opt.type === "positiveInt") return parsePositiveInteger(raw, opt.flag, parseError);
|
|
124
|
-
if (opt.type === "nonNegativeInt") return parseNonNegativeInteger(raw, opt.flag, parseError);
|
|
125
|
-
const n = Number(raw);
|
|
126
|
-
if (isNaN(n)) throw parseError(`${opt.flag} must be a number`);
|
|
127
|
-
return n;
|
|
128
|
-
}
|
|
129
|
-
case "pr": return parsePrNumber(raw, parseError);
|
|
130
|
-
case "issue": return parseIssueNumber(raw, parseError);
|
|
131
|
-
case "string":
|
|
132
|
-
default: {
|
|
133
|
-
const v = raw.trim();
|
|
134
|
-
if (opt.choices && !opt.choices.includes(v)) {
|
|
135
|
-
throw parseError(`${opt.flag} must be one of: ${opt.choices.join(", ")}`);
|
|
136
|
-
}
|
|
137
|
-
return v;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function parseArgs(argv) {
|
|
143
|
-
const args = [...argv];
|
|
144
|
-
const parsed = {};
|
|
145
|
-
// Initialize defaults for all options
|
|
146
|
-
for (const opt of options) {
|
|
147
|
-
if (opt.default !== undefined) {
|
|
148
|
-
parsed[optionKey(opt)] = opt.default;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
while (args.length > 0) {
|
|
153
|
-
const token = args.shift();
|
|
154
|
-
|
|
155
|
-
if (token === "--help" || token === "-h") {
|
|
156
|
-
return { help: true };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (removedFlags.has(token)) {
|
|
160
|
-
throw parseError(
|
|
161
|
-
`${token} has been removed. Omit the flag.`,
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const opt = options.find((o) => o.flag === token);
|
|
166
|
-
if (opt) {
|
|
167
|
-
if (opt.type === "boolean") {
|
|
168
|
-
// Boolean flags are presence-only: --flag sets true, no value required.
|
|
169
|
-
parsed[optionKey(opt)] = true;
|
|
170
|
-
} else {
|
|
171
|
-
const raw = requireOptionValue(args, opt.flag, parseError);
|
|
172
|
-
parsed[optionKey(opt)] = parseValue(raw, opt);
|
|
173
|
-
}
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
throw parseError(`Unknown argument: ${token}`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Validate option definitions
|
|
181
|
-
for (const opt of options) {
|
|
182
|
-
if (opt.required && opt.default !== undefined) {
|
|
183
|
-
throw new Error(`Option ${opt.flag}: 'required' and 'default' conflict`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Check required
|
|
188
|
-
for (const opt of requiredOpts) {
|
|
189
|
-
const key = optionKey(opt);
|
|
190
|
-
if (parsed[key] === undefined) {
|
|
191
|
-
throw parseError(`Missing required option: ${opt.flag}`);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return { parsed };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async function runAsScript(scriptArgv = process.argv.slice(2)) {
|
|
199
|
-
try {
|
|
200
|
-
const result = parseArgs(scriptArgv);
|
|
201
|
-
if (result.help) {
|
|
202
|
-
process.stdout.write(`${usage}\n`);
|
|
203
|
-
process.exitCode = 0;
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const code = await run(result.parsed, { args: scriptArgv, usage });
|
|
207
|
-
process.exitCode = typeof code === "number" ? code : 0;
|
|
208
|
-
} catch (error) {
|
|
209
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
210
|
-
if (error instanceof Error && typeof error.usage === "string") {
|
|
211
|
-
process.stderr.write(JSON.stringify({ ok: false, error: msg, usage: error.usage }) + "\n");
|
|
212
|
-
} else {
|
|
213
|
-
process.stderr.write(JSON.stringify({ ok: false, error: msg }) + "\n");
|
|
214
|
-
}
|
|
215
|
-
process.exitCode = 1;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return { parseArgs, runAsScript, usage, parseError };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Run a CLI function as the main entrypoint when the module is invoked directly.
|
|
224
|
-
* Handles process.exitCode and error formatting.
|
|
225
|
-
*
|
|
226
|
-
* Usage: replace `if (isDirectCliRun(import.meta.url)) { ... }` with:
|
|
227
|
-
* if (isDirectCliRun(import.meta.url)) { runAsMain(runCli); }
|
|
228
|
-
*
|
|
229
|
-
* @param {Function} fn - async function returning exit code or void
|
|
230
|
-
* @param {Object} [opts]
|
|
231
|
-
* @param {Function} [opts.formatError] - error formatter (default: JSON.stringify)
|
|
232
|
-
*/
|
|
233
|
-
export function runAsMain(fn, { formatError } = {}) {
|
|
234
|
-
Promise.resolve(fn()).then(
|
|
235
|
-
(code) => { process.exitCode = typeof code === "number" ? code : 0; },
|
|
236
|
-
(error) => {
|
|
237
|
-
const msg = formatError
|
|
238
|
-
? formatError(error)
|
|
239
|
-
: JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
240
|
-
process.stderr.write(`${msg}\n`);
|
|
241
|
-
process.exitCode = 1;
|
|
242
|
-
},
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export { isDirectCliRun };
|