@vyuhlabs/dxkit 2.5.1 → 2.6.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 +318 -0
- package/README.md +150 -28
- package/dist/allowlist/categories.d.ts +120 -0
- package/dist/allowlist/categories.d.ts.map +1 -0
- package/dist/allowlist/categories.js +194 -0
- package/dist/allowlist/categories.js.map +1 -0
- package/dist/allowlist/cli.d.ts +95 -0
- package/dist/allowlist/cli.d.ts.map +1 -0
- package/dist/allowlist/cli.js +454 -0
- package/dist/allowlist/cli.js.map +1 -0
- package/dist/allowlist/diff.d.ts +67 -0
- package/dist/allowlist/diff.d.ts.map +1 -0
- package/dist/allowlist/diff.js +147 -0
- package/dist/allowlist/diff.js.map +1 -0
- package/dist/allowlist/file.d.ts +249 -0
- package/dist/allowlist/file.d.ts.map +1 -0
- package/dist/allowlist/file.js +497 -0
- package/dist/allowlist/file.js.map +1 -0
- package/dist/allowlist/gather.d.ts +61 -0
- package/dist/allowlist/gather.d.ts.map +1 -0
- package/dist/allowlist/gather.js +143 -0
- package/dist/allowlist/gather.js.map +1 -0
- package/dist/allowlist/hint.d.ts +80 -0
- package/dist/allowlist/hint.d.ts.map +1 -0
- package/dist/allowlist/hint.js +271 -0
- package/dist/allowlist/hint.js.map +1 -0
- package/dist/allowlist/inline.d.ts +149 -0
- package/dist/allowlist/inline.d.ts.map +1 -0
- package/dist/allowlist/inline.js +306 -0
- package/dist/allowlist/inline.js.map +1 -0
- package/dist/analyzers/tools/tool-registry.d.ts.map +1 -1
- package/dist/analyzers/tools/tool-registry.js +25 -8
- package/dist/analyzers/tools/tool-registry.js.map +1 -1
- package/dist/baseline/baseline-file.d.ts +7 -0
- package/dist/baseline/baseline-file.d.ts.map +1 -1
- package/dist/baseline/baseline-file.js +22 -1
- package/dist/baseline/baseline-file.js.map +1 -1
- package/dist/baseline/check-renderers.d.ts +13 -1
- package/dist/baseline/check-renderers.d.ts.map +1 -1
- package/dist/baseline/check-renderers.js +67 -1
- package/dist/baseline/check-renderers.js.map +1 -1
- package/dist/baseline/check.d.ts +33 -7
- package/dist/baseline/check.d.ts.map +1 -1
- package/dist/baseline/check.js +90 -64
- package/dist/baseline/check.js.map +1 -1
- package/dist/baseline/create.d.ts +35 -7
- package/dist/baseline/create.d.ts.map +1 -1
- package/dist/baseline/create.js +43 -5
- package/dist/baseline/create.js.map +1 -1
- package/dist/baseline/entry-to-located.d.ts +6 -1
- package/dist/baseline/entry-to-located.d.ts.map +1 -1
- package/dist/baseline/entry-to-located.js +20 -2
- package/dist/baseline/entry-to-located.js.map +1 -1
- package/dist/baseline/finding-identity.d.ts.map +1 -1
- package/dist/baseline/finding-identity.js +15 -13
- package/dist/baseline/finding-identity.js.map +1 -1
- package/dist/baseline/modes.d.ts +140 -0
- package/dist/baseline/modes.d.ts.map +1 -0
- package/dist/baseline/modes.js +179 -0
- package/dist/baseline/modes.js.map +1 -0
- package/dist/baseline/policy.d.ts +64 -0
- package/dist/baseline/policy.d.ts.map +1 -1
- package/dist/baseline/policy.js +102 -1
- package/dist/baseline/policy.js.map +1 -1
- package/dist/baseline/producers/health.d.ts +2 -2
- package/dist/baseline/producers/health.d.ts.map +1 -1
- package/dist/baseline/producers/health.js.map +1 -1
- package/dist/baseline/producers/index.d.ts +11 -5
- package/dist/baseline/producers/index.d.ts.map +1 -1
- package/dist/baseline/producers/index.js +12 -9
- package/dist/baseline/producers/index.js.map +1 -1
- package/dist/baseline/producers/quality.d.ts +3 -3
- package/dist/baseline/producers/quality.d.ts.map +1 -1
- package/dist/baseline/producers/quality.js.map +1 -1
- package/dist/baseline/producers/secret-hmac.d.ts +2 -2
- package/dist/baseline/producers/secret-hmac.d.ts.map +1 -1
- package/dist/baseline/producers/secret-hmac.js.map +1 -1
- package/dist/baseline/producers/security.d.ts +2 -2
- package/dist/baseline/producers/security.d.ts.map +1 -1
- package/dist/baseline/producers/security.js.map +1 -1
- package/dist/baseline/producers/stale-allow.d.ts +70 -0
- package/dist/baseline/producers/stale-allow.d.ts.map +1 -0
- package/dist/baseline/producers/stale-allow.js +111 -0
- package/dist/baseline/producers/stale-allow.js.map +1 -0
- package/dist/baseline/producers/tests.d.ts +2 -2
- package/dist/baseline/producers/tests.d.ts.map +1 -1
- package/dist/baseline/producers/tests.js.map +1 -1
- package/dist/baseline/ref-baseline.d.ts +114 -0
- package/dist/baseline/ref-baseline.d.ts.map +1 -0
- package/dist/baseline/ref-baseline.js +260 -0
- package/dist/baseline/ref-baseline.js.map +1 -0
- package/dist/baseline/sanitize.d.ts +80 -0
- package/dist/baseline/sanitize.d.ts.map +1 -0
- package/dist/baseline/sanitize.js +91 -0
- package/dist/baseline/sanitize.js.map +1 -0
- package/dist/baseline/show.d.ts.map +1 -1
- package/dist/baseline/show.js +9 -3
- package/dist/baseline/show.js.map +1 -1
- package/dist/baseline/types.d.ts +73 -26
- package/dist/baseline/types.d.ts.map +1 -1
- package/dist/baseline/types.js +7 -1
- package/dist/baseline/types.js.map +1 -1
- package/dist/baseline/visibility.d.ts +61 -0
- package/dist/baseline/visibility.d.ts.map +1 -0
- package/dist/baseline/visibility.js +121 -0
- package/dist/baseline/visibility.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +154 -13
- package/dist/cli.js.map +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +0 -10
- package/dist/constants.js.map +1 -1
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +0 -15
- package/dist/detect.js.map +1 -1
- package/dist/doctor.d.ts +78 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +590 -101
- package/dist/doctor.js.map +1 -1
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +15 -0
- package/dist/generator.js.map +1 -1
- package/dist/issue-cli.d.ts +62 -0
- package/dist/issue-cli.d.ts.map +1 -0
- package/dist/issue-cli.js +252 -0
- package/dist/issue-cli.js.map +1 -0
- package/dist/languages/csharp.d.ts.map +1 -1
- package/dist/languages/csharp.js +2 -0
- package/dist/languages/csharp.js.map +1 -1
- package/dist/languages/go.d.ts.map +1 -1
- package/dist/languages/go.js +2 -0
- package/dist/languages/go.js.map +1 -1
- package/dist/languages/index.d.ts +25 -0
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +44 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/java.d.ts.map +1 -1
- package/dist/languages/java.js +2 -0
- package/dist/languages/java.js.map +1 -1
- package/dist/languages/kotlin.d.ts.map +1 -1
- package/dist/languages/kotlin.js +2 -0
- package/dist/languages/kotlin.js.map +1 -1
- package/dist/languages/python.d.ts.map +1 -1
- package/dist/languages/python.js +11 -1
- package/dist/languages/python.js.map +1 -1
- package/dist/languages/ruby.d.ts.map +1 -1
- package/dist/languages/ruby.js +2 -0
- package/dist/languages/ruby.js.map +1 -1
- package/dist/languages/rust.d.ts.map +1 -1
- package/dist/languages/rust.js +2 -0
- package/dist/languages/rust.js.map +1 -1
- package/dist/languages/types.d.ts +45 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript.d.ts.map +1 -1
- package/dist/languages/typescript.js +2 -0
- package/dist/languages/typescript.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +0 -5
- package/dist/prompts.js.map +1 -1
- package/dist/setup-branch-protection.d.ts +34 -0
- package/dist/setup-branch-protection.d.ts.map +1 -0
- package/dist/setup-branch-protection.js +190 -0
- package/dist/setup-branch-protection.js.map +1 -0
- package/dist/setup-gh.d.ts +75 -0
- package/dist/setup-gh.d.ts.map +1 -0
- package/dist/setup-gh.js +213 -0
- package/dist/setup-gh.js.map +1 -0
- package/dist/setup-prebuild.d.ts +34 -0
- package/dist/setup-prebuild.d.ts.map +1 -0
- package/dist/setup-prebuild.js +181 -0
- package/dist/setup-prebuild.js.map +1 -0
- package/dist/ship-installers.d.ts.map +1 -1
- package/dist/ship-installers.js +19 -4
- package/dist/ship-installers.js.map +1 -1
- package/dist/types.d.ts +24 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/update.d.ts +41 -0
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +154 -15
- package/dist/update.js.map +1 -1
- package/dist/upgrade.d.ts +88 -0
- package/dist/upgrade.d.ts.map +1 -0
- package/dist/upgrade.js +324 -0
- package/dist/upgrade.js.map +1 -0
- package/package.json +1 -1
- package/templates/.claude/skills/dxkit-action/SKILL.md +111 -17
- package/templates/.claude/skills/dxkit-config/SKILL.md +7 -7
- package/templates/.claude/skills/dxkit-fix/SKILL.md +165 -0
- package/templates/.claude/skills/dxkit-hooks/SKILL.md +8 -8
- package/templates/.claude/skills/dxkit-init/SKILL.md +3 -3
- package/templates/.claude/skills/dxkit-learn/SKILL.md +9 -9
- package/templates/.claude/skills/dxkit-onboard/SKILL.md +274 -0
- package/templates/.claude/skills/dxkit-reports/SKILL.md +18 -18
- package/templates/.claude/skills/dxkit-update/SKILL.md +164 -0
- package/templates/.devcontainer/devcontainer.json +6 -15
- package/templates/.devcontainer/post-create.sh +19 -4
- package/dist/baseline/producers/licenses.d.ts +0 -23
- package/dist/baseline/producers/licenses.d.ts.map +0 -1
- package/dist/baseline/producers/licenses.js +0 -46
- package/dist/baseline/producers/licenses.js.map +0 -1
package/dist/doctor.js
CHANGED
|
@@ -39,25 +39,8 @@ const path = __importStar(require("path"));
|
|
|
39
39
|
const child_process_1 = require("child_process");
|
|
40
40
|
const languages_1 = require("./languages");
|
|
41
41
|
const logger = __importStar(require("./logger"));
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
logger.success(label);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
logger.fail(label);
|
|
48
|
-
}
|
|
49
|
-
return condition;
|
|
50
|
-
}
|
|
51
|
-
/** Informational variant of `check` — failures render as warn, not fail. */
|
|
52
|
-
function checkInfo(label, condition) {
|
|
53
|
-
if (condition) {
|
|
54
|
-
logger.success(label);
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
logger.warn(label);
|
|
58
|
-
}
|
|
59
|
-
return condition;
|
|
60
|
-
}
|
|
42
|
+
const modes_1 = require("./baseline/modes");
|
|
43
|
+
const policy_1 = require("./baseline/policy");
|
|
61
44
|
function commandAvailable(cmd) {
|
|
62
45
|
try {
|
|
63
46
|
(0, child_process_1.execSync)(`which ${cmd} 2>/dev/null`, { stdio: 'pipe' });
|
|
@@ -72,55 +55,206 @@ function nodeMajorVersion() {
|
|
|
72
55
|
const m = raw.match(/^(\d+)/);
|
|
73
56
|
return m ? parseInt(m[1], 10) : 0;
|
|
74
57
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Wrap `loadPolicyFromCwd` with a swallowing try/catch — doctor
|
|
60
|
+
* checks must never throw on a malformed policy file (the customer
|
|
61
|
+
* would be unable to run doctor to discover that very fact). The
|
|
62
|
+
* "policy unreadable" condition surfaces separately via the
|
|
63
|
+
* existing scanner-toolchain / hooks checks.
|
|
64
|
+
*/
|
|
65
|
+
function safeLoadPolicy(cwd) {
|
|
66
|
+
try {
|
|
67
|
+
return (0, policy_1.loadPolicyFromCwd)(cwd);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Detect mode/visibility misalignment when the customer has pinned
|
|
75
|
+
* `committed-full` on a public repo. Returns null when the pin is
|
|
76
|
+
* fine. Only fires when the pin came from policy or CLI — auto-
|
|
77
|
+
* picked modes are always aligned by definition.
|
|
78
|
+
*/
|
|
79
|
+
function detectModeMisalignment(mode) {
|
|
80
|
+
// We can't read visibility directly here without triggering a
|
|
81
|
+
// duplicate `gh` probe. Instead we rely on the resolver's audit
|
|
82
|
+
// trail: when the customer pins `committed-full` AND the
|
|
83
|
+
// visibility-derived default would have been ref-based, that's
|
|
84
|
+
// the misalignment we want to flag. The resolver doesn't tell us
|
|
85
|
+
// what the auto-picker WOULD have chosen, so we re-probe here —
|
|
86
|
+
// the second probe is cache-warm and free.
|
|
87
|
+
if (mode.mode !== 'committed-full')
|
|
88
|
+
return null;
|
|
89
|
+
// Lazy import so non-baseline doctor runs don't pay the gh probe.
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
91
|
+
const { detectRepoVisibility } = require('./baseline/visibility');
|
|
92
|
+
const visibility = detectRepoVisibility(process.cwd());
|
|
93
|
+
if (visibility !== 'public')
|
|
94
|
+
return null;
|
|
95
|
+
return {
|
|
96
|
+
label: 'baseline mode pinned committed-full on a public repo',
|
|
97
|
+
hint: 'Public repos auto-pick ref-based for a reason: committed-full leaks file paths + package names + advisory IDs to anyone reading the repo. Switch to ref-based or committed-sanitized in .dxkit/policy.json.',
|
|
98
|
+
command: 'edit .dxkit/policy.json: set baseline.mode to ref-based or committed-sanitized',
|
|
80
99
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* `git config --local --get core.hooksPath` returns the configured
|
|
103
|
+
* hooksPath for the current repo, or non-zero if unset. dxkit's
|
|
104
|
+
* pre-push hook lives at `.githooks/pre-push` and only fires when
|
|
105
|
+
* hooksPath is set to `.githooks`. A repo with its own postinstall
|
|
106
|
+
* script (patch-package, husky bootstrap, etc.) silently skips the
|
|
107
|
+
* dxkit auto-activation; this check surfaces that gap.
|
|
108
|
+
*/
|
|
109
|
+
function readHooksPath(cwd) {
|
|
110
|
+
try {
|
|
111
|
+
const out = (0, child_process_1.execSync)('git config --local --get core.hooksPath', {
|
|
112
|
+
cwd,
|
|
113
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
114
|
+
});
|
|
115
|
+
return out.toString().trim() || null;
|
|
86
116
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
result.dx.pass++;
|
|
90
|
-
else
|
|
91
|
-
result.dx.fail++;
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
92
119
|
}
|
|
93
|
-
|
|
94
|
-
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Count failing scanner-tool installs by reading the cached
|
|
123
|
+
* dxkit-tools-status sentinel that `vyuh-dxkit tools install --yes`
|
|
124
|
+
* writes. We avoid re-running `tools list` here because it spawns
|
|
125
|
+
* subprocess probes for every tool (slow) and doctor should stay
|
|
126
|
+
* fast. The sentinel lives at `.dxkit/tools-status.json` and reflects
|
|
127
|
+
* the last `tools install` outcome.
|
|
128
|
+
*
|
|
129
|
+
* Returns `{ found: false }` if the sentinel doesn't exist — the
|
|
130
|
+
* check then renders as "unknown" (warn, not fail) because we can't
|
|
131
|
+
* tell. Returns `{ found: true, failed: [...] }` otherwise.
|
|
132
|
+
*/
|
|
133
|
+
function readToolsStatus(cwd) {
|
|
134
|
+
const statusPath = path.join(cwd, '.dxkit', 'tools-status.json');
|
|
135
|
+
if (!fs.existsSync(statusPath))
|
|
136
|
+
return { found: false };
|
|
137
|
+
try {
|
|
138
|
+
const data = JSON.parse(fs.readFileSync(statusPath, 'utf-8'));
|
|
139
|
+
const failed = (data.tools ?? [])
|
|
140
|
+
.filter((t) => t.status === 'missing' || t.status === 'failed')
|
|
141
|
+
.map((t) => t.name);
|
|
142
|
+
return { found: true, failed };
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return { found: true, failed: [] };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Detect whether the package.json install would hit a peer-dep ERESOLVE
|
|
150
|
+
* that requires `legacy-peer-deps=true` in `.npmrc`. We don't run
|
|
151
|
+
* `npm install --dry-run` here (too slow, hits the network). Instead we
|
|
152
|
+
* read the persistence sentinel: if `.npmrc` already has the entry,
|
|
153
|
+
* we're good. If it's missing AND the host has a package.json (i.e.
|
|
154
|
+
* Node project), flag it as "potentially needed" — informational only.
|
|
155
|
+
*
|
|
156
|
+
* The fix command is idempotent so spuriously suggesting it on a
|
|
157
|
+
* package without peer-dep conflicts is harmless.
|
|
158
|
+
*/
|
|
159
|
+
function npmrcHasLegacyPeerDeps(cwd) {
|
|
160
|
+
const npmrcPath = path.join(cwd, '.npmrc');
|
|
161
|
+
if (!fs.existsSync(npmrcPath))
|
|
162
|
+
return false;
|
|
163
|
+
try {
|
|
164
|
+
const lines = fs.readFileSync(npmrcPath, 'utf-8').split('\n');
|
|
165
|
+
return lines.some((l) => l.trim() === 'legacy-peer-deps=true');
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// ────────────────────────────────────────────────────────────────────
|
|
172
|
+
// Tier builders — each returns a CheckResult[]. Pure: no logging side
|
|
173
|
+
// effects. The renderer below produces the prose/JSON output.
|
|
174
|
+
// ────────────────────────────────────────────────────────────────────
|
|
175
|
+
function runReportsChecks() {
|
|
95
176
|
const nodeMajor = nodeMajorVersion();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
177
|
+
return [
|
|
178
|
+
{
|
|
179
|
+
label: `Node ≥ 18 (running ${process.versions.node})`,
|
|
180
|
+
ok: nodeMajor >= 18,
|
|
181
|
+
tier: 'reports',
|
|
182
|
+
...(nodeMajor >= 18
|
|
183
|
+
? {}
|
|
184
|
+
: {
|
|
185
|
+
fix: {
|
|
186
|
+
hint: `Upgrade Node to v18 or newer. dxkit uses Node 22 in its devcontainer.`,
|
|
187
|
+
command: 'nvm install 22 && nvm use 22',
|
|
188
|
+
},
|
|
189
|
+
}),
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
label: 'git',
|
|
193
|
+
ok: commandAvailable('git'),
|
|
194
|
+
tier: 'reports',
|
|
195
|
+
...(commandAvailable('git')
|
|
196
|
+
? {}
|
|
197
|
+
: {
|
|
198
|
+
fix: {
|
|
199
|
+
hint: 'Install git — dxkit reads git history for fingerprinting + baseline metadata.',
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
function runDxChecks(cwd, manifest, hasManifest) {
|
|
206
|
+
const checks = [];
|
|
207
|
+
checks.push({
|
|
208
|
+
label: '.vyuh-dxkit.json exists',
|
|
209
|
+
ok: hasManifest,
|
|
210
|
+
tier: 'dx',
|
|
211
|
+
...(hasManifest
|
|
212
|
+
? {}
|
|
213
|
+
: {
|
|
214
|
+
fix: {
|
|
215
|
+
hint: 'Run `vyuh-dxkit init` to scaffold the manifest + Agent DX surface.',
|
|
216
|
+
command: 'npx vyuh-dxkit init --full --yes',
|
|
217
|
+
skill: 'dxkit-init',
|
|
218
|
+
},
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
105
221
|
if (hasManifest) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
222
|
+
checks.push({
|
|
223
|
+
label: '.vyuh-dxkit.json is valid JSON',
|
|
224
|
+
ok: manifest !== null,
|
|
225
|
+
tier: 'dx',
|
|
226
|
+
...(manifest !== null
|
|
227
|
+
? {}
|
|
228
|
+
: {
|
|
229
|
+
fix: {
|
|
230
|
+
hint: 'Fix the JSON syntax in `.vyuh-dxkit.json`, or regenerate via `vyuh-dxkit update --force`.',
|
|
231
|
+
command: 'npx vyuh-dxkit update --force',
|
|
232
|
+
},
|
|
233
|
+
}),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
const dxFiles = [
|
|
237
|
+
{ label: 'AGENTS.md', relpath: 'AGENTS.md' },
|
|
238
|
+
{ label: 'CLAUDE.md', relpath: 'CLAUDE.md' },
|
|
239
|
+
{ label: '.claude/settings.json', relpath: path.join('.claude', 'settings.json') },
|
|
240
|
+
];
|
|
241
|
+
for (const { label, relpath } of dxFiles) {
|
|
242
|
+
const ok = fs.existsSync(path.join(cwd, relpath));
|
|
243
|
+
checks.push({
|
|
244
|
+
label,
|
|
245
|
+
ok,
|
|
246
|
+
tier: 'dx',
|
|
247
|
+
...(ok
|
|
248
|
+
? {}
|
|
249
|
+
: {
|
|
250
|
+
fix: {
|
|
251
|
+
hint: 'Re-run `vyuh-dxkit init --with-dxkit-agents --yes` to land the missing Agent DX files.',
|
|
252
|
+
command: 'npx vyuh-dxkit init --with-dxkit-agents --yes',
|
|
253
|
+
skill: 'dxkit-init',
|
|
254
|
+
},
|
|
255
|
+
}),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
124
258
|
const DXKIT_SKILL_NAMES = [
|
|
125
259
|
'dxkit-learn',
|
|
126
260
|
'dxkit-init',
|
|
@@ -128,78 +262,433 @@ async function runDoctor(cwd) {
|
|
|
128
262
|
'dxkit-hooks',
|
|
129
263
|
'dxkit-reports',
|
|
130
264
|
'dxkit-action',
|
|
265
|
+
'dxkit-fix',
|
|
266
|
+
'dxkit-update',
|
|
267
|
+
'dxkit-onboard',
|
|
131
268
|
];
|
|
132
269
|
const presentSkills = DXKIT_SKILL_NAMES.filter((name) => fs.existsSync(path.join(cwd, '.claude', 'skills', name, 'SKILL.md')));
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
270
|
+
const allSkillsOk = presentSkills.length === DXKIT_SKILL_NAMES.length;
|
|
271
|
+
checks.push({
|
|
272
|
+
label: `.claude/skills/dxkit-* (${presentSkills.length}/${DXKIT_SKILL_NAMES.length})`,
|
|
273
|
+
ok: allSkillsOk,
|
|
274
|
+
tier: 'dx',
|
|
275
|
+
...(allSkillsOk
|
|
276
|
+
? {}
|
|
277
|
+
: {
|
|
278
|
+
fix: {
|
|
279
|
+
hint: `${DXKIT_SKILL_NAMES.length - presentSkills.length} dxkit-* skill(s) missing. Re-run init or update.`,
|
|
280
|
+
command: 'npx vyuh-dxkit update',
|
|
281
|
+
},
|
|
282
|
+
}),
|
|
283
|
+
});
|
|
137
284
|
const expectsRules = manifest?.config?.languages &&
|
|
138
285
|
(0, languages_1.activeLanguagesFromStack)(manifest.config).some((l) => l.ruleFile);
|
|
139
286
|
if (expectsRules) {
|
|
140
|
-
|
|
287
|
+
const ok = fs.existsSync(path.join(cwd, '.claude', 'rules'));
|
|
288
|
+
checks.push({
|
|
289
|
+
label: '.claude/rules/',
|
|
290
|
+
ok,
|
|
291
|
+
tier: 'dx',
|
|
292
|
+
...(ok
|
|
293
|
+
? {}
|
|
294
|
+
: {
|
|
295
|
+
fix: {
|
|
296
|
+
hint: 'Per-language rule files missing. Re-run init or update.',
|
|
297
|
+
command: 'npx vyuh-dxkit update',
|
|
298
|
+
},
|
|
299
|
+
}),
|
|
300
|
+
});
|
|
141
301
|
}
|
|
142
|
-
// Settings.json validity (only when the file exists; absence already
|
|
143
|
-
// counted above).
|
|
144
302
|
const settingsPath = path.join(cwd, '.claude', 'settings.json');
|
|
145
303
|
if (fs.existsSync(settingsPath)) {
|
|
304
|
+
let valid = true;
|
|
146
305
|
try {
|
|
147
306
|
JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
148
|
-
trackDx(checkInfo('settings.json is valid JSON', true));
|
|
149
307
|
}
|
|
150
308
|
catch {
|
|
151
|
-
|
|
309
|
+
valid = false;
|
|
152
310
|
}
|
|
311
|
+
checks.push({
|
|
312
|
+
label: 'settings.json is valid JSON',
|
|
313
|
+
ok: valid,
|
|
314
|
+
tier: 'dx',
|
|
315
|
+
...(valid
|
|
316
|
+
? {}
|
|
317
|
+
: {
|
|
318
|
+
fix: {
|
|
319
|
+
hint: 'Fix syntax errors in `.claude/settings.json`, or regenerate via `vyuh-dxkit update --force`.',
|
|
320
|
+
command: 'npx vyuh-dxkit update --force',
|
|
321
|
+
},
|
|
322
|
+
}),
|
|
323
|
+
});
|
|
153
324
|
}
|
|
154
|
-
// Toolchain CLIs — pack-driven via `LanguageSupport.cliBinaries`.
|
|
155
|
-
// These are informational (a missing toolchain just disables that
|
|
156
|
-
// language's analyzers — the rest of dxkit keeps working).
|
|
157
325
|
if (manifest?.config?.languages) {
|
|
158
|
-
console.log(''); // slop-ok
|
|
159
|
-
logger.info('Agent DX — toolchains:');
|
|
160
326
|
for (const lang of (0, languages_1.activeLanguagesFromStack)(manifest.config)) {
|
|
161
327
|
for (const bin of lang.cliBinaries ?? []) {
|
|
162
|
-
|
|
328
|
+
const ok = commandAvailable(bin);
|
|
329
|
+
checks.push({
|
|
330
|
+
label: bin,
|
|
331
|
+
ok,
|
|
332
|
+
tier: 'dx',
|
|
333
|
+
...(ok
|
|
334
|
+
? {}
|
|
335
|
+
: {
|
|
336
|
+
fix: {
|
|
337
|
+
hint: `${bin} not on PATH — ${lang.id} analyzers will skip until it's available.`,
|
|
338
|
+
},
|
|
339
|
+
}),
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return checks;
|
|
345
|
+
}
|
|
346
|
+
function runOperationalChecks(cwd, hasManifest) {
|
|
347
|
+
const checks = [];
|
|
348
|
+
// 1. Hooks active. `git config core.hooksPath` should be `.githooks`
|
|
349
|
+
// when dxkit's pre-push protection is active. Empty → init's auto-
|
|
350
|
+
// activation didn't fire (most commonly because the repo's existing
|
|
351
|
+
// postinstall script preserved priority).
|
|
352
|
+
const hooksPath = readHooksPath(cwd);
|
|
353
|
+
const hookFileExists = fs.existsSync(path.join(cwd, '.githooks', 'pre-push'));
|
|
354
|
+
if (hookFileExists) {
|
|
355
|
+
const active = hooksPath === '.githooks';
|
|
356
|
+
checks.push({
|
|
357
|
+
label: 'git hooks active (core.hooksPath = .githooks)',
|
|
358
|
+
ok: active,
|
|
359
|
+
tier: 'operational',
|
|
360
|
+
...(active
|
|
361
|
+
? {}
|
|
362
|
+
: {
|
|
363
|
+
fix: {
|
|
364
|
+
hint: 'Activate the pre-push hook so dxkit guards regressions before push.',
|
|
365
|
+
command: 'npx vyuh-dxkit hooks activate',
|
|
366
|
+
skill: 'dxkit-hooks',
|
|
367
|
+
},
|
|
368
|
+
}),
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
// 2. Baseline captured. Without a baseline, `guardrail check`
|
|
372
|
+
// fails-fast on every push. In `ref-based` mode the file is NOT
|
|
373
|
+
// expected on disk — the prior side is recomputed from a git ref
|
|
374
|
+
// — so the check passes when mode is ref-based AND the resolver
|
|
375
|
+
// can identify the ref.
|
|
376
|
+
if (hasManifest) {
|
|
377
|
+
const policy = safeLoadPolicy(cwd);
|
|
378
|
+
const mode = (0, modes_1.resolveBaselineMode)({
|
|
379
|
+
cwd,
|
|
380
|
+
policyMode: policy?.baseline?.mode,
|
|
381
|
+
policyRef: policy?.baseline?.ref,
|
|
382
|
+
});
|
|
383
|
+
if (mode.mode === 'ref-based') {
|
|
384
|
+
checks.push({
|
|
385
|
+
label: `baseline mode: ref-based (ref: ${mode.ref ?? 'origin/main'})`,
|
|
386
|
+
ok: true,
|
|
387
|
+
tier: 'operational',
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
const baselinePath = path.join(cwd, '.dxkit', 'baselines', 'main.json');
|
|
392
|
+
const exists = fs.existsSync(baselinePath);
|
|
393
|
+
checks.push({
|
|
394
|
+
label: `baseline captured (.dxkit/baselines/main.json, mode: ${mode.mode})`,
|
|
395
|
+
ok: exists,
|
|
396
|
+
tier: 'operational',
|
|
397
|
+
...(exists
|
|
398
|
+
? {}
|
|
399
|
+
: {
|
|
400
|
+
fix: {
|
|
401
|
+
hint: "Capture today's state as the brownfield baseline. Existing findings get locked in; only net-new ones block thereafter.",
|
|
402
|
+
command: 'npx vyuh-dxkit baseline create',
|
|
403
|
+
skill: 'dxkit-init',
|
|
404
|
+
},
|
|
405
|
+
}),
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
// 2b. Baseline mode aligned with repo visibility. Warns when an
|
|
409
|
+
// explicit `committed-full` pin is in use on a public repo (the
|
|
410
|
+
// posture leaks file paths / package names / advisory IDs to
|
|
411
|
+
// anyone with read access). The auto-picker would have chosen
|
|
412
|
+
// ref-based; an explicit pin says the customer overrode that on
|
|
413
|
+
// purpose, so this is informational not failure.
|
|
414
|
+
if (mode.source === 'policy' || mode.source === 'cli') {
|
|
415
|
+
const alignmentWarning = detectModeMisalignment(mode);
|
|
416
|
+
if (alignmentWarning) {
|
|
417
|
+
checks.push({
|
|
418
|
+
label: alignmentWarning.label,
|
|
419
|
+
ok: false,
|
|
420
|
+
tier: 'operational',
|
|
421
|
+
fix: {
|
|
422
|
+
hint: alignmentWarning.hint,
|
|
423
|
+
command: alignmentWarning.command,
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
checks.push({
|
|
429
|
+
label: `baseline mode aligned with repo visibility`,
|
|
430
|
+
ok: true,
|
|
431
|
+
tier: 'operational',
|
|
432
|
+
});
|
|
163
433
|
}
|
|
164
434
|
}
|
|
165
|
-
|
|
166
|
-
|
|
435
|
+
}
|
|
436
|
+
// 3. PATH integrity. The bare `vyuh-dxkit` command must resolve in
|
|
437
|
+
// the customer's interactive shell — half the dxkit-* skill prose
|
|
438
|
+
// uses bare invocations (auto-adapted by Claude Code but broken for
|
|
439
|
+
// human shells + other agents).
|
|
440
|
+
const onPath = commandAvailable('vyuh-dxkit');
|
|
441
|
+
checks.push({
|
|
442
|
+
label: 'vyuh-dxkit on PATH',
|
|
443
|
+
ok: onPath,
|
|
444
|
+
tier: 'operational',
|
|
445
|
+
...(onPath
|
|
446
|
+
? {}
|
|
447
|
+
: {
|
|
448
|
+
fix: {
|
|
449
|
+
hint: 'Install dxkit globally so the bare command resolves in your shell.',
|
|
450
|
+
command: 'npm install -g @vyuhlabs/dxkit',
|
|
451
|
+
skill: 'dxkit-fix',
|
|
452
|
+
},
|
|
453
|
+
}),
|
|
454
|
+
});
|
|
455
|
+
// 4. Scanner toolchain healthy. Reads the cached tools-status.json
|
|
456
|
+
// sentinel from the last `tools install` run. If absent, we don't
|
|
457
|
+
// flag — first-run case where the customer hasn't run install yet.
|
|
458
|
+
const toolsStatus = readToolsStatus(cwd);
|
|
459
|
+
if (toolsStatus.found && toolsStatus.failed.length > 0) {
|
|
460
|
+
checks.push({
|
|
461
|
+
label: `scanner toolchain (${toolsStatus.failed.length} missing: ${toolsStatus.failed.slice(0, 3).join(', ')}${toolsStatus.failed.length > 3 ? ', …' : ''})`,
|
|
462
|
+
ok: false,
|
|
463
|
+
tier: 'operational',
|
|
464
|
+
fix: {
|
|
465
|
+
hint: 'Re-run scanner-tool install — pinned versions live in TOOL_DEFS.',
|
|
466
|
+
command: 'npx vyuh-dxkit tools install --yes',
|
|
467
|
+
skill: 'dxkit-fix',
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
else if (toolsStatus.found) {
|
|
472
|
+
checks.push({
|
|
473
|
+
label: 'scanner toolchain healthy',
|
|
474
|
+
ok: true,
|
|
475
|
+
tier: 'operational',
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
// 5. .npmrc peer-deps state. Only flag on Node projects where the
|
|
479
|
+
// entry is missing — informational because we can't cheaply prove
|
|
480
|
+
// it's NEEDED without a dry-run install.
|
|
481
|
+
const isNodeProject = fs.existsSync(path.join(cwd, 'package.json'));
|
|
482
|
+
if (isNodeProject) {
|
|
483
|
+
const hasEntry = npmrcHasLegacyPeerDeps(cwd);
|
|
484
|
+
// Only emit the check if the entry is missing — saves clutter on
|
|
485
|
+
// the common case where the customer doesn't have peer-dep
|
|
486
|
+
// conflicts. (Idempotent fix means a false-positive flag is
|
|
487
|
+
// harmless if the customer follows it.)
|
|
488
|
+
if (!hasEntry) {
|
|
489
|
+
checks.push({
|
|
490
|
+
label: '.npmrc legacy-peer-deps persistence',
|
|
491
|
+
ok: false,
|
|
492
|
+
tier: 'operational',
|
|
493
|
+
fix: {
|
|
494
|
+
hint: 'If create-dxkit fell back to --legacy-peer-deps, persist the choice to .npmrc so future installs work.',
|
|
495
|
+
command: 'echo "legacy-peer-deps=true" >> .npmrc',
|
|
496
|
+
skill: 'dxkit-fix',
|
|
497
|
+
},
|
|
498
|
+
});
|
|
167
499
|
}
|
|
168
|
-
|
|
169
|
-
|
|
500
|
+
}
|
|
501
|
+
// 6. CI workflows wired. Only relevant for Agent DX customers who
|
|
502
|
+
// ran init --with-ci. dxkit-guardrails.yml is the PR gate.
|
|
503
|
+
if (hasManifest) {
|
|
504
|
+
const guardrailWf = path.join(cwd, '.github', 'workflows', 'dxkit-guardrails.yml');
|
|
505
|
+
const ok = fs.existsSync(guardrailWf);
|
|
506
|
+
checks.push({
|
|
507
|
+
label: 'CI guardrails workflow (.github/workflows/dxkit-guardrails.yml)',
|
|
508
|
+
ok,
|
|
509
|
+
tier: 'operational',
|
|
510
|
+
...(ok
|
|
511
|
+
? {}
|
|
512
|
+
: {
|
|
513
|
+
fix: {
|
|
514
|
+
hint: 'Scaffold the dxkit-guardrails GitHub Actions workflow so PRs run the guardrail check.',
|
|
515
|
+
command: 'npx vyuh-dxkit init --with-ci --yes',
|
|
516
|
+
skill: 'dxkit-init',
|
|
517
|
+
},
|
|
518
|
+
}),
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
return checks;
|
|
522
|
+
}
|
|
523
|
+
// ────────────────────────────────────────────────────────────────────
|
|
524
|
+
// Renderers
|
|
525
|
+
// ────────────────────────────────────────────────────────────────────
|
|
526
|
+
function buildReport(cwd, checks) {
|
|
527
|
+
const byTier = {
|
|
528
|
+
reports: checks.filter((c) => c.tier === 'reports'),
|
|
529
|
+
dx: checks.filter((c) => c.tier === 'dx'),
|
|
530
|
+
operational: checks.filter((c) => c.tier === 'operational'),
|
|
531
|
+
};
|
|
532
|
+
const tally = (arr) => ({
|
|
533
|
+
pass: arr.filter((c) => c.ok).length,
|
|
534
|
+
fail: arr.filter((c) => !c.ok).length,
|
|
535
|
+
});
|
|
536
|
+
const reportsTally = tally(byTier.reports);
|
|
537
|
+
const dxTally = tally(byTier.dx);
|
|
538
|
+
const opTally = tally(byTier.operational);
|
|
539
|
+
return {
|
|
540
|
+
schema: 'doctor.v1',
|
|
541
|
+
generatedAt: new Date().toISOString(),
|
|
542
|
+
cwd,
|
|
543
|
+
checks,
|
|
544
|
+
summary: {
|
|
545
|
+
reports: {
|
|
546
|
+
...reportsTally,
|
|
547
|
+
status: reportsTally.fail === 0 ? 'ok' : 'fail',
|
|
548
|
+
},
|
|
549
|
+
dx: {
|
|
550
|
+
...dxTally,
|
|
551
|
+
status: byTier.dx.length === 0 ? 'absent' : dxTally.fail === 0 ? 'ok' : 'partial',
|
|
552
|
+
},
|
|
553
|
+
operational: {
|
|
554
|
+
...opTally,
|
|
555
|
+
status: byTier.operational.length === 0
|
|
556
|
+
? 'ok'
|
|
557
|
+
: opTally.fail === 0
|
|
558
|
+
? 'ok'
|
|
559
|
+
: opTally.fail === byTier.operational.length
|
|
560
|
+
? 'fail'
|
|
561
|
+
: 'partial',
|
|
562
|
+
},
|
|
563
|
+
fixable: checks.filter((c) => !c.ok && c.fix),
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function renderProse(report, hasManifest) {
|
|
568
|
+
logger.header('vyuh-dxkit doctor');
|
|
569
|
+
const byTier = {
|
|
570
|
+
reports: report.checks.filter((c) => c.tier === 'reports'),
|
|
571
|
+
dx: report.checks.filter((c) => c.tier === 'dx'),
|
|
572
|
+
operational: report.checks.filter((c) => c.tier === 'operational'),
|
|
573
|
+
};
|
|
574
|
+
// Tier 1
|
|
575
|
+
logger.info('Reports prerequisites (required to run any dxkit command):');
|
|
576
|
+
for (const c of byTier.reports) {
|
|
577
|
+
if (c.ok)
|
|
578
|
+
logger.success(c.label);
|
|
579
|
+
else
|
|
580
|
+
logger.fail(c.label);
|
|
581
|
+
}
|
|
582
|
+
// Tier 2
|
|
583
|
+
if (byTier.dx.length > 0) {
|
|
584
|
+
console.log(''); // slop-ok
|
|
585
|
+
logger.info('Agent DX prerequisites (only required for `init`-generated artifacts):');
|
|
586
|
+
for (const c of byTier.dx) {
|
|
587
|
+
if (c.ok)
|
|
588
|
+
logger.success(c.label);
|
|
589
|
+
else
|
|
590
|
+
logger.warn(c.label);
|
|
170
591
|
}
|
|
171
592
|
}
|
|
172
|
-
//
|
|
593
|
+
// Tier 3
|
|
594
|
+
if (byTier.operational.length > 0) {
|
|
595
|
+
console.log(''); // slop-ok
|
|
596
|
+
logger.info('Operational health (runtime state of this install):');
|
|
597
|
+
for (const c of byTier.operational) {
|
|
598
|
+
if (c.ok)
|
|
599
|
+
logger.success(c.label);
|
|
600
|
+
else
|
|
601
|
+
logger.warn(c.label);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
// Summary
|
|
173
605
|
console.log(''); // slop-ok
|
|
174
606
|
logger.header('Results');
|
|
175
|
-
|
|
176
|
-
|
|
607
|
+
const r = report.summary.reports;
|
|
608
|
+
if (r.status === 'ok') {
|
|
609
|
+
logger.success(`Reports: ${r.pass}/${r.pass + r.fail} — ready to run dxkit`);
|
|
177
610
|
}
|
|
178
611
|
else {
|
|
179
|
-
logger.fail(`Reports: ${
|
|
612
|
+
logger.fail(`Reports: ${r.pass}/${r.pass + r.fail} — fix the failures above before running other dxkit commands`);
|
|
180
613
|
}
|
|
181
|
-
const
|
|
614
|
+
const dx = report.summary.dx;
|
|
615
|
+
const dxTotal = dx.pass + dx.fail;
|
|
182
616
|
if (dxTotal > 0) {
|
|
183
|
-
if (
|
|
184
|
-
logger.success(`Agent DX: ${
|
|
617
|
+
if (dx.status === 'ok') {
|
|
618
|
+
logger.success(`Agent DX: ${dx.pass}/${dxTotal} — fully scaffolded`);
|
|
185
619
|
}
|
|
186
620
|
else {
|
|
187
|
-
logger.warn(`Agent DX: ${
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
621
|
+
logger.warn(`Agent DX: ${dx.pass}/${dxTotal} — partial scaffolding`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const op = report.summary.operational;
|
|
625
|
+
const opTotal = op.pass + op.fail;
|
|
626
|
+
if (opTotal > 0) {
|
|
627
|
+
if (op.status === 'ok') {
|
|
628
|
+
logger.success(`Operational health: ${op.pass}/${opTotal} — install is wired end-to-end`);
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
logger.warn(`Operational health: ${op.pass}/${opTotal} — ${op.fail} issue(s) to address`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// Fix hints — render when ANY tier has actionable failures.
|
|
635
|
+
if (report.summary.fixable.length > 0) {
|
|
636
|
+
console.log(''); // slop-ok
|
|
637
|
+
logger.info('Suggested fixes:');
|
|
638
|
+
for (const c of report.summary.fixable) {
|
|
639
|
+
const cmd = c.fix?.command ? ` → ${c.fix.command}` : '';
|
|
640
|
+
logger.dim(`• ${c.label}: ${c.fix?.hint ?? ''}`);
|
|
641
|
+
if (cmd)
|
|
642
|
+
logger.dim(cmd);
|
|
643
|
+
}
|
|
644
|
+
console.log(''); // slop-ok
|
|
645
|
+
logger.dim('💡 Ask Claude Code "fix dxkit" to walk through these via the dxkit-fix skill.');
|
|
646
|
+
}
|
|
647
|
+
else if (dxTotal > 0 && dx.status !== 'ok') {
|
|
648
|
+
// Legacy hint preserved for existing customers — only shows if no
|
|
649
|
+
// structured fix-list is available.
|
|
650
|
+
console.log(''); // slop-ok
|
|
651
|
+
if (!hasManifest) {
|
|
652
|
+
logger.dim('💡 Run `vyuh-dxkit init` to enable Agent DX features (skills, agents, slash commands). Reports CLI works without it.');
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
logger.dim('💡 Run `vyuh-dxkit update` to refresh missing Agent DX files (the manifest already exists).');
|
|
195
656
|
}
|
|
196
657
|
}
|
|
197
658
|
console.log(''); // slop-ok
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
659
|
+
}
|
|
660
|
+
// ────────────────────────────────────────────────────────────────────
|
|
661
|
+
// Entry point
|
|
662
|
+
// ────────────────────────────────────────────────────────────────────
|
|
663
|
+
async function runDoctor(cwd, opts = {}) {
|
|
664
|
+
const manifestPath = path.join(cwd, '.vyuh-dxkit.json');
|
|
665
|
+
const hasManifest = fs.existsSync(manifestPath);
|
|
666
|
+
let manifest = null;
|
|
667
|
+
if (hasManifest) {
|
|
668
|
+
try {
|
|
669
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
670
|
+
}
|
|
671
|
+
catch {
|
|
672
|
+
manifest = null;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const checks = [
|
|
676
|
+
...runReportsChecks(),
|
|
677
|
+
...runDxChecks(cwd, manifest, hasManifest),
|
|
678
|
+
...runOperationalChecks(cwd, hasManifest),
|
|
679
|
+
];
|
|
680
|
+
const report = buildReport(cwd, checks);
|
|
681
|
+
if (opts.json) {
|
|
682
|
+
// Logger is already in stderr mode (setJsonMode was called by cli.ts);
|
|
683
|
+
// stdout stays pure JSON for downstream consumption.
|
|
684
|
+
console.log(JSON.stringify(report, null, 2)); // slop-ok
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
renderProse(report, hasManifest);
|
|
688
|
+
}
|
|
689
|
+
if (report.summary.reports.status === 'fail') {
|
|
202
690
|
process.exitCode = 1;
|
|
203
691
|
}
|
|
692
|
+
return report;
|
|
204
693
|
}
|
|
205
694
|
//# sourceMappingURL=doctor.js.map
|