@devflow-tools/cli 0.16.0 → 0.16.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 (48) hide show
  1. package/dist/commands/doctor.d.ts.map +1 -1
  2. package/dist/commands/doctor.js +266 -5
  3. package/dist/commands/doctor.js.map +1 -1
  4. package/dist/commands/server.js +1 -1
  5. package/dist/commands/start.js +1 -1
  6. package/dist/lib/plugin-setup.d.ts +5 -6
  7. package/dist/lib/plugin-setup.d.ts.map +1 -1
  8. package/dist/lib/plugin-setup.js +132 -27
  9. package/dist/lib/plugin-setup.js.map +1 -1
  10. package/dist/lib/with-server.js +1 -1
  11. package/dist/plugin-files/.claude-plugin/plugin.json +1 -1
  12. package/dist/plugin-files/dist/hooks/hook-client.js +5 -5
  13. package/dist/plugin-files/dist/hooks/hook-daemon.js +11 -11
  14. package/dist/plugin-files/dist/hooks/post-tool-use-failure.js +2 -2
  15. package/dist/plugin-files/dist/hooks/post-tool-use.js +3 -3
  16. package/dist/plugin-files/dist/hooks/pre-compact.js +2 -2
  17. package/dist/plugin-files/dist/hooks/pre-tool-use.js +2 -2
  18. package/dist/plugin-files/dist/hooks/session-end.js +2 -2
  19. package/dist/plugin-files/dist/hooks/stop.js +2 -2
  20. package/dist/plugin-files/dist/hooks/user-prompt-submit.js +3 -2
  21. package/dist/plugin-files/dist/skills/devflow:react/SKILL.md +16 -3
  22. package/dist/plugin-files/hooks/session-start +0 -10
  23. package/dist/plugin-files/hooks/stop +3 -2
  24. package/dist/plugin-files/package.json +9 -8
  25. package/dist/plugin-files/skills/animation/SKILL.md +38 -0
  26. package/dist/plugin-files/skills/context/SKILL.md +63 -0
  27. package/dist/plugin-files/skills/css/SKILL.md +38 -0
  28. package/dist/plugin-files/skills/devflow/SKILL.md +20 -0
  29. package/dist/plugin-files/skills/docker/SKILL.md +42 -0
  30. package/dist/plugin-files/skills/doctor/SKILL.md +17 -0
  31. package/dist/plugin-files/skills/electron/SKILL.md +42 -0
  32. package/dist/plugin-files/skills/git/SKILL.md +42 -0
  33. package/dist/plugin-files/skills/graph/SKILL.md +55 -0
  34. package/dist/plugin-files/skills/graphql/SKILL.md +41 -0
  35. package/dist/plugin-files/skills/knowledge/SKILL.md +55 -0
  36. package/dist/plugin-files/skills/memory/SKILL.md +54 -0
  37. package/dist/plugin-files/skills/nest/SKILL.md +41 -0
  38. package/dist/plugin-files/skills/nextjs/SKILL.md +38 -0
  39. package/dist/plugin-files/skills/performance/SKILL.md +42 -0
  40. package/dist/plugin-files/skills/react/SKILL.md +63 -0
  41. package/dist/plugin-files/skills/start/SKILL.md +50 -0
  42. package/dist/plugin-files/skills/tailwind/SKILL.md +38 -0
  43. package/dist/plugin-files/skills/taro/SKILL.md +41 -0
  44. package/dist/plugin-files/skills/ui-layout/SKILL.md +38 -0
  45. package/dist/plugin-files/skills/vue/SKILL.md +45 -0
  46. package/dist/plugin-files/skills/workflow/SKILL.md +55 -0
  47. package/package.json +27 -27
  48. /package/dist/plugin-files/{hooks.json → hooks/hooks.json} +0 -0
@@ -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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"}
@@ -108,6 +108,13 @@ function checkIsolation(project, dependencies) {
108
108
  ok: true,
109
109
  severity: "info",
110
110
  });
111
+ metrics.push({
112
+ key: "hook_daemon_registration",
113
+ value: { activeProjectRegistrations: 0 },
114
+ target: "0 when idle or 1 while active",
115
+ ok: true,
116
+ severity: "info",
117
+ });
111
118
  }
112
119
  else {
113
120
  try {
@@ -144,6 +151,13 @@ function checkIsolation(project, dependencies) {
144
151
  ok: true,
145
152
  severity: "info",
146
153
  });
154
+ metrics.push({
155
+ key: "hook_daemon_registration",
156
+ value: { activeProjectRegistrations: projectEntries.length },
157
+ target: "0 when idle or 1 while active",
158
+ ok: projectEntries.length <= 1,
159
+ severity: "warning",
160
+ });
147
161
  const registeredSocketPaths = projectEntries.map((entry) => getDaemonSocketPath(entry.rootPath));
148
162
  metrics.push({
149
163
  key: "registered_daemon_socket_paths",
@@ -176,6 +190,13 @@ function checkIsolation(project, dependencies) {
176
190
  ok: false,
177
191
  severity: "warning",
178
192
  });
193
+ metrics.push({
194
+ key: "hook_daemon_registration",
195
+ value: "unreadable daemon registry",
196
+ target: "valid project registration",
197
+ ok: false,
198
+ severity: "warning",
199
+ });
179
200
  }
180
201
  }
181
202
  return { name: "Isolation", status: sectionStatus(metrics), metrics };
@@ -184,7 +205,9 @@ const REQUIRED_SCHEMA = {
184
205
  distill_leases: ["event_ids", "expires_at", "status"],
185
206
  observations: ["title"],
186
207
  session_events: ["created_at", "id", "processed"],
208
+ sessions: ["created_at", "id", "project_root", "status"],
187
209
  };
210
+ const SESSION_STALE_MS = 60 * 60 * 1000;
188
211
  function checkRequiredSchema(db) {
189
212
  const tableRows = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all();
190
213
  const tables = new Set(tableRows.map((row) => row.name));
@@ -340,6 +363,39 @@ function checkDistill(project, dependencies) {
340
363
  ok: staleUnleased === 0,
341
364
  severity: "error",
342
365
  });
366
+ const pendingEvents = doctorCount(openedDb, "SELECT COUNT(*) count FROM session_events WHERE processed = 0");
367
+ metrics.push({
368
+ key: "pending_memory_events",
369
+ value: pendingEvents,
370
+ target: "actively leased or younger than 5 minutes",
371
+ ok: staleUnleased === 0,
372
+ severity: "error",
373
+ });
374
+ const orphanEvents = doctorCount(openedDb, `
375
+ SELECT COUNT(*) count
376
+ FROM session_events event
377
+ LEFT JOIN sessions session ON session.id = event.session_id
378
+ WHERE TRIM(event.session_id) = '' OR session.id IS NULL
379
+ `);
380
+ metrics.push({
381
+ key: "orphan_memory_events",
382
+ value: orphanEvents,
383
+ target: 0,
384
+ ok: orphanEvents === 0,
385
+ severity: "error",
386
+ });
387
+ const staleSessions = doctorCount(openedDb, `
388
+ SELECT COUNT(*) count
389
+ FROM sessions
390
+ WHERE status = 'active' AND created_at < ?
391
+ `, now - SESSION_STALE_MS);
392
+ metrics.push({
393
+ key: "stale_memory_sessions",
394
+ value: staleSessions,
395
+ target: 0,
396
+ ok: staleSessions === 0,
397
+ severity: "error",
398
+ });
343
399
  const duplicates = openedDb.prepare(`
344
400
  SELECT COUNT(*) AS group_count, COALESCE(SUM(title_count), 0) AS row_count
345
401
  FROM (
@@ -386,6 +442,13 @@ function doctorOpenDatabase(path, dependencies) {
386
442
  function doctorTableExists(db, table) {
387
443
  return Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1").get(table));
388
444
  }
445
+ function doctorTableColumns(db, table) {
446
+ if (!doctorTableExists(db, table))
447
+ return new Set();
448
+ return new Set(db.prepare(`PRAGMA table_info(${table})`).all()
449
+ .map((column) => column.name)
450
+ .filter((name) => typeof name === "string"));
451
+ }
389
452
  function doctorCount(db, sql, ...params) {
390
453
  const row = db.prepare(sql).get(...params);
391
454
  return Number(row?.count ?? 0);
@@ -574,10 +637,69 @@ function checkTelemetry(project, strict, dependencies) {
574
637
  ? doctorCount(db, "SELECT COUNT(*) count FROM telemetry_failures WHERE created_at >= ?", cutoff)
575
638
  : 0;
576
639
  metrics.push({ key: "telemetry_failures_24h", value: failures, target: 0, ok: failures === 0, severity: strictSeverity });
640
+ const sessionColumns = doctorTableColumns(db, "sessions");
641
+ const toolEventColumns = doctorTableColumns(db, "tool_call_events");
642
+ const executionColumns = doctorTableColumns(db, "skill_executions");
643
+ const sessionSchemaReady = ["id", "project_root", "started_at", "status"]
644
+ .every((column) => sessionColumns.has(column));
645
+ metrics.push({
646
+ key: "telemetry_session_schema",
647
+ value: sessionSchemaReady ? "canonical" : "sessions table or identity columns missing",
648
+ target: "canonical sessions table",
649
+ ok: sessionSchemaReady,
650
+ severity: "error",
651
+ });
652
+ if (sessionSchemaReady) {
653
+ const recentSessionStarts = doctorCount(db, `
654
+ SELECT COUNT(*) count FROM sessions
655
+ WHERE project_root = ? AND started_at >= ?
656
+ `, project, cutoff);
657
+ metrics.push({
658
+ key: "session_start_evidence_24h",
659
+ value: recentSessionStarts,
660
+ target: "present after a Claude SessionStart",
661
+ ok: true,
662
+ severity: "info",
663
+ });
664
+ const staleSessions = doctorCount(db, `
665
+ SELECT COUNT(*) count FROM sessions
666
+ WHERE project_root = ? AND status = 'running' AND started_at < ?
667
+ `, project, (dependencies.now ?? Date.now)() - SESSION_STALE_MS);
668
+ metrics.push({
669
+ key: "stale_telemetry_sessions",
670
+ value: staleSessions,
671
+ target: 0,
672
+ ok: staleSessions === 0,
673
+ severity: "error",
674
+ });
675
+ let orphanToolEvents = 0;
676
+ if (toolEventColumns.has("session_id")) {
677
+ orphanToolEvents = doctorCount(db, `
678
+ SELECT COUNT(*) count
679
+ FROM tool_call_events event
680
+ LEFT JOIN sessions session ON session.id = event.session_id
681
+ WHERE TRIM(COALESCE(event.session_id, '')) = '' OR session.id IS NULL
682
+ `);
683
+ }
684
+ let orphanExecutions = 0;
685
+ if (executionColumns.has("session_id")) {
686
+ orphanExecutions = doctorCount(db, `
687
+ SELECT COUNT(*) count
688
+ FROM skill_executions execution
689
+ LEFT JOIN sessions session ON session.id = execution.session_id
690
+ WHERE TRIM(COALESCE(execution.session_id, '')) = '' OR session.id IS NULL
691
+ `);
692
+ }
693
+ metrics.push({
694
+ key: "orphan_telemetry_events",
695
+ value: { toolCalls: orphanToolEvents, skillExecutions: orphanExecutions },
696
+ target: { toolCalls: 0, skillExecutions: 0 },
697
+ ok: orphanToolEvents === 0 && orphanExecutions === 0,
698
+ severity: "error",
699
+ });
700
+ }
577
701
  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")) {
702
+ if (toolEventColumns.has("failure_category")) {
581
703
  const rows = db.prepare(`
582
704
  SELECT failure_category category, COUNT(*) count
583
705
  FROM tool_call_events
@@ -2575,7 +2697,8 @@ function marketplaceSourceRoot(sourcePath) {
2575
2697
  join(sourcePath, "plugins", "claude-code"),
2576
2698
  ]) {
2577
2699
  const root = canonicalDirectory(candidate);
2578
- if (root && existsSync(join(root, "hooks.json")))
2700
+ if (root && (existsSync(join(root, "hooks", "hooks.json"))
2701
+ || existsSync(join(root, "hooks.json"))))
2579
2702
  return root;
2580
2703
  }
2581
2704
  return null;
@@ -3266,7 +3389,11 @@ function inspectLegacyRoot(project, homePath, target, inspection) {
3266
3389
  enqueueCommand(command);
3267
3390
  }
3268
3391
  if (target.inspectPluginConfig || !target.commands) {
3269
- const configPath = join(root, "hooks.json");
3392
+ const conventionalConfigPath = join(root, "hooks", "hooks.json");
3393
+ const legacyConfigPath = join(root, "hooks.json");
3394
+ const configPath = existsSync(legacyConfigPath)
3395
+ ? legacyConfigPath
3396
+ : conventionalConfigPath;
3270
3397
  if (!existsSync(configPath)) {
3271
3398
  inspection.missing.push(configPath);
3272
3399
  enqueue(join(root, "hooks", "session-start"));
@@ -3395,6 +3522,111 @@ function inspectLegacyRoot(project, homePath, target, inspection) {
3395
3522
  }
3396
3523
  }
3397
3524
  }
3525
+ function installedPluginVersionsByRoot(dependencies) {
3526
+ const installedPluginsPath = dependencies.installedPluginsPath
3527
+ ?? join(dependencies.homePath ?? homedir(), ".claude", "plugins", "installed_plugins.json");
3528
+ const versions = new Map();
3529
+ if (!existsSync(installedPluginsPath))
3530
+ return new Map();
3531
+ try {
3532
+ const manifest = asRecord(parseJsonFile(installedPluginsPath));
3533
+ const plugins = asRecord(manifest?.plugins);
3534
+ if (!plugins)
3535
+ return new Map();
3536
+ for (const entries of Object.values(plugins)) {
3537
+ if (!Array.isArray(entries))
3538
+ continue;
3539
+ for (const entry of entries) {
3540
+ const record = asRecord(entry);
3541
+ if (typeof record?.installPath !== "string" || typeof record.version !== "string")
3542
+ continue;
3543
+ const root = canonicalDirectory(record.installPath);
3544
+ if (!root)
3545
+ continue;
3546
+ const rootVersions = versions.get(root) ?? new Set();
3547
+ rootVersions.add(record.version);
3548
+ versions.set(root, rootVersions);
3549
+ }
3550
+ }
3551
+ }
3552
+ catch {
3553
+ return new Map();
3554
+ }
3555
+ return new Map([...versions].map(([root, values]) => [root, [...values].sort()]));
3556
+ }
3557
+ function readPluginVersion(path) {
3558
+ try {
3559
+ const metadata = asRecord(parseJsonFile(path));
3560
+ return typeof metadata?.version === "string" ? metadata.version : undefined;
3561
+ }
3562
+ catch {
3563
+ return undefined;
3564
+ }
3565
+ }
3566
+ function inspectPluginLifecycle(targets, dependencies) {
3567
+ const inspection = {
3568
+ layoutIssues: [],
3569
+ versionIssues: [],
3570
+ versionRecords: [],
3571
+ sessionStartIssues: [],
3572
+ daemonIssues: [],
3573
+ };
3574
+ const installedVersions = installedPluginVersionsByRoot(dependencies);
3575
+ for (const target of targets) {
3576
+ const root = target.root;
3577
+ const packagePath = join(root, "package.json");
3578
+ const manifestPath = join(root, ".claude-plugin", "plugin.json");
3579
+ const hasPackageMetadata = existsSync(packagePath) || existsSync(manifestPath);
3580
+ if (!hasPackageMetadata)
3581
+ continue;
3582
+ const conventionalConfigPath = join(root, "hooks", "hooks.json");
3583
+ const legacyConfigPath = join(root, "hooks.json");
3584
+ if (!existsSync(conventionalConfigPath)) {
3585
+ inspection.layoutIssues.push({ root, issue: "missing hooks/hooks.json" });
3586
+ }
3587
+ if (existsSync(legacyConfigPath)) {
3588
+ inspection.layoutIssues.push({ root, issue: "retired root hooks.json is present" });
3589
+ }
3590
+ const activeConfigPath = existsSync(conventionalConfigPath)
3591
+ ? conventionalConfigPath
3592
+ : legacyConfigPath;
3593
+ try {
3594
+ const config = asRecord(parseJsonFile(activeConfigPath));
3595
+ const hooks = asRecord(config?.hooks);
3596
+ const commands = [];
3597
+ collectCommandStrings(hooks?.SessionStart, commands);
3598
+ if (!commands.some((command) => /(?:^|[/\\])session-start(?:\s|$|["'])/.test(command))) {
3599
+ inspection.sessionStartIssues.push({ root, issue: "SessionStart wrapper is not registered" });
3600
+ }
3601
+ }
3602
+ catch {
3603
+ inspection.sessionStartIssues.push({ root, issue: "hook configuration is unreadable" });
3604
+ }
3605
+ const packageVersion = readPluginVersion(packagePath);
3606
+ const manifestVersion = readPluginVersion(manifestPath);
3607
+ const registryVersions = installedVersions.get(root) ?? [];
3608
+ const versionRecord = { root, packageVersion, manifestVersion, registryVersions };
3609
+ inspection.versionRecords.push(versionRecord);
3610
+ if (!packageVersion
3611
+ || !manifestVersion
3612
+ || packageVersion !== manifestVersion
3613
+ || registryVersions.some((version) => version !== packageVersion)) {
3614
+ inspection.versionIssues.push(versionRecord);
3615
+ }
3616
+ const sessionStartPath = join(root, "hooks", "session-start");
3617
+ const daemonPath = join(root, "dist", "hooks", "hook-daemon.js");
3618
+ try {
3619
+ const sessionStart = readFileSync(sessionStartPath, "utf8");
3620
+ if (!existsSync(daemonPath) || !sessionStart.includes("hook-daemon.js")) {
3621
+ inspection.daemonIssues.push({ root, issue: "SessionStart does not register the packaged hook daemon" });
3622
+ }
3623
+ }
3624
+ catch {
3625
+ inspection.daemonIssues.push({ root, issue: "SessionStart wrapper or hook daemon is missing" });
3626
+ }
3627
+ }
3628
+ return inspection;
3629
+ }
3398
3630
  function checkLegacyPaths(project, dependencies) {
3399
3631
  const selection = selectActiveLegacyTargets(project, dependencies);
3400
3632
  const inspection = {
@@ -3438,6 +3670,7 @@ function checkLegacyPaths(project, dependencies) {
3438
3670
  const resolutionMethod = artifactResolutionFailed && !selection.method.endsWith("-unresolved")
3439
3671
  ? `${selection.method}-unresolved`
3440
3672
  : selection.method;
3673
+ const lifecycle = inspectPluginLifecycle(selection.targets, dependencies);
3441
3674
  const metrics = [
3442
3675
  {
3443
3676
  key: "active_plugin_resolution",
@@ -3458,6 +3691,34 @@ function checkLegacyPaths(project, dependencies) {
3458
3691
  ok: true,
3459
3692
  severity: "info",
3460
3693
  },
3694
+ {
3695
+ key: "plugin_hook_layout",
3696
+ value: lifecycle.layoutIssues,
3697
+ target: "hooks/hooks.json present and root hooks.json absent",
3698
+ ok: lifecycle.layoutIssues.length === 0,
3699
+ severity: "error",
3700
+ },
3701
+ {
3702
+ key: "plugin_version_parity",
3703
+ value: lifecycle.versionRecords,
3704
+ target: "manifest, package, and installed registry versions match",
3705
+ ok: lifecycle.versionIssues.length === 0,
3706
+ severity: "error",
3707
+ },
3708
+ {
3709
+ key: "session_start_hook_registration",
3710
+ value: lifecycle.sessionStartIssues,
3711
+ target: [],
3712
+ ok: lifecycle.sessionStartIssues.length === 0,
3713
+ severity: "error",
3714
+ },
3715
+ {
3716
+ key: "hook_daemon_artifact_registration",
3717
+ value: lifecycle.daemonIssues,
3718
+ target: [],
3719
+ ok: lifecycle.daemonIssues.length === 0,
3720
+ severity: "error",
3721
+ },
3461
3722
  {
3462
3723
  key: "malformed_active_settings",
3463
3724
  value: selection.malformedSettings,