@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.
- package/README.md +8 -4
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +4 -4
- package/node_modules/@herbertgao/resume-from/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +6 -1
- package/node_modules/@narumitw/pi-btw/dist/index.ts +209 -23
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
- package/node_modules/@narumitw/pi-btw/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +1 -12
- package/node_modules/@narumitw/pi-btw/src/menu.ts +244 -15
- package/node_modules/@narumitw/pi-btw/src/settings.ts +5 -0
- package/node_modules/grok-mermaid/CHANGELOG.md +46 -0
- package/node_modules/grok-mermaid/LICENSE +205 -0
- package/node_modules/grok-mermaid/README.md +191 -0
- package/node_modules/grok-mermaid/dist/ansi.d.ts +16 -0
- package/node_modules/grok-mermaid/dist/ansi.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/ansi.js +23 -0
- package/node_modules/grok-mermaid/dist/ansi.js.map +1 -0
- package/node_modules/grok-mermaid/dist/canvas.d.ts +87 -0
- package/node_modules/grok-mermaid/dist/canvas.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/canvas.js +366 -0
- package/node_modules/grok-mermaid/dist/canvas.js.map +1 -0
- package/node_modules/grok-mermaid/dist/graph.d.ts +74 -0
- package/node_modules/grok-mermaid/dist/graph.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/graph.js +91 -0
- package/node_modules/grok-mermaid/dist/graph.js.map +1 -0
- package/node_modules/grok-mermaid/dist/index.d.ts +32 -0
- package/node_modules/grok-mermaid/dist/index.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/index.js +100 -0
- package/node_modules/grok-mermaid/dist/index.js.map +1 -0
- package/node_modules/grok-mermaid/dist/labels.d.ts +62 -0
- package/node_modules/grok-mermaid/dist/labels.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/labels.js +324 -0
- package/node_modules/grok-mermaid/dist/labels.js.map +1 -0
- package/node_modules/grok-mermaid/dist/layout-seq.d.ts +12 -0
- package/node_modules/grok-mermaid/dist/layout-seq.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/layout-seq.js +194 -0
- package/node_modules/grok-mermaid/dist/layout-seq.js.map +1 -0
- package/node_modules/grok-mermaid/dist/layout.d.ts +87 -0
- package/node_modules/grok-mermaid/dist/layout.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/layout.js +881 -0
- package/node_modules/grok-mermaid/dist/layout.js.map +1 -0
- package/node_modules/grok-mermaid/dist/parse.d.ts +83 -0
- package/node_modules/grok-mermaid/dist/parse.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/parse.js +1151 -0
- package/node_modules/grok-mermaid/dist/parse.js.map +1 -0
- package/node_modules/grok-mermaid/dist/source-box.d.ts +18 -0
- package/node_modules/grok-mermaid/dist/source-box.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/source-box.js +78 -0
- package/node_modules/grok-mermaid/dist/source-box.js.map +1 -0
- package/node_modules/grok-mermaid/dist/types.d.ts +42 -0
- package/node_modules/grok-mermaid/dist/types.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/types.js +1 -0
- package/node_modules/grok-mermaid/dist/types.js.map +1 -0
- package/node_modules/grok-mermaid/dist/width-data.d.ts +2 -0
- package/node_modules/grok-mermaid/dist/width-data.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/width-data.js +994 -0
- package/node_modules/grok-mermaid/dist/width-data.js.map +1 -0
- package/node_modules/grok-mermaid/dist/width.d.ts +18 -0
- package/node_modules/grok-mermaid/dist/width.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/width.js +76 -0
- package/node_modules/grok-mermaid/dist/width.js.map +1 -0
- package/node_modules/grok-mermaid/package.json +49 -0
- package/node_modules/grok-mermaid/src/ansi.ts +34 -0
- package/node_modules/grok-mermaid/src/canvas.ts +373 -0
- package/node_modules/grok-mermaid/src/graph.ts +142 -0
- package/node_modules/grok-mermaid/src/index.ts +104 -0
- package/node_modules/grok-mermaid/src/labels.ts +326 -0
- package/node_modules/grok-mermaid/src/layout-seq.ts +203 -0
- package/node_modules/grok-mermaid/src/layout.ts +1015 -0
- package/node_modules/grok-mermaid/src/parse.ts +1189 -0
- package/node_modules/grok-mermaid/src/source-box.ts +89 -0
- package/node_modules/grok-mermaid/src/types.ts +43 -0
- package/node_modules/grok-mermaid/src/width-data.ts +993 -0
- package/node_modules/grok-mermaid/src/width.ts +74 -0
- package/node_modules/pi-jev-auto-mode/CHANGELOG.md +110 -0
- package/node_modules/pi-jev-auto-mode/LICENSE +21 -0
- package/node_modules/pi-jev-auto-mode/README.md +285 -0
- package/node_modules/pi-jev-auto-mode/SECURITY.md +32 -0
- package/node_modules/pi-jev-auto-mode/docs/calibration.md +154 -0
- package/node_modules/pi-jev-auto-mode/docs/design.md +164 -0
- package/node_modules/pi-jev-auto-mode/docs/security.md +124 -0
- package/node_modules/pi-jev-auto-mode/index.ts +1 -0
- package/node_modules/pi-jev-auto-mode/package.json +72 -0
- package/node_modules/pi-jev-auto-mode/src/call.ts +180 -0
- package/node_modules/pi-jev-auto-mode/src/decide.ts +86 -0
- package/node_modules/pi-jev-auto-mode/src/extension.ts +862 -0
- package/node_modules/pi-jev-auto-mode/src/intent.ts +71 -0
- package/node_modules/pi-jev-auto-mode/src/jev/availability.ts +53 -0
- package/node_modules/pi-jev-auto-mode/src/jev/criteria.ts +19 -0
- package/node_modules/pi-jev-auto-mode/src/jev/decide.ts +187 -0
- package/node_modules/pi-jev-auto-mode/src/jev/engine.ts +165 -0
- package/node_modules/pi-jev-auto-mode/src/jev/index.ts +20 -0
- package/node_modules/pi-jev-auto-mode/src/jev/questions.ts +273 -0
- package/node_modules/pi-jev-auto-mode/src/jev/response.ts +64 -0
- package/node_modules/pi-jev-auto-mode/src/jev/state.ts +20 -0
- package/node_modules/pi-jev-auto-mode/src/jev/transport.ts +117 -0
- package/node_modules/pi-jev-auto-mode/src/jev/types.ts +46 -0
- package/node_modules/pi-jev-auto-mode/src/policy.ts +558 -0
- package/node_modules/pi-jev-auto-mode/src/records.ts +118 -0
- package/node_modules/pi-jev-auto-mode/src/settings.ts +327 -0
- package/node_modules/pi-jev-auto-mode/src/ui.ts +233 -0
- package/node_modules/pi-lens/CHANGELOG.md +45 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +21 -9
- package/node_modules/pi-lens/dist/clients/cascade-format.js +24 -0
- package/node_modules/pi-lens/dist/clients/extension-log.js +48 -1
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +11 -12
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +3 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +41 -7
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +9 -2
- package/node_modules/pi-lens/dist/clients/path-utils.js +51 -9
- package/node_modules/pi-lens/dist/clients/persistent-reverify.js +271 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +15 -1
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +124 -7
- package/node_modules/pi-lens/dist/clients/skills-resolver.js +38 -2
- package/node_modules/pi-lens/dist/clients/widget-state.js +152 -6
- package/node_modules/pi-lens/dist/index.js +729 -344
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +14 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +84 -17
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +32 -5
- package/node_modules/pi-lens/package.json +1 -1
- package/node_modules/pi-typesafe/LICENSE +21 -0
- package/node_modules/pi-typesafe/README.md +153 -0
- package/node_modules/pi-typesafe/dist/ask.d.ts +32 -0
- package/node_modules/pi-typesafe/dist/ask.js +25 -0
- package/node_modules/pi-typesafe/dist/auth.d.ts +56 -0
- package/node_modules/pi-typesafe/dist/auth.js +114 -0
- package/node_modules/pi-typesafe/dist/batch.d.ts +74 -0
- package/node_modules/pi-typesafe/dist/batch.js +116 -0
- package/node_modules/pi-typesafe/dist/calibrate.d.ts +106 -0
- package/node_modules/pi-typesafe/dist/calibrate.js +157 -0
- package/node_modules/pi-typesafe/dist/client.d.ts +69 -0
- package/node_modules/pi-typesafe/dist/client.js +188 -0
- package/node_modules/pi-typesafe/dist/credentials.d.ts +46 -0
- package/node_modules/pi-typesafe/dist/credentials.js +112 -0
- package/node_modules/pi-typesafe/dist/errors.d.ts +9 -0
- package/node_modules/pi-typesafe/dist/errors.js +31 -0
- package/node_modules/pi-typesafe/dist/extension.d.ts +3 -0
- package/node_modules/pi-typesafe/dist/extension.js +222 -0
- package/node_modules/pi-typesafe/dist/index.d.ts +18 -0
- package/node_modules/pi-typesafe/dist/index.js +9 -0
- package/node_modules/pi-typesafe/dist/key-prompt.d.ts +3 -0
- package/node_modules/pi-typesafe/dist/key-prompt.js +43 -0
- package/node_modules/pi-typesafe/dist/login.d.ts +27 -0
- package/node_modules/pi-typesafe/dist/login.js +38 -0
- package/node_modules/pi-typesafe/dist/schema.d.ts +43 -0
- package/node_modules/pi-typesafe/dist/schema.js +152 -0
- package/node_modules/pi-typesafe/dist/ui.d.ts +3 -0
- package/node_modules/pi-typesafe/dist/ui.js +3 -0
- package/node_modules/pi-typesafe/dist/usage.d.ts +73 -0
- package/node_modules/pi-typesafe/dist/usage.js +182 -0
- package/node_modules/pi-typesafe/examples/decision-extension.ts +41 -0
- package/node_modules/pi-typesafe/extensions/index.js +2 -0
- package/node_modules/pi-typesafe/package.json +89 -0
- package/package.json +16 -8
|
@@ -251,7 +251,7 @@ function lineInModifiedRanges(line, ranges) {
|
|
|
251
251
|
return true;
|
|
252
252
|
return ranges.some((range) => line >= range.start - 2 && line <= range.end + 2);
|
|
253
253
|
}
|
|
254
|
-
function recordFromLspDiagnostic(diag, filePath, cwd) {
|
|
254
|
+
export function recordFromLspDiagnostic(diag, filePath, cwd) {
|
|
255
255
|
const line = diag.range.start.line + 1;
|
|
256
256
|
const column = diag.range.start.character + 1;
|
|
257
257
|
// Keep agreement keyed to the producer that supplied the diagnostic. The
|
|
@@ -736,7 +736,7 @@ export async function buildActionableWarningsReport(args) {
|
|
|
736
736
|
* off-hook loop run byte-identical logic — the deferral must not become a
|
|
737
737
|
* second, drifting copy of the enrichment.
|
|
738
738
|
*/
|
|
739
|
-
async function enrichFileFromLsp(cwd, args, target, deps) {
|
|
739
|
+
export async function enrichFileFromLsp(cwd, args, target, deps) {
|
|
740
740
|
const { lspService } = deps;
|
|
741
741
|
const { filePath } = target;
|
|
742
742
|
const out = [];
|
|
@@ -1110,12 +1110,24 @@ export function mergeActionableWarningsReports(args) {
|
|
|
1110
1110
|
}
|
|
1111
1111
|
mergedFiles++;
|
|
1112
1112
|
const merged = incumbent
|
|
1113
|
-
?
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1113
|
+
? incumbent.reVerified || incumbent.reVerifyIncomplete
|
|
1114
|
+
? // #3170: the runtime just RE-OBSERVED this file (the bounded
|
|
1115
|
+
// persistent-reverify pass) — the fresh observation supersedes
|
|
1116
|
+
// the carried entry instead of unioning with it: a converged
|
|
1117
|
+
// finding would otherwise survive its own supersession via the
|
|
1118
|
+
// id-union in `mergeWarnings`.
|
|
1119
|
+
{
|
|
1120
|
+
...incumbent,
|
|
1121
|
+
fileSeq: incumbent.fileSeq ?? entry.fileSeq,
|
|
1122
|
+
generatedAt: olderStamp(incumbent.generatedAt ?? newerReport?.generatedAt, entry.generatedAt ?? olderReport?.generatedAt),
|
|
1123
|
+
warnings: incumbent.warnings,
|
|
1124
|
+
}
|
|
1125
|
+
: {
|
|
1126
|
+
...incumbent,
|
|
1127
|
+
fileSeq: incumbent.fileSeq ?? entry.fileSeq,
|
|
1128
|
+
generatedAt: olderStamp(incumbent.generatedAt ?? newerReport?.generatedAt, entry.generatedAt ?? olderReport?.generatedAt),
|
|
1129
|
+
warnings: mergeWarnings([...incumbent.warnings, ...entry.warnings]),
|
|
1130
|
+
}
|
|
1119
1131
|
: { ...entry };
|
|
1120
1132
|
if (deferredPublish) {
|
|
1121
1133
|
// Mark it, so the next in-band publish carries it forward across the
|
|
@@ -1541,7 +1553,7 @@ export function formatActionableWarningsAdvisory(report, cwd, host = "pi") {
|
|
|
1541
1553
|
const files = report.files.filter((file) => file.warnings.some((warning) => !warning.suppressed));
|
|
1542
1554
|
const fileList = files
|
|
1543
1555
|
.slice(0, 5)
|
|
1544
|
-
.map((file) => ` ${file.displayPath}: ${file.warnings.filter((warning) => !warning.suppressed).length}`)
|
|
1556
|
+
.map((file) => ` ${file.displayPath}: ${file.warnings.filter((warning) => !warning.suppressed).length}${file.reVerifyIncomplete ? " (re-verify incomplete)" : ""}`)
|
|
1545
1557
|
.join("\n");
|
|
1546
1558
|
const more = files.length > 5 ? `\n ... and ${files.length - 5} more file(s)` : "";
|
|
1547
1559
|
const safe = report.summary.autoFixEligible > 0
|
|
@@ -3,6 +3,7 @@ import { retagAuxiliaryDiagnostics } from "./dispatch/auxiliary-lsp.js";
|
|
|
3
3
|
import { applyFindingPolicy, loadProjectRulePolicyMap, renderedRuleIdentities, } from "./dispatch/finding-policy.js";
|
|
4
4
|
import { detectFileRole } from "./file-role.js";
|
|
5
5
|
import { logLatency } from "./latency-logger.js";
|
|
6
|
+
import { formatCacheAgeLabel } from "./finding-delivery-gate.js";
|
|
6
7
|
import { convertLspDiagnostics } from "./dispatch/utils/lsp-diagnostics.js";
|
|
7
8
|
import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
|
|
8
9
|
export function formatCascadeNeighborDiagnostics(cwd, neighbors, options = {}) {
|
|
@@ -140,6 +141,12 @@ export function buildResolvedFoundCascadeRun(cwd, neighbor) {
|
|
|
140
141
|
},
|
|
141
142
|
neighborCount: 1,
|
|
142
143
|
diagnosticCount: diagnostics.length,
|
|
144
|
+
// #3168 F3: the #1444 publish stamp — the run's own observation time, so
|
|
145
|
+
// the carried-render age label states the real age instead of claiming
|
|
146
|
+
// no stamp exists.
|
|
147
|
+
...(neighbor.publishedAt !== undefined
|
|
148
|
+
? { observedAt: neighbor.publishedAt }
|
|
149
|
+
: {}),
|
|
143
150
|
};
|
|
144
151
|
}
|
|
145
152
|
/**
|
|
@@ -289,3 +296,20 @@ function readNeighborContent(filePath) {
|
|
|
289
296
|
return undefined;
|
|
290
297
|
}
|
|
291
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Fix B (#3167/#3168): the carry label for a cascade run re-rendered at a
|
|
301
|
+
* later turn_end. The carry is bounded to ONE turn (`RuntimeCoordinator.beginTurn`
|
|
302
|
+
* drops anything that would reach 2), so the honest label names the carry
|
|
303
|
+
* count. #3168 F3 corrected this docstring's earlier claim that no stamp
|
|
304
|
+
* exists: the run DOES carry one (`observedAt`, threaded from #1444's
|
|
305
|
+
* `publishedAt` through the resolved-found plumb), so the age half renders
|
|
306
|
+
* from it via `formatCacheAgeLabel`. The deferred-compute re-park arm has no
|
|
307
|
+
* stamp and correctly falls to the neutral wording. Returns `undefined` for
|
|
308
|
+
* non-carried runs: no label noise on fresh observations.
|
|
309
|
+
*/
|
|
310
|
+
export function cascadeCarrySuffix(carriedTurns, observedAt) {
|
|
311
|
+
if (carriedTurns === undefined || carriedTurns < 1)
|
|
312
|
+
return undefined;
|
|
313
|
+
const noun = carriedTurns === 1 ? "turn" : "turns";
|
|
314
|
+
return `(carried ${carriedTurns} ${noun} · ${formatCacheAgeLabel(observedAt)})`;
|
|
315
|
+
}
|
|
@@ -211,6 +211,48 @@ function inCaptureWindow(fn) {
|
|
|
211
211
|
function isCaptureSeam(prop) {
|
|
212
212
|
return (prop === "on" || (typeof prop === "string" && prop.startsWith("register")));
|
|
213
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* True for a host SCHEMA value that happens to be callable.
|
|
216
|
+
*
|
|
217
|
+
* A tool definition's `parameters` is DATA the host reads, not a pi-lens
|
|
218
|
+
* callback — but the schema builder pi-lens imports is host-provided
|
|
219
|
+
* (`clients/deps/typebox.ts`: the host resolves the bare `typebox`
|
|
220
|
+
* specifier), and on a host whose builders are ArkType-style the schema is a
|
|
221
|
+
* FUNCTION. `@oh-my-pi/pi-coding-agent` rewrites an extension's `typebox`
|
|
222
|
+
* import to its own `src/extensibility/legacy-typebox.ts`, which re-exports
|
|
223
|
+
* `@oh-my-pi/omptype`'s builders; those return a callable whose prototype
|
|
224
|
+
* carries `toJsonSchema`/`assert`. `inCaptureWindow` returns a plain rest
|
|
225
|
+
* wrapper with neither, so the host falls off its ark branch, the wire schema
|
|
226
|
+
* degrades to `undefined`, and every xd:// device renders `type Args =
|
|
227
|
+
* unknown` and rejects every call with `root: schema must be an object or
|
|
228
|
+
* boolean` (#3195).
|
|
229
|
+
*
|
|
230
|
+
* The predicate IS the host's own, copied from `@oh-my-pi/pi-ai@18.2.4`
|
|
231
|
+
* `src/utils/schema/wire.ts:21-27` (`isArkSchema`) — the same test the host's
|
|
232
|
+
* `applyToolProxy` (`@oh-my-pi/pi-coding-agent@18.2.4`
|
|
233
|
+
* `src/extensibility/tool-proxy.ts:26`) uses to leave a callable schema
|
|
234
|
+
* unbound for exactly this hazard ("`bind()` … drops the schema surface …
|
|
235
|
+
* so a bound schema later stringifies to `undefined`"). Matching it means
|
|
236
|
+
* pi-lens declines to wrap precisely the values the host reads as schemas,
|
|
237
|
+
* and still wraps everything else.
|
|
238
|
+
* `tests/clients/pi-host-callable-schema-contract.test.ts` asserts against
|
|
239
|
+
* that upstream predicate (and against a fixture measured from a real
|
|
240
|
+
* `@oh-my-pi/omptype@18.2.4` schema), not against this copy of it.
|
|
241
|
+
*
|
|
242
|
+
* Deliberately NOT an allow-list of handler keys (`execute`, `renderResult`,
|
|
243
|
+
* `handler` — the measured set today): that is `isCaptureSeam`'s #1434 S1a
|
|
244
|
+
* mistake one level down (defect shape 34, spelling enumerator). The host's
|
|
245
|
+
* own `ToolDefinition` already declares `onSession`, `renderCall` and
|
|
246
|
+
* `shellEnv`, and `registerCommand` takes `getArgumentCompletions`; the day
|
|
247
|
+
* pi-lens adopts one of those, an allow-list drops its console capture
|
|
248
|
+
* SILENTLY — the #1333 regression this seam exists to prevent — while this
|
|
249
|
+
* predicate keeps wrapping it.
|
|
250
|
+
*/
|
|
251
|
+
function isCallableSchema(value) {
|
|
252
|
+
return (typeof value === "function" &&
|
|
253
|
+
typeof value.toJsonSchema === "function" &&
|
|
254
|
+
typeof value.assert === "function");
|
|
255
|
+
}
|
|
214
256
|
/**
|
|
215
257
|
* Wrap every function value found in `value`, in place. Handles the two
|
|
216
258
|
* shapes a `register*`/`on` call takes today: a function passed directly
|
|
@@ -218,6 +260,11 @@ function isCaptureSeam(prop) {
|
|
|
218
260
|
* function ONE level inside a plain options/definition object
|
|
219
261
|
* (`options.handler`, `tool.execute`).
|
|
220
262
|
*
|
|
263
|
+
* Only the property walk screens for a callable schema (#3195): every
|
|
264
|
+
* function pi-lens passes as a register argument DIRECTLY is a handler
|
|
265
|
+
* (`on`, `registerMessageRenderer`), measured across the whole activation,
|
|
266
|
+
* so a schema can only ever arrive as a property of a definition object.
|
|
267
|
+
*
|
|
221
268
|
* Deliberately NOT recursive past that one level (#1434 perf review): a tool
|
|
222
269
|
* definition's `parameters`/`schema` is a large, deeply nested TypeBox object
|
|
223
270
|
* that holds no functions, but walking its full tree on every `registerTool`
|
|
@@ -249,7 +296,7 @@ function wrapFunctionsInPlace(value) {
|
|
|
249
296
|
const obj = value;
|
|
250
297
|
for (const key of Object.keys(obj)) {
|
|
251
298
|
const propValue = obj[key];
|
|
252
|
-
if (typeof propValue === "function") {
|
|
299
|
+
if (typeof propValue === "function" && !isCallableSchema(propValue)) {
|
|
253
300
|
assignWrapped(obj, key, propValue);
|
|
254
301
|
}
|
|
255
302
|
}
|
|
@@ -284,20 +284,19 @@ export const DELIVERY_SURFACES = {
|
|
|
284
284
|
"runtime-turn:late-runner-findings": gated(RUNTIME_TURN_FILE, "Turn-end CLI runner findings collected after the post-write path.", ["gateFindingsByPathFreshness"], ['store: "late-runner-findings"'], { evidenceMin: 2 }),
|
|
285
285
|
"runtime-turn:cascade-blocker": labeled(RUNTIME_TURN_FILE, "Turn-end 🧪 cascade neighbor blocker.", "Cascade results settle synchronously this turn where possible " +
|
|
286
286
|
"(`settleCascadeRuns`), but an unsettled compute can carry over to a " +
|
|
287
|
-
"later turn (bounded by a carry cap) —
|
|
288
|
-
"
|
|
289
|
-
|
|
290
|
-
partialReason: "A carried-over cascade result (run.carriedTurns > 0) is rendered " +
|
|
291
|
-
"without an age label. Follow-up: surface carriedTurns as an explicit " +
|
|
292
|
-
"label when > 0, or route through formatCacheAgeLabel using the run's " +
|
|
293
|
-
"own timestamp.",
|
|
294
|
-
}),
|
|
287
|
+
"later turn (bounded by a carry cap) — a carried-over result renders " +
|
|
288
|
+
"with an explicit `(carried N turns · scanned Xm ago)` label (#3167, " +
|
|
289
|
+
"#3168 F3), so the agent can tell it from a fresh observation.", "live", ["cascadeCarrySuffix("], { evidenceMin: 1 }),
|
|
295
290
|
"runtime-turn:cascade-coverage-advisory": labeled(RUNTIME_TURN_FILE, "Turn-end cascade-coverage-gap advisories (graph/binding/budget).", "Explains what the cascade check could NOT confirm this turn — not a " +
|
|
296
291
|
"finding with a cited path, an absence-of-coverage disclosure computed " +
|
|
297
|
-
"from this turn's own indeterminate-run list.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
292
|
+
"from this turn's own indeterminate-run list. An advisory computed from " +
|
|
293
|
+
"a carried indeterminate run is labeled `(carried N turns · <age>)` " +
|
|
294
|
+
"(#3167, #3168 F4 — dropped on mixed carried/fresh buckets). The age " +
|
|
295
|
+
"half reads `scan age unknown` on this surface today: no indeterminate-" +
|
|
296
|
+
"run producer stamps `observedAt` (only the resolved-found plumb does, " +
|
|
297
|
+
"`clients/cascade-format.ts`), and one unstamped carried run collapses " +
|
|
298
|
+
"the whole bucket's age rather than claiming the stamped runs' (#3168 " +
|
|
299
|
+
"F13). The carry COUNT is always real.", "live", ["withCarryLabel("], { evidenceMin: 3 }),
|
|
301
300
|
// #3102: the cold-neighbour cascade run is BUILT here, in the quiet-window
|
|
302
301
|
// reconcile (`onResolvedFound` in index.ts), a turn earlier than the
|
|
303
302
|
// `runtime-turn:cascade-blocker` render that finally carries it — so it is
|
|
@@ -344,6 +344,9 @@ export function registerCascadeTierReconcileTask(getLspService, options = {}) {
|
|
|
344
344
|
filePath: o.filePath,
|
|
345
345
|
serverId: o.serverId,
|
|
346
346
|
diagnostics: o.diagnostics,
|
|
347
|
+
...(o.publishedAt !== undefined
|
|
348
|
+
? { publishedAt: o.publishedAt }
|
|
349
|
+
: {}),
|
|
347
350
|
});
|
|
348
351
|
}
|
|
349
352
|
else if (o.outcome === "resolved-clean" && o.publishedAt != null) {
|
|
@@ -3955,6 +3955,12 @@ export class LSPService {
|
|
|
3955
3955
|
// (undefined = the race didn't produce an answer; the end-of-wait
|
|
3956
3956
|
// fallback below may still fill it in on a timed-out empty result).
|
|
3957
3957
|
let tsserverSyncEligible = false;
|
|
3958
|
+
// #1639/#3187: `ensureWarmForSweep`'s readiness probe (`source:
|
|
3959
|
+
// "lsp_sweep_warmup"`) is the one touch that is single-server,
|
|
3960
|
+
// `clientScope: "primary"` and NON-collecting at the same time. Both
|
|
3961
|
+
// sync-confirm gates below and the per-server wait's settle-source tag
|
|
3962
|
+
// read the same fact, so it is derived once, here.
|
|
3963
|
+
const isWarmupTouch = source === "lsp_sweep_warmup";
|
|
3958
3964
|
let tsserverSyncConfirmed;
|
|
3959
3965
|
let diagnosticsUnsupportedServerIds = [];
|
|
3960
3966
|
if (diagnosticsMode !== "none") {
|
|
@@ -4128,8 +4134,19 @@ export class LSPService {
|
|
|
4128
4134
|
// synchronous gate and pays ZERO extra work — not even the snapshot
|
|
4129
4135
|
// read; a non-typescript `silentOnClean` server instead gets the
|
|
4130
4136
|
// generic (non-racing) clean-confirm fallback further below.
|
|
4137
|
+
// #3187: the warm-up touch is eligible too, though it collects
|
|
4138
|
+
// nothing. `collectDiagnostics` is here because a touch that discards
|
|
4139
|
+
// its diagnostics has nothing to carry a confirm INTO — but the warm-up
|
|
4140
|
+
// is not after diagnostics at all, it is after a VERDICT, and the sync
|
|
4141
|
+
// commands are the only thing that can hand a tier3-silent server one
|
|
4142
|
+
// before its whole cold-start budget lapses (measured: 6,972 ms of a
|
|
4143
|
+
// 20,000 ms warm-up budget, then the sweep touched the same file again
|
|
4144
|
+
// in 113 ms). It stays non-collecting on purpose: a collecting warm-up
|
|
4145
|
+
// would write this file's `lastKnownDiagnostics` from primary-scope-only
|
|
4146
|
+
// evidence — and an empty confirm would DELETE a previously confirmed
|
|
4147
|
+
// record — which is the #1470/#1493 prime hazard from a new direction.
|
|
4131
4148
|
if (!notifyWriteTimedOut &&
|
|
4132
|
-
options.collectDiagnostics === true &&
|
|
4149
|
+
(options.collectDiagnostics === true || isWarmupTouch) &&
|
|
4133
4150
|
clientScope === "primary" &&
|
|
4134
4151
|
spawned.length === 1 &&
|
|
4135
4152
|
spawned[0].client.serverId === "typescript" &&
|
|
@@ -4279,8 +4296,8 @@ export class LSPService {
|
|
|
4279
4296
|
// consumer can tell them apart instead of double-counting. Omitted
|
|
4280
4297
|
// (rather than passed as "pull") on the common path — the client
|
|
4281
4298
|
// already defaults to "pull", and existing tests assert the exact
|
|
4282
|
-
// argument list `waitForDiagnostics` is called with.
|
|
4283
|
-
|
|
4299
|
+
// argument list `waitForDiagnostics` is called with. #3187: derived
|
|
4300
|
+
// once above, next to the sync-confirm gate that reads the same fact.
|
|
4284
4301
|
// #743: per-server — a server we DID push to still gets the
|
|
4285
4302
|
// version-baseline wait even when a sibling was debounced away.
|
|
4286
4303
|
const waitForDiagnostics = () => !notifySkippedServerIds.has(entry.info.id) &&
|
|
@@ -5127,9 +5144,9 @@ export class LSPService {
|
|
|
5127
5144
|
// ever double-deciding typescript's own touches — when the sync race
|
|
5128
5145
|
// was attempted and failed/was unavailable, typescript's existing
|
|
5129
5146
|
// "falls through to inconclusive, unchanged" contract (#707) is
|
|
5130
|
-
// preserved exactly
|
|
5131
|
-
//
|
|
5132
|
-
//
|
|
5147
|
+
// preserved exactly for a COLLECTING touch; #3187 re-admits the
|
|
5148
|
+
// non-collecting warm-up, which has no end-of-wait retry to fall
|
|
5149
|
+
// through to (see the exclusion's own comment below). Scoped
|
|
5133
5150
|
// to `clientScope === "primary"`/`spawned.length === 1` exactly like
|
|
5134
5151
|
// the sync-eligible gate above (and like `ensureWarmForSweep`'s own
|
|
5135
5152
|
// `clientScope: "primary"` warm-up touch) so a multi-server
|
|
@@ -5149,7 +5166,17 @@ export class LSPService {
|
|
|
5149
5166
|
// below means the one server IS the primary, so this is the same condition
|
|
5150
5167
|
// written in the vocabulary the rest of the merge now uses.
|
|
5151
5168
|
!primaryNotifyWriteTimedOut &&
|
|
5152
|
-
|
|
5169
|
+
// #3187: the warm-up is sync-ELIGIBLE now, and eligibility alone must
|
|
5170
|
+
// not cost it this fallback. For a collecting touch the exclusion is
|
|
5171
|
+
// #707's contract (the end-of-wait retry above is typescript's second
|
|
5172
|
+
// chance, and failing it stays inconclusive); the warm-up collects
|
|
5173
|
+
// nothing, so that retry's `collected !== undefined` precondition can
|
|
5174
|
+
// never hold for it and the exclusion would leave a server whose
|
|
5175
|
+
// `typescript.tsserverRequest` is unavailable stranded inconclusive —
|
|
5176
|
+
// a #744 warm-up failure, its retry, and a SKIPPED sweep group, where
|
|
5177
|
+
// today it is certified clean. Reaching here at all means the race
|
|
5178
|
+
// produced no answer, so this decides nothing the sync already decided.
|
|
5179
|
+
(!tsserverSyncEligible || isWarmupTouch) &&
|
|
5153
5180
|
clientScope === "primary" &&
|
|
5154
5181
|
spawned.length === 1 &&
|
|
5155
5182
|
getStrategy(spawned[0].client.serverId, spawned[0].client.getLaunchVariant?.()).silentOnClean === true) {
|
|
@@ -6824,6 +6851,13 @@ export class LSPService {
|
|
|
6824
6851
|
});
|
|
6825
6852
|
const warmupAttempt = this.touchFile(representativeFile, content, {
|
|
6826
6853
|
diagnostics: "document",
|
|
6854
|
+
// #3187: stays FALSE. This touch wants a verdict, not findings: its
|
|
6855
|
+
// `clientScope: "primary"` evidence does not speak for the auxiliaries
|
|
6856
|
+
// the sweep's own `clientScope: "all"` touch waits on, so collecting
|
|
6857
|
+
// here would prime this file's `lastKnownDiagnostics` from a narrower
|
|
6858
|
+
// scope than the result that follows (and an empty confirm would delete
|
|
6859
|
+
// a previously confirmed record). `touchFile`'s sync-confirm gates key
|
|
6860
|
+
// off `source` instead, so the verdict still arrives at ~grace+RTT.
|
|
6827
6861
|
collectDiagnostics: false,
|
|
6828
6862
|
clientScope: "primary",
|
|
6829
6863
|
source: "lsp_sweep_warmup",
|
|
@@ -273,8 +273,15 @@ export async function analyzeFile(filePath, cwd, options = {}) {
|
|
|
273
273
|
const durationMs = Date.now() - start;
|
|
274
274
|
if (options.record !== false) {
|
|
275
275
|
// Mirror pipeline.ts's recording so pilens_diagnostics (mode=all) and
|
|
276
|
-
// pilens_health see what this analysis found.
|
|
277
|
-
|
|
276
|
+
// pilens_health see what this analysis found. #3160 F1: `absPath` here
|
|
277
|
+
// is derived raw from the agent-supplied `file` arg (path.resolve above)
|
|
278
|
+
// — unlike pipeline.ts, which always records ctx.filePath, already
|
|
279
|
+
// canonicalized to on-disk casing by createDispatchContext (#2016/
|
|
280
|
+
// #3098). Normalize before recording so a mis-cased analyze call lands
|
|
281
|
+
// under the SAME widget-state key a canonical per-edit write already
|
|
282
|
+
// uses, instead of an orphaned key no reader (raw or normalized) can
|
|
283
|
+
// reach.
|
|
284
|
+
recordDiagnostics(normalizeMapKey(absPath), result.diagnostics);
|
|
278
285
|
if (result.diagnostics.length > 0) {
|
|
279
286
|
getDiagnosticTracker().trackShown(result.diagnostics);
|
|
280
287
|
}
|
|
@@ -161,6 +161,7 @@ function adoptCanonicalCasing(held, canonical) {
|
|
|
161
161
|
* (needed for new files where we haven't written yet)
|
|
162
162
|
*
|
|
163
163
|
* On POSIX:
|
|
164
|
+
* - Folds `.`, `..` and duplicate separators first (#3184, see below).
|
|
164
165
|
* - If the file exists: adopts the on-disk CASING of the trailing segments
|
|
165
166
|
* (see `adoptCanonicalCasing`) — no lowercasing, no symlink resolution.
|
|
166
167
|
* - If the file doesn't exist: returns the path as-is, case-PRESERVING. On a
|
|
@@ -169,6 +170,26 @@ function adoptCanonicalCasing(held, canonical) {
|
|
|
169
170
|
* answer for a path that EXISTS can tell the two apart, and for a path that
|
|
170
171
|
* does not exist there is no such answer to ask for.
|
|
171
172
|
*
|
|
173
|
+
* Why the POSIX arm folds dot segments (#3184): the casing arm returns the
|
|
174
|
+
* caller's own spelling whenever `adoptCanonicalCasing` changes nothing, and
|
|
175
|
+
* for `<base>/src/../src/a.ts` it always changes nothing — `realpath` answers
|
|
176
|
+
* a string with FEWER segments, which a casing-only rewrite cannot express, so
|
|
177
|
+
* it declines and the caller's un-folded spelling came back as the map key.
|
|
178
|
+
* Every canonical writer keys through `path.resolve` first (`ctx.filePath` =
|
|
179
|
+
* `normalizeMapKey(resolveAgainstAncestors(...))`, `clients/dispatch/
|
|
180
|
+
* runner-context.ts:49`), so a consumer that passes an ALREADY-absolute
|
|
181
|
+
* agent-typed path straight in (`tools/lens-diagnostic-mark.ts`,
|
|
182
|
+
* `clients/mcp/analyze.ts`) derived an orphan key that no reader could reach.
|
|
183
|
+
* Folding here is pure string algebra — no cwd, no filesystem — so a relative
|
|
184
|
+
* path stays relative (`src/../x` → `x`, `../x` → `../x`, never resolved
|
|
185
|
+
* against `process.cwd()`; refs #2490, where a cwd fold broke every monorepo)
|
|
186
|
+
* and a symlinked package still keys under the path the caller held. These
|
|
187
|
+
* are `path.resolve`'s own TEXTUAL `..` semantics, which is exactly what
|
|
188
|
+
* makes a folded reader key equal to the canonical writer's key: where a
|
|
189
|
+
* `..` sits right after a symlinked directory, textual folding and the
|
|
190
|
+
* kernel disagree, and both sides of every comparison take the textual
|
|
191
|
+
* answer because every canonical writer already resolved that way.
|
|
192
|
+
*
|
|
172
193
|
* Why POSIX canonicalizes casing at all (#3098, the #1024 defect's live half):
|
|
173
194
|
* a case-insensitive POSIX filesystem — macOS's default APFS, `nocase` vfat /
|
|
174
195
|
* ntfs3 / cifs mounts — makes `SUB/a.ts` and `sub/a.ts` ONE file, so a raw
|
|
@@ -194,16 +215,37 @@ export function normalizeFilePath(filePath) {
|
|
|
194
215
|
// Convert backslashes to forward slashes first
|
|
195
216
|
const normalized = filePath.replace(/\\/g, "/");
|
|
196
217
|
if (process.platform !== "win32" && !isWindowsPath(normalized)) {
|
|
218
|
+
// #3184. `path.posix`, not the host default: this branch is already
|
|
219
|
+
// committed to POSIX parsing of a slash-folded string (shape 2). Only
|
|
220
|
+
// the POSIX arm needs this — the win32 arm below reaches `realpath` or
|
|
221
|
+
// `win32.resolve`/`win32.normalize` on every path, all of which fold
|
|
222
|
+
// dot segments already (measured: `C:\repo\src\..\src\a.ts` →
|
|
223
|
+
// `c:/repo/src/a.ts` on this POSIX host, before this change) — and
|
|
224
|
+
// folding BEFORE the arms would also move which arm a degenerate
|
|
225
|
+
// drive-letter path selects (`path.posix.normalize("C:/repo/../..")`
|
|
226
|
+
// is `"."`, no longer Windows-shaped).
|
|
227
|
+
// Two inputs keep the caller's spelling instead:
|
|
228
|
+
// - "" is a non-path sentinel in this codebase's path-typed fields
|
|
229
|
+
// (see `normalizeLoggedPath`'s doc); `posix.normalize("")` invents
|
|
230
|
+
// ".", the process cwd.
|
|
231
|
+
// - a UNC root (`\\server\share`, slash-folded to `//server/share`)
|
|
232
|
+
// reaches THIS arm on a POSIX host, because `isWindowsPath` tests
|
|
233
|
+
// the already-folded string and sees no backslash; POSIX
|
|
234
|
+
// `normalize` collapses its leading `//` to `/`, renaming the path
|
|
235
|
+
// to an unrelated local one.
|
|
236
|
+
const folded = normalized === "" || normalized.startsWith("//")
|
|
237
|
+
? normalized
|
|
238
|
+
: path.posix.normalize(normalized);
|
|
197
239
|
try {
|
|
198
|
-
const canonical = realpathSync.native(
|
|
199
|
-
// Fast path, not a guard: both arms answer `
|
|
240
|
+
const canonical = realpathSync.native(folded);
|
|
241
|
+
// Fast path, not a guard: both arms answer `folded` when the
|
|
200
242
|
// strings match, but skipping the two `split`s there is a measured
|
|
201
243
|
// 1.9 vs 2.3 microseconds per call on the per-edit seam (#3098).
|
|
202
|
-
if (canonical ===
|
|
203
|
-
return
|
|
204
|
-
const adopted = adoptCanonicalCasing(
|
|
205
|
-
if (adopted ===
|
|
206
|
-
return
|
|
244
|
+
if (canonical === folded)
|
|
245
|
+
return folded;
|
|
246
|
+
const adopted = adoptCanonicalCasing(folded, canonical);
|
|
247
|
+
if (adopted === folded)
|
|
248
|
+
return folded;
|
|
207
249
|
// The rewrite is string algebra and can land on a DIFFERENT file
|
|
208
250
|
// (#3159 review round 2, F1 — see `adoptCanonicalCasing`). Adopt it
|
|
209
251
|
// only once the filesystem agrees it still names the file the caller
|
|
@@ -213,11 +255,11 @@ export function normalizeFilePath(filePath) {
|
|
|
213
255
|
// never on an already-canonical path. A throw here (the rewritten
|
|
214
256
|
// path does not exist, the #2490 monorepo case) lands in the catch
|
|
215
257
|
// below and keeps the caller's spelling, which is the same answer.
|
|
216
|
-
return realpathSync.native(adopted) === canonical ? adopted :
|
|
258
|
+
return realpathSync.native(adopted) === canonical ? adopted : folded;
|
|
217
259
|
}
|
|
218
260
|
catch {
|
|
219
261
|
// Does not exist (or is unreadable): case-preserving, as above.
|
|
220
|
-
return
|
|
262
|
+
return folded;
|
|
221
263
|
}
|
|
222
264
|
}
|
|
223
265
|
// Windows: try realpathSync.native() for canonical casing
|