@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.
Files changed (200) hide show
  1. package/CHANGELOG.md +318 -0
  2. package/README.md +150 -28
  3. package/dist/allowlist/categories.d.ts +120 -0
  4. package/dist/allowlist/categories.d.ts.map +1 -0
  5. package/dist/allowlist/categories.js +194 -0
  6. package/dist/allowlist/categories.js.map +1 -0
  7. package/dist/allowlist/cli.d.ts +95 -0
  8. package/dist/allowlist/cli.d.ts.map +1 -0
  9. package/dist/allowlist/cli.js +454 -0
  10. package/dist/allowlist/cli.js.map +1 -0
  11. package/dist/allowlist/diff.d.ts +67 -0
  12. package/dist/allowlist/diff.d.ts.map +1 -0
  13. package/dist/allowlist/diff.js +147 -0
  14. package/dist/allowlist/diff.js.map +1 -0
  15. package/dist/allowlist/file.d.ts +249 -0
  16. package/dist/allowlist/file.d.ts.map +1 -0
  17. package/dist/allowlist/file.js +497 -0
  18. package/dist/allowlist/file.js.map +1 -0
  19. package/dist/allowlist/gather.d.ts +61 -0
  20. package/dist/allowlist/gather.d.ts.map +1 -0
  21. package/dist/allowlist/gather.js +143 -0
  22. package/dist/allowlist/gather.js.map +1 -0
  23. package/dist/allowlist/hint.d.ts +80 -0
  24. package/dist/allowlist/hint.d.ts.map +1 -0
  25. package/dist/allowlist/hint.js +271 -0
  26. package/dist/allowlist/hint.js.map +1 -0
  27. package/dist/allowlist/inline.d.ts +149 -0
  28. package/dist/allowlist/inline.d.ts.map +1 -0
  29. package/dist/allowlist/inline.js +306 -0
  30. package/dist/allowlist/inline.js.map +1 -0
  31. package/dist/analyzers/tools/tool-registry.d.ts.map +1 -1
  32. package/dist/analyzers/tools/tool-registry.js +25 -8
  33. package/dist/analyzers/tools/tool-registry.js.map +1 -1
  34. package/dist/baseline/baseline-file.d.ts +7 -0
  35. package/dist/baseline/baseline-file.d.ts.map +1 -1
  36. package/dist/baseline/baseline-file.js +22 -1
  37. package/dist/baseline/baseline-file.js.map +1 -1
  38. package/dist/baseline/check-renderers.d.ts +13 -1
  39. package/dist/baseline/check-renderers.d.ts.map +1 -1
  40. package/dist/baseline/check-renderers.js +67 -1
  41. package/dist/baseline/check-renderers.js.map +1 -1
  42. package/dist/baseline/check.d.ts +33 -7
  43. package/dist/baseline/check.d.ts.map +1 -1
  44. package/dist/baseline/check.js +90 -64
  45. package/dist/baseline/check.js.map +1 -1
  46. package/dist/baseline/create.d.ts +35 -7
  47. package/dist/baseline/create.d.ts.map +1 -1
  48. package/dist/baseline/create.js +43 -5
  49. package/dist/baseline/create.js.map +1 -1
  50. package/dist/baseline/entry-to-located.d.ts +6 -1
  51. package/dist/baseline/entry-to-located.d.ts.map +1 -1
  52. package/dist/baseline/entry-to-located.js +20 -2
  53. package/dist/baseline/entry-to-located.js.map +1 -1
  54. package/dist/baseline/finding-identity.d.ts.map +1 -1
  55. package/dist/baseline/finding-identity.js +15 -13
  56. package/dist/baseline/finding-identity.js.map +1 -1
  57. package/dist/baseline/modes.d.ts +140 -0
  58. package/dist/baseline/modes.d.ts.map +1 -0
  59. package/dist/baseline/modes.js +179 -0
  60. package/dist/baseline/modes.js.map +1 -0
  61. package/dist/baseline/policy.d.ts +64 -0
  62. package/dist/baseline/policy.d.ts.map +1 -1
  63. package/dist/baseline/policy.js +102 -1
  64. package/dist/baseline/policy.js.map +1 -1
  65. package/dist/baseline/producers/health.d.ts +2 -2
  66. package/dist/baseline/producers/health.d.ts.map +1 -1
  67. package/dist/baseline/producers/health.js.map +1 -1
  68. package/dist/baseline/producers/index.d.ts +11 -5
  69. package/dist/baseline/producers/index.d.ts.map +1 -1
  70. package/dist/baseline/producers/index.js +12 -9
  71. package/dist/baseline/producers/index.js.map +1 -1
  72. package/dist/baseline/producers/quality.d.ts +3 -3
  73. package/dist/baseline/producers/quality.d.ts.map +1 -1
  74. package/dist/baseline/producers/quality.js.map +1 -1
  75. package/dist/baseline/producers/secret-hmac.d.ts +2 -2
  76. package/dist/baseline/producers/secret-hmac.d.ts.map +1 -1
  77. package/dist/baseline/producers/secret-hmac.js.map +1 -1
  78. package/dist/baseline/producers/security.d.ts +2 -2
  79. package/dist/baseline/producers/security.d.ts.map +1 -1
  80. package/dist/baseline/producers/security.js.map +1 -1
  81. package/dist/baseline/producers/stale-allow.d.ts +70 -0
  82. package/dist/baseline/producers/stale-allow.d.ts.map +1 -0
  83. package/dist/baseline/producers/stale-allow.js +111 -0
  84. package/dist/baseline/producers/stale-allow.js.map +1 -0
  85. package/dist/baseline/producers/tests.d.ts +2 -2
  86. package/dist/baseline/producers/tests.d.ts.map +1 -1
  87. package/dist/baseline/producers/tests.js.map +1 -1
  88. package/dist/baseline/ref-baseline.d.ts +114 -0
  89. package/dist/baseline/ref-baseline.d.ts.map +1 -0
  90. package/dist/baseline/ref-baseline.js +260 -0
  91. package/dist/baseline/ref-baseline.js.map +1 -0
  92. package/dist/baseline/sanitize.d.ts +80 -0
  93. package/dist/baseline/sanitize.d.ts.map +1 -0
  94. package/dist/baseline/sanitize.js +91 -0
  95. package/dist/baseline/sanitize.js.map +1 -0
  96. package/dist/baseline/show.d.ts.map +1 -1
  97. package/dist/baseline/show.js +9 -3
  98. package/dist/baseline/show.js.map +1 -1
  99. package/dist/baseline/types.d.ts +73 -26
  100. package/dist/baseline/types.d.ts.map +1 -1
  101. package/dist/baseline/types.js +7 -1
  102. package/dist/baseline/types.js.map +1 -1
  103. package/dist/baseline/visibility.d.ts +61 -0
  104. package/dist/baseline/visibility.d.ts.map +1 -0
  105. package/dist/baseline/visibility.js +121 -0
  106. package/dist/baseline/visibility.js.map +1 -0
  107. package/dist/cli.d.ts.map +1 -1
  108. package/dist/cli.js +154 -13
  109. package/dist/cli.js.map +1 -1
  110. package/dist/constants.d.ts.map +1 -1
  111. package/dist/constants.js +0 -10
  112. package/dist/constants.js.map +1 -1
  113. package/dist/detect.d.ts.map +1 -1
  114. package/dist/detect.js +0 -15
  115. package/dist/detect.js.map +1 -1
  116. package/dist/doctor.d.ts +78 -1
  117. package/dist/doctor.d.ts.map +1 -1
  118. package/dist/doctor.js +590 -101
  119. package/dist/doctor.js.map +1 -1
  120. package/dist/generator.d.ts.map +1 -1
  121. package/dist/generator.js +15 -0
  122. package/dist/generator.js.map +1 -1
  123. package/dist/issue-cli.d.ts +62 -0
  124. package/dist/issue-cli.d.ts.map +1 -0
  125. package/dist/issue-cli.js +252 -0
  126. package/dist/issue-cli.js.map +1 -0
  127. package/dist/languages/csharp.d.ts.map +1 -1
  128. package/dist/languages/csharp.js +2 -0
  129. package/dist/languages/csharp.js.map +1 -1
  130. package/dist/languages/go.d.ts.map +1 -1
  131. package/dist/languages/go.js +2 -0
  132. package/dist/languages/go.js.map +1 -1
  133. package/dist/languages/index.d.ts +25 -0
  134. package/dist/languages/index.d.ts.map +1 -1
  135. package/dist/languages/index.js +44 -0
  136. package/dist/languages/index.js.map +1 -1
  137. package/dist/languages/java.d.ts.map +1 -1
  138. package/dist/languages/java.js +2 -0
  139. package/dist/languages/java.js.map +1 -1
  140. package/dist/languages/kotlin.d.ts.map +1 -1
  141. package/dist/languages/kotlin.js +2 -0
  142. package/dist/languages/kotlin.js.map +1 -1
  143. package/dist/languages/python.d.ts.map +1 -1
  144. package/dist/languages/python.js +11 -1
  145. package/dist/languages/python.js.map +1 -1
  146. package/dist/languages/ruby.d.ts.map +1 -1
  147. package/dist/languages/ruby.js +2 -0
  148. package/dist/languages/ruby.js.map +1 -1
  149. package/dist/languages/rust.d.ts.map +1 -1
  150. package/dist/languages/rust.js +2 -0
  151. package/dist/languages/rust.js.map +1 -1
  152. package/dist/languages/types.d.ts +45 -0
  153. package/dist/languages/types.d.ts.map +1 -1
  154. package/dist/languages/typescript.d.ts.map +1 -1
  155. package/dist/languages/typescript.js +2 -0
  156. package/dist/languages/typescript.js.map +1 -1
  157. package/dist/prompts.d.ts.map +1 -1
  158. package/dist/prompts.js +0 -5
  159. package/dist/prompts.js.map +1 -1
  160. package/dist/setup-branch-protection.d.ts +34 -0
  161. package/dist/setup-branch-protection.d.ts.map +1 -0
  162. package/dist/setup-branch-protection.js +190 -0
  163. package/dist/setup-branch-protection.js.map +1 -0
  164. package/dist/setup-gh.d.ts +75 -0
  165. package/dist/setup-gh.d.ts.map +1 -0
  166. package/dist/setup-gh.js +213 -0
  167. package/dist/setup-gh.js.map +1 -0
  168. package/dist/setup-prebuild.d.ts +34 -0
  169. package/dist/setup-prebuild.d.ts.map +1 -0
  170. package/dist/setup-prebuild.js +181 -0
  171. package/dist/setup-prebuild.js.map +1 -0
  172. package/dist/ship-installers.d.ts.map +1 -1
  173. package/dist/ship-installers.js +19 -4
  174. package/dist/ship-installers.js.map +1 -1
  175. package/dist/types.d.ts +24 -6
  176. package/dist/types.d.ts.map +1 -1
  177. package/dist/update.d.ts +41 -0
  178. package/dist/update.d.ts.map +1 -1
  179. package/dist/update.js +154 -15
  180. package/dist/update.js.map +1 -1
  181. package/dist/upgrade.d.ts +88 -0
  182. package/dist/upgrade.d.ts.map +1 -0
  183. package/dist/upgrade.js +324 -0
  184. package/dist/upgrade.js.map +1 -0
  185. package/package.json +1 -1
  186. package/templates/.claude/skills/dxkit-action/SKILL.md +111 -17
  187. package/templates/.claude/skills/dxkit-config/SKILL.md +7 -7
  188. package/templates/.claude/skills/dxkit-fix/SKILL.md +165 -0
  189. package/templates/.claude/skills/dxkit-hooks/SKILL.md +8 -8
  190. package/templates/.claude/skills/dxkit-init/SKILL.md +3 -3
  191. package/templates/.claude/skills/dxkit-learn/SKILL.md +9 -9
  192. package/templates/.claude/skills/dxkit-onboard/SKILL.md +274 -0
  193. package/templates/.claude/skills/dxkit-reports/SKILL.md +18 -18
  194. package/templates/.claude/skills/dxkit-update/SKILL.md +164 -0
  195. package/templates/.devcontainer/devcontainer.json +6 -15
  196. package/templates/.devcontainer/post-create.sh +19 -4
  197. package/dist/baseline/producers/licenses.d.ts +0 -23
  198. package/dist/baseline/producers/licenses.d.ts.map +0 -1
  199. package/dist/baseline/producers/licenses.js +0 -46
  200. 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
- 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
- }
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
- 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 },
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
- function trackReports(ok) {
82
- if (ok)
83
- result.reports.pass++;
84
- else
85
- result.reports.fail++;
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
- function trackDx(ok) {
88
- if (ok)
89
- result.dx.pass++;
90
- else
91
- result.dx.fail++;
117
+ catch {
118
+ return null;
92
119
  }
93
- // ─── Tier 1: Reports prerequisites (mandatory) ─────────────────────────
94
- logger.info('Reports prerequisites (required to run any dxkit command):');
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
- 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;
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
- 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
- }
113
- }
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.
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
- 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.
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
- trackDx(checkInfo('.claude/rules/', fs.existsSync(path.join(cwd, '.claude', 'rules'))));
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
- trackDx(checkInfo('settings.json is valid JSON', false));
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
- trackDx(checkInfo(bin, commandAvailable(bin)));
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
- if (manifest.config.tools?.gcloud) {
166
- trackDx(checkInfo('gcloud', commandAvailable('gcloud')));
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
- if (manifest.config.tools?.infisical) {
169
- trackDx(checkInfo('infisical', commandAvailable('infisical')));
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
- // ─── Summary ───────────────────────────────────────────────────────────
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
- if (result.reports.fail === 0) {
176
- logger.success(`Reports: ${result.reports.pass}/${result.reports.pass + result.reports.fail} — ready to run dxkit`);
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: ${result.reports.pass}/${result.reports.pass + result.reports.fail} — fix the failures above before running other dxkit commands`);
612
+ logger.fail(`Reports: ${r.pass}/${r.pass + r.fail} — fix the failures above before running other dxkit commands`);
180
613
  }
181
- const dxTotal = result.dx.pass + result.dx.fail;
614
+ const dx = report.summary.dx;
615
+ const dxTotal = dx.pass + dx.fail;
182
616
  if (dxTotal > 0) {
183
- if (result.dx.fail === 0) {
184
- logger.success(`Agent DX: ${result.dx.pass}/${dxTotal} — fully scaffolded`);
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: ${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
- }
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
- // 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) {
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