jackherd 1.12.7 → 1.14.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.
- package/CHANGELOG.md +15 -0
- package/INSTALL.md +1 -1
- package/README.md +17 -17
- package/dist/config.d.ts +35 -11
- package/dist/config.js +49 -83
- package/herdr-plugin/herdr-plugin.toml +1 -1
- package/install.sh +1 -1
- package/kit/hooks/hooks/merge-guard/board-settings.ts +7 -32
- package/kit/hooks/hooks/merge-guard/lane-plan.ts +2 -29
- package/kit/hooks/hooks/plain-question/code-cache.ts +70 -0
- package/kit/hooks/hooks/plain-question/index.ts +80 -0
- package/kit/hooks/hooks/plain-question/run.ts +50 -0
- package/kit/hooks/hooks/shared/board-lanes.ts +19 -0
- package/kit/hooks/hooks/shared/boards.ts +96 -0
- package/kit/hooks/hooks/shared/plan-codes.ts +68 -0
- package/kit/hooks/hooks.json +9 -0
- package/kit/review/skills/diagnose/SKILL.md +2 -2
- package/kit/review/skills/diagnose/references/feedback-loops.md +4 -13
- package/kit/rules/rules/kit/code-style.md +15 -18
- package/kit/rules/rules/kit/testing.md +7 -28
- package/kit/workflow/skills/implement/SKILL.md +3 -3
- package/kit/workflow/skills/orchestrate/SKILL.md +4 -1
- package/kit/workflow/skills/orchestrate/references/events.md +6 -1
- package/kit/workflow/skills/orchestrate/references/plan-commit.md +2 -1
- package/kit/workflow/skills/start/SKILL.md +9 -6
- package/kit/workflow/skills/start/references/missing-commands.md +17 -0
- package/package.json +1 -1
- package/schemas/jackherd.schema.json +38 -11
- package/src/check/command.ts +167 -35
- package/src/check/file-kinds.ts +142 -0
- package/src/check/plan-structure-check.ts +34 -1
- package/src/check/plan.ts +230 -45
- package/src/check/report.ts +111 -3
- package/src/check/result.ts +13 -3
- package/src/check/roots.ts +47 -10
- package/src/check/schema.ts +20 -3
- package/src/check/settings.ts +25 -4
- package/src/check/skill-budget-defaults.ts +7 -4
- package/src/config/base-branch.ts +2 -0
- package/src/config/command.ts +32 -13
- package/src/config/detect.ts +242 -74
- package/src/config/location.ts +6 -0
- package/src/config/repository-facts.ts +32 -7
- package/src/config/repository-values.ts +162 -5
- package/src/config/schema.ts +34 -16
- package/src/lane-prompt/texts.ts +2 -2
- package/src/plan-format/bare-codes.ts +28 -0
- package/src/plans/lane-plans.ts +2 -1
- package/src/sync/command-context.ts +4 -2
- package/src/sync/herdr-plugin.ts +30 -6
- package/src/sync/init.ts +17 -16
- package/src/sync/render.ts +4 -6
- package/src/watch/command.ts +32 -1
- package/src/watch/lane-progress.ts +6 -1
- package/src/watch/lane-report.ts +10 -0
- package/src/watch/plan-missing-read.ts +47 -0
- package/src/watch/plan-missing.ts +43 -0
- package/src/watch/reference-paths.ts +1 -0
- package/src/watch/texts.ts +4 -0
- package/src/watch/watch-events.ts +7 -0
package/src/check/roots.ts
CHANGED
|
@@ -1,26 +1,63 @@
|
|
|
1
1
|
import { dirname } from 'node:path'
|
|
2
|
+
import { CLAUDE_DIRECTORY } from '../claude/paths'
|
|
3
|
+
import { parseManifest } from '../sync/manifest'
|
|
4
|
+
|
|
5
|
+
/** The manifest whose `workspaces` field gathers workspaces (npm, Yarn, Bun). */
|
|
6
|
+
export const WORKSPACE_MANIFEST = 'package.json'
|
|
7
|
+
|
|
8
|
+
/** pnpm lists its workspaces in a file of its own instead of `WORKSPACE_MANIFEST`. */
|
|
9
|
+
export const WORKSPACE_LIST_FILE = 'pnpm-workspace.yaml'
|
|
2
10
|
|
|
3
11
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* silently compiles `.tsx` against the wrong JSX runtime.
|
|
12
|
+
* Whether a parsed `WORKSPACE_MANIFEST` gathers workspaces. Such a root only holds packages: a test run from
|
|
13
|
+
* there walks the whole monorepo.
|
|
7
14
|
*/
|
|
8
|
-
export const
|
|
15
|
+
export const isWorkspaceManifest = (manifest: unknown): boolean =>
|
|
16
|
+
typeof manifest === 'object' && manifest !== null && Object.hasOwn(manifest, 'workspaces')
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The nearest ancestor holding the stack's root marker (`package.json`, `composer.json`), the repository
|
|
20
|
+
* root included. Tests and type checks run from there and nowhere else: Bun resolves `tsconfig.json`
|
|
21
|
+
* from the working directory, so descending below a package root silently compiles `.tsx` against the
|
|
22
|
+
* wrong JSX runtime.
|
|
23
|
+
*/
|
|
24
|
+
export const packageRootOf = (path: string, isProjectRoot: (dir: string) => boolean): string | null => {
|
|
9
25
|
let dir = dirname(path)
|
|
10
26
|
while (dir !== '.' && dir !== '/' && dir !== '') {
|
|
11
|
-
if (
|
|
27
|
+
if (isProjectRoot(dir)) return dir
|
|
12
28
|
dir = dirname(dir)
|
|
13
29
|
}
|
|
14
|
-
return
|
|
30
|
+
return isProjectRoot('.') ? '.' : null
|
|
15
31
|
}
|
|
16
32
|
|
|
17
|
-
export const packageRootsFor = (paths: readonly string[],
|
|
18
|
-
...new Set(paths.map((path) => packageRootOf(path,
|
|
33
|
+
export const packageRootsFor = (paths: readonly string[], isProjectRoot: (dir: string) => boolean): string[] => [
|
|
34
|
+
...new Set(paths.map((path) => packageRootOf(path, isProjectRoot)).filter((root): root is string => root !== null)),
|
|
19
35
|
]
|
|
20
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The repository paths of the files a parsed `.jackherd/lock.json` lists: copies whose tests run where the kit
|
|
39
|
+
* keeps their source. A missing or invalid lock lists none, so every changed source file needs its own test.
|
|
40
|
+
*/
|
|
41
|
+
export const kitCopyPaths = (manifest: unknown): string[] => {
|
|
42
|
+
const parsed = parseManifest(manifest)
|
|
43
|
+
return parsed.ok ? Object.keys(parsed.manifest.files).map((path) => `${CLAUDE_DIRECTORY}/${path}`) : []
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Whether a test run from `root` reaches `path`; from `.` it reaches every path. */
|
|
47
|
+
export const isUnder = (path: string, root: string): boolean => root === '.' || path.startsWith(`${root}/`)
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The deepest of `roots` above `path`; null when none is. Without a detected stack, a file no root marker holds
|
|
51
|
+
* belongs to the nearest `check.roots` entry, with the repository root `.` among them.
|
|
52
|
+
*/
|
|
53
|
+
export const nearestRootOf = (path: string, roots: readonly string[]): string | null =>
|
|
54
|
+
roots
|
|
55
|
+
.filter((root) => isUnder(path, root))
|
|
56
|
+
.reduce<string | null>((nearest, root) => (nearest === null || root.length > nearest.length ? root : nearest), null)
|
|
57
|
+
|
|
21
58
|
/**
|
|
22
59
|
* Directories with test files that belong to no package root, which would otherwise be tested by
|
|
23
|
-
* nobody. The
|
|
60
|
+
* nobody. The repository root `.` is excluded: a test run from there walks the whole repository.
|
|
24
61
|
*/
|
|
25
62
|
export const looseDirectories = (
|
|
26
63
|
paths: readonly string[],
|
|
@@ -29,7 +66,7 @@ export const looseDirectories = (
|
|
|
29
66
|
): string[] => [
|
|
30
67
|
...new Set(
|
|
31
68
|
paths
|
|
32
|
-
.filter((path) => !roots.some((root) => path
|
|
69
|
+
.filter((path) => !roots.some((root) => isUnder(path, root)))
|
|
33
70
|
.map((path) => dirname(path))
|
|
34
71
|
.filter((dir) => dir !== '.' && hasTests(dir)),
|
|
35
72
|
),
|
package/src/check/schema.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import { BUN_COMMANDS } from '../config/detect'
|
|
3
2
|
import { tokenize } from './command-line'
|
|
3
|
+
import { CHECK_KIND } from './result'
|
|
4
4
|
|
|
5
5
|
const nonEmpty = z.string().min(1)
|
|
6
6
|
|
|
@@ -20,6 +20,9 @@ const glob = nonEmpty.refine((value) => !/[{}[\]]/.test(value), 'Braces and brac
|
|
|
20
20
|
|
|
21
21
|
const globs = z.array(glob).min(1)
|
|
22
22
|
|
|
23
|
+
/** A directory from the repository root as the check compares it: `./crates/core/` reads as `crates/core`. */
|
|
24
|
+
const directory = nonEmpty.transform((value) => value.replace(/^(\.\/)+/, '').replace(/\/+$/, '') || '.')
|
|
25
|
+
|
|
23
26
|
/** Staged output that belongs to a build, never to a commit. */
|
|
24
27
|
export const DEFAULT_BUILD_ARTIFACT_GLOBS = [
|
|
25
28
|
'**/tsup.config.bundled_*',
|
|
@@ -40,6 +43,11 @@ const projectCheck = z.strictObject({
|
|
|
40
43
|
command,
|
|
41
44
|
/** Without globs the check runs on every change; with them, only when a changed path matches. */
|
|
42
45
|
globs: globs.optional(),
|
|
46
|
+
/**
|
|
47
|
+
* `tests` for a check that runs the project's tests, `types` for its type check; without one, a plain project check.
|
|
48
|
+
* A test run counts towards "no green without a test", and both run with the slow checks.
|
|
49
|
+
*/
|
|
50
|
+
kind: z.enum([CHECK_KIND.tests, CHECK_KIND.types]).optional(),
|
|
43
51
|
})
|
|
44
52
|
|
|
45
53
|
const generatorOwned = z.strictObject({
|
|
@@ -67,9 +75,9 @@ const skillBudgets = z.record(glob, z.number().int().positive())
|
|
|
67
75
|
export const checkConfigSchema = z.strictObject({
|
|
68
76
|
/**
|
|
69
77
|
* Run once over the changed files, which are appended as arguments. Separate from `commands.lint`,
|
|
70
|
-
* which lints the whole project.
|
|
78
|
+
* which lints the whole project. Detected from the repository when left out; without one, no lint runs.
|
|
71
79
|
*/
|
|
72
|
-
lintFilesCommand: command.
|
|
80
|
+
lintFilesCommand: command.optional(),
|
|
73
81
|
/** Project checks, run alongside lint; add repository-specific scripts here. */
|
|
74
82
|
checks: z.array(projectCheck).default([]),
|
|
75
83
|
generatorOwned: z.array(generatorOwned).default([]),
|
|
@@ -83,6 +91,15 @@ export const checkConfigSchema = z.strictObject({
|
|
|
83
91
|
skillBudgets: skillBudgets.optional(),
|
|
84
92
|
/** Also run tests in directories that hold test files but belong to no package. */
|
|
85
93
|
looseTests: z.boolean().default(false),
|
|
94
|
+
/**
|
|
95
|
+
* Package roots, as directories from the repository root, read only where no stack is detected: a changed file
|
|
96
|
+
* belongs to the nearest one above it, else to the repository root `.`.
|
|
97
|
+
*/
|
|
98
|
+
roots: z.array(directory).min(1).optional(),
|
|
99
|
+
/** The files a test covers; replaces the stack's pattern, or the rule of thumb where no stack is detected. */
|
|
100
|
+
sourceFiles: globs.optional(),
|
|
101
|
+
/** The test files; replaces the stack's pattern, or the rule of thumb where no stack is detected. */
|
|
102
|
+
testFiles: globs.optional(),
|
|
86
103
|
})
|
|
87
104
|
|
|
88
105
|
export type CheckConfig = z.infer<typeof checkConfigSchema>
|
package/src/check/settings.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { jackherdConfigSchema } from '../config'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
detectStack,
|
|
5
|
+
isDecided,
|
|
6
|
+
type RepositoryFacts,
|
|
7
|
+
type Stack,
|
|
8
|
+
type StackDetection,
|
|
9
|
+
withDetected,
|
|
10
|
+
} from '../config/detect'
|
|
4
11
|
import { PROJECT_CONFIG_PATH } from '../config/location'
|
|
5
12
|
import type { PlanLanguage } from '../vocabulary'
|
|
6
|
-
import type { PackageCommands } from './plan'
|
|
13
|
+
import type { NoStackFallback, PackageCommands } from './plan'
|
|
7
14
|
import { type CheckConfig, checkConfigSchema } from './schema'
|
|
8
15
|
|
|
9
16
|
export const CONFIG_PATH = PROJECT_CONFIG_PATH
|
|
@@ -16,13 +23,25 @@ export type CheckSettings = {
|
|
|
16
23
|
planLanguage: PlanLanguage
|
|
17
24
|
/** The branch on the remote whose plans count as merged. */
|
|
18
25
|
baseBranch: string
|
|
26
|
+
/** Where the lane worktrees live, below the main checkout: a board's plans are read there too. */
|
|
27
|
+
worktreeDirectory: string
|
|
28
|
+
/** What the lock files (without one, the manifests) decide: one stack, none, or several (ambiguous); `stackOf` reads the stack from it. */
|
|
29
|
+
stack: StackDetection
|
|
19
30
|
notes: string[]
|
|
20
31
|
}
|
|
21
32
|
|
|
33
|
+
/** The stack a detection decided, by lock file or manifest; null for none or several, where the check reads every stack's markers. */
|
|
34
|
+
export const stackOf = (detection: StackDetection): Stack | null => (isDecided(detection) ? detection.stack : null)
|
|
35
|
+
|
|
36
|
+
/** Without any detected stack: `check.roots` and the repository root `.` as fallback roots, `commands.lint` from the root. */
|
|
37
|
+
export const noStackFallbackOf = ({ stack, check, commands }: CheckSettings): NoStackFallback | null =>
|
|
38
|
+
stack.kind === 'none' ? { roots: [...(check.roots ?? []), '.'], lintCommand: commands.lint } : null
|
|
39
|
+
|
|
22
40
|
const commandsSchema = jackherdConfigSchema.shape.commands
|
|
23
41
|
const planDirectorySchema = jackherdConfigSchema.shape.planDirectory
|
|
24
42
|
const planLanguageSchema = jackherdConfigSchema.shape.planLanguage
|
|
25
43
|
const baseBranchSchema = jackherdConfigSchema.shape.baseBranch
|
|
44
|
+
const worktreeDirectorySchema = jackherdConfigSchema.shape.worktreeDirectory
|
|
26
45
|
|
|
27
46
|
/**
|
|
28
47
|
* The check runner's view of `.jackherd/config.json`: the shared `commands` block, `planDirectory`, `planLanguage` and `baseBranch` plus its own
|
|
@@ -32,13 +51,15 @@ const baseBranchSchema = jackherdConfigSchema.shape.baseBranch
|
|
|
32
51
|
export const parseCheckSettings = (raw: unknown, facts: RepositoryFacts): CheckSettings => {
|
|
33
52
|
const detected = withDetected(z.record(z.string(), z.unknown()).parse(raw), facts)
|
|
34
53
|
const object = z.record(z.string(), z.unknown()).parse(detected.raw)
|
|
35
|
-
const { test, typecheck } = commandsSchema.parse(object.commands)
|
|
54
|
+
const { test, typecheck, lint } = commandsSchema.parse(object.commands)
|
|
36
55
|
return {
|
|
37
|
-
commands: { test, typecheck },
|
|
56
|
+
commands: { test, typecheck, lint },
|
|
38
57
|
check: checkConfigSchema.parse(object.check ?? {}),
|
|
39
58
|
planDirectory: planDirectorySchema.parse(object.planDirectory),
|
|
40
59
|
planLanguage: planLanguageSchema.parse(object.planLanguage),
|
|
41
60
|
baseBranch: baseBranchSchema.parse(object.baseBranch),
|
|
61
|
+
worktreeDirectory: worktreeDirectorySchema.parse(object.worktreeDirectory),
|
|
62
|
+
stack: detectStack(facts.lockFiles, facts.manifests),
|
|
42
63
|
notes: detected.notes,
|
|
43
64
|
}
|
|
44
65
|
}
|
|
@@ -20,13 +20,15 @@ export const KIT_SKILL_BUDGETS: Readonly<Record<string, number>> = {
|
|
|
20
20
|
'.claude/skills/grill/SKILL.md': 685,
|
|
21
21
|
'.claude/skills/implement/SKILL.md': 2190,
|
|
22
22
|
// P33: the paw in the header chip, panes by tab and title, the lane in the context line of a passed-on question.
|
|
23
|
-
|
|
23
|
+
// Plain words: no plan code without its words in a short question.
|
|
24
|
+
// Any language: a pointer in § 0 and a section in events.md for `plan missing`.
|
|
25
|
+
'.claude/skills/orchestrate/SKILL.md': 7929,
|
|
24
26
|
'.claude/skills/orchestrate/references/acceptance.md': 200,
|
|
25
27
|
'.claude/skills/orchestrate/references/close-lane.md': 250,
|
|
26
28
|
'.claude/skills/orchestrate/references/coordinator-handover.md': 551,
|
|
27
29
|
'.claude/skills/orchestrate/references/draft-review.md': 300,
|
|
28
30
|
// P33: a source entry the user must read or decide becomes a row of what waits on them, and when it closes.
|
|
29
|
-
'.claude/skills/orchestrate/references/events.md':
|
|
31
|
+
'.claude/skills/orchestrate/references/events.md': 1166,
|
|
30
32
|
'.claude/skills/orchestrate/references/herd.md': 299,
|
|
31
33
|
'.claude/skills/orchestrate/references/lane-handover.md': 280,
|
|
32
34
|
'.claude/skills/orchestrate/references/lane-session.md': 280,
|
|
@@ -39,10 +41,11 @@ export const KIT_SKILL_BUDGETS: Readonly<Record<string, number>> = {
|
|
|
39
41
|
'.claude/skills/pre-commit/SKILL.md': 1003,
|
|
40
42
|
'.claude/skills/review/SKILL.md': 319,
|
|
41
43
|
'.claude/skills/security-audit/SKILL.md': 519,
|
|
42
|
-
|
|
44
|
+
// Any language: step 3 points to proposing the missing commands from the CI; the proposal itself is a reference.
|
|
45
|
+
'.claude/skills/start/SKILL.md': 630,
|
|
43
46
|
'.claude/skills/uncle-bob/SKILL.md': 466,
|
|
44
47
|
// P33: grows with the skill above.
|
|
45
|
-
'.claude/skills/orchestrate/SKILL.md + .claude/project/orchestrate.md':
|
|
48
|
+
'.claude/skills/orchestrate/SKILL.md + .claude/project/orchestrate.md': 8729,
|
|
46
49
|
'.claude/agents/devops.md': 104,
|
|
47
50
|
'.claude/agents/explore.md': 132,
|
|
48
51
|
'.claude/agents/implement.md': 303,
|
package/src/config/command.ts
CHANGED
|
@@ -2,35 +2,54 @@ import { homedir } from 'node:os'
|
|
|
2
2
|
import { mainCheckout, worktreeRoot } from '../plans/git'
|
|
3
3
|
import { CLI_NAME } from '../version/release'
|
|
4
4
|
import { readResolvedConfig } from './read-config'
|
|
5
|
-
import {
|
|
5
|
+
import { readRepositoryFacts } from './repository-facts'
|
|
6
|
+
import {
|
|
7
|
+
commandFindings,
|
|
8
|
+
formatRepositoryValues,
|
|
9
|
+
isRepositoryValue,
|
|
10
|
+
isRepositoryValueSet,
|
|
11
|
+
REPOSITORY_VALUE,
|
|
12
|
+
repositoryValuesOf,
|
|
13
|
+
valueFindings,
|
|
14
|
+
} from './repository-values'
|
|
6
15
|
|
|
7
|
-
const
|
|
16
|
+
export const CONFIG_EXIT = { printed: 0, failed: 1, usage: 2, unset: 3 } as const
|
|
8
17
|
|
|
9
|
-
const
|
|
18
|
+
const USAGE = `Usage: ${CLI_NAME} config [${Object.values(REPOSITORY_VALUE).join(' | ')}]
|
|
19
|
+
Exit codes: ${CONFIG_EXIT.printed} printed, ${CONFIG_EXIT.failed} failed, ${CONFIG_EXIT.usage} usage, ${CONFIG_EXIT.unset} the named value is not set (its finding goes to stderr).`
|
|
10
20
|
|
|
11
21
|
export type ConfigOutput = { log: (line: string) => void; error: (line: string) => void }
|
|
12
22
|
|
|
13
23
|
/**
|
|
14
24
|
* Prints the values this repository decides, as the config sets them or detection finds them: all of them, or
|
|
15
|
-
* the one `key` names. Notes about values the repository did not decide
|
|
16
|
-
* the
|
|
25
|
+
* the one `key` names. Notes about values the repository did not decide, and a finding for each command or lock
|
|
26
|
+
* file that is missing or contradicts the repository (`commandFindings`), go to `error`, so a key's output stays
|
|
27
|
+
* the bare value; for a `key`, only the finding about that key (the notes for `baseBranch`). A `key` whose value
|
|
28
|
+
* is unset prints nothing and returns `CONFIG_EXIT.unset`; its missing finding says where to set it.
|
|
17
29
|
*/
|
|
18
30
|
export const printRepositoryValues = (args: string[], roots: string[], home: string, output: ConfigOutput): number => {
|
|
19
31
|
const [key, extra] = args
|
|
20
32
|
if (extra !== undefined) {
|
|
21
33
|
output.error(USAGE)
|
|
22
|
-
return
|
|
34
|
+
return CONFIG_EXIT.usage
|
|
23
35
|
}
|
|
24
36
|
if (key !== undefined && !isRepositoryValue(key)) {
|
|
25
37
|
output.error(`Unknown key: ${key}\n${USAGE}`)
|
|
26
|
-
return
|
|
38
|
+
return CONFIG_EXIT.usage
|
|
27
39
|
}
|
|
28
|
-
const
|
|
29
|
-
|
|
40
|
+
const facts = readRepositoryFacts(roots[0])
|
|
41
|
+
const { config, notes } = readResolvedConfig(roots, home, facts)
|
|
30
42
|
const values = repositoryValuesOf(config)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
if (key === undefined) {
|
|
44
|
+
for (const note of [...notes, ...commandFindings(config, facts)]) output.error(note)
|
|
45
|
+
for (const line of formatRepositoryValues(values)) output.log(line)
|
|
46
|
+
return CONFIG_EXIT.printed
|
|
47
|
+
}
|
|
48
|
+
const diagnostics = key === REPOSITORY_VALUE.baseBranch ? notes : valueFindings(config, facts, key)
|
|
49
|
+
for (const line of diagnostics) output.error(line)
|
|
50
|
+
if (!isRepositoryValueSet(config, key)) return CONFIG_EXIT.unset
|
|
51
|
+
output.log(values[key])
|
|
52
|
+
return CONFIG_EXIT.printed
|
|
34
53
|
}
|
|
35
54
|
|
|
36
55
|
export const run = (args: string[]): number => {
|
|
@@ -40,6 +59,6 @@ export const run = (args: string[]): number => {
|
|
|
40
59
|
return printRepositoryValues(args, [worktreeRoot(cwd), mainCheckout(cwd)], homedir(), output)
|
|
41
60
|
} catch (error) {
|
|
42
61
|
output.error(`${CLI_NAME} config failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
43
|
-
return
|
|
62
|
+
return CONFIG_EXIT.failed
|
|
44
63
|
}
|
|
45
64
|
}
|