@devflow-tools/cli 0.16.1 → 0.16.3

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 (58) hide show
  1. package/dist/commands/doctor.d.ts +4 -19
  2. package/dist/commands/doctor.d.ts.map +1 -1
  3. package/dist/commands/doctor.js +352 -6
  4. package/dist/commands/doctor.js.map +1 -1
  5. package/dist/commands/init.d.ts +2 -0
  6. package/dist/commands/init.d.ts.map +1 -1
  7. package/dist/commands/init.js +13 -3
  8. package/dist/commands/init.js.map +1 -1
  9. package/dist/commands/memory-maintain.d.ts +9 -0
  10. package/dist/commands/memory-maintain.d.ts.map +1 -0
  11. package/dist/commands/memory-maintain.js +32 -0
  12. package/dist/commands/memory-maintain.js.map +1 -0
  13. package/dist/commands/status.d.ts.map +1 -1
  14. package/dist/commands/status.js +14 -0
  15. package/dist/commands/status.js.map +1 -1
  16. package/dist/index.js +29 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/lib/plugin-setup.d.ts +36 -2
  19. package/dist/lib/plugin-setup.d.ts.map +1 -1
  20. package/dist/lib/plugin-setup.js +183 -11
  21. package/dist/lib/plugin-setup.js.map +1 -1
  22. package/dist/plugin-files/.claude-plugin/plugin.json +2 -2
  23. package/dist/plugin-files/dist/hooks/hook-client.js +6 -6
  24. package/dist/plugin-files/dist/hooks/hook-daemon.js +11 -11
  25. package/dist/plugin-files/dist/hooks/post-tool-use-failure.js +2 -2
  26. package/dist/plugin-files/dist/hooks/post-tool-use.js +3 -3
  27. package/dist/plugin-files/dist/hooks/pre-compact.js +2 -2
  28. package/dist/plugin-files/dist/hooks/pre-tool-use.js +2 -2
  29. package/dist/plugin-files/dist/hooks/session-end.js +2 -2
  30. package/dist/plugin-files/dist/hooks/stop.js +2 -2
  31. package/dist/plugin-files/dist/hooks/user-prompt-submit.js +3 -2
  32. package/dist/plugin-files/dist/skills/devflow:react/SKILL.md +16 -3
  33. package/dist/plugin-files/hooks/session-start +37 -17
  34. package/dist/plugin-files/hooks/stop +3 -2
  35. package/dist/plugin-files/package.json +7 -8
  36. package/dist/plugin-files/skills/{devflow:react → react}/SKILL.md +16 -3
  37. package/package.json +27 -27
  38. /package/dist/plugin-files/{hooks.json → hooks/hooks.json} +0 -0
  39. /package/dist/plugin-files/skills/{devflow:animation → animation}/SKILL.md +0 -0
  40. /package/dist/plugin-files/skills/{devflow:context → context}/SKILL.md +0 -0
  41. /package/dist/plugin-files/skills/{devflow:css → css}/SKILL.md +0 -0
  42. /package/dist/plugin-files/skills/{devflow:docker → docker}/SKILL.md +0 -0
  43. /package/dist/plugin-files/skills/{devflow:doctor → doctor}/SKILL.md +0 -0
  44. /package/dist/plugin-files/skills/{devflow:electron → electron}/SKILL.md +0 -0
  45. /package/dist/plugin-files/skills/{devflow:git → git}/SKILL.md +0 -0
  46. /package/dist/plugin-files/skills/{devflow:graph → graph}/SKILL.md +0 -0
  47. /package/dist/plugin-files/skills/{devflow:graphql → graphql}/SKILL.md +0 -0
  48. /package/dist/plugin-files/skills/{devflow:knowledge → knowledge}/SKILL.md +0 -0
  49. /package/dist/plugin-files/skills/{devflow:memory → memory}/SKILL.md +0 -0
  50. /package/dist/plugin-files/skills/{devflow:nest → nest}/SKILL.md +0 -0
  51. /package/dist/plugin-files/skills/{devflow:nextjs → nextjs}/SKILL.md +0 -0
  52. /package/dist/plugin-files/skills/{devflow:performance → performance}/SKILL.md +0 -0
  53. /package/dist/plugin-files/skills/{devflow:start → start}/SKILL.md +0 -0
  54. /package/dist/plugin-files/skills/{devflow:tailwind → tailwind}/SKILL.md +0 -0
  55. /package/dist/plugin-files/skills/{devflow:taro → taro}/SKILL.md +0 -0
  56. /package/dist/plugin-files/skills/{devflow:ui-layout → ui-layout}/SKILL.md +0 -0
  57. /package/dist/plugin-files/skills/{devflow:vue → vue}/SKILL.md +0 -0
  58. /package/dist/plugin-files/skills/{devflow:workflow → workflow}/SKILL.md +0 -0
@@ -1,21 +1,6 @@
1
- export type DoctorStatus = "green" | "yellow" | "red";
2
- export type DoctorSeverity = "info" | "warning" | "error";
3
- export interface DoctorMetric {
4
- key: string;
5
- value: unknown;
6
- target?: unknown;
7
- ok: boolean;
8
- severity?: DoctorSeverity;
9
- }
10
- export interface DoctorSection {
11
- name: "Isolation" | "Distill" | "Vectors" | "Telemetry" | "Injection" | "Memory quality" | "Knowledge quality" | "Contract" | "Deadcode" | "Data face" | "Legacy paths" | "Runtime security" | "Benchmark" | "UI reachability" | "Hook path health";
12
- status: DoctorStatus;
13
- metrics: DoctorMetric[];
14
- }
15
- export interface DoctorResult {
16
- sections: DoctorSection[];
17
- exitCode: number;
18
- }
1
+ import type { DoctorReportV1 } from "@devflow-tools/sdk";
2
+ export type { DoctorMetric, DoctorReportV1, DoctorSection, DoctorSeverity, DoctorStatus, } from "@devflow-tools/sdk";
3
+ export type DoctorResult = DoctorReportV1;
19
4
  export interface DoctorOptions {
20
5
  project?: string;
21
6
  json?: boolean;
@@ -38,6 +23,7 @@ export interface DoctorDependencies {
38
23
  installedPluginsPath?: string;
39
24
  knownMarketplacesPath?: string;
40
25
  pluginCachePath?: string;
26
+ activePluginRoot?: string;
41
27
  env?: NodeJS.ProcessEnv;
42
28
  now?: () => number;
43
29
  openDatabase?: (path: string) => DoctorDatabase;
@@ -110,5 +96,4 @@ export declare function runDoctorReceipts(): {
110
96
  sessionId: string;
111
97
  data: unknown;
112
98
  };
113
- export {};
114
99
  //# sourceMappingURL=doctor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,8 @@ import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep }
6
6
  import { spawnSync } from "node:child_process";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import chalk from "chalk";
9
+ import { DOCTOR_SCHEMA_VERSION } from "@devflow-tools/sdk";
10
+ import { resolveActivePlugin, scanClaudeSkillShadowing } from "../lib/plugin-setup.js";
9
11
  import { serverStatus } from "./server.js";
10
12
  import { getGlobalDevFlowDbPath } from "@devflow-tools/database";
11
13
  function getProjectHash(rootPath) {
@@ -108,6 +110,13 @@ function checkIsolation(project, dependencies) {
108
110
  ok: true,
109
111
  severity: "info",
110
112
  });
113
+ metrics.push({
114
+ key: "hook_daemon_registration",
115
+ value: { activeProjectRegistrations: 0 },
116
+ target: "0 when idle or 1 while active",
117
+ ok: true,
118
+ severity: "info",
119
+ });
111
120
  }
112
121
  else {
113
122
  try {
@@ -144,6 +153,13 @@ function checkIsolation(project, dependencies) {
144
153
  ok: true,
145
154
  severity: "info",
146
155
  });
156
+ metrics.push({
157
+ key: "hook_daemon_registration",
158
+ value: { activeProjectRegistrations: projectEntries.length },
159
+ target: "0 when idle or 1 while active",
160
+ ok: projectEntries.length <= 1,
161
+ severity: "warning",
162
+ });
147
163
  const registeredSocketPaths = projectEntries.map((entry) => getDaemonSocketPath(entry.rootPath));
148
164
  metrics.push({
149
165
  key: "registered_daemon_socket_paths",
@@ -176,6 +192,13 @@ function checkIsolation(project, dependencies) {
176
192
  ok: false,
177
193
  severity: "warning",
178
194
  });
195
+ metrics.push({
196
+ key: "hook_daemon_registration",
197
+ value: "unreadable daemon registry",
198
+ target: "valid project registration",
199
+ ok: false,
200
+ severity: "warning",
201
+ });
179
202
  }
180
203
  }
181
204
  return { name: "Isolation", status: sectionStatus(metrics), metrics };
@@ -184,7 +207,9 @@ const REQUIRED_SCHEMA = {
184
207
  distill_leases: ["event_ids", "expires_at", "status"],
185
208
  observations: ["title"],
186
209
  session_events: ["created_at", "id", "processed"],
210
+ sessions: ["created_at", "id", "project_root", "status"],
187
211
  };
212
+ const SESSION_STALE_MS = 60 * 60 * 1000;
188
213
  function checkRequiredSchema(db) {
189
214
  const tableRows = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all();
190
215
  const tables = new Set(tableRows.map((row) => row.name));
@@ -340,6 +365,48 @@ function checkDistill(project, dependencies) {
340
365
  ok: staleUnleased === 0,
341
366
  severity: "error",
342
367
  });
368
+ const pendingEvents = doctorCount(openedDb, "SELECT COUNT(*) count FROM session_events WHERE processed = 0");
369
+ metrics.push({
370
+ key: "pending_memory_events",
371
+ value: pendingEvents,
372
+ target: "actively leased or younger than 5 minutes",
373
+ ok: staleUnleased === 0,
374
+ severity: "error",
375
+ });
376
+ const oldestPending = openedDb.prepare("SELECT MIN(created_at) oldest FROM session_events WHERE processed = 0").get();
377
+ const oldestPendingAt = oldestPending?.oldest == null ? null : Number(oldestPending.oldest);
378
+ metrics.push({
379
+ key: "pending_memory_oldest_age_ms",
380
+ value: oldestPendingAt === null ? 0 : Math.max(0, now - oldestPendingAt),
381
+ target: "<= 300000 when no active lease is present",
382
+ ok: oldestPendingAt === null || staleUnleased === 0,
383
+ severity: "warning",
384
+ });
385
+ const orphanEvents = doctorCount(openedDb, `
386
+ SELECT COUNT(*) count
387
+ FROM session_events event
388
+ LEFT JOIN sessions session ON session.id = event.session_id
389
+ WHERE TRIM(event.session_id) = '' OR session.id IS NULL
390
+ `);
391
+ metrics.push({
392
+ key: "orphan_memory_events",
393
+ value: orphanEvents,
394
+ target: 0,
395
+ ok: orphanEvents === 0,
396
+ severity: "error",
397
+ });
398
+ const staleSessions = doctorCount(openedDb, `
399
+ SELECT COUNT(*) count
400
+ FROM sessions
401
+ WHERE status = 'active' AND created_at < ?
402
+ `, now - SESSION_STALE_MS);
403
+ metrics.push({
404
+ key: "stale_memory_sessions",
405
+ value: staleSessions,
406
+ target: 0,
407
+ ok: staleSessions === 0,
408
+ severity: "error",
409
+ });
343
410
  const duplicates = openedDb.prepare(`
344
411
  SELECT COUNT(*) AS group_count, COALESCE(SUM(title_count), 0) AS row_count
345
412
  FROM (
@@ -386,6 +453,13 @@ function doctorOpenDatabase(path, dependencies) {
386
453
  function doctorTableExists(db, table) {
387
454
  return Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1").get(table));
388
455
  }
456
+ function doctorTableColumns(db, table) {
457
+ if (!doctorTableExists(db, table))
458
+ return new Set();
459
+ return new Set(db.prepare(`PRAGMA table_info(${table})`).all()
460
+ .map((column) => column.name)
461
+ .filter((name) => typeof name === "string"));
462
+ }
389
463
  function doctorCount(db, sql, ...params) {
390
464
  const row = db.prepare(sql).get(...params);
391
465
  return Number(row?.count ?? 0);
@@ -412,6 +486,14 @@ function checkVectors(project, dependencies) {
412
486
  codegraphNodes = doctorCount(db, "SELECT COUNT(*) count FROM nodes");
413
487
  metrics.push({ key: "codegraph_nodes", value: codegraphNodes, ok: true, severity: "info" });
414
488
  }
489
+ const freshnessMs = Math.max(0, (dependencies.now ?? Date.now)() - statSync(codegraphPath).mtimeMs);
490
+ metrics.push({
491
+ key: "index_freshness_ms",
492
+ value: freshnessMs,
493
+ target: "recently updated codegraph database",
494
+ ok: true,
495
+ severity: "info",
496
+ });
415
497
  }
416
498
  catch (error) {
417
499
  metrics.push({ key: "codegraph_db_read", value: error.message, target: "readable SQLite database", ok: false, severity: "error" });
@@ -574,10 +656,69 @@ function checkTelemetry(project, strict, dependencies) {
574
656
  ? doctorCount(db, "SELECT COUNT(*) count FROM telemetry_failures WHERE created_at >= ?", cutoff)
575
657
  : 0;
576
658
  metrics.push({ key: "telemetry_failures_24h", value: failures, target: 0, ok: failures === 0, severity: strictSeverity });
659
+ const sessionColumns = doctorTableColumns(db, "sessions");
660
+ const toolEventColumns = doctorTableColumns(db, "tool_call_events");
661
+ const executionColumns = doctorTableColumns(db, "skill_executions");
662
+ const sessionSchemaReady = ["id", "project_root", "started_at", "status"]
663
+ .every((column) => sessionColumns.has(column));
664
+ metrics.push({
665
+ key: "telemetry_session_schema",
666
+ value: sessionSchemaReady ? "canonical" : "sessions table or identity columns missing",
667
+ target: "canonical sessions table",
668
+ ok: sessionSchemaReady,
669
+ severity: "error",
670
+ });
671
+ if (sessionSchemaReady) {
672
+ const recentSessionStarts = doctorCount(db, `
673
+ SELECT COUNT(*) count FROM sessions
674
+ WHERE project_root = ? AND started_at >= ?
675
+ `, project, cutoff);
676
+ metrics.push({
677
+ key: "session_start_evidence_24h",
678
+ value: recentSessionStarts,
679
+ target: "present after a Claude SessionStart",
680
+ ok: true,
681
+ severity: "info",
682
+ });
683
+ const staleSessions = doctorCount(db, `
684
+ SELECT COUNT(*) count FROM sessions
685
+ WHERE project_root = ? AND status = 'running' AND started_at < ?
686
+ `, project, (dependencies.now ?? Date.now)() - SESSION_STALE_MS);
687
+ metrics.push({
688
+ key: "stale_telemetry_sessions",
689
+ value: staleSessions,
690
+ target: 0,
691
+ ok: staleSessions === 0,
692
+ severity: "error",
693
+ });
694
+ let orphanToolEvents = 0;
695
+ if (toolEventColumns.has("session_id")) {
696
+ orphanToolEvents = doctorCount(db, `
697
+ SELECT COUNT(*) count
698
+ FROM tool_call_events event
699
+ LEFT JOIN sessions session ON session.id = event.session_id
700
+ WHERE TRIM(COALESCE(event.session_id, '')) = '' OR session.id IS NULL
701
+ `);
702
+ }
703
+ let orphanExecutions = 0;
704
+ if (executionColumns.has("session_id")) {
705
+ orphanExecutions = doctorCount(db, `
706
+ SELECT COUNT(*) count
707
+ FROM skill_executions execution
708
+ LEFT JOIN sessions session ON session.id = execution.session_id
709
+ WHERE TRIM(COALESCE(execution.session_id, '')) = '' OR session.id IS NULL
710
+ `);
711
+ }
712
+ metrics.push({
713
+ key: "orphan_telemetry_events",
714
+ value: { toolCalls: orphanToolEvents, skillExecutions: orphanExecutions },
715
+ target: { toolCalls: 0, skillExecutions: 0 },
716
+ ok: orphanToolEvents === 0 && orphanExecutions === 0,
717
+ severity: "error",
718
+ });
719
+ }
577
720
  if (doctorTableExists(db, "tool_call_events")) {
578
- const eventColumns = db.prepare("PRAGMA table_info(tool_call_events)").all()
579
- .map(column => column.name);
580
- if (eventColumns.includes("failure_category")) {
721
+ if (toolEventColumns.has("failure_category")) {
581
722
  const rows = db.prepare(`
582
723
  SELECT failure_category category, COUNT(*) count
583
724
  FROM tool_call_events
@@ -2575,7 +2716,8 @@ function marketplaceSourceRoot(sourcePath) {
2575
2716
  join(sourcePath, "plugins", "claude-code"),
2576
2717
  ]) {
2577
2718
  const root = canonicalDirectory(candidate);
2578
- if (root && existsSync(join(root, "hooks.json")))
2719
+ if (root && (existsSync(join(root, "hooks", "hooks.json"))
2720
+ || existsSync(join(root, "hooks.json"))))
2579
2721
  return root;
2580
2722
  }
2581
2723
  return null;
@@ -3266,7 +3408,11 @@ function inspectLegacyRoot(project, homePath, target, inspection) {
3266
3408
  enqueueCommand(command);
3267
3409
  }
3268
3410
  if (target.inspectPluginConfig || !target.commands) {
3269
- const configPath = join(root, "hooks.json");
3411
+ const conventionalConfigPath = join(root, "hooks", "hooks.json");
3412
+ const legacyConfigPath = join(root, "hooks.json");
3413
+ const configPath = existsSync(legacyConfigPath)
3414
+ ? legacyConfigPath
3415
+ : conventionalConfigPath;
3270
3416
  if (!existsSync(configPath)) {
3271
3417
  inspection.missing.push(configPath);
3272
3418
  enqueue(join(root, "hooks", "session-start"));
@@ -3395,6 +3541,111 @@ function inspectLegacyRoot(project, homePath, target, inspection) {
3395
3541
  }
3396
3542
  }
3397
3543
  }
3544
+ function installedPluginVersionsByRoot(dependencies) {
3545
+ const installedPluginsPath = dependencies.installedPluginsPath
3546
+ ?? join(dependencies.homePath ?? homedir(), ".claude", "plugins", "installed_plugins.json");
3547
+ const versions = new Map();
3548
+ if (!existsSync(installedPluginsPath))
3549
+ return new Map();
3550
+ try {
3551
+ const manifest = asRecord(parseJsonFile(installedPluginsPath));
3552
+ const plugins = asRecord(manifest?.plugins);
3553
+ if (!plugins)
3554
+ return new Map();
3555
+ for (const entries of Object.values(plugins)) {
3556
+ if (!Array.isArray(entries))
3557
+ continue;
3558
+ for (const entry of entries) {
3559
+ const record = asRecord(entry);
3560
+ if (typeof record?.installPath !== "string" || typeof record.version !== "string")
3561
+ continue;
3562
+ const root = canonicalDirectory(record.installPath);
3563
+ if (!root)
3564
+ continue;
3565
+ const rootVersions = versions.get(root) ?? new Set();
3566
+ rootVersions.add(record.version);
3567
+ versions.set(root, rootVersions);
3568
+ }
3569
+ }
3570
+ }
3571
+ catch {
3572
+ return new Map();
3573
+ }
3574
+ return new Map([...versions].map(([root, values]) => [root, [...values].sort()]));
3575
+ }
3576
+ function readPluginVersion(path) {
3577
+ try {
3578
+ const metadata = asRecord(parseJsonFile(path));
3579
+ return typeof metadata?.version === "string" ? metadata.version : undefined;
3580
+ }
3581
+ catch {
3582
+ return undefined;
3583
+ }
3584
+ }
3585
+ function inspectPluginLifecycle(targets, dependencies) {
3586
+ const inspection = {
3587
+ layoutIssues: [],
3588
+ versionIssues: [],
3589
+ versionRecords: [],
3590
+ sessionStartIssues: [],
3591
+ daemonIssues: [],
3592
+ };
3593
+ const installedVersions = installedPluginVersionsByRoot(dependencies);
3594
+ for (const target of targets) {
3595
+ const root = target.root;
3596
+ const packagePath = join(root, "package.json");
3597
+ const manifestPath = join(root, ".claude-plugin", "plugin.json");
3598
+ const hasPackageMetadata = existsSync(packagePath) || existsSync(manifestPath);
3599
+ if (!hasPackageMetadata)
3600
+ continue;
3601
+ const conventionalConfigPath = join(root, "hooks", "hooks.json");
3602
+ const legacyConfigPath = join(root, "hooks.json");
3603
+ if (!existsSync(conventionalConfigPath)) {
3604
+ inspection.layoutIssues.push({ root, issue: "missing hooks/hooks.json" });
3605
+ }
3606
+ if (existsSync(legacyConfigPath)) {
3607
+ inspection.layoutIssues.push({ root, issue: "retired root hooks.json is present" });
3608
+ }
3609
+ const activeConfigPath = existsSync(conventionalConfigPath)
3610
+ ? conventionalConfigPath
3611
+ : legacyConfigPath;
3612
+ try {
3613
+ const config = asRecord(parseJsonFile(activeConfigPath));
3614
+ const hooks = asRecord(config?.hooks);
3615
+ const commands = [];
3616
+ collectCommandStrings(hooks?.SessionStart, commands);
3617
+ if (!commands.some((command) => /(?:^|[/\\])session-start(?:\s|$|["'])/.test(command))) {
3618
+ inspection.sessionStartIssues.push({ root, issue: "SessionStart wrapper is not registered" });
3619
+ }
3620
+ }
3621
+ catch {
3622
+ inspection.sessionStartIssues.push({ root, issue: "hook configuration is unreadable" });
3623
+ }
3624
+ const packageVersion = readPluginVersion(packagePath);
3625
+ const manifestVersion = readPluginVersion(manifestPath);
3626
+ const registryVersions = installedVersions.get(root) ?? [];
3627
+ const versionRecord = { root, packageVersion, manifestVersion, registryVersions };
3628
+ inspection.versionRecords.push(versionRecord);
3629
+ if (!packageVersion
3630
+ || !manifestVersion
3631
+ || packageVersion !== manifestVersion
3632
+ || registryVersions.some((version) => version !== packageVersion)) {
3633
+ inspection.versionIssues.push(versionRecord);
3634
+ }
3635
+ const sessionStartPath = join(root, "hooks", "session-start");
3636
+ const daemonPath = join(root, "dist", "hooks", "hook-daemon.js");
3637
+ try {
3638
+ const sessionStart = readFileSync(sessionStartPath, "utf8");
3639
+ if (!existsSync(daemonPath) || !sessionStart.includes("hook-daemon.js")) {
3640
+ inspection.daemonIssues.push({ root, issue: "SessionStart does not register the packaged hook daemon" });
3641
+ }
3642
+ }
3643
+ catch {
3644
+ inspection.daemonIssues.push({ root, issue: "SessionStart wrapper or hook daemon is missing" });
3645
+ }
3646
+ }
3647
+ return inspection;
3648
+ }
3398
3649
  function checkLegacyPaths(project, dependencies) {
3399
3650
  const selection = selectActiveLegacyTargets(project, dependencies);
3400
3651
  const inspection = {
@@ -3438,6 +3689,40 @@ function checkLegacyPaths(project, dependencies) {
3438
3689
  const resolutionMethod = artifactResolutionFailed && !selection.method.endsWith("-unresolved")
3439
3690
  ? `${selection.method}-unresolved`
3440
3691
  : selection.method;
3692
+ const lifecycle = inspectPluginLifecycle(selection.targets, dependencies);
3693
+ const injectedSettingsHome = dependencies.claudeSettingsPath
3694
+ ? dirname(dependencies.claudeSettingsPath)
3695
+ : null;
3696
+ const skillHome = dependencies.homePath
3697
+ ?? (injectedSettingsHome && basename(injectedSettingsHome) === ".claude"
3698
+ ? dirname(injectedSettingsHome)
3699
+ : injectedSettingsHome)
3700
+ ?? homedir();
3701
+ const selectedSkillRoot = selection.targets
3702
+ .map((target) => target.root)
3703
+ .find((root) => existsSync(join(root, "skills"))) ?? null;
3704
+ const activePlugin = dependencies.activePluginRoot
3705
+ ? {
3706
+ root: existsSync(dependencies.activePluginRoot) ? dependencies.activePluginRoot : null,
3707
+ version: null,
3708
+ }
3709
+ : selectedSkillRoot
3710
+ ? { root: selectedSkillRoot, version: null }
3711
+ : resolveActivePlugin(skillHome, {
3712
+ installedPluginsPath: dependencies.installedPluginsPath,
3713
+ pluginCachePath: dependencies.pluginCachePath,
3714
+ });
3715
+ const activeSkillRoot = activePlugin.root;
3716
+ const shadowedSkills = activeSkillRoot
3717
+ ? scanClaudeSkillShadowing(skillHome, activeSkillRoot)
3718
+ : [];
3719
+ const remediation = shadowedSkills
3720
+ .filter((skill) => !skill.isKnownDevFlowArtifact)
3721
+ .map((skill) => ({
3722
+ skillName: skill.skillName,
3723
+ path: skill.globalPath,
3724
+ action: "inspect manually; file is modified or not a known DevFlow artifact",
3725
+ }));
3441
3726
  const metrics = [
3442
3727
  {
3443
3728
  key: "active_plugin_resolution",
@@ -3458,6 +3743,34 @@ function checkLegacyPaths(project, dependencies) {
3458
3743
  ok: true,
3459
3744
  severity: "info",
3460
3745
  },
3746
+ {
3747
+ key: "plugin_hook_layout",
3748
+ value: lifecycle.layoutIssues,
3749
+ target: "hooks/hooks.json present and root hooks.json absent",
3750
+ ok: lifecycle.layoutIssues.length === 0,
3751
+ severity: "error",
3752
+ },
3753
+ {
3754
+ key: "plugin_version_parity",
3755
+ value: lifecycle.versionRecords,
3756
+ target: "manifest, package, and installed registry versions match",
3757
+ ok: lifecycle.versionIssues.length === 0,
3758
+ severity: "error",
3759
+ },
3760
+ {
3761
+ key: "session_start_hook_registration",
3762
+ value: lifecycle.sessionStartIssues,
3763
+ target: [],
3764
+ ok: lifecycle.sessionStartIssues.length === 0,
3765
+ severity: "error",
3766
+ },
3767
+ {
3768
+ key: "hook_daemon_artifact_registration",
3769
+ value: lifecycle.daemonIssues,
3770
+ target: [],
3771
+ ok: lifecycle.daemonIssues.length === 0,
3772
+ severity: "error",
3773
+ },
3461
3774
  {
3462
3775
  key: "malformed_active_settings",
3463
3776
  value: selection.malformedSettings,
@@ -3541,7 +3854,40 @@ function checkLegacyPaths(project, dependencies) {
3541
3854
  ok: true,
3542
3855
  severity: "info",
3543
3856
  },
3857
+ {
3858
+ key: "skill_shadowing_count",
3859
+ value: shadowedSkills.length,
3860
+ target: 0,
3861
+ ok: shadowedSkills.length === 0,
3862
+ severity: "error",
3863
+ },
3864
+ {
3865
+ key: "shadowed_skills",
3866
+ value: shadowedSkills,
3867
+ target: [],
3868
+ ok: shadowedSkills.length === 0,
3869
+ severity: "error",
3870
+ },
3871
+ {
3872
+ key: "active_skill_source",
3873
+ value: activeSkillRoot,
3874
+ target: "existing plugin cache path",
3875
+ ok: true,
3876
+ severity: "info",
3877
+ },
3878
+ {
3879
+ key: "skill_shadowing_remediation",
3880
+ value: remediation,
3881
+ target: [],
3882
+ ok: true,
3883
+ severity: "info",
3884
+ },
3544
3885
  ];
3886
+ const activePluginVersion = activePlugin.version
3887
+ ?? lifecycle.versionRecords.find((record) => record.root === activeSkillRoot)?.packageVersion;
3888
+ if (activePluginVersion) {
3889
+ metrics.push({ key: "active_plugin_version", value: activePluginVersion, ok: true, severity: "info" });
3890
+ }
3545
3891
  return { name: "Legacy paths", status: sectionStatus(metrics), metrics };
3546
3892
  }
3547
3893
  function loadAcceptanceManifest(dependencies) {
@@ -3839,7 +4185,7 @@ export async function runStructuredDoctor(options = {}, dependencies = {}) {
3839
4185
  const exitCode = options.strict && sections.some((section) => section.status !== "green")
3840
4186
  ? 1
3841
4187
  : 0;
3842
- return { sections, exitCode };
4188
+ return { schemaVersion: DOCTOR_SCHEMA_VERSION, sections, exitCode };
3843
4189
  }
3844
4190
  export function runDoctor(options, dependencies = {}) {
3845
4191
  return options === undefined