@herbertgao/pi-extensions 2026.9.7 → 2026.9.9

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 (158) hide show
  1. package/README.md +8 -4
  2. package/THIRD_PARTY_NOTICES.md +52 -0
  3. package/node_modules/@herbertgao/pi-bark/package.json +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
  5. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/package.json +4 -4
  7. package/node_modules/@herbertgao/resume-from/package.json +2 -2
  8. package/node_modules/@narumitw/pi-btw/README.md +6 -1
  9. package/node_modules/@narumitw/pi-btw/dist/index.ts +209 -23
  10. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  11. package/node_modules/@narumitw/pi-btw/package.json +1 -1
  12. package/node_modules/@narumitw/pi-btw/src/btw.ts +1 -12
  13. package/node_modules/@narumitw/pi-btw/src/menu.ts +244 -15
  14. package/node_modules/@narumitw/pi-btw/src/settings.ts +5 -0
  15. package/node_modules/grok-mermaid/CHANGELOG.md +46 -0
  16. package/node_modules/grok-mermaid/LICENSE +205 -0
  17. package/node_modules/grok-mermaid/README.md +191 -0
  18. package/node_modules/grok-mermaid/dist/ansi.d.ts +16 -0
  19. package/node_modules/grok-mermaid/dist/ansi.d.ts.map +1 -0
  20. package/node_modules/grok-mermaid/dist/ansi.js +23 -0
  21. package/node_modules/grok-mermaid/dist/ansi.js.map +1 -0
  22. package/node_modules/grok-mermaid/dist/canvas.d.ts +87 -0
  23. package/node_modules/grok-mermaid/dist/canvas.d.ts.map +1 -0
  24. package/node_modules/grok-mermaid/dist/canvas.js +366 -0
  25. package/node_modules/grok-mermaid/dist/canvas.js.map +1 -0
  26. package/node_modules/grok-mermaid/dist/graph.d.ts +74 -0
  27. package/node_modules/grok-mermaid/dist/graph.d.ts.map +1 -0
  28. package/node_modules/grok-mermaid/dist/graph.js +91 -0
  29. package/node_modules/grok-mermaid/dist/graph.js.map +1 -0
  30. package/node_modules/grok-mermaid/dist/index.d.ts +32 -0
  31. package/node_modules/grok-mermaid/dist/index.d.ts.map +1 -0
  32. package/node_modules/grok-mermaid/dist/index.js +100 -0
  33. package/node_modules/grok-mermaid/dist/index.js.map +1 -0
  34. package/node_modules/grok-mermaid/dist/labels.d.ts +62 -0
  35. package/node_modules/grok-mermaid/dist/labels.d.ts.map +1 -0
  36. package/node_modules/grok-mermaid/dist/labels.js +324 -0
  37. package/node_modules/grok-mermaid/dist/labels.js.map +1 -0
  38. package/node_modules/grok-mermaid/dist/layout-seq.d.ts +12 -0
  39. package/node_modules/grok-mermaid/dist/layout-seq.d.ts.map +1 -0
  40. package/node_modules/grok-mermaid/dist/layout-seq.js +194 -0
  41. package/node_modules/grok-mermaid/dist/layout-seq.js.map +1 -0
  42. package/node_modules/grok-mermaid/dist/layout.d.ts +87 -0
  43. package/node_modules/grok-mermaid/dist/layout.d.ts.map +1 -0
  44. package/node_modules/grok-mermaid/dist/layout.js +881 -0
  45. package/node_modules/grok-mermaid/dist/layout.js.map +1 -0
  46. package/node_modules/grok-mermaid/dist/parse.d.ts +83 -0
  47. package/node_modules/grok-mermaid/dist/parse.d.ts.map +1 -0
  48. package/node_modules/grok-mermaid/dist/parse.js +1151 -0
  49. package/node_modules/grok-mermaid/dist/parse.js.map +1 -0
  50. package/node_modules/grok-mermaid/dist/source-box.d.ts +18 -0
  51. package/node_modules/grok-mermaid/dist/source-box.d.ts.map +1 -0
  52. package/node_modules/grok-mermaid/dist/source-box.js +78 -0
  53. package/node_modules/grok-mermaid/dist/source-box.js.map +1 -0
  54. package/node_modules/grok-mermaid/dist/types.d.ts +42 -0
  55. package/node_modules/grok-mermaid/dist/types.d.ts.map +1 -0
  56. package/node_modules/grok-mermaid/dist/types.js +1 -0
  57. package/node_modules/grok-mermaid/dist/types.js.map +1 -0
  58. package/node_modules/grok-mermaid/dist/width-data.d.ts +2 -0
  59. package/node_modules/grok-mermaid/dist/width-data.d.ts.map +1 -0
  60. package/node_modules/grok-mermaid/dist/width-data.js +994 -0
  61. package/node_modules/grok-mermaid/dist/width-data.js.map +1 -0
  62. package/node_modules/grok-mermaid/dist/width.d.ts +18 -0
  63. package/node_modules/grok-mermaid/dist/width.d.ts.map +1 -0
  64. package/node_modules/grok-mermaid/dist/width.js +76 -0
  65. package/node_modules/grok-mermaid/dist/width.js.map +1 -0
  66. package/node_modules/grok-mermaid/package.json +49 -0
  67. package/node_modules/grok-mermaid/src/ansi.ts +34 -0
  68. package/node_modules/grok-mermaid/src/canvas.ts +373 -0
  69. package/node_modules/grok-mermaid/src/graph.ts +142 -0
  70. package/node_modules/grok-mermaid/src/index.ts +104 -0
  71. package/node_modules/grok-mermaid/src/labels.ts +326 -0
  72. package/node_modules/grok-mermaid/src/layout-seq.ts +203 -0
  73. package/node_modules/grok-mermaid/src/layout.ts +1015 -0
  74. package/node_modules/grok-mermaid/src/parse.ts +1189 -0
  75. package/node_modules/grok-mermaid/src/source-box.ts +89 -0
  76. package/node_modules/grok-mermaid/src/types.ts +43 -0
  77. package/node_modules/grok-mermaid/src/width-data.ts +993 -0
  78. package/node_modules/grok-mermaid/src/width.ts +74 -0
  79. package/node_modules/pi-jev-auto-mode/CHANGELOG.md +110 -0
  80. package/node_modules/pi-jev-auto-mode/LICENSE +21 -0
  81. package/node_modules/pi-jev-auto-mode/README.md +285 -0
  82. package/node_modules/pi-jev-auto-mode/SECURITY.md +32 -0
  83. package/node_modules/pi-jev-auto-mode/docs/calibration.md +154 -0
  84. package/node_modules/pi-jev-auto-mode/docs/design.md +164 -0
  85. package/node_modules/pi-jev-auto-mode/docs/security.md +124 -0
  86. package/node_modules/pi-jev-auto-mode/index.ts +1 -0
  87. package/node_modules/pi-jev-auto-mode/package.json +72 -0
  88. package/node_modules/pi-jev-auto-mode/src/call.ts +180 -0
  89. package/node_modules/pi-jev-auto-mode/src/decide.ts +86 -0
  90. package/node_modules/pi-jev-auto-mode/src/extension.ts +862 -0
  91. package/node_modules/pi-jev-auto-mode/src/intent.ts +71 -0
  92. package/node_modules/pi-jev-auto-mode/src/jev/availability.ts +53 -0
  93. package/node_modules/pi-jev-auto-mode/src/jev/criteria.ts +19 -0
  94. package/node_modules/pi-jev-auto-mode/src/jev/decide.ts +187 -0
  95. package/node_modules/pi-jev-auto-mode/src/jev/engine.ts +165 -0
  96. package/node_modules/pi-jev-auto-mode/src/jev/index.ts +20 -0
  97. package/node_modules/pi-jev-auto-mode/src/jev/questions.ts +273 -0
  98. package/node_modules/pi-jev-auto-mode/src/jev/response.ts +64 -0
  99. package/node_modules/pi-jev-auto-mode/src/jev/state.ts +20 -0
  100. package/node_modules/pi-jev-auto-mode/src/jev/transport.ts +117 -0
  101. package/node_modules/pi-jev-auto-mode/src/jev/types.ts +46 -0
  102. package/node_modules/pi-jev-auto-mode/src/policy.ts +558 -0
  103. package/node_modules/pi-jev-auto-mode/src/records.ts +118 -0
  104. package/node_modules/pi-jev-auto-mode/src/settings.ts +327 -0
  105. package/node_modules/pi-jev-auto-mode/src/ui.ts +233 -0
  106. package/node_modules/pi-lens/CHANGELOG.md +45 -0
  107. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +21 -9
  108. package/node_modules/pi-lens/dist/clients/cascade-format.js +24 -0
  109. package/node_modules/pi-lens/dist/clients/extension-log.js +48 -1
  110. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +11 -12
  111. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +3 -0
  112. package/node_modules/pi-lens/dist/clients/lsp/index.js +41 -7
  113. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +9 -2
  114. package/node_modules/pi-lens/dist/clients/path-utils.js +51 -9
  115. package/node_modules/pi-lens/dist/clients/persistent-reverify.js +271 -0
  116. package/node_modules/pi-lens/dist/clients/pipeline.js +15 -1
  117. package/node_modules/pi-lens/dist/clients/runtime-turn.js +124 -7
  118. package/node_modules/pi-lens/dist/clients/skills-resolver.js +38 -2
  119. package/node_modules/pi-lens/dist/clients/widget-state.js +152 -6
  120. package/node_modules/pi-lens/dist/index.js +729 -344
  121. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +14 -2
  122. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +84 -17
  123. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +32 -5
  124. package/node_modules/pi-lens/package.json +1 -1
  125. package/node_modules/pi-typesafe/LICENSE +21 -0
  126. package/node_modules/pi-typesafe/README.md +153 -0
  127. package/node_modules/pi-typesafe/dist/ask.d.ts +32 -0
  128. package/node_modules/pi-typesafe/dist/ask.js +25 -0
  129. package/node_modules/pi-typesafe/dist/auth.d.ts +56 -0
  130. package/node_modules/pi-typesafe/dist/auth.js +114 -0
  131. package/node_modules/pi-typesafe/dist/batch.d.ts +74 -0
  132. package/node_modules/pi-typesafe/dist/batch.js +116 -0
  133. package/node_modules/pi-typesafe/dist/calibrate.d.ts +106 -0
  134. package/node_modules/pi-typesafe/dist/calibrate.js +157 -0
  135. package/node_modules/pi-typesafe/dist/client.d.ts +69 -0
  136. package/node_modules/pi-typesafe/dist/client.js +188 -0
  137. package/node_modules/pi-typesafe/dist/credentials.d.ts +46 -0
  138. package/node_modules/pi-typesafe/dist/credentials.js +112 -0
  139. package/node_modules/pi-typesafe/dist/errors.d.ts +9 -0
  140. package/node_modules/pi-typesafe/dist/errors.js +31 -0
  141. package/node_modules/pi-typesafe/dist/extension.d.ts +3 -0
  142. package/node_modules/pi-typesafe/dist/extension.js +222 -0
  143. package/node_modules/pi-typesafe/dist/index.d.ts +18 -0
  144. package/node_modules/pi-typesafe/dist/index.js +9 -0
  145. package/node_modules/pi-typesafe/dist/key-prompt.d.ts +3 -0
  146. package/node_modules/pi-typesafe/dist/key-prompt.js +43 -0
  147. package/node_modules/pi-typesafe/dist/login.d.ts +27 -0
  148. package/node_modules/pi-typesafe/dist/login.js +38 -0
  149. package/node_modules/pi-typesafe/dist/schema.d.ts +43 -0
  150. package/node_modules/pi-typesafe/dist/schema.js +152 -0
  151. package/node_modules/pi-typesafe/dist/ui.d.ts +3 -0
  152. package/node_modules/pi-typesafe/dist/ui.js +3 -0
  153. package/node_modules/pi-typesafe/dist/usage.d.ts +73 -0
  154. package/node_modules/pi-typesafe/dist/usage.js +182 -0
  155. package/node_modules/pi-typesafe/examples/decision-extension.ts +41 -0
  156. package/node_modules/pi-typesafe/extensions/index.js +2 -0
  157. package/node_modules/pi-typesafe/package.json +89 -0
  158. package/package.json +16 -8
@@ -45,8 +45,9 @@ import * as path from "node:path";
45
45
  import { Type } from "../clients/deps/typebox.js";
46
46
  import { markDisposition, normalizeMessage, } from "../clients/diagnostic-dispositions.js";
47
47
  import { insertSuppressComment } from "../clients/dispatch/suppress-writer.js";
48
- import { getFileDiagnostics, } from "../clients/widget-state.js";
48
+ import { normalizeMapKey } from "../clients/path-utils.js";
49
49
  import { resolveLensToolName } from "../clients/tool-config.js";
50
+ import { getFileDiagnostics, } from "../clients/widget-state.js";
50
51
  const DISPOSITIONS = [
51
52
  "false-positive",
52
53
  "suppress",
@@ -70,11 +71,22 @@ function isBlank(text) {
70
71
  * violation) are ambiguous; per #802 this picks the match CLOSEST to the
71
72
  * caller's line rather than guessing — the conservative choice for suppress,
72
73
  * which must never place a comment above the wrong site.
74
+ *
75
+ * #3160: `getFileDiagnostics` keys with `normalizeEphemeralMapKey` — case-
76
+ * preserving on POSIX by design (no filesystem I/O on that hot path) — but
77
+ * the widget's own writers key from `ctx.filePath`, which
78
+ * `createDispatchContext` already canonicalizes to the on-disk casing
79
+ * (#2016/#3098). `absPath` here is the caller's RAW spelling, so on a
80
+ * case-insensitive filesystem a mis-cased mark call must derive the same
81
+ * canonical key the writers used or it silently misses the live diagnostic
82
+ * and falls through to the fuzzy fallback below — do NOT case-fold
83
+ * `normalizeEphemeralMapKey` itself for this: on a case-sensitive filesystem
84
+ * that would merge two genuinely different files.
73
85
  */
74
86
  function widgetCrossCheck(absPath, tool, rule, message, callerLine) {
75
87
  let diagnostics;
76
88
  try {
77
- diagnostics = getFileDiagnostics(absPath);
89
+ diagnostics = getFileDiagnostics(normalizeMapKey(absPath));
78
90
  }
79
91
  catch {
80
92
  return undefined;
@@ -18,13 +18,13 @@ import { DEPENDENCY_DRIFT_MAX_DELIVERIES } from "../clients/blocker-freshness.js
18
18
  import { freshnessFromMtime } from "../clients/freshness.js";
19
19
  import { applyFindingPolicy, loadProjectRulePolicyMap, } from "../clients/dispatch/finding-policy.js";
20
20
  import { gateFindingsByPathFreshness } from "../clients/advisory-provenance.js";
21
- import { markUnreconciledFindings } from "../clients/finding-delivery-gate.js";
21
+ import { formatCacheAgeLabel, markUnreconciledFindings, } from "../clients/finding-delivery-gate.js";
22
22
  import { normalizeRuleId } from "../clients/dispatch/rule-id-normalize.js";
23
23
  import { applyRulePolicy, } from "../clients/dispatch/rule-policy.js";
24
24
  import { compactRenderResult } from "./render-compact.js";
25
25
  import { combineAbortSignals } from "../clients/deadline-utils.js";
26
26
  import { getProjectIgnoreMatcher } from "../clients/file-utils.js";
27
- import { isAtOrAboveHomeDir, normalizeEphemeralMapKey, normalizeFilePath, realpathOrResolve, } from "../clients/path-utils.js";
27
+ import { isAtOrAboveHomeDir, normalizeEphemeralMapKey, normalizeFilePath, normalizeMapKey, realpathOrResolve, } from "../clients/path-utils.js";
28
28
  import { getLSPService } from "../clients/lsp/index.js";
29
29
  import { retireInlineBlockerAndResyncGuard } from "../clients/git-guard.js";
30
30
  import { primaryServerId, resolveLspCwdForFile, } from "../clients/lsp/config.js";
@@ -508,7 +508,7 @@ function formatProjectDeltaDiagnostic(diagnostic, stale) {
508
508
  * rule/message), live -> unchanged. See `clients/finding-delivery-gate.ts`
509
509
  * surface `lens-diagnostics:mode-delta`.
510
510
  */
511
- function appendProjectDiagnosticsDeltaLines(lines, cwd, report, severity, includeFile) {
511
+ function appendProjectDiagnosticsDeltaLines(groups, cwd, report, severity, includeFile) {
512
512
  const scoped = (report?.diagnostics ?? []).filter((diagnostic) => includeFile(diagnostic.filePath) &&
513
513
  matchesSeverity(projectDiagnosticToWidget(diagnostic, diagnostic.filePath), severity));
514
514
  const gated = gateFindingsByPathFreshness({
@@ -532,11 +532,9 @@ function appendProjectDiagnosticsDeltaLines(lines, cwd, report, severity, includ
532
532
  byFile.set(filePath, bucket);
533
533
  }
534
534
  for (const [filePath, fileDiagnostics] of byFile) {
535
- const rel = path.relative(cwd, filePath);
536
- if (!lines.includes(rel))
537
- lines.push(rel);
535
+ const group = getDeltaFileGroup(groups, cwd, filePath);
538
536
  for (const diagnostic of fileDiagnostics) {
539
- lines.push(formatProjectDeltaDiagnostic(diagnostic, staleSet.has(diagnostic)));
537
+ group.projectLines.push(formatProjectDeltaDiagnostic(diagnostic, staleSet.has(diagnostic)));
540
538
  }
541
539
  }
542
540
  return diagnostics.length;
@@ -723,7 +721,15 @@ function applyDeltaFreshnessGate(files, cwd, generatedAt) {
723
721
  }
724
722
  for (const f of gated.stale) {
725
723
  const arr = byFile.get(f.filePath) ?? [];
726
- arr.push({ ...f.warning, stale: true, line: undefined });
724
+ // Fix B (#3167): carry the stamp the row was judged against so the render
725
+ // can emit one age label per file group — the row's own observation stamp,
726
+ // not the report-level one (the #2504 r4 multi-stamp case).
727
+ arr.push({
728
+ ...f.warning,
729
+ stale: true,
730
+ line: undefined,
731
+ staleAsOf: effectiveAt,
732
+ });
727
733
  byFile.set(f.filePath, arr);
728
734
  }
729
735
  return files
@@ -733,10 +739,34 @@ function applyDeltaFreshnessGate(files, cwd, generatedAt) {
733
739
  }))
734
740
  .filter((file) => file.warnings.length > 0);
735
741
  }
742
+ function getDeltaFileGroup(groups, cwd, filePath) {
743
+ const rel = path.relative(cwd, filePath);
744
+ let group = groups.get(rel);
745
+ if (!group) {
746
+ group = {
747
+ rel,
748
+ actionableLines: [],
749
+ qualityLines: [],
750
+ projectLines: [],
751
+ incomplete: false,
752
+ };
753
+ groups.set(rel, group);
754
+ }
755
+ return group;
756
+ }
736
757
  // @delivery-surface: lens-diagnostics:mode-delta
737
758
  function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
738
759
  const actionableEntry = cacheManager.readCache("actionable-warnings", cwd);
739
760
  const qualityEntry = cacheManager.readCache("code-quality-warnings", cwd);
761
+ // #3170: files whose re-verify pass could not complete inside its budget —
762
+ // their rows render as carried, plus this explicit gap label (never a
763
+ // false clean). Computed from the RAW actionable-warnings cache entries
764
+ // because the freshness pipeline below rebuilds the file shape. The
765
+ // re-verify only ever writes the actionable-warnings cache — the quality
766
+ // report's file shape carries no such marker.
767
+ const reverifyIncompletePaths = new Set((actionableEntry?.data?.files ?? [])
768
+ .filter((file) => file.reVerifyIncomplete)
769
+ .map((file) => normalizeMapKey(file.filePath)));
740
770
  const actionable = actionableEntry?.data;
741
771
  const quality = qualityEntry?.data;
742
772
  // Project rule policy (`.pi-lens.json` `rules.<id>.disable` /
@@ -778,32 +808,69 @@ function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
778
808
  warnings: matchingWarnings(file.warnings),
779
809
  }))
780
810
  .filter((file) => file.warnings.length > 0);
781
- const lines = [];
811
+ // #3196: one grouping pass keyed by file — every tier below appends into
812
+ // the SAME per-file group (see `DeltaFileGroup`/`getDeltaFileGroup`), so a
813
+ // file present in more than one report always renders its rows under its
814
+ // own header, wherever that header ends up in the final buffer.
815
+ const groups = new Map();
782
816
  // Fixable warnings from actionable-warnings and quality cache entries retain
783
817
  // their own severity tier. Apply the same threshold semantics as the LSP path.
818
+ // F5/F10 (#3168): at most ONE label group (age and/or #3170's re-verify
819
+ // gap) per file. The actionable tier is folded first, so its stale row (if
820
+ // any) wins the group's age label; the quality tier only supplies one when
821
+ // the actionable tier had none for that file (#3168 F10(c)'s quality-only
822
+ // shape). The `(re-verify incomplete)` flag is a fact about the file (from
823
+ // the raw actionable cache, independent of which tier's rows happen to
824
+ // render it), so it is recorded on the group the first time either tier
825
+ // touches that file and never predicted.
784
826
  if (filteredActionableFiles.length > 0) {
785
827
  for (const file of filteredActionableFiles) {
786
- const rel = path.relative(cwd, file.filePath);
787
- lines.push(`${rel}`);
828
+ const group = getDeltaFileGroup(groups, cwd, file.filePath);
788
829
  for (const w of file.warnings) {
789
830
  const where = w.stale ? STALE_LINE_MARKER : `L${w.line ?? "?"}`;
790
- lines.push(` ⚠ ${where} ${w.rule ?? w.code ?? w.tool} ${w.message}`);
831
+ group.actionableLines.push(` ⚠ ${where} ${w.rule ?? w.code ?? w.tool} ${w.message}`);
832
+ }
833
+ const key = normalizeMapKey(file.filePath);
834
+ if (reverifyIncompletePaths.has(key))
835
+ group.incomplete = true;
836
+ if (!group.staleRow) {
837
+ const staleWarning = file.warnings.find((w) => w.stale);
838
+ if (staleWarning)
839
+ group.staleRow = { staleAsOf: staleWarning.staleAsOf };
791
840
  }
792
841
  }
793
842
  }
794
843
  // Quality issues
795
844
  if (filteredQualityFiles.length > 0) {
796
845
  for (const file of filteredQualityFiles) {
797
- const rel = path.relative(cwd, file.filePath);
798
- if (!lines.includes(rel))
799
- lines.push(rel);
846
+ const group = getDeltaFileGroup(groups, cwd, file.filePath);
800
847
  for (const w of file.warnings) {
801
848
  const where = w.stale ? STALE_LINE_MARKER : `L${w.line ?? "?"}`;
802
- lines.push(` ℹ ${where} ${w.rule ?? w.code ?? w.tool} ${w.message}`);
849
+ group.qualityLines.push(` ℹ ${where} ${w.rule ?? w.code ?? w.tool} ${w.message}`);
850
+ }
851
+ const key = normalizeMapKey(file.filePath);
852
+ if (reverifyIncompletePaths.has(key))
853
+ group.incomplete = true;
854
+ if (!group.staleRow) {
855
+ const staleWarning = file.warnings.find((w) => w.stale);
856
+ if (staleWarning)
857
+ group.staleRow = { staleAsOf: staleWarning.staleAsOf };
803
858
  }
804
859
  }
805
860
  }
806
- const projectDeltaCount = appendProjectDiagnosticsDeltaLines(lines, cwd, projectDelta, severity, includeFile);
861
+ const projectDeltaCount = appendProjectDiagnosticsDeltaLines(groups, cwd, projectDelta, severity, includeFile);
862
+ const lines = [];
863
+ for (const group of groups.values()) {
864
+ lines.push(group.rel);
865
+ lines.push(...group.actionableLines);
866
+ lines.push(...group.qualityLines);
867
+ lines.push(...group.projectLines);
868
+ if (group.staleRow) {
869
+ lines.push(` (${formatCacheAgeLabel(group.staleRow.staleAsOf)})`);
870
+ }
871
+ if (group.incomplete)
872
+ lines.push(" (re-verify incomplete)");
873
+ }
807
874
  const selectedActionableFiles = filteredActionableFiles;
808
875
  const selectedQualityFiles = filteredQualityFiles;
809
876
  const aw = selectedActionableFiles.reduce((count, file) => count + file.warnings.length, 0);
@@ -23,7 +23,8 @@ import { classifyCascadeWaitTier } from "../clients/lsp/wait-policy/index.js";
23
23
  import { attemptTsserverSyncDiagnostics } from "../clients/lsp/tsserver-sync.js";
24
24
  import { convertLspDiagnostics } from "../clients/dispatch/utils/lsp-diagnostics.js";
25
25
  import { demoteInferredProjectDiagnostics } from "../clients/lsp/inferred-project.js";
26
- import { isBlocking, reconcileScanDiagnostics, } from "../clients/widget-state.js";
26
+ import { normalizeMapKey } from "../clients/path-utils.js";
27
+ import { countRetainedSuppressedRows, isBlocking, reconcileScanDiagnostics, } from "../clients/widget-state.js";
27
28
  import { makeProgressReporter } from "./scan-progress.js";
28
29
  import { isWarmAttached, tryWarmAttachedDiagnostics, } from "../clients/warm-attach.js";
29
30
  import { extensionsForLanguage, SCAN_LANGUAGE_PRIORITY, } from "../clients/language-registry.js";
@@ -239,7 +240,18 @@ onConfirmedNoBlockers, getService = getLSPService) {
239
240
  // Preserve input order (including duplicate entries); normalize each
240
241
  // path before grouping so Windows separators and dot segments cannot
241
242
  // change cache/group identity. Explicit lists never enter the walker.
242
- const absPaths = rawPaths.map((entry) => path.normalize(path.isAbsolute(entry) ? entry : path.resolve(cwd, entry)));
243
+ // #3160/#3182: `path.resolve(cwd, entry)` folds dot segments (and
244
+ // separators) structurally — it ignores `cwd` whenever `entry` is
245
+ // already absolute, so the old `path.isAbsolute` ternary is
246
+ // redundant. `normalizeMapKey` then adopts on-disk CASING where the
247
+ // path exists — an agent-typed, possibly mis-cased `paths` entry
248
+ // must key `reconcileScanDiagnostics`'s widget-state write the same
249
+ // way a canonical writer (clients/pipeline.ts's ctx.filePath) or the
250
+ // #3160-fixed lens_diagnostic_mark reader would. `path.resolve`
251
+ // stays required for the relative→absolute step: `normalizeMapKey`
252
+ // folds dot segments itself since #3184, but never resolves against
253
+ // `cwd` (a cwd fold there broke every monorepo, #2490).
254
+ const absPaths = rawPaths.map((entry) => normalizeMapKey(path.resolve(cwd, entry)));
243
255
  return runBatchFileDiagnostics(absPaths, severity, lspService, {
244
256
  concurrency,
245
257
  waitMs,
@@ -264,9 +276,12 @@ onConfirmedNoBlockers, getService = getLSPService) {
264
276
  details: {},
265
277
  };
266
278
  }
267
- const absPath = path.isAbsolute(rawPath)
268
- ? rawPath
269
- : path.resolve(cwd, rawPath);
279
+ // #3184: the single-`path` sibling of the `paths` batch above, and
280
+ // keyed the same way — `runFileDiagnostics` hands this straight to
281
+ // `reconcileScanDiagnostics`, whose `normalizeEphemeralMapKey` key
282
+ // derivation folds neither dot segments nor casing. Same expression as
283
+ // :444 so both modes of this tool write under ONE key per file.
284
+ const absPath = normalizeMapKey(path.resolve(cwd, rawPath));
270
285
  let stat;
271
286
  try {
272
287
  stat = fs.statSync(absPath);
@@ -616,6 +631,17 @@ severity) {
616
631
  // otherwise have SEEN, never one per finding (AGENTS.md "bounded
617
632
  // observability"). `total` is the in-scope population for the same
618
633
  // reason the count is, so the record and the rendered line agree.
634
+ //
635
+ // #3158 round 2 F4: `retainedSuppressed` is the widget store's
636
+ // SUCCESS-path number — how many suppressed rows the store is carrying
637
+ // INTO this scan, written by the scans before it. Until this, retention
638
+ // was observable only when its per-file cap truncated
639
+ // (`widget-suppressed-retention-capped`), so a healthy footer chip had no
640
+ // record behind it at all. Folded into the record this lane already emits
641
+ // rather than added as a second one: same cardinality (one per filtered
642
+ // file per scan, never one per row), no new sink, and the two numbers are
643
+ // read together — `suppressed` is what THIS scan dropped,
644
+ // `retainedSuppressed` is what the footer chip is still counting.
619
645
  logLatency({
620
646
  type: "phase",
621
647
  toolName: "lsp_diagnostics",
@@ -625,6 +651,7 @@ severity) {
625
651
  metadata: {
626
652
  suppressed,
627
653
  total: inScopeBefore,
654
+ retainedSuppressed: countRetainedSuppressedRows(file),
628
655
  },
629
656
  });
630
657
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-lens",
3
- "version": "4.2.0",
3
+ "version": "4.2.1",
4
4
  "type": "module",
5
5
  "description": "Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo",
6
6
  "repository": {
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ryan Gapac
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,153 @@
1
+ # pi-typesafe
2
+
3
+ [Jev](https://typesafe.ai) inside [Pi](https://pi.dev). Jev is TypeSafe's judgment model: send it some state and typed questions and it returns probabilities instead of prose, in well under a second, for a fraction of a cent. This package gives Pi three things built on it:
4
+
5
+ - **A tool for the agent.** `typesafe_evaluate` hands small structured judgments (classify, triage, compare, score) to Jev and returns calibrated numbers in one batched call.
6
+ - **A playground.** `/typesafe test` and `/typesafe playground` run requests from the terminal without touching the model's context.
7
+ - **A typed API for other extensions.** One client, one key store, one login prompt, so extensions such as [pi-warden](https://github.com/DevMortimer/pi-warden) do not each ask for a key — and so a long run can tell you whether Jev was reachable and what it cost.
8
+
9
+ ![Pi triaging three bug reports with one batched TypeSafe call: the prompt, the rendered TypeSafe answers, and the agent's verdict](https://raw.githubusercontent.com/DevMortimer/pi-typesafe/main/docs/preview.png)
10
+
11
+ Independent project. Not affiliated with TypeSafe AI or the Pi authors.
12
+
13
+ ## In one minute
14
+
15
+ ```bash
16
+ pi install npm:pi-typesafe
17
+ ```
18
+
19
+ Then, inside Pi:
20
+
21
+ 1. `/typesafe login` and paste a key from [console.typesafe.ai](https://console.typesafe.ai). Input is hidden; the key is verified against the API and saved to `~/.pi/agent/pi-typesafe/auth.json` with owner-only permissions.
22
+ 2. `/typesafe test` sends one built-in sample request and shows the answers.
23
+ 3. `/typesafe enable` lets the agent call the tool for this session, after you confirm the data notice.
24
+
25
+ Requires Pi 0.85 or newer and Node.js 22.19 or newer. Usage is billed to your TypeSafe account. For CI or scripts set `TYPESAFE_API_KEY` in the environment instead; it takes precedence over the stored key. Do not paste the key into chat, command arguments, or project files.
26
+
27
+ ## The three question types
28
+
29
+ Jev answers three kinds of question about the state you send. Every question in a request runs in parallel and in isolation, so adding questions barely changes the latency.
30
+
31
+ | Type | Asks | Returns |
32
+ | --- | --- | --- |
33
+ | **Choice** | Which of these options fits? | the chosen key, a probability per option, confidence |
34
+ | **Score** | Where on this ordered rubric does it sit? | a position (may be fractional), probabilities, confidence |
35
+ | **Noul** | Is this statement true? | a probability from 0 to 1 |
36
+
37
+ ```json
38
+ {
39
+ "state": { "message": "I was charged twice. Please help today." },
40
+ "questions": {
41
+ "team": { "type": "choice", "instructions": "Which team should handle this?",
42
+ "criteria": { "billing": "Charges and payments", "technical": "Software failures", "other": "None of these" } },
43
+ "urgent": { "type": "noul", "instructions": "Does the sender request help today?" },
44
+ "frustration": { "type": "score", "instructions": "How frustrated does the sender sound?",
45
+ "criteria": ["Neutral request", "Frustrated but civil", "Explicit anger or threats"] }
46
+ }
47
+ }
48
+ ```
49
+
50
+ Read probabilities and confidence alongside the answer. Confidence describes how concentrated the distribution is; it is not proof of correctness or permission to act.
51
+
52
+ ## The agent tool
53
+
54
+ `typesafe_evaluate` is registered at startup but **disabled until you run `/typesafe enable`** in the session. For automated or headless runs, set `PI_TYPESAFE_ENABLED=1` explicitly.
55
+
56
+ The tool accepts JSON `state` plus 1 to 32 questions and returns answers, model, token usage, and elapsed time. Good uses: triaging a list of issues in one call, deciding which of several files a change belongs to, checking whether a reply answers the question that was asked, scoring candidates against a rubric you wrote. Bad uses: anything that needs reasoning across steps, or a single question that mixes several judgments.
57
+
58
+ ## Writing questions that work
59
+
60
+ The question text is the whole program. Jev answers exactly what is asked, so ambiguity shows up as a middling probability rather than an error.
61
+
62
+ - **Ask about what the state says, not what you would conclude.** A report that says "happens every time" scored `P(yes) = 0.36` for `Is the bug reproducible from the text?` because that can also mean "could a reader reproduce it using only this text?". `Does the reporter state that the problem occurs consistently?` is the intended question.
63
+ - **Describe situations in Score levels, not degrees.** `"Workaround exists"` is checkable; `"medium"` is not.
64
+ - **Include a no-match option** in a Choice (`other`, `unclear`) when nothing may fit; the model cannot pick an option you omitted.
65
+ - **One judgment per question.** Split independent dimensions into separate questions and batch them in one request; they run in parallel and cannot see each other.
66
+ - **Name the state fields you mean** with backticks (`` `report.body` ``) when the state has several parts.
67
+
68
+ The [TypeSafe docs](https://docs.typesafe.ai/primitives) cover each primitive in detail.
69
+
70
+ ## Availability and spend
71
+
72
+ An enabled extension with no usable key looks exactly like a working one — one evaluation ran keyless for hours before anyone noticed. Three answers exist now, and `/typesafe status` prints all of them:
73
+
74
+ - **Is Jev reachable?** `authState()` reports the key source, whether it has been accepted, and the last failure that degraded it; `describeAuth()` turns that into a level and one safe line. The extension calls both at session start and after an authentication rejection, so a headless run says judgments are skipped instead of quietly falling back to its offline path.
75
+ - **What has this run cost?** `getSpend()` returns session counters, today's persisted counters, and the cap currently reached. Cost is estimated from input tokens only, because output is free.
76
+ - **When does it stop?** `maxRequests` bounds a client instance. Three caps bound a local day, survive restarts, and stop a request before it is submitted:
77
+
78
+ | Option | Environment | Bounds |
79
+ | --- | --- | --- |
80
+ | `maxRequestsPerDay` | `PI_TYPESAFE_MAX_REQUESTS_PER_DAY` | requests |
81
+ | `maxInputTokensPerDay` | `PI_TYPESAFE_MAX_INPUT_TOKENS_PER_DAY` | input tokens |
82
+ | `maxUsdPerDay` | `PI_TYPESAFE_MAX_USD_PER_DAY` | estimated spend |
83
+
84
+ The environment may lower an explicit cap but never raise it. A reached cap raises a `budget` error naming the cap, the amount used, and the day.
85
+
86
+ Need more than one request? `evaluateAll(request)` asks any number of questions about one state (over 32 are chunked and fanned out), and `evaluateMany(requests)` runs several requests at once. Both preserve order, bound concurrency, never throw, and stop submitting once the budget is gone. [`pi-typesafe/calibrate`](docs/api.md#calibration-pi-typesafecalibrate) turns labelled cases into thresholds with AUC, a sweep, and a replay runner.
87
+
88
+ ## Commands
89
+
90
+ | Command | Effect |
91
+ | --- | --- |
92
+ | `/typesafe login` | Enter and verify an API key (hidden input), then store it |
93
+ | `/typesafe logout` | Delete the stored key and disable the tool |
94
+ | `/typesafe setup` | Check which key is in use; starts login if none |
95
+ | `/typesafe status` | Opt-in state, key state, session and today's counters, cost estimate, any reached cap |
96
+ | `/typesafe enable` | Confirm the data notice and allow agent tool calls this session |
97
+ | `/typesafe disable` | Stop future agent tool calls |
98
+ | `/typesafe test` | Send one built-in sample request |
99
+ | `/typesafe playground` | Edit request JSON in Pi's editor, confirm, view results |
100
+
101
+ Playground and test results are shown in the terminal only; they do not enter the model's context.
102
+
103
+ ## Questions people ask
104
+
105
+ **Do I need a key to install?**
106
+ Install works without one; nothing is sent until you log in and enable the tool. Jev is new and access may be limited at the moment. Keys come from [console.typesafe.ai](https://console.typesafe.ai).
107
+
108
+ **What does a request cost?**
109
+ Whatever TypeSafe bills for the input tokens of your state and questions; output is free. At the listed rate ($42 per billion input tokens at the time of writing) a few hundred tokens cost well under a hundredth of a cent. The per-session cap is 20 attempts, and `maxUsdPerDay` stops a long run at a number you choose.
110
+
111
+ **Why not just ask the main model?**
112
+ The main model can answer any of these questions in prose. It is slower, costs more per call, and grades its own work. Jev returns a calibrated number your code or the agent can branch on, in a quarter of a second, from a separate model. That matters most when the same question is asked many times: every tool call, every file, every issue in a list.
113
+
114
+ **What is sent?**
115
+ Only the state and questions you (or the agent, once enabled) submit, to `https://api.typesafe.ai` only. No files, conversation history, or telemetry. Error messages never include upstream response bodies, headers, keys, or your submitted state.
116
+
117
+ **Limits?**
118
+ Per request: 32 questions and 64 KiB of JSON. Per session: 20 attempts, 15-second timeout, no automatic retries. Per day: no cap unless you set one. Session limits reset when a session starts or reloads; daily counters live in `~/.pi/agent/pi-typesafe/usage.json` and roll over at local midnight. The SDK's `TYPESAFE_BASE_URL` and `TYPESAFE_LOG_LEVEL` overrides are ignored.
119
+
120
+ ## For extension authors
121
+
122
+ Import the library from your own extension. It has no dependency on Pi and is safe in tests.
123
+
124
+ ```ts
125
+ import { ask, createTypeSafe, choice, noul, score } from "pi-typesafe";
126
+
127
+ const typesafe = createTypeSafe({ maxRequests: 5, maxUsdPerDay: 1 }); // key: TYPESAFE_API_KEY, else the login store
128
+ const answer = await ask(typesafe, {
129
+ state: { title: "Login fails after update", body: "..." },
130
+ questions: {
131
+ area: choice("Which area does this report concern?", { auth: "Sign-in", ui: "Layout", other: null }),
132
+ duplicate: noul("Does the report describe the same defect as `known_issue`?"),
133
+ severity: score("How severe is the defect?", ["Cosmetic", "Workaround exists", "Blocking"]),
134
+ },
135
+ }, { timeoutMs: 5_000 });
136
+ if (!answer.ok) return { skipped: answer.errorCode === "budget" }; // never throws
137
+ ```
138
+
139
+ Your extension owns its own user consent and budget; `/typesafe enable` applies only to this package's tool. Check `authState()` rather than your own consent flag before you report that judgments are on. Every export — the client, `ask`, batching, the usage ledger, auth state, and the `pi-typesafe/calibrate` and `pi-typesafe/ui` entry points — is in [docs/api.md](docs/api.md).
140
+
141
+ ## Development
142
+
143
+ ```bash
144
+ npm install
145
+ npm run check # typecheck, offline tests, build
146
+ cp .env.example .env # add your key locally; .env is git-ignored
147
+ npm run test:live # one billable sample request
148
+ npm run dev:pi # start Pi with only this working tree as extension (.env optional)
149
+ ```
150
+
151
+ ## License
152
+
153
+ MIT
@@ -0,0 +1,32 @@
1
+ import type { Questions, SystemOneRequest } from "@typesafe-ai/sdk";
2
+ import type { Evaluation, TypeSafe } from "./client.js";
3
+ import type { IntegrationErrorCode } from "./errors.js";
4
+ /** Anything with pi-typesafe's `evaluate`: the real client in a session, a stub in tests. */
5
+ export type Judge = Pick<TypeSafe, "evaluate">;
6
+ /** Default per-ask deadline; matches the client's own request timeout. */
7
+ export declare const DEFAULT_ASK_TIMEOUT_MS = 15000;
8
+ export interface AskOptions {
9
+ /** Per-ask deadline, merged with the caller's own signal. Default: DEFAULT_ASK_TIMEOUT_MS. */
10
+ timeoutMs?: number;
11
+ signal?: AbortSignal;
12
+ }
13
+ export type AskAnswer<Q extends Questions> = {
14
+ readonly ok: true;
15
+ readonly answers: Evaluation<Q>["answers"];
16
+ readonly model: string;
17
+ readonly usage: Evaluation<Q>["usage"];
18
+ readonly elapsedMs: number;
19
+ } | {
20
+ readonly ok: false;
21
+ readonly error: string;
22
+ readonly errorCode?: IntegrationErrorCode;
23
+ };
24
+ /**
25
+ * One typed Jev request that never throws: a failure comes back as `{ ok: false }` with pi-typesafe's own message
26
+ * (which carries no upstream body, header, key, or submitted state) and its code, so a caller can stop asking after a
27
+ * `budget` error. The per-ask timeout is merged into the caller's signal, so either can cancel the request.
28
+ *
29
+ * This is the author-facing "ask Jev" seam. Agents get the same admission through the `typesafe_evaluate` tool; the two
30
+ * share `prepareEvaluationRequest`, so what one accepts the other accepts.
31
+ */
32
+ export declare function ask<Q extends Questions>(judge: Judge, request: SystemOneRequest<Q>, options?: AskOptions): Promise<AskAnswer<Q>>;
@@ -0,0 +1,25 @@
1
+ import { TypeSafeIntegrationError } from "./errors.js";
2
+ /** Default per-ask deadline; matches the client's own request timeout. */
3
+ export const DEFAULT_ASK_TIMEOUT_MS = 15_000;
4
+ const FALLBACK_MESSAGE = "TypeSafe request failed.";
5
+ /**
6
+ * One typed Jev request that never throws: a failure comes back as `{ ok: false }` with pi-typesafe's own message
7
+ * (which carries no upstream body, header, key, or submitted state) and its code, so a caller can stop asking after a
8
+ * `budget` error. The per-ask timeout is merged into the caller's signal, so either can cancel the request.
9
+ *
10
+ * This is the author-facing "ask Jev" seam. Agents get the same admission through the `typesafe_evaluate` tool; the two
11
+ * share `prepareEvaluationRequest`, so what one accepts the other accepts.
12
+ */
13
+ export async function ask(judge, request, options = {}) {
14
+ const timeout = AbortSignal.timeout(options.timeoutMs ?? DEFAULT_ASK_TIMEOUT_MS);
15
+ const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
16
+ try {
17
+ const result = await judge.evaluate(request, { signal });
18
+ return { ok: true, answers: result.answers, model: result.model, usage: result.usage, elapsedMs: result.elapsedMs };
19
+ }
20
+ catch (error) {
21
+ if (error instanceof TypeSafeIntegrationError)
22
+ return { ok: false, error: error.message, errorCode: error.code };
23
+ return { ok: false, error: FALLBACK_MESSAGE };
24
+ }
25
+ }
@@ -0,0 +1,56 @@
1
+ import type { KeySource } from "./credentials.js";
2
+ import { TypeSafeIntegrationError } from "./errors.js";
3
+ import type { IntegrationErrorCode } from "./errors.js";
4
+ /** The last request that degraded TypeSafe, with no upstream body, header, key, or submitted state. */
5
+ export interface AuthFailure {
6
+ readonly code: IntegrationErrorCode;
7
+ readonly status?: number;
8
+ readonly message: string;
9
+ readonly at: string;
10
+ }
11
+ /**
12
+ * The whole answer to "is Jev actually available right now": which key is in effect, whether it has been accepted, and
13
+ * the last failure that degraded it. Consumers must consult this instead of treating their own consent flag as proof
14
+ * that judgments will happen — an enabled extension with no key used to look identical to a working one.
15
+ */
16
+ export interface AuthState {
17
+ /** Same kinds as KeySituation: where the key in effect comes from. */
18
+ readonly kind: "environment" | "stored" | "missing" | "unusable";
19
+ readonly source?: KeySource;
20
+ /** Where the key would be read from. */
21
+ readonly path: string;
22
+ /** Why a stored key cannot be used, when that is the case. */
23
+ readonly reason?: string;
24
+ /** Short human label for the key source: `TYPESAFE_API_KEY`, `/typesafe login`, `no key`, `unusable key`. */
25
+ readonly keyName: string;
26
+ /** True when the key in effect was accepted by api.typesafe.ai (login verifies it; a successful request proves it). */
27
+ readonly verified: boolean;
28
+ readonly verifiedAt?: string;
29
+ /** The last failure, cleared by the next successful request. */
30
+ readonly lastFailure?: AuthFailure;
31
+ /** A key is present and the last authentication outcome was not a rejection. False means judgments are skipped. */
32
+ readonly usable: boolean;
33
+ }
34
+ /** The auth-state file: one small, owner-only record that outlives the process that wrote it. */
35
+ export declare function authStatePath(): string;
36
+ /** What the key situation, the last outcome, and the clock add up to. Never throws. */
37
+ export declare function authState(options?: {
38
+ path?: string;
39
+ }): AuthState;
40
+ /** Record that the key was accepted: login verification, or any successful request. Clears the last failure. */
41
+ export declare function recordAuthVerified(at?: Date): void;
42
+ /** Record the failure that degraded TypeSafe. The verification timestamp is kept so a recovered key stays known. */
43
+ export declare function recordAuthFailure(error: TypeSafeIntegrationError, at?: Date): void;
44
+ /** Forget verification and degradation: used when the key itself changes (login or logout). */
45
+ export declare function clearAuthState(): void;
46
+ export interface AuthReport {
47
+ /** `error` when judgments are skipped or were rejected, `warning` when the key is unverified, otherwise `ok`. */
48
+ readonly level: "ok" | "warning" | "error";
49
+ /** One line naming the key source and, when degraded, the reason. Safe to display. */
50
+ readonly text: string;
51
+ }
52
+ /**
53
+ * One line plus a level, so a status command, a headless log, or a consumer's own status line can call out a degraded
54
+ * state instead of reporting "enabled".
55
+ */
56
+ export declare function describeAuth(state?: AuthState): AuthReport;