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