@skillcap/gdh 0.25.4 → 0.26.0
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/INSTALL-BUNDLE.json +1 -1
- package/RELEASE-SPAN-UPDATE-CONTRACTS.json +147 -0
- package/node_modules/@gdh/adapters/dist/claude-settings-patch.d.ts.map +1 -1
- package/node_modules/@gdh/adapters/dist/claude-settings-patch.js +38 -15
- package/node_modules/@gdh/adapters/dist/claude-settings-patch.js.map +1 -1
- package/node_modules/@gdh/adapters/dist/index.d.ts +12 -0
- package/node_modules/@gdh/adapters/dist/index.d.ts.map +1 -1
- package/node_modules/@gdh/adapters/dist/index.js +21 -0
- package/node_modules/@gdh/adapters/dist/index.js.map +1 -1
- package/node_modules/@gdh/adapters/dist/self-update-mechanics.d.ts.map +1 -1
- package/node_modules/@gdh/adapters/dist/self-update-mechanics.js +49 -16
- package/node_modules/@gdh/adapters/dist/self-update-mechanics.js.map +1 -1
- package/node_modules/@gdh/adapters/dist/skill-rendering.d.ts +5 -2
- package/node_modules/@gdh/adapters/dist/skill-rendering.d.ts.map +1 -1
- package/node_modules/@gdh/adapters/dist/skill-rendering.js +39 -0
- package/node_modules/@gdh/adapters/dist/skill-rendering.js.map +1 -1
- package/node_modules/@gdh/adapters/dist/templates/authoring-hook.js.tpl +200 -15
- package/node_modules/@gdh/adapters/package.json +8 -8
- package/node_modules/@gdh/authoring/dist/diagnostics-broker-contract.d.ts +1 -0
- package/node_modules/@gdh/authoring/dist/diagnostics-broker-contract.d.ts.map +1 -1
- package/node_modules/@gdh/authoring/dist/diagnostics-broker-contract.js +1 -0
- package/node_modules/@gdh/authoring/dist/diagnostics-broker-contract.js.map +1 -1
- package/node_modules/@gdh/authoring/dist/diagnostics-broker.d.ts +2 -1
- package/node_modules/@gdh/authoring/dist/diagnostics-broker.d.ts.map +1 -1
- package/node_modules/@gdh/authoring/dist/diagnostics-broker.js +91 -13
- package/node_modules/@gdh/authoring/dist/diagnostics-broker.js.map +1 -1
- package/node_modules/@gdh/authoring/dist/index.d.ts +1 -1
- package/node_modules/@gdh/authoring/dist/index.d.ts.map +1 -1
- package/node_modules/@gdh/authoring/dist/index.js +2 -1
- package/node_modules/@gdh/authoring/dist/index.js.map +1 -1
- package/node_modules/@gdh/authoring/dist/lsp-warmup.d.ts +30 -0
- package/node_modules/@gdh/authoring/dist/lsp-warmup.d.ts.map +1 -0
- package/node_modules/@gdh/authoring/dist/lsp-warmup.js +213 -0
- package/node_modules/@gdh/authoring/dist/lsp-warmup.js.map +1 -0
- package/node_modules/@gdh/authoring/dist/lsp.d.ts +8 -1
- package/node_modules/@gdh/authoring/dist/lsp.d.ts.map +1 -1
- package/node_modules/@gdh/authoring/dist/lsp.js +256 -104
- package/node_modules/@gdh/authoring/dist/lsp.js.map +1 -1
- package/node_modules/@gdh/authoring/dist/scene-resource.d.ts.map +1 -1
- package/node_modules/@gdh/authoring/dist/scene-resource.js +140 -0
- package/node_modules/@gdh/authoring/dist/scene-resource.js.map +1 -1
- package/node_modules/@gdh/authoring/package.json +2 -2
- package/node_modules/@gdh/cli/dist/index.d.ts.map +1 -1
- package/node_modules/@gdh/cli/dist/index.js +75 -10
- package/node_modules/@gdh/cli/dist/index.js.map +1 -1
- package/node_modules/@gdh/cli/dist/self-update.d.ts.map +1 -1
- package/node_modules/@gdh/cli/dist/self-update.js +66 -10
- package/node_modules/@gdh/cli/dist/self-update.js.map +1 -1
- package/node_modules/@gdh/cli/package.json +10 -10
- package/node_modules/@gdh/core/dist/index.d.ts +178 -6
- package/node_modules/@gdh/core/dist/index.d.ts.map +1 -1
- package/node_modules/@gdh/core/dist/index.js +44 -4
- package/node_modules/@gdh/core/dist/index.js.map +1 -1
- package/node_modules/@gdh/core/dist/migrations/managed-surface-classes.d.ts +15 -0
- package/node_modules/@gdh/core/dist/migrations/managed-surface-classes.d.ts.map +1 -1
- package/node_modules/@gdh/core/dist/migrations/managed-surface-classes.js +18 -0
- package/node_modules/@gdh/core/dist/migrations/managed-surface-classes.js.map +1 -1
- package/node_modules/@gdh/core/dist/migrations/managed-target-surface-inventory.d.ts.map +1 -1
- package/node_modules/@gdh/core/dist/migrations/managed-target-surface-inventory.js +2 -0
- package/node_modules/@gdh/core/dist/migrations/managed-target-surface-inventory.js.map +1 -1
- package/node_modules/@gdh/core/package.json +1 -1
- package/node_modules/@gdh/docs/dist/templates/guidance/authoring-and-validation.md.tpl +4 -5
- package/node_modules/@gdh/docs/package.json +2 -2
- package/node_modules/@gdh/mcp/dist/index.d.ts.map +1 -1
- package/node_modules/@gdh/mcp/dist/index.js +30 -1
- package/node_modules/@gdh/mcp/dist/index.js.map +1 -1
- package/node_modules/@gdh/mcp/package.json +8 -8
- package/node_modules/@gdh/observability/package.json +2 -2
- package/node_modules/@gdh/runtime/package.json +2 -2
- package/node_modules/@gdh/scan/package.json +3 -3
- package/node_modules/@gdh/verify/package.json +7 -7
- package/package.json +11 -11
|
@@ -5,7 +5,7 @@ import fs from "node:fs/promises";
|
|
|
5
5
|
import net from "node:net";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
-
import { GDH_MANAGED_LSP_SURFACE_VERSION, resolveConfiguredGodotEditorBin } from "@gdh/core";
|
|
8
|
+
import { GDH_MANAGED_LSP_MIN_GODOT_VERSION, GDH_MANAGED_LSP_SURFACE_VERSION, resolveConfiguredGodotEditorBin, } from "@gdh/core";
|
|
9
9
|
import { connectGodotLspClient, GdhLspClientError, } from "./lsp-client.js";
|
|
10
10
|
import { summarizeBlockingDiagnostics } from "./diagnostics-summary.js";
|
|
11
11
|
import { classifyDiagnosticsFreshness, DIAGNOSTICS_BROKER_DEFAULT_FRESHNESS_STALE_AFTER_MS, resolveDiagnosticsBrokerPaths, } from "./diagnostics-broker-contract.js";
|
|
@@ -24,6 +24,7 @@ const MANAGED_LSP_HEALTH_RETRY_DELAY_MS = 100;
|
|
|
24
24
|
const MANAGED_LSP_DIAGNOSTIC_CONNECT_TIMEOUT_MS = 1_000;
|
|
25
25
|
const MANAGED_LSP_DIAGNOSTIC_REQUEST_TIMEOUT_MS = 1_000;
|
|
26
26
|
const MANAGED_LSP_DIAGNOSTIC_WAIT_TIMEOUT_MS = 10_000;
|
|
27
|
+
const MANAGED_LSP_POST_EDIT_DIAGNOSTIC_WAIT_TIMEOUT_MS = 1_500;
|
|
27
28
|
class GdhManagedLspStateLockError extends Error {
|
|
28
29
|
reason;
|
|
29
30
|
reasons;
|
|
@@ -60,7 +61,7 @@ async function readBrokerSnapshot(targetPath) {
|
|
|
60
61
|
*
|
|
61
62
|
* Returns null otherwise (fall through to direct LSP check).
|
|
62
63
|
*/
|
|
63
|
-
function extractBrokerDiagnosticsIfFresh(snapshot, currentInstanceId, requestedUris) {
|
|
64
|
+
function extractBrokerDiagnosticsIfFresh(snapshot, currentInstanceId, requestedUris, currentContentHashesByUri) {
|
|
64
65
|
if (snapshot.lspInstanceId !== currentInstanceId) {
|
|
65
66
|
return null;
|
|
66
67
|
}
|
|
@@ -73,6 +74,12 @@ function extractBrokerDiagnosticsIfFresh(snapshot, currentInstanceId, requestedU
|
|
|
73
74
|
if (file === undefined) {
|
|
74
75
|
return null; // Requested URI not in broker — fall through.
|
|
75
76
|
}
|
|
77
|
+
if (typeof file.contentHash !== "string" || file.contentHash.length === 0) {
|
|
78
|
+
return null; // Pre-content-hash snapshots cannot prove current file content.
|
|
79
|
+
}
|
|
80
|
+
if (currentContentHashesByUri.get(uri) !== file.contentHash) {
|
|
81
|
+
return null; // Snapshot was captured for different file content.
|
|
82
|
+
}
|
|
76
83
|
const freshness = classifyDiagnosticsFreshness({
|
|
77
84
|
fileSnapshot: {
|
|
78
85
|
uri: file.uri,
|
|
@@ -115,122 +122,190 @@ export async function getManagedLspStatus(input) {
|
|
|
115
122
|
return unavailable;
|
|
116
123
|
}
|
|
117
124
|
try {
|
|
118
|
-
return await withManagedLspStateLock(worktree, input.owner ?? "gdh lsp status", async (stateLock) =>
|
|
119
|
-
let cleanedStaleInstance = false;
|
|
120
|
-
let cleanupReasons = [];
|
|
125
|
+
return await withManagedLspStateLock(worktree, input.owner ?? "gdh lsp status", async (stateLock) => {
|
|
121
126
|
const projectPath = path.resolve(input.targetPath, input.status.primaryProjectPath ?? ".");
|
|
122
127
|
const expectedIdentity = await resolveCurrentManagedLspIdentity(worktree, projectPath);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
128
|
+
// Phase 81 / LSP-08 / D-17: refuse-to-launch when the configured
|
|
129
|
+
// Godot version is below the managed-LSP floor. Short-circuits
|
|
130
|
+
// BEFORE lsp-leases.json acquisition (withLease) and BEFORE
|
|
131
|
+
// persisted-instance reads — but executes inside the lsp.lock
|
|
132
|
+
// (stateLock) so identity resolution is serialized with other
|
|
133
|
+
// lifecycle mutations. Misconfigured 4.5.0/4.5.1 setups never
|
|
134
|
+
// acquire a lease or block other waiters.
|
|
135
|
+
if (expectedIdentity.editorVersionReason === "godot_editor_version_unsupported_for_lsp") {
|
|
136
|
+
return createUnavailableStatus({
|
|
137
|
+
targetPath: input.targetPath,
|
|
138
|
+
readiness: input.status.readiness,
|
|
139
|
+
worktree,
|
|
140
|
+
inventoryObservedAt: input.status.inventoryObservedAt,
|
|
141
|
+
projectConfigPresent: input.projectConfig !== null,
|
|
142
|
+
availability: "unavailable",
|
|
143
|
+
reasons: dedupe([
|
|
144
|
+
...stateLock.recoveredReasons,
|
|
145
|
+
"godot_editor_version_unsupported_for_lsp",
|
|
146
|
+
]),
|
|
147
|
+
summary: "The configured Godot editor version does not meet the minimum version required for the managed authoring.lsp. Upgrade to Godot 4.5.2 or later.",
|
|
148
|
+
versionFloorAdvisory: {
|
|
149
|
+
detectedVersion: expectedIdentity.editorVersion,
|
|
150
|
+
minimumVersion: GDH_MANAGED_LSP_MIN_GODOT_VERSION,
|
|
151
|
+
recommendedVersions: ["4.5.2", "4.6.x"],
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return await withLease(worktree, input.owner ?? "gdh lsp status", async (lease, activeLeaseCount) => {
|
|
156
|
+
let cleanedStaleInstance = false;
|
|
157
|
+
let cleanupReasons = [];
|
|
158
|
+
const existing = await readPersistedInstance(worktree);
|
|
159
|
+
if (existing !== null) {
|
|
160
|
+
const validation = await validatePersistedInstance(existing.instance, expectedIdentity);
|
|
161
|
+
if (validation.reusable) {
|
|
162
|
+
const reusedInstance = {
|
|
163
|
+
...existing.instance,
|
|
164
|
+
lastValidatedAt: new Date().toISOString(),
|
|
165
|
+
trust: validation.trust,
|
|
166
|
+
};
|
|
167
|
+
await persistInstance(worktree, {
|
|
168
|
+
...existing,
|
|
169
|
+
instance: reusedInstance,
|
|
170
|
+
rawPayload: validation.rawPayload ?? existing.rawPayload,
|
|
171
|
+
});
|
|
172
|
+
return createStatusResult({
|
|
173
|
+
targetPath: input.targetPath,
|
|
174
|
+
readiness: input.status.readiness,
|
|
175
|
+
availability: "available",
|
|
176
|
+
status: validation.trust === "trusted" ? "ready" : "degraded",
|
|
177
|
+
validationMode: existing.validationMode,
|
|
178
|
+
summary: validation.trust === "trusted"
|
|
179
|
+
? "Reused the managed authoring.lsp instance for this worktree."
|
|
180
|
+
: "Reused the managed authoring.lsp instance, but trust is degraded and the instance should be treated cautiously.",
|
|
181
|
+
reasons: dedupe([
|
|
182
|
+
...stateLock.recoveredReasons,
|
|
183
|
+
...(validation.trust === "trusted" ? [] : ["lsp_instance_degraded"]),
|
|
184
|
+
]),
|
|
185
|
+
worktree,
|
|
186
|
+
lease,
|
|
187
|
+
activeLeaseCount,
|
|
188
|
+
reusedInstance: true,
|
|
189
|
+
cleanedStaleInstance,
|
|
190
|
+
instance: reusedInstance,
|
|
191
|
+
inventoryObservedAt: input.status.inventoryObservedAt,
|
|
192
|
+
projectConfigPresent: input.projectConfig !== null,
|
|
193
|
+
rawPayload: validation.rawPayload ?? existing.rawPayload,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
cleanedStaleInstance = true;
|
|
197
|
+
cleanupReasons = validation.reasons;
|
|
198
|
+
await cleanupPersistedInstance(worktree, existing.instance, validation.reason, {
|
|
199
|
+
expectedIdentity,
|
|
136
200
|
});
|
|
137
|
-
|
|
201
|
+
// Phase 81 / LSP-04 / D-11 / Pitfall 7: when the LSP instance
|
|
202
|
+
// identity has changed, the broker snapshot referenced the
|
|
203
|
+
// now-defunct instance. Prune so the next read returns
|
|
204
|
+
// broker_metadata_missing instead of lsp_instance_identity_mismatch.
|
|
205
|
+
{
|
|
206
|
+
const { pruneAuthoringDiagnostics } = await import("./diagnostics-broker.js");
|
|
207
|
+
await pruneAuthoringDiagnostics({
|
|
208
|
+
targetPath: input.targetPath,
|
|
209
|
+
status: input.status,
|
|
210
|
+
projectConfig: input.projectConfig,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (input.launchPolicy === "reuse_existing") {
|
|
215
|
+
return createUnavailableStatus({
|
|
138
216
|
targetPath: input.targetPath,
|
|
139
217
|
readiness: input.status.readiness,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
? "Reused the managed authoring.lsp instance for this worktree."
|
|
145
|
-
: "Reused the managed authoring.lsp instance, but trust is degraded and the instance should be treated cautiously.",
|
|
218
|
+
worktree,
|
|
219
|
+
inventoryObservedAt: input.status.inventoryObservedAt,
|
|
220
|
+
projectConfigPresent: input.projectConfig !== null,
|
|
221
|
+
availability: lspCapability?.availability ?? "available",
|
|
146
222
|
reasons: dedupe([
|
|
147
223
|
...stateLock.recoveredReasons,
|
|
148
|
-
...(
|
|
224
|
+
...(cleanedStaleInstance ? ["stale_lsp_instance_cleaned"] : []),
|
|
225
|
+
...cleanupReasons,
|
|
226
|
+
"lsp_instance_not_running",
|
|
227
|
+
"post_edit_fast_path_did_not_launch_lsp",
|
|
149
228
|
]),
|
|
150
|
-
|
|
229
|
+
summary: "No reusable managed authoring.lsp instance is currently running; post-edit fast check did not launch Godot.",
|
|
151
230
|
lease,
|
|
152
231
|
activeLeaseCount,
|
|
153
|
-
reusedInstance: true,
|
|
154
232
|
cleanedStaleInstance,
|
|
155
|
-
|
|
233
|
+
rawPayload: {
|
|
234
|
+
launchPolicy: "reuse_existing",
|
|
235
|
+
projectPath,
|
|
236
|
+
cleanedStaleInstance,
|
|
237
|
+
cleanupReasons,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
const launchResult = await launchManagedInstance(worktree, projectPath);
|
|
242
|
+
if ("unavailable" in launchResult) {
|
|
243
|
+
return createUnavailableStatus({
|
|
244
|
+
targetPath: input.targetPath,
|
|
245
|
+
readiness: input.status.readiness,
|
|
246
|
+
worktree,
|
|
156
247
|
inventoryObservedAt: input.status.inventoryObservedAt,
|
|
157
248
|
projectConfigPresent: input.projectConfig !== null,
|
|
158
|
-
|
|
249
|
+
availability: "unavailable",
|
|
250
|
+
reasons: [
|
|
251
|
+
...stateLock.recoveredReasons,
|
|
252
|
+
...(cleanedStaleInstance ? ["stale_lsp_instance_cleaned"] : []),
|
|
253
|
+
...cleanupReasons,
|
|
254
|
+
...launchResult.unavailable.reasons,
|
|
255
|
+
],
|
|
256
|
+
summary: launchResult.unavailable.summary,
|
|
257
|
+
lease,
|
|
258
|
+
activeLeaseCount,
|
|
259
|
+
cleanedStaleInstance,
|
|
260
|
+
rawPayload: launchResult.unavailable.rawPayload,
|
|
159
261
|
});
|
|
160
262
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
263
|
+
const launchedInstance = {
|
|
264
|
+
instanceId: launchResult.instanceId,
|
|
265
|
+
launcher: launchResult.launcher,
|
|
266
|
+
port: launchResult.port,
|
|
267
|
+
pid: launchResult.pid,
|
|
268
|
+
projectPath,
|
|
269
|
+
command: launchResult.command,
|
|
270
|
+
trust: launchResult.trust,
|
|
271
|
+
launchedAt: new Date().toISOString(),
|
|
272
|
+
lastValidatedAt: new Date().toISOString(),
|
|
273
|
+
identity: launchResult.identity,
|
|
274
|
+
};
|
|
275
|
+
await persistInstance(worktree, {
|
|
276
|
+
schemaVersion: LSP_INSTANCE_SCHEMA_VERSION,
|
|
277
|
+
instance: launchedInstance,
|
|
278
|
+
validationMode: launchResult.validationMode,
|
|
279
|
+
diagnostics: launchResult.diagnostics,
|
|
280
|
+
rawPayload: launchResult.rawPayload,
|
|
165
281
|
});
|
|
166
|
-
|
|
167
|
-
const launchResult = await launchManagedInstance(worktree, projectPath);
|
|
168
|
-
if ("unavailable" in launchResult) {
|
|
169
|
-
return createUnavailableStatus({
|
|
282
|
+
return createStatusResult({
|
|
170
283
|
targetPath: input.targetPath,
|
|
171
284
|
readiness: input.status.readiness,
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
285
|
+
availability: "available",
|
|
286
|
+
status: launchResult.trust === "trusted" ? "ready" : "degraded",
|
|
287
|
+
validationMode: launchResult.validationMode,
|
|
288
|
+
summary: launchResult.trust === "trusted"
|
|
289
|
+
? "Started a managed authoring.lsp instance for this worktree."
|
|
290
|
+
: "Started a managed authoring.lsp instance, but trust is degraded and validation should be treated cautiously.",
|
|
291
|
+
reasons: dedupe([
|
|
177
292
|
...stateLock.recoveredReasons,
|
|
178
293
|
...(cleanedStaleInstance ? ["stale_lsp_instance_cleaned"] : []),
|
|
179
294
|
...cleanupReasons,
|
|
180
|
-
...launchResult.
|
|
181
|
-
],
|
|
182
|
-
|
|
295
|
+
...(launchResult.trust === "trusted" ? [] : ["lsp_instance_degraded"]),
|
|
296
|
+
]),
|
|
297
|
+
worktree,
|
|
183
298
|
lease,
|
|
184
299
|
activeLeaseCount,
|
|
300
|
+
reusedInstance: false,
|
|
185
301
|
cleanedStaleInstance,
|
|
186
|
-
|
|
302
|
+
instance: launchedInstance,
|
|
303
|
+
inventoryObservedAt: input.status.inventoryObservedAt,
|
|
304
|
+
projectConfigPresent: input.projectConfig !== null,
|
|
305
|
+
rawPayload: launchResult.rawPayload,
|
|
187
306
|
});
|
|
188
|
-
}
|
|
189
|
-
const launchedInstance = {
|
|
190
|
-
instanceId: launchResult.instanceId,
|
|
191
|
-
launcher: launchResult.launcher,
|
|
192
|
-
port: launchResult.port,
|
|
193
|
-
pid: launchResult.pid,
|
|
194
|
-
projectPath,
|
|
195
|
-
command: launchResult.command,
|
|
196
|
-
trust: launchResult.trust,
|
|
197
|
-
launchedAt: new Date().toISOString(),
|
|
198
|
-
lastValidatedAt: new Date().toISOString(),
|
|
199
|
-
identity: launchResult.identity,
|
|
200
|
-
};
|
|
201
|
-
await persistInstance(worktree, {
|
|
202
|
-
schemaVersion: LSP_INSTANCE_SCHEMA_VERSION,
|
|
203
|
-
instance: launchedInstance,
|
|
204
|
-
validationMode: launchResult.validationMode,
|
|
205
|
-
diagnostics: launchResult.diagnostics,
|
|
206
|
-
rawPayload: launchResult.rawPayload,
|
|
207
|
-
});
|
|
208
|
-
return createStatusResult({
|
|
209
|
-
targetPath: input.targetPath,
|
|
210
|
-
readiness: input.status.readiness,
|
|
211
|
-
availability: "available",
|
|
212
|
-
status: launchResult.trust === "trusted" ? "ready" : "degraded",
|
|
213
|
-
validationMode: launchResult.validationMode,
|
|
214
|
-
summary: launchResult.trust === "trusted"
|
|
215
|
-
? "Started a managed authoring.lsp instance for this worktree."
|
|
216
|
-
: "Started a managed authoring.lsp instance, but trust is degraded and validation should be treated cautiously.",
|
|
217
|
-
reasons: dedupe([
|
|
218
|
-
...stateLock.recoveredReasons,
|
|
219
|
-
...(cleanedStaleInstance ? ["stale_lsp_instance_cleaned"] : []),
|
|
220
|
-
...cleanupReasons,
|
|
221
|
-
...(launchResult.trust === "trusted" ? [] : ["lsp_instance_degraded"]),
|
|
222
|
-
]),
|
|
223
|
-
worktree,
|
|
224
|
-
lease,
|
|
225
|
-
activeLeaseCount,
|
|
226
|
-
reusedInstance: false,
|
|
227
|
-
cleanedStaleInstance,
|
|
228
|
-
instance: launchedInstance,
|
|
229
|
-
inventoryObservedAt: input.status.inventoryObservedAt,
|
|
230
|
-
projectConfigPresent: input.projectConfig !== null,
|
|
231
|
-
rawPayload: launchResult.rawPayload,
|
|
232
307
|
});
|
|
233
|
-
})
|
|
308
|
+
});
|
|
234
309
|
}
|
|
235
310
|
catch (error) {
|
|
236
311
|
if (error instanceof GdhManagedLspStateLockError) {
|
|
@@ -248,6 +323,36 @@ export async function getManagedLspStatus(input) {
|
|
|
248
323
|
throw error;
|
|
249
324
|
}
|
|
250
325
|
}
|
|
326
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
327
|
+
// Phase 82 / LSP-02 / LSP-07 — managed-LSP warmup surface.
|
|
328
|
+
//
|
|
329
|
+
// `warmupManagedLsp` is the in-process entry point that the post-edit hook
|
|
330
|
+
// detach-spawns (via `gdh lsp warmup`) and the MCP `authoring.warmup` tool
|
|
331
|
+
// invokes. It is idempotent: concurrent callers fan in via a single-attempt
|
|
332
|
+
// `<targetPath>/.gdh-state/lsp.lock` acquire so the underlying Godot launch
|
|
333
|
+
// happens at most once.
|
|
334
|
+
//
|
|
335
|
+
// The implementation lives in `./lsp-warmup.js`. That sibling module imports
|
|
336
|
+
// `getManagedLspStatus` from `./lsp.js`, which is the documented vitest
|
|
337
|
+
// workaround for the "vi.mock cannot intercept same-module internal calls"
|
|
338
|
+
// pitfall (https://vitest.dev/guide/mocking/modules — Internal Method Calls).
|
|
339
|
+
//
|
|
340
|
+
// IMPORTANT: this is a thin wrapper that DEFERS to the impl via dynamic
|
|
341
|
+
// `await import("./lsp-warmup.js")`. The deferral guarantees `lsp-warmup.ts`
|
|
342
|
+
// is NOT a static dependency of `lsp.ts`. Were it static (a re-export), it
|
|
343
|
+
// would be evaluated as part of `./lsp.js`'s module graph, with its
|
|
344
|
+
// `import { getManagedLspStatus } from "./lsp.js"` resolving against the
|
|
345
|
+
// partially-evaluated original module record — so the unit-test mock
|
|
346
|
+
// (`vi.mock("./lsp.js", ...)`) would never reach the warmup impl. By
|
|
347
|
+
// keeping it deferred, the test's `await import("./lsp-warmup.js")` is
|
|
348
|
+
// the FIRST point at which lsp-warmup.ts loads, and at that time the mock
|
|
349
|
+
// is already registered, so the `./lsp.js` import resolves to the mocked
|
|
350
|
+
// module. See `./lsp-warmup.ts` and `./lsp-warmup.test.ts` for context.
|
|
351
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
352
|
+
export async function warmupManagedLsp(input) {
|
|
353
|
+
const { warmupManagedLsp: impl } = await import("./lsp-warmup.js");
|
|
354
|
+
return impl(input);
|
|
355
|
+
}
|
|
251
356
|
export async function checkManagedLsp(input) {
|
|
252
357
|
const worktree = resolveWorktreeIdentity(input.targetPath, input.status, input.projectConfig);
|
|
253
358
|
return await withManagedLspLifecycleLock(worktree, "check", "gdh lsp check", async (stateLock) => {
|
|
@@ -370,6 +475,19 @@ export async function stopManagedLsp(input) {
|
|
|
370
475
|
killProcess: true,
|
|
371
476
|
expectedIdentity,
|
|
372
477
|
});
|
|
478
|
+
// Phase 81 / LSP-04 / D-11: prune the broker snapshot inside the lifecycle
|
|
479
|
+
// lock so concurrent readers cannot observe a stale snapshot pointing at
|
|
480
|
+
// the just-stopped LSP instance. The .primed marker is intentionally
|
|
481
|
+
// preserved (D-10) — broker absence remains "broker_metadata_missing"
|
|
482
|
+
// until the next refresh, distinct from "broker_not_yet_primed".
|
|
483
|
+
{
|
|
484
|
+
const { pruneAuthoringDiagnostics } = await import("./diagnostics-broker.js");
|
|
485
|
+
await pruneAuthoringDiagnostics({
|
|
486
|
+
targetPath: input.targetPath,
|
|
487
|
+
status: input.status,
|
|
488
|
+
projectConfig: input.projectConfig,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
373
491
|
return createLifecycleCommandResult({
|
|
374
492
|
targetPath: input.targetPath,
|
|
375
493
|
command: "stop",
|
|
@@ -820,7 +938,8 @@ async function tryBrokerDiagnosticsForPostEdit(input) {
|
|
|
820
938
|
return null;
|
|
821
939
|
}
|
|
822
940
|
const requestedUris = resolvedFiles.map((f) => pathToFileURL(f).href);
|
|
823
|
-
const
|
|
941
|
+
const currentContentHashesByUri = await hashFilesByUri(resolvedFiles);
|
|
942
|
+
const brokerResult = extractBrokerDiagnosticsIfFresh(snapshot, input.instance.instanceId, requestedUris, currentContentHashesByUri);
|
|
824
943
|
if (brokerResult === null) {
|
|
825
944
|
return null;
|
|
826
945
|
}
|
|
@@ -880,7 +999,7 @@ async function collectManagedGdscriptDiagnostics(input) {
|
|
|
880
999
|
const uri = pathToFileURL(filePath).href;
|
|
881
1000
|
const diagnosticsPromise = client.waitForDiagnostics({
|
|
882
1001
|
uri,
|
|
883
|
-
timeoutMs: resolveManagedLspDiagnosticWaitTimeoutMs(),
|
|
1002
|
+
timeoutMs: resolveManagedLspDiagnosticWaitTimeoutMs(input.mode),
|
|
884
1003
|
});
|
|
885
1004
|
await client.didOpen({
|
|
886
1005
|
uri,
|
|
@@ -1071,14 +1190,29 @@ function canonicalExistingPath(filePath) {
|
|
|
1071
1190
|
return path.resolve(filePath);
|
|
1072
1191
|
}
|
|
1073
1192
|
}
|
|
1074
|
-
function
|
|
1193
|
+
async function hashFilesByUri(filePaths) {
|
|
1194
|
+
const hashes = new Map();
|
|
1195
|
+
for (const filePath of filePaths) {
|
|
1196
|
+
const text = await fs.readFile(filePath, "utf8").catch(() => "");
|
|
1197
|
+
hashes.set(pathToFileURL(filePath).href, hashText(text));
|
|
1198
|
+
}
|
|
1199
|
+
return hashes;
|
|
1200
|
+
}
|
|
1201
|
+
function hashText(text) {
|
|
1202
|
+
return createHash("sha256").update(text).digest("hex");
|
|
1203
|
+
}
|
|
1204
|
+
function resolveManagedLspDiagnosticWaitTimeoutMs(mode) {
|
|
1075
1205
|
const configured = process.env["GDH_TEST_LSP_DIAGNOSTIC_WAIT_TIMEOUT_MS"];
|
|
1076
1206
|
if (configured === undefined) {
|
|
1077
|
-
return
|
|
1207
|
+
return mode === "post_edit"
|
|
1208
|
+
? MANAGED_LSP_POST_EDIT_DIAGNOSTIC_WAIT_TIMEOUT_MS
|
|
1209
|
+
: MANAGED_LSP_DIAGNOSTIC_WAIT_TIMEOUT_MS;
|
|
1078
1210
|
}
|
|
1079
1211
|
const parsed = Number(configured);
|
|
1080
1212
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
1081
|
-
return
|
|
1213
|
+
return mode === "post_edit"
|
|
1214
|
+
? MANAGED_LSP_POST_EDIT_DIAGNOSTIC_WAIT_TIMEOUT_MS
|
|
1215
|
+
: MANAGED_LSP_DIAGNOSTIC_WAIT_TIMEOUT_MS;
|
|
1082
1216
|
}
|
|
1083
1217
|
return parsed;
|
|
1084
1218
|
}
|
|
@@ -1102,13 +1236,20 @@ function resolveWorktreeIdentity(targetPath, status, projectConfig) {
|
|
|
1102
1236
|
async function resolveCurrentManagedLspIdentity(worktree, projectPath) {
|
|
1103
1237
|
const testMode = process.env["GDH_TEST_LSP_MODE"];
|
|
1104
1238
|
if (testMode) {
|
|
1239
|
+
const syntheticVersion = normalizeTestMode(testMode);
|
|
1240
|
+
// Phase 81 / LSP-08 / D-15: honor the version floor for synthetic test versions
|
|
1241
|
+
// so unit tests can exercise the floor-blocked path via GDH_TEST_LSP_MODE.
|
|
1242
|
+
const syntheticFloorViolation = typeof syntheticVersion === "string" &&
|
|
1243
|
+
(syntheticVersion.startsWith("4.5.0") || syntheticVersion.startsWith("4.5.1"));
|
|
1105
1244
|
return await createManagedLspInstanceIdentity({
|
|
1106
1245
|
worktree,
|
|
1107
1246
|
projectPath,
|
|
1108
1247
|
launcher: "test_fake",
|
|
1109
|
-
editorBinPath: `test_fake:${
|
|
1110
|
-
editorVersion:
|
|
1111
|
-
editorVersionReason:
|
|
1248
|
+
editorBinPath: `test_fake:${syntheticVersion}`,
|
|
1249
|
+
editorVersion: syntheticVersion,
|
|
1250
|
+
editorVersionReason: syntheticFloorViolation
|
|
1251
|
+
? "godot_editor_version_unsupported_for_lsp"
|
|
1252
|
+
: null,
|
|
1112
1253
|
});
|
|
1113
1254
|
}
|
|
1114
1255
|
const godotBin = await resolveConfiguredGodotEditorBin({
|
|
@@ -1116,15 +1257,25 @@ async function resolveCurrentManagedLspIdentity(worktree, projectPath) {
|
|
|
1116
1257
|
environment: process.env,
|
|
1117
1258
|
});
|
|
1118
1259
|
const version = godotBin === null ? null : await probeGodotEditorVersion(godotBin);
|
|
1260
|
+
const versionStr = version?.version ?? null;
|
|
1261
|
+
// Phase 81 / LSP-08 / D-15 / Pitfall 4: Godot reports versions like
|
|
1262
|
+
// "4.5.0.stable" or "4.5.1.stable.official"; use a startsWith prefix match
|
|
1263
|
+
// on the 3-part version prefix to handle every suffix form. No semver
|
|
1264
|
+
// dependency is added — the floor check is intentionally narrow.
|
|
1265
|
+
const isFloorViolation = versionStr !== null &&
|
|
1266
|
+
(versionStr.startsWith("4.5.0") || versionStr.startsWith("4.5.1"));
|
|
1267
|
+
const editorVersionReason = godotBin === null
|
|
1268
|
+
? "godot_editor_not_configured"
|
|
1269
|
+
: isFloorViolation
|
|
1270
|
+
? "godot_editor_version_unsupported_for_lsp"
|
|
1271
|
+
: (version?.reason ?? null);
|
|
1119
1272
|
return await createManagedLspInstanceIdentity({
|
|
1120
1273
|
worktree,
|
|
1121
1274
|
projectPath,
|
|
1122
1275
|
launcher: "godot_editor",
|
|
1123
1276
|
editorBinPath: godotBin,
|
|
1124
|
-
editorVersion:
|
|
1125
|
-
editorVersionReason
|
|
1126
|
-
? "godot_editor_not_configured"
|
|
1127
|
-
: (version?.reason ?? "godot_editor_version_unavailable"),
|
|
1277
|
+
editorVersion: versionStr,
|
|
1278
|
+
editorVersionReason,
|
|
1128
1279
|
});
|
|
1129
1280
|
}
|
|
1130
1281
|
async function createManagedLspInstanceIdentity(input) {
|
|
@@ -1976,6 +2127,7 @@ function createUnavailableStatus(input) {
|
|
|
1976
2127
|
stateRootPath: input.worktree.stateRootPath,
|
|
1977
2128
|
rawPayload: input.rawPayload ?? null,
|
|
1978
2129
|
},
|
|
2130
|
+
versionFloorAdvisory: input.versionFloorAdvisory ?? null,
|
|
1979
2131
|
};
|
|
1980
2132
|
}
|
|
1981
2133
|
function isPidAlive(pid) {
|