@devflow-tools/cli 0.16.12 → 0.16.14

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.
@@ -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,7 +6,7 @@ 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, checkMcpHealth, loadPluginsWithDiagnostics, readMcpInventory, redactMcpInventory, selectHostAdapter, } from "@devflow-tools/sdk";
9
+ import { DOCTOR_SCHEMA_VERSION, checkMcpHealth, loadPluginsWithDiagnostics, readMcpInventory, redactMcpInventory, readRuntimeEndpointState, listRuntimeHttpCircuits, selectHostAdapter, } from "@devflow-tools/sdk";
10
10
  import { resolveActivePlugin, scanClaudeSkillShadowing } from "../lib/plugin-setup.js";
11
11
  import { serverStatus } from "./server.js";
12
12
  import { inspectSkillHealth } from "./skill-health.js";
@@ -132,6 +132,24 @@ async function checkRuntimeControlPlane(project, dependencies) {
132
132
  severity: "warning",
133
133
  });
134
134
  }
135
+ const endpoint = readRuntimeEndpointState(home);
136
+ metrics.push({
137
+ key: 'runtime_endpoint_registry',
138
+ value: endpoint ?? 'not registered',
139
+ target: 'healthy while active, stopped or absent while idle',
140
+ ok: !endpoint || endpoint.status === 'healthy' || endpoint.status === 'stopped',
141
+ severity: endpoint?.status === 'degraded' ? 'error' : 'warning',
142
+ });
143
+ const now = (dependencies.now ?? Date.now)();
144
+ const openCircuits = listRuntimeHttpCircuits(home)
145
+ .filter(circuit => circuit.status === 'open' && (circuit.openUntil ?? 0) > now);
146
+ metrics.push({
147
+ key: 'runtime_http_circuits',
148
+ value: openCircuits,
149
+ target: [],
150
+ ok: openCircuits.length === 0,
151
+ severity: openCircuits.some(circuit => circuit.failures >= 2) ? 'error' : 'warning',
152
+ });
135
153
  return { name: "Contract", status: sectionStatus(metrics), metrics };
136
154
  }
137
155
  function canonicalizeProject(project) {
@@ -575,7 +593,10 @@ function semanticToolOutcome(row) {
575
593
  const data = doctorRecord(output?.data);
576
594
  const status = typeof output?.status === "string" ? output.status : undefined;
577
595
  const metricStatus = typeof row.metric_status === "string" ? row.metric_status : undefined;
578
- const failed = Boolean(row.error)
596
+ const expectedPolicyBlock = Boolean(row.blocked)
597
+ && typeof row.block_reason === "string"
598
+ && row.block_reason.startsWith("DEVFLOW_");
599
+ const failed = !expectedPolicyBlock && (Boolean(row.error)
579
600
  || Boolean(row.failure_category)
580
601
  || output?.ok === false
581
602
  || output?.success === false
@@ -583,7 +604,7 @@ function semanticToolOutcome(row) {
583
604
  || Boolean(output?.error)
584
605
  || status === "failed"
585
606
  || status === "rejected"
586
- || metricStatus === "error";
607
+ || metricStatus === "error");
587
608
  const degraded = !failed && (output?.degraded === true
588
609
  || Boolean(output?.degradation)
589
610
  || data?.degraded === true
@@ -691,8 +712,21 @@ async function checkVectors(project, dependencies) {
691
712
  metrics.push({ key: "knowledge_vector_schema", value: missing, target: [], ok: false, severity: "warning" });
692
713
  }
693
714
  else {
694
- const chunks = doctorCount(db, "SELECT COUNT(*) count FROM knowledge_chunks");
695
- const vectors = doctorCount(db, "SELECT COUNT(*) count FROM knowledge_vec_rowids");
715
+ const docColumns = doctorTableColumns(db, "knowledge_docs");
716
+ const activeClause = docColumns.has("status") ? "AND d.status = 'active'" : "";
717
+ const chunks = doctorCount(db, `
718
+ SELECT COUNT(*) count
719
+ FROM knowledge_chunks c
720
+ JOIN knowledge_docs d ON d.id = c.doc_id
721
+ WHERE 1 = 1 ${activeClause}
722
+ `);
723
+ const vectors = doctorCount(db, `
724
+ SELECT COUNT(*) count
725
+ FROM knowledge_vec_rowids v
726
+ JOIN knowledge_chunks c ON c.id = v.id
727
+ JOIN knowledge_docs d ON d.id = c.doc_id
728
+ WHERE 1 = 1 ${activeClause}
729
+ `);
696
730
  const value = coverage(chunks, vectors);
697
731
  const rows = db.prepare(`
698
732
  SELECT d.source source, COUNT(c.id) chunks,
@@ -700,6 +734,7 @@ async function checkVectors(project, dependencies) {
700
734
  FROM knowledge_docs d
701
735
  LEFT JOIN knowledge_chunks c ON c.doc_id = d.id
702
736
  LEFT JOIN knowledge_vec_rowids v ON v.id = c.id
737
+ WHERE 1 = 1 ${activeClause}
703
738
  GROUP BY d.source ORDER BY d.source
704
739
  `).all();
705
740
  const bySource = rows.map(row => {
@@ -779,7 +814,13 @@ async function checkVectors(project, dependencies) {
779
814
  metrics.push({ key: "embedder_error_log_read", value: error.message, target: "readable error log", ok: false, severity: "error" });
780
815
  }
781
816
  }
782
- metrics.push({ key: "embedder_errors_24h", value: recentErrors, target: 0, ok: recentErrors === 0, severity: "warning" });
817
+ metrics.push({
818
+ key: "embedder_errors_24h",
819
+ value: recentErrors,
820
+ target: "trend; current readiness is gated by model state and integrity",
821
+ ok: true,
822
+ severity: "info",
823
+ });
783
824
  try {
784
825
  const model = await (dependencies.inspectEmbeddingModel ?? inspectEmbeddingModel)({
785
826
  homeDir: dependencies.homePath,
@@ -1105,6 +1146,9 @@ function checkTelemetry(project, strict, dependencies) {
1105
1146
  toolEventColumns.has("error") ? "event.error" : "NULL error",
1106
1147
  toolEventColumns.has("failure_category") ? "event.failure_category" : "NULL failure_category",
1107
1148
  toolEventColumns.has("output") ? "event.output" : "NULL output",
1149
+ toolEventColumns.has("blocked") ? "event.blocked" : "0 blocked",
1150
+ toolEventColumns.has("block_reason") ? "event.block_reason" : "NULL block_reason",
1151
+ canProjectFilterEvents ? "event_session.status session_status" : "NULL session_status",
1108
1152
  canJoinMetrics ? "metric.status metric_status" : "NULL metric_status",
1109
1153
  ].join(", ");
1110
1154
  const eventRows = db.prepare(`
@@ -1117,11 +1161,20 @@ function checkTelemetry(project, strict, dependencies) {
1117
1161
  `).all(...(canProjectFilterEvents ? [cutoff, project] : [cutoff]));
1118
1162
  const semantic = eventRows.map(semanticToolOutcome);
1119
1163
  const semanticFailures = semantic.filter(outcome => outcome.failed).length;
1164
+ const unresolvedSemanticFailures = semantic.filter((outcome, index) => outcome.failed
1165
+ && eventRows[index]?.session_status !== "completed").length;
1120
1166
  const semanticDegradations = semantic.filter(outcome => outcome.degraded).length;
1121
1167
  metrics.push({ key: "semantic_tool_calls_24h", value: eventRows.length, ok: true, severity: "info" });
1122
- metrics.push({ key: "semantic_tool_failures_24h", value: semanticFailures, target: 0, ok: semanticFailures === 0, severity: strictSeverity });
1168
+ metrics.push({ key: "semantic_tool_failures_24h", value: semanticFailures, target: "trend", ok: true, severity: "info" });
1169
+ metrics.push({
1170
+ key: "unresolved_semantic_tool_failures_24h",
1171
+ value: unresolvedSemanticFailures,
1172
+ target: 0,
1173
+ ok: unresolvedSemanticFailures === 0,
1174
+ severity: strictSeverity,
1175
+ });
1123
1176
  metrics.push({ key: "semantic_tool_degradations_24h", value: semanticDegradations, target: 0, ok: semanticDegradations === 0, severity: strictSeverity });
1124
- metrics.push({ key: "semantic_tool_failure_rate_24h", value: rate(semanticFailures, eventRows.length), target: 0, ok: semanticFailures === 0, severity: strictSeverity });
1177
+ metrics.push({ key: "semantic_tool_failure_rate_24h", value: rate(semanticFailures, eventRows.length), target: "trend", ok: true, severity: "info" });
1125
1178
  metrics.push({ key: "semantic_tool_degradation_rate_24h", value: rate(semanticDegradations, eventRows.length), target: 0, ok: semanticDegradations === 0, severity: strictSeverity });
1126
1179
  metrics.push({
1127
1180
  key: "semantic_tool_project_scope",
@@ -1137,8 +1190,11 @@ function checkTelemetry(project, strict, dependencies) {
1137
1190
  continue;
1138
1191
  distribution[row.failure_category] = (distribution[row.failure_category] ?? 0) + 1;
1139
1192
  }
1140
- const classifiedEvents = Object.values(distribution).reduce((sum, count) => sum + count, 0);
1141
- const classificationCoverage = semanticFailures === 0 ? 1 : classifiedEvents / semanticFailures;
1193
+ const unresolvedRows = eventRows.filter((row, index) => semantic[index]?.failed
1194
+ && row.session_status !== "completed");
1195
+ const classifiedEvents = unresolvedRows.filter(row => typeof row.failure_category === "string"
1196
+ && row.failure_category.trim().length > 0).length;
1197
+ const classificationCoverage = unresolvedRows.length === 0 ? 1 : classifiedEvents / unresolvedRows.length;
1142
1198
  metrics.push({
1143
1199
  key: "failure_categories_24h",
1144
1200
  value: distribution,
@@ -1505,6 +1561,75 @@ function checkBenchmark(dependencies) {
1505
1561
  }
1506
1562
  return { name: "Benchmark", status: sectionStatus(metrics), metrics };
1507
1563
  }
1564
+ function checkRetrievalQuality(project, dependencies) {
1565
+ const metrics = [];
1566
+ const home = dependencies.homePath ?? homedir();
1567
+ const path = getGlobalDevFlowDbPath(home);
1568
+ if (!existsSync(path)) {
1569
+ metrics.push({
1570
+ key: 'retrieval_quality_report',
1571
+ value: 'missing global database',
1572
+ target: 'passing retrieval-quality report',
1573
+ ok: false,
1574
+ severity: 'warning',
1575
+ });
1576
+ return { name: 'Retrieval quality', status: sectionStatus(metrics), metrics };
1577
+ }
1578
+ let db;
1579
+ try {
1580
+ db = doctorOpenDatabase(path, dependencies);
1581
+ if (!doctorTableExists(db, 'benchmark_reports'))
1582
+ throw new Error('benchmark_reports table missing');
1583
+ const row = db.prepare(`
1584
+ SELECT report_json, created_at
1585
+ FROM benchmark_reports
1586
+ WHERE suite_id = 'retrieval-quality' AND project_root = ?
1587
+ ORDER BY created_at DESC
1588
+ LIMIT 1
1589
+ `).get(project);
1590
+ if (!row?.report_json) {
1591
+ metrics.push({
1592
+ key: 'retrieval_quality_report',
1593
+ value: 'no project report',
1594
+ target: 'run retrieval-quality benchmark for this project',
1595
+ ok: false,
1596
+ severity: 'warning',
1597
+ });
1598
+ return { name: 'Retrieval quality', status: sectionStatus(metrics), metrics };
1599
+ }
1600
+ const report = JSON.parse(row.report_json);
1601
+ const ageMs = (dependencies.now ?? Date.now)() - Number(row.created_at ?? 0);
1602
+ metrics.push({
1603
+ key: 'retrieval_quality_report',
1604
+ value: { status: report.qualityStatus, ageMs, diagnostics: report.diagnostics ?? [] },
1605
+ target: { status: 'passed', maxAgeMs: BENCHMARK_MAX_AGE_MS },
1606
+ ok: report.qualityStatus === 'passed' && ageMs <= BENCHMARK_MAX_AGE_MS,
1607
+ severity: report.qualityStatus === 'failed' ? 'error' : 'warning',
1608
+ });
1609
+ for (const [key, passed] of Object.entries(report.gates ?? {})) {
1610
+ metrics.push({
1611
+ key: `retrieval_${key}`,
1612
+ value: report.metrics?.[key],
1613
+ target: report.thresholds?.[key],
1614
+ ok: passed,
1615
+ severity: 'error',
1616
+ });
1617
+ }
1618
+ }
1619
+ catch (error) {
1620
+ metrics.push({
1621
+ key: 'retrieval_quality_read',
1622
+ value: error.message,
1623
+ target: 'readable retrieval quality report',
1624
+ ok: false,
1625
+ severity: 'error',
1626
+ });
1627
+ }
1628
+ finally {
1629
+ db?.close();
1630
+ }
1631
+ return { name: 'Retrieval quality', status: sectionStatus(metrics), metrics };
1632
+ }
1508
1633
  function hookDebugFootprint(root) {
1509
1634
  if (!existsSync(root))
1510
1635
  return { bytes: 0, files: 0 };
@@ -4505,6 +4630,50 @@ function checkMemoryQuality(project, dependencies) {
4505
4630
  ok: true,
4506
4631
  severity: "info",
4507
4632
  });
4633
+ if (doctorTableExists(db, 'retrieval_access_log')) {
4634
+ const attribution = db.prepare(`
4635
+ SELECT access_type, COUNT(*) count
4636
+ FROM retrieval_access_log
4637
+ GROUP BY access_type
4638
+ `).all();
4639
+ metrics.push({
4640
+ key: 'memory_retrieval_attribution',
4641
+ value: Object.fromEntries(attribution.map(row => [row.access_type, Number(row.count)])),
4642
+ target: 'selected/exposed are distinct from adopted',
4643
+ ok: true,
4644
+ severity: 'info',
4645
+ });
4646
+ }
4647
+ if (doctorTableExists(db, 'observations')) {
4648
+ const observationColumns = doctorTableColumns(db, 'observations');
4649
+ if (!observationColumns.has('evidence_status')) {
4650
+ metrics.push({
4651
+ key: 'explicit_memory_evidence_schema',
4652
+ value: 'evidence_status missing',
4653
+ target: 'memory schema v8+',
4654
+ ok: false,
4655
+ severity: 'error',
4656
+ });
4657
+ }
4658
+ else {
4659
+ const evidence = db.prepare(`
4660
+ SELECT
4661
+ COUNT(*) total,
4662
+ SUM(CASE WHEN evidence_status = 'verified' THEN 1 ELSE 0 END) verified
4663
+ FROM observations
4664
+ WHERE source = 'explicit_intent' AND quality_status = 'accepted'
4665
+ `).get();
4666
+ const total = Number(evidence?.total ?? 0);
4667
+ const verified = Number(evidence?.verified ?? 0);
4668
+ metrics.push({
4669
+ key: 'explicit_memory_evidence_coverage',
4670
+ value: total > 0 ? verified / total : 1,
4671
+ target: 1,
4672
+ ok: total === 0 || verified === total,
4673
+ severity: 'warning',
4674
+ });
4675
+ }
4676
+ }
4508
4677
  metrics.push({
4509
4678
  key: "memory_access_distribution",
4510
4679
  value: {
@@ -4538,13 +4707,41 @@ function checkKnowledgeQuality(project, dependencies) {
4538
4707
  metrics.push({ key: "knowledge_schema", value: "knowledge_chunks missing", target: "Batch 3 knowledge schema", ok: false, severity: "warning" });
4539
4708
  }
4540
4709
  else {
4541
- const lengths = db.prepare("SELECT length(content) length FROM knowledge_chunks ORDER BY length(content)").all().map(row => Number(row.length));
4710
+ const chunkColumns = doctorTableColumns(db, 'knowledge_chunks');
4711
+ const tokenExpression = chunkColumns.has('token_count')
4712
+ ? 'COALESCE(token_count, CAST((length(content) + 3) / 4 AS INTEGER))'
4713
+ : 'CAST((length(content) + 3) / 4 AS INTEGER)';
4714
+ const lengths = db.prepare(`SELECT ${tokenExpression} length FROM knowledge_chunks ORDER BY ${tokenExpression}`).all().map(row => Number(row.length));
4542
4715
  const p95Index = Math.max(0, Math.ceil(lengths.length * 0.95) - 1);
4543
4716
  const p95 = lengths.length === 0 ? 0 : lengths[p95Index];
4544
- metrics.push({ key: "knowledge_chunk_length_p95", value: p95, target: "<= 3000", ok: p95 <= 3000, severity: "warning" });
4717
+ metrics.push({ key: "knowledge_chunk_length_p95", value: p95, target: "<= 3000 tokens", ok: p95 <= 3000, severity: "error" });
4545
4718
  const fts = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'knowledge_fts'").get();
4546
4719
  const trigram = /tokenize\s*=\s*['\"]?trigram/i.test(fts?.sql ?? "");
4547
4720
  metrics.push({ key: "knowledge_fts_tokenizer", value: trigram ? "trigram" : "missing/non-trigram", target: "trigram", ok: trigram, severity: "warning" });
4721
+ if (doctorTableExists(db, 'knowledge_source_readiness')) {
4722
+ const readiness = db.prepare(`
4723
+ SELECT source, expected_version, documents, chunks, vectors, coverage, status, failure_category
4724
+ FROM knowledge_source_readiness ORDER BY source
4725
+ `).all();
4726
+ const active = readiness.filter(row => Number(row.documents ?? 0) > 0 || Number(row.chunks ?? 0) > 0);
4727
+ const minimumCoverage = active.length > 0
4728
+ ? Math.min(...active.map(row => Number(row.coverage ?? 0)))
4729
+ : 0;
4730
+ metrics.push({
4731
+ key: 'knowledge_source_readiness',
4732
+ value: readiness,
4733
+ target: 'all active sources current with >=95% vector coverage',
4734
+ ok: active.length > 0 && active.every(row => row.status === 'ready' && Number(row.coverage ?? 0) >= 0.95),
4735
+ severity: 'error',
4736
+ });
4737
+ metrics.push({
4738
+ key: 'knowledge_vector_coverage',
4739
+ value: minimumCoverage,
4740
+ target: '>= 0.95',
4741
+ ok: active.length > 0 && minimumCoverage >= 0.95,
4742
+ severity: 'error',
4743
+ });
4744
+ }
4548
4745
  }
4549
4746
  }
4550
4747
  catch (error) {
@@ -4596,8 +4793,19 @@ async function checkUiReachability(dependencies) {
4596
4793
  const { manifest, metrics } = acceptanceManifestMetrics(dependencies);
4597
4794
  const routes = manifest?.ui?.routes ?? [];
4598
4795
  metrics.push({ key: "dashboard_routes", value: routes.length, target: ">= 13", ok: manifest?.ui?.status === "green" && routes.length >= 13, severity: "warning" });
4796
+ const endpoint = readRuntimeEndpointState(dependencies.homePath ?? homedir());
4797
+ if ((!endpoint || endpoint.status === "stopped") && !dependencies.httpGet) {
4798
+ metrics.push({
4799
+ key: "dashboard_server_health",
4800
+ value: "inactive",
4801
+ target: "HTTP 200 when the runtime endpoint is active",
4802
+ ok: true,
4803
+ severity: "info",
4804
+ });
4805
+ return { name: "UI reachability", status: sectionStatus(metrics), metrics };
4806
+ }
4599
4807
  const port = dependencies.env?.PORT ?? process.env.PORT ?? "13337";
4600
- const healthUrl = `http://127.0.0.1:${port}/api/health`;
4808
+ const healthUrl = `${endpoint?.url ?? `http://127.0.0.1:${port}`}/api/health`;
4601
4809
  try {
4602
4810
  const response = await (dependencies.httpGet ?? defaultHttpGet)(healthUrl);
4603
4811
  metrics.push({ key: "dashboard_server_health", value: { url: healthUrl, status: response.status }, target: "HTTP 200", ok: response.status === 200, severity: "warning" });
@@ -4626,14 +4834,14 @@ export async function runStructuredDoctor(options = {}, dependencies = {}) {
4626
4834
  contract,
4627
4835
  checkDeadcode(dependencies),
4628
4836
  checkBenchmark(dependencies),
4837
+ checkRetrievalQuality(canonical.path, dependencies),
4629
4838
  checkDataFace(dependencies),
4630
4839
  checkRuntimeSecurity(dependencies),
4631
4840
  await checkUiReachability(dependencies),
4632
4841
  checkHookPathHealth(dependencies),
4633
4842
  ];
4634
- const exitCode = options.strict && sections.some((section) => section.status !== "green")
4635
- ? 1
4636
- : 0;
4843
+ const exitCode = sections.some(section => section.status === 'red'
4844
+ || (options.strict && section.status === 'yellow')) ? 1 : 0;
4637
4845
  return { schemaVersion: DOCTOR_SCHEMA_VERSION, sections, exitCode };
4638
4846
  }
4639
4847
  export function runDoctor(options, dependencies = {}) {