@emmaneugene/pi-cursor-sdk 0.3.7 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +99 -126
  3. package/dist/cursor-agents-context-registration.js +1 -3
  4. package/dist/cursor-agents-context.js +2 -2
  5. package/dist/cursor-config.js +2 -290
  6. package/dist/cursor-extension-factory-guard.js +31 -0
  7. package/dist/cursor-pi-tool-bridge-server.js +3 -0
  8. package/dist/cursor-pi-tool-bridge.js +5 -0
  9. package/dist/cursor-provider-errors.js +3 -58
  10. package/dist/cursor-provider-live-run-drain.js +0 -3
  11. package/dist/cursor-provider-run-finalizer.js +4 -7
  12. package/dist/cursor-provider-run-outcome.js +1 -1
  13. package/dist/cursor-provider-turn-finalize.js +3 -55
  14. package/dist/cursor-provider-turn-prepare.js +8 -144
  15. package/dist/cursor-provider-turn-runner.js +9 -23
  16. package/dist/cursor-provider-turn-send.js +8 -35
  17. package/dist/cursor-runtime-state.js +6 -268
  18. package/dist/cursor-sdk-billed-usage.js +3 -18
  19. package/dist/cursor-sdk-platform-package.js +88 -0
  20. package/dist/cursor-session-agent.js +18 -14
  21. package/dist/cursor-skill-tool.js +12 -22
  22. package/dist/cursor-state.js +5 -8
  23. package/dist/cursor-usage-accounting.js +1 -1
  24. package/dist/index.js +65 -49
  25. package/docs/cursor-dogfood-checklist.md +4 -4
  26. package/docs/cursor-live-smoke-checklist.md +24 -24
  27. package/docs/cursor-model-ux-spec.md +27 -30
  28. package/docs/cursor-native-tool-replay.md +4 -4
  29. package/docs/cursor-native-tool-visual-audit.md +10 -10
  30. package/docs/cursor-testing-lessons.md +38 -16
  31. package/docs/cursor-tool-surfaces.md +2 -4
  32. package/docs/platform-smoke-implementation.md +5 -5
  33. package/docs/platform-smoke.md +28 -73
  34. package/node_modules/cross-spawn/node_modules/which/CHANGELOG.md +166 -0
  35. package/package.json +3 -17
  36. package/platform-smoke.config.mjs +2 -1
  37. package/scripts/lib/cursor-visual-render.mjs +12 -2
  38. package/scripts/lib/local-resume-smoke-harness.mjs +0 -18
  39. package/scripts/platform-smoke/card-detect.mjs +1 -1
  40. package/scripts/platform-smoke/local-resume-runner.mjs +1 -1
  41. package/scripts/platform-smoke/scenarios.mjs +1 -1
  42. package/scripts/platform-smoke/targets.mjs +14 -5
  43. package/scripts/visual-tui-smoke-self-test.mjs +9 -33
  44. package/scripts/visual-tui-smoke.mjs +54 -11
  45. package/src/cursor-agents-context-registration.ts +0 -5
  46. package/src/cursor-agents-context.ts +1 -3
  47. package/src/cursor-config.ts +8 -379
  48. package/src/cursor-extension-factory-guard.ts +57 -0
  49. package/src/cursor-pi-tool-bridge-server.ts +4 -0
  50. package/src/cursor-pi-tool-bridge.ts +5 -0
  51. package/src/cursor-provider-errors.ts +2 -62
  52. package/src/cursor-provider-live-run-drain.ts +0 -3
  53. package/src/cursor-provider-run-finalizer.ts +4 -12
  54. package/src/cursor-provider-run-outcome.ts +0 -3
  55. package/src/cursor-provider-turn-finalize.ts +3 -61
  56. package/src/cursor-provider-turn-prepare.ts +8 -165
  57. package/src/cursor-provider-turn-runner.ts +15 -31
  58. package/src/cursor-provider-turn-send.ts +7 -38
  59. package/src/cursor-provider-turn-types.ts +9 -30
  60. package/src/cursor-runtime-state.ts +6 -345
  61. package/src/cursor-sdk-billed-usage.ts +3 -24
  62. package/src/cursor-sdk-platform-package.ts +106 -0
  63. package/src/cursor-session-agent.ts +16 -12
  64. package/src/cursor-skill-tool.ts +10 -26
  65. package/src/cursor-state.ts +5 -10
  66. package/src/cursor-usage-accounting.ts +1 -3
  67. package/src/index.ts +70 -49
  68. package/dist/cursor-cloud-lifecycle.js +0 -650
  69. package/dist/cursor-cloud-local-state.js +0 -460
  70. package/dist/cursor-cloud-options.js +0 -145
  71. package/dist/cursor-cloud-reporting.js +0 -222
  72. package/dist/cursor-ripgrep-path.js +0 -27
  73. package/scripts/cloud-runtime-smoke.d.mts +0 -42
  74. package/scripts/cloud-runtime-smoke.mjs +0 -623
  75. package/scripts/lib/cloud-smoke-artifacts.d.mts +0 -16
  76. package/scripts/lib/cloud-smoke-artifacts.mjs +0 -94
  77. package/scripts/lib/cloud-smoke-cleanup-evidence.d.mts +0 -209
  78. package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +0 -585
  79. package/scripts/lib/cloud-smoke-github.d.mts +0 -78
  80. package/scripts/lib/cloud-smoke-github.mjs +0 -331
  81. package/scripts/lib/cloud-smoke-pi-runner.d.mts +0 -42
  82. package/scripts/lib/cloud-smoke-pi-runner.mjs +0 -214
  83. package/scripts/lib/cloud-smoke-shutdown.d.mts +0 -67
  84. package/scripts/lib/cloud-smoke-shutdown.mjs +0 -133
  85. package/shared/cursor-cloud-lifecycle-constants.d.mts +0 -3
  86. package/shared/cursor-cloud-lifecycle-constants.mjs +0 -7
  87. package/src/cursor-cloud-lifecycle.ts +0 -733
  88. package/src/cursor-cloud-local-state.ts +0 -536
  89. package/src/cursor-cloud-options.ts +0 -182
  90. package/src/cursor-cloud-reporting.ts +0 -267
  91. package/src/cursor-ripgrep-path.ts +0 -32
@@ -1,585 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { lstatSync, readFileSync, readdirSync } from "node:fs";
3
- import { dirname, join, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { spawnSync } from "node:child_process";
6
- import { CLOUD_AGENT_ID_PATTERN } from "../../shared/cursor-cloud-lifecycle-constants.mjs";
7
- import { scrubSensitiveText } from "../../shared/cursor-sensitive-text.mjs";
8
-
9
- const DEFAULT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
10
- const CLOUD_RUN_ID_PATTERN = /^run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11
- const PACKAGE_SOURCE_SHA256_PATTERN = /^[a-f0-9]{64}$/;
12
-
13
- const LANE_NAMES = Object.freeze([
14
- "cancel",
15
- "explicit-https-repo-starting-ref-branch-pr-reporting",
16
- "lifecycle-delete",
17
- "direct-push-opt-in",
18
- "missing-branch-failure",
19
- "passive-artifacts-and-raw-usage",
20
- ]);
21
-
22
- function fail(message, details = "") {
23
- const error = new Error(message);
24
- error.details = details;
25
- error.isCloudSmokeFailure = true;
26
- throw error;
27
- }
28
-
29
- function requireObject(value, label) {
30
- if (!value || typeof value !== "object" || Array.isArray(value)) fail(`cloud smoke evidence ${label} must be an object`);
31
- return value;
32
- }
33
-
34
- function requireString(value, label) {
35
- if (typeof value !== "string" || value.length === 0) fail(`cloud smoke evidence ${label} must be a non-empty string`);
36
- return value;
37
- }
38
-
39
- function requireBoolean(value, label) {
40
- if (typeof value !== "boolean") fail(`cloud smoke evidence ${label} must be a boolean`);
41
- return value;
42
- }
43
-
44
- function requireTrue(value, label) {
45
- if (value !== true) fail(`cloud smoke evidence ${label} must be true`);
46
- return true;
47
- }
48
-
49
- function requireStatus(value, label) {
50
- if (value !== "passed") fail(`cloud smoke evidence ${label} must be "passed"`);
51
- return value;
52
- }
53
-
54
- function requireAgentId(value, label) {
55
- const agentId = requireString(value, label);
56
- if (!CLOUD_AGENT_ID_PATTERN.test(agentId)) fail(`cloud smoke evidence ${label} must be an exact cloud agent id`);
57
- return agentId;
58
- }
59
-
60
- function requireRunId(value, label) {
61
- const runId = requireString(value, label);
62
- if (!CLOUD_RUN_ID_PATTERN.test(runId)) fail(`cloud smoke evidence ${label} must be an exact cloud run id`);
63
- return runId;
64
- }
65
-
66
- function requireAgentIds(value, label) {
67
- if (!Array.isArray(value) || value.length > 5) fail(`cloud smoke evidence ${label} must be an array of at most 5 agent IDs`);
68
- const agentIds = value.map((agentId, index) => requireAgentId(agentId, `${label}[${index}]`));
69
- if (new Set(agentIds).size !== agentIds.length) fail(`cloud smoke evidence ${label} must contain unique agent IDs`);
70
- return agentIds;
71
- }
72
-
73
- function requireIsoTimestamp(value, label) {
74
- const timestamp = requireString(value, label);
75
- if (Number.isNaN(Date.parse(timestamp)) || new Date(timestamp).toISOString() !== timestamp) {
76
- fail(`cloud smoke evidence ${label} must be a canonical ISO timestamp`);
77
- }
78
- return timestamp;
79
- }
80
-
81
- function requirePackageSourceSha256(value, label) {
82
- const hash = requireString(value, label);
83
- if (!PACKAGE_SOURCE_SHA256_PATTERN.test(hash)) fail(`cloud smoke evidence ${label} must be a sha256 hex digest`);
84
- return hash;
85
- }
86
-
87
- function normalizeRelativePath(value) {
88
- const normalized = String(value).replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
89
- if (!normalized || normalized.startsWith("/") || normalized.split("/").some((part) => part === ".." || part === "")) {
90
- fail(`published package path is unsafe: ${value}`);
91
- }
92
- return normalized;
93
- }
94
-
95
- function projectBranches(report) {
96
- if (!report) return [];
97
- if (!Array.isArray(report.branches) || report.branches.length > 5) {
98
- fail("cloud smoke evidence report.branches must be an array of at most 5 entries when present");
99
- }
100
- return report.branches.map((branch, index) => {
101
- requireObject(branch, `report.branches[${index}]`);
102
- const branchName = branch.branch == null ? null : requireString(branch.branch, `report.branches[${index}].branch`);
103
- const prUrl = branch.prUrl == null ? null : requireString(branch.prUrl, `report.branches[${index}].prUrl`);
104
- if (branchName && branchName.length > 240) fail(`cloud smoke evidence report.branches[${index}].branch is too long`);
105
- if (prUrl && (prUrl.length > 500 || !prUrl.startsWith("https://github.com/"))) {
106
- fail(`cloud smoke evidence report.branches[${index}].prUrl must be a bounded GitHub URL`);
107
- }
108
- return { branch: branchName, prUrl };
109
- });
110
- }
111
-
112
- function artifactsObservedFromReport(report) {
113
- return Boolean(report && Array.isArray(report.artifacts) && report.artifacts.length > 0);
114
- }
115
-
116
- function rawUsageObservedFromReport(report) {
117
- return Boolean(report && report.usage && typeof report.usage === "object" && !Array.isArray(report.usage) && Object.keys(report.usage).length > 0);
118
- }
119
-
120
- function projectRawLane(lane, index) {
121
- const input = requireObject(lane, `lanes[${index}]`);
122
- const name = requireString(input.name, `lanes[${index}].name`);
123
- if (!LANE_NAMES.includes(name)) fail(`cloud smoke evidence unknown lane: ${name}`);
124
- const status = requireStatus(input.status, `lanes[${index}].status`);
125
- switch (name) {
126
- case "cancel":
127
- if (input.terminalStatus !== "cancelled") fail(`cloud smoke evidence lanes[${index}].terminalStatus must be "cancelled"`);
128
- if (input.runIdSource !== "metadata" && input.runIdSource !== "agent-list-runs") {
129
- fail(`cloud smoke evidence lanes[${index}].runIdSource must be metadata or agent-list-runs`);
130
- }
131
- return {
132
- name,
133
- status,
134
- agentId: requireAgentId(input.agentId, `lanes[${index}].agentId`),
135
- runId: requireRunId(input.runId, `lanes[${index}].runId`),
136
- runIdSource: input.runIdSource,
137
- terminalStatus: "cancelled",
138
- idsCapturedBeforeAbort: requireTrue(input.idsCapturedBeforeAbort, `lanes[${index}].idsCapturedBeforeAbort`),
139
- };
140
- case "explicit-https-repo-starting-ref-branch-pr-reporting":
141
- return {
142
- name,
143
- status,
144
- agentId: requireAgentId(input.agentId, `lanes[${index}].agentId`),
145
- runId: requireRunId(input.runId, `lanes[${index}].runId`),
146
- branchReportObserved: requireBoolean(input.branchReportObserved, `lanes[${index}].branchReportObserved`),
147
- startingRefAncestryVerified: requireTrue(input.startingRefAncestryVerified, `lanes[${index}].startingRefAncestryVerified`),
148
- remoteContentVerified: requireTrue(input.remoteContentVerified, `lanes[${index}].remoteContentVerified`),
149
- prUrlReturned: requireBoolean(input.prUrlReturned, `lanes[${index}].prUrlReturned`),
150
- branches: projectBranches(input.report),
151
- artifactsObserved: artifactsObservedFromReport(input.report),
152
- rawUsageObserved: rawUsageObservedFromReport(input.report),
153
- };
154
- case "lifecycle-delete":
155
- return {
156
- name,
157
- status,
158
- agentId: requireAgentId(input.agentId, `lanes[${index}].agentId`),
159
- runId: requireRunId(input.runId, `lanes[${index}].runId`),
160
- lifecycleDeleteVerified: requireTrue(input.lifecycleDeleteVerified, `lanes[${index}].lifecycleDeleteVerified`),
161
- };
162
- case "direct-push-opt-in":
163
- return {
164
- name,
165
- status,
166
- agentId: requireAgentId(input.agentId, `lanes[${index}].agentId`),
167
- runId: requireRunId(input.runId, `lanes[${index}].runId`),
168
- remoteContentChanged: requireTrue(input.remoteContentChanged, `lanes[${index}].remoteContentChanged`),
169
- branches: projectBranches(input.report),
170
- artifactsObserved: artifactsObservedFromReport(input.report),
171
- rawUsageObserved: rawUsageObservedFromReport(input.report),
172
- };
173
- case "missing-branch-failure":
174
- return {
175
- name,
176
- status,
177
- expectedFailureObserved: requireTrue(input.expectedFailureObserved, `lanes[${index}].expectedFailureObserved`),
178
- agentIds: requireAgentIds(input.agentIds, `lanes[${index}].agentIds`),
179
- };
180
- case "passive-artifacts-and-raw-usage":
181
- return {
182
- name,
183
- status,
184
- artifactsObserved: requireBoolean(input.artifactsObserved, `lanes[${index}].artifactsObserved`),
185
- rawUsageObserved: requireBoolean(input.rawUsageObserved, `lanes[${index}].rawUsageObserved`),
186
- observationsValidated: requireTrue(input.observationsValidated, `lanes[${index}].observationsValidated`),
187
- };
188
- default:
189
- fail(`cloud smoke evidence unknown lane: ${name}`);
190
- }
191
- }
192
-
193
- function projectCleanupEntry(entry, index) {
194
- const input = requireObject(entry, `cleanup[${index}]`);
195
- const agentId = requireAgentId(input.agentId, `cleanup[${index}].agentId`);
196
- if (input.alreadyDeleted === true) {
197
- if (input.archiveRequired !== false || input.deleted !== true || input.listExcluded !== true) {
198
- fail(`cloud smoke evidence cleanup[${index}] already-deleted proof is malformed`);
199
- }
200
- return {
201
- agentId,
202
- alreadyDeleted: true,
203
- archiveRequired: false,
204
- deleted: true,
205
- listExcluded: true,
206
- };
207
- }
208
- if (input.archived !== true || input.deleted !== true || input.listExcluded !== true) {
209
- fail(`cloud smoke evidence cleanup[${index}] archive-delete proof is malformed`);
210
- }
211
- return {
212
- agentId,
213
- archived: true,
214
- deleted: true,
215
- listExcluded: true,
216
- };
217
- }
218
-
219
- function projectThrowawayRepository(value) {
220
- const input = requireObject(value, "throwawayRepository");
221
- if (input.deleted !== true || input.httpStatus !== 404) {
222
- fail("cloud smoke evidence throwawayRepository must prove deleted:true with httpStatus 404");
223
- }
224
- const name = requireString(input.name, "throwawayRepository.name");
225
- if (!/^[^/]+\/pi-cursor-cloud-smoke-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(name)) {
226
- fail("cloud smoke evidence throwawayRepository.name must match the owned smoke repository shape");
227
- }
228
- return { name, deleted: true, httpStatus: 404 };
229
- }
230
-
231
- function projectProvenance(value) {
232
- const input = requireObject(value, "provenance");
233
- const extensionVersion = requireString(input.extensionVersion, "provenance.extensionVersion");
234
- const cursorSdkVersion = requireString(input.cursorSdkVersion, "provenance.cursorSdkVersion");
235
- const gitRevision = requireString(input.gitRevision, "provenance.gitRevision");
236
- if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(extensionVersion)) fail("cloud smoke evidence provenance.extensionVersion must be a version");
237
- if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(cursorSdkVersion)) fail("cloud smoke evidence provenance.cursorSdkVersion must be a version");
238
- if (!/^[a-f0-9]{40}$/.test(gitRevision)) fail("cloud smoke evidence provenance.gitRevision must be a full git revision");
239
- return {
240
- extensionVersion,
241
- cursorSdkVersion,
242
- gitRevision,
243
- packageSourceSha256: requirePackageSourceSha256(input.packageSourceSha256, "provenance.packageSourceSha256"),
244
- };
245
- }
246
-
247
- export function isAgentNotFound(error) {
248
- const text = `${error?.name ?? ""} ${error?.code ?? ""} ${error?.status ?? error?.statusCode ?? ""} ${error?.message ?? error ?? ""}`;
249
- return /AgentNotFound|agent_not_found|\b404\b/i.test(text);
250
- }
251
-
252
- export async function listCloudAgentIds(Agent, options = {}) {
253
- const apiKey = options.apiKey ?? process.env.CURSOR_API_KEY;
254
- const ids = new Set();
255
- const seenCursors = new Set();
256
- let cursor;
257
- do {
258
- if (cursor && seenCursors.has(cursor)) fail("Agent.list returned a repeated pagination cursor during cleanup verification");
259
- if (cursor) seenCursors.add(cursor);
260
- const page = await Agent.list({
261
- runtime: "cloud",
262
- includeArchived: true,
263
- apiKey,
264
- limit: 100,
265
- ...(cursor ? { cursor } : {}),
266
- });
267
- for (const item of page.items) if (CLOUD_AGENT_ID_PATTERN.test(item.agentId)) ids.add(item.agentId);
268
- cursor = page.nextCursor;
269
- } while (cursor);
270
- return ids;
271
- }
272
-
273
- export async function assertAgentListExcludes(Agent, agentId, options = {}) {
274
- if ((await listCloudAgentIds(Agent, options)).has(agentId)) fail(`deleted cloud agent ${agentId} remained in Agent.list`);
275
- }
276
-
277
- export async function assertAgentDeleted(Agent, agentId, options = {}) {
278
- const apiKey = options.apiKey ?? process.env.CURSOR_API_KEY;
279
- try {
280
- await Agent.get(agentId, { apiKey });
281
- fail(`Agent.get still returned deleted cloud agent ${agentId}`);
282
- } catch (error) {
283
- if (error?.isCloudSmokeFailure || !isAgentNotFound(error)) throw error;
284
- }
285
- await assertAgentListExcludes(Agent, agentId, options);
286
- }
287
-
288
- export async function cleanupCloudAgent(Agent, agentId, options = {}) {
289
- const apiKey = options.apiKey ?? process.env.CURSOR_API_KEY;
290
- let existing;
291
- try {
292
- existing = await Agent.get(agentId, { apiKey });
293
- } catch (error) {
294
- if (!isAgentNotFound(error)) throw error;
295
- await assertAgentListExcludes(Agent, agentId, options);
296
- return { agentId, alreadyDeleted: true, archiveRequired: false, deleted: true, listExcluded: true };
297
- }
298
- if (existing.archived !== true) await Agent.archive(agentId, { apiKey });
299
- const archived = await Agent.get(agentId, { apiKey });
300
- if (archived.archived !== true) fail(`cloud agent ${agentId} did not report archived:true`);
301
- await Agent.delete(agentId, { apiKey });
302
- await assertAgentDeleted(Agent, agentId, options);
303
- return { agentId, archived: true, deleted: true, listExcluded: true };
304
- }
305
-
306
- export function assertCloudSmokeEvidenceSafe(summary, apiKey = process.env.CURSOR_API_KEY) {
307
- const text = `${JSON.stringify(summary, null, 2)}\n`;
308
- if (scrubSensitiveText(text, apiKey) !== text) throw new Error("cloud smoke evidence failed canonical secret scan");
309
- if (/\b(?:authorization|cookie|api[_-]?key|prompt|rawOutput|stderr|stdout)\b/i.test(text)) {
310
- throw new Error("cloud smoke evidence contains a forbidden raw/credential field");
311
- }
312
- return text;
313
- }
314
-
315
- function validatePersistedLane(lane, index) {
316
- const input = requireObject(lane, `lanes[${index}]`);
317
- if (input.name === "explicit-https-repo-starting-ref-branch-pr-reporting") {
318
- return projectRawLane({
319
- name: input.name,
320
- status: input.status,
321
- agentId: input.agentId,
322
- runId: input.runId,
323
- branchReportObserved: input.branchReportObserved,
324
- startingRefAncestryVerified: input.startingRefAncestryVerified,
325
- remoteContentVerified: input.remoteContentVerified,
326
- prUrlReturned: input.prUrlReturned,
327
- report: {
328
- branches: input.branches,
329
- artifacts: requireBoolean(input.artifactsObserved, `lanes[${index}].artifactsObserved`) ? [{}] : [],
330
- usage: requireBoolean(input.rawUsageObserved, `lanes[${index}].rawUsageObserved`) ? { observed: true } : {},
331
- },
332
- }, index);
333
- }
334
- if (input.name === "direct-push-opt-in") {
335
- return projectRawLane({
336
- name: input.name,
337
- status: input.status,
338
- agentId: input.agentId,
339
- runId: input.runId,
340
- remoteContentChanged: input.remoteContentChanged,
341
- report: {
342
- branches: input.branches,
343
- artifacts: requireBoolean(input.artifactsObserved, `lanes[${index}].artifactsObserved`) ? [{}] : [],
344
- usage: requireBoolean(input.rawUsageObserved, `lanes[${index}].rawUsageObserved`) ? { observed: true } : {},
345
- },
346
- }, index);
347
- }
348
- return projectRawLane(input, index);
349
- }
350
-
351
- function buildCloudSmokeMatrixEvidence(input, laneProjector, allowDefaultTimestamp = false) {
352
- const data = requireObject(input, "root");
353
- const {
354
- model,
355
- lanes,
356
- cleanup,
357
- throwawayRepository,
358
- provenance,
359
- schemaVersion = 1,
360
- } = data;
361
- const timestamp = data.timestamp ?? (allowDefaultTimestamp ? new Date().toISOString() : undefined);
362
- if (schemaVersion !== 1) fail("cloud smoke evidence schemaVersion must be 1");
363
- if (model !== "cursor/grok-4.6") fail("cloud smoke evidence model must be cursor/grok-4.6");
364
- if (!Array.isArray(lanes) || lanes.length !== LANE_NAMES.length) fail("cloud smoke evidence lanes must contain the complete required matrix");
365
- if (!Array.isArray(cleanup) || cleanup.length === 0) fail("cloud smoke evidence cleanup must be a non-empty array");
366
- const projectedLanes = lanes.map((lane, index) => laneProjector(lane, index));
367
- if (new Set(projectedLanes.map((lane) => lane.name)).size !== LANE_NAMES.length) {
368
- fail("cloud smoke evidence lanes must contain every required lane exactly once");
369
- }
370
- const projectedCleanup = cleanup.map((entry, index) => projectCleanupEntry(entry, index));
371
- const cleanupAgentIds = new Set(projectedCleanup.map((entry) => entry.agentId));
372
- if (cleanupAgentIds.size !== projectedCleanup.length) fail("cloud smoke evidence cleanup agent IDs must be unique");
373
- for (const lane of projectedLanes) {
374
- for (const agentId of [lane.agentId, ...(lane.agentIds ?? [])].filter(Boolean)) {
375
- if (!cleanupAgentIds.has(agentId)) fail(`cloud smoke evidence cleanup is missing lane agent ${agentId}`);
376
- }
377
- }
378
- return {
379
- schemaVersion: 1,
380
- timestamp: requireIsoTimestamp(timestamp, "timestamp"),
381
- model,
382
- provenance: projectProvenance(provenance),
383
- lanes: projectedLanes,
384
- cleanup: projectedCleanup,
385
- throwawayRepository: projectThrowawayRepository(throwawayRepository),
386
- };
387
- }
388
-
389
- export function projectCloudSmokeMatrixEvidence(input = {}) {
390
- return buildCloudSmokeMatrixEvidence(input, projectRawLane, true);
391
- }
392
-
393
- export function validateCloudSmokeMatrixEvidence(input) {
394
- return buildCloudSmokeMatrixEvidence(input, validatePersistedLane);
395
- }
396
-
397
- export function listCloudSmokePackageSourcePaths(options = {}) {
398
- const root = resolve(options.root ?? DEFAULT_ROOT);
399
- const readFile = options.readFileSync ?? readFileSync;
400
- const lstat = options.lstatSync ?? lstatSync;
401
- const readdir = options.readdirSync ?? readdirSync;
402
- const packageJson = JSON.parse(readFile(join(root, "package.json"), "utf8"));
403
- if (!Array.isArray(packageJson.files)) fail("package.json files must be an array");
404
- const seeds = ["package.json", ...packageJson.files.map((entry) => normalizeRelativePath(entry))];
405
- const paths = new Set();
406
-
407
- const visit = (relativePath) => {
408
- const absolutePath = join(root, ...relativePath.split("/"));
409
- let stats;
410
- try {
411
- stats = lstat(absolutePath);
412
- } catch (error) {
413
- fail(`published package path is unreadable: ${relativePath}`, error instanceof Error ? error.message : String(error));
414
- }
415
- if (stats.isSymbolicLink()) fail(`published package path must not be a symlink: ${relativePath}`);
416
- if (stats.isFile()) {
417
- paths.add(relativePath);
418
- return;
419
- }
420
- if (!stats.isDirectory()) fail(`published package path must be a regular file or directory: ${relativePath}`);
421
- let entries;
422
- try {
423
- entries = readdir(absolutePath, { withFileTypes: true });
424
- } catch (error) {
425
- fail(`published package directory is unreadable: ${relativePath}`, error instanceof Error ? error.message : String(error));
426
- }
427
- for (const entry of entries) {
428
- const childRelative = `${relativePath}/${entry.name}`;
429
- if (entry.isSymbolicLink()) fail(`published package path must not be a symlink: ${childRelative}`);
430
- if (entry.isDirectory()) {
431
- visit(childRelative);
432
- continue;
433
- }
434
- if (!entry.isFile()) fail(`published package path must be a regular file or directory: ${childRelative}`);
435
- const childAbsolute = join(absolutePath, entry.name);
436
- let childStats;
437
- try {
438
- childStats = lstat(childAbsolute);
439
- } catch (error) {
440
- fail(`published package path is unreadable: ${childRelative}`, error instanceof Error ? error.message : String(error));
441
- }
442
- if (childStats.isSymbolicLink() || !childStats.isFile()) {
443
- fail(`published package path must be a regular file: ${childRelative}`);
444
- }
445
- paths.add(childRelative);
446
- }
447
- };
448
-
449
- for (const seed of seeds) visit(seed);
450
- return [...paths].sort((left, right) => left.localeCompare(right));
451
- }
452
-
453
- export function buildCloudSmokeEvidenceProvenance(options = {}) {
454
- const root = resolve(options.root ?? DEFAULT_ROOT);
455
- const readFile = options.readFileSync ?? readFileSync;
456
- const packageSourcePaths = options.packageSourcePaths ?? listCloudSmokePackageSourcePaths(options);
457
- const hash = createHash("sha256");
458
- for (const relativePath of packageSourcePaths) {
459
- const normalized = normalizeRelativePath(relativePath);
460
- hash.update(normalized);
461
- hash.update("\0");
462
- hash.update(readFile(join(root, ...normalized.split("/"))));
463
- hash.update("\0");
464
- }
465
- const extensionPackage = JSON.parse(readFile(join(root, "package.json"), "utf8"));
466
- const sdkPackage = JSON.parse(readFile(join(root, "node_modules", "@cursor", "sdk", "package.json"), "utf8"));
467
- let gitRevision = options.gitRevision;
468
- if (gitRevision === undefined) {
469
- const run = options.spawnSync ?? spawnSync;
470
- const result = run("git", ["rev-parse", "HEAD"], {
471
- cwd: root,
472
- encoding: "utf8",
473
- env: options.env ?? process.env,
474
- timeout: options.timeoutMs ?? 30_000,
475
- });
476
- if (result.error || result.status !== 0) {
477
- fail(
478
- "cloud smoke provenance could not read git revision",
479
- [result.error?.message, result.stderr, result.stdout].filter(Boolean).join("\n"),
480
- );
481
- }
482
- gitRevision = String(result.stdout ?? "").trim();
483
- }
484
- if (!gitRevision) fail("cloud smoke provenance git revision was empty");
485
- return {
486
- extensionVersion: extensionPackage.version,
487
- cursorSdkVersion: sdkPackage.version,
488
- gitRevision,
489
- packageSourceSha256: hash.digest("hex"),
490
- };
491
- }
492
-
493
- /**
494
- * Offline release-gate resource coordinator:
495
- * run state -> harvest IDs -> cleanup every ID -> repo cleanup -> evidence only on complete success.
496
- */
497
- export async function coordinateCloudSmokeReleaseGate(callbacks = {}) {
498
- const run = callbacks.run;
499
- const harvestAgentIds = callbacks.harvestAgentIds;
500
- const cleanupAgent = callbacks.cleanupAgent;
501
- const cleanupRepository = callbacks.cleanupRepository;
502
- const writeEvidence = callbacks.writeEvidence;
503
- const captureInterruption = () => {
504
- try {
505
- callbacks.throwIfInterrupted?.();
506
- } catch (error) {
507
- failure ??= error;
508
- }
509
- };
510
- if (typeof run !== "function") fail("coordinateCloudSmokeReleaseGate requires run()");
511
- if (typeof harvestAgentIds !== "function") fail("coordinateCloudSmokeReleaseGate requires harvestAgentIds()");
512
- if (typeof cleanupAgent !== "function") fail("coordinateCloudSmokeReleaseGate requires cleanupAgent()");
513
- if (typeof writeEvidence !== "function") fail("coordinateCloudSmokeReleaseGate requires writeEvidence()");
514
-
515
- const state = { lanes: [], repository: undefined };
516
- let failure;
517
- const cleanup = [];
518
- let throwawayRepository;
519
-
520
- try {
521
- await run(state);
522
- } catch (error) {
523
- failure = error;
524
- }
525
- captureInterruption();
526
-
527
- let agentIds = [];
528
- try {
529
- agentIds = [...await harvestAgentIds(state)];
530
- } catch (error) {
531
- failure ??= error;
532
- }
533
- captureInterruption();
534
-
535
- for (const agentId of agentIds) {
536
- try {
537
- cleanup.push(await cleanupAgent(agentId));
538
- } catch (error) {
539
- callbacks.onAgentCleanupError?.(error, agentId);
540
- failure ??= error;
541
- }
542
- captureInterruption();
543
- }
544
-
545
- if (state.repository) {
546
- if (typeof cleanupRepository !== "function") {
547
- failure ??= new Error("coordinateCloudSmokeReleaseGate requires cleanupRepository() when a repository is established");
548
- } else {
549
- try {
550
- const proof = await cleanupRepository(state.repository);
551
- throwawayRepository = {
552
- name: state.repository.fullName,
553
- deleted: proof?.deleted === true,
554
- httpStatus: proof?.httpStatus,
555
- };
556
- } catch (error) {
557
- callbacks.onRepositoryCleanupError?.(error, state.repository);
558
- failure ??= error;
559
- }
560
- }
561
- }
562
-
563
- captureInterruption();
564
- if (!failure) {
565
- try {
566
- await writeEvidence({
567
- lanes: state.lanes,
568
- cleanup,
569
- throwawayRepository,
570
- repository: state.repository,
571
- });
572
- } catch (error) {
573
- failure = error;
574
- }
575
- captureInterruption();
576
- }
577
-
578
- if (failure) throw failure;
579
- return {
580
- lanes: state.lanes,
581
- cleanup,
582
- throwawayRepository,
583
- repository: state.repository,
584
- };
585
- }
@@ -1,78 +0,0 @@
1
- export const CLOUD_SMOKE_REPO_NAME_PREFIX: "pi-cursor-cloud-smoke-";
2
- export const CLOUD_SMOKE_OWNERSHIP_TOKEN_PATTERN: RegExp;
3
-
4
- export type CloudSmokeOwnedRepository = {
5
- fullName: string;
6
- repoUrl: string;
7
- seedDir: string;
8
- ownershipToken: string;
9
- description: string;
10
- };
11
-
12
- export function cloudSmokeRepositoryDescription(ownershipToken: string): string;
13
-
14
- export function assertOwnedThrowawayRepositoryHandle(repo: unknown): {
15
- fullName: string;
16
- ownershipToken: string;
17
- description: string;
18
- repoUrl: string;
19
- seedDir?: string;
20
- };
21
-
22
- export function runTimedCommand(
23
- commandName: string,
24
- commandArgs: readonly string[],
25
- options?: {
26
- cwd?: string;
27
- env?: NodeJS.ProcessEnv;
28
- label?: string;
29
- maxBuffer?: number;
30
- timeoutMs?: number;
31
- spawnSync?: typeof import("node:child_process").spawnSync;
32
- },
33
- ): string;
34
-
35
- export function authenticatedGitArgs(commandArgs: readonly string[]): string[];
36
-
37
- export function normalizeCloudSmokeGitHubRepo(value: unknown): string | undefined;
38
-
39
- export function createThrowawayRepository(
40
- artifactRoot: string,
41
- onOwned?: (repo: CloudSmokeOwnedRepository) => void,
42
- options?: {
43
- cwd?: string;
44
- randomUUID?: () => string;
45
- runCommand?: (commandName: string, commandArgs: readonly string[], options?: Record<string, unknown>) => string;
46
- spawnSync?: typeof import("node:child_process").spawnSync;
47
- writeFileSync?: typeof import("node:fs").writeFileSync;
48
- env?: NodeJS.ProcessEnv;
49
- timeoutMs?: number;
50
- verificationAttempts?: number;
51
- verificationDelayMs?: number;
52
- sleep?: (milliseconds: number) => unknown;
53
- },
54
- ): CloudSmokeOwnedRepository;
55
-
56
- export function deleteThrowawayRepository(
57
- repo: CloudSmokeOwnedRepository | { fullName: string; ownershipToken: string; description: string; repoUrl?: string; seedDir?: string },
58
- options?: {
59
- cwd?: string;
60
- env?: NodeJS.ProcessEnv;
61
- timeoutMs?: number;
62
- verificationAttempts?: number;
63
- verificationDelayMs?: number;
64
- sleep?: (milliseconds: number) => unknown;
65
- runCommand?: (commandName: string, commandArgs: readonly string[], options?: Record<string, unknown>) => string;
66
- spawnSync?: typeof import("node:child_process").spawnSync;
67
- },
68
- ): { deleted: true; httpStatus: 404 };
69
-
70
- export function validatePrUrl(
71
- repo: { fullName: string },
72
- prUrl: string,
73
- options?: {
74
- cwd?: string;
75
- runCommand?: (commandName: string, commandArgs: readonly string[], options?: Record<string, unknown>) => string;
76
- spawnSync?: typeof import("node:child_process").spawnSync;
77
- },
78
- ): { url: string; state: string };