@vyuhlabs/dxkit 2.5.1 → 2.5.2

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