@brainervirus/workit-codex 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/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +962 -321
- package/dist/workit-hook.js +1108 -261
- package/package.json +3 -3
- 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/launch-mcp.js
CHANGED
|
@@ -6621,13 +6621,13 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
6621
6621
|
});
|
|
6622
6622
|
|
|
6623
6623
|
// packages/workit-codex/scripts/launch-mcp.ts
|
|
6624
|
-
import { existsSync as existsSync8, realpathSync as
|
|
6625
|
-
import
|
|
6624
|
+
import { existsSync as existsSync8, realpathSync as realpathSync9, statSync as statSync3 } from "node:fs";
|
|
6625
|
+
import path16 from "node:path";
|
|
6626
6626
|
|
|
6627
6627
|
// packages/workit-core/src/core.ts
|
|
6628
6628
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
6629
|
-
import { existsSync as existsSync5, realpathSync as
|
|
6630
|
-
import
|
|
6629
|
+
import { existsSync as existsSync5, realpathSync as realpathSync7 } from "node:fs";
|
|
6630
|
+
import path11 from "node:path";
|
|
6631
6631
|
|
|
6632
6632
|
// packages/workit-core/src/core/task-contract.ts
|
|
6633
6633
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -11934,6 +11934,17 @@ function _array(Class, element, params) {
|
|
|
11934
11934
|
...normalizeParams(params)
|
|
11935
11935
|
});
|
|
11936
11936
|
}
|
|
11937
|
+
function _custom(Class, fn, _params) {
|
|
11938
|
+
const norm = normalizeParams(_params);
|
|
11939
|
+
norm.abort ?? (norm.abort = true);
|
|
11940
|
+
const schema = new Class({
|
|
11941
|
+
type: "custom",
|
|
11942
|
+
check: "custom",
|
|
11943
|
+
fn,
|
|
11944
|
+
...norm
|
|
11945
|
+
});
|
|
11946
|
+
return schema;
|
|
11947
|
+
}
|
|
11937
11948
|
function _refine(Class, fn, _params) {
|
|
11938
11949
|
const schema = new Class({
|
|
11939
11950
|
type: "custom",
|
|
@@ -14087,6 +14098,9 @@ var ZodCustom = /* @__PURE__ */ $constructor("ZodCustom", (inst, def) => {
|
|
|
14087
14098
|
ZodType.init(inst, def);
|
|
14088
14099
|
inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);
|
|
14089
14100
|
});
|
|
14101
|
+
function custom(fn, _params) {
|
|
14102
|
+
return _custom(ZodCustom, fn ?? (() => true), _params);
|
|
14103
|
+
}
|
|
14090
14104
|
function refine(fn, _params = {}) {
|
|
14091
14105
|
return _refine(ZodCustom, fn, _params);
|
|
14092
14106
|
}
|
|
@@ -14520,7 +14534,8 @@ var decisionSchema = object({
|
|
|
14520
14534
|
consumption: object({
|
|
14521
14535
|
state: _enum(["reserved", "consumed", "uncertain"]),
|
|
14522
14536
|
at: utc,
|
|
14523
|
-
actionRef: refSchema
|
|
14537
|
+
actionRef: refSchema,
|
|
14538
|
+
historicalActionId: text.optional()
|
|
14524
14539
|
}).strict().nullable()
|
|
14525
14540
|
}).strict();
|
|
14526
14541
|
var findingSchema = object({
|
|
@@ -14551,6 +14566,7 @@ var workerSchema = object({
|
|
|
14551
14566
|
assignment: assignmentSchema,
|
|
14552
14567
|
state: _enum(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
|
|
14553
14568
|
session: refSchema.nullable(),
|
|
14569
|
+
coordinator: refSchema.optional(),
|
|
14554
14570
|
report: workerReportSchema.nullable()
|
|
14555
14571
|
}).strict();
|
|
14556
14572
|
var ownerSchema = object({
|
|
@@ -14662,6 +14678,7 @@ var taskSummarySchema = object({
|
|
|
14662
14678
|
requirements: array(requirementEvaluationSchema),
|
|
14663
14679
|
writer: workspaceRecordSchema.shape.writer
|
|
14664
14680
|
}).strict();
|
|
14681
|
+
var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true }).extend({ source: provenanceSchema.pick({ host: true, kind: true }) }).strict();
|
|
14665
14682
|
var exportBundleSchema = object({
|
|
14666
14683
|
schemaVersion: literal(1),
|
|
14667
14684
|
exportedAt: utc,
|
|
@@ -14683,11 +14700,16 @@ var taskOperations = {
|
|
|
14683
14700
|
expectedWorkspaceRevision: revision.nullable().optional(),
|
|
14684
14701
|
intent: intentSchema
|
|
14685
14702
|
}),
|
|
14686
|
-
list: operation({
|
|
14703
|
+
list: operation({
|
|
14704
|
+
action: literal("list"),
|
|
14705
|
+
status: _enum(["open", "closed", "all"]).optional(),
|
|
14706
|
+
query: string2().trim().min(1).max(200).optional(),
|
|
14707
|
+
limit: number2().int().min(1).max(50).optional()
|
|
14708
|
+
}),
|
|
14687
14709
|
inspect: operation({
|
|
14688
14710
|
action: literal("inspect"),
|
|
14689
14711
|
...taskId,
|
|
14690
|
-
view: _enum(["summary", "full"])
|
|
14712
|
+
view: _enum(["summary", "full"]).optional()
|
|
14691
14713
|
}),
|
|
14692
14714
|
revise: operation({
|
|
14693
14715
|
action: literal("revise"),
|
|
@@ -14715,7 +14737,7 @@ var taskOperations = {
|
|
|
14715
14737
|
...taskId,
|
|
14716
14738
|
...revisions,
|
|
14717
14739
|
expectedWorkspaceRevision: revision.optional(),
|
|
14718
|
-
authorityRefs: array(refSchema)
|
|
14740
|
+
authorityRefs: array(refSchema).optional()
|
|
14719
14741
|
}),
|
|
14720
14742
|
close: operation({
|
|
14721
14743
|
action: literal("close"),
|
|
@@ -15029,6 +15051,7 @@ function requirementId(input) {
|
|
|
15029
15051
|
}
|
|
15030
15052
|
// packages/workit-core/src/core/task-store.ts
|
|
15031
15053
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
15054
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
15032
15055
|
import * as fs4 from "node:fs";
|
|
15033
15056
|
import { hostname } from "node:os";
|
|
15034
15057
|
import path4 from "node:path";
|
|
@@ -15664,7 +15687,8 @@ var metadataLockSchema = object({
|
|
|
15664
15687
|
pid: number2().int().nonnegative().safe(),
|
|
15665
15688
|
processStart: string2().nullable(),
|
|
15666
15689
|
host: string2().min(1),
|
|
15667
|
-
nonce: string2().min(1)
|
|
15690
|
+
nonce: string2().min(1),
|
|
15691
|
+
externalAction: literal(true).optional()
|
|
15668
15692
|
}).strict();
|
|
15669
15693
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
15670
15694
|
var cachedRuntimeVersion = null;
|
|
@@ -15716,6 +15740,28 @@ var sameMetadataLock = (left, right) => {
|
|
|
15716
15740
|
const parsed = metadataLockSchema.safeParse(left);
|
|
15717
15741
|
return parsed.success && canonicalJson(parsed.data) === canonicalJson(right);
|
|
15718
15742
|
};
|
|
15743
|
+
var sameDirectoryIdentity = (left, right) => {
|
|
15744
|
+
if (!path4.isAbsolute(left) || !path4.isAbsolute(right))
|
|
15745
|
+
return false;
|
|
15746
|
+
const normalizedLeft = path4.resolve(left);
|
|
15747
|
+
const normalizedRight = path4.resolve(right);
|
|
15748
|
+
try {
|
|
15749
|
+
const a = fs4.statSync(normalizedLeft, { bigint: true });
|
|
15750
|
+
const b = fs4.statSync(normalizedRight, { bigint: true });
|
|
15751
|
+
if (!a.isDirectory() || !b.isDirectory())
|
|
15752
|
+
return false;
|
|
15753
|
+
if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
|
|
15754
|
+
return true;
|
|
15755
|
+
const canonicalLeft = fs4.realpathSync(normalizedLeft);
|
|
15756
|
+
const canonicalRight = fs4.realpathSync(normalizedRight);
|
|
15757
|
+
if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
|
|
15758
|
+
return true;
|
|
15759
|
+
return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
|
|
15760
|
+
} catch {
|
|
15761
|
+
return false;
|
|
15762
|
+
}
|
|
15763
|
+
};
|
|
15764
|
+
var externalActionLockRoots = new AsyncLocalStorage;
|
|
15719
15765
|
|
|
15720
15766
|
class TaskStore {
|
|
15721
15767
|
root;
|
|
@@ -15776,7 +15822,7 @@ class TaskStore {
|
|
|
15776
15822
|
const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
|
|
15777
15823
|
if (!item.exists)
|
|
15778
15824
|
return success(null, null, null);
|
|
15779
|
-
if (item.result.ok && item.result.data.root !== this.root)
|
|
15825
|
+
if (item.result.ok && item.result.data.root !== this.root && !sameDirectoryIdentity(item.result.data.root, this.root))
|
|
15780
15826
|
return failure("recovery_required", "workspace root binding is invalid", {
|
|
15781
15827
|
path: this.workspacePath
|
|
15782
15828
|
});
|
|
@@ -15867,6 +15913,29 @@ class TaskStore {
|
|
|
15867
15913
|
const current = this.readWorkspace();
|
|
15868
15914
|
if (!current.ok)
|
|
15869
15915
|
return current;
|
|
15916
|
+
const origin = input.task.origin;
|
|
15917
|
+
if (current.data && origin) {
|
|
15918
|
+
if (origin.workspaceId === current.data.id) {
|
|
15919
|
+
const source = this.readTask(origin.taskId);
|
|
15920
|
+
if (source.ok)
|
|
15921
|
+
return success(source.data.revision, current.data.revision, source.data);
|
|
15922
|
+
if (source.code !== "not_found")
|
|
15923
|
+
return source;
|
|
15924
|
+
return failure("not_found", "source task is not present in this shared workspace");
|
|
15925
|
+
}
|
|
15926
|
+
const tasks = this.listTasks();
|
|
15927
|
+
if (!tasks.ok)
|
|
15928
|
+
return tasks;
|
|
15929
|
+
const prior = tasks.data.filter((task) => task.origin?.workspaceId === origin.workspaceId && task.origin?.taskId === origin.taskId);
|
|
15930
|
+
if (prior.length > 1)
|
|
15931
|
+
return failure("recovery_required", `source task has multiple imported mappings: ${prior.map((task) => task.id).join(", ")}`);
|
|
15932
|
+
if (prior.length === 1) {
|
|
15933
|
+
const existing = prior[0];
|
|
15934
|
+
if (existing.origin.exportDigest !== origin.exportDigest)
|
|
15935
|
+
return failure("revision_conflict", "source task export changed; reconcile the mapped destination task before importing", { taskId: existing.id });
|
|
15936
|
+
return success(existing.revision, current.data.revision, existing);
|
|
15937
|
+
}
|
|
15938
|
+
}
|
|
15870
15939
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
15871
15940
|
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
15872
15941
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
@@ -16255,6 +16324,73 @@ class TaskStore {
|
|
|
16255
16324
|
})
|
|
16256
16325
|
};
|
|
16257
16326
|
}
|
|
16327
|
+
externalActionLockOptions() {
|
|
16328
|
+
const options = this.metadataLockOptions();
|
|
16329
|
+
return { ...options, payload: () => ({ ...options.payload(), externalAction: true }) };
|
|
16330
|
+
}
|
|
16331
|
+
async withExternalActionLock(operation, reentrant = false) {
|
|
16332
|
+
const activeRoots = externalActionLockRoots.getStore();
|
|
16333
|
+
if (activeRoots && [...activeRoots].some((root) => root === this.root || sameDirectoryIdentity(root, this.root)))
|
|
16334
|
+
return operation();
|
|
16335
|
+
try {
|
|
16336
|
+
this.initializeMutationStorage();
|
|
16337
|
+
} catch (error) {
|
|
16338
|
+
return failure("storage_error", `unable to initialize store: ${String(error)}`, {
|
|
16339
|
+
path: this.workitDir
|
|
16340
|
+
});
|
|
16341
|
+
}
|
|
16342
|
+
let handle;
|
|
16343
|
+
try {
|
|
16344
|
+
handle = acquireFileLockSync(this.workspacePath, this.externalActionLockOptions());
|
|
16345
|
+
} catch (error) {
|
|
16346
|
+
return this.lockFailure(error);
|
|
16347
|
+
}
|
|
16348
|
+
let result;
|
|
16349
|
+
try {
|
|
16350
|
+
if (!handle.verifyStillHeld())
|
|
16351
|
+
result = failure("recovery_required", "metadata lock was compromised", {
|
|
16352
|
+
path: this.lockPath
|
|
16353
|
+
});
|
|
16354
|
+
else {
|
|
16355
|
+
try {
|
|
16356
|
+
const run = () => operation();
|
|
16357
|
+
result = await (reentrant ? externalActionLockRoots.run(new Set([...activeRoots ?? [], this.root]), run) : run());
|
|
16358
|
+
} catch {
|
|
16359
|
+
result = failure("external_outcome_unknown", "external action outcome is unknown", {
|
|
16360
|
+
outcome: "unknown"
|
|
16361
|
+
});
|
|
16362
|
+
}
|
|
16363
|
+
try {
|
|
16364
|
+
if (!handle.verifyStillHeld())
|
|
16365
|
+
result = failure("external_outcome_unknown", "metadata lock was compromised", {
|
|
16366
|
+
outcome: "unknown",
|
|
16367
|
+
path: this.lockPath
|
|
16368
|
+
});
|
|
16369
|
+
} catch {
|
|
16370
|
+
result = failure("external_outcome_unknown", "metadata lock could not be verified", {
|
|
16371
|
+
outcome: "unknown",
|
|
16372
|
+
path: this.lockPath
|
|
16373
|
+
});
|
|
16374
|
+
}
|
|
16375
|
+
}
|
|
16376
|
+
} catch {
|
|
16377
|
+
result = failure("external_outcome_unknown", "external action lock operation failed", {
|
|
16378
|
+
outcome: "unknown",
|
|
16379
|
+
path: this.lockPath
|
|
16380
|
+
});
|
|
16381
|
+
}
|
|
16382
|
+
try {
|
|
16383
|
+
handle.release();
|
|
16384
|
+
} catch (error) {
|
|
16385
|
+
const released = this.lockFailure(error);
|
|
16386
|
+
const releaseError = released.ok ? "metadata lock release failed" : released.error;
|
|
16387
|
+
return result.ok ? failure("external_outcome_unknown", "external action metadata lock release is uncertain", {
|
|
16388
|
+
outcome: "unknown",
|
|
16389
|
+
path: this.lockPath
|
|
16390
|
+
}) : failure("recovery_required", `${result.error}; ${releaseError}`, result.details);
|
|
16391
|
+
}
|
|
16392
|
+
return result;
|
|
16393
|
+
}
|
|
16258
16394
|
recoveryLockOptions(observed, evidence, gate) {
|
|
16259
16395
|
const options = this.metadataLockOptions();
|
|
16260
16396
|
options.staleRecovery = "remove-if-unchanged";
|
|
@@ -16324,6 +16460,14 @@ class TaskStore {
|
|
|
16324
16460
|
lockFailure(error) {
|
|
16325
16461
|
const value = error;
|
|
16326
16462
|
const code = typeof value?.code === "string" ? value.code : "";
|
|
16463
|
+
if (code === "EEXIST" || code === "file_lock_timeout") {
|
|
16464
|
+
const lock = this.readLockSnapshot();
|
|
16465
|
+
if (lock.ok && lock.data?.data.externalAction)
|
|
16466
|
+
return failure("writer_conflict", "workspace is reserved by a managed external action", {
|
|
16467
|
+
outcome: "not_started",
|
|
16468
|
+
path: this.lockPath
|
|
16469
|
+
});
|
|
16470
|
+
}
|
|
16327
16471
|
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));
|
|
16328
16472
|
return failure(recovery ? "recovery_required" : "storage_error", `metadata lock operation failed: ${String(error)}`, { path: this.lockPath });
|
|
16329
16473
|
}
|
|
@@ -16548,6 +16692,10 @@ var validPath = (value) => typeof value === "string" && value.length > 0 && ![..
|
|
|
16548
16692
|
}) && !value.split(/[\\/]/).includes("..");
|
|
16549
16693
|
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
16550
16694
|
var workerIdOf = (value) => value.workerId ?? null;
|
|
16695
|
+
var currentWriterOwnsTask = (task, workspace, caller) => {
|
|
16696
|
+
const owner = workspace.writer?.owner;
|
|
16697
|
+
return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
|
|
16698
|
+
};
|
|
16551
16699
|
function assertProductWriteAllowed(input) {
|
|
16552
16700
|
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
16553
16701
|
return failure("invalid_input", "invalid product write path");
|
|
@@ -16587,21 +16735,23 @@ function assertProductWriteAllowed(input) {
|
|
|
16587
16735
|
}
|
|
16588
16736
|
|
|
16589
16737
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
16590
|
-
import { createHash as
|
|
16738
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
16591
16739
|
import * as fs7 from "node:fs";
|
|
16592
16740
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
16593
|
-
import
|
|
16741
|
+
import path10 from "node:path";
|
|
16594
16742
|
|
|
16595
16743
|
// packages/workit-core/src/core/authority.ts
|
|
16596
|
-
import { createHash as
|
|
16744
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
16597
16745
|
import * as fs6 from "node:fs";
|
|
16598
|
-
import
|
|
16746
|
+
import path9 from "node:path";
|
|
16599
16747
|
|
|
16600
16748
|
// packages/workit-core/src/core/auto-approval.ts
|
|
16601
|
-
import { createHash as
|
|
16602
|
-
import { readFileSync as readFileSync4 } from "node:fs";
|
|
16749
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
16750
|
+
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
16751
|
+
import path8 from "node:path";
|
|
16603
16752
|
|
|
16604
16753
|
// packages/workit-core/src/core/workspaces.ts
|
|
16754
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
16605
16755
|
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
16606
16756
|
import path6 from "node:path";
|
|
16607
16757
|
|
|
@@ -16619,7 +16769,7 @@ import os from "node:os";
|
|
|
16619
16769
|
import path5 from "node:path";
|
|
16620
16770
|
|
|
16621
16771
|
// packages/workit-core/src/core/boundary.ts
|
|
16622
|
-
import { statSync } from "node:fs";
|
|
16772
|
+
import { statSync as statSync2 } from "node:fs";
|
|
16623
16773
|
var EVENT = {
|
|
16624
16774
|
initialization: "initialization",
|
|
16625
16775
|
provenance: "provenance",
|
|
@@ -16647,7 +16797,7 @@ var changedSourcesSinceLoad = (marker) => {
|
|
|
16647
16797
|
const changed = [];
|
|
16648
16798
|
for (const file of marker.files) {
|
|
16649
16799
|
try {
|
|
16650
|
-
if (
|
|
16800
|
+
if (statSync2(file).mtimeMs > marker.loadedAt)
|
|
16651
16801
|
changed.push(file);
|
|
16652
16802
|
} catch {}
|
|
16653
16803
|
}
|
|
@@ -16806,33 +16956,197 @@ var COMMIT_PRESETS = [
|
|
|
16806
16956
|
"custom",
|
|
16807
16957
|
"auto"
|
|
16808
16958
|
];
|
|
16959
|
+
var resolveCommitPolicy = (config, workspace) => {
|
|
16960
|
+
const wp = workspace?.commitPolicy ?? {};
|
|
16961
|
+
if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
|
|
16962
|
+
return {
|
|
16963
|
+
preset: wp.preset,
|
|
16964
|
+
...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
|
|
16965
|
+
};
|
|
16966
|
+
}
|
|
16967
|
+
return config.commitPolicy ?? { preset: "conventional" };
|
|
16968
|
+
};
|
|
16809
16969
|
|
|
16810
16970
|
// packages/workit-core/src/core/workspaces.ts
|
|
16971
|
+
var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
|
|
16972
|
+
var branchPreset = custom((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
|
|
16973
|
+
var commitPreset = custom((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
|
|
16974
|
+
var workspaceBranchPolicySchema = object({
|
|
16975
|
+
preset: branchPreset,
|
|
16976
|
+
developBranch: nonBlank.optional(),
|
|
16977
|
+
prefixes: object({
|
|
16978
|
+
feature: nonBlank,
|
|
16979
|
+
bugfix: nonBlank,
|
|
16980
|
+
release: nonBlank,
|
|
16981
|
+
hotfix: nonBlank
|
|
16982
|
+
}).passthrough().optional(),
|
|
16983
|
+
allowed: array(nonBlank).optional(),
|
|
16984
|
+
protected: array(nonBlank).optional(),
|
|
16985
|
+
integration: _enum(["pr", "merge"]).optional()
|
|
16986
|
+
}).strict();
|
|
16987
|
+
var workspaceCommitPolicySchema = object({ preset: commitPreset, pattern: string2().optional() }).strict();
|
|
16988
|
+
var workspaceProfileSchema = object({
|
|
16989
|
+
branchPolicy: workspaceBranchPolicySchema.optional(),
|
|
16990
|
+
commitPolicy: workspaceCommitPolicySchema.optional()
|
|
16991
|
+
}).strict().refine((profile) => profile.branchPolicy !== undefined || profile.commitPolicy !== undefined, {
|
|
16992
|
+
message: "a profile must define branchPolicy or commitPolicy"
|
|
16993
|
+
});
|
|
16994
|
+
var versionSourceSchema = discriminatedUnion("kind", [
|
|
16995
|
+
object({
|
|
16996
|
+
kind: literal("package-json"),
|
|
16997
|
+
path: nonBlank,
|
|
16998
|
+
field: nonBlank
|
|
16999
|
+
}).strict(),
|
|
17000
|
+
object({ kind: literal("git-tag") }).strict(),
|
|
17001
|
+
object({ kind: literal("manual") }).strict()
|
|
17002
|
+
]);
|
|
17003
|
+
var releaseTrackSchema = object({
|
|
17004
|
+
strategy: branchPreset,
|
|
17005
|
+
productionBranch: nonBlank,
|
|
17006
|
+
integrationBranch: nonBlank,
|
|
17007
|
+
naming: object({ feature: nonBlank, release: nonBlank, hotfix: nonBlank }).strict(),
|
|
17008
|
+
baseBranch: nonBlank,
|
|
17009
|
+
mergeBackBranches: array(nonBlank),
|
|
17010
|
+
pullRequestTarget: nonBlank,
|
|
17011
|
+
tagNamespace: string2(),
|
|
17012
|
+
versionSource: versionSourceSchema,
|
|
17013
|
+
requiredChecks: array(nonBlank)
|
|
17014
|
+
}).strict().superRefine((track, context) => {
|
|
17015
|
+
for (const [field, values] of [
|
|
17016
|
+
["mergeBackBranches", track.mergeBackBranches],
|
|
17017
|
+
["requiredChecks", track.requiredChecks]
|
|
17018
|
+
]) {
|
|
17019
|
+
if (new Set(values).size !== values.length) {
|
|
17020
|
+
context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
|
|
17021
|
+
}
|
|
17022
|
+
}
|
|
17023
|
+
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(".."))) {
|
|
17024
|
+
context.addIssue({
|
|
17025
|
+
code: "custom",
|
|
17026
|
+
path: ["versionSource", "path"],
|
|
17027
|
+
message: "must stay inside the repository"
|
|
17028
|
+
});
|
|
17029
|
+
}
|
|
17030
|
+
});
|
|
17031
|
+
var workspaceConfigSchema = object({
|
|
17032
|
+
name: nonBlank,
|
|
17033
|
+
glob: nonBlank,
|
|
17034
|
+
vcs: object({
|
|
17035
|
+
provider: _enum(["gitlab", "github"]),
|
|
17036
|
+
defaultTargetBranch: nonBlank.optional(),
|
|
17037
|
+
tokenFile: nonBlank.optional(),
|
|
17038
|
+
account: nonBlank.optional()
|
|
17039
|
+
}).passthrough().optional(),
|
|
17040
|
+
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
17041
|
+
youtrack: object({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
|
|
17042
|
+
issues: object({ provider: literal("github").optional(), link_on_pr: boolean2().optional() }).passthrough().optional(),
|
|
17043
|
+
branchPolicy: workspaceBranchPolicySchema.optional(),
|
|
17044
|
+
commitPolicy: workspaceCommitPolicySchema.optional(),
|
|
17045
|
+
defaultProfile: nonBlank.optional(),
|
|
17046
|
+
profiles: record(nonBlank, workspaceProfileSchema).optional(),
|
|
17047
|
+
releaseTracks: record(nonBlank, releaseTrackSchema).optional()
|
|
17048
|
+
}).passthrough().superRefine((workspace, context) => {
|
|
17049
|
+
if (workspace.youtrack && workspace.vcs?.provider !== "gitlab") {
|
|
17050
|
+
context.addIssue({
|
|
17051
|
+
code: "custom",
|
|
17052
|
+
path: ["youtrack"],
|
|
17053
|
+
message: `YouTrack issue linking requires the gitlab provider, got ${workspace.vcs?.provider ?? "unset"}`
|
|
17054
|
+
});
|
|
17055
|
+
}
|
|
17056
|
+
if (workspace.issues && workspace.vcs?.provider !== "github") {
|
|
17057
|
+
context.addIssue({
|
|
17058
|
+
code: "custom",
|
|
17059
|
+
path: ["issues"],
|
|
17060
|
+
message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
|
|
17061
|
+
});
|
|
17062
|
+
}
|
|
17063
|
+
if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
|
|
17064
|
+
context.addIssue({
|
|
17065
|
+
code: "custom",
|
|
17066
|
+
path: ["defaultProfile"],
|
|
17067
|
+
message: `does not name a configured profile`
|
|
17068
|
+
});
|
|
17069
|
+
}
|
|
17070
|
+
});
|
|
17071
|
+
var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).optional() }).passthrough().superRefine((file, context) => {
|
|
17072
|
+
const names = new Set;
|
|
17073
|
+
for (const [index, workspace] of (file.workspaces ?? []).entries()) {
|
|
17074
|
+
if (names.has(workspace.name)) {
|
|
17075
|
+
context.addIssue({
|
|
17076
|
+
code: "custom",
|
|
17077
|
+
path: ["workspaces", index, "name"],
|
|
17078
|
+
message: `duplicate workspace name ${JSON.stringify(workspace.name)}`
|
|
17079
|
+
});
|
|
17080
|
+
}
|
|
17081
|
+
names.add(workspace.name);
|
|
17082
|
+
}
|
|
17083
|
+
});
|
|
17084
|
+
var workspacesRevision = (contents) => createHash3("sha256").update(contents ?? "<missing>").digest("hex");
|
|
17085
|
+
var validateWorkspacesDocument = (parsed, file) => {
|
|
17086
|
+
if (!isConfigObject(parsed)) {
|
|
17087
|
+
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
17088
|
+
}
|
|
17089
|
+
const validated = workspacesFileSchema.safeParse(parsed, { reportInput: true });
|
|
17090
|
+
if (!validated.success) {
|
|
17091
|
+
const detail = validated.error.issues.map((issue) => {
|
|
17092
|
+
const field = String(issue.path[issue.path.length - 1] ?? "");
|
|
17093
|
+
const attempted = ["provider", "preset", "strategy"].includes(field) && typeof issue.input === "string" ? ` (${JSON.stringify(issue.input)})` : "";
|
|
17094
|
+
return `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}${attempted}`;
|
|
17095
|
+
}).join("; ");
|
|
17096
|
+
return {
|
|
17097
|
+
status: "invalid",
|
|
17098
|
+
path: file,
|
|
17099
|
+
entries: [],
|
|
17100
|
+
error: `${file} has invalid workspace configuration: ${detail}`
|
|
17101
|
+
};
|
|
17102
|
+
}
|
|
17103
|
+
for (const [index, workspace] of (validated.data.workspaces ?? []).entries()) {
|
|
17104
|
+
const glob = validateWorkspaceGlob(workspace.glob);
|
|
17105
|
+
if (!glob.ok) {
|
|
17106
|
+
return {
|
|
17107
|
+
status: "invalid",
|
|
17108
|
+
path: file,
|
|
17109
|
+
entries: [],
|
|
17110
|
+
error: `${file} workspaces.${index}.glob: ${glob.error}`
|
|
17111
|
+
};
|
|
17112
|
+
}
|
|
17113
|
+
}
|
|
17114
|
+
return {
|
|
17115
|
+
status: "valid",
|
|
17116
|
+
path: file,
|
|
17117
|
+
entries: validated.data.workspaces ?? [],
|
|
17118
|
+
document: parsed
|
|
17119
|
+
};
|
|
17120
|
+
};
|
|
16811
17121
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
16812
17122
|
const file = path6.join(dir, "workspaces.json");
|
|
16813
17123
|
let raw;
|
|
16814
17124
|
try {
|
|
16815
17125
|
raw = readFileSync3(file, "utf8");
|
|
16816
|
-
} catch {
|
|
16817
|
-
|
|
17126
|
+
} catch (error) {
|
|
17127
|
+
if (error.code === "ENOENT")
|
|
17128
|
+
return { status: "missing", path: file, entries: [], revision: workspacesRevision(null) };
|
|
17129
|
+
return {
|
|
17130
|
+
status: "malformed",
|
|
17131
|
+
path: file,
|
|
17132
|
+
entries: [],
|
|
17133
|
+
error: `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
|
|
17134
|
+
};
|
|
16818
17135
|
}
|
|
16819
17136
|
let parsed;
|
|
16820
17137
|
try {
|
|
16821
17138
|
parsed = JSON.parse(raw);
|
|
16822
17139
|
} catch {
|
|
16823
|
-
return {
|
|
16824
|
-
|
|
16825
|
-
|
|
16826
|
-
|
|
17140
|
+
return {
|
|
17141
|
+
status: "malformed",
|
|
17142
|
+
path: file,
|
|
17143
|
+
entries: [],
|
|
17144
|
+
revision: workspacesRevision(raw),
|
|
17145
|
+
error: `${file} is not valid JSON`
|
|
17146
|
+
};
|
|
16827
17147
|
}
|
|
16828
|
-
|
|
16829
|
-
return {
|
|
16830
|
-
status: "valid",
|
|
16831
|
-
path: file,
|
|
16832
|
-
entries: Array.isArray(list) ? list : []
|
|
16833
|
-
};
|
|
17148
|
+
return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
|
|
16834
17149
|
};
|
|
16835
|
-
var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
|
|
16836
17150
|
var globToRegExp = (glob) => {
|
|
16837
17151
|
let out = "";
|
|
16838
17152
|
for (let i = 0;i < glob.length; i++) {
|
|
@@ -16867,6 +17181,30 @@ var globToRegExp = (glob) => {
|
|
|
16867
17181
|
return new RegExp(`^${out}$`);
|
|
16868
17182
|
};
|
|
16869
17183
|
var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
|
|
17184
|
+
var UNSUPPORTED_GLOB = /[?[\]{}]/;
|
|
17185
|
+
var UNSUPPORTED_EXTGLOB = /[@+*]\(|!/;
|
|
17186
|
+
var validateWorkspaceGlob = (glob) => {
|
|
17187
|
+
const trimmed = glob.trim();
|
|
17188
|
+
if (!trimmed)
|
|
17189
|
+
return { ok: false, error: "workspace pattern is required" };
|
|
17190
|
+
const m = UNSUPPORTED_GLOB.exec(trimmed);
|
|
17191
|
+
if (m) {
|
|
17192
|
+
return {
|
|
17193
|
+
ok: false,
|
|
17194
|
+
error: `unsupported glob character ${JSON.stringify(m[0])} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
|
|
17195
|
+
};
|
|
17196
|
+
}
|
|
17197
|
+
const ext = UNSUPPORTED_EXTGLOB.exec(trimmed);
|
|
17198
|
+
if (ext) {
|
|
17199
|
+
const token = ext[0];
|
|
17200
|
+
const kind = token === "!" ? "negation (!)" : `extglob (${token})`;
|
|
17201
|
+
return {
|
|
17202
|
+
ok: false,
|
|
17203
|
+
error: `unsupported glob ${kind} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
|
|
17204
|
+
};
|
|
17205
|
+
}
|
|
17206
|
+
return { ok: true };
|
|
17207
|
+
};
|
|
16870
17208
|
var realpathOf = (p) => {
|
|
16871
17209
|
const real = realpathSync2.native ?? realpathSync2;
|
|
16872
17210
|
try {
|
|
@@ -16884,29 +17222,137 @@ var canonicalGlob = (glob) => {
|
|
|
16884
17222
|
const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
|
|
16885
17223
|
return real + (prefix.endsWith("/") ? "/" : "") + rest;
|
|
16886
17224
|
};
|
|
16887
|
-
var resolveWorkspaceFrom = (cwd, dir) => {
|
|
17225
|
+
var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
|
|
17226
|
+
const result = readWorkspacesResult(dir);
|
|
17227
|
+
if (result.status === "malformed" || result.status === "invalid")
|
|
17228
|
+
throw new Error(result.error);
|
|
16888
17229
|
const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
|
|
16889
|
-
|
|
16890
|
-
|
|
16891
|
-
continue;
|
|
16892
|
-
const ws = entry;
|
|
16893
|
-
if (typeof ws.glob !== "string" || !ws.glob)
|
|
16894
|
-
continue;
|
|
17230
|
+
const matches = [];
|
|
17231
|
+
for (const ws of result.entries) {
|
|
16895
17232
|
const glob = ws.glob.replaceAll("\\", "/");
|
|
16896
17233
|
const canonical = canonicalGlob(glob);
|
|
17234
|
+
let matched = false;
|
|
16897
17235
|
for (const target of targets) {
|
|
16898
|
-
if (matchWorkspace(glob, target))
|
|
16899
|
-
|
|
16900
|
-
|
|
16901
|
-
|
|
17236
|
+
if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
|
|
17237
|
+
matched = true;
|
|
17238
|
+
break;
|
|
17239
|
+
}
|
|
16902
17240
|
}
|
|
17241
|
+
if (matched)
|
|
17242
|
+
matches.push(ws);
|
|
16903
17243
|
}
|
|
16904
|
-
|
|
17244
|
+
if (workspaceName !== undefined) {
|
|
17245
|
+
const selected = matches.find((ws) => ws.name === workspaceName);
|
|
17246
|
+
if (!selected && matches.length > 0) {
|
|
17247
|
+
const matchingNames = matches.map((ws) => ws.name);
|
|
17248
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matchingNames.join(", ")}`);
|
|
17249
|
+
}
|
|
17250
|
+
if (selected)
|
|
17251
|
+
return selected;
|
|
17252
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
|
|
17253
|
+
}
|
|
17254
|
+
if (matches.length > 1) {
|
|
17255
|
+
throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${matches.map((ws) => ws.name).join(", ")}`);
|
|
17256
|
+
}
|
|
17257
|
+
return matches[0] ?? null;
|
|
17258
|
+
};
|
|
17259
|
+
var runtimeWorkspacePolicySchema = (kind) => {
|
|
17260
|
+
const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
|
|
17261
|
+
const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object({
|
|
17262
|
+
provider: _enum(["gitlab", "github"]),
|
|
17263
|
+
defaultTargetBranch: nonBlank.optional(),
|
|
17264
|
+
account: nonBlank.optional()
|
|
17265
|
+
}).passthrough();
|
|
17266
|
+
const profileSchema = object({ [key]: policySchema.optional() }).passthrough();
|
|
17267
|
+
const entrySchema = object({
|
|
17268
|
+
name: nonBlank,
|
|
17269
|
+
glob: nonBlank,
|
|
17270
|
+
[key]: policySchema.optional(),
|
|
17271
|
+
defaultProfile: nonBlank.optional(),
|
|
17272
|
+
profiles: record(nonBlank, profileSchema).optional()
|
|
17273
|
+
}).passthrough();
|
|
17274
|
+
return entrySchema;
|
|
17275
|
+
};
|
|
17276
|
+
var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs");
|
|
17277
|
+
var runtimeWorkspaceIndexSchema = object({
|
|
17278
|
+
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
17279
|
+
}).passthrough();
|
|
17280
|
+
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
17281
|
+
const file = path6.join(configDir(), "workspaces.json");
|
|
17282
|
+
let raw;
|
|
17283
|
+
try {
|
|
17284
|
+
raw = readFileSync3(file, "utf8");
|
|
17285
|
+
} catch (error) {
|
|
17286
|
+
if (error.code === "ENOENT")
|
|
17287
|
+
return null;
|
|
17288
|
+
throw new Error(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
17289
|
+
}
|
|
17290
|
+
let parsed;
|
|
17291
|
+
try {
|
|
17292
|
+
parsed = JSON.parse(raw);
|
|
17293
|
+
} catch {
|
|
17294
|
+
throw new Error(`${file} is not valid JSON`);
|
|
17295
|
+
}
|
|
17296
|
+
const validated = runtimeWorkspaceIndexSchema.safeParse(parsed);
|
|
17297
|
+
if (!validated.success) {
|
|
17298
|
+
const detail = validated.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}`).join("; ");
|
|
17299
|
+
throw new Error(`${file} has invalid workspace matching data: ${detail}`);
|
|
17300
|
+
}
|
|
17301
|
+
const entries = validated.data.workspaces;
|
|
17302
|
+
for (const entry of entries ?? []) {
|
|
17303
|
+
const glob = validateWorkspaceGlob(entry.glob);
|
|
17304
|
+
if (!glob.ok)
|
|
17305
|
+
throw new Error(`${file} workspace ${entry.name}: ${glob.error}`);
|
|
17306
|
+
}
|
|
17307
|
+
const targets = [cwd, realpathOf(cwd)].map((value) => value.replaceAll("\\", "/"));
|
|
17308
|
+
const matches = (entries ?? []).filter((entry) => {
|
|
17309
|
+
const glob = entry.glob.replaceAll("\\", "/");
|
|
17310
|
+
const canonical = canonicalGlob(glob);
|
|
17311
|
+
return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
|
|
17312
|
+
});
|
|
17313
|
+
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
17314
|
+
let selected;
|
|
17315
|
+
if (workspaceName) {
|
|
17316
|
+
const named = matches.filter((entry) => entry.name === workspaceName);
|
|
17317
|
+
if (named.length === 1)
|
|
17318
|
+
selected = named[0];
|
|
17319
|
+
else if (named.length > 1)
|
|
17320
|
+
throw new Error(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
|
|
17321
|
+
else if (matches.length)
|
|
17322
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
|
|
17323
|
+
else
|
|
17324
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
|
|
17325
|
+
} else if (matches.length > 1)
|
|
17326
|
+
throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${matches.map((entry) => entry.name).join(", ")}`);
|
|
17327
|
+
else
|
|
17328
|
+
selected = matches[0];
|
|
17329
|
+
if (!selected)
|
|
17330
|
+
return null;
|
|
17331
|
+
const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
|
|
17332
|
+
if (!policy.success) {
|
|
17333
|
+
const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
|
|
17334
|
+
throw new Error(`${file} has invalid ${kind} policy configuration: ${detail}`);
|
|
17335
|
+
}
|
|
17336
|
+
return policy.data;
|
|
17337
|
+
};
|
|
17338
|
+
var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
|
|
17339
|
+
const candidate = resolveRuntimeWorkspaceCandidate(cwd, kind);
|
|
17340
|
+
if (!candidate)
|
|
17341
|
+
return { policy: undefined, source: "user-default" };
|
|
17342
|
+
const key = kind === "branch" ? "branchPolicy" : "commitPolicy";
|
|
17343
|
+
const profileName = requestedProfile?.trim() || candidate.defaultProfile;
|
|
17344
|
+
if (candidate.defaultProfile && !Object.hasOwn(candidate.profiles ?? {}, candidate.defaultProfile))
|
|
17345
|
+
throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
|
|
17346
|
+
const profile = profileName ? candidate.profiles?.[profileName] : undefined;
|
|
17347
|
+
if (profileName && !profile)
|
|
17348
|
+
throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
|
|
17349
|
+
const profilePolicy = profile?.[key];
|
|
17350
|
+
const workspacePolicy = candidate[key];
|
|
17351
|
+
return {
|
|
17352
|
+
policy: profilePolicy ?? workspacePolicy,
|
|
17353
|
+
source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
|
|
17354
|
+
};
|
|
16905
17355
|
};
|
|
16906
|
-
var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
|
|
16907
|
-
|
|
16908
|
-
// packages/workit-core/src/core/branch.ts
|
|
16909
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
16910
17356
|
|
|
16911
17357
|
// packages/workit-core/src/core/git.ts
|
|
16912
17358
|
import { execFileSync } from "node:child_process";
|
|
@@ -16980,7 +17426,6 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
16980
17426
|
import fs5 from "node:fs";
|
|
16981
17427
|
import path7 from "node:path";
|
|
16982
17428
|
import { spawnSync } from "node:child_process";
|
|
16983
|
-
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
16984
17429
|
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
|
|
16985
17430
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
16986
17431
|
var remoteProvider = (cwd) => {
|
|
@@ -16994,6 +17439,79 @@ var remoteProvider = (cwd) => {
|
|
|
16994
17439
|
return "gitlab";
|
|
16995
17440
|
return null;
|
|
16996
17441
|
};
|
|
17442
|
+
var remoteEndpoint = (remote) => {
|
|
17443
|
+
const raw = remote.trim();
|
|
17444
|
+
if (!raw)
|
|
17445
|
+
return null;
|
|
17446
|
+
const scp = /^(?:[^@\s]+@)?([^:]+):(.+)$/u.exec(raw);
|
|
17447
|
+
if (scp && !raw.includes("://"))
|
|
17448
|
+
return {
|
|
17449
|
+
host: scp[1].toLowerCase(),
|
|
17450
|
+
path: scp[2].replace(/^\//u, "").replace(/\.git$/u, ""),
|
|
17451
|
+
protocol: "ssh:",
|
|
17452
|
+
port: ""
|
|
17453
|
+
};
|
|
17454
|
+
try {
|
|
17455
|
+
const url = new URL(raw);
|
|
17456
|
+
return {
|
|
17457
|
+
host: url.hostname.toLowerCase(),
|
|
17458
|
+
path: url.pathname.replace(/^\//u, "").replace(/\.git$/u, "").replace(/\/$/u, ""),
|
|
17459
|
+
protocol: url.protocol,
|
|
17460
|
+
port: url.port
|
|
17461
|
+
};
|
|
17462
|
+
} catch {
|
|
17463
|
+
return null;
|
|
17464
|
+
}
|
|
17465
|
+
};
|
|
17466
|
+
var configuredSshEndpoint = (host) => {
|
|
17467
|
+
if (host.startsWith("-"))
|
|
17468
|
+
return null;
|
|
17469
|
+
const resolved = spawnSync("ssh", ["-G", host], {
|
|
17470
|
+
encoding: "utf8",
|
|
17471
|
+
timeout: 5000,
|
|
17472
|
+
maxBuffer: 1024 * 1024,
|
|
17473
|
+
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
17474
|
+
});
|
|
17475
|
+
if (resolved.status !== 0)
|
|
17476
|
+
return null;
|
|
17477
|
+
const output = resolved.stdout ?? "";
|
|
17478
|
+
const hostname = /^hostname\s+(\S+)/imu.exec(output)?.[1]?.toLowerCase();
|
|
17479
|
+
if (!hostname)
|
|
17480
|
+
return null;
|
|
17481
|
+
return { host: hostname, port: /^port\s+(\d+)/imu.exec(output)?.[1] ?? "22" };
|
|
17482
|
+
};
|
|
17483
|
+
var sshApiHostAliases = {
|
|
17484
|
+
"github.com": [{ host: "ssh.github.com", port: "443" }],
|
|
17485
|
+
"gitlab.com": [{ host: "altssh.gitlab.com", port: "443" }]
|
|
17486
|
+
};
|
|
17487
|
+
var hostingApiHostMatches = (remote, apiHost, resolveSshEndpoint = configuredSshEndpoint) => {
|
|
17488
|
+
const endpoint = remoteEndpoint(remote);
|
|
17489
|
+
if (!endpoint)
|
|
17490
|
+
return false;
|
|
17491
|
+
let expected;
|
|
17492
|
+
try {
|
|
17493
|
+
const url = new URL(`https://${apiHost}`);
|
|
17494
|
+
if (url.pathname !== "/" || url.search || url.hash || url.username || url.password)
|
|
17495
|
+
return false;
|
|
17496
|
+
expected = {
|
|
17497
|
+
host: url.hostname.toLowerCase().replace(/\.$/u, ""),
|
|
17498
|
+
port: url.port
|
|
17499
|
+
};
|
|
17500
|
+
} catch {
|
|
17501
|
+
return false;
|
|
17502
|
+
}
|
|
17503
|
+
if (endpoint.protocol !== "ssh:") {
|
|
17504
|
+
return endpoint.host.replace(/\.$/u, "") === expected.host && endpoint.port === expected.port;
|
|
17505
|
+
}
|
|
17506
|
+
const resolved = resolveSshEndpoint(endpoint.host);
|
|
17507
|
+
if (!resolved)
|
|
17508
|
+
return false;
|
|
17509
|
+
const hostname = resolved.host.toLowerCase().replace(/\.$/u, "");
|
|
17510
|
+
const port = endpoint.port || resolved.port;
|
|
17511
|
+
if (hostname === expected.host)
|
|
17512
|
+
return port === (expected.port || "22");
|
|
17513
|
+
return Boolean(sshApiHostAliases[expected.host]?.some((alias) => alias.host === hostname && alias.port === port && (!expected.port || expected.port === port)));
|
|
17514
|
+
};
|
|
16997
17515
|
var readVcsConfig = () => {
|
|
16998
17516
|
const file = vcsConfigPath();
|
|
16999
17517
|
let raw;
|
|
@@ -17014,7 +17532,7 @@ var readVcsConfig = () => {
|
|
|
17014
17532
|
return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
|
|
17015
17533
|
};
|
|
17016
17534
|
function vcsConfig(mode, cwd) {
|
|
17017
|
-
const ws =
|
|
17535
|
+
const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
|
|
17018
17536
|
const wsVcs = ws?.vcs ?? {};
|
|
17019
17537
|
const wsYt = ws?.youtrack ?? {};
|
|
17020
17538
|
const wsIssues = ws?.issues ?? {};
|
|
@@ -17027,8 +17545,8 @@ function vcsConfig(mode, cwd) {
|
|
|
17027
17545
|
const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
|
|
17028
17546
|
const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
|
|
17029
17547
|
const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
|
|
17030
|
-
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
17031
|
-
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
17548
|
+
const linkIssues = provider === "gitlab" && typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
17549
|
+
const youtrackBaseUrl = provider === "gitlab" && typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
17032
17550
|
let issuesProvider = null;
|
|
17033
17551
|
let linkOnPr = null;
|
|
17034
17552
|
if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
|
|
@@ -17052,9 +17570,8 @@ function vcsConfig(mode, cwd) {
|
|
|
17052
17570
|
if (cfgStatus === "malformed") {
|
|
17053
17571
|
return { ok: false, error: cfgError, configPath: cfgPath };
|
|
17054
17572
|
}
|
|
17055
|
-
if (cfgStatus === "missing")
|
|
17573
|
+
if (cfgStatus === "missing" && provider === null)
|
|
17056
17574
|
return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
|
|
17057
|
-
}
|
|
17058
17575
|
if (provider === null) {
|
|
17059
17576
|
return {
|
|
17060
17577
|
ok: false,
|
|
@@ -17063,24 +17580,12 @@ function vcsConfig(mode, cwd) {
|
|
|
17063
17580
|
};
|
|
17064
17581
|
}
|
|
17065
17582
|
const prov = cfg[provider] ?? {};
|
|
17066
|
-
const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
|
|
17067
|
-
const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path7.join(configDir(), `${provider}.token`));
|
|
17068
|
-
const tokenPath = path7.resolve(tokenFile);
|
|
17069
|
-
let tokenOk = false;
|
|
17070
|
-
if (fs5.existsSync(tokenPath)) {
|
|
17071
|
-
const token = fs5.readFileSync(tokenPath, "utf8").trim();
|
|
17072
|
-
const placeholder = !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
|
|
17073
|
-
tokenOk = !placeholder;
|
|
17074
|
-
}
|
|
17075
17583
|
const out = {
|
|
17076
17584
|
ok: true,
|
|
17077
17585
|
configPath: path7.resolve(cfgPath),
|
|
17078
17586
|
provider,
|
|
17079
17587
|
defaultTargetBranch: defaultTarget,
|
|
17080
17588
|
pr: cfg.pr ?? {},
|
|
17081
|
-
tokenPath,
|
|
17082
|
-
tokenPresent: fs5.existsSync(tokenPath),
|
|
17083
|
-
tokenReady: tokenOk,
|
|
17084
17589
|
workspace_name: ws?.name ?? null,
|
|
17085
17590
|
link_issues: linkIssues,
|
|
17086
17591
|
youtrack_base_url: youtrackBaseUrl,
|
|
@@ -17095,16 +17600,58 @@ function vcsConfig(mode, cwd) {
|
|
|
17095
17600
|
} else if (provider === "github") {
|
|
17096
17601
|
out.github = { host: prov.host ?? "github.com" };
|
|
17097
17602
|
}
|
|
17098
|
-
if (mode === "summary")
|
|
17099
|
-
delete out.tokenPath;
|
|
17100
17603
|
return out;
|
|
17101
17604
|
}
|
|
17605
|
+
function vcsCliIdentity(cwd) {
|
|
17606
|
+
const root = vcsCwd(cwd);
|
|
17607
|
+
const cfg = vcsConfig("load", root);
|
|
17608
|
+
if (!cfg.ok)
|
|
17609
|
+
return { ok: false, error: cfg.error ?? "vcs config not ready" };
|
|
17610
|
+
const provider = cfg.provider;
|
|
17611
|
+
if (provider !== "gitlab" && provider !== "github")
|
|
17612
|
+
return { ok: false, error: `unsupported provider: ${provider}` };
|
|
17613
|
+
const bin = provider === "gitlab" ? "glab" : "gh";
|
|
17614
|
+
const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
|
|
17615
|
+
const env = {
|
|
17616
|
+
...process.env,
|
|
17617
|
+
PATH: process.env.PATH ?? "",
|
|
17618
|
+
...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
|
|
17619
|
+
};
|
|
17620
|
+
const result = spawnSync(bin, ["api", "user"], {
|
|
17621
|
+
cwd: root,
|
|
17622
|
+
encoding: "utf8",
|
|
17623
|
+
env
|
|
17624
|
+
});
|
|
17625
|
+
if (result.status !== 0)
|
|
17626
|
+
return { ok: false, provider, error: `${bin} is missing or not authenticated` };
|
|
17627
|
+
try {
|
|
17628
|
+
const user = JSON.parse(result.stdout ?? "");
|
|
17629
|
+
const username = provider === "gitlab" ? user.username : user.login;
|
|
17630
|
+
if (typeof username !== "string" || !username)
|
|
17631
|
+
return { ok: false, provider, error: `invalid ${bin} identity response` };
|
|
17632
|
+
const expected = resolveRuntimeWorkspaceVcs(root)?.vcs?.account;
|
|
17633
|
+
if (expected && username.toLowerCase() !== expected.toLowerCase())
|
|
17634
|
+
return {
|
|
17635
|
+
ok: false,
|
|
17636
|
+
provider,
|
|
17637
|
+
error: `${bin} account ${username} does not match ${expected}`
|
|
17638
|
+
};
|
|
17639
|
+
return { ok: true, provider, host, username, name: user.name };
|
|
17640
|
+
} catch {
|
|
17641
|
+
return { ok: false, provider, error: `invalid JSON from ${bin} api user` };
|
|
17642
|
+
}
|
|
17643
|
+
}
|
|
17102
17644
|
function mergedPrStyle(limit = 6, cwd) {
|
|
17103
17645
|
const cfg = vcsConfig("load", cwd);
|
|
17104
|
-
if (!cfg.ok
|
|
17646
|
+
if (!cfg.ok)
|
|
17105
17647
|
return { ok: false, error: "vcs not configured" };
|
|
17106
17648
|
const provider = cfg.provider;
|
|
17107
|
-
const
|
|
17649
|
+
const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
|
|
17650
|
+
const env = {
|
|
17651
|
+
...process.env,
|
|
17652
|
+
PATH: process.env.PATH ?? "",
|
|
17653
|
+
...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
|
|
17654
|
+
};
|
|
17108
17655
|
const examples = [];
|
|
17109
17656
|
const descInfo = (desc, caseInsensitiveNotes = false) => ({
|
|
17110
17657
|
hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
|
|
@@ -17112,23 +17659,30 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
17112
17659
|
`).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
|
|
17113
17660
|
descriptionPreview: desc.slice(0, 600)
|
|
17114
17661
|
});
|
|
17662
|
+
const remoteResult = spawnSync("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
17663
|
+
cwd,
|
|
17664
|
+
encoding: "utf8"
|
|
17665
|
+
});
|
|
17666
|
+
const remotes = (remoteResult.stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
|
|
17667
|
+
if (remotes.length === 0)
|
|
17668
|
+
return { ok: false, error: "no origin remote" };
|
|
17669
|
+
if (remotes.length !== 1)
|
|
17670
|
+
return { ok: false, error: "origin has ambiguous push destinations" };
|
|
17671
|
+
const remote = remotes[0];
|
|
17672
|
+
const endpoint = remoteEndpoint(remote);
|
|
17673
|
+
if (!endpoint?.path)
|
|
17674
|
+
return { ok: false, error: "hosting repository could not be resolved" };
|
|
17675
|
+
if (!hostingApiHostMatches(remote, host))
|
|
17676
|
+
return { ok: false, error: "PR style target does not match the provider host" };
|
|
17115
17677
|
if (provider === "gitlab") {
|
|
17116
|
-
const
|
|
17117
|
-
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
|
|
17121
|
-
if (!m)
|
|
17122
|
-
return { ok: false, error: "not a gitlab.com origin" };
|
|
17123
|
-
const project = m[1];
|
|
17124
|
-
const env = { ...process.env, GITLAB_TOKEN: token };
|
|
17125
|
-
const run = (args) => spawnSync("glab", ["api", ...args], { cwd, encoding: "utf8", env });
|
|
17678
|
+
const run = (args) => spawnSync("glab", ["api", ...args], {
|
|
17679
|
+
cwd,
|
|
17680
|
+
encoding: "utf8",
|
|
17681
|
+
env
|
|
17682
|
+
});
|
|
17126
17683
|
let r = run([
|
|
17127
|
-
`projects/${
|
|
17684
|
+
`projects/${endpoint.path.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
|
|
17128
17685
|
]);
|
|
17129
|
-
if (r.status !== 0) {
|
|
17130
|
-
r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
|
|
17131
|
-
}
|
|
17132
17686
|
if (r.status !== 0)
|
|
17133
17687
|
return { ok: false, error: "could not list merge requests" };
|
|
17134
17688
|
for (const mr of JSON.parse(r.stdout ?? "[]")) {
|
|
@@ -17141,7 +17695,18 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
17141
17695
|
});
|
|
17142
17696
|
}
|
|
17143
17697
|
} else if (provider === "github") {
|
|
17144
|
-
const r = spawnSync("gh", [
|
|
17698
|
+
const r = spawnSync("gh", [
|
|
17699
|
+
"pr",
|
|
17700
|
+
"list",
|
|
17701
|
+
"--repo",
|
|
17702
|
+
`${host}/${endpoint.path}`,
|
|
17703
|
+
"--state",
|
|
17704
|
+
"merged",
|
|
17705
|
+
"--limit",
|
|
17706
|
+
String(limit),
|
|
17707
|
+
"--json",
|
|
17708
|
+
"title,url,body"
|
|
17709
|
+
], { cwd, encoding: "utf8", env });
|
|
17145
17710
|
if (r.status !== 0)
|
|
17146
17711
|
return { ok: false, error: "could not list pull requests" };
|
|
17147
17712
|
for (const pr of JSON.parse(r.stdout ?? "[]")) {
|
|
@@ -17163,26 +17728,28 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
17163
17728
|
}
|
|
17164
17729
|
|
|
17165
17730
|
// packages/workit-core/src/core/branch.ts
|
|
17166
|
-
|
|
17731
|
+
function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
17732
|
+
const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
17733
|
+
const config = readConfig();
|
|
17734
|
+
return kind === "branch" ? {
|
|
17735
|
+
branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
|
|
17736
|
+
provenance: { branchPolicy: selected.source }
|
|
17737
|
+
} : {
|
|
17738
|
+
commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
|
|
17739
|
+
provenance: { commitPolicy: selected.source }
|
|
17740
|
+
};
|
|
17741
|
+
}
|
|
17742
|
+
var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
|
|
17167
17743
|
var verifyPushIdentity = (root, provider, account) => {
|
|
17168
17744
|
if (!account || typeof account !== "string")
|
|
17169
17745
|
return { ok: false, error: "push identity requires a configured area account" };
|
|
17170
|
-
const
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
17174
|
-
login = execFileSync2(bin, [...probe], {
|
|
17175
|
-
cwd: root,
|
|
17176
|
-
encoding: "utf8",
|
|
17177
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
17178
|
-
}).trim();
|
|
17179
|
-
} catch {
|
|
17180
|
-
return { ok: false, error: `${bin} identity could not be resolved` };
|
|
17181
|
-
}
|
|
17182
|
-
if (!login || login.toLowerCase() !== account.toLowerCase())
|
|
17746
|
+
const identity = vcsCliIdentity(root);
|
|
17747
|
+
if (!identity.ok || identity.provider !== provider)
|
|
17748
|
+
return { ok: false, error: identity.error ?? "hosting CLI identity could not be resolved" };
|
|
17749
|
+
if (identity.username?.toLowerCase() !== account.toLowerCase())
|
|
17183
17750
|
return {
|
|
17184
17751
|
ok: false,
|
|
17185
|
-
error: `push identity ${
|
|
17752
|
+
error: `push identity ${identity.username || "(unknown)"} does not match area account ${account}`
|
|
17186
17753
|
};
|
|
17187
17754
|
return { ok: true };
|
|
17188
17755
|
};
|
|
@@ -17200,7 +17767,7 @@ var operationAutoClass = (operation) => typeof operation === "string" ? OPERATIO
|
|
|
17200
17767
|
var digestOf = (configDirPath) => {
|
|
17201
17768
|
try {
|
|
17202
17769
|
const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
|
|
17203
|
-
return
|
|
17770
|
+
return createHash4("sha256").update(raw).digest("hex");
|
|
17204
17771
|
} catch {
|
|
17205
17772
|
return null;
|
|
17206
17773
|
}
|
|
@@ -17238,14 +17805,30 @@ var resolveAutoApproval = (root, dir = configDir()) => {
|
|
|
17238
17805
|
};
|
|
17239
17806
|
};
|
|
17240
17807
|
var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
|
|
17241
|
-
var
|
|
17808
|
+
var targetOf = (root, descriptor) => {
|
|
17809
|
+
try {
|
|
17810
|
+
const parsed = typeof descriptor === "string" ? JSON.parse(descriptor) : descriptor;
|
|
17811
|
+
const cwd = parsed?.payload?.cwd;
|
|
17812
|
+
if (cwd === undefined)
|
|
17813
|
+
return root;
|
|
17814
|
+
if (typeof cwd !== "string" || !path8.isAbsolute(cwd))
|
|
17815
|
+
return null;
|
|
17816
|
+
return realpathSync3(cwd);
|
|
17817
|
+
} catch {
|
|
17818
|
+
return null;
|
|
17819
|
+
}
|
|
17820
|
+
};
|
|
17821
|
+
var standingApprovalLive = (root, receipt, cls, dir, descriptor) => {
|
|
17242
17822
|
if (!isStandingReceipt(receipt))
|
|
17243
17823
|
return false;
|
|
17244
17824
|
if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
|
|
17245
17825
|
return false;
|
|
17246
17826
|
if (receipt.class !== cls)
|
|
17247
17827
|
return false;
|
|
17248
|
-
const
|
|
17828
|
+
const target = targetOf(root, descriptor);
|
|
17829
|
+
if (!target)
|
|
17830
|
+
return false;
|
|
17831
|
+
const resolved = resolveAutoApproval(target, dir);
|
|
17249
17832
|
if (resolved.status !== "on")
|
|
17250
17833
|
return false;
|
|
17251
17834
|
if (resolved.workspace !== receipt.workspace)
|
|
@@ -17264,9 +17847,11 @@ var standingReceiptFor = (root, workspace, cls, dir) => {
|
|
|
17264
17847
|
var verifyStandingApproval = (root, task, caller, binding) => {
|
|
17265
17848
|
if (task.status !== "active")
|
|
17266
17849
|
return failure("invalid_transition", "only active tasks can authorize actions");
|
|
17267
|
-
const
|
|
17268
|
-
if (!
|
|
17269
|
-
return
|
|
17850
|
+
const workspace = new TaskStore(root).readWorkspace();
|
|
17851
|
+
if (!workspace.ok)
|
|
17852
|
+
return workspace;
|
|
17853
|
+
if (!workspace.data || !currentWriterOwnsTask(task, workspace.data, caller))
|
|
17854
|
+
return failure("permission_denied", "standing approval requires the current writer session");
|
|
17270
17855
|
const standing = binding.standing;
|
|
17271
17856
|
if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
|
|
17272
17857
|
return failure("invalid_input", "standing approval needs a workspace and class");
|
|
@@ -17279,16 +17864,19 @@ var verifyStandingApproval = (root, task, caller, binding) => {
|
|
|
17279
17864
|
const cls = operationAutoClass(operation);
|
|
17280
17865
|
if (!cls || cls !== standing.class)
|
|
17281
17866
|
return failure("permission_denied", "standing approval does not cover this operation");
|
|
17282
|
-
const
|
|
17867
|
+
const target = targetOf(root, binding.approvedContent);
|
|
17868
|
+
if (!target)
|
|
17869
|
+
return failure("permission_denied", "standing action target is invalid");
|
|
17870
|
+
const resolved = resolveAutoApproval(target);
|
|
17283
17871
|
if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
|
|
17284
17872
|
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
17285
17873
|
if (cls === "push") {
|
|
17286
|
-
const workspace = resolveWorkspaceFrom(
|
|
17874
|
+
const workspace = resolveWorkspaceFrom(target, configDir());
|
|
17287
17875
|
const provider = workspace?.vcs?.provider;
|
|
17288
17876
|
const account = resolved.account;
|
|
17289
17877
|
if (!provider || !account)
|
|
17290
17878
|
return failure("permission_denied", "auto-push requires a workspace provider and area account");
|
|
17291
|
-
const identity = verifyPushIdentity(
|
|
17879
|
+
const identity = verifyPushIdentity(target, provider, account);
|
|
17292
17880
|
if (!identity.ok)
|
|
17293
17881
|
return failure("permission_denied", identity.error);
|
|
17294
17882
|
}
|
|
@@ -17297,7 +17885,7 @@ var verifyStandingApproval = (root, task, caller, binding) => {
|
|
|
17297
17885
|
host: caller.host,
|
|
17298
17886
|
session: { kind: "host", host: caller.host, handle: caller.actor },
|
|
17299
17887
|
workerId: null,
|
|
17300
|
-
receipts: [standingReceiptFor(
|
|
17888
|
+
receipts: [standingReceiptFor(target, standing.workspace, cls)]
|
|
17301
17889
|
});
|
|
17302
17890
|
};
|
|
17303
17891
|
|
|
@@ -17448,7 +18036,7 @@ function applicableDecision(task, purpose, binding, checkoutRoot) {
|
|
|
17448
18036
|
return [];
|
|
17449
18037
|
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);
|
|
17450
18038
|
}
|
|
17451
|
-
var digestBytes2 = (bytes) =>
|
|
18039
|
+
var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
17452
18040
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
17453
18041
|
let root;
|
|
17454
18042
|
try {
|
|
@@ -17461,23 +18049,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
17461
18049
|
continue;
|
|
17462
18050
|
if (!reference.digest)
|
|
17463
18051
|
return failure("invalid_input", "document references require a byte digest");
|
|
17464
|
-
const target =
|
|
17465
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
18052
|
+
const target = path9.resolve(root, reference.path);
|
|
18053
|
+
if (target !== root && !target.startsWith(`${root}${path9.sep}`))
|
|
17466
18054
|
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
17467
18055
|
fields: [
|
|
17468
18056
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
17469
18057
|
]
|
|
17470
18058
|
});
|
|
17471
18059
|
try {
|
|
17472
|
-
const relative =
|
|
18060
|
+
const relative = path9.relative(root, target);
|
|
17473
18061
|
let current = root;
|
|
17474
|
-
for (const segment of relative.split(
|
|
18062
|
+
for (const segment of relative.split(path9.sep)) {
|
|
17475
18063
|
if (!segment)
|
|
17476
18064
|
continue;
|
|
17477
|
-
current =
|
|
18065
|
+
current = path9.join(current, segment);
|
|
17478
18066
|
const stat = fs6.lstatSync(current);
|
|
17479
18067
|
const real = fs6.realpathSync(current);
|
|
17480
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
18068
|
+
if (real !== root && !real.startsWith(`${root}${path9.sep}`))
|
|
17481
18069
|
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
17482
18070
|
fields: [
|
|
17483
18071
|
{
|
|
@@ -17503,6 +18091,20 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
17503
18091
|
};
|
|
17504
18092
|
var verifyContentRefs = (store, binding) => verifyDecisionContentAtRoot(store.root, binding);
|
|
17505
18093
|
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);
|
|
18094
|
+
var approvalAuthorityMatches = (store, task, approval, current) => {
|
|
18095
|
+
if (approval.kind !== "host_observed" || approval.receipts.length === 0)
|
|
18096
|
+
return false;
|
|
18097
|
+
if (provenanceMatches(approval, current))
|
|
18098
|
+
return true;
|
|
18099
|
+
const session = current.kind === "host_observed" ? current.session : null;
|
|
18100
|
+
if (!session || session.kind !== "host")
|
|
18101
|
+
return false;
|
|
18102
|
+
const workspace = store.readWorkspace();
|
|
18103
|
+
return workspace.ok && workspace.data !== null && currentWriterOwnsTask(task, workspace.data, {
|
|
18104
|
+
host: session.host,
|
|
18105
|
+
actor: session.handle
|
|
18106
|
+
});
|
|
18107
|
+
};
|
|
17506
18108
|
var validateAction = (store, task, workspaceId, input, authority) => {
|
|
17507
18109
|
if (task.status !== "active")
|
|
17508
18110
|
return failure("invalid_transition", "only active tasks can authorize actions");
|
|
@@ -17528,7 +18130,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
|
|
|
17528
18130
|
return failure("permission_denied", "native reservation authority does not match decision");
|
|
17529
18131
|
if (decision.response !== "approved")
|
|
17530
18132
|
return failure("permission_denied", "decision was rejected");
|
|
17531
|
-
if (
|
|
18133
|
+
if (!approvalAuthorityMatches(store, task, entry.provenance, authority.provenance))
|
|
17532
18134
|
return failure("permission_denied", "action approval lacks native receipt assurance");
|
|
17533
18135
|
const receipts = entry.provenance.receipts;
|
|
17534
18136
|
const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
|
|
@@ -17541,7 +18143,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
|
|
|
17541
18143
|
operation = null;
|
|
17542
18144
|
}
|
|
17543
18145
|
const cls = operationAutoClass(operation);
|
|
17544
|
-
if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
|
|
18146
|
+
if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls, undefined, decision.binding.approvedContent)))
|
|
17545
18147
|
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
17546
18148
|
}
|
|
17547
18149
|
if (decision.revoked)
|
|
@@ -17695,7 +18297,7 @@ function settleAction(input) {
|
|
|
17695
18297
|
return failure("not_found", "decision not found");
|
|
17696
18298
|
if (!authorityDecisionMatches(authority, entry.data))
|
|
17697
18299
|
return failure("permission_denied", "native settlement authority does not match decision");
|
|
17698
|
-
if (!
|
|
18300
|
+
if (!approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
|
|
17699
18301
|
return failure("permission_denied", "native settlement caller does not match approval");
|
|
17700
18302
|
if (entry.data.purpose !== "action" || entry.data.digest !== decisionDigest(entry.data))
|
|
17701
18303
|
return failure("permission_denied", "decision binding is invalid");
|
|
@@ -17794,7 +18396,7 @@ function reconcileAction(input) {
|
|
|
17794
18396
|
const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
|
|
17795
18397
|
if (!entry || entry.data.purpose !== "action")
|
|
17796
18398
|
return failure("not_found", "decision not found");
|
|
17797
|
-
if (!authorityDecisionMatches(authority, entry.data) || !
|
|
18399
|
+
if (!authorityDecisionMatches(authority, entry.data) || !approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
|
|
17798
18400
|
return failure("permission_denied", "native reconciliation caller does not match approval");
|
|
17799
18401
|
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)
|
|
17800
18402
|
return failure("permission_denied", "reconciliation decision binding is invalid");
|
|
@@ -17843,15 +18445,15 @@ var bindingCovers = scopeCovers;
|
|
|
17843
18445
|
var verifyDecisionContent = verifyContentRefs;
|
|
17844
18446
|
|
|
17845
18447
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
17846
|
-
var digestBytes3 = (value) =>
|
|
17847
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
17848
|
-
var relative = (root, candidate) =>
|
|
18448
|
+
var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
18449
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
|
|
18450
|
+
var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
|
|
17849
18451
|
var canonicalPath = (value) => {
|
|
17850
18452
|
if (typeof value !== "string" || !value)
|
|
17851
18453
|
return null;
|
|
17852
18454
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
17853
18455
|
return null;
|
|
17854
|
-
const normalized =
|
|
18456
|
+
const normalized = path10.posix.normalize(value);
|
|
17855
18457
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
17856
18458
|
return null;
|
|
17857
18459
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -17899,14 +18501,14 @@ var gitPaths = (root) => {
|
|
|
17899
18501
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
17900
18502
|
};
|
|
17901
18503
|
}
|
|
17902
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
18504
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
|
|
17903
18505
|
const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
17904
18506
|
if (stagedDeleted.status !== 0) {
|
|
17905
18507
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
17906
18508
|
if (!/not a git repository/i.test(stderr))
|
|
17907
18509
|
return { paths, uncertain: true, git: true };
|
|
17908
18510
|
} else if (stagedDeleted.stdout) {
|
|
17909
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
18511
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
|
|
17910
18512
|
}
|
|
17911
18513
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
17912
18514
|
};
|
|
@@ -17921,7 +18523,7 @@ var walk = (root, directory, output) => {
|
|
|
17921
18523
|
for (const name of names) {
|
|
17922
18524
|
if (name === ".git" || name === ".workit")
|
|
17923
18525
|
continue;
|
|
17924
|
-
const target =
|
|
18526
|
+
const target = path10.join(directory, name);
|
|
17925
18527
|
let stat;
|
|
17926
18528
|
try {
|
|
17927
18529
|
stat = fs7.lstatSync(target);
|
|
@@ -17939,12 +18541,12 @@ var walk = (root, directory, output) => {
|
|
|
17939
18541
|
var scopeRoots = (root, scope) => {
|
|
17940
18542
|
const roots = [];
|
|
17941
18543
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
17942
|
-
const target =
|
|
18544
|
+
const target = path10.resolve(root, item);
|
|
17943
18545
|
if (!inside(root, target))
|
|
17944
18546
|
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` }] });
|
|
17945
18547
|
let ancestor = target;
|
|
17946
18548
|
while (!fs7.existsSync(ancestor) && ancestor !== root)
|
|
17947
|
-
ancestor =
|
|
18549
|
+
ancestor = path10.dirname(ancestor);
|
|
17948
18550
|
try {
|
|
17949
18551
|
if (!inside(root, fs7.realpathSync(ancestor)))
|
|
17950
18552
|
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` }] });
|
|
@@ -17996,7 +18598,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
17996
18598
|
for (const item of [...names].sort()) {
|
|
17997
18599
|
if (!scopeMatches(item, normalizedScope))
|
|
17998
18600
|
continue;
|
|
17999
|
-
const target =
|
|
18601
|
+
const target = path10.join(checkout, item);
|
|
18000
18602
|
try {
|
|
18001
18603
|
const stat = fs7.lstatSync(target);
|
|
18002
18604
|
if (stat.isSymbolicLink()) {
|
|
@@ -18131,7 +18733,7 @@ var resolveBinding = (evidence, pin, currentCandidateId) => {
|
|
|
18131
18733
|
candidateId: evidence.candidateId ?? fallback
|
|
18132
18734
|
};
|
|
18133
18735
|
};
|
|
18134
|
-
var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId
|
|
18736
|
+
var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
|
|
18135
18737
|
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")));
|
|
18136
18738
|
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 }));
|
|
18137
18739
|
var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
|
|
@@ -18366,6 +18968,11 @@ var exportDigest = (bundle) => sha256({
|
|
|
18366
18968
|
task: bundle.task
|
|
18367
18969
|
});
|
|
18368
18970
|
// packages/workit-core/src/core/external-action.ts
|
|
18971
|
+
var chainStepSchema = union([
|
|
18972
|
+
string2().min(1),
|
|
18973
|
+
object({ branch: string2().min(1) }).strict(),
|
|
18974
|
+
object({ pr: literal(true) }).strict()
|
|
18975
|
+
]);
|
|
18369
18976
|
var externalActionSchema = discriminatedUnion("operation", [
|
|
18370
18977
|
object({
|
|
18371
18978
|
operation: literal("git.branch_setup"),
|
|
@@ -18373,20 +18980,22 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
18373
18980
|
action: _enum(["setup", "reapply_stash"]).optional(),
|
|
18374
18981
|
sdd_dir: string2().optional(),
|
|
18375
18982
|
target_branch: string2().optional(),
|
|
18376
|
-
stash: _enum(["yes", "no"]).optional()
|
|
18983
|
+
stash: _enum(["yes", "no"]).optional(),
|
|
18984
|
+
cwd: string2().min(1).optional()
|
|
18377
18985
|
}).strict()
|
|
18378
18986
|
}).strict(),
|
|
18379
18987
|
object({
|
|
18380
18988
|
operation: literal("git.commit"),
|
|
18381
18989
|
payload: object({
|
|
18382
18990
|
message: string2().min(1).optional(),
|
|
18383
|
-
plan_steps: array(
|
|
18384
|
-
plan_branch: string2().min(1).optional()
|
|
18991
|
+
plan_steps: array(chainStepSchema).min(1).max(32).optional(),
|
|
18992
|
+
plan_branch: string2().min(1).optional(),
|
|
18993
|
+
cwd: string2().min(1).optional()
|
|
18385
18994
|
}).strict()
|
|
18386
18995
|
}).strict(),
|
|
18387
18996
|
object({
|
|
18388
18997
|
operation: literal("git.push"),
|
|
18389
|
-
payload: object({ branch: string2().min(1).optional() }).strict()
|
|
18998
|
+
payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
|
|
18390
18999
|
}).strict(),
|
|
18391
19000
|
object({
|
|
18392
19001
|
operation: literal("hosting.pull_request"),
|
|
@@ -18395,16 +19004,22 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
18395
19004
|
body: string2().optional(),
|
|
18396
19005
|
draft: boolean2().optional(),
|
|
18397
19006
|
target_branch: string2().optional(),
|
|
18398
|
-
babysit: boolean2().optional()
|
|
19007
|
+
babysit: boolean2().optional(),
|
|
19008
|
+
cwd: string2().min(1).optional()
|
|
18399
19009
|
}).strict()
|
|
18400
19010
|
}).strict(),
|
|
18401
19011
|
object({
|
|
18402
19012
|
operation: literal("hosting.merge"),
|
|
18403
19013
|
payload: object({
|
|
18404
19014
|
target_branch: string2().min(1).optional(),
|
|
18405
|
-
source_branch: string2().min(1).optional()
|
|
19015
|
+
source_branch: string2().min(1).optional(),
|
|
19016
|
+
cwd: string2().min(1).optional()
|
|
18406
19017
|
}).strict()
|
|
18407
19018
|
}).strict(),
|
|
19019
|
+
object({
|
|
19020
|
+
operation: literal("hosting.delete_branch"),
|
|
19021
|
+
payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
|
|
19022
|
+
}).strict(),
|
|
18408
19023
|
object({
|
|
18409
19024
|
operation: literal("youtrack.update"),
|
|
18410
19025
|
payload: object({
|
|
@@ -18460,12 +19075,13 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
18460
19075
|
issueRef: string2().optional(),
|
|
18461
19076
|
mode: string2().optional(),
|
|
18462
19077
|
specPath: string2().optional(),
|
|
18463
|
-
planPath: string2().optional()
|
|
19078
|
+
planPath: string2().optional(),
|
|
19079
|
+
cwd: string2().min(1).optional()
|
|
18464
19080
|
}).strict()
|
|
18465
19081
|
}).strict()
|
|
18466
19082
|
]);
|
|
18467
19083
|
// packages/workit-core/src/core/task-engine.ts
|
|
18468
|
-
import { realpathSync as
|
|
19084
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
18469
19085
|
|
|
18470
19086
|
// packages/workit-core/src/core/policy-resolver.ts
|
|
18471
19087
|
var resolverInputSchema = object({
|
|
@@ -18866,6 +19482,7 @@ var verifyNativeWorker = (verifier, input, binding) => {
|
|
|
18866
19482
|
expectedWorkspaceRevision: expected.expectedWorkspaceRevision,
|
|
18867
19483
|
state: expected.state,
|
|
18868
19484
|
session: expected.session,
|
|
19485
|
+
report: expected.report ?? null,
|
|
18869
19486
|
provenance: result.data,
|
|
18870
19487
|
...binding
|
|
18871
19488
|
});
|
|
@@ -18876,7 +19493,7 @@ var takeWorker = (token, binding, expected) => {
|
|
|
18876
19493
|
verifiedWorkers.delete(token);
|
|
18877
19494
|
if (!value)
|
|
18878
19495
|
return null;
|
|
18879
|
-
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))
|
|
19496
|
+
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))
|
|
18880
19497
|
return null;
|
|
18881
19498
|
return value;
|
|
18882
19499
|
};
|
|
@@ -18917,6 +19534,8 @@ var applyWorkerLifecycle = (input) => {
|
|
|
18917
19534
|
return failure("invalid_transition", "worker already has an observed session");
|
|
18918
19535
|
if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
|
|
18919
19536
|
return failure("invalid_transition", "worker launch was not claimed");
|
|
19537
|
+
if (input.report && input.state !== "stopped")
|
|
19538
|
+
return failure("invalid_input", "worker reports require a stopped worker");
|
|
18920
19539
|
if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
|
|
18921
19540
|
return failure("invalid_input", "running worker observations require a session");
|
|
18922
19541
|
if (entry.data.session && !sameValue(entry.data.session, input.session))
|
|
@@ -18933,12 +19552,13 @@ var applyWorkerLifecycle = (input) => {
|
|
|
18933
19552
|
data: {
|
|
18934
19553
|
...entry.data,
|
|
18935
19554
|
state: terminalCancel ? "cancelling" : input.state,
|
|
18936
|
-
session: input.session
|
|
19555
|
+
session: input.session,
|
|
19556
|
+
report: entry.data.report ?? input.report ?? null
|
|
18937
19557
|
}
|
|
18938
19558
|
};
|
|
18939
19559
|
const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
18940
19560
|
const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
18941
|
-
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
19561
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && sameValue(entry.data.report, nextEntry.data.report) && !shouldClear && !shouldUncertain)
|
|
18942
19562
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
18943
19563
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
18944
19564
|
taskId: task.data.id,
|
|
@@ -19006,6 +19626,18 @@ var portableTask = (task) => {
|
|
|
19006
19626
|
};
|
|
19007
19627
|
for (const entry of clone.assessments)
|
|
19008
19628
|
demote(entry.data.facts, entry.data.signals, entry.data.consequences);
|
|
19629
|
+
const sourceDecisions = new Map(task.decisions.map((entry) => [entry.id, entry]));
|
|
19630
|
+
for (const entry of clone.decisions) {
|
|
19631
|
+
const consumption = sourceDecisions.get(entry.id)?.data.consumption;
|
|
19632
|
+
if (!consumption)
|
|
19633
|
+
continue;
|
|
19634
|
+
entry.data.consumption = {
|
|
19635
|
+
...consumption,
|
|
19636
|
+
state: consumption.state === "reserved" ? "uncertain" : consumption.state,
|
|
19637
|
+
actionRef: { kind: "record", collection: "decisions", id: entry.id },
|
|
19638
|
+
...consumption.historicalActionId ? { historicalActionId: consumption.historicalActionId } : consumption.actionRef.kind === "host" ? { historicalActionId: consumption.actionRef.handle } : {}
|
|
19639
|
+
};
|
|
19640
|
+
}
|
|
19009
19641
|
clone.candidates = [];
|
|
19010
19642
|
return clone;
|
|
19011
19643
|
};
|
|
@@ -19131,7 +19763,11 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
19131
19763
|
taskId: "",
|
|
19132
19764
|
workspaceId: destinationWorkspaceId
|
|
19133
19765
|
},
|
|
19134
|
-
consumption:
|
|
19766
|
+
consumption: data.consumption ? {
|
|
19767
|
+
...data.consumption,
|
|
19768
|
+
state: data.consumption.state === "reserved" ? "uncertain" : data.consumption.state,
|
|
19769
|
+
actionRef: { kind: "record", collection: "decisions", id: entry.id }
|
|
19770
|
+
} : null
|
|
19135
19771
|
}
|
|
19136
19772
|
};
|
|
19137
19773
|
}),
|
|
@@ -19172,7 +19808,7 @@ class WorkitCore {
|
|
|
19172
19808
|
}
|
|
19173
19809
|
contextRootError() {
|
|
19174
19810
|
try {
|
|
19175
|
-
if (
|
|
19811
|
+
if (realpathSync6(this.context.root) !== this.store.root)
|
|
19176
19812
|
return failure("invalid_input", "operation context root does not match the task store root");
|
|
19177
19813
|
} catch {
|
|
19178
19814
|
return failure("invalid_input", "operation context root cannot be resolved");
|
|
@@ -19331,7 +19967,7 @@ class WorkitCore {
|
|
|
19331
19967
|
return parsed;
|
|
19332
19968
|
const input = parsed.data;
|
|
19333
19969
|
if ((this.context.workerId ?? null) !== null) {
|
|
19334
|
-
if (input.action !== "inspect")
|
|
19970
|
+
if (input.action !== "inspect" && input.action !== "list")
|
|
19335
19971
|
return failure("permission_denied", "helpers cannot control task lifecycle or scope");
|
|
19336
19972
|
}
|
|
19337
19973
|
switch (input.action) {
|
|
@@ -19356,14 +19992,45 @@ class WorkitCore {
|
|
|
19356
19992
|
const listed = this.store.listTasks();
|
|
19357
19993
|
if (!listed.ok)
|
|
19358
19994
|
return listed;
|
|
19359
|
-
const
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
19364
|
-
|
|
19365
|
-
|
|
19366
|
-
|
|
19995
|
+
const status = input.status ?? "open";
|
|
19996
|
+
const limit = input.limit ?? 20;
|
|
19997
|
+
const query = input.query?.toLowerCase().split(/\s+/);
|
|
19998
|
+
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({
|
|
19999
|
+
id: task.id,
|
|
20000
|
+
workspaceId: task.workspaceId,
|
|
20001
|
+
origin: task.origin,
|
|
20002
|
+
createdAt: task.createdAt,
|
|
20003
|
+
updatedAt: task.updatedAt,
|
|
20004
|
+
source: task.intent.provenance,
|
|
20005
|
+
objective: task.intent.data.objective,
|
|
20006
|
+
progress: task.progress,
|
|
20007
|
+
decisions: task.decisions.map(({ data }) => data)
|
|
20008
|
+
}).toLowerCase().includes(term))).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit);
|
|
20009
|
+
if (selected.length === 0)
|
|
20010
|
+
return success(null, null, []);
|
|
20011
|
+
const workspace = this.store.readWorkspace();
|
|
20012
|
+
if (!workspace.ok)
|
|
20013
|
+
return workspace;
|
|
20014
|
+
if (!workspace.data)
|
|
20015
|
+
return failure("not_found", "workspace not found");
|
|
20016
|
+
const tasks = selected.map((task) => ({
|
|
20017
|
+
id: task.id,
|
|
20018
|
+
revision: task.revision,
|
|
20019
|
+
workspaceRevision: workspace.data.revision,
|
|
20020
|
+
createdAt: task.createdAt,
|
|
20021
|
+
updatedAt: task.updatedAt,
|
|
20022
|
+
runtime: task.runtime ?? null,
|
|
20023
|
+
objective: task.intent.data.objective,
|
|
20024
|
+
status: task.status,
|
|
20025
|
+
closure: task.closure,
|
|
20026
|
+
progress: task.progress,
|
|
20027
|
+
writer: task.status === "closed" ? null : workspace.data.writer,
|
|
20028
|
+
source: {
|
|
20029
|
+
host: task.intent.provenance.host,
|
|
20030
|
+
kind: task.intent.provenance.kind
|
|
20031
|
+
}
|
|
20032
|
+
}));
|
|
20033
|
+
return success(null, null, tasks);
|
|
19367
20034
|
}
|
|
19368
20035
|
case "inspect": {
|
|
19369
20036
|
const task = this.store.readTask(input.taskId);
|
|
@@ -19372,7 +20039,7 @@ class WorkitCore {
|
|
|
19372
20039
|
const helper = this.helperTaskGuard(task.data, true);
|
|
19373
20040
|
if (!helper.ok)
|
|
19374
20041
|
return helper;
|
|
19375
|
-
if (input.view === "summary")
|
|
20042
|
+
if ((input.view ?? "summary") === "summary")
|
|
19376
20043
|
return this.summary(task.data);
|
|
19377
20044
|
return this.view(task.data);
|
|
19378
20045
|
}
|
|
@@ -19716,7 +20383,7 @@ class WorkitCore {
|
|
|
19716
20383
|
const evaluations = evaluateEvidence(task.data, current.data);
|
|
19717
20384
|
const verified = evidence.some((entry) => {
|
|
19718
20385
|
const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
|
|
19719
|
-
return findingVerificationPasses(finding.data.candidateId, {
|
|
20386
|
+
return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
|
|
19720
20387
|
kind: entry.data.kind,
|
|
19721
20388
|
candidateId: entry.data.candidateId,
|
|
19722
20389
|
status: evaluation?.status ?? "missing"
|
|
@@ -19796,7 +20463,18 @@ class WorkitCore {
|
|
|
19796
20463
|
if (task.data.status !== "active")
|
|
19797
20464
|
return failure("invalid_transition", "paused or closed tasks cannot assign workers");
|
|
19798
20465
|
if (!bindingCovers(task.data.intent.data.scope, input.assignment.scope))
|
|
19799
|
-
return failure("permission_denied", "worker assignment is outside task scope"
|
|
20466
|
+
return failure("permission_denied", "worker assignment is outside task scope", {
|
|
20467
|
+
fields: [
|
|
20468
|
+
{
|
|
20469
|
+
path: "assignment.scope",
|
|
20470
|
+
reason: `paths ${JSON.stringify(input.assignment.scope.paths)} must be contained by lead paths ${JSON.stringify(task.data.intent.data.scope.paths)}`
|
|
20471
|
+
},
|
|
20472
|
+
{
|
|
20473
|
+
path: "task.intent.scope",
|
|
20474
|
+
reason: `lead paths are ${JSON.stringify(task.data.intent.data.scope.paths)}`
|
|
20475
|
+
}
|
|
20476
|
+
]
|
|
20477
|
+
});
|
|
19800
20478
|
if (input.assignment.decisionIds.some((id) => !task.data.decisions.some((entry) => entry.id === id)))
|
|
19801
20479
|
return failure("invalid_input", "worker assignment references an unknown decision");
|
|
19802
20480
|
if (input.assignment.requirementIds.some((id) => !task.data.policy?.requirements.some((requirement) => requirement.id === id)))
|
|
@@ -20003,7 +20681,15 @@ class WorkitCore {
|
|
|
20003
20681
|
...item,
|
|
20004
20682
|
recordedAt: mutation.now,
|
|
20005
20683
|
provenance: attested.data,
|
|
20006
|
-
data: {
|
|
20684
|
+
data: {
|
|
20685
|
+
...item.data,
|
|
20686
|
+
state: "dispatching",
|
|
20687
|
+
coordinator: {
|
|
20688
|
+
kind: "host",
|
|
20689
|
+
host: this.context.caller.host,
|
|
20690
|
+
handle: this.context.caller.actor
|
|
20691
|
+
}
|
|
20692
|
+
}
|
|
20007
20693
|
} : item)
|
|
20008
20694
|
});
|
|
20009
20695
|
}
|
|
@@ -20079,6 +20765,7 @@ class WorkitCore {
|
|
|
20079
20765
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
20080
20766
|
state: "stopped",
|
|
20081
20767
|
session: null,
|
|
20768
|
+
report: null,
|
|
20082
20769
|
provenance: attested.data,
|
|
20083
20770
|
owner: this.authorityOwner,
|
|
20084
20771
|
store: this.store,
|
|
@@ -20386,10 +21073,12 @@ class WorkitCore {
|
|
|
20386
21073
|
return workspace;
|
|
20387
21074
|
if (!workspace.data)
|
|
20388
21075
|
return failure("not_found", "workspace not found");
|
|
20389
|
-
const
|
|
21076
|
+
const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
|
|
21077
|
+
const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
|
|
20390
21078
|
if (!current.ok)
|
|
20391
21079
|
return current;
|
|
20392
|
-
const
|
|
21080
|
+
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
21081
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
|
|
20393
21082
|
return success(task.revision, workspace.data.revision, {
|
|
20394
21083
|
id: task.id,
|
|
20395
21084
|
revision: task.revision,
|
|
@@ -20403,7 +21092,7 @@ class WorkitCore {
|
|
|
20403
21092
|
progress: task.progress,
|
|
20404
21093
|
policy: task.policy,
|
|
20405
21094
|
requirements,
|
|
20406
|
-
writer:
|
|
21095
|
+
writer: evaluationWorkspace.writer
|
|
20407
21096
|
});
|
|
20408
21097
|
}
|
|
20409
21098
|
view(task) {
|
|
@@ -20412,13 +21101,15 @@ class WorkitCore {
|
|
|
20412
21101
|
return workspace;
|
|
20413
21102
|
if (!workspace.data)
|
|
20414
21103
|
return failure("not_found", "workspace not found");
|
|
20415
|
-
const
|
|
21104
|
+
const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
|
|
21105
|
+
const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
|
|
20416
21106
|
if (!current.ok)
|
|
20417
21107
|
return current;
|
|
20418
|
-
const
|
|
21108
|
+
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
21109
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
|
|
20419
21110
|
return success(task.revision, workspace.data.revision, {
|
|
20420
21111
|
task,
|
|
20421
|
-
workspace:
|
|
21112
|
+
workspace: evaluationWorkspace,
|
|
20422
21113
|
capabilities: this.context.capabilities,
|
|
20423
21114
|
requirements,
|
|
20424
21115
|
evidence: evaluateEvidence(task, current.data)
|
|
@@ -20433,7 +21124,7 @@ class WorkitCore {
|
|
|
20433
21124
|
if (view.task.workspaceId !== view.workspace.id)
|
|
20434
21125
|
return failure("invalid_input", "task and workspace bindings are invalid");
|
|
20435
21126
|
try {
|
|
20436
|
-
if (
|
|
21127
|
+
if (realpathSync6(view.workspace.root) !== this.store.root)
|
|
20437
21128
|
return failure("invalid_input", "task view root does not match the task store");
|
|
20438
21129
|
} catch {
|
|
20439
21130
|
return failure("invalid_input", "task view root cannot be resolved");
|
|
@@ -20509,8 +21200,6 @@ class WorkitCore {
|
|
|
20509
21200
|
return helper;
|
|
20510
21201
|
if (status === "paused" && task.data.status !== "active" || status === "active" && task.data.status !== "paused")
|
|
20511
21202
|
return failure("invalid_transition", `cannot transition ${task.data.status} to ${status}`);
|
|
20512
|
-
if (status === "active" && input.authorityRefs.length === 0)
|
|
20513
|
-
return failure("permission_denied", "resuming a task requires authority references");
|
|
20514
21203
|
const workspace = this.store.readWorkspace();
|
|
20515
21204
|
if (!workspace.ok)
|
|
20516
21205
|
return workspace;
|
|
@@ -20540,7 +21229,7 @@ class WorkitCore {
|
|
|
20540
21229
|
return failure("needs_input", "imported task requires fresh destination evidence");
|
|
20541
21230
|
if (current.data.blockers.some((blocker) => blocker.dependentAction === "resume" || blocker.reason === "worker state requires reconciliation"))
|
|
20542
21231
|
return failure("recovery_required", "imported task requires resume reconciliation");
|
|
20543
|
-
if (!validResumeApproval(task.data, workspace.data, input.authorityRefs, this.context))
|
|
21232
|
+
if (!validResumeApproval(task.data, workspace.data, input.authorityRefs ?? [], this.context))
|
|
20544
21233
|
return failure("permission_denied", "imported resume requires native destination approval");
|
|
20545
21234
|
resumeCandidate = current.data.candidate;
|
|
20546
21235
|
}
|
|
@@ -20670,7 +21359,7 @@ class WorkitCore {
|
|
|
20670
21359
|
task: (value, context) => success(context.revision, null, {
|
|
20671
21360
|
...value,
|
|
20672
21361
|
status: "closed",
|
|
20673
|
-
candidates:
|
|
21362
|
+
candidates: taskForView.candidates,
|
|
20674
21363
|
closure: {
|
|
20675
21364
|
outcome: closure.data.outcome,
|
|
20676
21365
|
at: context.now,
|
|
@@ -20712,22 +21401,22 @@ function resolveGitRevision(root, value) {
|
|
|
20712
21401
|
}
|
|
20713
21402
|
}
|
|
20714
21403
|
function resolveInside(root, candidate) {
|
|
20715
|
-
const base =
|
|
20716
|
-
const target =
|
|
20717
|
-
if (target !== base && !target.startsWith(base +
|
|
21404
|
+
const base = realpathSync7(root);
|
|
21405
|
+
const target = path11.resolve(base, candidate);
|
|
21406
|
+
if (target !== base && !target.startsWith(base + path11.sep)) {
|
|
20718
21407
|
throw new Error("path must stay inside repository root");
|
|
20719
21408
|
}
|
|
20720
21409
|
let ancestor = target;
|
|
20721
21410
|
while (!existsSync5(ancestor))
|
|
20722
|
-
ancestor =
|
|
20723
|
-
const canonicalAncestor =
|
|
20724
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
21411
|
+
ancestor = path11.dirname(ancestor);
|
|
21412
|
+
const canonicalAncestor = realpathSync7(ancestor);
|
|
21413
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path11.sep)) {
|
|
20725
21414
|
throw new Error("path must stay inside repository root");
|
|
20726
21415
|
}
|
|
20727
21416
|
return target;
|
|
20728
21417
|
}
|
|
20729
21418
|
function run2(root, executable, args, env = {}) {
|
|
20730
|
-
const cwd =
|
|
21419
|
+
const cwd = realpathSync7(root);
|
|
20731
21420
|
const result = spawnSync3(executable, args, {
|
|
20732
21421
|
cwd,
|
|
20733
21422
|
encoding: "utf8",
|
|
@@ -20751,8 +21440,8 @@ var SUPPORT_MATRIX = {
|
|
|
20751
21440
|
};
|
|
20752
21441
|
|
|
20753
21442
|
// packages/workit-mcp/src/server.ts
|
|
20754
|
-
import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as
|
|
20755
|
-
import
|
|
21443
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as realpathSync8 } from "node:fs";
|
|
21444
|
+
import path15 from "node:path";
|
|
20756
21445
|
|
|
20757
21446
|
// node_modules/@modelcontextprotocol/sdk/node_modules/zod/v4/core/core.js
|
|
20758
21447
|
var NEVER2 = Object.freeze({
|
|
@@ -24279,7 +24968,7 @@ function check(fn) {
|
|
|
24279
24968
|
ch._zod.check = fn;
|
|
24280
24969
|
return ch;
|
|
24281
24970
|
}
|
|
24282
|
-
function
|
|
24971
|
+
function custom2(fn, _params) {
|
|
24283
24972
|
return _custom2(ZodCustom2, fn ?? (() => true), _params);
|
|
24284
24973
|
}
|
|
24285
24974
|
function refine2(fn, _params = {}) {
|
|
@@ -24316,7 +25005,7 @@ var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
|
24316
25005
|
var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
|
|
24317
25006
|
var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
|
|
24318
25007
|
var JSONRPC_VERSION = "2.0";
|
|
24319
|
-
var AssertObjectSchema =
|
|
25008
|
+
var AssertObjectSchema = custom2((v) => v !== null && (typeof v === "object" || typeof v === "function"));
|
|
24320
25009
|
var ProgressTokenSchema = union2([string4(), number4().int()]);
|
|
24321
25010
|
var CursorSchema = string4();
|
|
24322
25011
|
var TaskCreationParamsSchema = looseObject({
|
|
@@ -26662,18 +27351,19 @@ var redactSecrets = (value) => applyPatterns(value);
|
|
|
26662
27351
|
|
|
26663
27352
|
// packages/workit-core/src/core/pr-create.ts
|
|
26664
27353
|
import fs8 from "node:fs";
|
|
26665
|
-
import
|
|
27354
|
+
import path12 from "node:path";
|
|
27355
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
26666
27356
|
function parseGhIssue(value) {
|
|
26667
27357
|
const m = /issues\/(\d+)/.exec(value);
|
|
26668
27358
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
26669
27359
|
}
|
|
26670
27360
|
function whichOnPath(tool) {
|
|
26671
27361
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
26672
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
27362
|
+
for (const dir of (process.env.PATH ?? "").split(path12.delimiter)) {
|
|
26673
27363
|
if (!dir)
|
|
26674
27364
|
continue;
|
|
26675
27365
|
for (const name of names) {
|
|
26676
|
-
const candidate =
|
|
27366
|
+
const candidate = path12.join(dir, name);
|
|
26677
27367
|
try {
|
|
26678
27368
|
fs8.accessSync(candidate, fs8.constants.X_OK);
|
|
26679
27369
|
return candidate;
|
|
@@ -26683,11 +27373,21 @@ function whichOnPath(tool) {
|
|
|
26683
27373
|
return null;
|
|
26684
27374
|
}
|
|
26685
27375
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
27376
|
+
var pushRemote = (cwd) => {
|
|
27377
|
+
const result = spawnSync4("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
27378
|
+
cwd,
|
|
27379
|
+
encoding: "utf8"
|
|
27380
|
+
});
|
|
27381
|
+
if (result.status !== 0)
|
|
27382
|
+
return null;
|
|
27383
|
+
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
27384
|
+
return urls.length === 1 ? urls[0] : null;
|
|
27385
|
+
};
|
|
26686
27386
|
|
|
26687
27387
|
// packages/workit-core/src/core/repo-context.ts
|
|
26688
|
-
import { spawnSync as
|
|
27388
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
26689
27389
|
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
|
|
26690
|
-
import
|
|
27390
|
+
import path13 from "node:path";
|
|
26691
27391
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26692
27392
|
try {
|
|
26693
27393
|
gitRevisionParts(value);
|
|
@@ -26714,7 +27414,7 @@ var invalidRange = (cwd) => ({
|
|
|
26714
27414
|
cwd
|
|
26715
27415
|
});
|
|
26716
27416
|
var runGit = (cwd, args) => {
|
|
26717
|
-
const result =
|
|
27417
|
+
const result = spawnSync5("git", args, { cwd, encoding: "utf8" });
|
|
26718
27418
|
return {
|
|
26719
27419
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
26720
27420
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -26821,7 +27521,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26821
27521
|
]) {
|
|
26822
27522
|
const parts = rel.split("/");
|
|
26823
27523
|
const base = parts[parts.length - 1];
|
|
26824
|
-
const dir = parts.length > 1 ?
|
|
27524
|
+
const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26825
27525
|
const actual = probeCaseInsensitive(dir, base);
|
|
26826
27526
|
if (actual)
|
|
26827
27527
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26852,7 +27552,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26852
27552
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26853
27553
|
};
|
|
26854
27554
|
var packageScripts = (cwd, keys) => {
|
|
26855
|
-
const pkgPath =
|
|
27555
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
26856
27556
|
if (!existsSync6(pkgPath))
|
|
26857
27557
|
return [];
|
|
26858
27558
|
try {
|
|
@@ -26878,9 +27578,9 @@ var documentationFiles = (cwd) => {
|
|
|
26878
27578
|
if (entry.isDirectory()) {
|
|
26879
27579
|
if (excluded.has(entry.name))
|
|
26880
27580
|
continue;
|
|
26881
|
-
walk(
|
|
27581
|
+
walk(path13.join(dir, entry.name), depth + 1);
|
|
26882
27582
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26883
|
-
matches.push("./" +
|
|
27583
|
+
matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26884
27584
|
}
|
|
26885
27585
|
}
|
|
26886
27586
|
};
|
|
@@ -26896,7 +27596,7 @@ var readTrimmed = (file, maxLines) => {
|
|
|
26896
27596
|
`);
|
|
26897
27597
|
};
|
|
26898
27598
|
function prReadyContext(root, range) {
|
|
26899
|
-
const cwd =
|
|
27599
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
26900
27600
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26901
27601
|
return invalidRange(cwd);
|
|
26902
27602
|
let stdout = "";
|
|
@@ -26954,7 +27654,7 @@ ${status.stderr}` : "");
|
|
|
26954
27654
|
stdout += `template_path: ${template}
|
|
26955
27655
|
|
|
26956
27656
|
`;
|
|
26957
|
-
stdout += readTrimmed(
|
|
27657
|
+
stdout += readTrimmed(path13.join(cwd, template), 220);
|
|
26958
27658
|
} else {
|
|
26959
27659
|
stdout += `template_path: none
|
|
26960
27660
|
|
|
@@ -26964,7 +27664,7 @@ ${status.stderr}` : "");
|
|
|
26964
27664
|
stdout += `
|
|
26965
27665
|
`;
|
|
26966
27666
|
stdout += printSection("Recent Validation Signals");
|
|
26967
|
-
if (existsSync6(
|
|
27667
|
+
if (existsSync6(path13.join(cwd, "package.json"))) {
|
|
26968
27668
|
stdout += `package.json detected. Common scripts:
|
|
26969
27669
|
`;
|
|
26970
27670
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -26973,7 +27673,7 @@ ${status.stderr}` : "");
|
|
|
26973
27673
|
`) + `
|
|
26974
27674
|
`;
|
|
26975
27675
|
}
|
|
26976
|
-
if (existsSync6(
|
|
27676
|
+
if (existsSync6(path13.join(cwd, "Cargo.toml")) || existsSync6(path13.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
26977
27677
|
stdout += `Rust project detected.
|
|
26978
27678
|
`;
|
|
26979
27679
|
}
|
|
@@ -27006,7 +27706,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
27006
27706
|
- Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
|
|
27007
27707
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
27008
27708
|
function changelogContext(root, range) {
|
|
27009
|
-
const cwd =
|
|
27709
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
27010
27710
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
27011
27711
|
return invalidRange(cwd);
|
|
27012
27712
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -27024,7 +27724,7 @@ function changelogContext(root, range) {
|
|
|
27024
27724
|
stdout += CHANGELOG_RULES + `
|
|
27025
27725
|
`;
|
|
27026
27726
|
stdout += printSection("Existing CHANGELOG.md");
|
|
27027
|
-
const changelogPath =
|
|
27727
|
+
const changelogPath = path13.join(cwd, "CHANGELOG.md");
|
|
27028
27728
|
if (existsSync6(changelogPath)) {
|
|
27029
27729
|
stdout += readTrimmed(changelogPath, 260) + `
|
|
27030
27730
|
`;
|
|
@@ -27044,7 +27744,7 @@ function changelogContext(root, range) {
|
|
|
27044
27744
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
27045
27745
|
}
|
|
27046
27746
|
function docsRefreshContext(root, range) {
|
|
27047
|
-
const cwd =
|
|
27747
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
27048
27748
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
27049
27749
|
return invalidRange(cwd);
|
|
27050
27750
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -27066,7 +27766,7 @@ function docsRefreshContext(root, range) {
|
|
|
27066
27766
|
`) + `
|
|
27067
27767
|
`;
|
|
27068
27768
|
stdout += printSection("README Preview");
|
|
27069
|
-
const readme =
|
|
27769
|
+
const readme = path13.join(cwd, "README.md");
|
|
27070
27770
|
if (existsSync6(readme)) {
|
|
27071
27771
|
stdout += readTrimmed(readme, 220) + `
|
|
27072
27772
|
`;
|
|
@@ -27075,7 +27775,7 @@ function docsRefreshContext(root, range) {
|
|
|
27075
27775
|
`;
|
|
27076
27776
|
}
|
|
27077
27777
|
stdout += printSection("Package Scripts");
|
|
27078
|
-
const pkgPath =
|
|
27778
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
27079
27779
|
if (existsSync6(pkgPath)) {
|
|
27080
27780
|
try {
|
|
27081
27781
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
@@ -27089,7 +27789,7 @@ function docsRefreshContext(root, range) {
|
|
|
27089
27789
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
27090
27790
|
}
|
|
27091
27791
|
function releaseNotesContext(root, rangeOrTag) {
|
|
27092
|
-
const cwd =
|
|
27792
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
27093
27793
|
if (!rangeOrTag) {
|
|
27094
27794
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
27095
27795
|
`, exitCode: 1, cwd };
|
|
@@ -27127,7 +27827,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
27127
27827
|
`;
|
|
27128
27828
|
stdout += printSection("Existing Release Files");
|
|
27129
27829
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
27130
|
-
if (existsSync6(
|
|
27830
|
+
if (existsSync6(path13.join(cwd, rel)))
|
|
27131
27831
|
stdout += rel + `
|
|
27132
27832
|
`;
|
|
27133
27833
|
}
|
|
@@ -27155,19 +27855,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
27155
27855
|
}
|
|
27156
27856
|
|
|
27157
27857
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
27158
|
-
import
|
|
27159
|
-
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
27160
|
-
var trackerRequest = async (url, init) => {
|
|
27161
|
-
try {
|
|
27162
|
-
const res = await fetch(url, { method: init.method, headers: init.headers });
|
|
27163
|
-
const text = await res.text();
|
|
27164
|
-
if (!res.ok)
|
|
27165
|
-
return { status: res.status, stdout: "", stderr: text.slice(0, 200) };
|
|
27166
|
-
return { status: 0, stdout: text, stderr: "" };
|
|
27167
|
-
} catch (err) {
|
|
27168
|
-
return { status: 1, stdout: "", stderr: err instanceof Error ? err.message : "network error" };
|
|
27169
|
-
}
|
|
27170
|
-
};
|
|
27858
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
27171
27859
|
var repoPathFromRemote = (remote) => {
|
|
27172
27860
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
27173
27861
|
if (!rest)
|
|
@@ -27188,7 +27876,11 @@ var defaultCli = (bin, available) => {
|
|
|
27188
27876
|
if (!available)
|
|
27189
27877
|
return null;
|
|
27190
27878
|
return (args, root) => {
|
|
27191
|
-
const out =
|
|
27879
|
+
const out = spawnSync6(bin, args, {
|
|
27880
|
+
cwd: root,
|
|
27881
|
+
encoding: "utf8",
|
|
27882
|
+
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
27883
|
+
});
|
|
27192
27884
|
return Promise.resolve({
|
|
27193
27885
|
status: out.status ?? 1,
|
|
27194
27886
|
stdout: out.stdout ?? "",
|
|
@@ -27196,49 +27888,22 @@ var defaultCli = (bin, available) => {
|
|
|
27196
27888
|
});
|
|
27197
27889
|
};
|
|
27198
27890
|
};
|
|
27199
|
-
var tryCliTriple = async (cli, args, root, id, idKeys, bodyKeys) => {
|
|
27891
|
+
var tryCliTriple = async (cli, name, args, root, id, idKeys, bodyKeys) => {
|
|
27200
27892
|
if (!cli)
|
|
27201
|
-
return
|
|
27893
|
+
return { error: `${name} CLI is unavailable`, kind: "creds" };
|
|
27202
27894
|
const run = cli(args, root);
|
|
27203
27895
|
if (!run)
|
|
27204
|
-
return
|
|
27896
|
+
return { error: `${name} CLI is unavailable`, kind: "creds" };
|
|
27205
27897
|
const out = await run;
|
|
27206
27898
|
if (out.status !== 0)
|
|
27207
|
-
return
|
|
27899
|
+
return { error: `${name} CLI is not authenticated or the issue is unavailable`, kind: "creds" };
|
|
27208
27900
|
try {
|
|
27209
|
-
|
|
27901
|
+
const triple = parseTriple(JSON.parse(out.stdout), id, idKeys, bodyKeys);
|
|
27902
|
+
return triple ? { data: triple } : { error: `unexpected ${name} issue response`, kind: "request" };
|
|
27210
27903
|
} catch {
|
|
27211
|
-
return
|
|
27904
|
+
return { error: `invalid ${name} issue response`, kind: "request" };
|
|
27212
27905
|
}
|
|
27213
27906
|
};
|
|
27214
|
-
var originRemote = (root) => {
|
|
27215
|
-
const out = spawnSync5("git", ["remote", "get-url", "origin"], { cwd: root, encoding: "utf8" });
|
|
27216
|
-
return out.status === 0 ? (out.stdout ?? "").trim() || null : null;
|
|
27217
|
-
};
|
|
27218
|
-
var trackerCreds = (root, provider) => {
|
|
27219
|
-
const cfg = vcsConfig("load", root);
|
|
27220
|
-
if (!cfg || cfg.provider !== provider || typeof cfg.tokenPath !== "string" || !cfg.tokenPath)
|
|
27221
|
-
return { error: `unconfigured ${provider} provider` };
|
|
27222
|
-
let token = "";
|
|
27223
|
-
try {
|
|
27224
|
-
token = fs9.readFileSync(cfg.tokenPath, "utf8").trim();
|
|
27225
|
-
} catch {
|
|
27226
|
-
return { error: `missing ${provider} token` };
|
|
27227
|
-
}
|
|
27228
|
-
if (!token)
|
|
27229
|
-
return { error: `empty ${provider} token file` };
|
|
27230
|
-
if (provider === "gitlab") {
|
|
27231
|
-
const gitlab = cfg.gitlab;
|
|
27232
|
-
const apiUrl = String(gitlab?.apiUrl ?? "https://gitlab.com/api/v4").replace(/\/+$/, "");
|
|
27233
|
-
return { token, api: apiUrl };
|
|
27234
|
-
}
|
|
27235
|
-
const github = cfg.github;
|
|
27236
|
-
const host = String(github?.host ?? "github.com").toLowerCase();
|
|
27237
|
-
return {
|
|
27238
|
-
token,
|
|
27239
|
-
api: host === "github.com" ? "https://api.github.com" : `https://${host}/api/v3`
|
|
27240
|
-
};
|
|
27241
|
-
};
|
|
27242
27907
|
var parseTriple = (parsed, id, idKeys, bodyKeys) => {
|
|
27243
27908
|
const title = parsed.title;
|
|
27244
27909
|
if (typeof title !== "string")
|
|
@@ -27261,62 +27926,36 @@ var fetchGitHubIssueBody = async (ref, root, deps = {}) => {
|
|
|
27261
27926
|
const id = parseGhIssue(ref);
|
|
27262
27927
|
if (!/^\d+$/.test(id))
|
|
27263
27928
|
return { error: `invalid GitHub issue ref "${ref}"`, kind: "input" };
|
|
27264
|
-
const
|
|
27929
|
+
const remote = (deps.remote ?? pushRemote)(root);
|
|
27930
|
+
const repo = repoPathFromRemote(remote ?? "");
|
|
27265
27931
|
if (!repo)
|
|
27266
27932
|
return { error: "no origin remote to resolve the repository", kind: "input" };
|
|
27267
|
-
const
|
|
27268
|
-
|
|
27269
|
-
|
|
27270
|
-
|
|
27271
|
-
|
|
27272
|
-
|
|
27273
|
-
const out = await (deps.request ?? trackerRequest)(`${credentials.api}/repos/${repo}/issues/${id}`, {
|
|
27274
|
-
method: "GET",
|
|
27275
|
-
headers: {
|
|
27276
|
-
Authorization: `Bearer ${credentials.token}`,
|
|
27277
|
-
Accept: "application/vnd.github+json"
|
|
27278
|
-
}
|
|
27279
|
-
});
|
|
27280
|
-
if (out.status !== 0)
|
|
27281
|
-
return { error: out.stderr || "GitHub issue fetch failed", kind: "request" };
|
|
27282
|
-
try {
|
|
27283
|
-
const triple = parseTriple(JSON.parse(out.stdout), id, ["number"], ["body"]);
|
|
27284
|
-
if (!triple)
|
|
27285
|
-
return { error: "unexpected GitHub issue shape", kind: "request" };
|
|
27286
|
-
return { data: triple };
|
|
27287
|
-
} catch {
|
|
27288
|
-
return { error: "invalid GitHub issue response", kind: "request" };
|
|
27289
|
-
}
|
|
27933
|
+
const cfg = vcsConfig("load", root);
|
|
27934
|
+
const host = (deps.host ?? (cfg.ok && cfg.provider === "github" ? cfg.github?.host : null) ?? "github.com").toLowerCase();
|
|
27935
|
+
if (!remote || !hostingApiHostMatches(remote, host))
|
|
27936
|
+
return { error: "hosting API host does not match the push destination", kind: "input" };
|
|
27937
|
+
const repoArg = host === "github.com" ? repo : `${host}/${repo}`;
|
|
27938
|
+
return tryCliTriple(deps.cli ?? defaultCli("gh", hostingCliAvailable("github")), "gh", ["issue", "view", id, "--repo", repoArg, "--json", "number,title,body,state"], root, id, ["number"], ["body"]);
|
|
27290
27939
|
};
|
|
27291
27940
|
var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
27292
27941
|
const id = parseGhIssue(ref);
|
|
27293
27942
|
if (!/^\d+$/.test(id))
|
|
27294
27943
|
return { error: `invalid GitLab issue ref "${ref}"`, kind: "input" };
|
|
27295
|
-
const
|
|
27944
|
+
const remote = (deps.remote ?? pushRemote)(root);
|
|
27945
|
+
const repo = repoPathFromRemote(remote ?? "");
|
|
27296
27946
|
if (!repo)
|
|
27297
27947
|
return { error: "no origin remote to resolve the project", kind: "input" };
|
|
27298
|
-
const
|
|
27299
|
-
|
|
27300
|
-
|
|
27301
|
-
|
|
27302
|
-
|
|
27303
|
-
|
|
27304
|
-
const out = await (deps.request ?? trackerRequest)(`${credentials.api}/projects/${encodeURIComponent(repo)}/issues/${id}`, { method: "GET", headers: { "PRIVATE-TOKEN": credentials.token } });
|
|
27305
|
-
if (out.status !== 0)
|
|
27306
|
-
return { error: out.stderr || "GitLab issue fetch failed", kind: "request" };
|
|
27307
|
-
try {
|
|
27308
|
-
const triple = parseTriple(JSON.parse(out.stdout), id, ["iid"], ["description"]);
|
|
27309
|
-
if (!triple)
|
|
27310
|
-
return { error: "unexpected GitLab issue shape", kind: "request" };
|
|
27311
|
-
return { data: triple };
|
|
27312
|
-
} catch {
|
|
27313
|
-
return { error: "invalid GitLab issue response", kind: "request" };
|
|
27314
|
-
}
|
|
27948
|
+
const cfg = vcsConfig("load", root);
|
|
27949
|
+
const host = (deps.host ?? (cfg.ok && cfg.provider === "gitlab" ? cfg.gitlab?.host : null) ?? "gitlab.com").toLowerCase();
|
|
27950
|
+
if (!remote || !hostingApiHostMatches(remote, host))
|
|
27951
|
+
return { error: "hosting API host does not match the push destination", kind: "input" };
|
|
27952
|
+
const repoArg = host === "gitlab.com" ? repo : `https://${host}/${repo}`;
|
|
27953
|
+
return tryCliTriple(deps.cli ?? defaultCli("glab", hostingCliAvailable("gitlab")), "glab", ["issue", "view", id, "-R", repoArg, "-F", "json"], root, id, ["iid"], ["description"]);
|
|
27315
27954
|
};
|
|
27316
27955
|
|
|
27317
27956
|
// packages/workit-core/src/core/youtrack.ts
|
|
27318
|
-
import
|
|
27319
|
-
import
|
|
27957
|
+
import fs9 from "node:fs";
|
|
27958
|
+
import path14 from "node:path";
|
|
27320
27959
|
|
|
27321
27960
|
// packages/workit-core/src/core/templates.ts
|
|
27322
27961
|
var repoRoot2 = assetRoot();
|
|
@@ -27328,23 +27967,23 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
27328
27967
|
|
|
27329
27968
|
// packages/workit-core/src/core/youtrack.ts
|
|
27330
27969
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
27331
|
-
var
|
|
27332
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
27333
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
27970
|
+
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
27971
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(configDir(), "youtrack.json");
|
|
27972
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(resolveConfigDir(), "youtrack.json");
|
|
27334
27973
|
var youTrackTokenModeOk = (p) => {
|
|
27335
27974
|
if (process.platform === "win32")
|
|
27336
27975
|
return true;
|
|
27337
|
-
const mode =
|
|
27976
|
+
const mode = fs9.statSync(p).mode & 511;
|
|
27338
27977
|
return mode === 384;
|
|
27339
27978
|
};
|
|
27340
27979
|
function readYouTrackConfig(required) {
|
|
27341
27980
|
const cfgPath = youTrackConfigPath();
|
|
27342
|
-
if (!
|
|
27981
|
+
if (!fs9.existsSync(cfgPath)) {
|
|
27343
27982
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
27344
27983
|
}
|
|
27345
27984
|
let parsed;
|
|
27346
27985
|
try {
|
|
27347
|
-
parsed = JSON.parse(
|
|
27986
|
+
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
27348
27987
|
} catch {
|
|
27349
27988
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
27350
27989
|
}
|
|
@@ -27355,11 +27994,11 @@ function readYouTrackConfig(required) {
|
|
|
27355
27994
|
}
|
|
27356
27995
|
var readYouTrackContextConfig = () => {
|
|
27357
27996
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
27358
|
-
if (!
|
|
27997
|
+
if (!fs9.existsSync(cfgPath))
|
|
27359
27998
|
return { data: {} };
|
|
27360
27999
|
let parsed;
|
|
27361
28000
|
try {
|
|
27362
|
-
parsed = JSON.parse(
|
|
28001
|
+
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
27363
28002
|
} catch {
|
|
27364
28003
|
return { error: "YouTrack configuration is unavailable" };
|
|
27365
28004
|
}
|
|
@@ -27384,12 +28023,12 @@ function youTrackGreeting(configOverride) {
|
|
|
27384
28023
|
const cfgPath = configOverride ?? youTrackConfigPath();
|
|
27385
28024
|
let parsed;
|
|
27386
28025
|
try {
|
|
27387
|
-
parsed = JSON.parse(
|
|
28026
|
+
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
27388
28027
|
} catch {
|
|
27389
28028
|
return {
|
|
27390
28029
|
stdout: "",
|
|
27391
28030
|
exitCode: 1,
|
|
27392
|
-
stderr:
|
|
28031
|
+
stderr: fs9.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
|
|
27393
28032
|
};
|
|
27394
28033
|
}
|
|
27395
28034
|
if (!isConfigObject(parsed)) {
|
|
@@ -27435,15 +28074,15 @@ var youTrackToken = () => {
|
|
|
27435
28074
|
if ("error" in loaded)
|
|
27436
28075
|
return loaded;
|
|
27437
28076
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
27438
|
-
const tokenPath = tokenFile ?
|
|
27439
|
-
if (!tokenPath || !
|
|
28077
|
+
const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
|
|
28078
|
+
if (!tokenPath || !fs9.existsSync(tokenPath))
|
|
27440
28079
|
return { error: "missing youtrack.token" };
|
|
27441
28080
|
if (!youTrackTokenModeOk(tokenPath))
|
|
27442
28081
|
return { error: "youtrack.token mode must be 0600" };
|
|
27443
|
-
const token =
|
|
28082
|
+
const token = fs9.readFileSync(tokenPath, "utf8").trim();
|
|
27444
28083
|
if (!token)
|
|
27445
28084
|
return { error: "empty token file" };
|
|
27446
|
-
if (token ===
|
|
28085
|
+
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
27447
28086
|
return {
|
|
27448
28087
|
error: "token file still has placeholder YOUR_TOKEN_HERE — edit the file locally, then run /wk-status"
|
|
27449
28088
|
};
|
|
@@ -27506,9 +28145,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
27506
28145
|
} catch {
|
|
27507
28146
|
continue;
|
|
27508
28147
|
}
|
|
27509
|
-
if (!
|
|
28148
|
+
if (!fs9.existsSync(full))
|
|
27510
28149
|
continue;
|
|
27511
|
-
const text =
|
|
28150
|
+
const text = fs9.readFileSync(full, "utf8");
|
|
27512
28151
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
27513
28152
|
if (m)
|
|
27514
28153
|
return m[1];
|
|
@@ -27742,6 +28381,7 @@ var readExternalContext = async (root, payload) => {
|
|
|
27742
28381
|
}
|
|
27743
28382
|
}
|
|
27744
28383
|
};
|
|
28384
|
+
var targetWriterActionLease = Symbol("target-writer-action-lease");
|
|
27745
28385
|
|
|
27746
28386
|
// packages/workit-mcp/src/server.ts
|
|
27747
28387
|
class McpCapabilityUnavailableError extends Error {
|
|
@@ -27786,13 +28426,13 @@ var rootVariants = (workspaceRoot) => {
|
|
|
27786
28426
|
if (!workspaceRoot)
|
|
27787
28427
|
return [];
|
|
27788
28428
|
const roots = new Set;
|
|
27789
|
-
for (const candidate of [workspaceRoot,
|
|
27790
|
-
if (
|
|
28429
|
+
for (const candidate of [workspaceRoot, path15.resolve(workspaceRoot)]) {
|
|
28430
|
+
if (path15.isAbsolute(candidate) && candidate !== path15.parse(candidate).root)
|
|
27791
28431
|
roots.add(candidate);
|
|
27792
28432
|
}
|
|
27793
28433
|
try {
|
|
27794
|
-
const canonical =
|
|
27795
|
-
if (canonical !==
|
|
28434
|
+
const canonical = realpathSync8(workspaceRoot);
|
|
28435
|
+
if (canonical !== path15.parse(canonical).root)
|
|
27796
28436
|
roots.add(canonical);
|
|
27797
28437
|
} catch {}
|
|
27798
28438
|
return [...roots].sort((left, right) => right.length - left.length);
|
|
@@ -27893,11 +28533,11 @@ var thrownResult = (tool, error, workspaceRoot) => {
|
|
|
27893
28533
|
};
|
|
27894
28534
|
function createMcpServer(host, contextProvider) {
|
|
27895
28535
|
assertMcpHost(host);
|
|
27896
|
-
const here =
|
|
28536
|
+
const here = path15.dirname(fileURLToPath2(import.meta.url));
|
|
27897
28537
|
const sourceMarker = markSourcesLoaded([
|
|
27898
|
-
|
|
27899
|
-
|
|
27900
|
-
|
|
28538
|
+
path15.join(here, "server.ts"),
|
|
28539
|
+
path15.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
|
|
28540
|
+
path15.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
|
|
27901
28541
|
].filter((file) => existsSync7(file)));
|
|
27902
28542
|
let staleWarned = false;
|
|
27903
28543
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
@@ -28178,6 +28818,7 @@ var allowedKeys = {
|
|
|
28178
28818
|
"stop_hook_active"
|
|
28179
28819
|
])
|
|
28180
28820
|
};
|
|
28821
|
+
var historyOfferSessions = new Set;
|
|
28181
28822
|
if (false)
|
|
28182
28823
|
;
|
|
28183
28824
|
|
|
@@ -28190,14 +28831,14 @@ var codexQualification = (host) => host === "codex_cli" ? { surface: host, cli:
|
|
|
28190
28831
|
var resolveCodexWorkspaceRoot = (pluginRoot, env) => {
|
|
28191
28832
|
const explicit = env.WORKFLOW_WORKSPACE_ROOT;
|
|
28192
28833
|
const inherited = env.PWD;
|
|
28193
|
-
const candidate = explicit ?? (inherited &&
|
|
28194
|
-
if (!candidate || !
|
|
28834
|
+
const candidate = explicit ?? (inherited && path16.resolve(inherited) !== path16.resolve(pluginRoot) ? inherited : undefined);
|
|
28835
|
+
if (!candidate || !path16.isAbsolute(candidate) || !existsSync8(candidate))
|
|
28195
28836
|
return null;
|
|
28196
28837
|
try {
|
|
28197
|
-
if (!
|
|
28838
|
+
if (!statSync3(candidate).isDirectory())
|
|
28198
28839
|
return null;
|
|
28199
|
-
const canonical =
|
|
28200
|
-
if (canonical ===
|
|
28840
|
+
const canonical = realpathSync9(candidate);
|
|
28841
|
+
if (canonical === realpathSync9(pluginRoot)) {
|
|
28201
28842
|
if (!explicit)
|
|
28202
28843
|
return null;
|
|
28203
28844
|
const workspace = new TaskStore(canonical).readWorkspace();
|