@brainervirus/workit-cursor 8.14.0 → 8.15.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/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +107 -27
- package/dist/mcp-server.js +108 -23
- package/dist/workit-hook.js +109 -24
- package/package.json +3 -3
- package/skills/workit-ship/SKILL.md +2 -2
|
@@ -7624,6 +7624,16 @@ var init_zod = __esm(() => {
|
|
|
7624
7624
|
|
|
7625
7625
|
// packages/workit-core/src/core/task-contract.ts
|
|
7626
7626
|
import { createHash, randomUUID } from "node:crypto";
|
|
7627
|
+
function resolveIdRef(kind, ref, ids) {
|
|
7628
|
+
if (ids.includes(ref))
|
|
7629
|
+
return success(null, null, ref);
|
|
7630
|
+
const matches = ids.filter((candidate) => candidate.startsWith(ref));
|
|
7631
|
+
if (matches.length === 1)
|
|
7632
|
+
return success(null, null, matches[0]);
|
|
7633
|
+
if (matches.length === 0)
|
|
7634
|
+
return failure2("not_found", `${kind} not found`);
|
|
7635
|
+
return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
|
|
7636
|
+
}
|
|
7627
7637
|
function parseOperation(family, input) {
|
|
7628
7638
|
if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
|
|
7629
7639
|
return failure2("unsupported_version", "unsupported schema version", { operation: family });
|
|
@@ -7726,7 +7736,7 @@ var SCHEMA_VERSION = 1, POLICY_VERSION = "1.0.0", OPERATION_FAMILIES, uuidPatter
|
|
|
7726
7736
|
return true;
|
|
7727
7737
|
}
|
|
7728
7738
|
return false;
|
|
7729
|
-
}, text, id, digest, revision, validUtc = (value) => {
|
|
7739
|
+
}, text, id, idRefPattern, idRef, digest, revision, validUtc = (value) => {
|
|
7730
7740
|
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?Z$/.exec(value);
|
|
7731
7741
|
if (!match)
|
|
7732
7742
|
return false;
|
|
@@ -7909,6 +7919,8 @@ var init_task_contract = __esm(() => {
|
|
|
7909
7919
|
ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
|
|
7910
7920
|
});
|
|
7911
7921
|
id = text.regex(uuidPattern, "expected lowercase UUID");
|
|
7922
|
+
idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
|
|
7923
|
+
idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
|
|
7912
7924
|
digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
|
|
7913
7925
|
revision = text.regex(uuidPattern, "expected UUID revision");
|
|
7914
7926
|
utcSchema = text.check((ctx) => {
|
|
@@ -8177,7 +8189,7 @@ var init_task_contract = __esm(() => {
|
|
|
8177
8189
|
outcome: _enum(["completed", "failed", "cancelled"]),
|
|
8178
8190
|
summary: text,
|
|
8179
8191
|
evidenceIds: array(id),
|
|
8180
|
-
findingIds: array(
|
|
8192
|
+
findingIds: array(idRef)
|
|
8181
8193
|
}).strict();
|
|
8182
8194
|
workerSchema = object({
|
|
8183
8195
|
assignment: assignmentSchema,
|
|
@@ -8369,7 +8381,7 @@ var init_task_contract = __esm(() => {
|
|
|
8369
8381
|
action: literal("resolve"),
|
|
8370
8382
|
...taskId,
|
|
8371
8383
|
expectedRevision: revision.optional(),
|
|
8372
|
-
findingId:
|
|
8384
|
+
findingId: idRef,
|
|
8373
8385
|
disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
|
|
8374
8386
|
reason: text,
|
|
8375
8387
|
evidenceIds: array(id),
|
|
@@ -8410,7 +8422,7 @@ var init_task_contract = __esm(() => {
|
|
|
8410
8422
|
...taskId,
|
|
8411
8423
|
expectedRevision: revision.optional(),
|
|
8412
8424
|
expectedWorkspaceRevision: revision.optional(),
|
|
8413
|
-
workerId:
|
|
8425
|
+
workerId: idRef,
|
|
8414
8426
|
report: workerReportSchema
|
|
8415
8427
|
}),
|
|
8416
8428
|
cancel: operation({
|
|
@@ -8418,7 +8430,7 @@ var init_task_contract = __esm(() => {
|
|
|
8418
8430
|
...taskId,
|
|
8419
8431
|
...revisions,
|
|
8420
8432
|
expectedWorkspaceRevision: revision.optional(),
|
|
8421
|
-
workerId:
|
|
8433
|
+
workerId: idRef,
|
|
8422
8434
|
reason: text
|
|
8423
8435
|
})
|
|
8424
8436
|
};
|
|
@@ -9707,20 +9719,19 @@ var init_workspaces = __esm(() => {
|
|
|
9707
9719
|
union([boolean2(), literal("verified")]).optional()
|
|
9708
9720
|
]))).passthrough().optional(),
|
|
9709
9721
|
youtrack: object({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
|
|
9710
|
-
issues: object({
|
|
9722
|
+
issues: object({
|
|
9723
|
+
provider: _enum(["github", "gitlab", "youtrack"]).optional(),
|
|
9724
|
+
link: boolean2().optional(),
|
|
9725
|
+
relation: unknown().optional(),
|
|
9726
|
+
backlink: boolean2().optional(),
|
|
9727
|
+
link_on_pr: boolean2().optional()
|
|
9728
|
+
}).passthrough().optional(),
|
|
9711
9729
|
branchPolicy: workspaceBranchPolicySchema.optional(),
|
|
9712
9730
|
commitPolicy: workspaceCommitPolicySchema.optional(),
|
|
9713
9731
|
defaultProfile: nonBlank.optional(),
|
|
9714
9732
|
profiles: record(nonBlank, workspaceProfileSchema).optional(),
|
|
9715
9733
|
releaseTracks: record(nonBlank, releaseTrackSchema).optional()
|
|
9716
9734
|
}).passthrough().superRefine((workspace, context) => {
|
|
9717
|
-
if (workspace.issues && workspace.vcs?.provider !== "github") {
|
|
9718
|
-
context.addIssue({
|
|
9719
|
-
code: "custom",
|
|
9720
|
-
path: ["issues"],
|
|
9721
|
-
message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
|
|
9722
|
-
});
|
|
9723
|
-
}
|
|
9724
9735
|
if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
|
|
9725
9736
|
context.addIssue({
|
|
9726
9737
|
code: "custom",
|
|
@@ -10157,6 +10168,40 @@ var init_release_tracks = __esm(() => {
|
|
|
10157
10168
|
import fs4 from "node:fs";
|
|
10158
10169
|
import path5 from "node:path";
|
|
10159
10170
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
10171
|
+
function issueTrackerOf(issues, youtrack) {
|
|
10172
|
+
const iss = asRecord(issues);
|
|
10173
|
+
const yt = asRecord(youtrack);
|
|
10174
|
+
const warnings = [];
|
|
10175
|
+
const named = typeof iss.provider === "string" && TRACKERS.has(iss.provider.toLowerCase()) ? iss.provider.toLowerCase() : null;
|
|
10176
|
+
if (iss.relation !== undefined && iss.relation !== "closes" && iss.relation !== "refs")
|
|
10177
|
+
warnings.push(`issues.relation ${JSON.stringify(iss.relation)} is not closes or refs; read as closes`);
|
|
10178
|
+
for (const key of ["repo", "host"])
|
|
10179
|
+
if (iss[key] !== undefined && text2(iss[key]) === null)
|
|
10180
|
+
warnings.push(`issues.${key} is not a non-empty string; ignored`);
|
|
10181
|
+
const common = {
|
|
10182
|
+
relation: iss.relation === "refs" ? "refs" : "closes",
|
|
10183
|
+
backlink: iss.backlink === true,
|
|
10184
|
+
baseUrl: text2(yt.baseUrl),
|
|
10185
|
+
repo: text2(iss.repo)?.replace(/^\/+|\/+$/gu, "") ?? null,
|
|
10186
|
+
host: text2(iss.host)?.toLowerCase() ?? null,
|
|
10187
|
+
warnings
|
|
10188
|
+
};
|
|
10189
|
+
const legacyGithub = named === "github" && flag(iss.link) === null;
|
|
10190
|
+
if (flag(yt.link_issues) === true && (named === null || legacyGithub))
|
|
10191
|
+
return { provider: "youtrack", link: true, ...common, source: "youtrack.link_issues" };
|
|
10192
|
+
if (named) {
|
|
10193
|
+
const link = flag(iss.link) ?? flag(iss.link_on_pr) ?? (named === "youtrack" ? flag(yt.link_issues) : null) ?? false;
|
|
10194
|
+
return {
|
|
10195
|
+
provider: named,
|
|
10196
|
+
link,
|
|
10197
|
+
...common,
|
|
10198
|
+
source: flag(iss.link) === null && flag(iss.link_on_pr) !== null ? "issues.link_on_pr" : "issues"
|
|
10199
|
+
};
|
|
10200
|
+
}
|
|
10201
|
+
if (common.baseUrl)
|
|
10202
|
+
return { provider: "youtrack", link: false, ...common, source: "youtrack.baseUrl" };
|
|
10203
|
+
return null;
|
|
10204
|
+
}
|
|
10160
10205
|
function vcsConfig(mode, cwd, options = {}) {
|
|
10161
10206
|
const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
|
|
10162
10207
|
const wsVcs = ws?.vcs ?? {};
|
|
@@ -10168,6 +10213,7 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
10168
10213
|
const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
|
|
10169
10214
|
const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
|
|
10170
10215
|
const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
|
|
10216
|
+
const issueTracker = issueTrackerOf(wsIssues, wsYt);
|
|
10171
10217
|
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
10172
10218
|
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
10173
10219
|
let issuesProvider = null;
|
|
@@ -10200,7 +10246,8 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
10200
10246
|
link_issues: linkIssues,
|
|
10201
10247
|
youtrack_base_url: youtrackBaseUrl,
|
|
10202
10248
|
issues_provider: issuesProvider,
|
|
10203
|
-
link_on_pr: linkOnPr
|
|
10249
|
+
link_on_pr: linkOnPr,
|
|
10250
|
+
issue_tracker: issueTracker
|
|
10204
10251
|
};
|
|
10205
10252
|
}
|
|
10206
10253
|
if (cfgStatus === "malformed") {
|
|
@@ -10226,7 +10273,8 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
10226
10273
|
link_issues: linkIssues,
|
|
10227
10274
|
youtrack_base_url: youtrackBaseUrl,
|
|
10228
10275
|
issues_provider: issuesProvider,
|
|
10229
|
-
link_on_pr: linkOnPr
|
|
10276
|
+
link_on_pr: linkOnPr,
|
|
10277
|
+
issue_tracker: issueTracker
|
|
10230
10278
|
};
|
|
10231
10279
|
if (provider === "gitlab") {
|
|
10232
10280
|
out.gitlab = {
|
|
@@ -10266,7 +10314,7 @@ var workspacesPath = () => path5.join(configDir(), "workspaces.json"), vcsCwd =
|
|
|
10266
10314
|
return { status: "valid", path: file, config: parsed };
|
|
10267
10315
|
}
|
|
10268
10316
|
return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
|
|
10269
|
-
}, trackSummary = (resolution) => resolution.status === "resolved" ? {
|
|
10317
|
+
}, TRACKERS, asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {}, flag = (value) => typeof value === "boolean" ? value : null, text2 = (value) => typeof value === "string" && value.trim() ? value.trim() : null, trackSummary = (resolution) => resolution.status === "resolved" ? {
|
|
10270
10318
|
name: resolution.track?.name ?? null,
|
|
10271
10319
|
source: resolution.source,
|
|
10272
10320
|
detail: resolution.detail,
|
|
@@ -10279,6 +10327,7 @@ var init_vcs_config = __esm(() => {
|
|
|
10279
10327
|
init_config();
|
|
10280
10328
|
init_workspaces();
|
|
10281
10329
|
init_release_tracks();
|
|
10330
|
+
TRACKERS = new Set(["github", "gitlab", "youtrack"]);
|
|
10282
10331
|
});
|
|
10283
10332
|
|
|
10284
10333
|
// packages/workit-core/src/core/logger.ts
|
|
@@ -10399,7 +10448,7 @@ function migrateRepoIssued(cwd) {
|
|
|
10399
10448
|
removeIfEmpty(old);
|
|
10400
10449
|
}
|
|
10401
10450
|
function recordIssuedSession(cwd, lead, issued) {
|
|
10402
|
-
const token = randomBytes(18).toString("
|
|
10451
|
+
const token = randomBytes(18).toString("hex");
|
|
10403
10452
|
try {
|
|
10404
10453
|
migrateRepoIssued(cwd);
|
|
10405
10454
|
const own = hash(issued);
|
|
@@ -11233,7 +11282,12 @@ var init_ledger = __esm(() => {
|
|
|
11233
11282
|
PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
11234
11283
|
FAILING = new Set(["failed", "blocked"]);
|
|
11235
11284
|
STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
11236
|
-
PR_ROW_TYPES = new Set([
|
|
11285
|
+
PR_ROW_TYPES = new Set([
|
|
11286
|
+
"pr.created",
|
|
11287
|
+
"pr.status",
|
|
11288
|
+
"pr.merged",
|
|
11289
|
+
"pr.linked"
|
|
11290
|
+
]);
|
|
11237
11291
|
NETWORK_FS_TYPES = new Set([
|
|
11238
11292
|
26985,
|
|
11239
11293
|
20859,
|
|
@@ -11948,12 +12002,12 @@ function normalizeJudgment(raw, previous) {
|
|
|
11948
12002
|
const items = Array.isArray(value) ? value : [value];
|
|
11949
12003
|
judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
|
|
11950
12004
|
} else {
|
|
11951
|
-
if (name === "needsPlan" && typeof value === "string" && !
|
|
12005
|
+
if (name === "needsPlan" && typeof value === "string" && !flag2(value, false).ok && value.trim()) {
|
|
11952
12006
|
judgment.needsPlan = true;
|
|
11953
12007
|
judgment.refs.push(refOf(value.trim()));
|
|
11954
12008
|
continue;
|
|
11955
12009
|
}
|
|
11956
|
-
const parsed =
|
|
12010
|
+
const parsed = flag2(value, name === "behaviorChange");
|
|
11957
12011
|
if (parsed.ok)
|
|
11958
12012
|
judgment[name] = parsed.value;
|
|
11959
12013
|
else
|
|
@@ -11977,7 +12031,7 @@ function normalizeJudgment(raw, previous) {
|
|
|
11977
12031
|
});
|
|
11978
12032
|
return success(null, null, { judgment: parsed.data, ignored, legacy });
|
|
11979
12033
|
}
|
|
11980
|
-
var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), squash = (key) => key.toLowerCase().replace(/[_-]/g, ""), ALIASES, LEGACY_KEYS, RISK, YES, NO,
|
|
12034
|
+
var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), squash = (key) => key.toLowerCase().replace(/[_-]/g, ""), ALIASES, LEGACY_KEYS, RISK, YES, NO, flag2 = (value, unknownAs) => {
|
|
11981
12035
|
if (typeof value === "boolean")
|
|
11982
12036
|
return { ok: true, value };
|
|
11983
12037
|
if (typeof value === "number" && (value === 0 || value === 1))
|
|
@@ -13462,20 +13516,26 @@ class TaskStore {
|
|
|
13462
13516
|
return success(null, null, { key: key.data, bound: bound.data });
|
|
13463
13517
|
}
|
|
13464
13518
|
taskForWorker(workerId) {
|
|
13465
|
-
if (typeof workerId !== "string" || !
|
|
13519
|
+
if (typeof workerId !== "string" || !idRefPattern.test(workerId))
|
|
13466
13520
|
return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
|
|
13467
13521
|
const listed = this.listTaskIndex();
|
|
13468
13522
|
if (!listed.ok)
|
|
13469
13523
|
return listed;
|
|
13524
|
+
const owners = new Map;
|
|
13470
13525
|
for (const entry of listed.data) {
|
|
13471
13526
|
if (entry.status === "closed")
|
|
13472
13527
|
continue;
|
|
13473
13528
|
const loaded = this.load(entry.id);
|
|
13474
13529
|
if (!loaded.ok)
|
|
13475
13530
|
return loaded;
|
|
13476
|
-
|
|
13477
|
-
|
|
13531
|
+
for (const worker of loaded.data?.record.workers ?? [])
|
|
13532
|
+
owners.set(worker.id, entry.id);
|
|
13478
13533
|
}
|
|
13534
|
+
const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
|
|
13535
|
+
if (resolved.ok)
|
|
13536
|
+
return success(null, null, owners.get(resolved.data) ?? null);
|
|
13537
|
+
if (resolved.code !== "not_found")
|
|
13538
|
+
return resolved;
|
|
13479
13539
|
return success(null, null, null);
|
|
13480
13540
|
}
|
|
13481
13541
|
originTaskEntry() {
|
|
@@ -16734,6 +16794,10 @@ class WorkitCore {
|
|
|
16734
16794
|
}
|
|
16735
16795
|
if (helper?.ok)
|
|
16736
16796
|
return failure2("permission_denied", "helpers cannot resolve findings");
|
|
16797
|
+
const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
|
|
16798
|
+
if (!findingRef.ok)
|
|
16799
|
+
return findingRef;
|
|
16800
|
+
input.findingId = findingRef.data;
|
|
16737
16801
|
const finding = task.data.findings.find((entry) => entry.id === input.findingId);
|
|
16738
16802
|
if (!finding)
|
|
16739
16803
|
return failure2("not_found", "finding not found");
|
|
@@ -16885,15 +16949,31 @@ class WorkitCore {
|
|
|
16885
16949
|
return changed;
|
|
16886
16950
|
return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
|
|
16887
16951
|
}
|
|
16952
|
+
const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
|
|
16953
|
+
if (!workerRef.ok)
|
|
16954
|
+
return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
|
|
16955
|
+
input.workerId = workerRef.data;
|
|
16888
16956
|
const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
|
|
16889
16957
|
if (!entry)
|
|
16890
16958
|
return failure2("not_found", "worker not found");
|
|
16891
16959
|
if (input.action === "report") {
|
|
16892
16960
|
if (helperId === null || helperId !== input.workerId)
|
|
16893
|
-
return failure2("permission_denied",
|
|
16961
|
+
return failure2("permission_denied", ONLY_OWN_REPORT);
|
|
16894
16962
|
const helper = this.helperEntry(task.data, true, true);
|
|
16895
16963
|
if (!helper.ok)
|
|
16896
16964
|
return helper;
|
|
16965
|
+
const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
|
|
16966
|
+
const reported = [];
|
|
16967
|
+
for (const findingRef of input.report.findingIds) {
|
|
16968
|
+
const found = resolveIdRef("finding", findingRef, ownFindings);
|
|
16969
|
+
if (found.ok)
|
|
16970
|
+
reported.push(found.data);
|
|
16971
|
+
else if (found.code === "not_found")
|
|
16972
|
+
reported.push(findingRef);
|
|
16973
|
+
else
|
|
16974
|
+
return found;
|
|
16975
|
+
}
|
|
16976
|
+
input.report.findingIds = reported;
|
|
16897
16977
|
const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
|
|
16898
16978
|
const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
|
|
16899
16979
|
if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
|
|
@@ -16921,7 +17001,7 @@ class WorkitCore {
|
|
|
16921
17001
|
return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
|
|
16922
17002
|
}
|
|
16923
17003
|
if (helperId !== null)
|
|
16924
|
-
return failure2("permission_denied",
|
|
17004
|
+
return failure2("permission_denied", NO_HELPER_CONTROL);
|
|
16925
17005
|
if (task.data.status === "closed")
|
|
16926
17006
|
return failure2("invalid_transition", "closed task cannot cancel workers");
|
|
16927
17007
|
if (entry.data.state === "stopped") {
|
|
@@ -17450,7 +17530,7 @@ var MAX_JUDGED_REFS = 5, workspaceVerification = (root) => {
|
|
|
17450
17530
|
return changed;
|
|
17451
17531
|
const updated = changed.data.task.workers.find((candidate) => candidate.id === input.workerId);
|
|
17452
17532
|
return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during lifecycle observation");
|
|
17453
|
-
}, refsWithinScope = (refs, scope) => refs.every((ref) => ref.kind !== "file" || scopeCovers(scope, { description: "", paths: [ref.path], exclusions: [] })), TASK_SCOPED_ACTIONS, WORKER_ADDRESSED_ACTIONS, workerCommand = (taskId, request) => {
|
|
17533
|
+
}, refsWithinScope = (refs, scope) => refs.every((ref) => ref.kind !== "file" || scopeCovers(scope, { description: "", paths: [ref.path], exclusions: [] })), TASK_SCOPED_ACTIONS, ONLY_OWN_REPORT = "workers can submit only their own report", NO_HELPER_CONTROL = "helpers cannot cancel or control workers", WORKER_ADDRESSED_ACTIONS, workerCommand = (taskId, request) => {
|
|
17454
17534
|
const { action, workerId, reason, report } = request;
|
|
17455
17535
|
const rest = JSON.stringify({ reason, report });
|
|
17456
17536
|
const payload = rest === "{}" ? "" : ` --payload '${rest.replace(/'/gu, "'\\''")}'`;
|
package/dist/mcp-server.js
CHANGED
|
@@ -16617,6 +16617,8 @@ var text = string2().check((ctx) => {
|
|
|
16617
16617
|
ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
|
|
16618
16618
|
});
|
|
16619
16619
|
var id = text.regex(uuidPattern, "expected lowercase UUID");
|
|
16620
|
+
var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
|
|
16621
|
+
var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
|
|
16620
16622
|
var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
|
|
16621
16623
|
var revision = text.regex(uuidPattern, "expected UUID revision");
|
|
16622
16624
|
var validUtc = (value) => {
|
|
@@ -16994,7 +16996,7 @@ var workerReportSchema = object2({
|
|
|
16994
16996
|
outcome: _enum(["completed", "failed", "cancelled"]),
|
|
16995
16997
|
summary: text,
|
|
16996
16998
|
evidenceIds: array(id),
|
|
16997
|
-
findingIds: array(
|
|
16999
|
+
findingIds: array(idRef)
|
|
16998
17000
|
}).strict();
|
|
16999
17001
|
var workerSchema = object2({
|
|
17000
17002
|
assignment: assignmentSchema,
|
|
@@ -17240,7 +17242,7 @@ var findingOperations = {
|
|
|
17240
17242
|
action: literal("resolve"),
|
|
17241
17243
|
...taskId,
|
|
17242
17244
|
expectedRevision: revision.optional(),
|
|
17243
|
-
findingId:
|
|
17245
|
+
findingId: idRef,
|
|
17244
17246
|
disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
|
|
17245
17247
|
reason: text,
|
|
17246
17248
|
evidenceIds: array(id),
|
|
@@ -17281,7 +17283,7 @@ var workerOperations = {
|
|
|
17281
17283
|
...taskId,
|
|
17282
17284
|
expectedRevision: revision.optional(),
|
|
17283
17285
|
expectedWorkspaceRevision: revision.optional(),
|
|
17284
|
-
workerId:
|
|
17286
|
+
workerId: idRef,
|
|
17285
17287
|
report: workerReportSchema
|
|
17286
17288
|
}),
|
|
17287
17289
|
cancel: operation({
|
|
@@ -17289,7 +17291,7 @@ var workerOperations = {
|
|
|
17289
17291
|
...taskId,
|
|
17290
17292
|
...revisions,
|
|
17291
17293
|
expectedWorkspaceRevision: revision.optional(),
|
|
17292
|
-
workerId:
|
|
17294
|
+
workerId: idRef,
|
|
17293
17295
|
reason: text
|
|
17294
17296
|
})
|
|
17295
17297
|
};
|
|
@@ -17339,6 +17341,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
|
|
|
17339
17341
|
data
|
|
17340
17342
|
});
|
|
17341
17343
|
var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
|
|
17344
|
+
function resolveIdRef(kind, ref, ids) {
|
|
17345
|
+
if (ids.includes(ref))
|
|
17346
|
+
return success(null, null, ref);
|
|
17347
|
+
const matches = ids.filter((candidate) => candidate.startsWith(ref));
|
|
17348
|
+
if (matches.length === 1)
|
|
17349
|
+
return success(null, null, matches[0]);
|
|
17350
|
+
if (matches.length === 0)
|
|
17351
|
+
return failure2("not_found", `${kind} not found`);
|
|
17352
|
+
return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
|
|
17353
|
+
}
|
|
17342
17354
|
function parseOperation(family, input) {
|
|
17343
17355
|
if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
|
|
17344
17356
|
return failure2("unsupported_version", "unsupported schema version", { operation: family });
|
|
@@ -19857,20 +19869,26 @@ class TaskStore {
|
|
|
19857
19869
|
return success(null, null, { key: key.data, bound: bound.data });
|
|
19858
19870
|
}
|
|
19859
19871
|
taskForWorker(workerId) {
|
|
19860
|
-
if (typeof workerId !== "string" || !
|
|
19872
|
+
if (typeof workerId !== "string" || !idRefPattern.test(workerId))
|
|
19861
19873
|
return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
|
|
19862
19874
|
const listed = this.listTaskIndex();
|
|
19863
19875
|
if (!listed.ok)
|
|
19864
19876
|
return listed;
|
|
19877
|
+
const owners = new Map;
|
|
19865
19878
|
for (const entry of listed.data) {
|
|
19866
19879
|
if (entry.status === "closed")
|
|
19867
19880
|
continue;
|
|
19868
19881
|
const loaded = this.load(entry.id);
|
|
19869
19882
|
if (!loaded.ok)
|
|
19870
19883
|
return loaded;
|
|
19871
|
-
|
|
19872
|
-
|
|
19884
|
+
for (const worker of loaded.data?.record.workers ?? [])
|
|
19885
|
+
owners.set(worker.id, entry.id);
|
|
19873
19886
|
}
|
|
19887
|
+
const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
|
|
19888
|
+
if (resolved.ok)
|
|
19889
|
+
return success(null, null, owners.get(resolved.data) ?? null);
|
|
19890
|
+
if (resolved.code !== "not_found")
|
|
19891
|
+
return resolved;
|
|
19874
19892
|
return success(null, null, null);
|
|
19875
19893
|
}
|
|
19876
19894
|
originTaskEntry() {
|
|
@@ -21990,20 +22008,19 @@ var workspaceConfigSchema = object2({
|
|
|
21990
22008
|
union([boolean2(), literal("verified")]).optional()
|
|
21991
22009
|
]))).passthrough().optional(),
|
|
21992
22010
|
youtrack: object2({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
|
|
21993
|
-
issues: object2({
|
|
22011
|
+
issues: object2({
|
|
22012
|
+
provider: _enum(["github", "gitlab", "youtrack"]).optional(),
|
|
22013
|
+
link: boolean2().optional(),
|
|
22014
|
+
relation: unknown().optional(),
|
|
22015
|
+
backlink: boolean2().optional(),
|
|
22016
|
+
link_on_pr: boolean2().optional()
|
|
22017
|
+
}).passthrough().optional(),
|
|
21994
22018
|
branchPolicy: workspaceBranchPolicySchema.optional(),
|
|
21995
22019
|
commitPolicy: workspaceCommitPolicySchema.optional(),
|
|
21996
22020
|
defaultProfile: nonBlank.optional(),
|
|
21997
22021
|
profiles: record(nonBlank, workspaceProfileSchema).optional(),
|
|
21998
22022
|
releaseTracks: record(nonBlank, releaseTrackSchema).optional()
|
|
21999
22023
|
}).passthrough().superRefine((workspace, context) => {
|
|
22000
|
-
if (workspace.issues && workspace.vcs?.provider !== "github") {
|
|
22001
|
-
context.addIssue({
|
|
22002
|
-
code: "custom",
|
|
22003
|
-
path: ["issues"],
|
|
22004
|
-
message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
|
|
22005
|
-
});
|
|
22006
|
-
}
|
|
22007
22024
|
if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
|
|
22008
22025
|
context.addIssue({
|
|
22009
22026
|
code: "custom",
|
|
@@ -22843,6 +22860,44 @@ var readVcsConfig = () => {
|
|
|
22843
22860
|
}
|
|
22844
22861
|
return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
|
|
22845
22862
|
};
|
|
22863
|
+
var TRACKERS = new Set(["github", "gitlab", "youtrack"]);
|
|
22864
|
+
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
22865
|
+
var flag = (value) => typeof value === "boolean" ? value : null;
|
|
22866
|
+
var text2 = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
22867
|
+
function issueTrackerOf(issues, youtrack) {
|
|
22868
|
+
const iss = asRecord(issues);
|
|
22869
|
+
const yt = asRecord(youtrack);
|
|
22870
|
+
const warnings = [];
|
|
22871
|
+
const named = typeof iss.provider === "string" && TRACKERS.has(iss.provider.toLowerCase()) ? iss.provider.toLowerCase() : null;
|
|
22872
|
+
if (iss.relation !== undefined && iss.relation !== "closes" && iss.relation !== "refs")
|
|
22873
|
+
warnings.push(`issues.relation ${JSON.stringify(iss.relation)} is not closes or refs; read as closes`);
|
|
22874
|
+
for (const key of ["repo", "host"])
|
|
22875
|
+
if (iss[key] !== undefined && text2(iss[key]) === null)
|
|
22876
|
+
warnings.push(`issues.${key} is not a non-empty string; ignored`);
|
|
22877
|
+
const common = {
|
|
22878
|
+
relation: iss.relation === "refs" ? "refs" : "closes",
|
|
22879
|
+
backlink: iss.backlink === true,
|
|
22880
|
+
baseUrl: text2(yt.baseUrl),
|
|
22881
|
+
repo: text2(iss.repo)?.replace(/^\/+|\/+$/gu, "") ?? null,
|
|
22882
|
+
host: text2(iss.host)?.toLowerCase() ?? null,
|
|
22883
|
+
warnings
|
|
22884
|
+
};
|
|
22885
|
+
const legacyGithub = named === "github" && flag(iss.link) === null;
|
|
22886
|
+
if (flag(yt.link_issues) === true && (named === null || legacyGithub))
|
|
22887
|
+
return { provider: "youtrack", link: true, ...common, source: "youtrack.link_issues" };
|
|
22888
|
+
if (named) {
|
|
22889
|
+
const link = flag(iss.link) ?? flag(iss.link_on_pr) ?? (named === "youtrack" ? flag(yt.link_issues) : null) ?? false;
|
|
22890
|
+
return {
|
|
22891
|
+
provider: named,
|
|
22892
|
+
link,
|
|
22893
|
+
...common,
|
|
22894
|
+
source: flag(iss.link) === null && flag(iss.link_on_pr) !== null ? "issues.link_on_pr" : "issues"
|
|
22895
|
+
};
|
|
22896
|
+
}
|
|
22897
|
+
if (common.baseUrl)
|
|
22898
|
+
return { provider: "youtrack", link: false, ...common, source: "youtrack.baseUrl" };
|
|
22899
|
+
return null;
|
|
22900
|
+
}
|
|
22846
22901
|
var trackSummary = (resolution) => resolution.status === "resolved" ? {
|
|
22847
22902
|
name: resolution.track?.name ?? null,
|
|
22848
22903
|
source: resolution.source,
|
|
@@ -22863,6 +22918,7 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
22863
22918
|
const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
|
|
22864
22919
|
const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
|
|
22865
22920
|
const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
|
|
22921
|
+
const issueTracker = issueTrackerOf(wsIssues, wsYt);
|
|
22866
22922
|
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
22867
22923
|
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
22868
22924
|
let issuesProvider = null;
|
|
@@ -22895,7 +22951,8 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
22895
22951
|
link_issues: linkIssues,
|
|
22896
22952
|
youtrack_base_url: youtrackBaseUrl,
|
|
22897
22953
|
issues_provider: issuesProvider,
|
|
22898
|
-
link_on_pr: linkOnPr
|
|
22954
|
+
link_on_pr: linkOnPr,
|
|
22955
|
+
issue_tracker: issueTracker
|
|
22899
22956
|
};
|
|
22900
22957
|
}
|
|
22901
22958
|
if (cfgStatus === "malformed") {
|
|
@@ -22921,7 +22978,8 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
22921
22978
|
link_issues: linkIssues,
|
|
22922
22979
|
youtrack_base_url: youtrackBaseUrl,
|
|
22923
22980
|
issues_provider: issuesProvider,
|
|
22924
|
-
link_on_pr: linkOnPr
|
|
22981
|
+
link_on_pr: linkOnPr,
|
|
22982
|
+
issue_tracker: issueTracker
|
|
22925
22983
|
};
|
|
22926
22984
|
if (provider === "gitlab") {
|
|
22927
22985
|
out.gitlab = {
|
|
@@ -23025,7 +23083,12 @@ var MAX_LINE_BYTES = 4096;
|
|
|
23025
23083
|
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
23026
23084
|
var FAILING = new Set(["failed", "blocked"]);
|
|
23027
23085
|
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
23028
|
-
var PR_ROW_TYPES = new Set([
|
|
23086
|
+
var PR_ROW_TYPES = new Set([
|
|
23087
|
+
"pr.created",
|
|
23088
|
+
"pr.status",
|
|
23089
|
+
"pr.merged",
|
|
23090
|
+
"pr.linked"
|
|
23091
|
+
]);
|
|
23029
23092
|
var err = (code, error, unblock) => ({
|
|
23030
23093
|
ok: false,
|
|
23031
23094
|
code,
|
|
@@ -25162,7 +25225,7 @@ var RISK = {
|
|
|
25162
25225
|
};
|
|
25163
25226
|
var YES = new Set(["true", "yes", "y", "1", "on"]);
|
|
25164
25227
|
var NO = new Set(["false", "no", "n", "0", "off", "none"]);
|
|
25165
|
-
var
|
|
25228
|
+
var flag2 = (value, unknownAs) => {
|
|
25166
25229
|
if (typeof value === "boolean")
|
|
25167
25230
|
return { ok: true, value };
|
|
25168
25231
|
if (typeof value === "number" && (value === 0 || value === 1))
|
|
@@ -25228,12 +25291,12 @@ function normalizeJudgment(raw, previous) {
|
|
|
25228
25291
|
const items = Array.isArray(value) ? value : [value];
|
|
25229
25292
|
judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
|
|
25230
25293
|
} else {
|
|
25231
|
-
if (name === "needsPlan" && typeof value === "string" && !
|
|
25294
|
+
if (name === "needsPlan" && typeof value === "string" && !flag2(value, false).ok && value.trim()) {
|
|
25232
25295
|
judgment.needsPlan = true;
|
|
25233
25296
|
judgment.refs.push(refOf(value.trim()));
|
|
25234
25297
|
continue;
|
|
25235
25298
|
}
|
|
25236
|
-
const parsed =
|
|
25299
|
+
const parsed = flag2(value, name === "behaviorChange");
|
|
25237
25300
|
if (parsed.ok)
|
|
25238
25301
|
judgment[name] = parsed.value;
|
|
25239
25302
|
else
|
|
@@ -25588,6 +25651,8 @@ var TASK_SCOPED_ACTIONS = new Set([
|
|
|
25588
25651
|
"worker.cancel",
|
|
25589
25652
|
"state.export"
|
|
25590
25653
|
]);
|
|
25654
|
+
var ONLY_OWN_REPORT = "workers can submit only their own report";
|
|
25655
|
+
var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
|
|
25591
25656
|
var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
|
|
25592
25657
|
var workerCommand = (taskId, request) => {
|
|
25593
25658
|
const { action, workerId, reason, report } = request;
|
|
@@ -26485,6 +26550,10 @@ class WorkitCore {
|
|
|
26485
26550
|
}
|
|
26486
26551
|
if (helper?.ok)
|
|
26487
26552
|
return failure2("permission_denied", "helpers cannot resolve findings");
|
|
26553
|
+
const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
|
|
26554
|
+
if (!findingRef.ok)
|
|
26555
|
+
return findingRef;
|
|
26556
|
+
input.findingId = findingRef.data;
|
|
26488
26557
|
const finding = task.data.findings.find((entry) => entry.id === input.findingId);
|
|
26489
26558
|
if (!finding)
|
|
26490
26559
|
return failure2("not_found", "finding not found");
|
|
@@ -26636,15 +26705,31 @@ class WorkitCore {
|
|
|
26636
26705
|
return changed;
|
|
26637
26706
|
return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
|
|
26638
26707
|
}
|
|
26708
|
+
const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
|
|
26709
|
+
if (!workerRef.ok)
|
|
26710
|
+
return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
|
|
26711
|
+
input.workerId = workerRef.data;
|
|
26639
26712
|
const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
|
|
26640
26713
|
if (!entry)
|
|
26641
26714
|
return failure2("not_found", "worker not found");
|
|
26642
26715
|
if (input.action === "report") {
|
|
26643
26716
|
if (helperId === null || helperId !== input.workerId)
|
|
26644
|
-
return failure2("permission_denied",
|
|
26717
|
+
return failure2("permission_denied", ONLY_OWN_REPORT);
|
|
26645
26718
|
const helper = this.helperEntry(task.data, true, true);
|
|
26646
26719
|
if (!helper.ok)
|
|
26647
26720
|
return helper;
|
|
26721
|
+
const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
|
|
26722
|
+
const reported = [];
|
|
26723
|
+
for (const findingRef of input.report.findingIds) {
|
|
26724
|
+
const found = resolveIdRef("finding", findingRef, ownFindings);
|
|
26725
|
+
if (found.ok)
|
|
26726
|
+
reported.push(found.data);
|
|
26727
|
+
else if (found.code === "not_found")
|
|
26728
|
+
reported.push(findingRef);
|
|
26729
|
+
else
|
|
26730
|
+
return found;
|
|
26731
|
+
}
|
|
26732
|
+
input.report.findingIds = reported;
|
|
26648
26733
|
const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
|
|
26649
26734
|
const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
|
|
26650
26735
|
if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
|
|
@@ -26672,7 +26757,7 @@ class WorkitCore {
|
|
|
26672
26757
|
return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
|
|
26673
26758
|
}
|
|
26674
26759
|
if (helperId !== null)
|
|
26675
|
-
return failure2("permission_denied",
|
|
26760
|
+
return failure2("permission_denied", NO_HELPER_CONTROL);
|
|
26676
26761
|
if (task.data.status === "closed")
|
|
26677
26762
|
return failure2("invalid_transition", "closed task cannot cancel workers");
|
|
26678
26763
|
if (entry.data.state === "stopped") {
|
package/dist/workit-hook.js
CHANGED
|
@@ -7548,6 +7548,8 @@ var text = string2().check((ctx) => {
|
|
|
7548
7548
|
ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
|
|
7549
7549
|
});
|
|
7550
7550
|
var id = text.regex(uuidPattern, "expected lowercase UUID");
|
|
7551
|
+
var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
|
|
7552
|
+
var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
|
|
7551
7553
|
var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
|
|
7552
7554
|
var revision = text.regex(uuidPattern, "expected UUID revision");
|
|
7553
7555
|
var validUtc = (value) => {
|
|
@@ -7925,7 +7927,7 @@ var workerReportSchema = object({
|
|
|
7925
7927
|
outcome: _enum(["completed", "failed", "cancelled"]),
|
|
7926
7928
|
summary: text,
|
|
7927
7929
|
evidenceIds: array(id),
|
|
7928
|
-
findingIds: array(
|
|
7930
|
+
findingIds: array(idRef)
|
|
7929
7931
|
}).strict();
|
|
7930
7932
|
var workerSchema = object({
|
|
7931
7933
|
assignment: assignmentSchema,
|
|
@@ -8171,7 +8173,7 @@ var findingOperations = {
|
|
|
8171
8173
|
action: literal("resolve"),
|
|
8172
8174
|
...taskId,
|
|
8173
8175
|
expectedRevision: revision.optional(),
|
|
8174
|
-
findingId:
|
|
8176
|
+
findingId: idRef,
|
|
8175
8177
|
disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
|
|
8176
8178
|
reason: text,
|
|
8177
8179
|
evidenceIds: array(id),
|
|
@@ -8212,7 +8214,7 @@ var workerOperations = {
|
|
|
8212
8214
|
...taskId,
|
|
8213
8215
|
expectedRevision: revision.optional(),
|
|
8214
8216
|
expectedWorkspaceRevision: revision.optional(),
|
|
8215
|
-
workerId:
|
|
8217
|
+
workerId: idRef,
|
|
8216
8218
|
report: workerReportSchema
|
|
8217
8219
|
}),
|
|
8218
8220
|
cancel: operation({
|
|
@@ -8220,7 +8222,7 @@ var workerOperations = {
|
|
|
8220
8222
|
...taskId,
|
|
8221
8223
|
...revisions,
|
|
8222
8224
|
expectedWorkspaceRevision: revision.optional(),
|
|
8223
|
-
workerId:
|
|
8225
|
+
workerId: idRef,
|
|
8224
8226
|
reason: text
|
|
8225
8227
|
})
|
|
8226
8228
|
};
|
|
@@ -8270,6 +8272,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
|
|
|
8270
8272
|
data
|
|
8271
8273
|
});
|
|
8272
8274
|
var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
|
|
8275
|
+
function resolveIdRef(kind, ref, ids) {
|
|
8276
|
+
if (ids.includes(ref))
|
|
8277
|
+
return success(null, null, ref);
|
|
8278
|
+
const matches = ids.filter((candidate) => candidate.startsWith(ref));
|
|
8279
|
+
if (matches.length === 1)
|
|
8280
|
+
return success(null, null, matches[0]);
|
|
8281
|
+
if (matches.length === 0)
|
|
8282
|
+
return failure2("not_found", `${kind} not found`);
|
|
8283
|
+
return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
|
|
8284
|
+
}
|
|
8273
8285
|
function parseOperation(family, input) {
|
|
8274
8286
|
if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
|
|
8275
8287
|
return failure2("unsupported_version", "unsupported schema version", { operation: family });
|
|
@@ -9362,20 +9374,19 @@ var workspaceConfigSchema = object({
|
|
|
9362
9374
|
union([boolean2(), literal("verified")]).optional()
|
|
9363
9375
|
]))).passthrough().optional(),
|
|
9364
9376
|
youtrack: object({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
|
|
9365
|
-
issues: object({
|
|
9377
|
+
issues: object({
|
|
9378
|
+
provider: _enum(["github", "gitlab", "youtrack"]).optional(),
|
|
9379
|
+
link: boolean2().optional(),
|
|
9380
|
+
relation: unknown().optional(),
|
|
9381
|
+
backlink: boolean2().optional(),
|
|
9382
|
+
link_on_pr: boolean2().optional()
|
|
9383
|
+
}).passthrough().optional(),
|
|
9366
9384
|
branchPolicy: workspaceBranchPolicySchema.optional(),
|
|
9367
9385
|
commitPolicy: workspaceCommitPolicySchema.optional(),
|
|
9368
9386
|
defaultProfile: nonBlank.optional(),
|
|
9369
9387
|
profiles: record(nonBlank, workspaceProfileSchema).optional(),
|
|
9370
9388
|
releaseTracks: record(nonBlank, releaseTrackSchema).optional()
|
|
9371
9389
|
}).passthrough().superRefine((workspace, context) => {
|
|
9372
|
-
if (workspace.issues && workspace.vcs?.provider !== "github") {
|
|
9373
|
-
context.addIssue({
|
|
9374
|
-
code: "custom",
|
|
9375
|
-
path: ["issues"],
|
|
9376
|
-
message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
|
|
9377
|
-
});
|
|
9378
|
-
}
|
|
9379
9390
|
if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
|
|
9380
9391
|
context.addIssue({
|
|
9381
9392
|
code: "custom",
|
|
@@ -10177,6 +10188,44 @@ var readVcsConfig = () => {
|
|
|
10177
10188
|
}
|
|
10178
10189
|
return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
|
|
10179
10190
|
};
|
|
10191
|
+
var TRACKERS = new Set(["github", "gitlab", "youtrack"]);
|
|
10192
|
+
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
10193
|
+
var flag = (value) => typeof value === "boolean" ? value : null;
|
|
10194
|
+
var text2 = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
10195
|
+
function issueTrackerOf(issues, youtrack) {
|
|
10196
|
+
const iss = asRecord(issues);
|
|
10197
|
+
const yt = asRecord(youtrack);
|
|
10198
|
+
const warnings = [];
|
|
10199
|
+
const named = typeof iss.provider === "string" && TRACKERS.has(iss.provider.toLowerCase()) ? iss.provider.toLowerCase() : null;
|
|
10200
|
+
if (iss.relation !== undefined && iss.relation !== "closes" && iss.relation !== "refs")
|
|
10201
|
+
warnings.push(`issues.relation ${JSON.stringify(iss.relation)} is not closes or refs; read as closes`);
|
|
10202
|
+
for (const key of ["repo", "host"])
|
|
10203
|
+
if (iss[key] !== undefined && text2(iss[key]) === null)
|
|
10204
|
+
warnings.push(`issues.${key} is not a non-empty string; ignored`);
|
|
10205
|
+
const common = {
|
|
10206
|
+
relation: iss.relation === "refs" ? "refs" : "closes",
|
|
10207
|
+
backlink: iss.backlink === true,
|
|
10208
|
+
baseUrl: text2(yt.baseUrl),
|
|
10209
|
+
repo: text2(iss.repo)?.replace(/^\/+|\/+$/gu, "") ?? null,
|
|
10210
|
+
host: text2(iss.host)?.toLowerCase() ?? null,
|
|
10211
|
+
warnings
|
|
10212
|
+
};
|
|
10213
|
+
const legacyGithub = named === "github" && flag(iss.link) === null;
|
|
10214
|
+
if (flag(yt.link_issues) === true && (named === null || legacyGithub))
|
|
10215
|
+
return { provider: "youtrack", link: true, ...common, source: "youtrack.link_issues" };
|
|
10216
|
+
if (named) {
|
|
10217
|
+
const link = flag(iss.link) ?? flag(iss.link_on_pr) ?? (named === "youtrack" ? flag(yt.link_issues) : null) ?? false;
|
|
10218
|
+
return {
|
|
10219
|
+
provider: named,
|
|
10220
|
+
link,
|
|
10221
|
+
...common,
|
|
10222
|
+
source: flag(iss.link) === null && flag(iss.link_on_pr) !== null ? "issues.link_on_pr" : "issues"
|
|
10223
|
+
};
|
|
10224
|
+
}
|
|
10225
|
+
if (common.baseUrl)
|
|
10226
|
+
return { provider: "youtrack", link: false, ...common, source: "youtrack.baseUrl" };
|
|
10227
|
+
return null;
|
|
10228
|
+
}
|
|
10180
10229
|
var trackSummary = (resolution) => resolution.status === "resolved" ? {
|
|
10181
10230
|
name: resolution.track?.name ?? null,
|
|
10182
10231
|
source: resolution.source,
|
|
@@ -10197,6 +10246,7 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
10197
10246
|
const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
|
|
10198
10247
|
const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
|
|
10199
10248
|
const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
|
|
10249
|
+
const issueTracker = issueTrackerOf(wsIssues, wsYt);
|
|
10200
10250
|
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
10201
10251
|
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
10202
10252
|
let issuesProvider = null;
|
|
@@ -10229,7 +10279,8 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
10229
10279
|
link_issues: linkIssues,
|
|
10230
10280
|
youtrack_base_url: youtrackBaseUrl,
|
|
10231
10281
|
issues_provider: issuesProvider,
|
|
10232
|
-
link_on_pr: linkOnPr
|
|
10282
|
+
link_on_pr: linkOnPr,
|
|
10283
|
+
issue_tracker: issueTracker
|
|
10233
10284
|
};
|
|
10234
10285
|
}
|
|
10235
10286
|
if (cfgStatus === "malformed") {
|
|
@@ -10255,7 +10306,8 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
10255
10306
|
link_issues: linkIssues,
|
|
10256
10307
|
youtrack_base_url: youtrackBaseUrl,
|
|
10257
10308
|
issues_provider: issuesProvider,
|
|
10258
|
-
link_on_pr: linkOnPr
|
|
10309
|
+
link_on_pr: linkOnPr,
|
|
10310
|
+
issue_tracker: issueTracker
|
|
10259
10311
|
};
|
|
10260
10312
|
if (provider === "gitlab") {
|
|
10261
10313
|
out.gitlab = {
|
|
@@ -10414,7 +10466,7 @@ var removeIfEmpty = (dir) => {
|
|
|
10414
10466
|
};
|
|
10415
10467
|
var tokenHash = (token) => createHash6("sha256").update(token).digest("hex");
|
|
10416
10468
|
function recordIssuedSession(cwd, lead, issued) {
|
|
10417
|
-
const token = randomBytes(18).toString("
|
|
10469
|
+
const token = randomBytes(18).toString("hex");
|
|
10418
10470
|
try {
|
|
10419
10471
|
migrateRepoIssued(cwd);
|
|
10420
10472
|
const own = hash(issued);
|
|
@@ -10485,7 +10537,12 @@ var MAX_LINE_BYTES = 4096;
|
|
|
10485
10537
|
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
10486
10538
|
var FAILING = new Set(["failed", "blocked"]);
|
|
10487
10539
|
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
10488
|
-
var PR_ROW_TYPES = new Set([
|
|
10540
|
+
var PR_ROW_TYPES = new Set([
|
|
10541
|
+
"pr.created",
|
|
10542
|
+
"pr.status",
|
|
10543
|
+
"pr.merged",
|
|
10544
|
+
"pr.linked"
|
|
10545
|
+
]);
|
|
10489
10546
|
var err = (code, error, unblock) => ({
|
|
10490
10547
|
ok: false,
|
|
10491
10548
|
code,
|
|
@@ -11966,7 +12023,7 @@ var RISK = {
|
|
|
11966
12023
|
};
|
|
11967
12024
|
var YES = new Set(["true", "yes", "y", "1", "on"]);
|
|
11968
12025
|
var NO = new Set(["false", "no", "n", "0", "off", "none"]);
|
|
11969
|
-
var
|
|
12026
|
+
var flag2 = (value, unknownAs) => {
|
|
11970
12027
|
if (typeof value === "boolean")
|
|
11971
12028
|
return { ok: true, value };
|
|
11972
12029
|
if (typeof value === "number" && (value === 0 || value === 1))
|
|
@@ -12032,12 +12089,12 @@ function normalizeJudgment(raw, previous) {
|
|
|
12032
12089
|
const items = Array.isArray(value) ? value : [value];
|
|
12033
12090
|
judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
|
|
12034
12091
|
} else {
|
|
12035
|
-
if (name === "needsPlan" && typeof value === "string" && !
|
|
12092
|
+
if (name === "needsPlan" && typeof value === "string" && !flag2(value, false).ok && value.trim()) {
|
|
12036
12093
|
judgment.needsPlan = true;
|
|
12037
12094
|
judgment.refs.push(refOf(value.trim()));
|
|
12038
12095
|
continue;
|
|
12039
12096
|
}
|
|
12040
|
-
const parsed =
|
|
12097
|
+
const parsed = flag2(value, name === "behaviorChange");
|
|
12041
12098
|
if (parsed.ok)
|
|
12042
12099
|
judgment[name] = parsed.value;
|
|
12043
12100
|
else
|
|
@@ -13631,20 +13688,26 @@ class TaskStore {
|
|
|
13631
13688
|
return success(null, null, { key: key.data, bound: bound.data });
|
|
13632
13689
|
}
|
|
13633
13690
|
taskForWorker(workerId) {
|
|
13634
|
-
if (typeof workerId !== "string" || !
|
|
13691
|
+
if (typeof workerId !== "string" || !idRefPattern.test(workerId))
|
|
13635
13692
|
return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
|
|
13636
13693
|
const listed = this.listTaskIndex();
|
|
13637
13694
|
if (!listed.ok)
|
|
13638
13695
|
return listed;
|
|
13696
|
+
const owners = new Map;
|
|
13639
13697
|
for (const entry of listed.data) {
|
|
13640
13698
|
if (entry.status === "closed")
|
|
13641
13699
|
continue;
|
|
13642
13700
|
const loaded = this.load(entry.id);
|
|
13643
13701
|
if (!loaded.ok)
|
|
13644
13702
|
return loaded;
|
|
13645
|
-
|
|
13646
|
-
|
|
13703
|
+
for (const worker of loaded.data?.record.workers ?? [])
|
|
13704
|
+
owners.set(worker.id, entry.id);
|
|
13647
13705
|
}
|
|
13706
|
+
const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
|
|
13707
|
+
if (resolved.ok)
|
|
13708
|
+
return success(null, null, owners.get(resolved.data) ?? null);
|
|
13709
|
+
if (resolved.code !== "not_found")
|
|
13710
|
+
return resolved;
|
|
13648
13711
|
return success(null, null, null);
|
|
13649
13712
|
}
|
|
13650
13713
|
originTaskEntry() {
|
|
@@ -16270,6 +16333,8 @@ var TASK_SCOPED_ACTIONS = new Set([
|
|
|
16270
16333
|
"worker.cancel",
|
|
16271
16334
|
"state.export"
|
|
16272
16335
|
]);
|
|
16336
|
+
var ONLY_OWN_REPORT = "workers can submit only their own report";
|
|
16337
|
+
var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
|
|
16273
16338
|
var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
|
|
16274
16339
|
var workerCommand = (taskId, request) => {
|
|
16275
16340
|
const { action, workerId, reason, report } = request;
|
|
@@ -17167,6 +17232,10 @@ class WorkitCore {
|
|
|
17167
17232
|
}
|
|
17168
17233
|
if (helper?.ok)
|
|
17169
17234
|
return failure2("permission_denied", "helpers cannot resolve findings");
|
|
17235
|
+
const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
|
|
17236
|
+
if (!findingRef.ok)
|
|
17237
|
+
return findingRef;
|
|
17238
|
+
input.findingId = findingRef.data;
|
|
17170
17239
|
const finding = task.data.findings.find((entry) => entry.id === input.findingId);
|
|
17171
17240
|
if (!finding)
|
|
17172
17241
|
return failure2("not_found", "finding not found");
|
|
@@ -17318,15 +17387,31 @@ class WorkitCore {
|
|
|
17318
17387
|
return changed;
|
|
17319
17388
|
return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
|
|
17320
17389
|
}
|
|
17390
|
+
const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
|
|
17391
|
+
if (!workerRef.ok)
|
|
17392
|
+
return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
|
|
17393
|
+
input.workerId = workerRef.data;
|
|
17321
17394
|
const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
|
|
17322
17395
|
if (!entry)
|
|
17323
17396
|
return failure2("not_found", "worker not found");
|
|
17324
17397
|
if (input.action === "report") {
|
|
17325
17398
|
if (helperId === null || helperId !== input.workerId)
|
|
17326
|
-
return failure2("permission_denied",
|
|
17399
|
+
return failure2("permission_denied", ONLY_OWN_REPORT);
|
|
17327
17400
|
const helper = this.helperEntry(task.data, true, true);
|
|
17328
17401
|
if (!helper.ok)
|
|
17329
17402
|
return helper;
|
|
17403
|
+
const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
|
|
17404
|
+
const reported = [];
|
|
17405
|
+
for (const findingRef of input.report.findingIds) {
|
|
17406
|
+
const found = resolveIdRef("finding", findingRef, ownFindings);
|
|
17407
|
+
if (found.ok)
|
|
17408
|
+
reported.push(found.data);
|
|
17409
|
+
else if (found.code === "not_found")
|
|
17410
|
+
reported.push(findingRef);
|
|
17411
|
+
else
|
|
17412
|
+
return found;
|
|
17413
|
+
}
|
|
17414
|
+
input.report.findingIds = reported;
|
|
17330
17415
|
const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
|
|
17331
17416
|
const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
|
|
17332
17417
|
if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
|
|
@@ -17354,7 +17439,7 @@ class WorkitCore {
|
|
|
17354
17439
|
return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
|
|
17355
17440
|
}
|
|
17356
17441
|
if (helperId !== null)
|
|
17357
|
-
return failure2("permission_denied",
|
|
17442
|
+
return failure2("permission_denied", NO_HELPER_CONTROL);
|
|
17358
17443
|
if (task.data.status === "closed")
|
|
17359
17444
|
return failure2("invalid_transition", "closed task cannot cancel workers");
|
|
17360
17445
|
if (entry.data.state === "stopped") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-cursor",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.15.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit Cursor plugin — shared MCP transport, native hooks, and sixteen method skills",
|
|
6
6
|
"keywords": [
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"build": "bun scripts/build.ts"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@brainervirus/workit-core": "^8.
|
|
50
|
-
"@brainervirus/workit-mcp": "^8.
|
|
49
|
+
"@brainervirus/workit-core": "^8.15.0",
|
|
50
|
+
"@brainervirus/workit-mcp": "^8.15.0"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": ">=24"
|
|
@@ -33,8 +33,8 @@ that repeats because the base keeps moving, or after 3 failed fix attempts on
|
|
|
33
33
|
the same check.
|
|
34
34
|
|
|
35
35
|
1. **Open.** `workit git push`, then `workit pr create --title "<title>"
|
|
36
|
-
--body-file <f|->` (
|
|
37
|
-
`workit pr edit`). A stack: `workit stack plan <bottom> ... <top>`, one
|
|
36
|
+
--body-file <f|->` plus `--issue <#n|ID>` per issue (`--refs`: no close; idempotent;
|
|
37
|
+
body: `references/pr-body.md`; edits: `workit pr edit`). A stack: `workit stack plan <bottom> ... <top>`, one
|
|
38
38
|
`workit pr create --base <parent> --fill` per branch, then
|
|
39
39
|
`workit stack sync`. Finish the whole stack before babysitting any PR.
|
|
40
40
|
2. **Read state** with `workit pr status --json`. `REVIEW` alone is a pending
|