@herbertgao/pi-extensions 2026.8.5 → 2026.8.6
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 +5 -5
- 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/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +10 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +5 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +24 -17
- package/node_modules/@narumitw/pi-btw/package.json +5 -5
- package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +33 -13
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/pi-lens/CHANGELOG.md +95 -0
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
- package/node_modules/pi-lens/dist/clients/biome-client.js +121 -13
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
- package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +135 -30
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +19 -7
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +6 -4
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +114 -10
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +101 -16
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +230 -50
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/extension-log.js +296 -3
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
- package/node_modules/pi-lens/dist/clients/git-guard.js +18 -21
- package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +116 -7
- package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +5 -1
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +4 -9
- package/node_modules/pi-lens/dist/clients/knip-client.js +51 -14
- package/node_modules/pi-lens/dist/clients/latency-logger.js +50 -1
- package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +58 -13
- package/node_modules/pi-lens/dist/clients/lsp/client.js +169 -8
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +29 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +264 -66
- package/node_modules/pi-lens/dist/clients/lsp/server.js +247 -60
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +30 -17
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +29 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +153 -7
- package/node_modules/pi-lens/dist/clients/runtime-context.js +104 -11
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +170 -22
- package/node_modules/pi-lens/dist/clients/runtime-session.js +28 -5
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +124 -10
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +388 -35
- package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +88 -5
- package/node_modules/pi-lens/dist/clients/sg-runner.js +141 -23
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
- package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +431 -24
- package/node_modules/pi-lens/dist/clients/tool-policy.js +2 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
- package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
- package/node_modules/pi-lens/dist/index.js +4827 -1633
- package/node_modules/pi-lens/dist/mcp/server.js +8 -2
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +3 -2
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +62 -7
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +38 -13
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +11 -3
- package/node_modules/pi-lens/docs/features.md +14 -1
- package/node_modules/pi-lens/docs/globalconfig.md +11 -0
- package/node_modules/pi-lens/docs/servercapabilities.md +1 -1
- package/node_modules/pi-lens/docs/settings.md +6 -0
- package/node_modules/pi-lens/docs/usage.md +23 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +1 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +79 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +13 -0
- package/node_modules/pi-mcp-adapter/README.md +4 -1
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/cli.js +25 -4
- package/node_modules/pi-mcp-adapter/config.ts +4 -4
- package/node_modules/pi-mcp-adapter/direct-tools.ts +18 -15
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +18 -8
- package/node_modules/pi-mcp-adapter/package.json +2 -1
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
- package/node_modules/pi-mcp-adapter/server-manager.ts +5 -0
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
- package/node_modules/pi-mcp-adapter/types.ts +93 -10
- package/node_modules/pi-web-access/CHANGELOG.md +14 -0
- package/node_modules/pi-web-access/README.md +18 -12
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +5 -3
- package/node_modules/pi-web-access/curator-server.ts +2 -1
- package/node_modules/pi-web-access/extract.ts +106 -34
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +18 -4
- package/node_modules/pi-web-access/index.ts +120 -48
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/summary-review.ts +11 -5
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +9 -9
|
@@ -13,7 +13,7 @@ import path from "node:path";
|
|
|
13
13
|
import { getGlobalPiLensDir, getProjectIgnoreGlobs, isPathIgnoredByProject, } from "../file-utils.js";
|
|
14
14
|
import { STAGE_TMP_PATTERN } from "../atomic-write-staging.js";
|
|
15
15
|
import { DOTNET_CSHARP_ROOT_MARKERS, DOTNET_FSHARP_ROOT_MARKERS, KIND_EXTENSIONS, } from "../file-kinds.js";
|
|
16
|
-
import { direntsHaveMarkerGlobMatch, isAtOrAboveHomeDir, isFullyQualified, } from "../path-utils.js";
|
|
16
|
+
import { direntsHaveMarkerGlobMatch, isAtOrAboveHomeDir, isFullyQualified, isWindowsPath, } from "../path-utils.js";
|
|
17
17
|
import { ensureTool, getToolEnvironment, getToolPath, } from "../installer/index.js";
|
|
18
18
|
import { resolveOpengrepConfig } from "../opengrep-config.js";
|
|
19
19
|
import { isZizmorAuditTarget, resolveZizmorGitHubToken } from "../zizmor-config.js";
|
|
@@ -78,17 +78,22 @@ const PROJECT_BOUNDARY_MARKERS = [
|
|
|
78
78
|
// with FALLBACK_PROJECT_MARKERS, the shared fallback root-policy marker set.
|
|
79
79
|
const loggedRootCeilingClamps = new Set();
|
|
80
80
|
function isSameOrWithin(ancestor, candidate) {
|
|
81
|
-
const
|
|
82
|
-
|
|
81
|
+
const windowsShaped = isWindowsPath(ancestor) || isWindowsPath(candidate);
|
|
82
|
+
const pathApi = windowsShaped ? path.win32 : path;
|
|
83
|
+
const relative = pathApi.relative(pathApi.resolve(ancestor), pathApi.resolve(candidate));
|
|
84
|
+
return (relative === "" ||
|
|
85
|
+
(!relative.startsWith("..") && !pathApi.isAbsolute(relative)));
|
|
83
86
|
}
|
|
84
87
|
/** Enforce the session cwd as the hard boundary for LSP client root selection. */
|
|
85
88
|
export function enforceLspRootCeiling(root, sessionCwd, filePath) {
|
|
86
|
-
const
|
|
87
|
-
const
|
|
89
|
+
const windowsShaped = isWindowsPath(root) || isWindowsPath(sessionCwd);
|
|
90
|
+
const pathApi = windowsShaped ? path.win32 : path;
|
|
91
|
+
const resolvedRoot = pathApi.resolve(root);
|
|
92
|
+
const resolvedCwd = pathApi.resolve(sessionCwd);
|
|
88
93
|
// Callers may explicitly inspect an out-of-session file (notably isolated
|
|
89
94
|
// tests and API consumers). The cwd ceiling governs roots for files that are
|
|
90
95
|
// actually inside the declared session project.
|
|
91
|
-
if (filePath && !isSameOrWithin(resolvedCwd,
|
|
96
|
+
if (filePath && !isSameOrWithin(resolvedCwd, pathApi.resolve(filePath))) {
|
|
92
97
|
return resolvedRoot;
|
|
93
98
|
}
|
|
94
99
|
if (isSameOrWithin(resolvedCwd, resolvedRoot))
|
|
@@ -717,6 +722,12 @@ export function NearestRoot(includePatterns, excludePatterns, stopDir) {
|
|
|
717
722
|
// different servers (e.g. TypeScript vs Go) with different marker sets never
|
|
718
723
|
// share entries. vi.resetModules() in tests resets module state between cases.
|
|
719
724
|
const cache = new Map();
|
|
725
|
+
// Only cache successful hits. Undefined results are NOT cached so that a
|
|
726
|
+
// newly-created root marker (e.g. package.json or tsconfig.json scaffolded
|
|
727
|
+
// mid-session by the agent) is detected on the next call — the absent →
|
|
728
|
+
// present transition must work without a process restart. The uncached
|
|
729
|
+
// re-walk cost for configless repos is a known trade-off; bounding the
|
|
730
|
+
// walk with stopDir for in-cwd files is the tracked optimization (#1412).
|
|
720
731
|
const inFlight = new Map();
|
|
721
732
|
return async (file) => {
|
|
722
733
|
// Cache key is the resolved directory — all files in the same dir share a root.
|
|
@@ -774,9 +785,6 @@ export function NearestRoot(includePatterns, excludePatterns, stopDir) {
|
|
|
774
785
|
inFlight.set(dirKey, promise);
|
|
775
786
|
try {
|
|
776
787
|
const result = await promise;
|
|
777
|
-
// Only cache successful hits. Undefined results are not cached so that
|
|
778
|
-
// a newly-created root marker (e.g. package.json added mid-session) is
|
|
779
|
-
// detected on the next call.
|
|
780
788
|
if (result !== undefined)
|
|
781
789
|
cache.set(dirKey, result);
|
|
782
790
|
return result;
|
|
@@ -829,44 +837,173 @@ export async function tryDotnetToolInstall(tool) {
|
|
|
829
837
|
return !updateResult.error && updateResult.status === 0;
|
|
830
838
|
}
|
|
831
839
|
/**
|
|
832
|
-
*
|
|
833
|
-
*
|
|
840
|
+
* #1412 M1: walk up from `startDir` (inclusive) looking for a file at
|
|
841
|
+
* `startDir/<relativeSegments>`, `dirname(startDir)/<relativeSegments>`, and so
|
|
842
|
+
* on — the same ancestor walk `findNativeTypeScriptLsp` uses, bounded the same
|
|
843
|
+
* way (`isAtOrAboveHomeDir`). A nested config root (e.g. a `cypress/tsconfig.json`
|
|
844
|
+
* LSP root inside a repo whose `node_modules` only exists at the repo root) must
|
|
845
|
+
* still resolve tooling installed at an ancestor, not just directly under the
|
|
846
|
+
* LSP root — mirrors how node module resolution itself walks up.
|
|
834
847
|
*/
|
|
835
|
-
async function
|
|
848
|
+
async function findAncestorFile(startDir, ...relativeSegments) {
|
|
849
|
+
return findAncestorFileAmong(startDir, [relativeSegments]);
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Same ancestor walk as `findAncestorFile`, but checks every candidate
|
|
853
|
+
* relative-path in `candidateSegmentLists` AT EACH LEVEL before moving up —
|
|
854
|
+
* so the nearest ancestor wins regardless of which candidate name matched
|
|
855
|
+
* there, matching normal node_modules resolution priority (nearest install
|
|
856
|
+
* shadows a further one, never the reverse).
|
|
857
|
+
*/
|
|
858
|
+
async function findAncestorFileAmong(startDir, candidateSegmentLists) {
|
|
836
859
|
const fs = await import("node:fs/promises");
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
860
|
+
let currentDir = path.resolve(startDir);
|
|
861
|
+
while (!isAtOrAboveHomeDir(currentDir)) {
|
|
862
|
+
for (const segments of candidateSegmentLists) {
|
|
863
|
+
const candidate = path.join(currentDir, ...segments);
|
|
864
|
+
try {
|
|
865
|
+
await fs.access(candidate);
|
|
866
|
+
return candidate;
|
|
867
|
+
}
|
|
868
|
+
catch {
|
|
869
|
+
/* not found at this level */
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
const parent = path.dirname(currentDir);
|
|
873
|
+
if (parent === currentDir)
|
|
874
|
+
break;
|
|
875
|
+
currentDir = parent;
|
|
876
|
+
}
|
|
877
|
+
return undefined;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* A failed classic-compiler repair must not repeat. `ensureTool` caches
|
|
881
|
+
* successful installs, so a repair that works short-circuits later calls on
|
|
882
|
+
* its own. A repair that fails leaves nothing behind, and `findTsserverPath`
|
|
883
|
+
* has three call sites (TypeScript, Vue, Svelte). Without this guard an
|
|
884
|
+
* offline or partial install re-runs a 120 s forced reinstall on every spawn.
|
|
885
|
+
*/
|
|
886
|
+
let classicTsRepairAttempted = false;
|
|
887
|
+
/** Test hook — clears the per-process classic-repair guard. */
|
|
888
|
+
export function _resetClassicTsRepairForTests() {
|
|
889
|
+
classicTsRepairAttempted = false;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Directories that may hold the TypeScript package next to a resolved `tsc`
|
|
893
|
+
* binary: `<bin>/../typescript` (npm-global layout) and
|
|
894
|
+
* `<bin>/../../typescript` (managed `node_modules/.bin` layout).
|
|
895
|
+
*/
|
|
896
|
+
function typescriptDirsForTsc(tscPath) {
|
|
897
|
+
const binDir = path.dirname(tscPath);
|
|
898
|
+
return [
|
|
899
|
+
path.join(binDir, "..", "typescript"),
|
|
900
|
+
path.join(binDir, "..", "..", "typescript"),
|
|
840
901
|
];
|
|
841
|
-
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Read the major version of the TypeScript package that backs a resolved
|
|
905
|
+
* `tsc` binary. Returns undefined when the version is unknowable: `ensureTool`
|
|
906
|
+
* returns the bare string `"tsc"` for a PATH hit, and `path.dirname("tsc")` is
|
|
907
|
+
* `"."`, so the candidates would go cwd-relative. Callers must not repair on
|
|
908
|
+
* an unknown version — a healthy global TypeScript 5.x would be reinstalled
|
|
909
|
+
* for nothing.
|
|
910
|
+
*/
|
|
911
|
+
async function typescriptVersionForTsc(tscPath) {
|
|
912
|
+
if (!path.isAbsolute(tscPath))
|
|
913
|
+
return undefined;
|
|
914
|
+
for (const dir of typescriptDirsForTsc(tscPath)) {
|
|
915
|
+
let manifest;
|
|
842
916
|
try {
|
|
843
|
-
await
|
|
844
|
-
return p;
|
|
917
|
+
manifest = await readFile(path.join(dir, "package.json"), "utf8");
|
|
845
918
|
}
|
|
846
919
|
catch {
|
|
847
|
-
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
let version;
|
|
923
|
+
try {
|
|
924
|
+
const parsed = JSON.parse(manifest);
|
|
925
|
+
if (typeof parsed !== "object" ||
|
|
926
|
+
parsed === null ||
|
|
927
|
+
!("version" in parsed) ||
|
|
928
|
+
typeof parsed.version !== "string") {
|
|
929
|
+
return undefined;
|
|
930
|
+
}
|
|
931
|
+
version = parsed.version;
|
|
848
932
|
}
|
|
933
|
+
catch {
|
|
934
|
+
return undefined;
|
|
935
|
+
}
|
|
936
|
+
const majorText = version.split(".", 1)[0] ?? "";
|
|
937
|
+
const major = /^\d+$/.test(majorText) ? Number(majorText) : Number.NaN;
|
|
938
|
+
if (!Number.isFinite(major))
|
|
939
|
+
return undefined;
|
|
940
|
+
return { version, major };
|
|
849
941
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
942
|
+
return undefined;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Locate tsserver.js — tries local project (walking up from root, #1412 M1),
|
|
946
|
+
* then process.cwd() as a last-resort fallback, then pi-lens managed
|
|
947
|
+
* TypeScript. Returns the path to tsserver.js, or undefined if not found.
|
|
948
|
+
*/
|
|
949
|
+
async function findTsserverPath(root, allowInstall) {
|
|
950
|
+
const fs = await import("node:fs/promises");
|
|
951
|
+
const ancestorHit = await findAncestorFile(root, "node_modules", "typescript", "lib", "tsserver.js");
|
|
952
|
+
if (ancestorHit)
|
|
953
|
+
return ancestorHit;
|
|
954
|
+
const cwdCandidate = path.join(process.cwd(), "node_modules", "typescript", "lib", "tsserver.js");
|
|
955
|
+
try {
|
|
956
|
+
await fs.access(cwdCandidate);
|
|
957
|
+
return cwdCandidate;
|
|
958
|
+
}
|
|
959
|
+
catch {
|
|
960
|
+
/* not found */
|
|
961
|
+
}
|
|
962
|
+
const tsserverForTsc = async (tscPath) => {
|
|
963
|
+
if (!tscPath)
|
|
964
|
+
return undefined;
|
|
965
|
+
for (const dir of typescriptDirsForTsc(tscPath)) {
|
|
966
|
+
const candidate = path.join(dir, "lib", "tsserver.js");
|
|
860
967
|
try {
|
|
861
|
-
await fs.access(
|
|
862
|
-
return
|
|
968
|
+
await fs.access(candidate);
|
|
969
|
+
return candidate;
|
|
863
970
|
}
|
|
864
971
|
catch {
|
|
865
972
|
/* not found */
|
|
866
973
|
}
|
|
867
974
|
}
|
|
975
|
+
return undefined;
|
|
976
|
+
};
|
|
977
|
+
// Discover the TypeScript install (PATH / npm-global) even when installation
|
|
978
|
+
// is disabled; only the download is gated by allowInstall.
|
|
979
|
+
const installAllowed = canInstall(allowInstall);
|
|
980
|
+
const discoveredTsc = await ensureTool("typescript", {
|
|
981
|
+
allowInstall: installAllowed,
|
|
982
|
+
});
|
|
983
|
+
const discoveredTsserver = await tsserverForTsc(discoveredTsc);
|
|
984
|
+
if (discoveredTsserver ||
|
|
985
|
+
!discoveredTsc ||
|
|
986
|
+
!installAllowed ||
|
|
987
|
+
classicTsRepairAttempted) {
|
|
988
|
+
return discoveredTsserver;
|
|
868
989
|
}
|
|
869
|
-
|
|
990
|
+
// Repair only a compiler we can prove is TypeScript 7+. TypeScript 7 dropped
|
|
991
|
+
// lib/tsserver.js, so the classic wrapper cannot start against it. Any other
|
|
992
|
+
// version — or a version we cannot read, such as a bare PATH `tsc` — is left
|
|
993
|
+
// alone rather than force-reinstalled.
|
|
994
|
+
const discoveredVersion = await typescriptVersionForTsc(discoveredTsc);
|
|
995
|
+
if (!discoveredVersion || discoveredVersion.major < 7)
|
|
996
|
+
return undefined;
|
|
997
|
+
// An older managed tree took `latest` before the registry pinned the classic
|
|
998
|
+
// compiler. Reinstall the pinned version once so that tree self-heals,
|
|
999
|
+
// without deleting user or project-local TypeScript installations.
|
|
1000
|
+
classicTsRepairAttempted = true;
|
|
1001
|
+
logSessionStart(`lsp typescript: managed compiler resolved to TypeScript ${discoveredVersion.version}, which ships no tsserver.js; reinstalling pinned classic fallback`);
|
|
1002
|
+
const repairedTsc = await ensureTool("typescript", {
|
|
1003
|
+
allowInstall: true,
|
|
1004
|
+
forceReinstall: true,
|
|
1005
|
+
});
|
|
1006
|
+
return tsserverForTsc(repairedTsc);
|
|
870
1007
|
}
|
|
871
1008
|
/**
|
|
872
1009
|
* TypeScript 7+ ships the native typescript-go language server through the
|
|
@@ -1046,10 +1183,17 @@ export async function detectPythonVenv(root) {
|
|
|
1046
1183
|
}
|
|
1047
1184
|
// --- Server Definitions ---
|
|
1048
1185
|
const JS_TS_LSP_EXTENSIONS = KIND_EXTENSIONS["jsts"].filter((ext) => ext !== ".svelte" && ext !== ".vue");
|
|
1049
|
-
//
|
|
1050
|
-
//
|
|
1051
|
-
//
|
|
1052
|
-
|
|
1186
|
+
// TypeScript identity and tooling discovery deliberately use separate marker
|
|
1187
|
+
// families. A governing config wins even when a package directory supplies
|
|
1188
|
+
// hoisted binaries. Keep configs out of PROJECT_BOUNDARY_MARKERS: #1373 still
|
|
1189
|
+
// coalesces a config-only nested root when an ancestor client was hosted first
|
|
1190
|
+
// (nested-config-first remains intentionally open-order-sensitive). #1412
|
|
1191
|
+
// accepted risk (M2, not fixed here): honoring nested config roots at all
|
|
1192
|
+
// enlarges the population of directories that can independently coalesce or
|
|
1193
|
+
// diverge under #1373's open-order sensitivity — the same pre-existing
|
|
1194
|
+
// blast-radius, just triggered by more roots than before.
|
|
1195
|
+
const TS_CONFIG_MARKERS = ["tsconfig.json", "jsconfig.json"];
|
|
1196
|
+
const TS_TOOLING_MARKERS = [
|
|
1053
1197
|
"package-lock.json",
|
|
1054
1198
|
"bun.lockb",
|
|
1055
1199
|
"bun.lock",
|
|
@@ -1057,21 +1201,71 @@ const TS_PROJECT_MARKERS = [
|
|
|
1057
1201
|
"yarn.lock",
|
|
1058
1202
|
"package.json",
|
|
1059
1203
|
];
|
|
1060
|
-
|
|
1204
|
+
// #1412 M3: tsserver associates jsconfig.json with JS files only (its identity
|
|
1205
|
+
// probe reports a jsconfig-governed .ts file as unassociated) — so a TS-family
|
|
1206
|
+
// file under a jsconfig-only directory must NOT root there; keep walking up
|
|
1207
|
+
// for a real tsconfig.json. A JS-family file accepts either: tsconfig also
|
|
1208
|
+
// governs plain JS via `allowJs`, so accepting tsconfig for a .js file is
|
|
1209
|
+
// correct, and jsconfig obviously is too.
|
|
1210
|
+
const TS_FAMILY_EXTENSIONS = new Set([".ts", ".tsx", ".mts", ".cts"]);
|
|
1211
|
+
function isTsFamilyFile(file) {
|
|
1212
|
+
return TS_FAMILY_EXTENSIONS.has(path.extname(file).toLowerCase());
|
|
1213
|
+
}
|
|
1214
|
+
function tsConfigMarkersForFile(file) {
|
|
1215
|
+
return isTsFamilyFile(file) ? ["tsconfig.json"] : TS_CONFIG_MARKERS;
|
|
1216
|
+
}
|
|
1217
|
+
// Two detector instances (not one parameterized by file) so each keeps its own
|
|
1218
|
+
// per-directory NearestRoot cache valid for its fixed marker set — a shared
|
|
1219
|
+
// cache keyed only by directory would conflate the TS-only and either-config
|
|
1220
|
+
// answers for the same directory.
|
|
1221
|
+
const TypeScriptConfigRootTsOnly = IgnoreHomeRoot(createRootDetector(["tsconfig.json"]));
|
|
1222
|
+
const TypeScriptConfigRootEither = IgnoreHomeRoot(createRootDetector([...TS_CONFIG_MARKERS]));
|
|
1223
|
+
const TypeScriptToolingRoot = IgnoreHomeRoot(createRootDetector([...TS_TOOLING_MARKERS]));
|
|
1224
|
+
async function findTypeScriptProjectRoot(file) {
|
|
1225
|
+
const configDetector = isTsFamilyFile(file)
|
|
1226
|
+
? TypeScriptConfigRootTsOnly
|
|
1227
|
+
: TypeScriptConfigRootEither;
|
|
1228
|
+
const [configRoot, toolingRoot] = await Promise.all([
|
|
1229
|
+
configDetector(file),
|
|
1230
|
+
TypeScriptToolingRoot(file),
|
|
1231
|
+
]);
|
|
1232
|
+
if (!configRoot)
|
|
1233
|
+
return toolingRoot;
|
|
1234
|
+
if (!toolingRoot)
|
|
1235
|
+
return configRoot;
|
|
1236
|
+
// A config inside (or beside) the nearest package governs its files. A
|
|
1237
|
+
// config above a nearer package must not erase that topology boundary.
|
|
1238
|
+
return isSameOrWithin(toolingRoot, configRoot) ? configRoot : toolingRoot;
|
|
1239
|
+
}
|
|
1061
1240
|
/**
|
|
1062
|
-
* Walk up from the file's directory looking for a TypeScript project marker
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1241
|
+
* Walk up from the file's directory looking for a TypeScript project marker
|
|
1242
|
+
* (a governing config first, per-directory, per #1412 M3's extension-family
|
|
1243
|
+
* filter; else a tooling/lockfile marker), but stop at `extensionRootKey` so
|
|
1244
|
+
* we never escape the .pi/agent/extensions boundary into a higher-up project
|
|
1245
|
+
* (e.g. ~/.pi/agent/package.json which would pull every extension in the
|
|
1246
|
+
* directory into one LSP workspace).
|
|
1066
1247
|
*
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1248
|
+
* #1412 L4: each directory returns immediately on its first match (config or
|
|
1249
|
+
* tooling) — there is no cross-level "nearest tooling root" to carry forward,
|
|
1250
|
+
* since a match always wins on the spot. Returns the nearest directory
|
|
1251
|
+
* containing a marker, or undefined if none is found between the file and the
|
|
1252
|
+
* extensions root inclusive.
|
|
1069
1253
|
*/
|
|
1070
1254
|
async function findExtensionBoundedRoot(file, extensionRootKey) {
|
|
1071
1255
|
const startDir = path.resolve(path.dirname(file));
|
|
1072
1256
|
let currentDir = startDir;
|
|
1257
|
+
const configMarkers = tsConfigMarkersForFile(file);
|
|
1073
1258
|
while (true) {
|
|
1074
|
-
for (const pattern of
|
|
1259
|
+
for (const pattern of configMarkers) {
|
|
1260
|
+
try {
|
|
1261
|
+
await stat(path.join(currentDir, pattern));
|
|
1262
|
+
return currentDir;
|
|
1263
|
+
}
|
|
1264
|
+
catch {
|
|
1265
|
+
/* not found, try next marker */
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
for (const pattern of TS_TOOLING_MARKERS) {
|
|
1075
1269
|
try {
|
|
1076
1270
|
await stat(path.join(currentDir, pattern));
|
|
1077
1271
|
return currentDir;
|
|
@@ -1102,7 +1296,7 @@ async function hasAgentLevelProjectMarker(extensionRootKey) {
|
|
|
1102
1296
|
const agentDir = path.dirname(extensionRootKey);
|
|
1103
1297
|
if (!agentDir || agentDir === extensionRootKey)
|
|
1104
1298
|
return false;
|
|
1105
|
-
for (const pattern of
|
|
1299
|
+
for (const pattern of [...TS_CONFIG_MARKERS, ...TS_TOOLING_MARKERS]) {
|
|
1106
1300
|
try {
|
|
1107
1301
|
await stat(path.join(agentDir, pattern));
|
|
1108
1302
|
return true;
|
|
@@ -1133,7 +1327,7 @@ const TypeScriptRoot = DenoExcludeRoot(async (file) => {
|
|
|
1133
1327
|
// analyze a lone .ts file with no package.json above or below).
|
|
1134
1328
|
return undefined;
|
|
1135
1329
|
}
|
|
1136
|
-
const projectRoot = await
|
|
1330
|
+
const projectRoot = await findTypeScriptProjectRoot(file);
|
|
1137
1331
|
if (projectRoot)
|
|
1138
1332
|
return projectRoot;
|
|
1139
1333
|
return FileDirRoot(file);
|
|
@@ -1159,20 +1353,13 @@ export const TypeScriptServer = {
|
|
|
1159
1353
|
let source = "direct";
|
|
1160
1354
|
// TypeScript <=6 uses typescript-language-server + tsserver.js. Prefer a
|
|
1161
1355
|
// project-local wrapper, then fall back to discovered/managed tooling.
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
lspPath = checkPath;
|
|
1170
|
-
break;
|
|
1171
|
-
}
|
|
1172
|
-
catch {
|
|
1173
|
-
/* not found */
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1356
|
+
// #1412 M1: walk up from root (Windows .cmd first, then Unix at each
|
|
1357
|
+
// level) — a nested config root's node_modules/.bin lives at an ancestor,
|
|
1358
|
+
// not necessarily directly under the LSP root.
|
|
1359
|
+
let lspPath = await findAncestorFileAmong(root, [
|
|
1360
|
+
["node_modules", ".bin", "typescript-language-server.cmd"],
|
|
1361
|
+
["node_modules", ".bin", "typescript-language-server"],
|
|
1362
|
+
]);
|
|
1176
1363
|
// Fall back to a discovered or managed install. ensureTool() runs PATH /
|
|
1177
1364
|
// npm-global discovery even when install is disabled (only the download is
|
|
1178
1365
|
// gated by canInstall), so a globally-installed typescript-language-server
|
|
@@ -32,10 +32,106 @@
|
|
|
32
32
|
* `undefined`). The caller must handle `undefined` as "sync path unavailable,
|
|
33
33
|
* fall back to existing unconfirmed/timed-out behavior".
|
|
34
34
|
*/
|
|
35
|
+
import { logLatency } from "../latency-logger.js";
|
|
36
|
+
import { normalizeMapKey } from "../path-utils.js";
|
|
35
37
|
// ---------------------------------------------------------------------------
|
|
36
38
|
// Constants
|
|
37
39
|
// ---------------------------------------------------------------------------
|
|
38
40
|
export const TSSERVER_REQUEST_COMMAND = "typescript.tsserverRequest";
|
|
41
|
+
function classifyProjectInfo(body) {
|
|
42
|
+
if (!body || typeof body !== "object") {
|
|
43
|
+
return { projectKind: "unassociated", association: "unassociated" };
|
|
44
|
+
}
|
|
45
|
+
const info = body;
|
|
46
|
+
const configFile = typeof info.configFileName === "string" && info.configFileName.length > 0
|
|
47
|
+
? info.configFileName
|
|
48
|
+
: undefined;
|
|
49
|
+
const inferred = configFile
|
|
50
|
+
? /(?:^|[/\\])inferredProject\d*\*?$/i.test(configFile) ||
|
|
51
|
+
/inferred[-_ ]?project/i.test(configFile)
|
|
52
|
+
: false;
|
|
53
|
+
const projectKind = configFile
|
|
54
|
+
? inferred
|
|
55
|
+
? "inferred"
|
|
56
|
+
: /(?:^|[/\\])(?:tsconfig|jsconfig)\.json$/i.test(configFile)
|
|
57
|
+
? "configured"
|
|
58
|
+
: "unassociated"
|
|
59
|
+
: "unassociated";
|
|
60
|
+
return {
|
|
61
|
+
projectKind,
|
|
62
|
+
...(configFile ? { configFile } : {}),
|
|
63
|
+
association: info.languageServiceDisabled === true
|
|
64
|
+
? "language-service-disabled"
|
|
65
|
+
: projectKind === "unassociated"
|
|
66
|
+
? "unassociated"
|
|
67
|
+
: "associated",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* #1412: after classic TypeScript's first successful didOpen, sample the
|
|
72
|
+
* tsserver project association without delaying diagnostics. The supplied
|
|
73
|
+
* executeCommand channel owns the existing bounded anti-deadlock backstop.
|
|
74
|
+
*/
|
|
75
|
+
export async function probeTsserverProjectIdentity(options) {
|
|
76
|
+
const normalizedFile = options.normalizedFile ?? normalizeMapKey(options.file);
|
|
77
|
+
const startedAt = Date.now();
|
|
78
|
+
// Logging starts only once a probe is actually eligible and attempted:
|
|
79
|
+
// ineligible servers (wrong serverId/launchVariant, no command channel) and
|
|
80
|
+
// already-probed dedupe are both routine, high-volume, and per-server — a
|
|
81
|
+
// bare return keeps them out of the telemetry stream entirely instead of
|
|
82
|
+
// writing an `lsp_typescript_project_identity` row per didOpen on every
|
|
83
|
+
// server (python, go, opengrep, ...).
|
|
84
|
+
const logOutcome = (outcome, metadata = {}) => logLatency({
|
|
85
|
+
type: "phase", phase: "lsp_typescript_project_identity", filePath: normalizedFile,
|
|
86
|
+
durationMs: Date.now() - startedAt,
|
|
87
|
+
metadata: { serverId: options.serverId, launchVariant: options.launchVariant, clientRoot: options.clientRoot, outcome, ...metadata },
|
|
88
|
+
});
|
|
89
|
+
if (options.serverId !== "typescript" ||
|
|
90
|
+
options.launchVariant !== "classic" ||
|
|
91
|
+
typeof options.commandChannel.executeCommand !== "function") {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (options.probedFiles.has(normalizedFile))
|
|
95
|
+
return;
|
|
96
|
+
// Claim before yielding so concurrent opens cannot issue duplicate probes.
|
|
97
|
+
options.probedFiles.add(normalizedFile);
|
|
98
|
+
try {
|
|
99
|
+
const outcome = await options.commandChannel.executeCommand(TSSERVER_REQUEST_COMMAND, ["projectInfo", { file: options.file, needFileNameList: false }]);
|
|
100
|
+
if (!outcome.executed) {
|
|
101
|
+
logOutcome("not-executed");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const response = outcome.result;
|
|
105
|
+
if (!response) {
|
|
106
|
+
logOutcome("no-response");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (response.success !== true) {
|
|
110
|
+
logOutcome("unsuccessful");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const identity = classifyProjectInfo(response.body);
|
|
114
|
+
logLatency({
|
|
115
|
+
type: "phase",
|
|
116
|
+
phase: "lsp_typescript_project_identity",
|
|
117
|
+
filePath: normalizedFile,
|
|
118
|
+
durationMs: Date.now() - startedAt,
|
|
119
|
+
metadata: {
|
|
120
|
+
outcome: "ok",
|
|
121
|
+
serverId: options.serverId,
|
|
122
|
+
launchVariant: options.launchVariant,
|
|
123
|
+
clientRoot: options.clientRoot,
|
|
124
|
+
projectKind: identity.projectKind,
|
|
125
|
+
configFile: identity.configFile,
|
|
126
|
+
association: identity.association,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
logOutcome("threw");
|
|
132
|
+
// Best-effort telemetry: command errors/timeouts never reach diagnostics.
|
|
133
|
+
}
|
|
134
|
+
}
|
|
39
135
|
// ---------------------------------------------------------------------------
|
|
40
136
|
// Helpers
|
|
41
137
|
// ---------------------------------------------------------------------------
|
|
@@ -26,7 +26,7 @@ export function classifyServerWaitTier(serverId, snapshot) {
|
|
|
26
26
|
return "pull-capable";
|
|
27
27
|
if (mode !== "push-only")
|
|
28
28
|
return "waits";
|
|
29
|
-
const strategy = getStrategy(serverId);
|
|
29
|
+
const strategy = getStrategy(serverId, snapshot.launchVariant);
|
|
30
30
|
if (strategy.silentOnClean !== true)
|
|
31
31
|
return "waits"; // 2*/unknown push-only
|
|
32
32
|
// #524/#529/#541/#558: `silentOnClean` on a server-id-keyed strategy is
|
|
@@ -44,6 +44,24 @@ export function classifyServerWaitTier(serverId, snapshot) {
|
|
|
44
44
|
return "waits";
|
|
45
45
|
return "tier3-silent";
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolve `filePath`'s PRIMARY (non-auxiliary) server id and its live
|
|
49
|
+
* capability snapshot, or `undefined` when the file has no primary server
|
|
50
|
+
* configured. The single place that decides what "this file's server" means
|
|
51
|
+
* for wait-policy purposes — shared by {@link classifyCascadeWaitTier} and the
|
|
52
|
+
* cascade-lane wrapper in `clients/lsp/cascade-tier.ts`, which needs the same
|
|
53
|
+
* id/snapshot pair to apply its one cascade-only override.
|
|
54
|
+
*/
|
|
55
|
+
export function resolvePrimaryServerForWaitPolicy(filePath, snapshots) {
|
|
56
|
+
const servers = getServersForFileWithConfig(filePath).filter((s) => s.role !== "auxiliary");
|
|
57
|
+
const primary = servers[0];
|
|
58
|
+
if (!primary)
|
|
59
|
+
return undefined;
|
|
60
|
+
return {
|
|
61
|
+
serverId: primary.id,
|
|
62
|
+
snapshot: snapshots.find((s) => s.serverId === primary.id),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
47
65
|
/**
|
|
48
66
|
* Classify whether `filePath`'s PRIMARY language server is a cascade-lane
|
|
49
67
|
* Tier-3 (push-only, silent-on-clean) server. Ambiguous or missing capability
|
|
@@ -54,10 +72,8 @@ export function classifyServerWaitTier(serverId, snapshot) {
|
|
|
54
72
|
*/
|
|
55
73
|
export function classifyCascadeWaitTier(lspService, filePath, snapshots) {
|
|
56
74
|
void lspService; // kept in the signature for call-site clarity/typing only
|
|
57
|
-
const
|
|
58
|
-
const primary = servers[0];
|
|
75
|
+
const primary = resolvePrimaryServerForWaitPolicy(filePath, snapshots);
|
|
59
76
|
if (!primary)
|
|
60
77
|
return "waits";
|
|
61
|
-
|
|
62
|
-
return classifyServerWaitTier(primary.id, snapshot);
|
|
78
|
+
return classifyServerWaitTier(primary.serverId, primary.snapshot);
|
|
63
79
|
}
|
|
@@ -138,7 +138,8 @@ export const SERVER_DIAGNOSTIC_STRATEGIES = {
|
|
|
138
138
|
// (#242), so on a 2500ms-capped edit opengrep waits min(2500, 3500)=2500
|
|
139
139
|
// and on uncapped paths it gets its full 3500. 3500 covers warm and most
|
|
140
140
|
// cold; a cold scan that overruns isn't lost — late diagnostics are cached
|
|
141
|
-
// and surface on the next
|
|
141
|
+
// and surface on the next unchanged-content read through the content-bound
|
|
142
|
+
// auxiliary carry-over path.
|
|
142
143
|
opengrep: {
|
|
143
144
|
seedFirstPush: false,
|
|
144
145
|
pullRetryBudgetMs: 0,
|
|
@@ -174,7 +175,8 @@ export const SERVER_DIAGNOSTIC_STRATEGIES = {
|
|
|
174
175
|
// reopen-on-resync. A native single-workflow audit is sub-second offline;
|
|
175
176
|
// online mode may add a GitHub-API round-trip, so 2000ms gives headroom
|
|
176
177
|
// (bounded by the per-edit caller cap as a ceiling, #242), and any late online
|
|
177
|
-
// finding
|
|
178
|
+
// finding can surface on the next unchanged-content read through the
|
|
179
|
+
// content-bound auxiliary carry-over path.
|
|
178
180
|
zizmor: {
|
|
179
181
|
seedFirstPush: true,
|
|
180
182
|
pullRetryBudgetMs: 0,
|
|
@@ -231,6 +233,16 @@ export const SERVER_DIAGNOSTIC_STRATEGIES = {
|
|
|
231
233
|
silentOnClean: true,
|
|
232
234
|
},
|
|
233
235
|
};
|
|
236
|
+
/** Native TS7 can publish multiple versionless partial-program snapshots for a
|
|
237
|
+
* single open. Its first push is therefore provisional; a quiet window, rather
|
|
238
|
+
* than a protocol-unstable publication count, establishes the settled push. */
|
|
239
|
+
const NATIVE_TS7_DIAGNOSTIC_STRATEGY = {
|
|
240
|
+
...SERVER_DIAGNOSTIC_STRATEGIES.typescript,
|
|
241
|
+
seedFirstPush: false,
|
|
242
|
+
// Native TS7 demonstrably publishes on clean, so the classic-only marker
|
|
243
|
+
// must not leak through the shared server id.
|
|
244
|
+
silentOnClean: false,
|
|
245
|
+
};
|
|
234
246
|
/** Fallback for unknown servers. Conservative defaults. */
|
|
235
247
|
export const DEFAULT_STRATEGY = {
|
|
236
248
|
seedFirstPush: false,
|
|
@@ -239,6 +251,9 @@ export const DEFAULT_STRATEGY = {
|
|
|
239
251
|
aggregateWaitMs: 1500,
|
|
240
252
|
expectSemanticSecondPush: false,
|
|
241
253
|
};
|
|
242
|
-
export function getStrategy(serverId) {
|
|
254
|
+
export function getStrategy(serverId, launchVariant) {
|
|
255
|
+
if (serverId === "typescript" && launchVariant === "native-ts7") {
|
|
256
|
+
return NATIVE_TS7_DIAGNOSTIC_STRATEGY;
|
|
257
|
+
}
|
|
243
258
|
return SERVER_DIAGNOSTIC_STRATEGIES[serverId] ?? DEFAULT_STRATEGY;
|
|
244
259
|
}
|
|
@@ -239,6 +239,10 @@ export async function analyzeFile(filePath, cwd, options = {}) {
|
|
|
239
239
|
}
|
|
240
240
|
const reportsBefore = getLatencyReports().length;
|
|
241
241
|
const start = Date.now();
|
|
242
|
+
// No telemetryModel/telemetryProvider here (#1448): this MCP facade has no
|
|
243
|
+
// RuntimeCoordinator to hold a host-reported identity (see the module doc
|
|
244
|
+
// above), so any worklog entries this dispatch produces get a blank
|
|
245
|
+
// model/provider — that's correct, not a gap.
|
|
242
246
|
const result = await dispatchLintWithResult(absPath, cwd, host, undefined, undefined, {
|
|
243
247
|
blockingOnly: options.blockingOnly ?? false,
|
|
244
248
|
});
|