@brainervirus/workit-mcp 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +893 -289
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -6620,11 +6620,11 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
6620
6620
|
|
|
6621
6621
|
// packages/workit-mcp/src/index.ts
|
|
6622
6622
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
6623
|
-
import
|
|
6623
|
+
import path16 from "node:path";
|
|
6624
6624
|
|
|
6625
6625
|
// packages/workit-mcp/src/server.ts
|
|
6626
|
-
import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as
|
|
6627
|
-
import
|
|
6626
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as realpathSync8 } from "node:fs";
|
|
6627
|
+
import path15 from "node:path";
|
|
6628
6628
|
|
|
6629
6629
|
// node_modules/@modelcontextprotocol/sdk/node_modules/zod/v4/core/core.js
|
|
6630
6630
|
var NEVER = Object.freeze({
|
|
@@ -12477,8 +12477,8 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
12477
12477
|
|
|
12478
12478
|
// packages/workit-core/src/core.ts
|
|
12479
12479
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
12480
|
-
import { existsSync as existsSync5, realpathSync as
|
|
12481
|
-
import
|
|
12480
|
+
import { existsSync as existsSync5, realpathSync as realpathSync7 } from "node:fs";
|
|
12481
|
+
import path11 from "node:path";
|
|
12482
12482
|
|
|
12483
12483
|
// packages/workit-core/src/core/task-contract.ts
|
|
12484
12484
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -17785,6 +17785,17 @@ function _array2(Class, element, params) {
|
|
|
17785
17785
|
...normalizeParams2(params)
|
|
17786
17786
|
});
|
|
17787
17787
|
}
|
|
17788
|
+
function _custom2(Class, fn, _params) {
|
|
17789
|
+
const norm = normalizeParams2(_params);
|
|
17790
|
+
norm.abort ?? (norm.abort = true);
|
|
17791
|
+
const schema = new Class({
|
|
17792
|
+
type: "custom",
|
|
17793
|
+
check: "custom",
|
|
17794
|
+
fn,
|
|
17795
|
+
...norm
|
|
17796
|
+
});
|
|
17797
|
+
return schema;
|
|
17798
|
+
}
|
|
17788
17799
|
function _refine2(Class, fn, _params) {
|
|
17789
17800
|
const schema = new Class({
|
|
17790
17801
|
type: "custom",
|
|
@@ -19938,6 +19949,9 @@ var ZodCustom2 = /* @__PURE__ */ $constructor2("ZodCustom", (inst, def) => {
|
|
|
19938
19949
|
ZodType2.init(inst, def);
|
|
19939
19950
|
inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);
|
|
19940
19951
|
});
|
|
19952
|
+
function custom2(fn, _params) {
|
|
19953
|
+
return _custom2(ZodCustom2, fn ?? (() => true), _params);
|
|
19954
|
+
}
|
|
19941
19955
|
function refine2(fn, _params = {}) {
|
|
19942
19956
|
return _refine2(ZodCustom2, fn, _params);
|
|
19943
19957
|
}
|
|
@@ -20371,7 +20385,8 @@ var decisionSchema = object4({
|
|
|
20371
20385
|
consumption: object4({
|
|
20372
20386
|
state: _enum2(["reserved", "consumed", "uncertain"]),
|
|
20373
20387
|
at: utc,
|
|
20374
|
-
actionRef: refSchema
|
|
20388
|
+
actionRef: refSchema,
|
|
20389
|
+
historicalActionId: text.optional()
|
|
20375
20390
|
}).strict().nullable()
|
|
20376
20391
|
}).strict();
|
|
20377
20392
|
var findingSchema = object4({
|
|
@@ -20514,7 +20529,7 @@ var taskSummarySchema = object4({
|
|
|
20514
20529
|
requirements: array3(requirementEvaluationSchema),
|
|
20515
20530
|
writer: workspaceRecordSchema.shape.writer
|
|
20516
20531
|
}).strict();
|
|
20517
|
-
var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true });
|
|
20532
|
+
var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true }).extend({ source: provenanceSchema.pick({ host: true, kind: true }) }).strict();
|
|
20518
20533
|
var exportBundleSchema = object4({
|
|
20519
20534
|
schemaVersion: literal3(1),
|
|
20520
20535
|
exportedAt: utc,
|
|
@@ -20539,6 +20554,7 @@ var taskOperations = {
|
|
|
20539
20554
|
list: operation({
|
|
20540
20555
|
action: literal3("list"),
|
|
20541
20556
|
status: _enum2(["open", "closed", "all"]).optional(),
|
|
20557
|
+
query: string5().trim().min(1).max(200).optional(),
|
|
20542
20558
|
limit: number5().int().min(1).max(50).optional()
|
|
20543
20559
|
}),
|
|
20544
20560
|
inspect: operation({
|
|
@@ -20739,7 +20755,47 @@ function operationJsonSchema(family) {
|
|
|
20739
20755
|
return toJSONSchema(operationSchemas[family], { target: "draft-2020-12" });
|
|
20740
20756
|
}
|
|
20741
20757
|
var OPERATION_SCHEMA_DEPTH = 1;
|
|
20742
|
-
var
|
|
20758
|
+
var schemaShapeDescription = (value, depth = 0) => {
|
|
20759
|
+
if (!value || typeof value !== "object")
|
|
20760
|
+
return "value";
|
|
20761
|
+
const schema = value;
|
|
20762
|
+
if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
|
|
20763
|
+
const options = Array.isArray(schema.oneOf) ? schema.oneOf : schema.anyOf;
|
|
20764
|
+
return `(${options.map((option) => schemaShapeDescription(option, depth)).join("|")})`;
|
|
20765
|
+
}
|
|
20766
|
+
if (schema.const !== undefined)
|
|
20767
|
+
return `=${JSON.stringify(schema.const)}`;
|
|
20768
|
+
if (Array.isArray(schema.enum))
|
|
20769
|
+
return `[${schema.enum.map(String).join("|")}]`;
|
|
20770
|
+
const type = Array.isArray(schema.type) ? schema.type.join("/") : schema.type;
|
|
20771
|
+
if (type === "array")
|
|
20772
|
+
return `arr<${schemaShapeDescription(schema.items, depth)}>`;
|
|
20773
|
+
if (type === "object" && schema.properties && typeof schema.properties === "object") {
|
|
20774
|
+
if (depth >= 2)
|
|
20775
|
+
return "obj";
|
|
20776
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
20777
|
+
const properties = schema.properties;
|
|
20778
|
+
const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child, depth + 1)}`);
|
|
20779
|
+
return `obj{${fields.join(",")}}`;
|
|
20780
|
+
}
|
|
20781
|
+
if (typeof type !== "string")
|
|
20782
|
+
return "?";
|
|
20783
|
+
return type.split("/").map((part) => ({ string: "str", boolean: "bool", number: "num", null: "null" })[part] ?? part).join("|");
|
|
20784
|
+
};
|
|
20785
|
+
var canonicalFieldsDescription = (value) => {
|
|
20786
|
+
if (Array.isArray(value))
|
|
20787
|
+
return `Canonical object fields: ${value.join(", ")}. Workit validates the complete nested value.`;
|
|
20788
|
+
const isZodType = (schema) => ("_zod" in schema) && typeof schema._zod === "object";
|
|
20789
|
+
const schema = isZodType(value) ? toJSONSchema(value, { target: "draft-2020-12" }) : value;
|
|
20790
|
+
if (!schema.properties || typeof schema.properties !== "object") {
|
|
20791
|
+
const variants = Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : null;
|
|
20792
|
+
return variants ? `Canonical shapes: ${variants.map((variant) => schemaShapeDescription(variant)).join(" | ")}. Full nested value is validated.` : "Canonical nested value; Workit validates the complete value.";
|
|
20793
|
+
}
|
|
20794
|
+
const properties = schema.properties;
|
|
20795
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
20796
|
+
const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child)}`);
|
|
20797
|
+
return `Fields (! required): ${fields.join(", ")}. Full nested value is validated.`;
|
|
20798
|
+
};
|
|
20743
20799
|
function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, stringifiedObjects = false) {
|
|
20744
20800
|
const tolerate = (projected) => {
|
|
20745
20801
|
if (!stringifiedObjects)
|
|
@@ -20763,9 +20819,10 @@ function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, s
|
|
|
20763
20819
|
return node;
|
|
20764
20820
|
const record = node;
|
|
20765
20821
|
if (record.properties && typeof record.properties === "object") {
|
|
20766
|
-
const
|
|
20822
|
+
const properties = record.properties;
|
|
20823
|
+
const fields = Object.keys(properties);
|
|
20767
20824
|
if (currentDepth >= maxDepth || fields.length === 0) {
|
|
20768
|
-
const description = stringifiedObjects ? `${canonicalFieldsDescription(
|
|
20825
|
+
const description = stringifiedObjects ? `${canonicalFieldsDescription(record)} A JSON-encoded string is also accepted.` : canonicalFieldsDescription(record);
|
|
20769
20826
|
return tolerate({ type: "object", description });
|
|
20770
20827
|
}
|
|
20771
20828
|
return tolerate({
|
|
@@ -20886,6 +20943,7 @@ function requirementId(input) {
|
|
|
20886
20943
|
}
|
|
20887
20944
|
// packages/workit-core/src/core/task-store.ts
|
|
20888
20945
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
20946
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
20889
20947
|
import * as fs4 from "node:fs";
|
|
20890
20948
|
import { hostname as hostname2 } from "node:os";
|
|
20891
20949
|
import path4 from "node:path";
|
|
@@ -21521,7 +21579,8 @@ var metadataLockSchema = object4({
|
|
|
21521
21579
|
pid: number5().int().nonnegative().safe(),
|
|
21522
21580
|
processStart: string5().nullable(),
|
|
21523
21581
|
host: string5().min(1),
|
|
21524
|
-
nonce: string5().min(1)
|
|
21582
|
+
nonce: string5().min(1),
|
|
21583
|
+
externalAction: literal3(true).optional()
|
|
21525
21584
|
}).strict();
|
|
21526
21585
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
21527
21586
|
var cachedRuntimeVersion = null;
|
|
@@ -21573,6 +21632,28 @@ var sameMetadataLock = (left, right) => {
|
|
|
21573
21632
|
const parsed = metadataLockSchema.safeParse(left);
|
|
21574
21633
|
return parsed.success && canonicalJson(parsed.data) === canonicalJson(right);
|
|
21575
21634
|
};
|
|
21635
|
+
var sameDirectoryIdentity = (left, right) => {
|
|
21636
|
+
if (!path4.isAbsolute(left) || !path4.isAbsolute(right))
|
|
21637
|
+
return false;
|
|
21638
|
+
const normalizedLeft = path4.resolve(left);
|
|
21639
|
+
const normalizedRight = path4.resolve(right);
|
|
21640
|
+
try {
|
|
21641
|
+
const a = fs4.statSync(normalizedLeft, { bigint: true });
|
|
21642
|
+
const b = fs4.statSync(normalizedRight, { bigint: true });
|
|
21643
|
+
if (!a.isDirectory() || !b.isDirectory())
|
|
21644
|
+
return false;
|
|
21645
|
+
if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
|
|
21646
|
+
return true;
|
|
21647
|
+
const canonicalLeft = fs4.realpathSync(normalizedLeft);
|
|
21648
|
+
const canonicalRight = fs4.realpathSync(normalizedRight);
|
|
21649
|
+
if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
|
|
21650
|
+
return true;
|
|
21651
|
+
return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
|
|
21652
|
+
} catch {
|
|
21653
|
+
return false;
|
|
21654
|
+
}
|
|
21655
|
+
};
|
|
21656
|
+
var externalActionLockRoots = new AsyncLocalStorage;
|
|
21576
21657
|
|
|
21577
21658
|
class TaskStore {
|
|
21578
21659
|
root;
|
|
@@ -21633,7 +21714,7 @@ class TaskStore {
|
|
|
21633
21714
|
const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
|
|
21634
21715
|
if (!item.exists)
|
|
21635
21716
|
return success(null, null, null);
|
|
21636
|
-
if (item.result.ok && item.result.data.root !== this.root)
|
|
21717
|
+
if (item.result.ok && item.result.data.root !== this.root && !sameDirectoryIdentity(item.result.data.root, this.root))
|
|
21637
21718
|
return failure("recovery_required", "workspace root binding is invalid", {
|
|
21638
21719
|
path: this.workspacePath
|
|
21639
21720
|
});
|
|
@@ -21724,6 +21805,29 @@ class TaskStore {
|
|
|
21724
21805
|
const current = this.readWorkspace();
|
|
21725
21806
|
if (!current.ok)
|
|
21726
21807
|
return current;
|
|
21808
|
+
const origin = input.task.origin;
|
|
21809
|
+
if (current.data && origin) {
|
|
21810
|
+
if (origin.workspaceId === current.data.id) {
|
|
21811
|
+
const source = this.readTask(origin.taskId);
|
|
21812
|
+
if (source.ok)
|
|
21813
|
+
return success(source.data.revision, current.data.revision, source.data);
|
|
21814
|
+
if (source.code !== "not_found")
|
|
21815
|
+
return source;
|
|
21816
|
+
return failure("not_found", "source task is not present in this shared workspace");
|
|
21817
|
+
}
|
|
21818
|
+
const tasks = this.listTasks();
|
|
21819
|
+
if (!tasks.ok)
|
|
21820
|
+
return tasks;
|
|
21821
|
+
const prior = tasks.data.filter((task) => task.origin?.workspaceId === origin.workspaceId && task.origin?.taskId === origin.taskId);
|
|
21822
|
+
if (prior.length > 1)
|
|
21823
|
+
return failure("recovery_required", `source task has multiple imported mappings: ${prior.map((task) => task.id).join(", ")}`);
|
|
21824
|
+
if (prior.length === 1) {
|
|
21825
|
+
const existing = prior[0];
|
|
21826
|
+
if (existing.origin.exportDigest !== origin.exportDigest)
|
|
21827
|
+
return failure("revision_conflict", "source task export changed; reconcile the mapped destination task before importing", { taskId: existing.id });
|
|
21828
|
+
return success(existing.revision, current.data.revision, existing);
|
|
21829
|
+
}
|
|
21830
|
+
}
|
|
21727
21831
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
21728
21832
|
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
21729
21833
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
@@ -22112,6 +22216,73 @@ class TaskStore {
|
|
|
22112
22216
|
})
|
|
22113
22217
|
};
|
|
22114
22218
|
}
|
|
22219
|
+
externalActionLockOptions() {
|
|
22220
|
+
const options = this.metadataLockOptions();
|
|
22221
|
+
return { ...options, payload: () => ({ ...options.payload(), externalAction: true }) };
|
|
22222
|
+
}
|
|
22223
|
+
async withExternalActionLock(operation, reentrant = false) {
|
|
22224
|
+
const activeRoots = externalActionLockRoots.getStore();
|
|
22225
|
+
if (activeRoots && [...activeRoots].some((root) => root === this.root || sameDirectoryIdentity(root, this.root)))
|
|
22226
|
+
return operation();
|
|
22227
|
+
try {
|
|
22228
|
+
this.initializeMutationStorage();
|
|
22229
|
+
} catch (error) {
|
|
22230
|
+
return failure("storage_error", `unable to initialize store: ${String(error)}`, {
|
|
22231
|
+
path: this.workitDir
|
|
22232
|
+
});
|
|
22233
|
+
}
|
|
22234
|
+
let handle;
|
|
22235
|
+
try {
|
|
22236
|
+
handle = acquireFileLockSync(this.workspacePath, this.externalActionLockOptions());
|
|
22237
|
+
} catch (error) {
|
|
22238
|
+
return this.lockFailure(error);
|
|
22239
|
+
}
|
|
22240
|
+
let result;
|
|
22241
|
+
try {
|
|
22242
|
+
if (!handle.verifyStillHeld())
|
|
22243
|
+
result = failure("recovery_required", "metadata lock was compromised", {
|
|
22244
|
+
path: this.lockPath
|
|
22245
|
+
});
|
|
22246
|
+
else {
|
|
22247
|
+
try {
|
|
22248
|
+
const run = () => operation();
|
|
22249
|
+
result = await (reentrant ? externalActionLockRoots.run(new Set([...activeRoots ?? [], this.root]), run) : run());
|
|
22250
|
+
} catch {
|
|
22251
|
+
result = failure("external_outcome_unknown", "external action outcome is unknown", {
|
|
22252
|
+
outcome: "unknown"
|
|
22253
|
+
});
|
|
22254
|
+
}
|
|
22255
|
+
try {
|
|
22256
|
+
if (!handle.verifyStillHeld())
|
|
22257
|
+
result = failure("external_outcome_unknown", "metadata lock was compromised", {
|
|
22258
|
+
outcome: "unknown",
|
|
22259
|
+
path: this.lockPath
|
|
22260
|
+
});
|
|
22261
|
+
} catch {
|
|
22262
|
+
result = failure("external_outcome_unknown", "metadata lock could not be verified", {
|
|
22263
|
+
outcome: "unknown",
|
|
22264
|
+
path: this.lockPath
|
|
22265
|
+
});
|
|
22266
|
+
}
|
|
22267
|
+
}
|
|
22268
|
+
} catch {
|
|
22269
|
+
result = failure("external_outcome_unknown", "external action lock operation failed", {
|
|
22270
|
+
outcome: "unknown",
|
|
22271
|
+
path: this.lockPath
|
|
22272
|
+
});
|
|
22273
|
+
}
|
|
22274
|
+
try {
|
|
22275
|
+
handle.release();
|
|
22276
|
+
} catch (error) {
|
|
22277
|
+
const released = this.lockFailure(error);
|
|
22278
|
+
const releaseError = released.ok ? "metadata lock release failed" : released.error;
|
|
22279
|
+
return result.ok ? failure("external_outcome_unknown", "external action metadata lock release is uncertain", {
|
|
22280
|
+
outcome: "unknown",
|
|
22281
|
+
path: this.lockPath
|
|
22282
|
+
}) : failure("recovery_required", `${result.error}; ${releaseError}`, result.details);
|
|
22283
|
+
}
|
|
22284
|
+
return result;
|
|
22285
|
+
}
|
|
22115
22286
|
recoveryLockOptions(observed, evidence, gate) {
|
|
22116
22287
|
const options = this.metadataLockOptions();
|
|
22117
22288
|
options.staleRecovery = "remove-if-unchanged";
|
|
@@ -22181,6 +22352,14 @@ class TaskStore {
|
|
|
22181
22352
|
lockFailure(error) {
|
|
22182
22353
|
const value = error;
|
|
22183
22354
|
const code = typeof value?.code === "string" ? value.code : "";
|
|
22355
|
+
if (code === "EEXIST" || code === "file_lock_timeout") {
|
|
22356
|
+
const lock = this.readLockSnapshot();
|
|
22357
|
+
if (lock.ok && lock.data?.data.externalAction)
|
|
22358
|
+
return failure("writer_conflict", "workspace is reserved by a managed external action", {
|
|
22359
|
+
outcome: "not_started",
|
|
22360
|
+
path: this.lockPath
|
|
22361
|
+
});
|
|
22362
|
+
}
|
|
22184
22363
|
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));
|
|
22185
22364
|
return failure(recovery ? "recovery_required" : "storage_error", `metadata lock operation failed: ${String(error)}`, { path: this.lockPath });
|
|
22186
22365
|
}
|
|
@@ -22448,21 +22627,23 @@ function assertProductWriteAllowed(input) {
|
|
|
22448
22627
|
}
|
|
22449
22628
|
|
|
22450
22629
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
22451
|
-
import { createHash as
|
|
22630
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
22452
22631
|
import * as fs7 from "node:fs";
|
|
22453
22632
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
22454
|
-
import
|
|
22633
|
+
import path10 from "node:path";
|
|
22455
22634
|
|
|
22456
22635
|
// packages/workit-core/src/core/authority.ts
|
|
22457
|
-
import { createHash as
|
|
22636
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
22458
22637
|
import * as fs6 from "node:fs";
|
|
22459
|
-
import
|
|
22638
|
+
import path9 from "node:path";
|
|
22460
22639
|
|
|
22461
22640
|
// packages/workit-core/src/core/auto-approval.ts
|
|
22462
|
-
import { createHash as
|
|
22463
|
-
import { readFileSync as readFileSync4 } from "node:fs";
|
|
22641
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
22642
|
+
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
22643
|
+
import path8 from "node:path";
|
|
22464
22644
|
|
|
22465
22645
|
// packages/workit-core/src/core/workspaces.ts
|
|
22646
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
22466
22647
|
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
22467
22648
|
import path6 from "node:path";
|
|
22468
22649
|
|
|
@@ -22480,7 +22661,7 @@ import os from "node:os";
|
|
|
22480
22661
|
import path5 from "node:path";
|
|
22481
22662
|
|
|
22482
22663
|
// packages/workit-core/src/core/boundary.ts
|
|
22483
|
-
import { statSync } from "node:fs";
|
|
22664
|
+
import { statSync as statSync2 } from "node:fs";
|
|
22484
22665
|
var EVENT = {
|
|
22485
22666
|
initialization: "initialization",
|
|
22486
22667
|
provenance: "provenance",
|
|
@@ -22508,7 +22689,7 @@ var changedSourcesSinceLoad = (marker) => {
|
|
|
22508
22689
|
const changed = [];
|
|
22509
22690
|
for (const file of marker.files) {
|
|
22510
22691
|
try {
|
|
22511
|
-
if (
|
|
22692
|
+
if (statSync2(file).mtimeMs > marker.loadedAt)
|
|
22512
22693
|
changed.push(file);
|
|
22513
22694
|
} catch {}
|
|
22514
22695
|
}
|
|
@@ -22667,33 +22848,197 @@ var COMMIT_PRESETS = [
|
|
|
22667
22848
|
"custom",
|
|
22668
22849
|
"auto"
|
|
22669
22850
|
];
|
|
22851
|
+
var resolveCommitPolicy = (config, workspace) => {
|
|
22852
|
+
const wp = workspace?.commitPolicy ?? {};
|
|
22853
|
+
if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
|
|
22854
|
+
return {
|
|
22855
|
+
preset: wp.preset,
|
|
22856
|
+
...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
|
|
22857
|
+
};
|
|
22858
|
+
}
|
|
22859
|
+
return config.commitPolicy ?? { preset: "conventional" };
|
|
22860
|
+
};
|
|
22670
22861
|
|
|
22671
22862
|
// packages/workit-core/src/core/workspaces.ts
|
|
22863
|
+
var nonBlank = string5().refine((value) => value.trim().length > 0, "must not be blank");
|
|
22864
|
+
var branchPreset = custom2((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
|
|
22865
|
+
var commitPreset = custom2((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
|
|
22866
|
+
var workspaceBranchPolicySchema = object4({
|
|
22867
|
+
preset: branchPreset,
|
|
22868
|
+
developBranch: nonBlank.optional(),
|
|
22869
|
+
prefixes: object4({
|
|
22870
|
+
feature: nonBlank,
|
|
22871
|
+
bugfix: nonBlank,
|
|
22872
|
+
release: nonBlank,
|
|
22873
|
+
hotfix: nonBlank
|
|
22874
|
+
}).passthrough().optional(),
|
|
22875
|
+
allowed: array3(nonBlank).optional(),
|
|
22876
|
+
protected: array3(nonBlank).optional(),
|
|
22877
|
+
integration: _enum2(["pr", "merge"]).optional()
|
|
22878
|
+
}).strict();
|
|
22879
|
+
var workspaceCommitPolicySchema = object4({ preset: commitPreset, pattern: string5().optional() }).strict();
|
|
22880
|
+
var workspaceProfileSchema = object4({
|
|
22881
|
+
branchPolicy: workspaceBranchPolicySchema.optional(),
|
|
22882
|
+
commitPolicy: workspaceCommitPolicySchema.optional()
|
|
22883
|
+
}).strict().refine((profile) => profile.branchPolicy !== undefined || profile.commitPolicy !== undefined, {
|
|
22884
|
+
message: "a profile must define branchPolicy or commitPolicy"
|
|
22885
|
+
});
|
|
22886
|
+
var versionSourceSchema = discriminatedUnion2("kind", [
|
|
22887
|
+
object4({
|
|
22888
|
+
kind: literal3("package-json"),
|
|
22889
|
+
path: nonBlank,
|
|
22890
|
+
field: nonBlank
|
|
22891
|
+
}).strict(),
|
|
22892
|
+
object4({ kind: literal3("git-tag") }).strict(),
|
|
22893
|
+
object4({ kind: literal3("manual") }).strict()
|
|
22894
|
+
]);
|
|
22895
|
+
var releaseTrackSchema = object4({
|
|
22896
|
+
strategy: branchPreset,
|
|
22897
|
+
productionBranch: nonBlank,
|
|
22898
|
+
integrationBranch: nonBlank,
|
|
22899
|
+
naming: object4({ feature: nonBlank, release: nonBlank, hotfix: nonBlank }).strict(),
|
|
22900
|
+
baseBranch: nonBlank,
|
|
22901
|
+
mergeBackBranches: array3(nonBlank),
|
|
22902
|
+
pullRequestTarget: nonBlank,
|
|
22903
|
+
tagNamespace: string5(),
|
|
22904
|
+
versionSource: versionSourceSchema,
|
|
22905
|
+
requiredChecks: array3(nonBlank)
|
|
22906
|
+
}).strict().superRefine((track, context) => {
|
|
22907
|
+
for (const [field, values] of [
|
|
22908
|
+
["mergeBackBranches", track.mergeBackBranches],
|
|
22909
|
+
["requiredChecks", track.requiredChecks]
|
|
22910
|
+
]) {
|
|
22911
|
+
if (new Set(values).size !== values.length) {
|
|
22912
|
+
context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
|
|
22913
|
+
}
|
|
22914
|
+
}
|
|
22915
|
+
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(".."))) {
|
|
22916
|
+
context.addIssue({
|
|
22917
|
+
code: "custom",
|
|
22918
|
+
path: ["versionSource", "path"],
|
|
22919
|
+
message: "must stay inside the repository"
|
|
22920
|
+
});
|
|
22921
|
+
}
|
|
22922
|
+
});
|
|
22923
|
+
var workspaceConfigSchema = object4({
|
|
22924
|
+
name: nonBlank,
|
|
22925
|
+
glob: nonBlank,
|
|
22926
|
+
vcs: object4({
|
|
22927
|
+
provider: _enum2(["gitlab", "github"]),
|
|
22928
|
+
defaultTargetBranch: nonBlank.optional(),
|
|
22929
|
+
tokenFile: nonBlank.optional(),
|
|
22930
|
+
account: nonBlank.optional()
|
|
22931
|
+
}).passthrough().optional(),
|
|
22932
|
+
autoApprove: union3([boolean5(), array3(nonBlank)]).optional(),
|
|
22933
|
+
youtrack: object4({ baseUrl: nonBlank.optional(), link_issues: boolean5().optional() }).passthrough().optional(),
|
|
22934
|
+
issues: object4({ provider: literal3("github").optional(), link_on_pr: boolean5().optional() }).passthrough().optional(),
|
|
22935
|
+
branchPolicy: workspaceBranchPolicySchema.optional(),
|
|
22936
|
+
commitPolicy: workspaceCommitPolicySchema.optional(),
|
|
22937
|
+
defaultProfile: nonBlank.optional(),
|
|
22938
|
+
profiles: record3(nonBlank, workspaceProfileSchema).optional(),
|
|
22939
|
+
releaseTracks: record3(nonBlank, releaseTrackSchema).optional()
|
|
22940
|
+
}).passthrough().superRefine((workspace, context) => {
|
|
22941
|
+
if (workspace.youtrack && workspace.vcs?.provider !== "gitlab") {
|
|
22942
|
+
context.addIssue({
|
|
22943
|
+
code: "custom",
|
|
22944
|
+
path: ["youtrack"],
|
|
22945
|
+
message: `YouTrack issue linking requires the gitlab provider, got ${workspace.vcs?.provider ?? "unset"}`
|
|
22946
|
+
});
|
|
22947
|
+
}
|
|
22948
|
+
if (workspace.issues && workspace.vcs?.provider !== "github") {
|
|
22949
|
+
context.addIssue({
|
|
22950
|
+
code: "custom",
|
|
22951
|
+
path: ["issues"],
|
|
22952
|
+
message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
|
|
22953
|
+
});
|
|
22954
|
+
}
|
|
22955
|
+
if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
|
|
22956
|
+
context.addIssue({
|
|
22957
|
+
code: "custom",
|
|
22958
|
+
path: ["defaultProfile"],
|
|
22959
|
+
message: `does not name a configured profile`
|
|
22960
|
+
});
|
|
22961
|
+
}
|
|
22962
|
+
});
|
|
22963
|
+
var workspacesFileSchema = object4({ workspaces: array3(workspaceConfigSchema).optional() }).passthrough().superRefine((file, context) => {
|
|
22964
|
+
const names = new Set;
|
|
22965
|
+
for (const [index, workspace] of (file.workspaces ?? []).entries()) {
|
|
22966
|
+
if (names.has(workspace.name)) {
|
|
22967
|
+
context.addIssue({
|
|
22968
|
+
code: "custom",
|
|
22969
|
+
path: ["workspaces", index, "name"],
|
|
22970
|
+
message: `duplicate workspace name ${JSON.stringify(workspace.name)}`
|
|
22971
|
+
});
|
|
22972
|
+
}
|
|
22973
|
+
names.add(workspace.name);
|
|
22974
|
+
}
|
|
22975
|
+
});
|
|
22976
|
+
var workspacesRevision = (contents) => createHash3("sha256").update(contents ?? "<missing>").digest("hex");
|
|
22977
|
+
var validateWorkspacesDocument = (parsed, file) => {
|
|
22978
|
+
if (!isConfigObject(parsed)) {
|
|
22979
|
+
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
22980
|
+
}
|
|
22981
|
+
const validated = workspacesFileSchema.safeParse(parsed, { reportInput: true });
|
|
22982
|
+
if (!validated.success) {
|
|
22983
|
+
const detail = validated.error.issues.map((issue) => {
|
|
22984
|
+
const field = String(issue.path[issue.path.length - 1] ?? "");
|
|
22985
|
+
const attempted = ["provider", "preset", "strategy"].includes(field) && typeof issue.input === "string" ? ` (${JSON.stringify(issue.input)})` : "";
|
|
22986
|
+
return `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}${attempted}`;
|
|
22987
|
+
}).join("; ");
|
|
22988
|
+
return {
|
|
22989
|
+
status: "invalid",
|
|
22990
|
+
path: file,
|
|
22991
|
+
entries: [],
|
|
22992
|
+
error: `${file} has invalid workspace configuration: ${detail}`
|
|
22993
|
+
};
|
|
22994
|
+
}
|
|
22995
|
+
for (const [index, workspace] of (validated.data.workspaces ?? []).entries()) {
|
|
22996
|
+
const glob = validateWorkspaceGlob(workspace.glob);
|
|
22997
|
+
if (!glob.ok) {
|
|
22998
|
+
return {
|
|
22999
|
+
status: "invalid",
|
|
23000
|
+
path: file,
|
|
23001
|
+
entries: [],
|
|
23002
|
+
error: `${file} workspaces.${index}.glob: ${glob.error}`
|
|
23003
|
+
};
|
|
23004
|
+
}
|
|
23005
|
+
}
|
|
23006
|
+
return {
|
|
23007
|
+
status: "valid",
|
|
23008
|
+
path: file,
|
|
23009
|
+
entries: validated.data.workspaces ?? [],
|
|
23010
|
+
document: parsed
|
|
23011
|
+
};
|
|
23012
|
+
};
|
|
22672
23013
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
22673
23014
|
const file = path6.join(dir, "workspaces.json");
|
|
22674
23015
|
let raw;
|
|
22675
23016
|
try {
|
|
22676
23017
|
raw = readFileSync3(file, "utf8");
|
|
22677
|
-
} catch {
|
|
22678
|
-
|
|
23018
|
+
} catch (error) {
|
|
23019
|
+
if (error.code === "ENOENT")
|
|
23020
|
+
return { status: "missing", path: file, entries: [], revision: workspacesRevision(null) };
|
|
23021
|
+
return {
|
|
23022
|
+
status: "malformed",
|
|
23023
|
+
path: file,
|
|
23024
|
+
entries: [],
|
|
23025
|
+
error: `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
|
|
23026
|
+
};
|
|
22679
23027
|
}
|
|
22680
23028
|
let parsed;
|
|
22681
23029
|
try {
|
|
22682
23030
|
parsed = JSON.parse(raw);
|
|
22683
23031
|
} catch {
|
|
22684
|
-
return {
|
|
22685
|
-
|
|
22686
|
-
|
|
22687
|
-
|
|
23032
|
+
return {
|
|
23033
|
+
status: "malformed",
|
|
23034
|
+
path: file,
|
|
23035
|
+
entries: [],
|
|
23036
|
+
revision: workspacesRevision(raw),
|
|
23037
|
+
error: `${file} is not valid JSON`
|
|
23038
|
+
};
|
|
22688
23039
|
}
|
|
22689
|
-
|
|
22690
|
-
return {
|
|
22691
|
-
status: "valid",
|
|
22692
|
-
path: file,
|
|
22693
|
-
entries: Array.isArray(list) ? list : []
|
|
22694
|
-
};
|
|
23040
|
+
return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
|
|
22695
23041
|
};
|
|
22696
|
-
var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
|
|
22697
23042
|
var globToRegExp = (glob) => {
|
|
22698
23043
|
let out = "";
|
|
22699
23044
|
for (let i = 0;i < glob.length; i++) {
|
|
@@ -22728,6 +23073,30 @@ var globToRegExp = (glob) => {
|
|
|
22728
23073
|
return new RegExp(`^${out}$`);
|
|
22729
23074
|
};
|
|
22730
23075
|
var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
|
|
23076
|
+
var UNSUPPORTED_GLOB = /[?[\]{}]/;
|
|
23077
|
+
var UNSUPPORTED_EXTGLOB = /[@+*]\(|!/;
|
|
23078
|
+
var validateWorkspaceGlob = (glob) => {
|
|
23079
|
+
const trimmed = glob.trim();
|
|
23080
|
+
if (!trimmed)
|
|
23081
|
+
return { ok: false, error: "workspace pattern is required" };
|
|
23082
|
+
const m = UNSUPPORTED_GLOB.exec(trimmed);
|
|
23083
|
+
if (m) {
|
|
23084
|
+
return {
|
|
23085
|
+
ok: false,
|
|
23086
|
+
error: `unsupported glob character ${JSON.stringify(m[0])} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
|
|
23087
|
+
};
|
|
23088
|
+
}
|
|
23089
|
+
const ext = UNSUPPORTED_EXTGLOB.exec(trimmed);
|
|
23090
|
+
if (ext) {
|
|
23091
|
+
const token = ext[0];
|
|
23092
|
+
const kind = token === "!" ? "negation (!)" : `extglob (${token})`;
|
|
23093
|
+
return {
|
|
23094
|
+
ok: false,
|
|
23095
|
+
error: `unsupported glob ${kind} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
|
|
23096
|
+
};
|
|
23097
|
+
}
|
|
23098
|
+
return { ok: true };
|
|
23099
|
+
};
|
|
22731
23100
|
var realpathOf = (p) => {
|
|
22732
23101
|
const real = realpathSync2.native ?? realpathSync2;
|
|
22733
23102
|
try {
|
|
@@ -22745,29 +23114,132 @@ var canonicalGlob = (glob) => {
|
|
|
22745
23114
|
const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
|
|
22746
23115
|
return real + (prefix.endsWith("/") ? "/" : "") + rest;
|
|
22747
23116
|
};
|
|
22748
|
-
var
|
|
23117
|
+
var globSpecificity = (glob) => glob.replaceAll("\\", "/").split("/").filter((segment) => segment.length > 0 && !/[*?[\]{]/.test(segment)).length;
|
|
23118
|
+
var selectWorkspaceMatch = (matches, cwd, workspaceName) => {
|
|
23119
|
+
if (workspaceName !== undefined) {
|
|
23120
|
+
const named = matches.filter((entry) => entry.name === workspaceName);
|
|
23121
|
+
if (named.length === 1)
|
|
23122
|
+
return named[0];
|
|
23123
|
+
if (named.length > 1)
|
|
23124
|
+
throw new Error(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
|
|
23125
|
+
if (matches.length > 0) {
|
|
23126
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
|
|
23127
|
+
}
|
|
23128
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
|
|
23129
|
+
}
|
|
23130
|
+
if (matches.length < 2)
|
|
23131
|
+
return matches[0];
|
|
23132
|
+
const highestSpecificity = Math.max(...matches.map((entry) => globSpecificity(entry.glob)));
|
|
23133
|
+
const mostSpecific = matches.filter((entry) => globSpecificity(entry.glob) === highestSpecificity);
|
|
23134
|
+
if (mostSpecific.length > 1) {
|
|
23135
|
+
throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${mostSpecific.map((entry) => entry.name).join(", ")}`);
|
|
23136
|
+
}
|
|
23137
|
+
return mostSpecific[0];
|
|
23138
|
+
};
|
|
23139
|
+
var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
|
|
23140
|
+
const result = readWorkspacesResult(dir);
|
|
23141
|
+
if (result.status === "malformed" || result.status === "invalid")
|
|
23142
|
+
throw new Error(result.error);
|
|
22749
23143
|
const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
|
|
22750
|
-
|
|
22751
|
-
|
|
22752
|
-
continue;
|
|
22753
|
-
const ws = entry;
|
|
22754
|
-
if (typeof ws.glob !== "string" || !ws.glob)
|
|
22755
|
-
continue;
|
|
23144
|
+
const matches = [];
|
|
23145
|
+
for (const ws of result.entries) {
|
|
22756
23146
|
const glob = ws.glob.replaceAll("\\", "/");
|
|
22757
23147
|
const canonical = canonicalGlob(glob);
|
|
23148
|
+
let matched = false;
|
|
22758
23149
|
for (const target of targets) {
|
|
22759
|
-
if (matchWorkspace(glob, target))
|
|
22760
|
-
|
|
22761
|
-
|
|
22762
|
-
|
|
23150
|
+
if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
|
|
23151
|
+
matched = true;
|
|
23152
|
+
break;
|
|
23153
|
+
}
|
|
22763
23154
|
}
|
|
23155
|
+
if (matched)
|
|
23156
|
+
matches.push(ws);
|
|
22764
23157
|
}
|
|
22765
|
-
return null;
|
|
23158
|
+
return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
|
|
23159
|
+
};
|
|
23160
|
+
var runtimeWorkspacePolicySchema = (kind) => {
|
|
23161
|
+
const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
|
|
23162
|
+
const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object4({
|
|
23163
|
+
provider: _enum2(["gitlab", "github"]),
|
|
23164
|
+
defaultTargetBranch: nonBlank.optional(),
|
|
23165
|
+
account: nonBlank.optional()
|
|
23166
|
+
}).passthrough();
|
|
23167
|
+
const profileSchema = object4({ [key]: policySchema.optional() }).passthrough();
|
|
23168
|
+
const entrySchema = object4({
|
|
23169
|
+
name: nonBlank,
|
|
23170
|
+
glob: nonBlank,
|
|
23171
|
+
[key]: policySchema.optional(),
|
|
23172
|
+
defaultProfile: nonBlank.optional(),
|
|
23173
|
+
profiles: record3(nonBlank, profileSchema).optional()
|
|
23174
|
+
}).passthrough();
|
|
23175
|
+
return entrySchema;
|
|
23176
|
+
};
|
|
23177
|
+
var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs");
|
|
23178
|
+
var runtimeWorkspaceIndexSchema = object4({
|
|
23179
|
+
workspaces: array3(object4({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
23180
|
+
}).passthrough();
|
|
23181
|
+
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
23182
|
+
const file = path6.join(configDir(), "workspaces.json");
|
|
23183
|
+
let raw;
|
|
23184
|
+
try {
|
|
23185
|
+
raw = readFileSync3(file, "utf8");
|
|
23186
|
+
} catch (error) {
|
|
23187
|
+
if (error.code === "ENOENT")
|
|
23188
|
+
return null;
|
|
23189
|
+
throw new Error(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
23190
|
+
}
|
|
23191
|
+
let parsed;
|
|
23192
|
+
try {
|
|
23193
|
+
parsed = JSON.parse(raw);
|
|
23194
|
+
} catch {
|
|
23195
|
+
throw new Error(`${file} is not valid JSON`);
|
|
23196
|
+
}
|
|
23197
|
+
const validated = runtimeWorkspaceIndexSchema.safeParse(parsed);
|
|
23198
|
+
if (!validated.success) {
|
|
23199
|
+
const detail = validated.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}`).join("; ");
|
|
23200
|
+
throw new Error(`${file} has invalid workspace matching data: ${detail}`);
|
|
23201
|
+
}
|
|
23202
|
+
const entries = validated.data.workspaces;
|
|
23203
|
+
for (const entry of entries ?? []) {
|
|
23204
|
+
const glob = validateWorkspaceGlob(entry.glob);
|
|
23205
|
+
if (!glob.ok)
|
|
23206
|
+
throw new Error(`${file} workspace ${entry.name}: ${glob.error}`);
|
|
23207
|
+
}
|
|
23208
|
+
const targets = [cwd, realpathOf(cwd)].map((value) => value.replaceAll("\\", "/"));
|
|
23209
|
+
const matches = (entries ?? []).filter((entry) => {
|
|
23210
|
+
const glob = entry.glob.replaceAll("\\", "/");
|
|
23211
|
+
const canonical = canonicalGlob(glob);
|
|
23212
|
+
return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
|
|
23213
|
+
});
|
|
23214
|
+
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
23215
|
+
const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
|
|
23216
|
+
if (!selected)
|
|
23217
|
+
return null;
|
|
23218
|
+
const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
|
|
23219
|
+
if (!policy.success) {
|
|
23220
|
+
const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
|
|
23221
|
+
throw new Error(`${file} has invalid ${kind} policy configuration: ${detail}`);
|
|
23222
|
+
}
|
|
23223
|
+
return policy.data;
|
|
23224
|
+
};
|
|
23225
|
+
var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
|
|
23226
|
+
const candidate = resolveRuntimeWorkspaceCandidate(cwd, kind);
|
|
23227
|
+
if (!candidate)
|
|
23228
|
+
return { policy: undefined, source: "user-default" };
|
|
23229
|
+
const key = kind === "branch" ? "branchPolicy" : "commitPolicy";
|
|
23230
|
+
const profileName = requestedProfile?.trim() || candidate.defaultProfile;
|
|
23231
|
+
if (candidate.defaultProfile && !Object.hasOwn(candidate.profiles ?? {}, candidate.defaultProfile))
|
|
23232
|
+
throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
|
|
23233
|
+
const profile = profileName ? candidate.profiles?.[profileName] : undefined;
|
|
23234
|
+
if (profileName && !profile)
|
|
23235
|
+
throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
|
|
23236
|
+
const profilePolicy = profile?.[key];
|
|
23237
|
+
const workspacePolicy = candidate[key];
|
|
23238
|
+
return {
|
|
23239
|
+
policy: profilePolicy ?? workspacePolicy,
|
|
23240
|
+
source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
|
|
23241
|
+
};
|
|
22766
23242
|
};
|
|
22767
|
-
var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
|
|
22768
|
-
|
|
22769
|
-
// packages/workit-core/src/core/branch.ts
|
|
22770
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
22771
23243
|
|
|
22772
23244
|
// packages/workit-core/src/core/git.ts
|
|
22773
23245
|
import { execFileSync } from "node:child_process";
|
|
@@ -22841,7 +23313,6 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
22841
23313
|
import fs5 from "node:fs";
|
|
22842
23314
|
import path7 from "node:path";
|
|
22843
23315
|
import { spawnSync } from "node:child_process";
|
|
22844
|
-
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
22845
23316
|
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
|
|
22846
23317
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
22847
23318
|
var remoteProvider = (cwd) => {
|
|
@@ -22855,6 +23326,79 @@ var remoteProvider = (cwd) => {
|
|
|
22855
23326
|
return "gitlab";
|
|
22856
23327
|
return null;
|
|
22857
23328
|
};
|
|
23329
|
+
var remoteEndpoint = (remote) => {
|
|
23330
|
+
const raw = remote.trim();
|
|
23331
|
+
if (!raw)
|
|
23332
|
+
return null;
|
|
23333
|
+
const scp = /^(?:[^@\s]+@)?([^:]+):(.+)$/u.exec(raw);
|
|
23334
|
+
if (scp && !raw.includes("://"))
|
|
23335
|
+
return {
|
|
23336
|
+
host: scp[1].toLowerCase(),
|
|
23337
|
+
path: scp[2].replace(/^\//u, "").replace(/\.git$/u, ""),
|
|
23338
|
+
protocol: "ssh:",
|
|
23339
|
+
port: ""
|
|
23340
|
+
};
|
|
23341
|
+
try {
|
|
23342
|
+
const url = new URL(raw);
|
|
23343
|
+
return {
|
|
23344
|
+
host: url.hostname.toLowerCase(),
|
|
23345
|
+
path: url.pathname.replace(/^\//u, "").replace(/\.git$/u, "").replace(/\/$/u, ""),
|
|
23346
|
+
protocol: url.protocol,
|
|
23347
|
+
port: url.port
|
|
23348
|
+
};
|
|
23349
|
+
} catch {
|
|
23350
|
+
return null;
|
|
23351
|
+
}
|
|
23352
|
+
};
|
|
23353
|
+
var configuredSshEndpoint = (host) => {
|
|
23354
|
+
if (host.startsWith("-"))
|
|
23355
|
+
return null;
|
|
23356
|
+
const resolved = spawnSync("ssh", ["-G", host], {
|
|
23357
|
+
encoding: "utf8",
|
|
23358
|
+
timeout: 5000,
|
|
23359
|
+
maxBuffer: 1024 * 1024,
|
|
23360
|
+
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
23361
|
+
});
|
|
23362
|
+
if (resolved.status !== 0)
|
|
23363
|
+
return null;
|
|
23364
|
+
const output = resolved.stdout ?? "";
|
|
23365
|
+
const hostname = /^hostname\s+(\S+)/imu.exec(output)?.[1]?.toLowerCase();
|
|
23366
|
+
if (!hostname)
|
|
23367
|
+
return null;
|
|
23368
|
+
return { host: hostname, port: /^port\s+(\d+)/imu.exec(output)?.[1] ?? "22" };
|
|
23369
|
+
};
|
|
23370
|
+
var sshApiHostAliases = {
|
|
23371
|
+
"github.com": [{ host: "ssh.github.com", port: "443" }],
|
|
23372
|
+
"gitlab.com": [{ host: "altssh.gitlab.com", port: "443" }]
|
|
23373
|
+
};
|
|
23374
|
+
var hostingApiHostMatches = (remote, apiHost, resolveSshEndpoint = configuredSshEndpoint) => {
|
|
23375
|
+
const endpoint = remoteEndpoint(remote);
|
|
23376
|
+
if (!endpoint)
|
|
23377
|
+
return false;
|
|
23378
|
+
let expected;
|
|
23379
|
+
try {
|
|
23380
|
+
const url = new URL(`https://${apiHost}`);
|
|
23381
|
+
if (url.pathname !== "/" || url.search || url.hash || url.username || url.password)
|
|
23382
|
+
return false;
|
|
23383
|
+
expected = {
|
|
23384
|
+
host: url.hostname.toLowerCase().replace(/\.$/u, ""),
|
|
23385
|
+
port: url.port
|
|
23386
|
+
};
|
|
23387
|
+
} catch {
|
|
23388
|
+
return false;
|
|
23389
|
+
}
|
|
23390
|
+
if (endpoint.protocol !== "ssh:") {
|
|
23391
|
+
return endpoint.host.replace(/\.$/u, "") === expected.host && endpoint.port === expected.port;
|
|
23392
|
+
}
|
|
23393
|
+
const resolved = resolveSshEndpoint(endpoint.host);
|
|
23394
|
+
if (!resolved)
|
|
23395
|
+
return false;
|
|
23396
|
+
const hostname = resolved.host.toLowerCase().replace(/\.$/u, "");
|
|
23397
|
+
const port = endpoint.port || resolved.port;
|
|
23398
|
+
if (hostname === expected.host)
|
|
23399
|
+
return port === (expected.port || "22");
|
|
23400
|
+
return Boolean(sshApiHostAliases[expected.host]?.some((alias) => alias.host === hostname && alias.port === port && (!expected.port || expected.port === port)));
|
|
23401
|
+
};
|
|
22858
23402
|
var readVcsConfig = () => {
|
|
22859
23403
|
const file = vcsConfigPath();
|
|
22860
23404
|
let raw;
|
|
@@ -22875,7 +23419,7 @@ var readVcsConfig = () => {
|
|
|
22875
23419
|
return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
|
|
22876
23420
|
};
|
|
22877
23421
|
function vcsConfig(mode, cwd) {
|
|
22878
|
-
const ws =
|
|
23422
|
+
const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
|
|
22879
23423
|
const wsVcs = ws?.vcs ?? {};
|
|
22880
23424
|
const wsYt = ws?.youtrack ?? {};
|
|
22881
23425
|
const wsIssues = ws?.issues ?? {};
|
|
@@ -22888,8 +23432,8 @@ function vcsConfig(mode, cwd) {
|
|
|
22888
23432
|
const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
|
|
22889
23433
|
const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
|
|
22890
23434
|
const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
|
|
22891
|
-
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
22892
|
-
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
23435
|
+
const linkIssues = provider === "gitlab" && typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
23436
|
+
const youtrackBaseUrl = provider === "gitlab" && typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
22893
23437
|
let issuesProvider = null;
|
|
22894
23438
|
let linkOnPr = null;
|
|
22895
23439
|
if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
|
|
@@ -22913,9 +23457,8 @@ function vcsConfig(mode, cwd) {
|
|
|
22913
23457
|
if (cfgStatus === "malformed") {
|
|
22914
23458
|
return { ok: false, error: cfgError, configPath: cfgPath };
|
|
22915
23459
|
}
|
|
22916
|
-
if (cfgStatus === "missing")
|
|
23460
|
+
if (cfgStatus === "missing" && provider === null)
|
|
22917
23461
|
return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
|
|
22918
|
-
}
|
|
22919
23462
|
if (provider === null) {
|
|
22920
23463
|
return {
|
|
22921
23464
|
ok: false,
|
|
@@ -22924,24 +23467,12 @@ function vcsConfig(mode, cwd) {
|
|
|
22924
23467
|
};
|
|
22925
23468
|
}
|
|
22926
23469
|
const prov = cfg[provider] ?? {};
|
|
22927
|
-
const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
|
|
22928
|
-
const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path7.join(configDir(), `${provider}.token`));
|
|
22929
|
-
const tokenPath = path7.resolve(tokenFile);
|
|
22930
|
-
let tokenOk = false;
|
|
22931
|
-
if (fs5.existsSync(tokenPath)) {
|
|
22932
|
-
const token = fs5.readFileSync(tokenPath, "utf8").trim();
|
|
22933
|
-
const placeholder = !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
|
|
22934
|
-
tokenOk = !placeholder;
|
|
22935
|
-
}
|
|
22936
23470
|
const out = {
|
|
22937
23471
|
ok: true,
|
|
22938
23472
|
configPath: path7.resolve(cfgPath),
|
|
22939
23473
|
provider,
|
|
22940
23474
|
defaultTargetBranch: defaultTarget,
|
|
22941
23475
|
pr: cfg.pr ?? {},
|
|
22942
|
-
tokenPath,
|
|
22943
|
-
tokenPresent: fs5.existsSync(tokenPath),
|
|
22944
|
-
tokenReady: tokenOk,
|
|
22945
23476
|
workspace_name: ws?.name ?? null,
|
|
22946
23477
|
link_issues: linkIssues,
|
|
22947
23478
|
youtrack_base_url: youtrackBaseUrl,
|
|
@@ -22956,16 +23487,58 @@ function vcsConfig(mode, cwd) {
|
|
|
22956
23487
|
} else if (provider === "github") {
|
|
22957
23488
|
out.github = { host: prov.host ?? "github.com" };
|
|
22958
23489
|
}
|
|
22959
|
-
if (mode === "summary")
|
|
22960
|
-
delete out.tokenPath;
|
|
22961
23490
|
return out;
|
|
22962
23491
|
}
|
|
23492
|
+
function vcsCliIdentity(cwd) {
|
|
23493
|
+
const root = vcsCwd(cwd);
|
|
23494
|
+
const cfg = vcsConfig("load", root);
|
|
23495
|
+
if (!cfg.ok)
|
|
23496
|
+
return { ok: false, error: cfg.error ?? "vcs config not ready" };
|
|
23497
|
+
const provider = cfg.provider;
|
|
23498
|
+
if (provider !== "gitlab" && provider !== "github")
|
|
23499
|
+
return { ok: false, error: `unsupported provider: ${provider}` };
|
|
23500
|
+
const bin = provider === "gitlab" ? "glab" : "gh";
|
|
23501
|
+
const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
|
|
23502
|
+
const env = {
|
|
23503
|
+
...process.env,
|
|
23504
|
+
PATH: process.env.PATH ?? "",
|
|
23505
|
+
...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
|
|
23506
|
+
};
|
|
23507
|
+
const result = spawnSync(bin, ["api", "user"], {
|
|
23508
|
+
cwd: root,
|
|
23509
|
+
encoding: "utf8",
|
|
23510
|
+
env
|
|
23511
|
+
});
|
|
23512
|
+
if (result.status !== 0)
|
|
23513
|
+
return { ok: false, provider, error: `${bin} is missing or not authenticated` };
|
|
23514
|
+
try {
|
|
23515
|
+
const user = JSON.parse(result.stdout ?? "");
|
|
23516
|
+
const username = provider === "gitlab" ? user.username : user.login;
|
|
23517
|
+
if (typeof username !== "string" || !username)
|
|
23518
|
+
return { ok: false, provider, error: `invalid ${bin} identity response` };
|
|
23519
|
+
const expected = resolveRuntimeWorkspaceVcs(root)?.vcs?.account;
|
|
23520
|
+
if (expected && username.toLowerCase() !== expected.toLowerCase())
|
|
23521
|
+
return {
|
|
23522
|
+
ok: false,
|
|
23523
|
+
provider,
|
|
23524
|
+
error: `${bin} account ${username} does not match ${expected}`
|
|
23525
|
+
};
|
|
23526
|
+
return { ok: true, provider, host, username, name: user.name };
|
|
23527
|
+
} catch {
|
|
23528
|
+
return { ok: false, provider, error: `invalid JSON from ${bin} api user` };
|
|
23529
|
+
}
|
|
23530
|
+
}
|
|
22963
23531
|
function mergedPrStyle(limit = 6, cwd) {
|
|
22964
23532
|
const cfg = vcsConfig("load", cwd);
|
|
22965
|
-
if (!cfg.ok
|
|
23533
|
+
if (!cfg.ok)
|
|
22966
23534
|
return { ok: false, error: "vcs not configured" };
|
|
22967
23535
|
const provider = cfg.provider;
|
|
22968
|
-
const
|
|
23536
|
+
const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
|
|
23537
|
+
const env = {
|
|
23538
|
+
...process.env,
|
|
23539
|
+
PATH: process.env.PATH ?? "",
|
|
23540
|
+
...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
|
|
23541
|
+
};
|
|
22969
23542
|
const examples = [];
|
|
22970
23543
|
const descInfo = (desc, caseInsensitiveNotes = false) => ({
|
|
22971
23544
|
hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
|
|
@@ -22973,23 +23546,30 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
22973
23546
|
`).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
|
|
22974
23547
|
descriptionPreview: desc.slice(0, 600)
|
|
22975
23548
|
});
|
|
23549
|
+
const remoteResult = spawnSync("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
23550
|
+
cwd,
|
|
23551
|
+
encoding: "utf8"
|
|
23552
|
+
});
|
|
23553
|
+
const remotes = (remoteResult.stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
|
|
23554
|
+
if (remotes.length === 0)
|
|
23555
|
+
return { ok: false, error: "no origin remote" };
|
|
23556
|
+
if (remotes.length !== 1)
|
|
23557
|
+
return { ok: false, error: "origin has ambiguous push destinations" };
|
|
23558
|
+
const remote = remotes[0];
|
|
23559
|
+
const endpoint = remoteEndpoint(remote);
|
|
23560
|
+
if (!endpoint?.path)
|
|
23561
|
+
return { ok: false, error: "hosting repository could not be resolved" };
|
|
23562
|
+
if (!hostingApiHostMatches(remote, host))
|
|
23563
|
+
return { ok: false, error: "PR style target does not match the provider host" };
|
|
22976
23564
|
if (provider === "gitlab") {
|
|
22977
|
-
const
|
|
22978
|
-
|
|
22979
|
-
|
|
22980
|
-
|
|
22981
|
-
|
|
22982
|
-
if (!m)
|
|
22983
|
-
return { ok: false, error: "not a gitlab.com origin" };
|
|
22984
|
-
const project = m[1];
|
|
22985
|
-
const env = { ...process.env, GITLAB_TOKEN: token };
|
|
22986
|
-
const run = (args) => spawnSync("glab", ["api", ...args], { cwd, encoding: "utf8", env });
|
|
23565
|
+
const run = (args) => spawnSync("glab", ["api", ...args], {
|
|
23566
|
+
cwd,
|
|
23567
|
+
encoding: "utf8",
|
|
23568
|
+
env
|
|
23569
|
+
});
|
|
22987
23570
|
let r = run([
|
|
22988
|
-
`projects/${
|
|
23571
|
+
`projects/${endpoint.path.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
|
|
22989
23572
|
]);
|
|
22990
|
-
if (r.status !== 0) {
|
|
22991
|
-
r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
|
|
22992
|
-
}
|
|
22993
23573
|
if (r.status !== 0)
|
|
22994
23574
|
return { ok: false, error: "could not list merge requests" };
|
|
22995
23575
|
for (const mr of JSON.parse(r.stdout ?? "[]")) {
|
|
@@ -23002,7 +23582,18 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
23002
23582
|
});
|
|
23003
23583
|
}
|
|
23004
23584
|
} else if (provider === "github") {
|
|
23005
|
-
const r = spawnSync("gh", [
|
|
23585
|
+
const r = spawnSync("gh", [
|
|
23586
|
+
"pr",
|
|
23587
|
+
"list",
|
|
23588
|
+
"--repo",
|
|
23589
|
+
`${host}/${endpoint.path}`,
|
|
23590
|
+
"--state",
|
|
23591
|
+
"merged",
|
|
23592
|
+
"--limit",
|
|
23593
|
+
String(limit),
|
|
23594
|
+
"--json",
|
|
23595
|
+
"title,url,body"
|
|
23596
|
+
], { cwd, encoding: "utf8", env });
|
|
23006
23597
|
if (r.status !== 0)
|
|
23007
23598
|
return { ok: false, error: "could not list pull requests" };
|
|
23008
23599
|
for (const pr of JSON.parse(r.stdout ?? "[]")) {
|
|
@@ -23024,26 +23615,28 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
23024
23615
|
}
|
|
23025
23616
|
|
|
23026
23617
|
// packages/workit-core/src/core/branch.ts
|
|
23027
|
-
|
|
23618
|
+
function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
23619
|
+
const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
23620
|
+
const config = readConfig();
|
|
23621
|
+
return kind === "branch" ? {
|
|
23622
|
+
branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
|
|
23623
|
+
provenance: { branchPolicy: selected.source }
|
|
23624
|
+
} : {
|
|
23625
|
+
commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
|
|
23626
|
+
provenance: { commitPolicy: selected.source }
|
|
23627
|
+
};
|
|
23628
|
+
}
|
|
23629
|
+
var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
|
|
23028
23630
|
var verifyPushIdentity = (root, provider, account) => {
|
|
23029
23631
|
if (!account || typeof account !== "string")
|
|
23030
23632
|
return { ok: false, error: "push identity requires a configured area account" };
|
|
23031
|
-
const
|
|
23032
|
-
|
|
23033
|
-
|
|
23034
|
-
|
|
23035
|
-
login = execFileSync2(bin, [...probe], {
|
|
23036
|
-
cwd: root,
|
|
23037
|
-
encoding: "utf8",
|
|
23038
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
23039
|
-
}).trim();
|
|
23040
|
-
} catch {
|
|
23041
|
-
return { ok: false, error: `${bin} identity could not be resolved` };
|
|
23042
|
-
}
|
|
23043
|
-
if (!login || login.toLowerCase() !== account.toLowerCase())
|
|
23633
|
+
const identity = vcsCliIdentity(root);
|
|
23634
|
+
if (!identity.ok || identity.provider !== provider)
|
|
23635
|
+
return { ok: false, error: identity.error ?? "hosting CLI identity could not be resolved" };
|
|
23636
|
+
if (identity.username?.toLowerCase() !== account.toLowerCase())
|
|
23044
23637
|
return {
|
|
23045
23638
|
ok: false,
|
|
23046
|
-
error: `push identity ${
|
|
23639
|
+
error: `push identity ${identity.username || "(unknown)"} does not match area account ${account}`
|
|
23047
23640
|
};
|
|
23048
23641
|
return { ok: true };
|
|
23049
23642
|
};
|
|
@@ -23061,7 +23654,7 @@ var operationAutoClass = (operation) => typeof operation === "string" ? OPERATIO
|
|
|
23061
23654
|
var digestOf = (configDirPath) => {
|
|
23062
23655
|
try {
|
|
23063
23656
|
const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
|
|
23064
|
-
return
|
|
23657
|
+
return createHash4("sha256").update(raw).digest("hex");
|
|
23065
23658
|
} catch {
|
|
23066
23659
|
return null;
|
|
23067
23660
|
}
|
|
@@ -23099,14 +23692,30 @@ var resolveAutoApproval = (root, dir = configDir()) => {
|
|
|
23099
23692
|
};
|
|
23100
23693
|
};
|
|
23101
23694
|
var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
|
|
23102
|
-
var
|
|
23695
|
+
var targetOf = (root, descriptor) => {
|
|
23696
|
+
try {
|
|
23697
|
+
const parsed = typeof descriptor === "string" ? JSON.parse(descriptor) : descriptor;
|
|
23698
|
+
const cwd = parsed?.payload?.cwd;
|
|
23699
|
+
if (cwd === undefined)
|
|
23700
|
+
return root;
|
|
23701
|
+
if (typeof cwd !== "string" || !path8.isAbsolute(cwd))
|
|
23702
|
+
return null;
|
|
23703
|
+
return realpathSync3(cwd);
|
|
23704
|
+
} catch {
|
|
23705
|
+
return null;
|
|
23706
|
+
}
|
|
23707
|
+
};
|
|
23708
|
+
var standingApprovalLive = (root, receipt, cls, dir, descriptor) => {
|
|
23103
23709
|
if (!isStandingReceipt(receipt))
|
|
23104
23710
|
return false;
|
|
23105
23711
|
if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
|
|
23106
23712
|
return false;
|
|
23107
23713
|
if (receipt.class !== cls)
|
|
23108
23714
|
return false;
|
|
23109
|
-
const
|
|
23715
|
+
const target = targetOf(root, descriptor);
|
|
23716
|
+
if (!target)
|
|
23717
|
+
return false;
|
|
23718
|
+
const resolved = resolveAutoApproval(target, dir);
|
|
23110
23719
|
if (resolved.status !== "on")
|
|
23111
23720
|
return false;
|
|
23112
23721
|
if (resolved.workspace !== receipt.workspace)
|
|
@@ -23142,16 +23751,19 @@ var verifyStandingApproval = (root, task, caller, binding) => {
|
|
|
23142
23751
|
const cls = operationAutoClass(operation);
|
|
23143
23752
|
if (!cls || cls !== standing.class)
|
|
23144
23753
|
return failure("permission_denied", "standing approval does not cover this operation");
|
|
23145
|
-
const
|
|
23754
|
+
const target = targetOf(root, binding.approvedContent);
|
|
23755
|
+
if (!target)
|
|
23756
|
+
return failure("permission_denied", "standing action target is invalid");
|
|
23757
|
+
const resolved = resolveAutoApproval(target);
|
|
23146
23758
|
if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
|
|
23147
23759
|
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
23148
23760
|
if (cls === "push") {
|
|
23149
|
-
const workspace = resolveWorkspaceFrom(
|
|
23761
|
+
const workspace = resolveWorkspaceFrom(target, configDir());
|
|
23150
23762
|
const provider = workspace?.vcs?.provider;
|
|
23151
23763
|
const account = resolved.account;
|
|
23152
23764
|
if (!provider || !account)
|
|
23153
23765
|
return failure("permission_denied", "auto-push requires a workspace provider and area account");
|
|
23154
|
-
const identity = verifyPushIdentity(
|
|
23766
|
+
const identity = verifyPushIdentity(target, provider, account);
|
|
23155
23767
|
if (!identity.ok)
|
|
23156
23768
|
return failure("permission_denied", identity.error);
|
|
23157
23769
|
}
|
|
@@ -23160,7 +23772,7 @@ var verifyStandingApproval = (root, task, caller, binding) => {
|
|
|
23160
23772
|
host: caller.host,
|
|
23161
23773
|
session: { kind: "host", host: caller.host, handle: caller.actor },
|
|
23162
23774
|
workerId: null,
|
|
23163
|
-
receipts: [standingReceiptFor(
|
|
23775
|
+
receipts: [standingReceiptFor(target, standing.workspace, cls)]
|
|
23164
23776
|
});
|
|
23165
23777
|
};
|
|
23166
23778
|
|
|
@@ -23311,7 +23923,7 @@ function applicableDecision(task, purpose, binding, checkoutRoot) {
|
|
|
23311
23923
|
return [];
|
|
23312
23924
|
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);
|
|
23313
23925
|
}
|
|
23314
|
-
var digestBytes2 = (bytes) =>
|
|
23926
|
+
var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
23315
23927
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
23316
23928
|
let root;
|
|
23317
23929
|
try {
|
|
@@ -23324,23 +23936,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
23324
23936
|
continue;
|
|
23325
23937
|
if (!reference.digest)
|
|
23326
23938
|
return failure("invalid_input", "document references require a byte digest");
|
|
23327
|
-
const target =
|
|
23328
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
23939
|
+
const target = path9.resolve(root, reference.path);
|
|
23940
|
+
if (target !== root && !target.startsWith(`${root}${path9.sep}`))
|
|
23329
23941
|
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
23330
23942
|
fields: [
|
|
23331
23943
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
23332
23944
|
]
|
|
23333
23945
|
});
|
|
23334
23946
|
try {
|
|
23335
|
-
const relative =
|
|
23947
|
+
const relative = path9.relative(root, target);
|
|
23336
23948
|
let current = root;
|
|
23337
|
-
for (const segment of relative.split(
|
|
23949
|
+
for (const segment of relative.split(path9.sep)) {
|
|
23338
23950
|
if (!segment)
|
|
23339
23951
|
continue;
|
|
23340
|
-
current =
|
|
23952
|
+
current = path9.join(current, segment);
|
|
23341
23953
|
const stat = fs6.lstatSync(current);
|
|
23342
23954
|
const real = fs6.realpathSync(current);
|
|
23343
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
23955
|
+
if (real !== root && !real.startsWith(`${root}${path9.sep}`))
|
|
23344
23956
|
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
23345
23957
|
fields: [
|
|
23346
23958
|
{
|
|
@@ -23418,7 +24030,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
|
|
|
23418
24030
|
operation = null;
|
|
23419
24031
|
}
|
|
23420
24032
|
const cls = operationAutoClass(operation);
|
|
23421
|
-
if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
|
|
24033
|
+
if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls, undefined, decision.binding.approvedContent)))
|
|
23422
24034
|
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
23423
24035
|
}
|
|
23424
24036
|
if (decision.revoked)
|
|
@@ -23720,15 +24332,15 @@ var bindingCovers = scopeCovers;
|
|
|
23720
24332
|
var verifyDecisionContent = verifyContentRefs;
|
|
23721
24333
|
|
|
23722
24334
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
23723
|
-
var digestBytes3 = (value) =>
|
|
23724
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
23725
|
-
var relative = (root, candidate) =>
|
|
24335
|
+
var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
24336
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
|
|
24337
|
+
var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
|
|
23726
24338
|
var canonicalPath = (value) => {
|
|
23727
24339
|
if (typeof value !== "string" || !value)
|
|
23728
24340
|
return null;
|
|
23729
24341
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
23730
24342
|
return null;
|
|
23731
|
-
const normalized =
|
|
24343
|
+
const normalized = path10.posix.normalize(value);
|
|
23732
24344
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
23733
24345
|
return null;
|
|
23734
24346
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -23776,14 +24388,14 @@ var gitPaths = (root) => {
|
|
|
23776
24388
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
23777
24389
|
};
|
|
23778
24390
|
}
|
|
23779
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
24391
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
|
|
23780
24392
|
const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
23781
24393
|
if (stagedDeleted.status !== 0) {
|
|
23782
24394
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
23783
24395
|
if (!/not a git repository/i.test(stderr))
|
|
23784
24396
|
return { paths, uncertain: true, git: true };
|
|
23785
24397
|
} else if (stagedDeleted.stdout) {
|
|
23786
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
24398
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
|
|
23787
24399
|
}
|
|
23788
24400
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
23789
24401
|
};
|
|
@@ -23798,7 +24410,7 @@ var walk = (root, directory, output) => {
|
|
|
23798
24410
|
for (const name of names) {
|
|
23799
24411
|
if (name === ".git" || name === ".workit")
|
|
23800
24412
|
continue;
|
|
23801
|
-
const target =
|
|
24413
|
+
const target = path10.join(directory, name);
|
|
23802
24414
|
let stat;
|
|
23803
24415
|
try {
|
|
23804
24416
|
stat = fs7.lstatSync(target);
|
|
@@ -23816,12 +24428,12 @@ var walk = (root, directory, output) => {
|
|
|
23816
24428
|
var scopeRoots = (root, scope) => {
|
|
23817
24429
|
const roots = [];
|
|
23818
24430
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
23819
|
-
const target =
|
|
24431
|
+
const target = path10.resolve(root, item);
|
|
23820
24432
|
if (!inside(root, target))
|
|
23821
24433
|
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` }] });
|
|
23822
24434
|
let ancestor = target;
|
|
23823
24435
|
while (!fs7.existsSync(ancestor) && ancestor !== root)
|
|
23824
|
-
ancestor =
|
|
24436
|
+
ancestor = path10.dirname(ancestor);
|
|
23825
24437
|
try {
|
|
23826
24438
|
if (!inside(root, fs7.realpathSync(ancestor)))
|
|
23827
24439
|
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` }] });
|
|
@@ -23873,7 +24485,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
23873
24485
|
for (const item of [...names].sort()) {
|
|
23874
24486
|
if (!scopeMatches(item, normalizedScope))
|
|
23875
24487
|
continue;
|
|
23876
|
-
const target =
|
|
24488
|
+
const target = path10.join(checkout, item);
|
|
23877
24489
|
try {
|
|
23878
24490
|
const stat = fs7.lstatSync(target);
|
|
23879
24491
|
if (stat.isSymbolicLink()) {
|
|
@@ -24255,7 +24867,8 @@ var externalActionSchema = discriminatedUnion2("operation", [
|
|
|
24255
24867
|
action: _enum2(["setup", "reapply_stash"]).optional(),
|
|
24256
24868
|
sdd_dir: string5().optional(),
|
|
24257
24869
|
target_branch: string5().optional(),
|
|
24258
|
-
stash: _enum2(["yes", "no"]).optional()
|
|
24870
|
+
stash: _enum2(["yes", "no"]).optional(),
|
|
24871
|
+
cwd: string5().min(1).optional()
|
|
24259
24872
|
}).strict()
|
|
24260
24873
|
}).strict(),
|
|
24261
24874
|
object4({
|
|
@@ -24263,12 +24876,13 @@ var externalActionSchema = discriminatedUnion2("operation", [
|
|
|
24263
24876
|
payload: object4({
|
|
24264
24877
|
message: string5().min(1).optional(),
|
|
24265
24878
|
plan_steps: array3(chainStepSchema).min(1).max(32).optional(),
|
|
24266
|
-
plan_branch: string5().min(1).optional()
|
|
24879
|
+
plan_branch: string5().min(1).optional(),
|
|
24880
|
+
cwd: string5().min(1).optional()
|
|
24267
24881
|
}).strict()
|
|
24268
24882
|
}).strict(),
|
|
24269
24883
|
object4({
|
|
24270
24884
|
operation: literal3("git.push"),
|
|
24271
|
-
payload: object4({ branch: string5().min(1).optional() }).strict()
|
|
24885
|
+
payload: object4({ branch: string5().min(1).optional(), cwd: string5().min(1).optional() }).strict()
|
|
24272
24886
|
}).strict(),
|
|
24273
24887
|
object4({
|
|
24274
24888
|
operation: literal3("hosting.pull_request"),
|
|
@@ -24277,16 +24891,22 @@ var externalActionSchema = discriminatedUnion2("operation", [
|
|
|
24277
24891
|
body: string5().optional(),
|
|
24278
24892
|
draft: boolean5().optional(),
|
|
24279
24893
|
target_branch: string5().optional(),
|
|
24280
|
-
babysit: boolean5().optional()
|
|
24894
|
+
babysit: boolean5().optional(),
|
|
24895
|
+
cwd: string5().min(1).optional()
|
|
24281
24896
|
}).strict()
|
|
24282
24897
|
}).strict(),
|
|
24283
24898
|
object4({
|
|
24284
24899
|
operation: literal3("hosting.merge"),
|
|
24285
24900
|
payload: object4({
|
|
24286
24901
|
target_branch: string5().min(1).optional(),
|
|
24287
|
-
source_branch: string5().min(1).optional()
|
|
24902
|
+
source_branch: string5().min(1).optional(),
|
|
24903
|
+
cwd: string5().min(1).optional()
|
|
24288
24904
|
}).strict()
|
|
24289
24905
|
}).strict(),
|
|
24906
|
+
object4({
|
|
24907
|
+
operation: literal3("hosting.delete_branch"),
|
|
24908
|
+
payload: object4({ branch: string5().min(1), cwd: string5().min(1).optional() }).strict()
|
|
24909
|
+
}).strict(),
|
|
24290
24910
|
object4({
|
|
24291
24911
|
operation: literal3("youtrack.update"),
|
|
24292
24912
|
payload: object4({
|
|
@@ -24342,12 +24962,13 @@ var externalActionSchema = discriminatedUnion2("operation", [
|
|
|
24342
24962
|
issueRef: string5().optional(),
|
|
24343
24963
|
mode: string5().optional(),
|
|
24344
24964
|
specPath: string5().optional(),
|
|
24345
|
-
planPath: string5().optional()
|
|
24965
|
+
planPath: string5().optional(),
|
|
24966
|
+
cwd: string5().min(1).optional()
|
|
24346
24967
|
}).strict()
|
|
24347
24968
|
}).strict()
|
|
24348
24969
|
]);
|
|
24349
24970
|
// packages/workit-core/src/core/task-engine.ts
|
|
24350
|
-
import { realpathSync as
|
|
24971
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
24351
24972
|
|
|
24352
24973
|
// packages/workit-core/src/core/policy-resolver.ts
|
|
24353
24974
|
var resolverInputSchema = object4({
|
|
@@ -24520,6 +25141,7 @@ function resolveRequirements(input) {
|
|
|
24520
25141
|
dependentAction: null,
|
|
24521
25142
|
acceptanceAllowed: false
|
|
24522
25143
|
}));
|
|
25144
|
+
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
24523
25145
|
if (signals.behaviorChange.value === true)
|
|
24524
25146
|
requirements.push(requirement({
|
|
24525
25147
|
ruleId: "behavioral-verification",
|
|
@@ -24531,14 +25153,14 @@ function resolveRequirements(input) {
|
|
|
24531
25153
|
dependentAction: null,
|
|
24532
25154
|
acceptanceAllowed: false
|
|
24533
25155
|
}), requirement({
|
|
24534
|
-
ruleId: "fresh-context-review",
|
|
25156
|
+
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
24535
25157
|
dimension: "review",
|
|
24536
25158
|
scope,
|
|
24537
|
-
reason: "
|
|
24538
|
-
satisfaction: "A separate review context examines the current candidate and records its result.",
|
|
25159
|
+
reason: independentBehaviorReview ? "Consequential behavior or the thorough preference requires fresh-context review." : "Bounded behavior changes use self-review unless consequential risks or trusted rules require more.",
|
|
25160
|
+
satisfaction: independentBehaviorReview ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
24539
25161
|
before: "close",
|
|
24540
25162
|
dependentAction: null,
|
|
24541
|
-
acceptanceAllowed:
|
|
25163
|
+
acceptanceAllowed: !independentBehaviorReview
|
|
24542
25164
|
}));
|
|
24543
25165
|
else if (signals.mechanicalLowRisk.value === true)
|
|
24544
25166
|
requirements.push(requirement({
|
|
@@ -24892,6 +25514,18 @@ var portableTask = (task) => {
|
|
|
24892
25514
|
};
|
|
24893
25515
|
for (const entry of clone.assessments)
|
|
24894
25516
|
demote(entry.data.facts, entry.data.signals, entry.data.consequences);
|
|
25517
|
+
const sourceDecisions = new Map(task.decisions.map((entry) => [entry.id, entry]));
|
|
25518
|
+
for (const entry of clone.decisions) {
|
|
25519
|
+
const consumption = sourceDecisions.get(entry.id)?.data.consumption;
|
|
25520
|
+
if (!consumption)
|
|
25521
|
+
continue;
|
|
25522
|
+
entry.data.consumption = {
|
|
25523
|
+
...consumption,
|
|
25524
|
+
state: consumption.state === "reserved" ? "uncertain" : consumption.state,
|
|
25525
|
+
actionRef: { kind: "record", collection: "decisions", id: entry.id },
|
|
25526
|
+
...consumption.historicalActionId ? { historicalActionId: consumption.historicalActionId } : consumption.actionRef.kind === "host" ? { historicalActionId: consumption.actionRef.handle } : {}
|
|
25527
|
+
};
|
|
25528
|
+
}
|
|
24895
25529
|
clone.candidates = [];
|
|
24896
25530
|
return clone;
|
|
24897
25531
|
};
|
|
@@ -25017,7 +25651,11 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
25017
25651
|
taskId: "",
|
|
25018
25652
|
workspaceId: destinationWorkspaceId
|
|
25019
25653
|
},
|
|
25020
|
-
consumption:
|
|
25654
|
+
consumption: data.consumption ? {
|
|
25655
|
+
...data.consumption,
|
|
25656
|
+
state: data.consumption.state === "reserved" ? "uncertain" : data.consumption.state,
|
|
25657
|
+
actionRef: { kind: "record", collection: "decisions", id: entry.id }
|
|
25658
|
+
} : null
|
|
25021
25659
|
}
|
|
25022
25660
|
};
|
|
25023
25661
|
}),
|
|
@@ -25058,7 +25696,7 @@ class WorkitCore {
|
|
|
25058
25696
|
}
|
|
25059
25697
|
contextRootError() {
|
|
25060
25698
|
try {
|
|
25061
|
-
if (
|
|
25699
|
+
if (realpathSync6(this.context.root) !== this.store.root)
|
|
25062
25700
|
return failure("invalid_input", "operation context root does not match the task store root");
|
|
25063
25701
|
} catch {
|
|
25064
25702
|
return failure("invalid_input", "operation context root cannot be resolved");
|
|
@@ -25217,7 +25855,7 @@ class WorkitCore {
|
|
|
25217
25855
|
return parsed;
|
|
25218
25856
|
const input = parsed.data;
|
|
25219
25857
|
if ((this.context.workerId ?? null) !== null) {
|
|
25220
|
-
if (input.action !== "inspect")
|
|
25858
|
+
if (input.action !== "inspect" && input.action !== "list")
|
|
25221
25859
|
return failure("permission_denied", "helpers cannot control task lifecycle or scope");
|
|
25222
25860
|
}
|
|
25223
25861
|
switch (input.action) {
|
|
@@ -25244,7 +25882,18 @@ class WorkitCore {
|
|
|
25244
25882
|
return listed;
|
|
25245
25883
|
const status = input.status ?? "open";
|
|
25246
25884
|
const limit = input.limit ?? 20;
|
|
25247
|
-
const
|
|
25885
|
+
const query = input.query?.toLowerCase().split(/\s+/);
|
|
25886
|
+
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({
|
|
25887
|
+
id: task.id,
|
|
25888
|
+
workspaceId: task.workspaceId,
|
|
25889
|
+
origin: task.origin,
|
|
25890
|
+
createdAt: task.createdAt,
|
|
25891
|
+
updatedAt: task.updatedAt,
|
|
25892
|
+
source: task.intent.provenance,
|
|
25893
|
+
objective: task.intent.data.objective,
|
|
25894
|
+
progress: task.progress,
|
|
25895
|
+
decisions: task.decisions.map(({ data }) => data)
|
|
25896
|
+
}).toLowerCase().includes(term))).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit);
|
|
25248
25897
|
if (selected.length === 0)
|
|
25249
25898
|
return success(null, null, []);
|
|
25250
25899
|
const workspace = this.store.readWorkspace();
|
|
@@ -25263,7 +25912,11 @@ class WorkitCore {
|
|
|
25263
25912
|
status: task.status,
|
|
25264
25913
|
closure: task.closure,
|
|
25265
25914
|
progress: task.progress,
|
|
25266
|
-
writer: task.status === "closed" ? null : workspace.data.writer
|
|
25915
|
+
writer: task.status === "closed" ? null : workspace.data.writer,
|
|
25916
|
+
source: {
|
|
25917
|
+
host: task.intent.provenance.host,
|
|
25918
|
+
kind: task.intent.provenance.kind
|
|
25919
|
+
}
|
|
25267
25920
|
}));
|
|
25268
25921
|
return success(null, null, tasks);
|
|
25269
25922
|
}
|
|
@@ -26359,7 +27012,7 @@ class WorkitCore {
|
|
|
26359
27012
|
if (view.task.workspaceId !== view.workspace.id)
|
|
26360
27013
|
return failure("invalid_input", "task and workspace bindings are invalid");
|
|
26361
27014
|
try {
|
|
26362
|
-
if (
|
|
27015
|
+
if (realpathSync6(view.workspace.root) !== this.store.root)
|
|
26363
27016
|
return failure("invalid_input", "task view root does not match the task store");
|
|
26364
27017
|
} catch {
|
|
26365
27018
|
return failure("invalid_input", "task view root cannot be resolved");
|
|
@@ -26636,22 +27289,22 @@ function resolveGitRevision(root, value) {
|
|
|
26636
27289
|
}
|
|
26637
27290
|
}
|
|
26638
27291
|
function resolveInside(root, candidate) {
|
|
26639
|
-
const base =
|
|
26640
|
-
const target =
|
|
26641
|
-
if (target !== base && !target.startsWith(base +
|
|
27292
|
+
const base = realpathSync7(root);
|
|
27293
|
+
const target = path11.resolve(base, candidate);
|
|
27294
|
+
if (target !== base && !target.startsWith(base + path11.sep)) {
|
|
26642
27295
|
throw new Error("path must stay inside repository root");
|
|
26643
27296
|
}
|
|
26644
27297
|
let ancestor = target;
|
|
26645
27298
|
while (!existsSync5(ancestor))
|
|
26646
|
-
ancestor =
|
|
26647
|
-
const canonicalAncestor =
|
|
26648
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
27299
|
+
ancestor = path11.dirname(ancestor);
|
|
27300
|
+
const canonicalAncestor = realpathSync7(ancestor);
|
|
27301
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path11.sep)) {
|
|
26649
27302
|
throw new Error("path must stay inside repository root");
|
|
26650
27303
|
}
|
|
26651
27304
|
return target;
|
|
26652
27305
|
}
|
|
26653
27306
|
function run2(root, executable, args, env = {}) {
|
|
26654
|
-
const cwd =
|
|
27307
|
+
const cwd = realpathSync7(root);
|
|
26655
27308
|
const result = spawnSync3(executable, args, {
|
|
26656
27309
|
cwd,
|
|
26657
27310
|
encoding: "utf8",
|
|
@@ -26724,18 +27377,19 @@ var redactSecrets = (value) => applyPatterns(value);
|
|
|
26724
27377
|
|
|
26725
27378
|
// packages/workit-core/src/core/pr-create.ts
|
|
26726
27379
|
import fs8 from "node:fs";
|
|
26727
|
-
import
|
|
27380
|
+
import path12 from "node:path";
|
|
27381
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
26728
27382
|
function parseGhIssue(value) {
|
|
26729
27383
|
const m = /issues\/(\d+)/.exec(value);
|
|
26730
27384
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
26731
27385
|
}
|
|
26732
27386
|
function whichOnPath(tool) {
|
|
26733
27387
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
26734
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
27388
|
+
for (const dir of (process.env.PATH ?? "").split(path12.delimiter)) {
|
|
26735
27389
|
if (!dir)
|
|
26736
27390
|
continue;
|
|
26737
27391
|
for (const name of names) {
|
|
26738
|
-
const candidate =
|
|
27392
|
+
const candidate = path12.join(dir, name);
|
|
26739
27393
|
try {
|
|
26740
27394
|
fs8.accessSync(candidate, fs8.constants.X_OK);
|
|
26741
27395
|
return candidate;
|
|
@@ -26745,11 +27399,21 @@ function whichOnPath(tool) {
|
|
|
26745
27399
|
return null;
|
|
26746
27400
|
}
|
|
26747
27401
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
27402
|
+
var pushRemote = (cwd) => {
|
|
27403
|
+
const result = spawnSync4("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
27404
|
+
cwd,
|
|
27405
|
+
encoding: "utf8"
|
|
27406
|
+
});
|
|
27407
|
+
if (result.status !== 0)
|
|
27408
|
+
return null;
|
|
27409
|
+
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
27410
|
+
return urls.length === 1 ? urls[0] : null;
|
|
27411
|
+
};
|
|
26748
27412
|
|
|
26749
27413
|
// packages/workit-core/src/core/repo-context.ts
|
|
26750
|
-
import { spawnSync as
|
|
27414
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
26751
27415
|
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
|
|
26752
|
-
import
|
|
27416
|
+
import path13 from "node:path";
|
|
26753
27417
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26754
27418
|
try {
|
|
26755
27419
|
gitRevisionParts(value);
|
|
@@ -26776,7 +27440,7 @@ var invalidRange = (cwd) => ({
|
|
|
26776
27440
|
cwd
|
|
26777
27441
|
});
|
|
26778
27442
|
var runGit = (cwd, args) => {
|
|
26779
|
-
const result =
|
|
27443
|
+
const result = spawnSync5("git", args, { cwd, encoding: "utf8" });
|
|
26780
27444
|
return {
|
|
26781
27445
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
26782
27446
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -26883,7 +27547,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26883
27547
|
]) {
|
|
26884
27548
|
const parts = rel.split("/");
|
|
26885
27549
|
const base = parts[parts.length - 1];
|
|
26886
|
-
const dir = parts.length > 1 ?
|
|
27550
|
+
const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26887
27551
|
const actual = probeCaseInsensitive(dir, base);
|
|
26888
27552
|
if (actual)
|
|
26889
27553
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26914,7 +27578,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26914
27578
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26915
27579
|
};
|
|
26916
27580
|
var packageScripts = (cwd, keys) => {
|
|
26917
|
-
const pkgPath =
|
|
27581
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
26918
27582
|
if (!existsSync6(pkgPath))
|
|
26919
27583
|
return [];
|
|
26920
27584
|
try {
|
|
@@ -26940,9 +27604,9 @@ var documentationFiles = (cwd) => {
|
|
|
26940
27604
|
if (entry.isDirectory()) {
|
|
26941
27605
|
if (excluded.has(entry.name))
|
|
26942
27606
|
continue;
|
|
26943
|
-
walk(
|
|
27607
|
+
walk(path13.join(dir, entry.name), depth + 1);
|
|
26944
27608
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26945
|
-
matches.push("./" +
|
|
27609
|
+
matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26946
27610
|
}
|
|
26947
27611
|
}
|
|
26948
27612
|
};
|
|
@@ -26958,7 +27622,7 @@ var readTrimmed = (file, maxLines) => {
|
|
|
26958
27622
|
`);
|
|
26959
27623
|
};
|
|
26960
27624
|
function prReadyContext(root, range) {
|
|
26961
|
-
const cwd =
|
|
27625
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
26962
27626
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26963
27627
|
return invalidRange(cwd);
|
|
26964
27628
|
let stdout = "";
|
|
@@ -27016,7 +27680,7 @@ ${status.stderr}` : "");
|
|
|
27016
27680
|
stdout += `template_path: ${template}
|
|
27017
27681
|
|
|
27018
27682
|
`;
|
|
27019
|
-
stdout += readTrimmed(
|
|
27683
|
+
stdout += readTrimmed(path13.join(cwd, template), 220);
|
|
27020
27684
|
} else {
|
|
27021
27685
|
stdout += `template_path: none
|
|
27022
27686
|
|
|
@@ -27026,7 +27690,7 @@ ${status.stderr}` : "");
|
|
|
27026
27690
|
stdout += `
|
|
27027
27691
|
`;
|
|
27028
27692
|
stdout += printSection("Recent Validation Signals");
|
|
27029
|
-
if (existsSync6(
|
|
27693
|
+
if (existsSync6(path13.join(cwd, "package.json"))) {
|
|
27030
27694
|
stdout += `package.json detected. Common scripts:
|
|
27031
27695
|
`;
|
|
27032
27696
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -27035,7 +27699,7 @@ ${status.stderr}` : "");
|
|
|
27035
27699
|
`) + `
|
|
27036
27700
|
`;
|
|
27037
27701
|
}
|
|
27038
|
-
if (existsSync6(
|
|
27702
|
+
if (existsSync6(path13.join(cwd, "Cargo.toml")) || existsSync6(path13.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
27039
27703
|
stdout += `Rust project detected.
|
|
27040
27704
|
`;
|
|
27041
27705
|
}
|
|
@@ -27068,7 +27732,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
27068
27732
|
- Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
|
|
27069
27733
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
27070
27734
|
function changelogContext(root, range) {
|
|
27071
|
-
const cwd =
|
|
27735
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
27072
27736
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
27073
27737
|
return invalidRange(cwd);
|
|
27074
27738
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -27086,7 +27750,7 @@ function changelogContext(root, range) {
|
|
|
27086
27750
|
stdout += CHANGELOG_RULES + `
|
|
27087
27751
|
`;
|
|
27088
27752
|
stdout += printSection("Existing CHANGELOG.md");
|
|
27089
|
-
const changelogPath =
|
|
27753
|
+
const changelogPath = path13.join(cwd, "CHANGELOG.md");
|
|
27090
27754
|
if (existsSync6(changelogPath)) {
|
|
27091
27755
|
stdout += readTrimmed(changelogPath, 260) + `
|
|
27092
27756
|
`;
|
|
@@ -27106,7 +27770,7 @@ function changelogContext(root, range) {
|
|
|
27106
27770
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
27107
27771
|
}
|
|
27108
27772
|
function docsRefreshContext(root, range) {
|
|
27109
|
-
const cwd =
|
|
27773
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
27110
27774
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
27111
27775
|
return invalidRange(cwd);
|
|
27112
27776
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -27128,7 +27792,7 @@ function docsRefreshContext(root, range) {
|
|
|
27128
27792
|
`) + `
|
|
27129
27793
|
`;
|
|
27130
27794
|
stdout += printSection("README Preview");
|
|
27131
|
-
const readme =
|
|
27795
|
+
const readme = path13.join(cwd, "README.md");
|
|
27132
27796
|
if (existsSync6(readme)) {
|
|
27133
27797
|
stdout += readTrimmed(readme, 220) + `
|
|
27134
27798
|
`;
|
|
@@ -27137,7 +27801,7 @@ function docsRefreshContext(root, range) {
|
|
|
27137
27801
|
`;
|
|
27138
27802
|
}
|
|
27139
27803
|
stdout += printSection("Package Scripts");
|
|
27140
|
-
const pkgPath =
|
|
27804
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
27141
27805
|
if (existsSync6(pkgPath)) {
|
|
27142
27806
|
try {
|
|
27143
27807
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
@@ -27151,7 +27815,7 @@ function docsRefreshContext(root, range) {
|
|
|
27151
27815
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
27152
27816
|
}
|
|
27153
27817
|
function releaseNotesContext(root, rangeOrTag) {
|
|
27154
|
-
const cwd =
|
|
27818
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
27155
27819
|
if (!rangeOrTag) {
|
|
27156
27820
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
27157
27821
|
`, exitCode: 1, cwd };
|
|
@@ -27189,7 +27853,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
27189
27853
|
`;
|
|
27190
27854
|
stdout += printSection("Existing Release Files");
|
|
27191
27855
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
27192
|
-
if (existsSync6(
|
|
27856
|
+
if (existsSync6(path13.join(cwd, rel)))
|
|
27193
27857
|
stdout += rel + `
|
|
27194
27858
|
`;
|
|
27195
27859
|
}
|
|
@@ -27217,19 +27881,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
27217
27881
|
}
|
|
27218
27882
|
|
|
27219
27883
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
27220
|
-
import
|
|
27221
|
-
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
27222
|
-
var trackerRequest = async (url, init) => {
|
|
27223
|
-
try {
|
|
27224
|
-
const res = await fetch(url, { method: init.method, headers: init.headers });
|
|
27225
|
-
const text = await res.text();
|
|
27226
|
-
if (!res.ok)
|
|
27227
|
-
return { status: res.status, stdout: "", stderr: text.slice(0, 200) };
|
|
27228
|
-
return { status: 0, stdout: text, stderr: "" };
|
|
27229
|
-
} catch (err) {
|
|
27230
|
-
return { status: 1, stdout: "", stderr: err instanceof Error ? err.message : "network error" };
|
|
27231
|
-
}
|
|
27232
|
-
};
|
|
27884
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
27233
27885
|
var repoPathFromRemote = (remote) => {
|
|
27234
27886
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
27235
27887
|
if (!rest)
|
|
@@ -27250,7 +27902,11 @@ var defaultCli = (bin, available) => {
|
|
|
27250
27902
|
if (!available)
|
|
27251
27903
|
return null;
|
|
27252
27904
|
return (args, root) => {
|
|
27253
|
-
const out =
|
|
27905
|
+
const out = spawnSync6(bin, args, {
|
|
27906
|
+
cwd: root,
|
|
27907
|
+
encoding: "utf8",
|
|
27908
|
+
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
27909
|
+
});
|
|
27254
27910
|
return Promise.resolve({
|
|
27255
27911
|
status: out.status ?? 1,
|
|
27256
27912
|
stdout: out.stdout ?? "",
|
|
@@ -27258,49 +27914,22 @@ var defaultCli = (bin, available) => {
|
|
|
27258
27914
|
});
|
|
27259
27915
|
};
|
|
27260
27916
|
};
|
|
27261
|
-
var tryCliTriple = async (cli, args, root, id, idKeys, bodyKeys) => {
|
|
27917
|
+
var tryCliTriple = async (cli, name, args, root, id, idKeys, bodyKeys) => {
|
|
27262
27918
|
if (!cli)
|
|
27263
|
-
return
|
|
27919
|
+
return { error: `${name} CLI is unavailable`, kind: "creds" };
|
|
27264
27920
|
const run = cli(args, root);
|
|
27265
27921
|
if (!run)
|
|
27266
|
-
return
|
|
27922
|
+
return { error: `${name} CLI is unavailable`, kind: "creds" };
|
|
27267
27923
|
const out = await run;
|
|
27268
27924
|
if (out.status !== 0)
|
|
27269
|
-
return
|
|
27925
|
+
return { error: `${name} CLI is not authenticated or the issue is unavailable`, kind: "creds" };
|
|
27270
27926
|
try {
|
|
27271
|
-
|
|
27927
|
+
const triple = parseTriple(JSON.parse(out.stdout), id, idKeys, bodyKeys);
|
|
27928
|
+
return triple ? { data: triple } : { error: `unexpected ${name} issue response`, kind: "request" };
|
|
27272
27929
|
} catch {
|
|
27273
|
-
return
|
|
27930
|
+
return { error: `invalid ${name} issue response`, kind: "request" };
|
|
27274
27931
|
}
|
|
27275
27932
|
};
|
|
27276
|
-
var originRemote = (root) => {
|
|
27277
|
-
const out = spawnSync5("git", ["remote", "get-url", "origin"], { cwd: root, encoding: "utf8" });
|
|
27278
|
-
return out.status === 0 ? (out.stdout ?? "").trim() || null : null;
|
|
27279
|
-
};
|
|
27280
|
-
var trackerCreds = (root, provider) => {
|
|
27281
|
-
const cfg = vcsConfig("load", root);
|
|
27282
|
-
if (!cfg || cfg.provider !== provider || typeof cfg.tokenPath !== "string" || !cfg.tokenPath)
|
|
27283
|
-
return { error: `unconfigured ${provider} provider` };
|
|
27284
|
-
let token = "";
|
|
27285
|
-
try {
|
|
27286
|
-
token = fs9.readFileSync(cfg.tokenPath, "utf8").trim();
|
|
27287
|
-
} catch {
|
|
27288
|
-
return { error: `missing ${provider} token` };
|
|
27289
|
-
}
|
|
27290
|
-
if (!token)
|
|
27291
|
-
return { error: `empty ${provider} token file` };
|
|
27292
|
-
if (provider === "gitlab") {
|
|
27293
|
-
const gitlab = cfg.gitlab;
|
|
27294
|
-
const apiUrl = String(gitlab?.apiUrl ?? "https://gitlab.com/api/v4").replace(/\/+$/, "");
|
|
27295
|
-
return { token, api: apiUrl };
|
|
27296
|
-
}
|
|
27297
|
-
const github = cfg.github;
|
|
27298
|
-
const host = String(github?.host ?? "github.com").toLowerCase();
|
|
27299
|
-
return {
|
|
27300
|
-
token,
|
|
27301
|
-
api: host === "github.com" ? "https://api.github.com" : `https://${host}/api/v3`
|
|
27302
|
-
};
|
|
27303
|
-
};
|
|
27304
27933
|
var parseTriple = (parsed, id, idKeys, bodyKeys) => {
|
|
27305
27934
|
const title = parsed.title;
|
|
27306
27935
|
if (typeof title !== "string")
|
|
@@ -27323,62 +27952,36 @@ var fetchGitHubIssueBody = async (ref, root, deps = {}) => {
|
|
|
27323
27952
|
const id = parseGhIssue(ref);
|
|
27324
27953
|
if (!/^\d+$/.test(id))
|
|
27325
27954
|
return { error: `invalid GitHub issue ref "${ref}"`, kind: "input" };
|
|
27326
|
-
const
|
|
27955
|
+
const remote = (deps.remote ?? pushRemote)(root);
|
|
27956
|
+
const repo = repoPathFromRemote(remote ?? "");
|
|
27327
27957
|
if (!repo)
|
|
27328
27958
|
return { error: "no origin remote to resolve the repository", kind: "input" };
|
|
27329
|
-
const
|
|
27330
|
-
|
|
27331
|
-
|
|
27332
|
-
|
|
27333
|
-
|
|
27334
|
-
|
|
27335
|
-
const out = await (deps.request ?? trackerRequest)(`${credentials.api}/repos/${repo}/issues/${id}`, {
|
|
27336
|
-
method: "GET",
|
|
27337
|
-
headers: {
|
|
27338
|
-
Authorization: `Bearer ${credentials.token}`,
|
|
27339
|
-
Accept: "application/vnd.github+json"
|
|
27340
|
-
}
|
|
27341
|
-
});
|
|
27342
|
-
if (out.status !== 0)
|
|
27343
|
-
return { error: out.stderr || "GitHub issue fetch failed", kind: "request" };
|
|
27344
|
-
try {
|
|
27345
|
-
const triple = parseTriple(JSON.parse(out.stdout), id, ["number"], ["body"]);
|
|
27346
|
-
if (!triple)
|
|
27347
|
-
return { error: "unexpected GitHub issue shape", kind: "request" };
|
|
27348
|
-
return { data: triple };
|
|
27349
|
-
} catch {
|
|
27350
|
-
return { error: "invalid GitHub issue response", kind: "request" };
|
|
27351
|
-
}
|
|
27959
|
+
const cfg = vcsConfig("load", root);
|
|
27960
|
+
const host = (deps.host ?? (cfg.ok && cfg.provider === "github" ? cfg.github?.host : null) ?? "github.com").toLowerCase();
|
|
27961
|
+
if (!remote || !hostingApiHostMatches(remote, host))
|
|
27962
|
+
return { error: "hosting API host does not match the push destination", kind: "input" };
|
|
27963
|
+
const repoArg = host === "github.com" ? repo : `${host}/${repo}`;
|
|
27964
|
+
return tryCliTriple(deps.cli ?? defaultCli("gh", hostingCliAvailable("github")), "gh", ["issue", "view", id, "--repo", repoArg, "--json", "number,title,body,state"], root, id, ["number"], ["body"]);
|
|
27352
27965
|
};
|
|
27353
27966
|
var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
27354
27967
|
const id = parseGhIssue(ref);
|
|
27355
27968
|
if (!/^\d+$/.test(id))
|
|
27356
27969
|
return { error: `invalid GitLab issue ref "${ref}"`, kind: "input" };
|
|
27357
|
-
const
|
|
27970
|
+
const remote = (deps.remote ?? pushRemote)(root);
|
|
27971
|
+
const repo = repoPathFromRemote(remote ?? "");
|
|
27358
27972
|
if (!repo)
|
|
27359
27973
|
return { error: "no origin remote to resolve the project", kind: "input" };
|
|
27360
|
-
const
|
|
27361
|
-
|
|
27362
|
-
|
|
27363
|
-
|
|
27364
|
-
|
|
27365
|
-
|
|
27366
|
-
const out = await (deps.request ?? trackerRequest)(`${credentials.api}/projects/${encodeURIComponent(repo)}/issues/${id}`, { method: "GET", headers: { "PRIVATE-TOKEN": credentials.token } });
|
|
27367
|
-
if (out.status !== 0)
|
|
27368
|
-
return { error: out.stderr || "GitLab issue fetch failed", kind: "request" };
|
|
27369
|
-
try {
|
|
27370
|
-
const triple = parseTriple(JSON.parse(out.stdout), id, ["iid"], ["description"]);
|
|
27371
|
-
if (!triple)
|
|
27372
|
-
return { error: "unexpected GitLab issue shape", kind: "request" };
|
|
27373
|
-
return { data: triple };
|
|
27374
|
-
} catch {
|
|
27375
|
-
return { error: "invalid GitLab issue response", kind: "request" };
|
|
27376
|
-
}
|
|
27974
|
+
const cfg = vcsConfig("load", root);
|
|
27975
|
+
const host = (deps.host ?? (cfg.ok && cfg.provider === "gitlab" ? cfg.gitlab?.host : null) ?? "gitlab.com").toLowerCase();
|
|
27976
|
+
if (!remote || !hostingApiHostMatches(remote, host))
|
|
27977
|
+
return { error: "hosting API host does not match the push destination", kind: "input" };
|
|
27978
|
+
const repoArg = host === "gitlab.com" ? repo : `https://${host}/${repo}`;
|
|
27979
|
+
return tryCliTriple(deps.cli ?? defaultCli("glab", hostingCliAvailable("gitlab")), "glab", ["issue", "view", id, "-R", repoArg, "-F", "json"], root, id, ["iid"], ["description"]);
|
|
27377
27980
|
};
|
|
27378
27981
|
|
|
27379
27982
|
// packages/workit-core/src/core/youtrack.ts
|
|
27380
|
-
import
|
|
27381
|
-
import
|
|
27983
|
+
import fs9 from "node:fs";
|
|
27984
|
+
import path14 from "node:path";
|
|
27382
27985
|
|
|
27383
27986
|
// packages/workit-core/src/core/templates.ts
|
|
27384
27987
|
var repoRoot2 = assetRoot();
|
|
@@ -27390,23 +27993,23 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
27390
27993
|
|
|
27391
27994
|
// packages/workit-core/src/core/youtrack.ts
|
|
27392
27995
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
27393
|
-
var
|
|
27394
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
27395
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
27996
|
+
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
27997
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(configDir(), "youtrack.json");
|
|
27998
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(resolveConfigDir(), "youtrack.json");
|
|
27396
27999
|
var youTrackTokenModeOk = (p) => {
|
|
27397
28000
|
if (process.platform === "win32")
|
|
27398
28001
|
return true;
|
|
27399
|
-
const mode =
|
|
28002
|
+
const mode = fs9.statSync(p).mode & 511;
|
|
27400
28003
|
return mode === 384;
|
|
27401
28004
|
};
|
|
27402
28005
|
function readYouTrackConfig(required) {
|
|
27403
28006
|
const cfgPath = youTrackConfigPath();
|
|
27404
|
-
if (!
|
|
28007
|
+
if (!fs9.existsSync(cfgPath)) {
|
|
27405
28008
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
27406
28009
|
}
|
|
27407
28010
|
let parsed;
|
|
27408
28011
|
try {
|
|
27409
|
-
parsed = JSON.parse(
|
|
28012
|
+
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
27410
28013
|
} catch {
|
|
27411
28014
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
27412
28015
|
}
|
|
@@ -27417,11 +28020,11 @@ function readYouTrackConfig(required) {
|
|
|
27417
28020
|
}
|
|
27418
28021
|
var readYouTrackContextConfig = () => {
|
|
27419
28022
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
27420
|
-
if (!
|
|
28023
|
+
if (!fs9.existsSync(cfgPath))
|
|
27421
28024
|
return { data: {} };
|
|
27422
28025
|
let parsed;
|
|
27423
28026
|
try {
|
|
27424
|
-
parsed = JSON.parse(
|
|
28027
|
+
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
27425
28028
|
} catch {
|
|
27426
28029
|
return { error: "YouTrack configuration is unavailable" };
|
|
27427
28030
|
}
|
|
@@ -27446,12 +28049,12 @@ function youTrackGreeting(configOverride) {
|
|
|
27446
28049
|
const cfgPath = configOverride ?? youTrackConfigPath();
|
|
27447
28050
|
let parsed;
|
|
27448
28051
|
try {
|
|
27449
|
-
parsed = JSON.parse(
|
|
28052
|
+
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
27450
28053
|
} catch {
|
|
27451
28054
|
return {
|
|
27452
28055
|
stdout: "",
|
|
27453
28056
|
exitCode: 1,
|
|
27454
|
-
stderr:
|
|
28057
|
+
stderr: fs9.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
|
|
27455
28058
|
};
|
|
27456
28059
|
}
|
|
27457
28060
|
if (!isConfigObject(parsed)) {
|
|
@@ -27497,15 +28100,15 @@ var youTrackToken = () => {
|
|
|
27497
28100
|
if ("error" in loaded)
|
|
27498
28101
|
return loaded;
|
|
27499
28102
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
27500
|
-
const tokenPath = tokenFile ?
|
|
27501
|
-
if (!tokenPath || !
|
|
28103
|
+
const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
|
|
28104
|
+
if (!tokenPath || !fs9.existsSync(tokenPath))
|
|
27502
28105
|
return { error: "missing youtrack.token" };
|
|
27503
28106
|
if (!youTrackTokenModeOk(tokenPath))
|
|
27504
28107
|
return { error: "youtrack.token mode must be 0600" };
|
|
27505
|
-
const token =
|
|
28108
|
+
const token = fs9.readFileSync(tokenPath, "utf8").trim();
|
|
27506
28109
|
if (!token)
|
|
27507
28110
|
return { error: "empty token file" };
|
|
27508
|
-
if (token ===
|
|
28111
|
+
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
27509
28112
|
return {
|
|
27510
28113
|
error: "token file still has placeholder YOUR_TOKEN_HERE — edit the file locally, then run /wk-status"
|
|
27511
28114
|
};
|
|
@@ -27568,9 +28171,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
27568
28171
|
} catch {
|
|
27569
28172
|
continue;
|
|
27570
28173
|
}
|
|
27571
|
-
if (!
|
|
28174
|
+
if (!fs9.existsSync(full))
|
|
27572
28175
|
continue;
|
|
27573
|
-
const text =
|
|
28176
|
+
const text = fs9.readFileSync(full, "utf8");
|
|
27574
28177
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
27575
28178
|
if (m)
|
|
27576
28179
|
return m[1];
|
|
@@ -27804,6 +28407,7 @@ var readExternalContext = async (root, payload) => {
|
|
|
27804
28407
|
}
|
|
27805
28408
|
}
|
|
27806
28409
|
};
|
|
28410
|
+
var targetWriterActionLease = Symbol("target-writer-action-lease");
|
|
27807
28411
|
|
|
27808
28412
|
// packages/workit-mcp/src/server.ts
|
|
27809
28413
|
class McpCapabilityUnavailableError extends Error {
|
|
@@ -27848,13 +28452,13 @@ var rootVariants = (workspaceRoot) => {
|
|
|
27848
28452
|
if (!workspaceRoot)
|
|
27849
28453
|
return [];
|
|
27850
28454
|
const roots = new Set;
|
|
27851
|
-
for (const candidate of [workspaceRoot,
|
|
27852
|
-
if (
|
|
28455
|
+
for (const candidate of [workspaceRoot, path15.resolve(workspaceRoot)]) {
|
|
28456
|
+
if (path15.isAbsolute(candidate) && candidate !== path15.parse(candidate).root)
|
|
27853
28457
|
roots.add(candidate);
|
|
27854
28458
|
}
|
|
27855
28459
|
try {
|
|
27856
|
-
const canonical =
|
|
27857
|
-
if (canonical !==
|
|
28460
|
+
const canonical = realpathSync8(workspaceRoot);
|
|
28461
|
+
if (canonical !== path15.parse(canonical).root)
|
|
27858
28462
|
roots.add(canonical);
|
|
27859
28463
|
} catch {}
|
|
27860
28464
|
return [...roots].sort((left, right) => right.length - left.length);
|
|
@@ -27955,11 +28559,11 @@ var thrownResult = (tool, error, workspaceRoot) => {
|
|
|
27955
28559
|
};
|
|
27956
28560
|
function createMcpServer(host, contextProvider) {
|
|
27957
28561
|
assertMcpHost(host);
|
|
27958
|
-
const here =
|
|
28562
|
+
const here = path15.dirname(fileURLToPath2(import.meta.url));
|
|
27959
28563
|
const sourceMarker = markSourcesLoaded([
|
|
27960
|
-
|
|
27961
|
-
|
|
27962
|
-
|
|
28564
|
+
path15.join(here, "server.ts"),
|
|
28565
|
+
path15.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
|
|
28566
|
+
path15.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
|
|
27963
28567
|
].filter((file) => existsSync7(file)));
|
|
27964
28568
|
let staleWarned = false;
|
|
27965
28569
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
@@ -28139,8 +28743,8 @@ var main = async () => {
|
|
|
28139
28743
|
process.exitCode = 2;
|
|
28140
28744
|
}
|
|
28141
28745
|
};
|
|
28142
|
-
var entryPath = process.argv[1] ?
|
|
28143
|
-
if (entryPath ===
|
|
28746
|
+
var entryPath = process.argv[1] ? path16.resolve(process.argv[1]) : "";
|
|
28747
|
+
if (entryPath === path16.resolve(fileURLToPath3(import.meta.url)))
|
|
28144
28748
|
await main();
|
|
28145
28749
|
export {
|
|
28146
28750
|
McpCapabilityUnavailableError,
|