@vyuhlabs/dxkit 2.5.0 → 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 (179) hide show
  1. package/CHANGELOG.md +231 -0
  2. package/README.md +48 -28
  3. package/dist/analyzers/tools/graphify.d.ts.map +1 -1
  4. package/dist/analyzers/tools/graphify.js +9 -5
  5. package/dist/analyzers/tools/graphify.js.map +1 -1
  6. package/dist/analyzers/tools/tool-registry.d.ts +19 -1
  7. package/dist/analyzers/tools/tool-registry.d.ts.map +1 -1
  8. package/dist/analyzers/tools/tool-registry.js +50 -8
  9. package/dist/analyzers/tools/tool-registry.js.map +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +104 -11
  12. package/dist/cli.js.map +1 -1
  13. package/dist/constants.d.ts.map +1 -1
  14. package/dist/constants.js +0 -10
  15. package/dist/constants.js.map +1 -1
  16. package/dist/detect.d.ts.map +1 -1
  17. package/dist/detect.js +0 -15
  18. package/dist/detect.js.map +1 -1
  19. package/dist/doctor.d.ts +78 -1
  20. package/dist/doctor.d.ts.map +1 -1
  21. package/dist/doctor.js +509 -103
  22. package/dist/doctor.js.map +1 -1
  23. package/dist/generator.d.ts +1 -1
  24. package/dist/generator.d.ts.map +1 -1
  25. package/dist/generator.js +96 -135
  26. package/dist/generator.js.map +1 -1
  27. package/dist/hooks-cli.d.ts +20 -0
  28. package/dist/hooks-cli.d.ts.map +1 -0
  29. package/dist/hooks-cli.js +145 -0
  30. package/dist/hooks-cli.js.map +1 -0
  31. package/dist/languages/csharp.d.ts.map +1 -1
  32. package/dist/languages/csharp.js +5 -0
  33. package/dist/languages/csharp.js.map +1 -1
  34. package/dist/languages/go.d.ts.map +1 -1
  35. package/dist/languages/go.js +5 -0
  36. package/dist/languages/go.js.map +1 -1
  37. package/dist/languages/index.d.ts +43 -0
  38. package/dist/languages/index.d.ts.map +1 -1
  39. package/dist/languages/index.js +76 -0
  40. package/dist/languages/index.js.map +1 -1
  41. package/dist/languages/java.d.ts.map +1 -1
  42. package/dist/languages/java.js +5 -0
  43. package/dist/languages/java.js.map +1 -1
  44. package/dist/languages/kotlin.d.ts.map +1 -1
  45. package/dist/languages/kotlin.js +10 -0
  46. package/dist/languages/kotlin.js.map +1 -1
  47. package/dist/languages/python.d.ts.map +1 -1
  48. package/dist/languages/python.js +13 -0
  49. package/dist/languages/python.js.map +1 -1
  50. package/dist/languages/ruby.d.ts.map +1 -1
  51. package/dist/languages/ruby.js +5 -0
  52. package/dist/languages/ruby.js.map +1 -1
  53. package/dist/languages/rust.d.ts.map +1 -1
  54. package/dist/languages/rust.js +5 -0
  55. package/dist/languages/rust.js.map +1 -1
  56. package/dist/languages/types.d.ts +47 -0
  57. package/dist/languages/types.d.ts.map +1 -1
  58. package/dist/languages/typescript.d.ts.map +1 -1
  59. package/dist/languages/typescript.js +6 -0
  60. package/dist/languages/typescript.js.map +1 -1
  61. package/dist/prompts.d.ts.map +1 -1
  62. package/dist/prompts.js +0 -5
  63. package/dist/prompts.js.map +1 -1
  64. package/dist/setup-branch-protection.d.ts +34 -0
  65. package/dist/setup-branch-protection.d.ts.map +1 -0
  66. package/dist/setup-branch-protection.js +190 -0
  67. package/dist/setup-branch-protection.js.map +1 -0
  68. package/dist/setup-gh.d.ts +75 -0
  69. package/dist/setup-gh.d.ts.map +1 -0
  70. package/dist/setup-gh.js +213 -0
  71. package/dist/setup-gh.js.map +1 -0
  72. package/dist/setup-prebuild.d.ts +34 -0
  73. package/dist/setup-prebuild.d.ts.map +1 -0
  74. package/dist/setup-prebuild.js +181 -0
  75. package/dist/setup-prebuild.js.map +1 -0
  76. package/dist/ship-installers.d.ts +6 -0
  77. package/dist/ship-installers.d.ts.map +1 -1
  78. package/dist/ship-installers.js +135 -5
  79. package/dist/ship-installers.js.map +1 -1
  80. package/dist/tools-cli.d.ts.map +1 -1
  81. package/dist/tools-cli.js +45 -9
  82. package/dist/tools-cli.js.map +1 -1
  83. package/dist/types.d.ts +24 -6
  84. package/dist/types.d.ts.map +1 -1
  85. package/dist/update.d.ts +41 -0
  86. package/dist/update.d.ts.map +1 -1
  87. package/dist/update.js +154 -15
  88. package/dist/update.js.map +1 -1
  89. package/dist/upgrade.d.ts +88 -0
  90. package/dist/upgrade.d.ts.map +1 -0
  91. package/dist/upgrade.js +324 -0
  92. package/dist/upgrade.js.map +1 -0
  93. package/package.json +1 -1
  94. package/templates/.claude/skills/dxkit-action/SKILL.md +150 -0
  95. package/templates/.claude/skills/dxkit-config/SKILL.md +124 -0
  96. package/templates/.claude/skills/dxkit-fix/SKILL.md +165 -0
  97. package/templates/.claude/skills/dxkit-hooks/SKILL.md +109 -0
  98. package/templates/.claude/skills/dxkit-init/SKILL.md +93 -0
  99. package/templates/.claude/skills/dxkit-learn/SKILL.md +84 -0
  100. package/templates/.claude/skills/dxkit-onboard/SKILL.md +246 -0
  101. package/templates/.claude/skills/dxkit-reports/SKILL.md +111 -0
  102. package/templates/.claude/skills/dxkit-update/SKILL.md +164 -0
  103. package/templates/.devcontainer/devcontainer.json +13 -48
  104. package/templates/.devcontainer/post-create.sh +37 -8
  105. package/templates/AGENTS.md.template +137 -0
  106. package/templates/CLAUDE.md.template +16 -111
  107. package/dist/codebase-scanner.d.ts +0 -36
  108. package/dist/codebase-scanner.d.ts.map +0 -1
  109. package/dist/codebase-scanner.js +0 -687
  110. package/dist/codebase-scanner.js.map +0 -1
  111. package/templates/.claude/agents/doc-writer.md +0 -107
  112. package/templates/.claude/agents/knowledge-bot.md +0 -64
  113. package/templates/.claude/agents/onboarding.md +0 -62
  114. package/templates/.claude/agents/quality-reviewer.md +0 -85
  115. package/templates/.claude/agents-available/code-reviewer.md +0 -29
  116. package/templates/.claude/agents-available/codebase-explorer.md +0 -100
  117. package/templates/.claude/agents-available/dashboard-builder.md +0 -433
  118. package/templates/.claude/agents-available/debugger.md +0 -29
  119. package/templates/.claude/agents-available/dependency-mapper.md +0 -80
  120. package/templates/.claude/agents-available/dev-report.md +0 -108
  121. package/templates/.claude/agents-available/doc-writer.md +0 -107
  122. package/templates/.claude/agents-available/feature-builder.md +0 -163
  123. package/templates/.claude/agents-available/feature-planner.md +0 -185
  124. package/templates/.claude/agents-available/health-auditor.md +0 -95
  125. package/templates/.claude/agents-available/hooks-configurator.md +0 -211
  126. package/templates/.claude/agents-available/knowledge-bot.md +0 -62
  127. package/templates/.claude/agents-available/plan-executor.md +0 -133
  128. package/templates/.claude/agents-available/strategic-planner.md +0 -141
  129. package/templates/.claude/agents-available/test-gap-finder.md +0 -67
  130. package/templates/.claude/agents-available/test-writer.md +0 -34
  131. package/templates/.claude/agents-available/vulnerability-scanner.md +0 -173
  132. package/templates/.claude/commands/ask.md +0 -7
  133. package/templates/.claude/commands/build-feature.md +0 -26
  134. package/templates/.claude/commands/build.md.template +0 -30
  135. package/templates/.claude/commands/check.md.template +0 -43
  136. package/templates/.claude/commands/dashboard.md +0 -28
  137. package/templates/.claude/commands/deps.md +0 -15
  138. package/templates/.claude/commands/dev-report.md +0 -50
  139. package/templates/.claude/commands/docs.md +0 -21
  140. package/templates/.claude/commands/doctor.md +0 -29
  141. package/templates/.claude/commands/enable-agent.md +0 -12
  142. package/templates/.claude/commands/execute-plan.md +0 -25
  143. package/templates/.claude/commands/explore-codebase.md +0 -12
  144. package/templates/.claude/commands/export-pdf.md +0 -30
  145. package/templates/.claude/commands/feature.md +0 -25
  146. package/templates/.claude/commands/fix-issue.md +0 -12
  147. package/templates/.claude/commands/fix.md.template +0 -32
  148. package/templates/.claude/commands/health.md +0 -58
  149. package/templates/.claude/commands/help.md +0 -36
  150. package/templates/.claude/commands/learn.md +0 -48
  151. package/templates/.claude/commands/onboarding.md +0 -21
  152. package/templates/.claude/commands/plan.md +0 -20
  153. package/templates/.claude/commands/quality.md.template +0 -65
  154. package/templates/.claude/commands/session-end.md +0 -40
  155. package/templates/.claude/commands/session-start.md +0 -30
  156. package/templates/.claude/commands/setup-hooks.md +0 -18
  157. package/templates/.claude/commands/stealth-mode.md +0 -17
  158. package/templates/.claude/commands/test-gaps.md +0 -49
  159. package/templates/.claude/commands/test.md.template +0 -40
  160. package/templates/.claude/commands/vulnerabilities.md +0 -49
  161. package/templates/.claude/skills/build/SKILL.md.template +0 -90
  162. package/templates/.claude/skills/deploy/SKILL.md.template +0 -111
  163. package/templates/.claude/skills/deploy/references/gotchas.md +0 -5
  164. package/templates/.claude/skills/doctor/SKILL.md +0 -31
  165. package/templates/.claude/skills/gcloud/SKILL.md +0 -66
  166. package/templates/.claude/skills/gcloud/references/gotchas.md +0 -5
  167. package/templates/.claude/skills/learned/SKILL.md +0 -55
  168. package/templates/.claude/skills/learned/references/conventions.md +0 -11
  169. package/templates/.claude/skills/learned/references/deny-recommendations.md +0 -18
  170. package/templates/.claude/skills/learned/references/gotchas.md +0 -11
  171. package/templates/.claude/skills/pulumi/SKILL.md +0 -73
  172. package/templates/.claude/skills/quality/SKILL.md.template +0 -89
  173. package/templates/.claude/skills/quality/references/gotchas.md +0 -5
  174. package/templates/.claude/skills/review/SKILL.md.template +0 -74
  175. package/templates/.claude/skills/scaffold/SKILL.md.template +0 -113
  176. package/templates/.claude/skills/secrets/SKILL.md +0 -51
  177. package/templates/.claude/skills/session/SKILL.md +0 -32
  178. package/templates/.claude/skills/test/SKILL.md.template +0 -116
  179. package/templates/.claude/skills/test/references/gotchas.md +0 -5
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,127 +53,552 @@ 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
- trackDx(checkInfo('CLAUDE.md', fs.existsSync(path.join(cwd, 'CLAUDE.md'))));
115
- trackDx(checkInfo('.claude/settings.json', fs.existsSync(path.join(cwd, '.claude', 'settings.json'))));
116
- trackDx(checkInfo('.claude/skills/', fs.existsSync(path.join(cwd, '.claude', 'skills'))));
117
- trackDx(checkInfo('.claude/commands/', fs.existsSync(path.join(cwd, '.claude', 'commands'))));
118
- // .claude/rules/ is created only when an active language pack declares
119
- // a ruleFile. Pure-typescript projects skip this dir (typescript pack
120
- // has no ruleFile) — don't flag its absence as a scaffolding gap.
213
+ const DXKIT_SKILL_NAMES = [
214
+ 'dxkit-learn',
215
+ 'dxkit-init',
216
+ 'dxkit-config',
217
+ 'dxkit-hooks',
218
+ 'dxkit-reports',
219
+ 'dxkit-action',
220
+ 'dxkit-fix',
221
+ 'dxkit-update',
222
+ 'dxkit-onboard',
223
+ ];
224
+ const presentSkills = DXKIT_SKILL_NAMES.filter((name) => fs.existsSync(path.join(cwd, '.claude', 'skills', name, 'SKILL.md')));
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
+ });
121
239
  const expectsRules = manifest?.config?.languages &&
122
240
  (0, languages_1.activeLanguagesFromStack)(manifest.config).some((l) => l.ruleFile);
123
241
  if (expectsRules) {
124
- 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
+ });
125
256
  }
126
- trackDx(checkInfo('.claude/agents-available/', fs.existsSync(path.join(cwd, '.claude', 'agents-available'))));
127
- // Settings.json validity (only when the file exists; absence already
128
- // counted above).
129
257
  const settingsPath = path.join(cwd, '.claude', 'settings.json');
130
258
  if (fs.existsSync(settingsPath)) {
259
+ let valid = true;
131
260
  try {
132
261
  JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
133
- trackDx(checkInfo('settings.json is valid JSON', true));
134
262
  }
135
263
  catch {
136
- trackDx(checkInfo('settings.json is valid JSON', false));
264
+ valid = false;
137
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
+ });
138
279
  }
139
- // Evolving files. These start empty after `init` and accumulate as
140
- // Claude learns — their absence is mildly informational, not a
141
- // problem in any tier.
142
- console.log(''); // slop-ok
143
- logger.info('Agent DX — evolving files (start empty, fill over time):');
144
- trackDx(checkInfo('learned/gotchas.md', fs.existsSync(path.join(cwd, '.claude', 'skills', 'learned', 'references', 'gotchas.md'))));
145
- trackDx(checkInfo('learned/conventions.md', fs.existsSync(path.join(cwd, '.claude', 'skills', 'learned', 'references', 'conventions.md'))));
146
- trackDx(checkInfo('deny-recommendations.md', fs.existsSync(path.join(cwd, '.claude', 'skills', 'learned', 'references', 'deny-recommendations.md'))));
147
- // Toolchain CLIs — pack-driven via `LanguageSupport.cliBinaries`.
148
- // These are informational (a missing toolchain just disables that
149
- // language's analyzers — the rest of dxkit keeps working).
150
280
  if (manifest?.config?.languages) {
151
- console.log(''); // slop-ok
152
- logger.info('Agent DX — toolchains:');
153
281
  for (const lang of (0, languages_1.activeLanguagesFromStack)(manifest.config)) {
154
282
  for (const bin of lang.cliBinaries ?? []) {
155
- 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
+ });
156
296
  }
157
297
  }
158
- if (manifest.config.tools?.gcloud) {
159
- 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
+ });
160
409
  }
161
- if (manifest.config.tools?.infisical) {
162
- trackDx(checkInfo('infisical', commandAvailable('infisical')));
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);
501
+ }
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);
163
512
  }
164
513
  }
165
- // ─── Summary ───────────────────────────────────────────────────────────
514
+ // Summary
166
515
  console.log(''); // slop-ok
167
516
  logger.header('Results');
168
- if (result.reports.fail === 0) {
169
- 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`);
170
520
  }
171
521
  else {
172
- 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`);
173
523
  }
174
- const dxTotal = result.dx.pass + result.dx.fail;
524
+ const dx = report.summary.dx;
525
+ const dxTotal = dx.pass + dx.fail;
175
526
  if (dxTotal > 0) {
176
- if (result.dx.fail === 0) {
177
- 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`);
178
529
  }
179
530
  else {
180
- logger.warn(`Agent DX: ${result.dx.pass}/${dxTotal} — partial scaffolding`);
181
- console.log(''); // slop-ok
182
- if (!hasManifest) {
183
- logger.dim('💡 Run `vyuh-dxkit init` to enable Agent DX features (skills, agents, slash commands). Reports CLI works without it.');
184
- }
185
- else {
186
- logger.dim('💡 Run `vyuh-dxkit update` to refresh missing Agent DX files (the manifest already exists).');
187
- }
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).');
188
566
  }
189
567
  }
190
568
  console.log(''); // slop-ok
191
- // Exit non-zero only when the reports tier failed — DX-tier failures
192
- // are informational and shouldn't break CI scripts that gate on
193
- // `dxkit doctor`.
194
- 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') {
195
600
  process.exitCode = 1;
196
601
  }
602
+ return report;
197
603
  }
198
604
  //# sourceMappingURL=doctor.js.map