@brainervirus/workit-cursor 1.2.0 → 1.3.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/commands/wk-babysit.md +1 -1
- package/dist/cursor-session-start.js +1024 -207
- package/dist/mcp-server.js +956 -315
- package/dist/workit-hook.js +1027 -196
- package/package.json +3 -3
- package/rules/workit-contract.mdc +20 -22
- package/skills/workit-babysit/SKILL.md +20 -26
- package/skills/workit-behavioral-tdd/SKILL.md +0 -5
- package/skills/workit-blast-radius/SKILL.md +0 -5
- package/skills/workit-challenge/SKILL.md +30 -47
- package/skills/workit-debug/SKILL.md +5 -27
- package/skills/workit-deslop/SKILL.md +0 -5
- package/skills/workit-diagram/SKILL.md +0 -5
- package/skills/workit-green-run/SKILL.md +0 -5
- package/skills/workit-handoff/SKILL.md +2 -8
- package/skills/workit-implement/SKILL.md +14 -20
- package/skills/workit-mockup/SKILL.md +0 -5
- package/skills/workit-plan/SKILL.md +30 -63
- package/skills/workit-review/SKILL.md +0 -5
- package/skills/workit-steer/SKILL.md +27 -27
package/dist/mcp-server.js
CHANGED
|
@@ -6622,11 +6622,11 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
6622
6622
|
|
|
6623
6623
|
// packages/workit-mcp/src/index.ts
|
|
6624
6624
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
6625
|
-
import
|
|
6625
|
+
import path16 from "node:path";
|
|
6626
6626
|
|
|
6627
6627
|
// packages/workit-mcp/src/server.ts
|
|
6628
|
-
import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as
|
|
6629
|
-
import
|
|
6628
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as realpathSync8 } from "node:fs";
|
|
6629
|
+
import path15 from "node:path";
|
|
6630
6630
|
|
|
6631
6631
|
// node_modules/@modelcontextprotocol/sdk/node_modules/zod/v4/core/core.js
|
|
6632
6632
|
var NEVER = Object.freeze({
|
|
@@ -12479,8 +12479,8 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
12479
12479
|
|
|
12480
12480
|
// packages/workit-core/src/core.ts
|
|
12481
12481
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
12482
|
-
import { existsSync as existsSync5, realpathSync as
|
|
12483
|
-
import
|
|
12482
|
+
import { existsSync as existsSync5, realpathSync as realpathSync7 } from "node:fs";
|
|
12483
|
+
import path11 from "node:path";
|
|
12484
12484
|
|
|
12485
12485
|
// packages/workit-core/src/core/task-contract.ts
|
|
12486
12486
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -17787,6 +17787,17 @@ function _array2(Class, element, params) {
|
|
|
17787
17787
|
...normalizeParams2(params)
|
|
17788
17788
|
});
|
|
17789
17789
|
}
|
|
17790
|
+
function _custom2(Class, fn, _params) {
|
|
17791
|
+
const norm = normalizeParams2(_params);
|
|
17792
|
+
norm.abort ?? (norm.abort = true);
|
|
17793
|
+
const schema = new Class({
|
|
17794
|
+
type: "custom",
|
|
17795
|
+
check: "custom",
|
|
17796
|
+
fn,
|
|
17797
|
+
...norm
|
|
17798
|
+
});
|
|
17799
|
+
return schema;
|
|
17800
|
+
}
|
|
17790
17801
|
function _refine2(Class, fn, _params) {
|
|
17791
17802
|
const schema = new Class({
|
|
17792
17803
|
type: "custom",
|
|
@@ -19940,6 +19951,9 @@ var ZodCustom2 = /* @__PURE__ */ $constructor2("ZodCustom", (inst, def) => {
|
|
|
19940
19951
|
ZodType2.init(inst, def);
|
|
19941
19952
|
inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);
|
|
19942
19953
|
});
|
|
19954
|
+
function custom2(fn, _params) {
|
|
19955
|
+
return _custom2(ZodCustom2, fn ?? (() => true), _params);
|
|
19956
|
+
}
|
|
19943
19957
|
function refine2(fn, _params = {}) {
|
|
19944
19958
|
return _refine2(ZodCustom2, fn, _params);
|
|
19945
19959
|
}
|
|
@@ -20373,7 +20387,8 @@ var decisionSchema = object4({
|
|
|
20373
20387
|
consumption: object4({
|
|
20374
20388
|
state: _enum2(["reserved", "consumed", "uncertain"]),
|
|
20375
20389
|
at: utc,
|
|
20376
|
-
actionRef: refSchema
|
|
20390
|
+
actionRef: refSchema,
|
|
20391
|
+
historicalActionId: text.optional()
|
|
20377
20392
|
}).strict().nullable()
|
|
20378
20393
|
}).strict();
|
|
20379
20394
|
var findingSchema = object4({
|
|
@@ -20404,6 +20419,7 @@ var workerSchema = object4({
|
|
|
20404
20419
|
assignment: assignmentSchema,
|
|
20405
20420
|
state: _enum2(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
|
|
20406
20421
|
session: refSchema.nullable(),
|
|
20422
|
+
coordinator: refSchema.optional(),
|
|
20407
20423
|
report: workerReportSchema.nullable()
|
|
20408
20424
|
}).strict();
|
|
20409
20425
|
var ownerSchema = object4({
|
|
@@ -20515,6 +20531,7 @@ var taskSummarySchema = object4({
|
|
|
20515
20531
|
requirements: array3(requirementEvaluationSchema),
|
|
20516
20532
|
writer: workspaceRecordSchema.shape.writer
|
|
20517
20533
|
}).strict();
|
|
20534
|
+
var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true }).extend({ source: provenanceSchema.pick({ host: true, kind: true }) }).strict();
|
|
20518
20535
|
var exportBundleSchema = object4({
|
|
20519
20536
|
schemaVersion: literal3(1),
|
|
20520
20537
|
exportedAt: utc,
|
|
@@ -20536,11 +20553,16 @@ var taskOperations = {
|
|
|
20536
20553
|
expectedWorkspaceRevision: revision.nullable().optional(),
|
|
20537
20554
|
intent: intentSchema
|
|
20538
20555
|
}),
|
|
20539
|
-
list: operation({
|
|
20556
|
+
list: operation({
|
|
20557
|
+
action: literal3("list"),
|
|
20558
|
+
status: _enum2(["open", "closed", "all"]).optional(),
|
|
20559
|
+
query: string5().trim().min(1).max(200).optional(),
|
|
20560
|
+
limit: number5().int().min(1).max(50).optional()
|
|
20561
|
+
}),
|
|
20540
20562
|
inspect: operation({
|
|
20541
20563
|
action: literal3("inspect"),
|
|
20542
20564
|
...taskId,
|
|
20543
|
-
view: _enum2(["summary", "full"])
|
|
20565
|
+
view: _enum2(["summary", "full"]).optional()
|
|
20544
20566
|
}),
|
|
20545
20567
|
revise: operation({
|
|
20546
20568
|
action: literal3("revise"),
|
|
@@ -20568,7 +20590,7 @@ var taskOperations = {
|
|
|
20568
20590
|
...taskId,
|
|
20569
20591
|
...revisions,
|
|
20570
20592
|
expectedWorkspaceRevision: revision.optional(),
|
|
20571
|
-
authorityRefs: array3(refSchema)
|
|
20593
|
+
authorityRefs: array3(refSchema).optional()
|
|
20572
20594
|
}),
|
|
20573
20595
|
close: operation({
|
|
20574
20596
|
action: literal3("close"),
|
|
@@ -20882,6 +20904,7 @@ function requirementId(input) {
|
|
|
20882
20904
|
}
|
|
20883
20905
|
// packages/workit-core/src/core/task-store.ts
|
|
20884
20906
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
20907
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
20885
20908
|
import * as fs4 from "node:fs";
|
|
20886
20909
|
import { hostname as hostname2 } from "node:os";
|
|
20887
20910
|
import path4 from "node:path";
|
|
@@ -21517,7 +21540,8 @@ var metadataLockSchema = object4({
|
|
|
21517
21540
|
pid: number5().int().nonnegative().safe(),
|
|
21518
21541
|
processStart: string5().nullable(),
|
|
21519
21542
|
host: string5().min(1),
|
|
21520
|
-
nonce: string5().min(1)
|
|
21543
|
+
nonce: string5().min(1),
|
|
21544
|
+
externalAction: literal3(true).optional()
|
|
21521
21545
|
}).strict();
|
|
21522
21546
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
21523
21547
|
var cachedRuntimeVersion = null;
|
|
@@ -21569,6 +21593,28 @@ var sameMetadataLock = (left, right) => {
|
|
|
21569
21593
|
const parsed = metadataLockSchema.safeParse(left);
|
|
21570
21594
|
return parsed.success && canonicalJson(parsed.data) === canonicalJson(right);
|
|
21571
21595
|
};
|
|
21596
|
+
var sameDirectoryIdentity = (left, right) => {
|
|
21597
|
+
if (!path4.isAbsolute(left) || !path4.isAbsolute(right))
|
|
21598
|
+
return false;
|
|
21599
|
+
const normalizedLeft = path4.resolve(left);
|
|
21600
|
+
const normalizedRight = path4.resolve(right);
|
|
21601
|
+
try {
|
|
21602
|
+
const a = fs4.statSync(normalizedLeft, { bigint: true });
|
|
21603
|
+
const b = fs4.statSync(normalizedRight, { bigint: true });
|
|
21604
|
+
if (!a.isDirectory() || !b.isDirectory())
|
|
21605
|
+
return false;
|
|
21606
|
+
if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
|
|
21607
|
+
return true;
|
|
21608
|
+
const canonicalLeft = fs4.realpathSync(normalizedLeft);
|
|
21609
|
+
const canonicalRight = fs4.realpathSync(normalizedRight);
|
|
21610
|
+
if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
|
|
21611
|
+
return true;
|
|
21612
|
+
return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
|
|
21613
|
+
} catch {
|
|
21614
|
+
return false;
|
|
21615
|
+
}
|
|
21616
|
+
};
|
|
21617
|
+
var externalActionLockRoots = new AsyncLocalStorage;
|
|
21572
21618
|
|
|
21573
21619
|
class TaskStore {
|
|
21574
21620
|
root;
|
|
@@ -21629,7 +21675,7 @@ class TaskStore {
|
|
|
21629
21675
|
const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
|
|
21630
21676
|
if (!item.exists)
|
|
21631
21677
|
return success(null, null, null);
|
|
21632
|
-
if (item.result.ok && item.result.data.root !== this.root)
|
|
21678
|
+
if (item.result.ok && item.result.data.root !== this.root && !sameDirectoryIdentity(item.result.data.root, this.root))
|
|
21633
21679
|
return failure("recovery_required", "workspace root binding is invalid", {
|
|
21634
21680
|
path: this.workspacePath
|
|
21635
21681
|
});
|
|
@@ -21720,6 +21766,29 @@ class TaskStore {
|
|
|
21720
21766
|
const current = this.readWorkspace();
|
|
21721
21767
|
if (!current.ok)
|
|
21722
21768
|
return current;
|
|
21769
|
+
const origin = input.task.origin;
|
|
21770
|
+
if (current.data && origin) {
|
|
21771
|
+
if (origin.workspaceId === current.data.id) {
|
|
21772
|
+
const source = this.readTask(origin.taskId);
|
|
21773
|
+
if (source.ok)
|
|
21774
|
+
return success(source.data.revision, current.data.revision, source.data);
|
|
21775
|
+
if (source.code !== "not_found")
|
|
21776
|
+
return source;
|
|
21777
|
+
return failure("not_found", "source task is not present in this shared workspace");
|
|
21778
|
+
}
|
|
21779
|
+
const tasks = this.listTasks();
|
|
21780
|
+
if (!tasks.ok)
|
|
21781
|
+
return tasks;
|
|
21782
|
+
const prior = tasks.data.filter((task) => task.origin?.workspaceId === origin.workspaceId && task.origin?.taskId === origin.taskId);
|
|
21783
|
+
if (prior.length > 1)
|
|
21784
|
+
return failure("recovery_required", `source task has multiple imported mappings: ${prior.map((task) => task.id).join(", ")}`);
|
|
21785
|
+
if (prior.length === 1) {
|
|
21786
|
+
const existing = prior[0];
|
|
21787
|
+
if (existing.origin.exportDigest !== origin.exportDigest)
|
|
21788
|
+
return failure("revision_conflict", "source task export changed; reconcile the mapped destination task before importing", { taskId: existing.id });
|
|
21789
|
+
return success(existing.revision, current.data.revision, existing);
|
|
21790
|
+
}
|
|
21791
|
+
}
|
|
21723
21792
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
21724
21793
|
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
21725
21794
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
@@ -22108,6 +22177,73 @@ class TaskStore {
|
|
|
22108
22177
|
})
|
|
22109
22178
|
};
|
|
22110
22179
|
}
|
|
22180
|
+
externalActionLockOptions() {
|
|
22181
|
+
const options = this.metadataLockOptions();
|
|
22182
|
+
return { ...options, payload: () => ({ ...options.payload(), externalAction: true }) };
|
|
22183
|
+
}
|
|
22184
|
+
async withExternalActionLock(operation, reentrant = false) {
|
|
22185
|
+
const activeRoots = externalActionLockRoots.getStore();
|
|
22186
|
+
if (activeRoots && [...activeRoots].some((root) => root === this.root || sameDirectoryIdentity(root, this.root)))
|
|
22187
|
+
return operation();
|
|
22188
|
+
try {
|
|
22189
|
+
this.initializeMutationStorage();
|
|
22190
|
+
} catch (error) {
|
|
22191
|
+
return failure("storage_error", `unable to initialize store: ${String(error)}`, {
|
|
22192
|
+
path: this.workitDir
|
|
22193
|
+
});
|
|
22194
|
+
}
|
|
22195
|
+
let handle;
|
|
22196
|
+
try {
|
|
22197
|
+
handle = acquireFileLockSync(this.workspacePath, this.externalActionLockOptions());
|
|
22198
|
+
} catch (error) {
|
|
22199
|
+
return this.lockFailure(error);
|
|
22200
|
+
}
|
|
22201
|
+
let result;
|
|
22202
|
+
try {
|
|
22203
|
+
if (!handle.verifyStillHeld())
|
|
22204
|
+
result = failure("recovery_required", "metadata lock was compromised", {
|
|
22205
|
+
path: this.lockPath
|
|
22206
|
+
});
|
|
22207
|
+
else {
|
|
22208
|
+
try {
|
|
22209
|
+
const run = () => operation();
|
|
22210
|
+
result = await (reentrant ? externalActionLockRoots.run(new Set([...activeRoots ?? [], this.root]), run) : run());
|
|
22211
|
+
} catch {
|
|
22212
|
+
result = failure("external_outcome_unknown", "external action outcome is unknown", {
|
|
22213
|
+
outcome: "unknown"
|
|
22214
|
+
});
|
|
22215
|
+
}
|
|
22216
|
+
try {
|
|
22217
|
+
if (!handle.verifyStillHeld())
|
|
22218
|
+
result = failure("external_outcome_unknown", "metadata lock was compromised", {
|
|
22219
|
+
outcome: "unknown",
|
|
22220
|
+
path: this.lockPath
|
|
22221
|
+
});
|
|
22222
|
+
} catch {
|
|
22223
|
+
result = failure("external_outcome_unknown", "metadata lock could not be verified", {
|
|
22224
|
+
outcome: "unknown",
|
|
22225
|
+
path: this.lockPath
|
|
22226
|
+
});
|
|
22227
|
+
}
|
|
22228
|
+
}
|
|
22229
|
+
} catch {
|
|
22230
|
+
result = failure("external_outcome_unknown", "external action lock operation failed", {
|
|
22231
|
+
outcome: "unknown",
|
|
22232
|
+
path: this.lockPath
|
|
22233
|
+
});
|
|
22234
|
+
}
|
|
22235
|
+
try {
|
|
22236
|
+
handle.release();
|
|
22237
|
+
} catch (error) {
|
|
22238
|
+
const released = this.lockFailure(error);
|
|
22239
|
+
const releaseError = released.ok ? "metadata lock release failed" : released.error;
|
|
22240
|
+
return result.ok ? failure("external_outcome_unknown", "external action metadata lock release is uncertain", {
|
|
22241
|
+
outcome: "unknown",
|
|
22242
|
+
path: this.lockPath
|
|
22243
|
+
}) : failure("recovery_required", `${result.error}; ${releaseError}`, result.details);
|
|
22244
|
+
}
|
|
22245
|
+
return result;
|
|
22246
|
+
}
|
|
22111
22247
|
recoveryLockOptions(observed, evidence, gate) {
|
|
22112
22248
|
const options = this.metadataLockOptions();
|
|
22113
22249
|
options.staleRecovery = "remove-if-unchanged";
|
|
@@ -22177,6 +22313,14 @@ class TaskStore {
|
|
|
22177
22313
|
lockFailure(error) {
|
|
22178
22314
|
const value = error;
|
|
22179
22315
|
const code = typeof value?.code === "string" ? value.code : "";
|
|
22316
|
+
if (code === "EEXIST" || code === "file_lock_timeout") {
|
|
22317
|
+
const lock = this.readLockSnapshot();
|
|
22318
|
+
if (lock.ok && lock.data?.data.externalAction)
|
|
22319
|
+
return failure("writer_conflict", "workspace is reserved by a managed external action", {
|
|
22320
|
+
outcome: "not_started",
|
|
22321
|
+
path: this.lockPath
|
|
22322
|
+
});
|
|
22323
|
+
}
|
|
22180
22324
|
const recovery = code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || code === "metadata_lock_invalid" || code === "not-file" || /metadata lock|file lock|reclaim/i.test(String(value?.message ?? error));
|
|
22181
22325
|
return failure(recovery ? "recovery_required" : "storage_error", `metadata lock operation failed: ${String(error)}`, { path: this.lockPath });
|
|
22182
22326
|
}
|
|
@@ -22401,6 +22545,10 @@ var validPath = (value) => typeof value === "string" && value.length > 0 && ![..
|
|
|
22401
22545
|
}) && !value.split(/[\\/]/).includes("..");
|
|
22402
22546
|
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
22403
22547
|
var workerIdOf = (value) => value.workerId ?? null;
|
|
22548
|
+
var currentWriterOwnsTask = (task, workspace, caller) => {
|
|
22549
|
+
const owner = workspace.writer?.owner;
|
|
22550
|
+
return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
|
|
22551
|
+
};
|
|
22404
22552
|
function assertProductWriteAllowed(input) {
|
|
22405
22553
|
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
22406
22554
|
return failure("invalid_input", "invalid product write path");
|
|
@@ -22440,21 +22588,23 @@ function assertProductWriteAllowed(input) {
|
|
|
22440
22588
|
}
|
|
22441
22589
|
|
|
22442
22590
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
22443
|
-
import { createHash as
|
|
22591
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
22444
22592
|
import * as fs7 from "node:fs";
|
|
22445
22593
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
22446
|
-
import
|
|
22594
|
+
import path10 from "node:path";
|
|
22447
22595
|
|
|
22448
22596
|
// packages/workit-core/src/core/authority.ts
|
|
22449
|
-
import { createHash as
|
|
22597
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
22450
22598
|
import * as fs6 from "node:fs";
|
|
22451
|
-
import
|
|
22599
|
+
import path9 from "node:path";
|
|
22452
22600
|
|
|
22453
22601
|
// packages/workit-core/src/core/auto-approval.ts
|
|
22454
|
-
import { createHash as
|
|
22455
|
-
import { readFileSync as readFileSync4 } from "node:fs";
|
|
22602
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
22603
|
+
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
22604
|
+
import path8 from "node:path";
|
|
22456
22605
|
|
|
22457
22606
|
// packages/workit-core/src/core/workspaces.ts
|
|
22607
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
22458
22608
|
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
22459
22609
|
import path6 from "node:path";
|
|
22460
22610
|
|
|
@@ -22472,7 +22622,7 @@ import os from "node:os";
|
|
|
22472
22622
|
import path5 from "node:path";
|
|
22473
22623
|
|
|
22474
22624
|
// packages/workit-core/src/core/boundary.ts
|
|
22475
|
-
import { statSync } from "node:fs";
|
|
22625
|
+
import { statSync as statSync2 } from "node:fs";
|
|
22476
22626
|
var EVENT = {
|
|
22477
22627
|
initialization: "initialization",
|
|
22478
22628
|
provenance: "provenance",
|
|
@@ -22500,7 +22650,7 @@ var changedSourcesSinceLoad = (marker) => {
|
|
|
22500
22650
|
const changed = [];
|
|
22501
22651
|
for (const file of marker.files) {
|
|
22502
22652
|
try {
|
|
22503
|
-
if (
|
|
22653
|
+
if (statSync2(file).mtimeMs > marker.loadedAt)
|
|
22504
22654
|
changed.push(file);
|
|
22505
22655
|
} catch {}
|
|
22506
22656
|
}
|
|
@@ -22659,33 +22809,197 @@ var COMMIT_PRESETS = [
|
|
|
22659
22809
|
"custom",
|
|
22660
22810
|
"auto"
|
|
22661
22811
|
];
|
|
22812
|
+
var resolveCommitPolicy = (config, workspace) => {
|
|
22813
|
+
const wp = workspace?.commitPolicy ?? {};
|
|
22814
|
+
if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
|
|
22815
|
+
return {
|
|
22816
|
+
preset: wp.preset,
|
|
22817
|
+
...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
|
|
22818
|
+
};
|
|
22819
|
+
}
|
|
22820
|
+
return config.commitPolicy ?? { preset: "conventional" };
|
|
22821
|
+
};
|
|
22662
22822
|
|
|
22663
22823
|
// packages/workit-core/src/core/workspaces.ts
|
|
22824
|
+
var nonBlank = string5().refine((value) => value.trim().length > 0, "must not be blank");
|
|
22825
|
+
var branchPreset = custom2((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
|
|
22826
|
+
var commitPreset = custom2((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
|
|
22827
|
+
var workspaceBranchPolicySchema = object4({
|
|
22828
|
+
preset: branchPreset,
|
|
22829
|
+
developBranch: nonBlank.optional(),
|
|
22830
|
+
prefixes: object4({
|
|
22831
|
+
feature: nonBlank,
|
|
22832
|
+
bugfix: nonBlank,
|
|
22833
|
+
release: nonBlank,
|
|
22834
|
+
hotfix: nonBlank
|
|
22835
|
+
}).passthrough().optional(),
|
|
22836
|
+
allowed: array3(nonBlank).optional(),
|
|
22837
|
+
protected: array3(nonBlank).optional(),
|
|
22838
|
+
integration: _enum2(["pr", "merge"]).optional()
|
|
22839
|
+
}).strict();
|
|
22840
|
+
var workspaceCommitPolicySchema = object4({ preset: commitPreset, pattern: string5().optional() }).strict();
|
|
22841
|
+
var workspaceProfileSchema = object4({
|
|
22842
|
+
branchPolicy: workspaceBranchPolicySchema.optional(),
|
|
22843
|
+
commitPolicy: workspaceCommitPolicySchema.optional()
|
|
22844
|
+
}).strict().refine((profile) => profile.branchPolicy !== undefined || profile.commitPolicy !== undefined, {
|
|
22845
|
+
message: "a profile must define branchPolicy or commitPolicy"
|
|
22846
|
+
});
|
|
22847
|
+
var versionSourceSchema = discriminatedUnion2("kind", [
|
|
22848
|
+
object4({
|
|
22849
|
+
kind: literal3("package-json"),
|
|
22850
|
+
path: nonBlank,
|
|
22851
|
+
field: nonBlank
|
|
22852
|
+
}).strict(),
|
|
22853
|
+
object4({ kind: literal3("git-tag") }).strict(),
|
|
22854
|
+
object4({ kind: literal3("manual") }).strict()
|
|
22855
|
+
]);
|
|
22856
|
+
var releaseTrackSchema = object4({
|
|
22857
|
+
strategy: branchPreset,
|
|
22858
|
+
productionBranch: nonBlank,
|
|
22859
|
+
integrationBranch: nonBlank,
|
|
22860
|
+
naming: object4({ feature: nonBlank, release: nonBlank, hotfix: nonBlank }).strict(),
|
|
22861
|
+
baseBranch: nonBlank,
|
|
22862
|
+
mergeBackBranches: array3(nonBlank),
|
|
22863
|
+
pullRequestTarget: nonBlank,
|
|
22864
|
+
tagNamespace: string5(),
|
|
22865
|
+
versionSource: versionSourceSchema,
|
|
22866
|
+
requiredChecks: array3(nonBlank)
|
|
22867
|
+
}).strict().superRefine((track, context) => {
|
|
22868
|
+
for (const [field, values] of [
|
|
22869
|
+
["mergeBackBranches", track.mergeBackBranches],
|
|
22870
|
+
["requiredChecks", track.requiredChecks]
|
|
22871
|
+
]) {
|
|
22872
|
+
if (new Set(values).size !== values.length) {
|
|
22873
|
+
context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
|
|
22874
|
+
}
|
|
22875
|
+
}
|
|
22876
|
+
if (track.versionSource.kind === "package-json" && (path6.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
|
|
22877
|
+
context.addIssue({
|
|
22878
|
+
code: "custom",
|
|
22879
|
+
path: ["versionSource", "path"],
|
|
22880
|
+
message: "must stay inside the repository"
|
|
22881
|
+
});
|
|
22882
|
+
}
|
|
22883
|
+
});
|
|
22884
|
+
var workspaceConfigSchema = object4({
|
|
22885
|
+
name: nonBlank,
|
|
22886
|
+
glob: nonBlank,
|
|
22887
|
+
vcs: object4({
|
|
22888
|
+
provider: _enum2(["gitlab", "github"]),
|
|
22889
|
+
defaultTargetBranch: nonBlank.optional(),
|
|
22890
|
+
tokenFile: nonBlank.optional(),
|
|
22891
|
+
account: nonBlank.optional()
|
|
22892
|
+
}).passthrough().optional(),
|
|
22893
|
+
autoApprove: union3([boolean5(), array3(nonBlank)]).optional(),
|
|
22894
|
+
youtrack: object4({ baseUrl: nonBlank.optional(), link_issues: boolean5().optional() }).passthrough().optional(),
|
|
22895
|
+
issues: object4({ provider: literal3("github").optional(), link_on_pr: boolean5().optional() }).passthrough().optional(),
|
|
22896
|
+
branchPolicy: workspaceBranchPolicySchema.optional(),
|
|
22897
|
+
commitPolicy: workspaceCommitPolicySchema.optional(),
|
|
22898
|
+
defaultProfile: nonBlank.optional(),
|
|
22899
|
+
profiles: record3(nonBlank, workspaceProfileSchema).optional(),
|
|
22900
|
+
releaseTracks: record3(nonBlank, releaseTrackSchema).optional()
|
|
22901
|
+
}).passthrough().superRefine((workspace, context) => {
|
|
22902
|
+
if (workspace.youtrack && workspace.vcs?.provider !== "gitlab") {
|
|
22903
|
+
context.addIssue({
|
|
22904
|
+
code: "custom",
|
|
22905
|
+
path: ["youtrack"],
|
|
22906
|
+
message: `YouTrack issue linking requires the gitlab provider, got ${workspace.vcs?.provider ?? "unset"}`
|
|
22907
|
+
});
|
|
22908
|
+
}
|
|
22909
|
+
if (workspace.issues && workspace.vcs?.provider !== "github") {
|
|
22910
|
+
context.addIssue({
|
|
22911
|
+
code: "custom",
|
|
22912
|
+
path: ["issues"],
|
|
22913
|
+
message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
|
|
22914
|
+
});
|
|
22915
|
+
}
|
|
22916
|
+
if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
|
|
22917
|
+
context.addIssue({
|
|
22918
|
+
code: "custom",
|
|
22919
|
+
path: ["defaultProfile"],
|
|
22920
|
+
message: `does not name a configured profile`
|
|
22921
|
+
});
|
|
22922
|
+
}
|
|
22923
|
+
});
|
|
22924
|
+
var workspacesFileSchema = object4({ workspaces: array3(workspaceConfigSchema).optional() }).passthrough().superRefine((file, context) => {
|
|
22925
|
+
const names = new Set;
|
|
22926
|
+
for (const [index, workspace] of (file.workspaces ?? []).entries()) {
|
|
22927
|
+
if (names.has(workspace.name)) {
|
|
22928
|
+
context.addIssue({
|
|
22929
|
+
code: "custom",
|
|
22930
|
+
path: ["workspaces", index, "name"],
|
|
22931
|
+
message: `duplicate workspace name ${JSON.stringify(workspace.name)}`
|
|
22932
|
+
});
|
|
22933
|
+
}
|
|
22934
|
+
names.add(workspace.name);
|
|
22935
|
+
}
|
|
22936
|
+
});
|
|
22937
|
+
var workspacesRevision = (contents) => createHash3("sha256").update(contents ?? "<missing>").digest("hex");
|
|
22938
|
+
var validateWorkspacesDocument = (parsed, file) => {
|
|
22939
|
+
if (!isConfigObject(parsed)) {
|
|
22940
|
+
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
22941
|
+
}
|
|
22942
|
+
const validated = workspacesFileSchema.safeParse(parsed, { reportInput: true });
|
|
22943
|
+
if (!validated.success) {
|
|
22944
|
+
const detail = validated.error.issues.map((issue) => {
|
|
22945
|
+
const field = String(issue.path[issue.path.length - 1] ?? "");
|
|
22946
|
+
const attempted = ["provider", "preset", "strategy"].includes(field) && typeof issue.input === "string" ? ` (${JSON.stringify(issue.input)})` : "";
|
|
22947
|
+
return `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}${attempted}`;
|
|
22948
|
+
}).join("; ");
|
|
22949
|
+
return {
|
|
22950
|
+
status: "invalid",
|
|
22951
|
+
path: file,
|
|
22952
|
+
entries: [],
|
|
22953
|
+
error: `${file} has invalid workspace configuration: ${detail}`
|
|
22954
|
+
};
|
|
22955
|
+
}
|
|
22956
|
+
for (const [index, workspace] of (validated.data.workspaces ?? []).entries()) {
|
|
22957
|
+
const glob = validateWorkspaceGlob(workspace.glob);
|
|
22958
|
+
if (!glob.ok) {
|
|
22959
|
+
return {
|
|
22960
|
+
status: "invalid",
|
|
22961
|
+
path: file,
|
|
22962
|
+
entries: [],
|
|
22963
|
+
error: `${file} workspaces.${index}.glob: ${glob.error}`
|
|
22964
|
+
};
|
|
22965
|
+
}
|
|
22966
|
+
}
|
|
22967
|
+
return {
|
|
22968
|
+
status: "valid",
|
|
22969
|
+
path: file,
|
|
22970
|
+
entries: validated.data.workspaces ?? [],
|
|
22971
|
+
document: parsed
|
|
22972
|
+
};
|
|
22973
|
+
};
|
|
22664
22974
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
22665
22975
|
const file = path6.join(dir, "workspaces.json");
|
|
22666
22976
|
let raw;
|
|
22667
22977
|
try {
|
|
22668
22978
|
raw = readFileSync3(file, "utf8");
|
|
22669
|
-
} catch {
|
|
22670
|
-
|
|
22979
|
+
} catch (error) {
|
|
22980
|
+
if (error.code === "ENOENT")
|
|
22981
|
+
return { status: "missing", path: file, entries: [], revision: workspacesRevision(null) };
|
|
22982
|
+
return {
|
|
22983
|
+
status: "malformed",
|
|
22984
|
+
path: file,
|
|
22985
|
+
entries: [],
|
|
22986
|
+
error: `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
|
|
22987
|
+
};
|
|
22671
22988
|
}
|
|
22672
22989
|
let parsed;
|
|
22673
22990
|
try {
|
|
22674
22991
|
parsed = JSON.parse(raw);
|
|
22675
22992
|
} catch {
|
|
22676
|
-
return {
|
|
22677
|
-
|
|
22678
|
-
|
|
22679
|
-
|
|
22993
|
+
return {
|
|
22994
|
+
status: "malformed",
|
|
22995
|
+
path: file,
|
|
22996
|
+
entries: [],
|
|
22997
|
+
revision: workspacesRevision(raw),
|
|
22998
|
+
error: `${file} is not valid JSON`
|
|
22999
|
+
};
|
|
22680
23000
|
}
|
|
22681
|
-
|
|
22682
|
-
return {
|
|
22683
|
-
status: "valid",
|
|
22684
|
-
path: file,
|
|
22685
|
-
entries: Array.isArray(list) ? list : []
|
|
22686
|
-
};
|
|
23001
|
+
return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
|
|
22687
23002
|
};
|
|
22688
|
-
var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
|
|
22689
23003
|
var globToRegExp = (glob) => {
|
|
22690
23004
|
let out = "";
|
|
22691
23005
|
for (let i = 0;i < glob.length; i++) {
|
|
@@ -22720,6 +23034,30 @@ var globToRegExp = (glob) => {
|
|
|
22720
23034
|
return new RegExp(`^${out}$`);
|
|
22721
23035
|
};
|
|
22722
23036
|
var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
|
|
23037
|
+
var UNSUPPORTED_GLOB = /[?[\]{}]/;
|
|
23038
|
+
var UNSUPPORTED_EXTGLOB = /[@+*]\(|!/;
|
|
23039
|
+
var validateWorkspaceGlob = (glob) => {
|
|
23040
|
+
const trimmed = glob.trim();
|
|
23041
|
+
if (!trimmed)
|
|
23042
|
+
return { ok: false, error: "workspace pattern is required" };
|
|
23043
|
+
const m = UNSUPPORTED_GLOB.exec(trimmed);
|
|
23044
|
+
if (m) {
|
|
23045
|
+
return {
|
|
23046
|
+
ok: false,
|
|
23047
|
+
error: `unsupported glob character ${JSON.stringify(m[0])} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
|
|
23048
|
+
};
|
|
23049
|
+
}
|
|
23050
|
+
const ext = UNSUPPORTED_EXTGLOB.exec(trimmed);
|
|
23051
|
+
if (ext) {
|
|
23052
|
+
const token = ext[0];
|
|
23053
|
+
const kind = token === "!" ? "negation (!)" : `extglob (${token})`;
|
|
23054
|
+
return {
|
|
23055
|
+
ok: false,
|
|
23056
|
+
error: `unsupported glob ${kind} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
|
|
23057
|
+
};
|
|
23058
|
+
}
|
|
23059
|
+
return { ok: true };
|
|
23060
|
+
};
|
|
22723
23061
|
var realpathOf = (p) => {
|
|
22724
23062
|
const real = realpathSync2.native ?? realpathSync2;
|
|
22725
23063
|
try {
|
|
@@ -22737,29 +23075,137 @@ var canonicalGlob = (glob) => {
|
|
|
22737
23075
|
const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
|
|
22738
23076
|
return real + (prefix.endsWith("/") ? "/" : "") + rest;
|
|
22739
23077
|
};
|
|
22740
|
-
var resolveWorkspaceFrom = (cwd, dir) => {
|
|
23078
|
+
var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
|
|
23079
|
+
const result = readWorkspacesResult(dir);
|
|
23080
|
+
if (result.status === "malformed" || result.status === "invalid")
|
|
23081
|
+
throw new Error(result.error);
|
|
22741
23082
|
const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
|
|
22742
|
-
|
|
22743
|
-
|
|
22744
|
-
continue;
|
|
22745
|
-
const ws = entry;
|
|
22746
|
-
if (typeof ws.glob !== "string" || !ws.glob)
|
|
22747
|
-
continue;
|
|
23083
|
+
const matches = [];
|
|
23084
|
+
for (const ws of result.entries) {
|
|
22748
23085
|
const glob = ws.glob.replaceAll("\\", "/");
|
|
22749
23086
|
const canonical = canonicalGlob(glob);
|
|
23087
|
+
let matched = false;
|
|
22750
23088
|
for (const target of targets) {
|
|
22751
|
-
if (matchWorkspace(glob, target))
|
|
22752
|
-
|
|
22753
|
-
|
|
22754
|
-
|
|
23089
|
+
if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
|
|
23090
|
+
matched = true;
|
|
23091
|
+
break;
|
|
23092
|
+
}
|
|
22755
23093
|
}
|
|
23094
|
+
if (matched)
|
|
23095
|
+
matches.push(ws);
|
|
22756
23096
|
}
|
|
22757
|
-
|
|
23097
|
+
if (workspaceName !== undefined) {
|
|
23098
|
+
const selected = matches.find((ws) => ws.name === workspaceName);
|
|
23099
|
+
if (!selected && matches.length > 0) {
|
|
23100
|
+
const matchingNames = matches.map((ws) => ws.name);
|
|
23101
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matchingNames.join(", ")}`);
|
|
23102
|
+
}
|
|
23103
|
+
if (selected)
|
|
23104
|
+
return selected;
|
|
23105
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
|
|
23106
|
+
}
|
|
23107
|
+
if (matches.length > 1) {
|
|
23108
|
+
throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${matches.map((ws) => ws.name).join(", ")}`);
|
|
23109
|
+
}
|
|
23110
|
+
return matches[0] ?? null;
|
|
23111
|
+
};
|
|
23112
|
+
var runtimeWorkspacePolicySchema = (kind) => {
|
|
23113
|
+
const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
|
|
23114
|
+
const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object4({
|
|
23115
|
+
provider: _enum2(["gitlab", "github"]),
|
|
23116
|
+
defaultTargetBranch: nonBlank.optional(),
|
|
23117
|
+
account: nonBlank.optional()
|
|
23118
|
+
}).passthrough();
|
|
23119
|
+
const profileSchema = object4({ [key]: policySchema.optional() }).passthrough();
|
|
23120
|
+
const entrySchema = object4({
|
|
23121
|
+
name: nonBlank,
|
|
23122
|
+
glob: nonBlank,
|
|
23123
|
+
[key]: policySchema.optional(),
|
|
23124
|
+
defaultProfile: nonBlank.optional(),
|
|
23125
|
+
profiles: record3(nonBlank, profileSchema).optional()
|
|
23126
|
+
}).passthrough();
|
|
23127
|
+
return entrySchema;
|
|
23128
|
+
};
|
|
23129
|
+
var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs");
|
|
23130
|
+
var runtimeWorkspaceIndexSchema = object4({
|
|
23131
|
+
workspaces: array3(object4({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
23132
|
+
}).passthrough();
|
|
23133
|
+
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
23134
|
+
const file = path6.join(configDir(), "workspaces.json");
|
|
23135
|
+
let raw;
|
|
23136
|
+
try {
|
|
23137
|
+
raw = readFileSync3(file, "utf8");
|
|
23138
|
+
} catch (error) {
|
|
23139
|
+
if (error.code === "ENOENT")
|
|
23140
|
+
return null;
|
|
23141
|
+
throw new Error(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
23142
|
+
}
|
|
23143
|
+
let parsed;
|
|
23144
|
+
try {
|
|
23145
|
+
parsed = JSON.parse(raw);
|
|
23146
|
+
} catch {
|
|
23147
|
+
throw new Error(`${file} is not valid JSON`);
|
|
23148
|
+
}
|
|
23149
|
+
const validated = runtimeWorkspaceIndexSchema.safeParse(parsed);
|
|
23150
|
+
if (!validated.success) {
|
|
23151
|
+
const detail = validated.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}`).join("; ");
|
|
23152
|
+
throw new Error(`${file} has invalid workspace matching data: ${detail}`);
|
|
23153
|
+
}
|
|
23154
|
+
const entries = validated.data.workspaces;
|
|
23155
|
+
for (const entry of entries ?? []) {
|
|
23156
|
+
const glob = validateWorkspaceGlob(entry.glob);
|
|
23157
|
+
if (!glob.ok)
|
|
23158
|
+
throw new Error(`${file} workspace ${entry.name}: ${glob.error}`);
|
|
23159
|
+
}
|
|
23160
|
+
const targets = [cwd, realpathOf(cwd)].map((value) => value.replaceAll("\\", "/"));
|
|
23161
|
+
const matches = (entries ?? []).filter((entry) => {
|
|
23162
|
+
const glob = entry.glob.replaceAll("\\", "/");
|
|
23163
|
+
const canonical = canonicalGlob(glob);
|
|
23164
|
+
return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
|
|
23165
|
+
});
|
|
23166
|
+
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
23167
|
+
let selected;
|
|
23168
|
+
if (workspaceName) {
|
|
23169
|
+
const named = matches.filter((entry) => entry.name === workspaceName);
|
|
23170
|
+
if (named.length === 1)
|
|
23171
|
+
selected = named[0];
|
|
23172
|
+
else if (named.length > 1)
|
|
23173
|
+
throw new Error(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
|
|
23174
|
+
else if (matches.length)
|
|
23175
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
|
|
23176
|
+
else
|
|
23177
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
|
|
23178
|
+
} else if (matches.length > 1)
|
|
23179
|
+
throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${matches.map((entry) => entry.name).join(", ")}`);
|
|
23180
|
+
else
|
|
23181
|
+
selected = matches[0];
|
|
23182
|
+
if (!selected)
|
|
23183
|
+
return null;
|
|
23184
|
+
const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
|
|
23185
|
+
if (!policy.success) {
|
|
23186
|
+
const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
|
|
23187
|
+
throw new Error(`${file} has invalid ${kind} policy configuration: ${detail}`);
|
|
23188
|
+
}
|
|
23189
|
+
return policy.data;
|
|
23190
|
+
};
|
|
23191
|
+
var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
|
|
23192
|
+
const candidate = resolveRuntimeWorkspaceCandidate(cwd, kind);
|
|
23193
|
+
if (!candidate)
|
|
23194
|
+
return { policy: undefined, source: "user-default" };
|
|
23195
|
+
const key = kind === "branch" ? "branchPolicy" : "commitPolicy";
|
|
23196
|
+
const profileName = requestedProfile?.trim() || candidate.defaultProfile;
|
|
23197
|
+
if (candidate.defaultProfile && !Object.hasOwn(candidate.profiles ?? {}, candidate.defaultProfile))
|
|
23198
|
+
throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
|
|
23199
|
+
const profile = profileName ? candidate.profiles?.[profileName] : undefined;
|
|
23200
|
+
if (profileName && !profile)
|
|
23201
|
+
throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
|
|
23202
|
+
const profilePolicy = profile?.[key];
|
|
23203
|
+
const workspacePolicy = candidate[key];
|
|
23204
|
+
return {
|
|
23205
|
+
policy: profilePolicy ?? workspacePolicy,
|
|
23206
|
+
source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
|
|
23207
|
+
};
|
|
22758
23208
|
};
|
|
22759
|
-
var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
|
|
22760
|
-
|
|
22761
|
-
// packages/workit-core/src/core/branch.ts
|
|
22762
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
22763
23209
|
|
|
22764
23210
|
// packages/workit-core/src/core/git.ts
|
|
22765
23211
|
import { execFileSync } from "node:child_process";
|
|
@@ -22833,7 +23279,6 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
22833
23279
|
import fs5 from "node:fs";
|
|
22834
23280
|
import path7 from "node:path";
|
|
22835
23281
|
import { spawnSync } from "node:child_process";
|
|
22836
|
-
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
22837
23282
|
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
|
|
22838
23283
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
22839
23284
|
var remoteProvider = (cwd) => {
|
|
@@ -22847,6 +23292,79 @@ var remoteProvider = (cwd) => {
|
|
|
22847
23292
|
return "gitlab";
|
|
22848
23293
|
return null;
|
|
22849
23294
|
};
|
|
23295
|
+
var remoteEndpoint = (remote) => {
|
|
23296
|
+
const raw = remote.trim();
|
|
23297
|
+
if (!raw)
|
|
23298
|
+
return null;
|
|
23299
|
+
const scp = /^(?:[^@\s]+@)?([^:]+):(.+)$/u.exec(raw);
|
|
23300
|
+
if (scp && !raw.includes("://"))
|
|
23301
|
+
return {
|
|
23302
|
+
host: scp[1].toLowerCase(),
|
|
23303
|
+
path: scp[2].replace(/^\//u, "").replace(/\.git$/u, ""),
|
|
23304
|
+
protocol: "ssh:",
|
|
23305
|
+
port: ""
|
|
23306
|
+
};
|
|
23307
|
+
try {
|
|
23308
|
+
const url = new URL(raw);
|
|
23309
|
+
return {
|
|
23310
|
+
host: url.hostname.toLowerCase(),
|
|
23311
|
+
path: url.pathname.replace(/^\//u, "").replace(/\.git$/u, "").replace(/\/$/u, ""),
|
|
23312
|
+
protocol: url.protocol,
|
|
23313
|
+
port: url.port
|
|
23314
|
+
};
|
|
23315
|
+
} catch {
|
|
23316
|
+
return null;
|
|
23317
|
+
}
|
|
23318
|
+
};
|
|
23319
|
+
var configuredSshEndpoint = (host) => {
|
|
23320
|
+
if (host.startsWith("-"))
|
|
23321
|
+
return null;
|
|
23322
|
+
const resolved = spawnSync("ssh", ["-G", host], {
|
|
23323
|
+
encoding: "utf8",
|
|
23324
|
+
timeout: 5000,
|
|
23325
|
+
maxBuffer: 1024 * 1024,
|
|
23326
|
+
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
23327
|
+
});
|
|
23328
|
+
if (resolved.status !== 0)
|
|
23329
|
+
return null;
|
|
23330
|
+
const output = resolved.stdout ?? "";
|
|
23331
|
+
const hostname = /^hostname\s+(\S+)/imu.exec(output)?.[1]?.toLowerCase();
|
|
23332
|
+
if (!hostname)
|
|
23333
|
+
return null;
|
|
23334
|
+
return { host: hostname, port: /^port\s+(\d+)/imu.exec(output)?.[1] ?? "22" };
|
|
23335
|
+
};
|
|
23336
|
+
var sshApiHostAliases = {
|
|
23337
|
+
"github.com": [{ host: "ssh.github.com", port: "443" }],
|
|
23338
|
+
"gitlab.com": [{ host: "altssh.gitlab.com", port: "443" }]
|
|
23339
|
+
};
|
|
23340
|
+
var hostingApiHostMatches = (remote, apiHost, resolveSshEndpoint = configuredSshEndpoint) => {
|
|
23341
|
+
const endpoint = remoteEndpoint(remote);
|
|
23342
|
+
if (!endpoint)
|
|
23343
|
+
return false;
|
|
23344
|
+
let expected;
|
|
23345
|
+
try {
|
|
23346
|
+
const url = new URL(`https://${apiHost}`);
|
|
23347
|
+
if (url.pathname !== "/" || url.search || url.hash || url.username || url.password)
|
|
23348
|
+
return false;
|
|
23349
|
+
expected = {
|
|
23350
|
+
host: url.hostname.toLowerCase().replace(/\.$/u, ""),
|
|
23351
|
+
port: url.port
|
|
23352
|
+
};
|
|
23353
|
+
} catch {
|
|
23354
|
+
return false;
|
|
23355
|
+
}
|
|
23356
|
+
if (endpoint.protocol !== "ssh:") {
|
|
23357
|
+
return endpoint.host.replace(/\.$/u, "") === expected.host && endpoint.port === expected.port;
|
|
23358
|
+
}
|
|
23359
|
+
const resolved = resolveSshEndpoint(endpoint.host);
|
|
23360
|
+
if (!resolved)
|
|
23361
|
+
return false;
|
|
23362
|
+
const hostname = resolved.host.toLowerCase().replace(/\.$/u, "");
|
|
23363
|
+
const port = endpoint.port || resolved.port;
|
|
23364
|
+
if (hostname === expected.host)
|
|
23365
|
+
return port === (expected.port || "22");
|
|
23366
|
+
return Boolean(sshApiHostAliases[expected.host]?.some((alias) => alias.host === hostname && alias.port === port && (!expected.port || expected.port === port)));
|
|
23367
|
+
};
|
|
22850
23368
|
var readVcsConfig = () => {
|
|
22851
23369
|
const file = vcsConfigPath();
|
|
22852
23370
|
let raw;
|
|
@@ -22867,7 +23385,7 @@ var readVcsConfig = () => {
|
|
|
22867
23385
|
return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
|
|
22868
23386
|
};
|
|
22869
23387
|
function vcsConfig(mode, cwd) {
|
|
22870
|
-
const ws =
|
|
23388
|
+
const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
|
|
22871
23389
|
const wsVcs = ws?.vcs ?? {};
|
|
22872
23390
|
const wsYt = ws?.youtrack ?? {};
|
|
22873
23391
|
const wsIssues = ws?.issues ?? {};
|
|
@@ -22880,8 +23398,8 @@ function vcsConfig(mode, cwd) {
|
|
|
22880
23398
|
const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
|
|
22881
23399
|
const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
|
|
22882
23400
|
const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
|
|
22883
|
-
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
22884
|
-
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
23401
|
+
const linkIssues = provider === "gitlab" && typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
23402
|
+
const youtrackBaseUrl = provider === "gitlab" && typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
22885
23403
|
let issuesProvider = null;
|
|
22886
23404
|
let linkOnPr = null;
|
|
22887
23405
|
if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
|
|
@@ -22905,9 +23423,8 @@ function vcsConfig(mode, cwd) {
|
|
|
22905
23423
|
if (cfgStatus === "malformed") {
|
|
22906
23424
|
return { ok: false, error: cfgError, configPath: cfgPath };
|
|
22907
23425
|
}
|
|
22908
|
-
if (cfgStatus === "missing")
|
|
23426
|
+
if (cfgStatus === "missing" && provider === null)
|
|
22909
23427
|
return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
|
|
22910
|
-
}
|
|
22911
23428
|
if (provider === null) {
|
|
22912
23429
|
return {
|
|
22913
23430
|
ok: false,
|
|
@@ -22916,24 +23433,12 @@ function vcsConfig(mode, cwd) {
|
|
|
22916
23433
|
};
|
|
22917
23434
|
}
|
|
22918
23435
|
const prov = cfg[provider] ?? {};
|
|
22919
|
-
const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
|
|
22920
|
-
const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path7.join(configDir(), `${provider}.token`));
|
|
22921
|
-
const tokenPath = path7.resolve(tokenFile);
|
|
22922
|
-
let tokenOk = false;
|
|
22923
|
-
if (fs5.existsSync(tokenPath)) {
|
|
22924
|
-
const token = fs5.readFileSync(tokenPath, "utf8").trim();
|
|
22925
|
-
const placeholder = !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
|
|
22926
|
-
tokenOk = !placeholder;
|
|
22927
|
-
}
|
|
22928
23436
|
const out = {
|
|
22929
23437
|
ok: true,
|
|
22930
23438
|
configPath: path7.resolve(cfgPath),
|
|
22931
23439
|
provider,
|
|
22932
23440
|
defaultTargetBranch: defaultTarget,
|
|
22933
23441
|
pr: cfg.pr ?? {},
|
|
22934
|
-
tokenPath,
|
|
22935
|
-
tokenPresent: fs5.existsSync(tokenPath),
|
|
22936
|
-
tokenReady: tokenOk,
|
|
22937
23442
|
workspace_name: ws?.name ?? null,
|
|
22938
23443
|
link_issues: linkIssues,
|
|
22939
23444
|
youtrack_base_url: youtrackBaseUrl,
|
|
@@ -22948,16 +23453,58 @@ function vcsConfig(mode, cwd) {
|
|
|
22948
23453
|
} else if (provider === "github") {
|
|
22949
23454
|
out.github = { host: prov.host ?? "github.com" };
|
|
22950
23455
|
}
|
|
22951
|
-
if (mode === "summary")
|
|
22952
|
-
delete out.tokenPath;
|
|
22953
23456
|
return out;
|
|
22954
23457
|
}
|
|
23458
|
+
function vcsCliIdentity(cwd) {
|
|
23459
|
+
const root = vcsCwd(cwd);
|
|
23460
|
+
const cfg = vcsConfig("load", root);
|
|
23461
|
+
if (!cfg.ok)
|
|
23462
|
+
return { ok: false, error: cfg.error ?? "vcs config not ready" };
|
|
23463
|
+
const provider = cfg.provider;
|
|
23464
|
+
if (provider !== "gitlab" && provider !== "github")
|
|
23465
|
+
return { ok: false, error: `unsupported provider: ${provider}` };
|
|
23466
|
+
const bin = provider === "gitlab" ? "glab" : "gh";
|
|
23467
|
+
const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
|
|
23468
|
+
const env = {
|
|
23469
|
+
...process.env,
|
|
23470
|
+
PATH: process.env.PATH ?? "",
|
|
23471
|
+
...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
|
|
23472
|
+
};
|
|
23473
|
+
const result = spawnSync(bin, ["api", "user"], {
|
|
23474
|
+
cwd: root,
|
|
23475
|
+
encoding: "utf8",
|
|
23476
|
+
env
|
|
23477
|
+
});
|
|
23478
|
+
if (result.status !== 0)
|
|
23479
|
+
return { ok: false, provider, error: `${bin} is missing or not authenticated` };
|
|
23480
|
+
try {
|
|
23481
|
+
const user = JSON.parse(result.stdout ?? "");
|
|
23482
|
+
const username = provider === "gitlab" ? user.username : user.login;
|
|
23483
|
+
if (typeof username !== "string" || !username)
|
|
23484
|
+
return { ok: false, provider, error: `invalid ${bin} identity response` };
|
|
23485
|
+
const expected = resolveRuntimeWorkspaceVcs(root)?.vcs?.account;
|
|
23486
|
+
if (expected && username.toLowerCase() !== expected.toLowerCase())
|
|
23487
|
+
return {
|
|
23488
|
+
ok: false,
|
|
23489
|
+
provider,
|
|
23490
|
+
error: `${bin} account ${username} does not match ${expected}`
|
|
23491
|
+
};
|
|
23492
|
+
return { ok: true, provider, host, username, name: user.name };
|
|
23493
|
+
} catch {
|
|
23494
|
+
return { ok: false, provider, error: `invalid JSON from ${bin} api user` };
|
|
23495
|
+
}
|
|
23496
|
+
}
|
|
22955
23497
|
function mergedPrStyle(limit = 6, cwd) {
|
|
22956
23498
|
const cfg = vcsConfig("load", cwd);
|
|
22957
|
-
if (!cfg.ok
|
|
23499
|
+
if (!cfg.ok)
|
|
22958
23500
|
return { ok: false, error: "vcs not configured" };
|
|
22959
23501
|
const provider = cfg.provider;
|
|
22960
|
-
const
|
|
23502
|
+
const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
|
|
23503
|
+
const env = {
|
|
23504
|
+
...process.env,
|
|
23505
|
+
PATH: process.env.PATH ?? "",
|
|
23506
|
+
...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
|
|
23507
|
+
};
|
|
22961
23508
|
const examples = [];
|
|
22962
23509
|
const descInfo = (desc, caseInsensitiveNotes = false) => ({
|
|
22963
23510
|
hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
|
|
@@ -22965,23 +23512,30 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
22965
23512
|
`).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
|
|
22966
23513
|
descriptionPreview: desc.slice(0, 600)
|
|
22967
23514
|
});
|
|
23515
|
+
const remoteResult = spawnSync("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
23516
|
+
cwd,
|
|
23517
|
+
encoding: "utf8"
|
|
23518
|
+
});
|
|
23519
|
+
const remotes = (remoteResult.stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
|
|
23520
|
+
if (remotes.length === 0)
|
|
23521
|
+
return { ok: false, error: "no origin remote" };
|
|
23522
|
+
if (remotes.length !== 1)
|
|
23523
|
+
return { ok: false, error: "origin has ambiguous push destinations" };
|
|
23524
|
+
const remote = remotes[0];
|
|
23525
|
+
const endpoint = remoteEndpoint(remote);
|
|
23526
|
+
if (!endpoint?.path)
|
|
23527
|
+
return { ok: false, error: "hosting repository could not be resolved" };
|
|
23528
|
+
if (!hostingApiHostMatches(remote, host))
|
|
23529
|
+
return { ok: false, error: "PR style target does not match the provider host" };
|
|
22968
23530
|
if (provider === "gitlab") {
|
|
22969
|
-
const
|
|
22970
|
-
|
|
22971
|
-
|
|
22972
|
-
|
|
22973
|
-
|
|
22974
|
-
if (!m)
|
|
22975
|
-
return { ok: false, error: "not a gitlab.com origin" };
|
|
22976
|
-
const project = m[1];
|
|
22977
|
-
const env = { ...process.env, GITLAB_TOKEN: token };
|
|
22978
|
-
const run = (args) => spawnSync("glab", ["api", ...args], { cwd, encoding: "utf8", env });
|
|
23531
|
+
const run = (args) => spawnSync("glab", ["api", ...args], {
|
|
23532
|
+
cwd,
|
|
23533
|
+
encoding: "utf8",
|
|
23534
|
+
env
|
|
23535
|
+
});
|
|
22979
23536
|
let r = run([
|
|
22980
|
-
`projects/${
|
|
23537
|
+
`projects/${endpoint.path.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
|
|
22981
23538
|
]);
|
|
22982
|
-
if (r.status !== 0) {
|
|
22983
|
-
r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
|
|
22984
|
-
}
|
|
22985
23539
|
if (r.status !== 0)
|
|
22986
23540
|
return { ok: false, error: "could not list merge requests" };
|
|
22987
23541
|
for (const mr of JSON.parse(r.stdout ?? "[]")) {
|
|
@@ -22994,7 +23548,18 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
22994
23548
|
});
|
|
22995
23549
|
}
|
|
22996
23550
|
} else if (provider === "github") {
|
|
22997
|
-
const r = spawnSync("gh", [
|
|
23551
|
+
const r = spawnSync("gh", [
|
|
23552
|
+
"pr",
|
|
23553
|
+
"list",
|
|
23554
|
+
"--repo",
|
|
23555
|
+
`${host}/${endpoint.path}`,
|
|
23556
|
+
"--state",
|
|
23557
|
+
"merged",
|
|
23558
|
+
"--limit",
|
|
23559
|
+
String(limit),
|
|
23560
|
+
"--json",
|
|
23561
|
+
"title,url,body"
|
|
23562
|
+
], { cwd, encoding: "utf8", env });
|
|
22998
23563
|
if (r.status !== 0)
|
|
22999
23564
|
return { ok: false, error: "could not list pull requests" };
|
|
23000
23565
|
for (const pr of JSON.parse(r.stdout ?? "[]")) {
|
|
@@ -23016,26 +23581,28 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
23016
23581
|
}
|
|
23017
23582
|
|
|
23018
23583
|
// packages/workit-core/src/core/branch.ts
|
|
23019
|
-
|
|
23584
|
+
function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
23585
|
+
const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
23586
|
+
const config = readConfig();
|
|
23587
|
+
return kind === "branch" ? {
|
|
23588
|
+
branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
|
|
23589
|
+
provenance: { branchPolicy: selected.source }
|
|
23590
|
+
} : {
|
|
23591
|
+
commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
|
|
23592
|
+
provenance: { commitPolicy: selected.source }
|
|
23593
|
+
};
|
|
23594
|
+
}
|
|
23595
|
+
var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
|
|
23020
23596
|
var verifyPushIdentity = (root, provider, account) => {
|
|
23021
23597
|
if (!account || typeof account !== "string")
|
|
23022
23598
|
return { ok: false, error: "push identity requires a configured area account" };
|
|
23023
|
-
const
|
|
23024
|
-
|
|
23025
|
-
|
|
23026
|
-
|
|
23027
|
-
login = execFileSync2(bin, [...probe], {
|
|
23028
|
-
cwd: root,
|
|
23029
|
-
encoding: "utf8",
|
|
23030
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
23031
|
-
}).trim();
|
|
23032
|
-
} catch {
|
|
23033
|
-
return { ok: false, error: `${bin} identity could not be resolved` };
|
|
23034
|
-
}
|
|
23035
|
-
if (!login || login.toLowerCase() !== account.toLowerCase())
|
|
23599
|
+
const identity = vcsCliIdentity(root);
|
|
23600
|
+
if (!identity.ok || identity.provider !== provider)
|
|
23601
|
+
return { ok: false, error: identity.error ?? "hosting CLI identity could not be resolved" };
|
|
23602
|
+
if (identity.username?.toLowerCase() !== account.toLowerCase())
|
|
23036
23603
|
return {
|
|
23037
23604
|
ok: false,
|
|
23038
|
-
error: `push identity ${
|
|
23605
|
+
error: `push identity ${identity.username || "(unknown)"} does not match area account ${account}`
|
|
23039
23606
|
};
|
|
23040
23607
|
return { ok: true };
|
|
23041
23608
|
};
|
|
@@ -23053,7 +23620,7 @@ var operationAutoClass = (operation) => typeof operation === "string" ? OPERATIO
|
|
|
23053
23620
|
var digestOf = (configDirPath) => {
|
|
23054
23621
|
try {
|
|
23055
23622
|
const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
|
|
23056
|
-
return
|
|
23623
|
+
return createHash4("sha256").update(raw).digest("hex");
|
|
23057
23624
|
} catch {
|
|
23058
23625
|
return null;
|
|
23059
23626
|
}
|
|
@@ -23091,14 +23658,30 @@ var resolveAutoApproval = (root, dir = configDir()) => {
|
|
|
23091
23658
|
};
|
|
23092
23659
|
};
|
|
23093
23660
|
var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
|
|
23094
|
-
var
|
|
23661
|
+
var targetOf = (root, descriptor) => {
|
|
23662
|
+
try {
|
|
23663
|
+
const parsed = typeof descriptor === "string" ? JSON.parse(descriptor) : descriptor;
|
|
23664
|
+
const cwd = parsed?.payload?.cwd;
|
|
23665
|
+
if (cwd === undefined)
|
|
23666
|
+
return root;
|
|
23667
|
+
if (typeof cwd !== "string" || !path8.isAbsolute(cwd))
|
|
23668
|
+
return null;
|
|
23669
|
+
return realpathSync3(cwd);
|
|
23670
|
+
} catch {
|
|
23671
|
+
return null;
|
|
23672
|
+
}
|
|
23673
|
+
};
|
|
23674
|
+
var standingApprovalLive = (root, receipt, cls, dir, descriptor) => {
|
|
23095
23675
|
if (!isStandingReceipt(receipt))
|
|
23096
23676
|
return false;
|
|
23097
23677
|
if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
|
|
23098
23678
|
return false;
|
|
23099
23679
|
if (receipt.class !== cls)
|
|
23100
23680
|
return false;
|
|
23101
|
-
const
|
|
23681
|
+
const target = targetOf(root, descriptor);
|
|
23682
|
+
if (!target)
|
|
23683
|
+
return false;
|
|
23684
|
+
const resolved = resolveAutoApproval(target, dir);
|
|
23102
23685
|
if (resolved.status !== "on")
|
|
23103
23686
|
return false;
|
|
23104
23687
|
if (resolved.workspace !== receipt.workspace)
|
|
@@ -23117,9 +23700,11 @@ var standingReceiptFor = (root, workspace, cls, dir) => {
|
|
|
23117
23700
|
var verifyStandingApproval = (root, task, caller, binding) => {
|
|
23118
23701
|
if (task.status !== "active")
|
|
23119
23702
|
return failure("invalid_transition", "only active tasks can authorize actions");
|
|
23120
|
-
const
|
|
23121
|
-
if (!
|
|
23122
|
-
return
|
|
23703
|
+
const workspace = new TaskStore(root).readWorkspace();
|
|
23704
|
+
if (!workspace.ok)
|
|
23705
|
+
return workspace;
|
|
23706
|
+
if (!workspace.data || !currentWriterOwnsTask(task, workspace.data, caller))
|
|
23707
|
+
return failure("permission_denied", "standing approval requires the current writer session");
|
|
23123
23708
|
const standing = binding.standing;
|
|
23124
23709
|
if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
|
|
23125
23710
|
return failure("invalid_input", "standing approval needs a workspace and class");
|
|
@@ -23132,16 +23717,19 @@ var verifyStandingApproval = (root, task, caller, binding) => {
|
|
|
23132
23717
|
const cls = operationAutoClass(operation);
|
|
23133
23718
|
if (!cls || cls !== standing.class)
|
|
23134
23719
|
return failure("permission_denied", "standing approval does not cover this operation");
|
|
23135
|
-
const
|
|
23720
|
+
const target = targetOf(root, binding.approvedContent);
|
|
23721
|
+
if (!target)
|
|
23722
|
+
return failure("permission_denied", "standing action target is invalid");
|
|
23723
|
+
const resolved = resolveAutoApproval(target);
|
|
23136
23724
|
if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
|
|
23137
23725
|
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
23138
23726
|
if (cls === "push") {
|
|
23139
|
-
const workspace = resolveWorkspaceFrom(
|
|
23727
|
+
const workspace = resolveWorkspaceFrom(target, configDir());
|
|
23140
23728
|
const provider = workspace?.vcs?.provider;
|
|
23141
23729
|
const account = resolved.account;
|
|
23142
23730
|
if (!provider || !account)
|
|
23143
23731
|
return failure("permission_denied", "auto-push requires a workspace provider and area account");
|
|
23144
|
-
const identity = verifyPushIdentity(
|
|
23732
|
+
const identity = verifyPushIdentity(target, provider, account);
|
|
23145
23733
|
if (!identity.ok)
|
|
23146
23734
|
return failure("permission_denied", identity.error);
|
|
23147
23735
|
}
|
|
@@ -23150,7 +23738,7 @@ var verifyStandingApproval = (root, task, caller, binding) => {
|
|
|
23150
23738
|
host: caller.host,
|
|
23151
23739
|
session: { kind: "host", host: caller.host, handle: caller.actor },
|
|
23152
23740
|
workerId: null,
|
|
23153
|
-
receipts: [standingReceiptFor(
|
|
23741
|
+
receipts: [standingReceiptFor(target, standing.workspace, cls)]
|
|
23154
23742
|
});
|
|
23155
23743
|
};
|
|
23156
23744
|
|
|
@@ -23301,7 +23889,7 @@ function applicableDecision(task, purpose, binding, checkoutRoot) {
|
|
|
23301
23889
|
return [];
|
|
23302
23890
|
return task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, entry.data.binding).ok : entry.data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map((entry) => entry.data);
|
|
23303
23891
|
}
|
|
23304
|
-
var digestBytes2 = (bytes) =>
|
|
23892
|
+
var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
23305
23893
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
23306
23894
|
let root;
|
|
23307
23895
|
try {
|
|
@@ -23314,23 +23902,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
23314
23902
|
continue;
|
|
23315
23903
|
if (!reference.digest)
|
|
23316
23904
|
return failure("invalid_input", "document references require a byte digest");
|
|
23317
|
-
const target =
|
|
23318
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
23905
|
+
const target = path9.resolve(root, reference.path);
|
|
23906
|
+
if (target !== root && !target.startsWith(`${root}${path9.sep}`))
|
|
23319
23907
|
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
23320
23908
|
fields: [
|
|
23321
23909
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
23322
23910
|
]
|
|
23323
23911
|
});
|
|
23324
23912
|
try {
|
|
23325
|
-
const relative =
|
|
23913
|
+
const relative = path9.relative(root, target);
|
|
23326
23914
|
let current = root;
|
|
23327
|
-
for (const segment of relative.split(
|
|
23915
|
+
for (const segment of relative.split(path9.sep)) {
|
|
23328
23916
|
if (!segment)
|
|
23329
23917
|
continue;
|
|
23330
|
-
current =
|
|
23918
|
+
current = path9.join(current, segment);
|
|
23331
23919
|
const stat = fs6.lstatSync(current);
|
|
23332
23920
|
const real = fs6.realpathSync(current);
|
|
23333
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
23921
|
+
if (real !== root && !real.startsWith(`${root}${path9.sep}`))
|
|
23334
23922
|
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
23335
23923
|
fields: [
|
|
23336
23924
|
{
|
|
@@ -23356,6 +23944,20 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
23356
23944
|
};
|
|
23357
23945
|
var verifyContentRefs = (store, binding) => verifyDecisionContentAtRoot(store.root, binding);
|
|
23358
23946
|
var storedDecisionApplicable = (store, task, purpose, binding) => task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && verifyDecisionContentAtRoot(store.root, entry.data.binding).ok);
|
|
23947
|
+
var approvalAuthorityMatches = (store, task, approval, current) => {
|
|
23948
|
+
if (approval.kind !== "host_observed" || approval.receipts.length === 0)
|
|
23949
|
+
return false;
|
|
23950
|
+
if (provenanceMatches(approval, current))
|
|
23951
|
+
return true;
|
|
23952
|
+
const session = current.kind === "host_observed" ? current.session : null;
|
|
23953
|
+
if (!session || session.kind !== "host")
|
|
23954
|
+
return false;
|
|
23955
|
+
const workspace = store.readWorkspace();
|
|
23956
|
+
return workspace.ok && workspace.data !== null && currentWriterOwnsTask(task, workspace.data, {
|
|
23957
|
+
host: session.host,
|
|
23958
|
+
actor: session.handle
|
|
23959
|
+
});
|
|
23960
|
+
};
|
|
23359
23961
|
var validateAction = (store, task, workspaceId, input, authority) => {
|
|
23360
23962
|
if (task.status !== "active")
|
|
23361
23963
|
return failure("invalid_transition", "only active tasks can authorize actions");
|
|
@@ -23381,7 +23983,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
|
|
|
23381
23983
|
return failure("permission_denied", "native reservation authority does not match decision");
|
|
23382
23984
|
if (decision.response !== "approved")
|
|
23383
23985
|
return failure("permission_denied", "decision was rejected");
|
|
23384
|
-
if (
|
|
23986
|
+
if (!approvalAuthorityMatches(store, task, entry.provenance, authority.provenance))
|
|
23385
23987
|
return failure("permission_denied", "action approval lacks native receipt assurance");
|
|
23386
23988
|
const receipts = entry.provenance.receipts;
|
|
23387
23989
|
const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
|
|
@@ -23394,7 +23996,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
|
|
|
23394
23996
|
operation = null;
|
|
23395
23997
|
}
|
|
23396
23998
|
const cls = operationAutoClass(operation);
|
|
23397
|
-
if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
|
|
23999
|
+
if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls, undefined, decision.binding.approvedContent)))
|
|
23398
24000
|
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
23399
24001
|
}
|
|
23400
24002
|
if (decision.revoked)
|
|
@@ -23548,7 +24150,7 @@ function settleAction(input) {
|
|
|
23548
24150
|
return failure("not_found", "decision not found");
|
|
23549
24151
|
if (!authorityDecisionMatches(authority, entry.data))
|
|
23550
24152
|
return failure("permission_denied", "native settlement authority does not match decision");
|
|
23551
|
-
if (!
|
|
24153
|
+
if (!approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
|
|
23552
24154
|
return failure("permission_denied", "native settlement caller does not match approval");
|
|
23553
24155
|
if (entry.data.purpose !== "action" || entry.data.digest !== decisionDigest(entry.data))
|
|
23554
24156
|
return failure("permission_denied", "decision binding is invalid");
|
|
@@ -23647,7 +24249,7 @@ function reconcileAction(input) {
|
|
|
23647
24249
|
const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
|
|
23648
24250
|
if (!entry || entry.data.purpose !== "action")
|
|
23649
24251
|
return failure("not_found", "decision not found");
|
|
23650
|
-
if (!authorityDecisionMatches(authority, entry.data) || !
|
|
24252
|
+
if (!authorityDecisionMatches(authority, entry.data) || !approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
|
|
23651
24253
|
return failure("permission_denied", "native reconciliation caller does not match approval");
|
|
23652
24254
|
if (entry.data.response !== "approved" || entry.data.revoked !== null || entry.data.digest !== decisionDigest(entry.data) || entry.data.binding.taskId !== current.id || entry.data.binding.workspaceId !== current.workspaceId || entry.data.binding.workspaceId !== workspace.data.id)
|
|
23653
24255
|
return failure("permission_denied", "reconciliation decision binding is invalid");
|
|
@@ -23696,15 +24298,15 @@ var bindingCovers = scopeCovers;
|
|
|
23696
24298
|
var verifyDecisionContent = verifyContentRefs;
|
|
23697
24299
|
|
|
23698
24300
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
23699
|
-
var digestBytes3 = (value) =>
|
|
23700
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
23701
|
-
var relative = (root, candidate) =>
|
|
24301
|
+
var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
24302
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
|
|
24303
|
+
var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
|
|
23702
24304
|
var canonicalPath = (value) => {
|
|
23703
24305
|
if (typeof value !== "string" || !value)
|
|
23704
24306
|
return null;
|
|
23705
24307
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
23706
24308
|
return null;
|
|
23707
|
-
const normalized =
|
|
24309
|
+
const normalized = path10.posix.normalize(value);
|
|
23708
24310
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
23709
24311
|
return null;
|
|
23710
24312
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -23752,14 +24354,14 @@ var gitPaths = (root) => {
|
|
|
23752
24354
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
23753
24355
|
};
|
|
23754
24356
|
}
|
|
23755
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
24357
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
|
|
23756
24358
|
const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
23757
24359
|
if (stagedDeleted.status !== 0) {
|
|
23758
24360
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
23759
24361
|
if (!/not a git repository/i.test(stderr))
|
|
23760
24362
|
return { paths, uncertain: true, git: true };
|
|
23761
24363
|
} else if (stagedDeleted.stdout) {
|
|
23762
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
24364
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
|
|
23763
24365
|
}
|
|
23764
24366
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
23765
24367
|
};
|
|
@@ -23774,7 +24376,7 @@ var walk = (root, directory, output) => {
|
|
|
23774
24376
|
for (const name of names) {
|
|
23775
24377
|
if (name === ".git" || name === ".workit")
|
|
23776
24378
|
continue;
|
|
23777
|
-
const target =
|
|
24379
|
+
const target = path10.join(directory, name);
|
|
23778
24380
|
let stat;
|
|
23779
24381
|
try {
|
|
23780
24382
|
stat = fs7.lstatSync(target);
|
|
@@ -23792,12 +24394,12 @@ var walk = (root, directory, output) => {
|
|
|
23792
24394
|
var scopeRoots = (root, scope) => {
|
|
23793
24395
|
const roots = [];
|
|
23794
24396
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
23795
|
-
const target =
|
|
24397
|
+
const target = path10.resolve(root, item);
|
|
23796
24398
|
if (!inside(root, target))
|
|
23797
24399
|
return failure("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
23798
24400
|
let ancestor = target;
|
|
23799
24401
|
while (!fs7.existsSync(ancestor) && ancestor !== root)
|
|
23800
|
-
ancestor =
|
|
24402
|
+
ancestor = path10.dirname(ancestor);
|
|
23801
24403
|
try {
|
|
23802
24404
|
if (!inside(root, fs7.realpathSync(ancestor)))
|
|
23803
24405
|
return failure("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
@@ -23849,7 +24451,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
23849
24451
|
for (const item of [...names].sort()) {
|
|
23850
24452
|
if (!scopeMatches(item, normalizedScope))
|
|
23851
24453
|
continue;
|
|
23852
|
-
const target =
|
|
24454
|
+
const target = path10.join(checkout, item);
|
|
23853
24455
|
try {
|
|
23854
24456
|
const stat = fs7.lstatSync(target);
|
|
23855
24457
|
if (stat.isSymbolicLink()) {
|
|
@@ -23984,7 +24586,7 @@ var resolveBinding = (evidence, pin, currentCandidateId) => {
|
|
|
23984
24586
|
candidateId: evidence.candidateId ?? fallback
|
|
23985
24587
|
};
|
|
23986
24588
|
};
|
|
23987
|
-
var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId
|
|
24589
|
+
var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
|
|
23988
24590
|
var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
|
|
23989
24591
|
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
23990
24592
|
var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
|
|
@@ -24219,6 +24821,11 @@ var exportDigest = (bundle) => sha256({
|
|
|
24219
24821
|
task: bundle.task
|
|
24220
24822
|
});
|
|
24221
24823
|
// packages/workit-core/src/core/external-action.ts
|
|
24824
|
+
var chainStepSchema = union3([
|
|
24825
|
+
string5().min(1),
|
|
24826
|
+
object4({ branch: string5().min(1) }).strict(),
|
|
24827
|
+
object4({ pr: literal3(true) }).strict()
|
|
24828
|
+
]);
|
|
24222
24829
|
var externalActionSchema = discriminatedUnion2("operation", [
|
|
24223
24830
|
object4({
|
|
24224
24831
|
operation: literal3("git.branch_setup"),
|
|
@@ -24226,20 +24833,22 @@ var externalActionSchema = discriminatedUnion2("operation", [
|
|
|
24226
24833
|
action: _enum2(["setup", "reapply_stash"]).optional(),
|
|
24227
24834
|
sdd_dir: string5().optional(),
|
|
24228
24835
|
target_branch: string5().optional(),
|
|
24229
|
-
stash: _enum2(["yes", "no"]).optional()
|
|
24836
|
+
stash: _enum2(["yes", "no"]).optional(),
|
|
24837
|
+
cwd: string5().min(1).optional()
|
|
24230
24838
|
}).strict()
|
|
24231
24839
|
}).strict(),
|
|
24232
24840
|
object4({
|
|
24233
24841
|
operation: literal3("git.commit"),
|
|
24234
24842
|
payload: object4({
|
|
24235
24843
|
message: string5().min(1).optional(),
|
|
24236
|
-
plan_steps: array3(
|
|
24237
|
-
plan_branch: string5().min(1).optional()
|
|
24844
|
+
plan_steps: array3(chainStepSchema).min(1).max(32).optional(),
|
|
24845
|
+
plan_branch: string5().min(1).optional(),
|
|
24846
|
+
cwd: string5().min(1).optional()
|
|
24238
24847
|
}).strict()
|
|
24239
24848
|
}).strict(),
|
|
24240
24849
|
object4({
|
|
24241
24850
|
operation: literal3("git.push"),
|
|
24242
|
-
payload: object4({ branch: string5().min(1).optional() }).strict()
|
|
24851
|
+
payload: object4({ branch: string5().min(1).optional(), cwd: string5().min(1).optional() }).strict()
|
|
24243
24852
|
}).strict(),
|
|
24244
24853
|
object4({
|
|
24245
24854
|
operation: literal3("hosting.pull_request"),
|
|
@@ -24248,16 +24857,22 @@ var externalActionSchema = discriminatedUnion2("operation", [
|
|
|
24248
24857
|
body: string5().optional(),
|
|
24249
24858
|
draft: boolean5().optional(),
|
|
24250
24859
|
target_branch: string5().optional(),
|
|
24251
|
-
babysit: boolean5().optional()
|
|
24860
|
+
babysit: boolean5().optional(),
|
|
24861
|
+
cwd: string5().min(1).optional()
|
|
24252
24862
|
}).strict()
|
|
24253
24863
|
}).strict(),
|
|
24254
24864
|
object4({
|
|
24255
24865
|
operation: literal3("hosting.merge"),
|
|
24256
24866
|
payload: object4({
|
|
24257
24867
|
target_branch: string5().min(1).optional(),
|
|
24258
|
-
source_branch: string5().min(1).optional()
|
|
24868
|
+
source_branch: string5().min(1).optional(),
|
|
24869
|
+
cwd: string5().min(1).optional()
|
|
24259
24870
|
}).strict()
|
|
24260
24871
|
}).strict(),
|
|
24872
|
+
object4({
|
|
24873
|
+
operation: literal3("hosting.delete_branch"),
|
|
24874
|
+
payload: object4({ branch: string5().min(1), cwd: string5().min(1).optional() }).strict()
|
|
24875
|
+
}).strict(),
|
|
24261
24876
|
object4({
|
|
24262
24877
|
operation: literal3("youtrack.update"),
|
|
24263
24878
|
payload: object4({
|
|
@@ -24313,12 +24928,13 @@ var externalActionSchema = discriminatedUnion2("operation", [
|
|
|
24313
24928
|
issueRef: string5().optional(),
|
|
24314
24929
|
mode: string5().optional(),
|
|
24315
24930
|
specPath: string5().optional(),
|
|
24316
|
-
planPath: string5().optional()
|
|
24931
|
+
planPath: string5().optional(),
|
|
24932
|
+
cwd: string5().min(1).optional()
|
|
24317
24933
|
}).strict()
|
|
24318
24934
|
}).strict()
|
|
24319
24935
|
]);
|
|
24320
24936
|
// packages/workit-core/src/core/task-engine.ts
|
|
24321
|
-
import { realpathSync as
|
|
24937
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
24322
24938
|
|
|
24323
24939
|
// packages/workit-core/src/core/policy-resolver.ts
|
|
24324
24940
|
var resolverInputSchema = object4({
|
|
@@ -24719,6 +25335,7 @@ var verifyNativeWorker = (verifier, input, binding) => {
|
|
|
24719
25335
|
expectedWorkspaceRevision: expected.expectedWorkspaceRevision,
|
|
24720
25336
|
state: expected.state,
|
|
24721
25337
|
session: expected.session,
|
|
25338
|
+
report: expected.report ?? null,
|
|
24722
25339
|
provenance: result.data,
|
|
24723
25340
|
...binding
|
|
24724
25341
|
});
|
|
@@ -24729,7 +25346,7 @@ var takeWorker = (token, binding, expected) => {
|
|
|
24729
25346
|
verifiedWorkers.delete(token);
|
|
24730
25347
|
if (!value)
|
|
24731
25348
|
return null;
|
|
24732
|
-
if (value.owner !== binding.owner || value.store !== binding.store || value.root !== binding.root || value.root !== value.store.root || !sameValue(value.caller, binding.caller) || value.taskId !== expected.taskId || value.workerId !== expected.workerId || value.expectedRevision !== expected.expectedRevision || value.expectedWorkspaceRevision !== expected.expectedWorkspaceRevision || value.state !== expected.state || !sameValue(value.session, expected.session))
|
|
25349
|
+
if (value.owner !== binding.owner || value.store !== binding.store || value.root !== binding.root || value.root !== value.store.root || !sameValue(value.caller, binding.caller) || value.taskId !== expected.taskId || value.workerId !== expected.workerId || value.expectedRevision !== expected.expectedRevision || value.expectedWorkspaceRevision !== expected.expectedWorkspaceRevision || value.state !== expected.state || !sameValue(value.session, expected.session) || !sameValue(value.report, expected.report ?? null))
|
|
24733
25350
|
return null;
|
|
24734
25351
|
return value;
|
|
24735
25352
|
};
|
|
@@ -24770,6 +25387,8 @@ var applyWorkerLifecycle = (input) => {
|
|
|
24770
25387
|
return failure("invalid_transition", "worker already has an observed session");
|
|
24771
25388
|
if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
|
|
24772
25389
|
return failure("invalid_transition", "worker launch was not claimed");
|
|
25390
|
+
if (input.report && input.state !== "stopped")
|
|
25391
|
+
return failure("invalid_input", "worker reports require a stopped worker");
|
|
24773
25392
|
if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
|
|
24774
25393
|
return failure("invalid_input", "running worker observations require a session");
|
|
24775
25394
|
if (entry.data.session && !sameValue(entry.data.session, input.session))
|
|
@@ -24786,12 +25405,13 @@ var applyWorkerLifecycle = (input) => {
|
|
|
24786
25405
|
data: {
|
|
24787
25406
|
...entry.data,
|
|
24788
25407
|
state: terminalCancel ? "cancelling" : input.state,
|
|
24789
|
-
session: input.session
|
|
25408
|
+
session: input.session,
|
|
25409
|
+
report: entry.data.report ?? input.report ?? null
|
|
24790
25410
|
}
|
|
24791
25411
|
};
|
|
24792
25412
|
const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
24793
25413
|
const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
24794
|
-
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
25414
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && sameValue(entry.data.report, nextEntry.data.report) && !shouldClear && !shouldUncertain)
|
|
24795
25415
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
24796
25416
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
24797
25417
|
taskId: task.data.id,
|
|
@@ -24859,6 +25479,18 @@ var portableTask = (task) => {
|
|
|
24859
25479
|
};
|
|
24860
25480
|
for (const entry of clone.assessments)
|
|
24861
25481
|
demote(entry.data.facts, entry.data.signals, entry.data.consequences);
|
|
25482
|
+
const sourceDecisions = new Map(task.decisions.map((entry) => [entry.id, entry]));
|
|
25483
|
+
for (const entry of clone.decisions) {
|
|
25484
|
+
const consumption = sourceDecisions.get(entry.id)?.data.consumption;
|
|
25485
|
+
if (!consumption)
|
|
25486
|
+
continue;
|
|
25487
|
+
entry.data.consumption = {
|
|
25488
|
+
...consumption,
|
|
25489
|
+
state: consumption.state === "reserved" ? "uncertain" : consumption.state,
|
|
25490
|
+
actionRef: { kind: "record", collection: "decisions", id: entry.id },
|
|
25491
|
+
...consumption.historicalActionId ? { historicalActionId: consumption.historicalActionId } : consumption.actionRef.kind === "host" ? { historicalActionId: consumption.actionRef.handle } : {}
|
|
25492
|
+
};
|
|
25493
|
+
}
|
|
24862
25494
|
clone.candidates = [];
|
|
24863
25495
|
return clone;
|
|
24864
25496
|
};
|
|
@@ -24984,7 +25616,11 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
24984
25616
|
taskId: "",
|
|
24985
25617
|
workspaceId: destinationWorkspaceId
|
|
24986
25618
|
},
|
|
24987
|
-
consumption:
|
|
25619
|
+
consumption: data.consumption ? {
|
|
25620
|
+
...data.consumption,
|
|
25621
|
+
state: data.consumption.state === "reserved" ? "uncertain" : data.consumption.state,
|
|
25622
|
+
actionRef: { kind: "record", collection: "decisions", id: entry.id }
|
|
25623
|
+
} : null
|
|
24988
25624
|
}
|
|
24989
25625
|
};
|
|
24990
25626
|
}),
|
|
@@ -25025,7 +25661,7 @@ class WorkitCore {
|
|
|
25025
25661
|
}
|
|
25026
25662
|
contextRootError() {
|
|
25027
25663
|
try {
|
|
25028
|
-
if (
|
|
25664
|
+
if (realpathSync6(this.context.root) !== this.store.root)
|
|
25029
25665
|
return failure("invalid_input", "operation context root does not match the task store root");
|
|
25030
25666
|
} catch {
|
|
25031
25667
|
return failure("invalid_input", "operation context root cannot be resolved");
|
|
@@ -25184,7 +25820,7 @@ class WorkitCore {
|
|
|
25184
25820
|
return parsed;
|
|
25185
25821
|
const input = parsed.data;
|
|
25186
25822
|
if ((this.context.workerId ?? null) !== null) {
|
|
25187
|
-
if (input.action !== "inspect")
|
|
25823
|
+
if (input.action !== "inspect" && input.action !== "list")
|
|
25188
25824
|
return failure("permission_denied", "helpers cannot control task lifecycle or scope");
|
|
25189
25825
|
}
|
|
25190
25826
|
switch (input.action) {
|
|
@@ -25209,14 +25845,45 @@ class WorkitCore {
|
|
|
25209
25845
|
const listed = this.store.listTasks();
|
|
25210
25846
|
if (!listed.ok)
|
|
25211
25847
|
return listed;
|
|
25212
|
-
const
|
|
25213
|
-
|
|
25214
|
-
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
|
|
25848
|
+
const status = input.status ?? "open";
|
|
25849
|
+
const limit = input.limit ?? 20;
|
|
25850
|
+
const query = input.query?.toLowerCase().split(/\s+/);
|
|
25851
|
+
const selected = listed.data.filter((task) => status === "all" ? true : status === "closed" ? task.status === "closed" : task.status !== "closed").filter((task) => !query || query.every((term) => JSON.stringify({
|
|
25852
|
+
id: task.id,
|
|
25853
|
+
workspaceId: task.workspaceId,
|
|
25854
|
+
origin: task.origin,
|
|
25855
|
+
createdAt: task.createdAt,
|
|
25856
|
+
updatedAt: task.updatedAt,
|
|
25857
|
+
source: task.intent.provenance,
|
|
25858
|
+
objective: task.intent.data.objective,
|
|
25859
|
+
progress: task.progress,
|
|
25860
|
+
decisions: task.decisions.map(({ data }) => data)
|
|
25861
|
+
}).toLowerCase().includes(term))).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit);
|
|
25862
|
+
if (selected.length === 0)
|
|
25863
|
+
return success(null, null, []);
|
|
25864
|
+
const workspace = this.store.readWorkspace();
|
|
25865
|
+
if (!workspace.ok)
|
|
25866
|
+
return workspace;
|
|
25867
|
+
if (!workspace.data)
|
|
25868
|
+
return failure("not_found", "workspace not found");
|
|
25869
|
+
const tasks = selected.map((task) => ({
|
|
25870
|
+
id: task.id,
|
|
25871
|
+
revision: task.revision,
|
|
25872
|
+
workspaceRevision: workspace.data.revision,
|
|
25873
|
+
createdAt: task.createdAt,
|
|
25874
|
+
updatedAt: task.updatedAt,
|
|
25875
|
+
runtime: task.runtime ?? null,
|
|
25876
|
+
objective: task.intent.data.objective,
|
|
25877
|
+
status: task.status,
|
|
25878
|
+
closure: task.closure,
|
|
25879
|
+
progress: task.progress,
|
|
25880
|
+
writer: task.status === "closed" ? null : workspace.data.writer,
|
|
25881
|
+
source: {
|
|
25882
|
+
host: task.intent.provenance.host,
|
|
25883
|
+
kind: task.intent.provenance.kind
|
|
25884
|
+
}
|
|
25885
|
+
}));
|
|
25886
|
+
return success(null, null, tasks);
|
|
25220
25887
|
}
|
|
25221
25888
|
case "inspect": {
|
|
25222
25889
|
const task = this.store.readTask(input.taskId);
|
|
@@ -25225,7 +25892,7 @@ class WorkitCore {
|
|
|
25225
25892
|
const helper = this.helperTaskGuard(task.data, true);
|
|
25226
25893
|
if (!helper.ok)
|
|
25227
25894
|
return helper;
|
|
25228
|
-
if (input.view === "summary")
|
|
25895
|
+
if ((input.view ?? "summary") === "summary")
|
|
25229
25896
|
return this.summary(task.data);
|
|
25230
25897
|
return this.view(task.data);
|
|
25231
25898
|
}
|
|
@@ -25569,7 +26236,7 @@ class WorkitCore {
|
|
|
25569
26236
|
const evaluations = evaluateEvidence(task.data, current.data);
|
|
25570
26237
|
const verified = evidence.some((entry) => {
|
|
25571
26238
|
const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
|
|
25572
|
-
return findingVerificationPasses(finding.data.candidateId, {
|
|
26239
|
+
return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
|
|
25573
26240
|
kind: entry.data.kind,
|
|
25574
26241
|
candidateId: entry.data.candidateId,
|
|
25575
26242
|
status: evaluation?.status ?? "missing"
|
|
@@ -25649,7 +26316,18 @@ class WorkitCore {
|
|
|
25649
26316
|
if (task.data.status !== "active")
|
|
25650
26317
|
return failure("invalid_transition", "paused or closed tasks cannot assign workers");
|
|
25651
26318
|
if (!bindingCovers(task.data.intent.data.scope, input.assignment.scope))
|
|
25652
|
-
return failure("permission_denied", "worker assignment is outside task scope"
|
|
26319
|
+
return failure("permission_denied", "worker assignment is outside task scope", {
|
|
26320
|
+
fields: [
|
|
26321
|
+
{
|
|
26322
|
+
path: "assignment.scope",
|
|
26323
|
+
reason: `paths ${JSON.stringify(input.assignment.scope.paths)} must be contained by lead paths ${JSON.stringify(task.data.intent.data.scope.paths)}`
|
|
26324
|
+
},
|
|
26325
|
+
{
|
|
26326
|
+
path: "task.intent.scope",
|
|
26327
|
+
reason: `lead paths are ${JSON.stringify(task.data.intent.data.scope.paths)}`
|
|
26328
|
+
}
|
|
26329
|
+
]
|
|
26330
|
+
});
|
|
25653
26331
|
if (input.assignment.decisionIds.some((id) => !task.data.decisions.some((entry) => entry.id === id)))
|
|
25654
26332
|
return failure("invalid_input", "worker assignment references an unknown decision");
|
|
25655
26333
|
if (input.assignment.requirementIds.some((id) => !task.data.policy?.requirements.some((requirement) => requirement.id === id)))
|
|
@@ -25856,7 +26534,15 @@ class WorkitCore {
|
|
|
25856
26534
|
...item,
|
|
25857
26535
|
recordedAt: mutation.now,
|
|
25858
26536
|
provenance: attested.data,
|
|
25859
|
-
data: {
|
|
26537
|
+
data: {
|
|
26538
|
+
...item.data,
|
|
26539
|
+
state: "dispatching",
|
|
26540
|
+
coordinator: {
|
|
26541
|
+
kind: "host",
|
|
26542
|
+
host: this.context.caller.host,
|
|
26543
|
+
handle: this.context.caller.actor
|
|
26544
|
+
}
|
|
26545
|
+
}
|
|
25860
26546
|
} : item)
|
|
25861
26547
|
});
|
|
25862
26548
|
}
|
|
@@ -25932,6 +26618,7 @@ class WorkitCore {
|
|
|
25932
26618
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
25933
26619
|
state: "stopped",
|
|
25934
26620
|
session: null,
|
|
26621
|
+
report: null,
|
|
25935
26622
|
provenance: attested.data,
|
|
25936
26623
|
owner: this.authorityOwner,
|
|
25937
26624
|
store: this.store,
|
|
@@ -26239,10 +26926,12 @@ class WorkitCore {
|
|
|
26239
26926
|
return workspace;
|
|
26240
26927
|
if (!workspace.data)
|
|
26241
26928
|
return failure("not_found", "workspace not found");
|
|
26242
|
-
const
|
|
26929
|
+
const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
|
|
26930
|
+
const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
|
|
26243
26931
|
if (!current.ok)
|
|
26244
26932
|
return current;
|
|
26245
|
-
const
|
|
26933
|
+
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
26934
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
|
|
26246
26935
|
return success(task.revision, workspace.data.revision, {
|
|
26247
26936
|
id: task.id,
|
|
26248
26937
|
revision: task.revision,
|
|
@@ -26256,7 +26945,7 @@ class WorkitCore {
|
|
|
26256
26945
|
progress: task.progress,
|
|
26257
26946
|
policy: task.policy,
|
|
26258
26947
|
requirements,
|
|
26259
|
-
writer:
|
|
26948
|
+
writer: evaluationWorkspace.writer
|
|
26260
26949
|
});
|
|
26261
26950
|
}
|
|
26262
26951
|
view(task) {
|
|
@@ -26265,13 +26954,15 @@ class WorkitCore {
|
|
|
26265
26954
|
return workspace;
|
|
26266
26955
|
if (!workspace.data)
|
|
26267
26956
|
return failure("not_found", "workspace not found");
|
|
26268
|
-
const
|
|
26957
|
+
const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
|
|
26958
|
+
const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
|
|
26269
26959
|
if (!current.ok)
|
|
26270
26960
|
return current;
|
|
26271
|
-
const
|
|
26961
|
+
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
26962
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
|
|
26272
26963
|
return success(task.revision, workspace.data.revision, {
|
|
26273
26964
|
task,
|
|
26274
|
-
workspace:
|
|
26965
|
+
workspace: evaluationWorkspace,
|
|
26275
26966
|
capabilities: this.context.capabilities,
|
|
26276
26967
|
requirements,
|
|
26277
26968
|
evidence: evaluateEvidence(task, current.data)
|
|
@@ -26286,7 +26977,7 @@ class WorkitCore {
|
|
|
26286
26977
|
if (view.task.workspaceId !== view.workspace.id)
|
|
26287
26978
|
return failure("invalid_input", "task and workspace bindings are invalid");
|
|
26288
26979
|
try {
|
|
26289
|
-
if (
|
|
26980
|
+
if (realpathSync6(view.workspace.root) !== this.store.root)
|
|
26290
26981
|
return failure("invalid_input", "task view root does not match the task store");
|
|
26291
26982
|
} catch {
|
|
26292
26983
|
return failure("invalid_input", "task view root cannot be resolved");
|
|
@@ -26362,8 +27053,6 @@ class WorkitCore {
|
|
|
26362
27053
|
return helper;
|
|
26363
27054
|
if (status === "paused" && task.data.status !== "active" || status === "active" && task.data.status !== "paused")
|
|
26364
27055
|
return failure("invalid_transition", `cannot transition ${task.data.status} to ${status}`);
|
|
26365
|
-
if (status === "active" && input.authorityRefs.length === 0)
|
|
26366
|
-
return failure("permission_denied", "resuming a task requires authority references");
|
|
26367
27056
|
const workspace = this.store.readWorkspace();
|
|
26368
27057
|
if (!workspace.ok)
|
|
26369
27058
|
return workspace;
|
|
@@ -26393,7 +27082,7 @@ class WorkitCore {
|
|
|
26393
27082
|
return failure("needs_input", "imported task requires fresh destination evidence");
|
|
26394
27083
|
if (current.data.blockers.some((blocker) => blocker.dependentAction === "resume" || blocker.reason === "worker state requires reconciliation"))
|
|
26395
27084
|
return failure("recovery_required", "imported task requires resume reconciliation");
|
|
26396
|
-
if (!validResumeApproval(task.data, workspace.data, input.authorityRefs, this.context))
|
|
27085
|
+
if (!validResumeApproval(task.data, workspace.data, input.authorityRefs ?? [], this.context))
|
|
26397
27086
|
return failure("permission_denied", "imported resume requires native destination approval");
|
|
26398
27087
|
resumeCandidate = current.data.candidate;
|
|
26399
27088
|
}
|
|
@@ -26523,7 +27212,7 @@ class WorkitCore {
|
|
|
26523
27212
|
task: (value, context) => success(context.revision, null, {
|
|
26524
27213
|
...value,
|
|
26525
27214
|
status: "closed",
|
|
26526
|
-
candidates:
|
|
27215
|
+
candidates: taskForView.candidates,
|
|
26527
27216
|
closure: {
|
|
26528
27217
|
outcome: closure.data.outcome,
|
|
26529
27218
|
at: context.now,
|
|
@@ -26565,22 +27254,22 @@ function resolveGitRevision(root, value) {
|
|
|
26565
27254
|
}
|
|
26566
27255
|
}
|
|
26567
27256
|
function resolveInside(root, candidate) {
|
|
26568
|
-
const base =
|
|
26569
|
-
const target =
|
|
26570
|
-
if (target !== base && !target.startsWith(base +
|
|
27257
|
+
const base = realpathSync7(root);
|
|
27258
|
+
const target = path11.resolve(base, candidate);
|
|
27259
|
+
if (target !== base && !target.startsWith(base + path11.sep)) {
|
|
26571
27260
|
throw new Error("path must stay inside repository root");
|
|
26572
27261
|
}
|
|
26573
27262
|
let ancestor = target;
|
|
26574
27263
|
while (!existsSync5(ancestor))
|
|
26575
|
-
ancestor =
|
|
26576
|
-
const canonicalAncestor =
|
|
26577
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
27264
|
+
ancestor = path11.dirname(ancestor);
|
|
27265
|
+
const canonicalAncestor = realpathSync7(ancestor);
|
|
27266
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path11.sep)) {
|
|
26578
27267
|
throw new Error("path must stay inside repository root");
|
|
26579
27268
|
}
|
|
26580
27269
|
return target;
|
|
26581
27270
|
}
|
|
26582
27271
|
function run2(root, executable, args, env = {}) {
|
|
26583
|
-
const cwd =
|
|
27272
|
+
const cwd = realpathSync7(root);
|
|
26584
27273
|
const result = spawnSync3(executable, args, {
|
|
26585
27274
|
cwd,
|
|
26586
27275
|
encoding: "utf8",
|
|
@@ -26653,18 +27342,19 @@ var redactSecrets = (value) => applyPatterns(value);
|
|
|
26653
27342
|
|
|
26654
27343
|
// packages/workit-core/src/core/pr-create.ts
|
|
26655
27344
|
import fs8 from "node:fs";
|
|
26656
|
-
import
|
|
27345
|
+
import path12 from "node:path";
|
|
27346
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
26657
27347
|
function parseGhIssue(value) {
|
|
26658
27348
|
const m = /issues\/(\d+)/.exec(value);
|
|
26659
27349
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
26660
27350
|
}
|
|
26661
27351
|
function whichOnPath(tool) {
|
|
26662
27352
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
26663
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
27353
|
+
for (const dir of (process.env.PATH ?? "").split(path12.delimiter)) {
|
|
26664
27354
|
if (!dir)
|
|
26665
27355
|
continue;
|
|
26666
27356
|
for (const name of names) {
|
|
26667
|
-
const candidate =
|
|
27357
|
+
const candidate = path12.join(dir, name);
|
|
26668
27358
|
try {
|
|
26669
27359
|
fs8.accessSync(candidate, fs8.constants.X_OK);
|
|
26670
27360
|
return candidate;
|
|
@@ -26674,11 +27364,21 @@ function whichOnPath(tool) {
|
|
|
26674
27364
|
return null;
|
|
26675
27365
|
}
|
|
26676
27366
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
27367
|
+
var pushRemote = (cwd) => {
|
|
27368
|
+
const result = spawnSync4("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
27369
|
+
cwd,
|
|
27370
|
+
encoding: "utf8"
|
|
27371
|
+
});
|
|
27372
|
+
if (result.status !== 0)
|
|
27373
|
+
return null;
|
|
27374
|
+
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
27375
|
+
return urls.length === 1 ? urls[0] : null;
|
|
27376
|
+
};
|
|
26677
27377
|
|
|
26678
27378
|
// packages/workit-core/src/core/repo-context.ts
|
|
26679
|
-
import { spawnSync as
|
|
27379
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
26680
27380
|
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
|
|
26681
|
-
import
|
|
27381
|
+
import path13 from "node:path";
|
|
26682
27382
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26683
27383
|
try {
|
|
26684
27384
|
gitRevisionParts(value);
|
|
@@ -26705,7 +27405,7 @@ var invalidRange = (cwd) => ({
|
|
|
26705
27405
|
cwd
|
|
26706
27406
|
});
|
|
26707
27407
|
var runGit = (cwd, args) => {
|
|
26708
|
-
const result =
|
|
27408
|
+
const result = spawnSync5("git", args, { cwd, encoding: "utf8" });
|
|
26709
27409
|
return {
|
|
26710
27410
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
26711
27411
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -26812,7 +27512,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26812
27512
|
]) {
|
|
26813
27513
|
const parts = rel.split("/");
|
|
26814
27514
|
const base = parts[parts.length - 1];
|
|
26815
|
-
const dir = parts.length > 1 ?
|
|
27515
|
+
const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26816
27516
|
const actual = probeCaseInsensitive(dir, base);
|
|
26817
27517
|
if (actual)
|
|
26818
27518
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26843,7 +27543,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26843
27543
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26844
27544
|
};
|
|
26845
27545
|
var packageScripts = (cwd, keys) => {
|
|
26846
|
-
const pkgPath =
|
|
27546
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
26847
27547
|
if (!existsSync6(pkgPath))
|
|
26848
27548
|
return [];
|
|
26849
27549
|
try {
|
|
@@ -26869,9 +27569,9 @@ var documentationFiles = (cwd) => {
|
|
|
26869
27569
|
if (entry.isDirectory()) {
|
|
26870
27570
|
if (excluded.has(entry.name))
|
|
26871
27571
|
continue;
|
|
26872
|
-
walk(
|
|
27572
|
+
walk(path13.join(dir, entry.name), depth + 1);
|
|
26873
27573
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26874
|
-
matches.push("./" +
|
|
27574
|
+
matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26875
27575
|
}
|
|
26876
27576
|
}
|
|
26877
27577
|
};
|
|
@@ -26887,7 +27587,7 @@ var readTrimmed = (file, maxLines) => {
|
|
|
26887
27587
|
`);
|
|
26888
27588
|
};
|
|
26889
27589
|
function prReadyContext(root, range) {
|
|
26890
|
-
const cwd =
|
|
27590
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
26891
27591
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26892
27592
|
return invalidRange(cwd);
|
|
26893
27593
|
let stdout = "";
|
|
@@ -26945,7 +27645,7 @@ ${status.stderr}` : "");
|
|
|
26945
27645
|
stdout += `template_path: ${template}
|
|
26946
27646
|
|
|
26947
27647
|
`;
|
|
26948
|
-
stdout += readTrimmed(
|
|
27648
|
+
stdout += readTrimmed(path13.join(cwd, template), 220);
|
|
26949
27649
|
} else {
|
|
26950
27650
|
stdout += `template_path: none
|
|
26951
27651
|
|
|
@@ -26955,7 +27655,7 @@ ${status.stderr}` : "");
|
|
|
26955
27655
|
stdout += `
|
|
26956
27656
|
`;
|
|
26957
27657
|
stdout += printSection("Recent Validation Signals");
|
|
26958
|
-
if (existsSync6(
|
|
27658
|
+
if (existsSync6(path13.join(cwd, "package.json"))) {
|
|
26959
27659
|
stdout += `package.json detected. Common scripts:
|
|
26960
27660
|
`;
|
|
26961
27661
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -26964,7 +27664,7 @@ ${status.stderr}` : "");
|
|
|
26964
27664
|
`) + `
|
|
26965
27665
|
`;
|
|
26966
27666
|
}
|
|
26967
|
-
if (existsSync6(
|
|
27667
|
+
if (existsSync6(path13.join(cwd, "Cargo.toml")) || existsSync6(path13.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
26968
27668
|
stdout += `Rust project detected.
|
|
26969
27669
|
`;
|
|
26970
27670
|
}
|
|
@@ -26997,7 +27697,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
26997
27697
|
- Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
|
|
26998
27698
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
26999
27699
|
function changelogContext(root, range) {
|
|
27000
|
-
const cwd =
|
|
27700
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
27001
27701
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
27002
27702
|
return invalidRange(cwd);
|
|
27003
27703
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -27015,7 +27715,7 @@ function changelogContext(root, range) {
|
|
|
27015
27715
|
stdout += CHANGELOG_RULES + `
|
|
27016
27716
|
`;
|
|
27017
27717
|
stdout += printSection("Existing CHANGELOG.md");
|
|
27018
|
-
const changelogPath =
|
|
27718
|
+
const changelogPath = path13.join(cwd, "CHANGELOG.md");
|
|
27019
27719
|
if (existsSync6(changelogPath)) {
|
|
27020
27720
|
stdout += readTrimmed(changelogPath, 260) + `
|
|
27021
27721
|
`;
|
|
@@ -27035,7 +27735,7 @@ function changelogContext(root, range) {
|
|
|
27035
27735
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
27036
27736
|
}
|
|
27037
27737
|
function docsRefreshContext(root, range) {
|
|
27038
|
-
const cwd =
|
|
27738
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
27039
27739
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
27040
27740
|
return invalidRange(cwd);
|
|
27041
27741
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -27057,7 +27757,7 @@ function docsRefreshContext(root, range) {
|
|
|
27057
27757
|
`) + `
|
|
27058
27758
|
`;
|
|
27059
27759
|
stdout += printSection("README Preview");
|
|
27060
|
-
const readme =
|
|
27760
|
+
const readme = path13.join(cwd, "README.md");
|
|
27061
27761
|
if (existsSync6(readme)) {
|
|
27062
27762
|
stdout += readTrimmed(readme, 220) + `
|
|
27063
27763
|
`;
|
|
@@ -27066,7 +27766,7 @@ function docsRefreshContext(root, range) {
|
|
|
27066
27766
|
`;
|
|
27067
27767
|
}
|
|
27068
27768
|
stdout += printSection("Package Scripts");
|
|
27069
|
-
const pkgPath =
|
|
27769
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
27070
27770
|
if (existsSync6(pkgPath)) {
|
|
27071
27771
|
try {
|
|
27072
27772
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
@@ -27080,7 +27780,7 @@ function docsRefreshContext(root, range) {
|
|
|
27080
27780
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
27081
27781
|
}
|
|
27082
27782
|
function releaseNotesContext(root, rangeOrTag) {
|
|
27083
|
-
const cwd =
|
|
27783
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
27084
27784
|
if (!rangeOrTag) {
|
|
27085
27785
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
27086
27786
|
`, exitCode: 1, cwd };
|
|
@@ -27118,7 +27818,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
27118
27818
|
`;
|
|
27119
27819
|
stdout += printSection("Existing Release Files");
|
|
27120
27820
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
27121
|
-
if (existsSync6(
|
|
27821
|
+
if (existsSync6(path13.join(cwd, rel)))
|
|
27122
27822
|
stdout += rel + `
|
|
27123
27823
|
`;
|
|
27124
27824
|
}
|
|
@@ -27146,19 +27846,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
27146
27846
|
}
|
|
27147
27847
|
|
|
27148
27848
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
27149
|
-
import
|
|
27150
|
-
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
27151
|
-
var trackerRequest = async (url, init) => {
|
|
27152
|
-
try {
|
|
27153
|
-
const res = await fetch(url, { method: init.method, headers: init.headers });
|
|
27154
|
-
const text = await res.text();
|
|
27155
|
-
if (!res.ok)
|
|
27156
|
-
return { status: res.status, stdout: "", stderr: text.slice(0, 200) };
|
|
27157
|
-
return { status: 0, stdout: text, stderr: "" };
|
|
27158
|
-
} catch (err) {
|
|
27159
|
-
return { status: 1, stdout: "", stderr: err instanceof Error ? err.message : "network error" };
|
|
27160
|
-
}
|
|
27161
|
-
};
|
|
27849
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
27162
27850
|
var repoPathFromRemote = (remote) => {
|
|
27163
27851
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
27164
27852
|
if (!rest)
|
|
@@ -27179,7 +27867,11 @@ var defaultCli = (bin, available) => {
|
|
|
27179
27867
|
if (!available)
|
|
27180
27868
|
return null;
|
|
27181
27869
|
return (args, root) => {
|
|
27182
|
-
const out =
|
|
27870
|
+
const out = spawnSync6(bin, args, {
|
|
27871
|
+
cwd: root,
|
|
27872
|
+
encoding: "utf8",
|
|
27873
|
+
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
27874
|
+
});
|
|
27183
27875
|
return Promise.resolve({
|
|
27184
27876
|
status: out.status ?? 1,
|
|
27185
27877
|
stdout: out.stdout ?? "",
|
|
@@ -27187,49 +27879,22 @@ var defaultCli = (bin, available) => {
|
|
|
27187
27879
|
});
|
|
27188
27880
|
};
|
|
27189
27881
|
};
|
|
27190
|
-
var tryCliTriple = async (cli, args, root, id, idKeys, bodyKeys) => {
|
|
27882
|
+
var tryCliTriple = async (cli, name, args, root, id, idKeys, bodyKeys) => {
|
|
27191
27883
|
if (!cli)
|
|
27192
|
-
return
|
|
27884
|
+
return { error: `${name} CLI is unavailable`, kind: "creds" };
|
|
27193
27885
|
const run = cli(args, root);
|
|
27194
27886
|
if (!run)
|
|
27195
|
-
return
|
|
27887
|
+
return { error: `${name} CLI is unavailable`, kind: "creds" };
|
|
27196
27888
|
const out = await run;
|
|
27197
27889
|
if (out.status !== 0)
|
|
27198
|
-
return
|
|
27890
|
+
return { error: `${name} CLI is not authenticated or the issue is unavailable`, kind: "creds" };
|
|
27199
27891
|
try {
|
|
27200
|
-
|
|
27892
|
+
const triple = parseTriple(JSON.parse(out.stdout), id, idKeys, bodyKeys);
|
|
27893
|
+
return triple ? { data: triple } : { error: `unexpected ${name} issue response`, kind: "request" };
|
|
27201
27894
|
} catch {
|
|
27202
|
-
return
|
|
27895
|
+
return { error: `invalid ${name} issue response`, kind: "request" };
|
|
27203
27896
|
}
|
|
27204
27897
|
};
|
|
27205
|
-
var originRemote = (root) => {
|
|
27206
|
-
const out = spawnSync5("git", ["remote", "get-url", "origin"], { cwd: root, encoding: "utf8" });
|
|
27207
|
-
return out.status === 0 ? (out.stdout ?? "").trim() || null : null;
|
|
27208
|
-
};
|
|
27209
|
-
var trackerCreds = (root, provider) => {
|
|
27210
|
-
const cfg = vcsConfig("load", root);
|
|
27211
|
-
if (!cfg || cfg.provider !== provider || typeof cfg.tokenPath !== "string" || !cfg.tokenPath)
|
|
27212
|
-
return { error: `unconfigured ${provider} provider` };
|
|
27213
|
-
let token = "";
|
|
27214
|
-
try {
|
|
27215
|
-
token = fs9.readFileSync(cfg.tokenPath, "utf8").trim();
|
|
27216
|
-
} catch {
|
|
27217
|
-
return { error: `missing ${provider} token` };
|
|
27218
|
-
}
|
|
27219
|
-
if (!token)
|
|
27220
|
-
return { error: `empty ${provider} token file` };
|
|
27221
|
-
if (provider === "gitlab") {
|
|
27222
|
-
const gitlab = cfg.gitlab;
|
|
27223
|
-
const apiUrl = String(gitlab?.apiUrl ?? "https://gitlab.com/api/v4").replace(/\/+$/, "");
|
|
27224
|
-
return { token, api: apiUrl };
|
|
27225
|
-
}
|
|
27226
|
-
const github = cfg.github;
|
|
27227
|
-
const host = String(github?.host ?? "github.com").toLowerCase();
|
|
27228
|
-
return {
|
|
27229
|
-
token,
|
|
27230
|
-
api: host === "github.com" ? "https://api.github.com" : `https://${host}/api/v3`
|
|
27231
|
-
};
|
|
27232
|
-
};
|
|
27233
27898
|
var parseTriple = (parsed, id, idKeys, bodyKeys) => {
|
|
27234
27899
|
const title = parsed.title;
|
|
27235
27900
|
if (typeof title !== "string")
|
|
@@ -27252,62 +27917,36 @@ var fetchGitHubIssueBody = async (ref, root, deps = {}) => {
|
|
|
27252
27917
|
const id = parseGhIssue(ref);
|
|
27253
27918
|
if (!/^\d+$/.test(id))
|
|
27254
27919
|
return { error: `invalid GitHub issue ref "${ref}"`, kind: "input" };
|
|
27255
|
-
const
|
|
27920
|
+
const remote = (deps.remote ?? pushRemote)(root);
|
|
27921
|
+
const repo = repoPathFromRemote(remote ?? "");
|
|
27256
27922
|
if (!repo)
|
|
27257
27923
|
return { error: "no origin remote to resolve the repository", kind: "input" };
|
|
27258
|
-
const
|
|
27259
|
-
|
|
27260
|
-
|
|
27261
|
-
|
|
27262
|
-
|
|
27263
|
-
|
|
27264
|
-
const out = await (deps.request ?? trackerRequest)(`${credentials.api}/repos/${repo}/issues/${id}`, {
|
|
27265
|
-
method: "GET",
|
|
27266
|
-
headers: {
|
|
27267
|
-
Authorization: `Bearer ${credentials.token}`,
|
|
27268
|
-
Accept: "application/vnd.github+json"
|
|
27269
|
-
}
|
|
27270
|
-
});
|
|
27271
|
-
if (out.status !== 0)
|
|
27272
|
-
return { error: out.stderr || "GitHub issue fetch failed", kind: "request" };
|
|
27273
|
-
try {
|
|
27274
|
-
const triple = parseTriple(JSON.parse(out.stdout), id, ["number"], ["body"]);
|
|
27275
|
-
if (!triple)
|
|
27276
|
-
return { error: "unexpected GitHub issue shape", kind: "request" };
|
|
27277
|
-
return { data: triple };
|
|
27278
|
-
} catch {
|
|
27279
|
-
return { error: "invalid GitHub issue response", kind: "request" };
|
|
27280
|
-
}
|
|
27924
|
+
const cfg = vcsConfig("load", root);
|
|
27925
|
+
const host = (deps.host ?? (cfg.ok && cfg.provider === "github" ? cfg.github?.host : null) ?? "github.com").toLowerCase();
|
|
27926
|
+
if (!remote || !hostingApiHostMatches(remote, host))
|
|
27927
|
+
return { error: "hosting API host does not match the push destination", kind: "input" };
|
|
27928
|
+
const repoArg = host === "github.com" ? repo : `${host}/${repo}`;
|
|
27929
|
+
return tryCliTriple(deps.cli ?? defaultCli("gh", hostingCliAvailable("github")), "gh", ["issue", "view", id, "--repo", repoArg, "--json", "number,title,body,state"], root, id, ["number"], ["body"]);
|
|
27281
27930
|
};
|
|
27282
27931
|
var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
27283
27932
|
const id = parseGhIssue(ref);
|
|
27284
27933
|
if (!/^\d+$/.test(id))
|
|
27285
27934
|
return { error: `invalid GitLab issue ref "${ref}"`, kind: "input" };
|
|
27286
|
-
const
|
|
27935
|
+
const remote = (deps.remote ?? pushRemote)(root);
|
|
27936
|
+
const repo = repoPathFromRemote(remote ?? "");
|
|
27287
27937
|
if (!repo)
|
|
27288
27938
|
return { error: "no origin remote to resolve the project", kind: "input" };
|
|
27289
|
-
const
|
|
27290
|
-
|
|
27291
|
-
|
|
27292
|
-
|
|
27293
|
-
|
|
27294
|
-
|
|
27295
|
-
const out = await (deps.request ?? trackerRequest)(`${credentials.api}/projects/${encodeURIComponent(repo)}/issues/${id}`, { method: "GET", headers: { "PRIVATE-TOKEN": credentials.token } });
|
|
27296
|
-
if (out.status !== 0)
|
|
27297
|
-
return { error: out.stderr || "GitLab issue fetch failed", kind: "request" };
|
|
27298
|
-
try {
|
|
27299
|
-
const triple = parseTriple(JSON.parse(out.stdout), id, ["iid"], ["description"]);
|
|
27300
|
-
if (!triple)
|
|
27301
|
-
return { error: "unexpected GitLab issue shape", kind: "request" };
|
|
27302
|
-
return { data: triple };
|
|
27303
|
-
} catch {
|
|
27304
|
-
return { error: "invalid GitLab issue response", kind: "request" };
|
|
27305
|
-
}
|
|
27939
|
+
const cfg = vcsConfig("load", root);
|
|
27940
|
+
const host = (deps.host ?? (cfg.ok && cfg.provider === "gitlab" ? cfg.gitlab?.host : null) ?? "gitlab.com").toLowerCase();
|
|
27941
|
+
if (!remote || !hostingApiHostMatches(remote, host))
|
|
27942
|
+
return { error: "hosting API host does not match the push destination", kind: "input" };
|
|
27943
|
+
const repoArg = host === "gitlab.com" ? repo : `https://${host}/${repo}`;
|
|
27944
|
+
return tryCliTriple(deps.cli ?? defaultCli("glab", hostingCliAvailable("gitlab")), "glab", ["issue", "view", id, "-R", repoArg, "-F", "json"], root, id, ["iid"], ["description"]);
|
|
27306
27945
|
};
|
|
27307
27946
|
|
|
27308
27947
|
// packages/workit-core/src/core/youtrack.ts
|
|
27309
|
-
import
|
|
27310
|
-
import
|
|
27948
|
+
import fs9 from "node:fs";
|
|
27949
|
+
import path14 from "node:path";
|
|
27311
27950
|
|
|
27312
27951
|
// packages/workit-core/src/core/templates.ts
|
|
27313
27952
|
var repoRoot2 = assetRoot();
|
|
@@ -27319,23 +27958,23 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
27319
27958
|
|
|
27320
27959
|
// packages/workit-core/src/core/youtrack.ts
|
|
27321
27960
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
27322
|
-
var
|
|
27323
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
27324
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
27961
|
+
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
27962
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(configDir(), "youtrack.json");
|
|
27963
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(resolveConfigDir(), "youtrack.json");
|
|
27325
27964
|
var youTrackTokenModeOk = (p) => {
|
|
27326
27965
|
if (process.platform === "win32")
|
|
27327
27966
|
return true;
|
|
27328
|
-
const mode =
|
|
27967
|
+
const mode = fs9.statSync(p).mode & 511;
|
|
27329
27968
|
return mode === 384;
|
|
27330
27969
|
};
|
|
27331
27970
|
function readYouTrackConfig(required) {
|
|
27332
27971
|
const cfgPath = youTrackConfigPath();
|
|
27333
|
-
if (!
|
|
27972
|
+
if (!fs9.existsSync(cfgPath)) {
|
|
27334
27973
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
27335
27974
|
}
|
|
27336
27975
|
let parsed;
|
|
27337
27976
|
try {
|
|
27338
|
-
parsed = JSON.parse(
|
|
27977
|
+
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
27339
27978
|
} catch {
|
|
27340
27979
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
27341
27980
|
}
|
|
@@ -27346,11 +27985,11 @@ function readYouTrackConfig(required) {
|
|
|
27346
27985
|
}
|
|
27347
27986
|
var readYouTrackContextConfig = () => {
|
|
27348
27987
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
27349
|
-
if (!
|
|
27988
|
+
if (!fs9.existsSync(cfgPath))
|
|
27350
27989
|
return { data: {} };
|
|
27351
27990
|
let parsed;
|
|
27352
27991
|
try {
|
|
27353
|
-
parsed = JSON.parse(
|
|
27992
|
+
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
27354
27993
|
} catch {
|
|
27355
27994
|
return { error: "YouTrack configuration is unavailable" };
|
|
27356
27995
|
}
|
|
@@ -27375,12 +28014,12 @@ function youTrackGreeting(configOverride) {
|
|
|
27375
28014
|
const cfgPath = configOverride ?? youTrackConfigPath();
|
|
27376
28015
|
let parsed;
|
|
27377
28016
|
try {
|
|
27378
|
-
parsed = JSON.parse(
|
|
28017
|
+
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
27379
28018
|
} catch {
|
|
27380
28019
|
return {
|
|
27381
28020
|
stdout: "",
|
|
27382
28021
|
exitCode: 1,
|
|
27383
|
-
stderr:
|
|
28022
|
+
stderr: fs9.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
|
|
27384
28023
|
};
|
|
27385
28024
|
}
|
|
27386
28025
|
if (!isConfigObject(parsed)) {
|
|
@@ -27426,15 +28065,15 @@ var youTrackToken = () => {
|
|
|
27426
28065
|
if ("error" in loaded)
|
|
27427
28066
|
return loaded;
|
|
27428
28067
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
27429
|
-
const tokenPath = tokenFile ?
|
|
27430
|
-
if (!tokenPath || !
|
|
28068
|
+
const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
|
|
28069
|
+
if (!tokenPath || !fs9.existsSync(tokenPath))
|
|
27431
28070
|
return { error: "missing youtrack.token" };
|
|
27432
28071
|
if (!youTrackTokenModeOk(tokenPath))
|
|
27433
28072
|
return { error: "youtrack.token mode must be 0600" };
|
|
27434
|
-
const token =
|
|
28073
|
+
const token = fs9.readFileSync(tokenPath, "utf8").trim();
|
|
27435
28074
|
if (!token)
|
|
27436
28075
|
return { error: "empty token file" };
|
|
27437
|
-
if (token ===
|
|
28076
|
+
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
27438
28077
|
return {
|
|
27439
28078
|
error: "token file still has placeholder YOUR_TOKEN_HERE — edit the file locally, then run /wk-status"
|
|
27440
28079
|
};
|
|
@@ -27497,9 +28136,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
27497
28136
|
} catch {
|
|
27498
28137
|
continue;
|
|
27499
28138
|
}
|
|
27500
|
-
if (!
|
|
28139
|
+
if (!fs9.existsSync(full))
|
|
27501
28140
|
continue;
|
|
27502
|
-
const text =
|
|
28141
|
+
const text = fs9.readFileSync(full, "utf8");
|
|
27503
28142
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
27504
28143
|
if (m)
|
|
27505
28144
|
return m[1];
|
|
@@ -27733,6 +28372,7 @@ var readExternalContext = async (root, payload) => {
|
|
|
27733
28372
|
}
|
|
27734
28373
|
}
|
|
27735
28374
|
};
|
|
28375
|
+
var targetWriterActionLease = Symbol("target-writer-action-lease");
|
|
27736
28376
|
|
|
27737
28377
|
// packages/workit-mcp/src/server.ts
|
|
27738
28378
|
class McpCapabilityUnavailableError extends Error {
|
|
@@ -27777,13 +28417,13 @@ var rootVariants = (workspaceRoot) => {
|
|
|
27777
28417
|
if (!workspaceRoot)
|
|
27778
28418
|
return [];
|
|
27779
28419
|
const roots = new Set;
|
|
27780
|
-
for (const candidate of [workspaceRoot,
|
|
27781
|
-
if (
|
|
28420
|
+
for (const candidate of [workspaceRoot, path15.resolve(workspaceRoot)]) {
|
|
28421
|
+
if (path15.isAbsolute(candidate) && candidate !== path15.parse(candidate).root)
|
|
27782
28422
|
roots.add(candidate);
|
|
27783
28423
|
}
|
|
27784
28424
|
try {
|
|
27785
|
-
const canonical =
|
|
27786
|
-
if (canonical !==
|
|
28425
|
+
const canonical = realpathSync8(workspaceRoot);
|
|
28426
|
+
if (canonical !== path15.parse(canonical).root)
|
|
27787
28427
|
roots.add(canonical);
|
|
27788
28428
|
} catch {}
|
|
27789
28429
|
return [...roots].sort((left, right) => right.length - left.length);
|
|
@@ -27884,11 +28524,11 @@ var thrownResult = (tool, error, workspaceRoot) => {
|
|
|
27884
28524
|
};
|
|
27885
28525
|
function createMcpServer(host, contextProvider) {
|
|
27886
28526
|
assertMcpHost(host);
|
|
27887
|
-
const here =
|
|
28527
|
+
const here = path15.dirname(fileURLToPath2(import.meta.url));
|
|
27888
28528
|
const sourceMarker = markSourcesLoaded([
|
|
27889
|
-
|
|
27890
|
-
|
|
27891
|
-
|
|
28529
|
+
path15.join(here, "server.ts"),
|
|
28530
|
+
path15.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
|
|
28531
|
+
path15.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
|
|
27892
28532
|
].filter((file) => existsSync7(file)));
|
|
27893
28533
|
let staleWarned = false;
|
|
27894
28534
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
@@ -28068,8 +28708,8 @@ var main = async () => {
|
|
|
28068
28708
|
process.exitCode = 2;
|
|
28069
28709
|
}
|
|
28070
28710
|
};
|
|
28071
|
-
var entryPath = process.argv[1] ?
|
|
28072
|
-
if (entryPath ===
|
|
28711
|
+
var entryPath = process.argv[1] ? path16.resolve(process.argv[1]) : "";
|
|
28712
|
+
if (entryPath === path16.resolve(fileURLToPath3(import.meta.url)))
|
|
28073
28713
|
await main();
|
|
28074
28714
|
|
|
28075
28715
|
// packages/workit-cursor/hooks/workit-hook.ts
|
|
@@ -28136,6 +28776,7 @@ var eventNames = new Set([
|
|
|
28136
28776
|
"subagentStop",
|
|
28137
28777
|
"preCompact"
|
|
28138
28778
|
]);
|
|
28779
|
+
var historyOfferSessions = new Set;
|
|
28139
28780
|
if (false)
|
|
28140
28781
|
;
|
|
28141
28782
|
// packages/workit-cursor/mcp/run-server.ts
|