@brainervirus/workit-codex 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/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +900 -295
- package/dist/workit-hook.js +1011 -232
- package/package.json +3 -3
- package/skills/workit-babysit/SKILL.md +20 -26
- package/skills/workit-behavioral-tdd/SKILL.md +0 -5
- package/skills/workit-blast-radius/SKILL.md +0 -5
- package/skills/workit-challenge/SKILL.md +30 -47
- package/skills/workit-debug/SKILL.md +5 -27
- package/skills/workit-deslop/SKILL.md +0 -5
- package/skills/workit-diagram/SKILL.md +0 -5
- package/skills/workit-green-run/SKILL.md +0 -5
- package/skills/workit-handoff/SKILL.md +2 -8
- package/skills/workit-implement/SKILL.md +14 -20
- package/skills/workit-mockup/SKILL.md +0 -5
- package/skills/workit-plan/SKILL.md +30 -63
- package/skills/workit-review/SKILL.md +0 -5
- package/skills/workit-steer/SKILL.md +27 -27
package/dist/launch-mcp.js
CHANGED
|
@@ -6621,13 +6621,13 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
6621
6621
|
});
|
|
6622
6622
|
|
|
6623
6623
|
// packages/workit-codex/scripts/launch-mcp.ts
|
|
6624
|
-
import { existsSync as existsSync8, realpathSync as
|
|
6625
|
-
import
|
|
6624
|
+
import { existsSync as existsSync8, realpathSync as realpathSync9, statSync as statSync3 } from "node:fs";
|
|
6625
|
+
import path16 from "node:path";
|
|
6626
6626
|
|
|
6627
6627
|
// packages/workit-core/src/core.ts
|
|
6628
6628
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
6629
|
-
import { existsSync as existsSync5, realpathSync as
|
|
6630
|
-
import
|
|
6629
|
+
import { existsSync as existsSync5, realpathSync as realpathSync7 } from "node:fs";
|
|
6630
|
+
import path11 from "node:path";
|
|
6631
6631
|
|
|
6632
6632
|
// packages/workit-core/src/core/task-contract.ts
|
|
6633
6633
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -11934,6 +11934,17 @@ function _array(Class, element, params) {
|
|
|
11934
11934
|
...normalizeParams(params)
|
|
11935
11935
|
});
|
|
11936
11936
|
}
|
|
11937
|
+
function _custom(Class, fn, _params) {
|
|
11938
|
+
const norm = normalizeParams(_params);
|
|
11939
|
+
norm.abort ?? (norm.abort = true);
|
|
11940
|
+
const schema = new Class({
|
|
11941
|
+
type: "custom",
|
|
11942
|
+
check: "custom",
|
|
11943
|
+
fn,
|
|
11944
|
+
...norm
|
|
11945
|
+
});
|
|
11946
|
+
return schema;
|
|
11947
|
+
}
|
|
11937
11948
|
function _refine(Class, fn, _params) {
|
|
11938
11949
|
const schema = new Class({
|
|
11939
11950
|
type: "custom",
|
|
@@ -14087,6 +14098,9 @@ var ZodCustom = /* @__PURE__ */ $constructor("ZodCustom", (inst, def) => {
|
|
|
14087
14098
|
ZodType.init(inst, def);
|
|
14088
14099
|
inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);
|
|
14089
14100
|
});
|
|
14101
|
+
function custom(fn, _params) {
|
|
14102
|
+
return _custom(ZodCustom, fn ?? (() => true), _params);
|
|
14103
|
+
}
|
|
14090
14104
|
function refine(fn, _params = {}) {
|
|
14091
14105
|
return _refine(ZodCustom, fn, _params);
|
|
14092
14106
|
}
|
|
@@ -14520,7 +14534,8 @@ var decisionSchema = object({
|
|
|
14520
14534
|
consumption: object({
|
|
14521
14535
|
state: _enum(["reserved", "consumed", "uncertain"]),
|
|
14522
14536
|
at: utc,
|
|
14523
|
-
actionRef: refSchema
|
|
14537
|
+
actionRef: refSchema,
|
|
14538
|
+
historicalActionId: text.optional()
|
|
14524
14539
|
}).strict().nullable()
|
|
14525
14540
|
}).strict();
|
|
14526
14541
|
var findingSchema = object({
|
|
@@ -14663,7 +14678,7 @@ var taskSummarySchema = object({
|
|
|
14663
14678
|
requirements: array(requirementEvaluationSchema),
|
|
14664
14679
|
writer: workspaceRecordSchema.shape.writer
|
|
14665
14680
|
}).strict();
|
|
14666
|
-
var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true });
|
|
14681
|
+
var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true }).extend({ source: provenanceSchema.pick({ host: true, kind: true }) }).strict();
|
|
14667
14682
|
var exportBundleSchema = object({
|
|
14668
14683
|
schemaVersion: literal(1),
|
|
14669
14684
|
exportedAt: utc,
|
|
@@ -14688,6 +14703,7 @@ var taskOperations = {
|
|
|
14688
14703
|
list: operation({
|
|
14689
14704
|
action: literal("list"),
|
|
14690
14705
|
status: _enum(["open", "closed", "all"]).optional(),
|
|
14706
|
+
query: string2().trim().min(1).max(200).optional(),
|
|
14691
14707
|
limit: number2().int().min(1).max(50).optional()
|
|
14692
14708
|
}),
|
|
14693
14709
|
inspect: operation({
|
|
@@ -14888,7 +14904,47 @@ function operationJsonSchema(family) {
|
|
|
14888
14904
|
return toJSONSchema(operationSchemas[family], { target: "draft-2020-12" });
|
|
14889
14905
|
}
|
|
14890
14906
|
var OPERATION_SCHEMA_DEPTH = 1;
|
|
14891
|
-
var
|
|
14907
|
+
var schemaShapeDescription = (value, depth = 0) => {
|
|
14908
|
+
if (!value || typeof value !== "object")
|
|
14909
|
+
return "value";
|
|
14910
|
+
const schema = value;
|
|
14911
|
+
if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
|
|
14912
|
+
const options = Array.isArray(schema.oneOf) ? schema.oneOf : schema.anyOf;
|
|
14913
|
+
return `(${options.map((option) => schemaShapeDescription(option, depth)).join("|")})`;
|
|
14914
|
+
}
|
|
14915
|
+
if (schema.const !== undefined)
|
|
14916
|
+
return `=${JSON.stringify(schema.const)}`;
|
|
14917
|
+
if (Array.isArray(schema.enum))
|
|
14918
|
+
return `[${schema.enum.map(String).join("|")}]`;
|
|
14919
|
+
const type = Array.isArray(schema.type) ? schema.type.join("/") : schema.type;
|
|
14920
|
+
if (type === "array")
|
|
14921
|
+
return `arr<${schemaShapeDescription(schema.items, depth)}>`;
|
|
14922
|
+
if (type === "object" && schema.properties && typeof schema.properties === "object") {
|
|
14923
|
+
if (depth >= 2)
|
|
14924
|
+
return "obj";
|
|
14925
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
14926
|
+
const properties = schema.properties;
|
|
14927
|
+
const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child, depth + 1)}`);
|
|
14928
|
+
return `obj{${fields.join(",")}}`;
|
|
14929
|
+
}
|
|
14930
|
+
if (typeof type !== "string")
|
|
14931
|
+
return "?";
|
|
14932
|
+
return type.split("/").map((part) => ({ string: "str", boolean: "bool", number: "num", null: "null" })[part] ?? part).join("|");
|
|
14933
|
+
};
|
|
14934
|
+
var canonicalFieldsDescription = (value) => {
|
|
14935
|
+
if (Array.isArray(value))
|
|
14936
|
+
return `Canonical object fields: ${value.join(", ")}. Workit validates the complete nested value.`;
|
|
14937
|
+
const isZodType = (schema) => ("_zod" in schema) && typeof schema._zod === "object";
|
|
14938
|
+
const schema = isZodType(value) ? toJSONSchema(value, { target: "draft-2020-12" }) : value;
|
|
14939
|
+
if (!schema.properties || typeof schema.properties !== "object") {
|
|
14940
|
+
const variants = Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : null;
|
|
14941
|
+
return variants ? `Canonical shapes: ${variants.map((variant) => schemaShapeDescription(variant)).join(" | ")}. Full nested value is validated.` : "Canonical nested value; Workit validates the complete value.";
|
|
14942
|
+
}
|
|
14943
|
+
const properties = schema.properties;
|
|
14944
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
14945
|
+
const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child)}`);
|
|
14946
|
+
return `Fields (! required): ${fields.join(", ")}. Full nested value is validated.`;
|
|
14947
|
+
};
|
|
14892
14948
|
function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, stringifiedObjects = false) {
|
|
14893
14949
|
const tolerate = (projected) => {
|
|
14894
14950
|
if (!stringifiedObjects)
|
|
@@ -14912,9 +14968,10 @@ function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, s
|
|
|
14912
14968
|
return node;
|
|
14913
14969
|
const record = node;
|
|
14914
14970
|
if (record.properties && typeof record.properties === "object") {
|
|
14915
|
-
const
|
|
14971
|
+
const properties = record.properties;
|
|
14972
|
+
const fields = Object.keys(properties);
|
|
14916
14973
|
if (currentDepth >= maxDepth || fields.length === 0) {
|
|
14917
|
-
const description = stringifiedObjects ? `${canonicalFieldsDescription(
|
|
14974
|
+
const description = stringifiedObjects ? `${canonicalFieldsDescription(record)} A JSON-encoded string is also accepted.` : canonicalFieldsDescription(record);
|
|
14918
14975
|
return tolerate({ type: "object", description });
|
|
14919
14976
|
}
|
|
14920
14977
|
return tolerate({
|
|
@@ -15035,6 +15092,7 @@ function requirementId(input) {
|
|
|
15035
15092
|
}
|
|
15036
15093
|
// packages/workit-core/src/core/task-store.ts
|
|
15037
15094
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
15095
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
15038
15096
|
import * as fs4 from "node:fs";
|
|
15039
15097
|
import { hostname } from "node:os";
|
|
15040
15098
|
import path4 from "node:path";
|
|
@@ -15670,7 +15728,8 @@ var metadataLockSchema = object({
|
|
|
15670
15728
|
pid: number2().int().nonnegative().safe(),
|
|
15671
15729
|
processStart: string2().nullable(),
|
|
15672
15730
|
host: string2().min(1),
|
|
15673
|
-
nonce: string2().min(1)
|
|
15731
|
+
nonce: string2().min(1),
|
|
15732
|
+
externalAction: literal(true).optional()
|
|
15674
15733
|
}).strict();
|
|
15675
15734
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
15676
15735
|
var cachedRuntimeVersion = null;
|
|
@@ -15722,6 +15781,28 @@ var sameMetadataLock = (left, right) => {
|
|
|
15722
15781
|
const parsed = metadataLockSchema.safeParse(left);
|
|
15723
15782
|
return parsed.success && canonicalJson(parsed.data) === canonicalJson(right);
|
|
15724
15783
|
};
|
|
15784
|
+
var sameDirectoryIdentity = (left, right) => {
|
|
15785
|
+
if (!path4.isAbsolute(left) || !path4.isAbsolute(right))
|
|
15786
|
+
return false;
|
|
15787
|
+
const normalizedLeft = path4.resolve(left);
|
|
15788
|
+
const normalizedRight = path4.resolve(right);
|
|
15789
|
+
try {
|
|
15790
|
+
const a = fs4.statSync(normalizedLeft, { bigint: true });
|
|
15791
|
+
const b = fs4.statSync(normalizedRight, { bigint: true });
|
|
15792
|
+
if (!a.isDirectory() || !b.isDirectory())
|
|
15793
|
+
return false;
|
|
15794
|
+
if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
|
|
15795
|
+
return true;
|
|
15796
|
+
const canonicalLeft = fs4.realpathSync(normalizedLeft);
|
|
15797
|
+
const canonicalRight = fs4.realpathSync(normalizedRight);
|
|
15798
|
+
if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
|
|
15799
|
+
return true;
|
|
15800
|
+
return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
|
|
15801
|
+
} catch {
|
|
15802
|
+
return false;
|
|
15803
|
+
}
|
|
15804
|
+
};
|
|
15805
|
+
var externalActionLockRoots = new AsyncLocalStorage;
|
|
15725
15806
|
|
|
15726
15807
|
class TaskStore {
|
|
15727
15808
|
root;
|
|
@@ -15782,7 +15863,7 @@ class TaskStore {
|
|
|
15782
15863
|
const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
|
|
15783
15864
|
if (!item.exists)
|
|
15784
15865
|
return success(null, null, null);
|
|
15785
|
-
if (item.result.ok && item.result.data.root !== this.root)
|
|
15866
|
+
if (item.result.ok && item.result.data.root !== this.root && !sameDirectoryIdentity(item.result.data.root, this.root))
|
|
15786
15867
|
return failure("recovery_required", "workspace root binding is invalid", {
|
|
15787
15868
|
path: this.workspacePath
|
|
15788
15869
|
});
|
|
@@ -15873,6 +15954,29 @@ class TaskStore {
|
|
|
15873
15954
|
const current = this.readWorkspace();
|
|
15874
15955
|
if (!current.ok)
|
|
15875
15956
|
return current;
|
|
15957
|
+
const origin = input.task.origin;
|
|
15958
|
+
if (current.data && origin) {
|
|
15959
|
+
if (origin.workspaceId === current.data.id) {
|
|
15960
|
+
const source = this.readTask(origin.taskId);
|
|
15961
|
+
if (source.ok)
|
|
15962
|
+
return success(source.data.revision, current.data.revision, source.data);
|
|
15963
|
+
if (source.code !== "not_found")
|
|
15964
|
+
return source;
|
|
15965
|
+
return failure("not_found", "source task is not present in this shared workspace");
|
|
15966
|
+
}
|
|
15967
|
+
const tasks = this.listTasks();
|
|
15968
|
+
if (!tasks.ok)
|
|
15969
|
+
return tasks;
|
|
15970
|
+
const prior = tasks.data.filter((task) => task.origin?.workspaceId === origin.workspaceId && task.origin?.taskId === origin.taskId);
|
|
15971
|
+
if (prior.length > 1)
|
|
15972
|
+
return failure("recovery_required", `source task has multiple imported mappings: ${prior.map((task) => task.id).join(", ")}`);
|
|
15973
|
+
if (prior.length === 1) {
|
|
15974
|
+
const existing = prior[0];
|
|
15975
|
+
if (existing.origin.exportDigest !== origin.exportDigest)
|
|
15976
|
+
return failure("revision_conflict", "source task export changed; reconcile the mapped destination task before importing", { taskId: existing.id });
|
|
15977
|
+
return success(existing.revision, current.data.revision, existing);
|
|
15978
|
+
}
|
|
15979
|
+
}
|
|
15876
15980
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
15877
15981
|
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
15878
15982
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
@@ -16261,6 +16365,73 @@ class TaskStore {
|
|
|
16261
16365
|
})
|
|
16262
16366
|
};
|
|
16263
16367
|
}
|
|
16368
|
+
externalActionLockOptions() {
|
|
16369
|
+
const options = this.metadataLockOptions();
|
|
16370
|
+
return { ...options, payload: () => ({ ...options.payload(), externalAction: true }) };
|
|
16371
|
+
}
|
|
16372
|
+
async withExternalActionLock(operation, reentrant = false) {
|
|
16373
|
+
const activeRoots = externalActionLockRoots.getStore();
|
|
16374
|
+
if (activeRoots && [...activeRoots].some((root) => root === this.root || sameDirectoryIdentity(root, this.root)))
|
|
16375
|
+
return operation();
|
|
16376
|
+
try {
|
|
16377
|
+
this.initializeMutationStorage();
|
|
16378
|
+
} catch (error) {
|
|
16379
|
+
return failure("storage_error", `unable to initialize store: ${String(error)}`, {
|
|
16380
|
+
path: this.workitDir
|
|
16381
|
+
});
|
|
16382
|
+
}
|
|
16383
|
+
let handle;
|
|
16384
|
+
try {
|
|
16385
|
+
handle = acquireFileLockSync(this.workspacePath, this.externalActionLockOptions());
|
|
16386
|
+
} catch (error) {
|
|
16387
|
+
return this.lockFailure(error);
|
|
16388
|
+
}
|
|
16389
|
+
let result;
|
|
16390
|
+
try {
|
|
16391
|
+
if (!handle.verifyStillHeld())
|
|
16392
|
+
result = failure("recovery_required", "metadata lock was compromised", {
|
|
16393
|
+
path: this.lockPath
|
|
16394
|
+
});
|
|
16395
|
+
else {
|
|
16396
|
+
try {
|
|
16397
|
+
const run = () => operation();
|
|
16398
|
+
result = await (reentrant ? externalActionLockRoots.run(new Set([...activeRoots ?? [], this.root]), run) : run());
|
|
16399
|
+
} catch {
|
|
16400
|
+
result = failure("external_outcome_unknown", "external action outcome is unknown", {
|
|
16401
|
+
outcome: "unknown"
|
|
16402
|
+
});
|
|
16403
|
+
}
|
|
16404
|
+
try {
|
|
16405
|
+
if (!handle.verifyStillHeld())
|
|
16406
|
+
result = failure("external_outcome_unknown", "metadata lock was compromised", {
|
|
16407
|
+
outcome: "unknown",
|
|
16408
|
+
path: this.lockPath
|
|
16409
|
+
});
|
|
16410
|
+
} catch {
|
|
16411
|
+
result = failure("external_outcome_unknown", "metadata lock could not be verified", {
|
|
16412
|
+
outcome: "unknown",
|
|
16413
|
+
path: this.lockPath
|
|
16414
|
+
});
|
|
16415
|
+
}
|
|
16416
|
+
}
|
|
16417
|
+
} catch {
|
|
16418
|
+
result = failure("external_outcome_unknown", "external action lock operation failed", {
|
|
16419
|
+
outcome: "unknown",
|
|
16420
|
+
path: this.lockPath
|
|
16421
|
+
});
|
|
16422
|
+
}
|
|
16423
|
+
try {
|
|
16424
|
+
handle.release();
|
|
16425
|
+
} catch (error) {
|
|
16426
|
+
const released = this.lockFailure(error);
|
|
16427
|
+
const releaseError = released.ok ? "metadata lock release failed" : released.error;
|
|
16428
|
+
return result.ok ? failure("external_outcome_unknown", "external action metadata lock release is uncertain", {
|
|
16429
|
+
outcome: "unknown",
|
|
16430
|
+
path: this.lockPath
|
|
16431
|
+
}) : failure("recovery_required", `${result.error}; ${releaseError}`, result.details);
|
|
16432
|
+
}
|
|
16433
|
+
return result;
|
|
16434
|
+
}
|
|
16264
16435
|
recoveryLockOptions(observed, evidence, gate) {
|
|
16265
16436
|
const options = this.metadataLockOptions();
|
|
16266
16437
|
options.staleRecovery = "remove-if-unchanged";
|
|
@@ -16330,6 +16501,14 @@ class TaskStore {
|
|
|
16330
16501
|
lockFailure(error) {
|
|
16331
16502
|
const value = error;
|
|
16332
16503
|
const code = typeof value?.code === "string" ? value.code : "";
|
|
16504
|
+
if (code === "EEXIST" || code === "file_lock_timeout") {
|
|
16505
|
+
const lock = this.readLockSnapshot();
|
|
16506
|
+
if (lock.ok && lock.data?.data.externalAction)
|
|
16507
|
+
return failure("writer_conflict", "workspace is reserved by a managed external action", {
|
|
16508
|
+
outcome: "not_started",
|
|
16509
|
+
path: this.lockPath
|
|
16510
|
+
});
|
|
16511
|
+
}
|
|
16333
16512
|
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));
|
|
16334
16513
|
return failure(recovery ? "recovery_required" : "storage_error", `metadata lock operation failed: ${String(error)}`, { path: this.lockPath });
|
|
16335
16514
|
}
|
|
@@ -16597,21 +16776,23 @@ function assertProductWriteAllowed(input) {
|
|
|
16597
16776
|
}
|
|
16598
16777
|
|
|
16599
16778
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
16600
|
-
import { createHash as
|
|
16779
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
16601
16780
|
import * as fs7 from "node:fs";
|
|
16602
16781
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
16603
|
-
import
|
|
16782
|
+
import path10 from "node:path";
|
|
16604
16783
|
|
|
16605
16784
|
// packages/workit-core/src/core/authority.ts
|
|
16606
|
-
import { createHash as
|
|
16785
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
16607
16786
|
import * as fs6 from "node:fs";
|
|
16608
|
-
import
|
|
16787
|
+
import path9 from "node:path";
|
|
16609
16788
|
|
|
16610
16789
|
// packages/workit-core/src/core/auto-approval.ts
|
|
16611
|
-
import { createHash as
|
|
16612
|
-
import { readFileSync as readFileSync4 } from "node:fs";
|
|
16790
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
16791
|
+
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
16792
|
+
import path8 from "node:path";
|
|
16613
16793
|
|
|
16614
16794
|
// packages/workit-core/src/core/workspaces.ts
|
|
16795
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
16615
16796
|
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
16616
16797
|
import path6 from "node:path";
|
|
16617
16798
|
|
|
@@ -16629,7 +16810,7 @@ import os from "node:os";
|
|
|
16629
16810
|
import path5 from "node:path";
|
|
16630
16811
|
|
|
16631
16812
|
// packages/workit-core/src/core/boundary.ts
|
|
16632
|
-
import { statSync } from "node:fs";
|
|
16813
|
+
import { statSync as statSync2 } from "node:fs";
|
|
16633
16814
|
var EVENT = {
|
|
16634
16815
|
initialization: "initialization",
|
|
16635
16816
|
provenance: "provenance",
|
|
@@ -16657,7 +16838,7 @@ var changedSourcesSinceLoad = (marker) => {
|
|
|
16657
16838
|
const changed = [];
|
|
16658
16839
|
for (const file of marker.files) {
|
|
16659
16840
|
try {
|
|
16660
|
-
if (
|
|
16841
|
+
if (statSync2(file).mtimeMs > marker.loadedAt)
|
|
16661
16842
|
changed.push(file);
|
|
16662
16843
|
} catch {}
|
|
16663
16844
|
}
|
|
@@ -16816,33 +16997,197 @@ var COMMIT_PRESETS = [
|
|
|
16816
16997
|
"custom",
|
|
16817
16998
|
"auto"
|
|
16818
16999
|
];
|
|
17000
|
+
var resolveCommitPolicy = (config, workspace) => {
|
|
17001
|
+
const wp = workspace?.commitPolicy ?? {};
|
|
17002
|
+
if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
|
|
17003
|
+
return {
|
|
17004
|
+
preset: wp.preset,
|
|
17005
|
+
...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
|
|
17006
|
+
};
|
|
17007
|
+
}
|
|
17008
|
+
return config.commitPolicy ?? { preset: "conventional" };
|
|
17009
|
+
};
|
|
16819
17010
|
|
|
16820
17011
|
// packages/workit-core/src/core/workspaces.ts
|
|
17012
|
+
var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
|
|
17013
|
+
var branchPreset = custom((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
|
|
17014
|
+
var commitPreset = custom((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
|
|
17015
|
+
var workspaceBranchPolicySchema = object({
|
|
17016
|
+
preset: branchPreset,
|
|
17017
|
+
developBranch: nonBlank.optional(),
|
|
17018
|
+
prefixes: object({
|
|
17019
|
+
feature: nonBlank,
|
|
17020
|
+
bugfix: nonBlank,
|
|
17021
|
+
release: nonBlank,
|
|
17022
|
+
hotfix: nonBlank
|
|
17023
|
+
}).passthrough().optional(),
|
|
17024
|
+
allowed: array(nonBlank).optional(),
|
|
17025
|
+
protected: array(nonBlank).optional(),
|
|
17026
|
+
integration: _enum(["pr", "merge"]).optional()
|
|
17027
|
+
}).strict();
|
|
17028
|
+
var workspaceCommitPolicySchema = object({ preset: commitPreset, pattern: string2().optional() }).strict();
|
|
17029
|
+
var workspaceProfileSchema = object({
|
|
17030
|
+
branchPolicy: workspaceBranchPolicySchema.optional(),
|
|
17031
|
+
commitPolicy: workspaceCommitPolicySchema.optional()
|
|
17032
|
+
}).strict().refine((profile) => profile.branchPolicy !== undefined || profile.commitPolicy !== undefined, {
|
|
17033
|
+
message: "a profile must define branchPolicy or commitPolicy"
|
|
17034
|
+
});
|
|
17035
|
+
var versionSourceSchema = discriminatedUnion("kind", [
|
|
17036
|
+
object({
|
|
17037
|
+
kind: literal("package-json"),
|
|
17038
|
+
path: nonBlank,
|
|
17039
|
+
field: nonBlank
|
|
17040
|
+
}).strict(),
|
|
17041
|
+
object({ kind: literal("git-tag") }).strict(),
|
|
17042
|
+
object({ kind: literal("manual") }).strict()
|
|
17043
|
+
]);
|
|
17044
|
+
var releaseTrackSchema = object({
|
|
17045
|
+
strategy: branchPreset,
|
|
17046
|
+
productionBranch: nonBlank,
|
|
17047
|
+
integrationBranch: nonBlank,
|
|
17048
|
+
naming: object({ feature: nonBlank, release: nonBlank, hotfix: nonBlank }).strict(),
|
|
17049
|
+
baseBranch: nonBlank,
|
|
17050
|
+
mergeBackBranches: array(nonBlank),
|
|
17051
|
+
pullRequestTarget: nonBlank,
|
|
17052
|
+
tagNamespace: string2(),
|
|
17053
|
+
versionSource: versionSourceSchema,
|
|
17054
|
+
requiredChecks: array(nonBlank)
|
|
17055
|
+
}).strict().superRefine((track, context) => {
|
|
17056
|
+
for (const [field, values] of [
|
|
17057
|
+
["mergeBackBranches", track.mergeBackBranches],
|
|
17058
|
+
["requiredChecks", track.requiredChecks]
|
|
17059
|
+
]) {
|
|
17060
|
+
if (new Set(values).size !== values.length) {
|
|
17061
|
+
context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
|
|
17062
|
+
}
|
|
17063
|
+
}
|
|
17064
|
+
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(".."))) {
|
|
17065
|
+
context.addIssue({
|
|
17066
|
+
code: "custom",
|
|
17067
|
+
path: ["versionSource", "path"],
|
|
17068
|
+
message: "must stay inside the repository"
|
|
17069
|
+
});
|
|
17070
|
+
}
|
|
17071
|
+
});
|
|
17072
|
+
var workspaceConfigSchema = object({
|
|
17073
|
+
name: nonBlank,
|
|
17074
|
+
glob: nonBlank,
|
|
17075
|
+
vcs: object({
|
|
17076
|
+
provider: _enum(["gitlab", "github"]),
|
|
17077
|
+
defaultTargetBranch: nonBlank.optional(),
|
|
17078
|
+
tokenFile: nonBlank.optional(),
|
|
17079
|
+
account: nonBlank.optional()
|
|
17080
|
+
}).passthrough().optional(),
|
|
17081
|
+
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
17082
|
+
youtrack: object({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
|
|
17083
|
+
issues: object({ provider: literal("github").optional(), link_on_pr: boolean2().optional() }).passthrough().optional(),
|
|
17084
|
+
branchPolicy: workspaceBranchPolicySchema.optional(),
|
|
17085
|
+
commitPolicy: workspaceCommitPolicySchema.optional(),
|
|
17086
|
+
defaultProfile: nonBlank.optional(),
|
|
17087
|
+
profiles: record(nonBlank, workspaceProfileSchema).optional(),
|
|
17088
|
+
releaseTracks: record(nonBlank, releaseTrackSchema).optional()
|
|
17089
|
+
}).passthrough().superRefine((workspace, context) => {
|
|
17090
|
+
if (workspace.youtrack && workspace.vcs?.provider !== "gitlab") {
|
|
17091
|
+
context.addIssue({
|
|
17092
|
+
code: "custom",
|
|
17093
|
+
path: ["youtrack"],
|
|
17094
|
+
message: `YouTrack issue linking requires the gitlab provider, got ${workspace.vcs?.provider ?? "unset"}`
|
|
17095
|
+
});
|
|
17096
|
+
}
|
|
17097
|
+
if (workspace.issues && workspace.vcs?.provider !== "github") {
|
|
17098
|
+
context.addIssue({
|
|
17099
|
+
code: "custom",
|
|
17100
|
+
path: ["issues"],
|
|
17101
|
+
message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
|
|
17102
|
+
});
|
|
17103
|
+
}
|
|
17104
|
+
if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
|
|
17105
|
+
context.addIssue({
|
|
17106
|
+
code: "custom",
|
|
17107
|
+
path: ["defaultProfile"],
|
|
17108
|
+
message: `does not name a configured profile`
|
|
17109
|
+
});
|
|
17110
|
+
}
|
|
17111
|
+
});
|
|
17112
|
+
var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).optional() }).passthrough().superRefine((file, context) => {
|
|
17113
|
+
const names = new Set;
|
|
17114
|
+
for (const [index, workspace] of (file.workspaces ?? []).entries()) {
|
|
17115
|
+
if (names.has(workspace.name)) {
|
|
17116
|
+
context.addIssue({
|
|
17117
|
+
code: "custom",
|
|
17118
|
+
path: ["workspaces", index, "name"],
|
|
17119
|
+
message: `duplicate workspace name ${JSON.stringify(workspace.name)}`
|
|
17120
|
+
});
|
|
17121
|
+
}
|
|
17122
|
+
names.add(workspace.name);
|
|
17123
|
+
}
|
|
17124
|
+
});
|
|
17125
|
+
var workspacesRevision = (contents) => createHash3("sha256").update(contents ?? "<missing>").digest("hex");
|
|
17126
|
+
var validateWorkspacesDocument = (parsed, file) => {
|
|
17127
|
+
if (!isConfigObject(parsed)) {
|
|
17128
|
+
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
17129
|
+
}
|
|
17130
|
+
const validated = workspacesFileSchema.safeParse(parsed, { reportInput: true });
|
|
17131
|
+
if (!validated.success) {
|
|
17132
|
+
const detail = validated.error.issues.map((issue) => {
|
|
17133
|
+
const field = String(issue.path[issue.path.length - 1] ?? "");
|
|
17134
|
+
const attempted = ["provider", "preset", "strategy"].includes(field) && typeof issue.input === "string" ? ` (${JSON.stringify(issue.input)})` : "";
|
|
17135
|
+
return `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}${attempted}`;
|
|
17136
|
+
}).join("; ");
|
|
17137
|
+
return {
|
|
17138
|
+
status: "invalid",
|
|
17139
|
+
path: file,
|
|
17140
|
+
entries: [],
|
|
17141
|
+
error: `${file} has invalid workspace configuration: ${detail}`
|
|
17142
|
+
};
|
|
17143
|
+
}
|
|
17144
|
+
for (const [index, workspace] of (validated.data.workspaces ?? []).entries()) {
|
|
17145
|
+
const glob = validateWorkspaceGlob(workspace.glob);
|
|
17146
|
+
if (!glob.ok) {
|
|
17147
|
+
return {
|
|
17148
|
+
status: "invalid",
|
|
17149
|
+
path: file,
|
|
17150
|
+
entries: [],
|
|
17151
|
+
error: `${file} workspaces.${index}.glob: ${glob.error}`
|
|
17152
|
+
};
|
|
17153
|
+
}
|
|
17154
|
+
}
|
|
17155
|
+
return {
|
|
17156
|
+
status: "valid",
|
|
17157
|
+
path: file,
|
|
17158
|
+
entries: validated.data.workspaces ?? [],
|
|
17159
|
+
document: parsed
|
|
17160
|
+
};
|
|
17161
|
+
};
|
|
16821
17162
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
16822
17163
|
const file = path6.join(dir, "workspaces.json");
|
|
16823
17164
|
let raw;
|
|
16824
17165
|
try {
|
|
16825
17166
|
raw = readFileSync3(file, "utf8");
|
|
16826
|
-
} catch {
|
|
16827
|
-
|
|
17167
|
+
} catch (error) {
|
|
17168
|
+
if (error.code === "ENOENT")
|
|
17169
|
+
return { status: "missing", path: file, entries: [], revision: workspacesRevision(null) };
|
|
17170
|
+
return {
|
|
17171
|
+
status: "malformed",
|
|
17172
|
+
path: file,
|
|
17173
|
+
entries: [],
|
|
17174
|
+
error: `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
|
|
17175
|
+
};
|
|
16828
17176
|
}
|
|
16829
17177
|
let parsed;
|
|
16830
17178
|
try {
|
|
16831
17179
|
parsed = JSON.parse(raw);
|
|
16832
17180
|
} catch {
|
|
16833
|
-
return {
|
|
16834
|
-
|
|
16835
|
-
|
|
16836
|
-
|
|
17181
|
+
return {
|
|
17182
|
+
status: "malformed",
|
|
17183
|
+
path: file,
|
|
17184
|
+
entries: [],
|
|
17185
|
+
revision: workspacesRevision(raw),
|
|
17186
|
+
error: `${file} is not valid JSON`
|
|
17187
|
+
};
|
|
16837
17188
|
}
|
|
16838
|
-
|
|
16839
|
-
return {
|
|
16840
|
-
status: "valid",
|
|
16841
|
-
path: file,
|
|
16842
|
-
entries: Array.isArray(list) ? list : []
|
|
16843
|
-
};
|
|
17189
|
+
return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
|
|
16844
17190
|
};
|
|
16845
|
-
var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
|
|
16846
17191
|
var globToRegExp = (glob) => {
|
|
16847
17192
|
let out = "";
|
|
16848
17193
|
for (let i = 0;i < glob.length; i++) {
|
|
@@ -16877,6 +17222,30 @@ var globToRegExp = (glob) => {
|
|
|
16877
17222
|
return new RegExp(`^${out}$`);
|
|
16878
17223
|
};
|
|
16879
17224
|
var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
|
|
17225
|
+
var UNSUPPORTED_GLOB = /[?[\]{}]/;
|
|
17226
|
+
var UNSUPPORTED_EXTGLOB = /[@+*]\(|!/;
|
|
17227
|
+
var validateWorkspaceGlob = (glob) => {
|
|
17228
|
+
const trimmed = glob.trim();
|
|
17229
|
+
if (!trimmed)
|
|
17230
|
+
return { ok: false, error: "workspace pattern is required" };
|
|
17231
|
+
const m = UNSUPPORTED_GLOB.exec(trimmed);
|
|
17232
|
+
if (m) {
|
|
17233
|
+
return {
|
|
17234
|
+
ok: false,
|
|
17235
|
+
error: `unsupported glob character ${JSON.stringify(m[0])} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
|
|
17236
|
+
};
|
|
17237
|
+
}
|
|
17238
|
+
const ext = UNSUPPORTED_EXTGLOB.exec(trimmed);
|
|
17239
|
+
if (ext) {
|
|
17240
|
+
const token = ext[0];
|
|
17241
|
+
const kind = token === "!" ? "negation (!)" : `extglob (${token})`;
|
|
17242
|
+
return {
|
|
17243
|
+
ok: false,
|
|
17244
|
+
error: `unsupported glob ${kind} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
|
|
17245
|
+
};
|
|
17246
|
+
}
|
|
17247
|
+
return { ok: true };
|
|
17248
|
+
};
|
|
16880
17249
|
var realpathOf = (p) => {
|
|
16881
17250
|
const real = realpathSync2.native ?? realpathSync2;
|
|
16882
17251
|
try {
|
|
@@ -16894,29 +17263,132 @@ var canonicalGlob = (glob) => {
|
|
|
16894
17263
|
const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
|
|
16895
17264
|
return real + (prefix.endsWith("/") ? "/" : "") + rest;
|
|
16896
17265
|
};
|
|
16897
|
-
var
|
|
17266
|
+
var globSpecificity = (glob) => glob.replaceAll("\\", "/").split("/").filter((segment) => segment.length > 0 && !/[*?[\]{]/.test(segment)).length;
|
|
17267
|
+
var selectWorkspaceMatch = (matches, cwd, workspaceName) => {
|
|
17268
|
+
if (workspaceName !== undefined) {
|
|
17269
|
+
const named = matches.filter((entry) => entry.name === workspaceName);
|
|
17270
|
+
if (named.length === 1)
|
|
17271
|
+
return named[0];
|
|
17272
|
+
if (named.length > 1)
|
|
17273
|
+
throw new Error(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
|
|
17274
|
+
if (matches.length > 0) {
|
|
17275
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
|
|
17276
|
+
}
|
|
17277
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
|
|
17278
|
+
}
|
|
17279
|
+
if (matches.length < 2)
|
|
17280
|
+
return matches[0];
|
|
17281
|
+
const highestSpecificity = Math.max(...matches.map((entry) => globSpecificity(entry.glob)));
|
|
17282
|
+
const mostSpecific = matches.filter((entry) => globSpecificity(entry.glob) === highestSpecificity);
|
|
17283
|
+
if (mostSpecific.length > 1) {
|
|
17284
|
+
throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${mostSpecific.map((entry) => entry.name).join(", ")}`);
|
|
17285
|
+
}
|
|
17286
|
+
return mostSpecific[0];
|
|
17287
|
+
};
|
|
17288
|
+
var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
|
|
17289
|
+
const result = readWorkspacesResult(dir);
|
|
17290
|
+
if (result.status === "malformed" || result.status === "invalid")
|
|
17291
|
+
throw new Error(result.error);
|
|
16898
17292
|
const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
|
|
16899
|
-
|
|
16900
|
-
|
|
16901
|
-
continue;
|
|
16902
|
-
const ws = entry;
|
|
16903
|
-
if (typeof ws.glob !== "string" || !ws.glob)
|
|
16904
|
-
continue;
|
|
17293
|
+
const matches = [];
|
|
17294
|
+
for (const ws of result.entries) {
|
|
16905
17295
|
const glob = ws.glob.replaceAll("\\", "/");
|
|
16906
17296
|
const canonical = canonicalGlob(glob);
|
|
17297
|
+
let matched = false;
|
|
16907
17298
|
for (const target of targets) {
|
|
16908
|
-
if (matchWorkspace(glob, target))
|
|
16909
|
-
|
|
16910
|
-
|
|
16911
|
-
|
|
17299
|
+
if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
|
|
17300
|
+
matched = true;
|
|
17301
|
+
break;
|
|
17302
|
+
}
|
|
16912
17303
|
}
|
|
17304
|
+
if (matched)
|
|
17305
|
+
matches.push(ws);
|
|
16913
17306
|
}
|
|
16914
|
-
return null;
|
|
17307
|
+
return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
|
|
17308
|
+
};
|
|
17309
|
+
var runtimeWorkspacePolicySchema = (kind) => {
|
|
17310
|
+
const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
|
|
17311
|
+
const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object({
|
|
17312
|
+
provider: _enum(["gitlab", "github"]),
|
|
17313
|
+
defaultTargetBranch: nonBlank.optional(),
|
|
17314
|
+
account: nonBlank.optional()
|
|
17315
|
+
}).passthrough();
|
|
17316
|
+
const profileSchema = object({ [key]: policySchema.optional() }).passthrough();
|
|
17317
|
+
const entrySchema = object({
|
|
17318
|
+
name: nonBlank,
|
|
17319
|
+
glob: nonBlank,
|
|
17320
|
+
[key]: policySchema.optional(),
|
|
17321
|
+
defaultProfile: nonBlank.optional(),
|
|
17322
|
+
profiles: record(nonBlank, profileSchema).optional()
|
|
17323
|
+
}).passthrough();
|
|
17324
|
+
return entrySchema;
|
|
17325
|
+
};
|
|
17326
|
+
var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs");
|
|
17327
|
+
var runtimeWorkspaceIndexSchema = object({
|
|
17328
|
+
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
17329
|
+
}).passthrough();
|
|
17330
|
+
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
17331
|
+
const file = path6.join(configDir(), "workspaces.json");
|
|
17332
|
+
let raw;
|
|
17333
|
+
try {
|
|
17334
|
+
raw = readFileSync3(file, "utf8");
|
|
17335
|
+
} catch (error) {
|
|
17336
|
+
if (error.code === "ENOENT")
|
|
17337
|
+
return null;
|
|
17338
|
+
throw new Error(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
17339
|
+
}
|
|
17340
|
+
let parsed;
|
|
17341
|
+
try {
|
|
17342
|
+
parsed = JSON.parse(raw);
|
|
17343
|
+
} catch {
|
|
17344
|
+
throw new Error(`${file} is not valid JSON`);
|
|
17345
|
+
}
|
|
17346
|
+
const validated = runtimeWorkspaceIndexSchema.safeParse(parsed);
|
|
17347
|
+
if (!validated.success) {
|
|
17348
|
+
const detail = validated.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}`).join("; ");
|
|
17349
|
+
throw new Error(`${file} has invalid workspace matching data: ${detail}`);
|
|
17350
|
+
}
|
|
17351
|
+
const entries = validated.data.workspaces;
|
|
17352
|
+
for (const entry of entries ?? []) {
|
|
17353
|
+
const glob = validateWorkspaceGlob(entry.glob);
|
|
17354
|
+
if (!glob.ok)
|
|
17355
|
+
throw new Error(`${file} workspace ${entry.name}: ${glob.error}`);
|
|
17356
|
+
}
|
|
17357
|
+
const targets = [cwd, realpathOf(cwd)].map((value) => value.replaceAll("\\", "/"));
|
|
17358
|
+
const matches = (entries ?? []).filter((entry) => {
|
|
17359
|
+
const glob = entry.glob.replaceAll("\\", "/");
|
|
17360
|
+
const canonical = canonicalGlob(glob);
|
|
17361
|
+
return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
|
|
17362
|
+
});
|
|
17363
|
+
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
17364
|
+
const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
|
|
17365
|
+
if (!selected)
|
|
17366
|
+
return null;
|
|
17367
|
+
const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
|
|
17368
|
+
if (!policy.success) {
|
|
17369
|
+
const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
|
|
17370
|
+
throw new Error(`${file} has invalid ${kind} policy configuration: ${detail}`);
|
|
17371
|
+
}
|
|
17372
|
+
return policy.data;
|
|
17373
|
+
};
|
|
17374
|
+
var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
|
|
17375
|
+
const candidate = resolveRuntimeWorkspaceCandidate(cwd, kind);
|
|
17376
|
+
if (!candidate)
|
|
17377
|
+
return { policy: undefined, source: "user-default" };
|
|
17378
|
+
const key = kind === "branch" ? "branchPolicy" : "commitPolicy";
|
|
17379
|
+
const profileName = requestedProfile?.trim() || candidate.defaultProfile;
|
|
17380
|
+
if (candidate.defaultProfile && !Object.hasOwn(candidate.profiles ?? {}, candidate.defaultProfile))
|
|
17381
|
+
throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
|
|
17382
|
+
const profile = profileName ? candidate.profiles?.[profileName] : undefined;
|
|
17383
|
+
if (profileName && !profile)
|
|
17384
|
+
throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
|
|
17385
|
+
const profilePolicy = profile?.[key];
|
|
17386
|
+
const workspacePolicy = candidate[key];
|
|
17387
|
+
return {
|
|
17388
|
+
policy: profilePolicy ?? workspacePolicy,
|
|
17389
|
+
source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
|
|
17390
|
+
};
|
|
16915
17391
|
};
|
|
16916
|
-
var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
|
|
16917
|
-
|
|
16918
|
-
// packages/workit-core/src/core/branch.ts
|
|
16919
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
16920
17392
|
|
|
16921
17393
|
// packages/workit-core/src/core/git.ts
|
|
16922
17394
|
import { execFileSync } from "node:child_process";
|
|
@@ -16990,7 +17462,6 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
16990
17462
|
import fs5 from "node:fs";
|
|
16991
17463
|
import path7 from "node:path";
|
|
16992
17464
|
import { spawnSync } from "node:child_process";
|
|
16993
|
-
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
16994
17465
|
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
|
|
16995
17466
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
16996
17467
|
var remoteProvider = (cwd) => {
|
|
@@ -17004,6 +17475,79 @@ var remoteProvider = (cwd) => {
|
|
|
17004
17475
|
return "gitlab";
|
|
17005
17476
|
return null;
|
|
17006
17477
|
};
|
|
17478
|
+
var remoteEndpoint = (remote) => {
|
|
17479
|
+
const raw = remote.trim();
|
|
17480
|
+
if (!raw)
|
|
17481
|
+
return null;
|
|
17482
|
+
const scp = /^(?:[^@\s]+@)?([^:]+):(.+)$/u.exec(raw);
|
|
17483
|
+
if (scp && !raw.includes("://"))
|
|
17484
|
+
return {
|
|
17485
|
+
host: scp[1].toLowerCase(),
|
|
17486
|
+
path: scp[2].replace(/^\//u, "").replace(/\.git$/u, ""),
|
|
17487
|
+
protocol: "ssh:",
|
|
17488
|
+
port: ""
|
|
17489
|
+
};
|
|
17490
|
+
try {
|
|
17491
|
+
const url = new URL(raw);
|
|
17492
|
+
return {
|
|
17493
|
+
host: url.hostname.toLowerCase(),
|
|
17494
|
+
path: url.pathname.replace(/^\//u, "").replace(/\.git$/u, "").replace(/\/$/u, ""),
|
|
17495
|
+
protocol: url.protocol,
|
|
17496
|
+
port: url.port
|
|
17497
|
+
};
|
|
17498
|
+
} catch {
|
|
17499
|
+
return null;
|
|
17500
|
+
}
|
|
17501
|
+
};
|
|
17502
|
+
var configuredSshEndpoint = (host) => {
|
|
17503
|
+
if (host.startsWith("-"))
|
|
17504
|
+
return null;
|
|
17505
|
+
const resolved = spawnSync("ssh", ["-G", host], {
|
|
17506
|
+
encoding: "utf8",
|
|
17507
|
+
timeout: 5000,
|
|
17508
|
+
maxBuffer: 1024 * 1024,
|
|
17509
|
+
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
17510
|
+
});
|
|
17511
|
+
if (resolved.status !== 0)
|
|
17512
|
+
return null;
|
|
17513
|
+
const output = resolved.stdout ?? "";
|
|
17514
|
+
const hostname = /^hostname\s+(\S+)/imu.exec(output)?.[1]?.toLowerCase();
|
|
17515
|
+
if (!hostname)
|
|
17516
|
+
return null;
|
|
17517
|
+
return { host: hostname, port: /^port\s+(\d+)/imu.exec(output)?.[1] ?? "22" };
|
|
17518
|
+
};
|
|
17519
|
+
var sshApiHostAliases = {
|
|
17520
|
+
"github.com": [{ host: "ssh.github.com", port: "443" }],
|
|
17521
|
+
"gitlab.com": [{ host: "altssh.gitlab.com", port: "443" }]
|
|
17522
|
+
};
|
|
17523
|
+
var hostingApiHostMatches = (remote, apiHost, resolveSshEndpoint = configuredSshEndpoint) => {
|
|
17524
|
+
const endpoint = remoteEndpoint(remote);
|
|
17525
|
+
if (!endpoint)
|
|
17526
|
+
return false;
|
|
17527
|
+
let expected;
|
|
17528
|
+
try {
|
|
17529
|
+
const url = new URL(`https://${apiHost}`);
|
|
17530
|
+
if (url.pathname !== "/" || url.search || url.hash || url.username || url.password)
|
|
17531
|
+
return false;
|
|
17532
|
+
expected = {
|
|
17533
|
+
host: url.hostname.toLowerCase().replace(/\.$/u, ""),
|
|
17534
|
+
port: url.port
|
|
17535
|
+
};
|
|
17536
|
+
} catch {
|
|
17537
|
+
return false;
|
|
17538
|
+
}
|
|
17539
|
+
if (endpoint.protocol !== "ssh:") {
|
|
17540
|
+
return endpoint.host.replace(/\.$/u, "") === expected.host && endpoint.port === expected.port;
|
|
17541
|
+
}
|
|
17542
|
+
const resolved = resolveSshEndpoint(endpoint.host);
|
|
17543
|
+
if (!resolved)
|
|
17544
|
+
return false;
|
|
17545
|
+
const hostname = resolved.host.toLowerCase().replace(/\.$/u, "");
|
|
17546
|
+
const port = endpoint.port || resolved.port;
|
|
17547
|
+
if (hostname === expected.host)
|
|
17548
|
+
return port === (expected.port || "22");
|
|
17549
|
+
return Boolean(sshApiHostAliases[expected.host]?.some((alias) => alias.host === hostname && alias.port === port && (!expected.port || expected.port === port)));
|
|
17550
|
+
};
|
|
17007
17551
|
var readVcsConfig = () => {
|
|
17008
17552
|
const file = vcsConfigPath();
|
|
17009
17553
|
let raw;
|
|
@@ -17024,7 +17568,7 @@ var readVcsConfig = () => {
|
|
|
17024
17568
|
return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
|
|
17025
17569
|
};
|
|
17026
17570
|
function vcsConfig(mode, cwd) {
|
|
17027
|
-
const ws =
|
|
17571
|
+
const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
|
|
17028
17572
|
const wsVcs = ws?.vcs ?? {};
|
|
17029
17573
|
const wsYt = ws?.youtrack ?? {};
|
|
17030
17574
|
const wsIssues = ws?.issues ?? {};
|
|
@@ -17037,8 +17581,8 @@ function vcsConfig(mode, cwd) {
|
|
|
17037
17581
|
const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
|
|
17038
17582
|
const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
|
|
17039
17583
|
const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
|
|
17040
|
-
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
17041
|
-
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
17584
|
+
const linkIssues = provider === "gitlab" && typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
17585
|
+
const youtrackBaseUrl = provider === "gitlab" && typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
17042
17586
|
let issuesProvider = null;
|
|
17043
17587
|
let linkOnPr = null;
|
|
17044
17588
|
if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
|
|
@@ -17062,9 +17606,8 @@ function vcsConfig(mode, cwd) {
|
|
|
17062
17606
|
if (cfgStatus === "malformed") {
|
|
17063
17607
|
return { ok: false, error: cfgError, configPath: cfgPath };
|
|
17064
17608
|
}
|
|
17065
|
-
if (cfgStatus === "missing")
|
|
17609
|
+
if (cfgStatus === "missing" && provider === null)
|
|
17066
17610
|
return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
|
|
17067
|
-
}
|
|
17068
17611
|
if (provider === null) {
|
|
17069
17612
|
return {
|
|
17070
17613
|
ok: false,
|
|
@@ -17073,24 +17616,12 @@ function vcsConfig(mode, cwd) {
|
|
|
17073
17616
|
};
|
|
17074
17617
|
}
|
|
17075
17618
|
const prov = cfg[provider] ?? {};
|
|
17076
|
-
const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
|
|
17077
|
-
const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path7.join(configDir(), `${provider}.token`));
|
|
17078
|
-
const tokenPath = path7.resolve(tokenFile);
|
|
17079
|
-
let tokenOk = false;
|
|
17080
|
-
if (fs5.existsSync(tokenPath)) {
|
|
17081
|
-
const token = fs5.readFileSync(tokenPath, "utf8").trim();
|
|
17082
|
-
const placeholder = !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
|
|
17083
|
-
tokenOk = !placeholder;
|
|
17084
|
-
}
|
|
17085
17619
|
const out = {
|
|
17086
17620
|
ok: true,
|
|
17087
17621
|
configPath: path7.resolve(cfgPath),
|
|
17088
17622
|
provider,
|
|
17089
17623
|
defaultTargetBranch: defaultTarget,
|
|
17090
17624
|
pr: cfg.pr ?? {},
|
|
17091
|
-
tokenPath,
|
|
17092
|
-
tokenPresent: fs5.existsSync(tokenPath),
|
|
17093
|
-
tokenReady: tokenOk,
|
|
17094
17625
|
workspace_name: ws?.name ?? null,
|
|
17095
17626
|
link_issues: linkIssues,
|
|
17096
17627
|
youtrack_base_url: youtrackBaseUrl,
|
|
@@ -17105,16 +17636,58 @@ function vcsConfig(mode, cwd) {
|
|
|
17105
17636
|
} else if (provider === "github") {
|
|
17106
17637
|
out.github = { host: prov.host ?? "github.com" };
|
|
17107
17638
|
}
|
|
17108
|
-
if (mode === "summary")
|
|
17109
|
-
delete out.tokenPath;
|
|
17110
17639
|
return out;
|
|
17111
17640
|
}
|
|
17641
|
+
function vcsCliIdentity(cwd) {
|
|
17642
|
+
const root = vcsCwd(cwd);
|
|
17643
|
+
const cfg = vcsConfig("load", root);
|
|
17644
|
+
if (!cfg.ok)
|
|
17645
|
+
return { ok: false, error: cfg.error ?? "vcs config not ready" };
|
|
17646
|
+
const provider = cfg.provider;
|
|
17647
|
+
if (provider !== "gitlab" && provider !== "github")
|
|
17648
|
+
return { ok: false, error: `unsupported provider: ${provider}` };
|
|
17649
|
+
const bin = provider === "gitlab" ? "glab" : "gh";
|
|
17650
|
+
const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
|
|
17651
|
+
const env = {
|
|
17652
|
+
...process.env,
|
|
17653
|
+
PATH: process.env.PATH ?? "",
|
|
17654
|
+
...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
|
|
17655
|
+
};
|
|
17656
|
+
const result = spawnSync(bin, ["api", "user"], {
|
|
17657
|
+
cwd: root,
|
|
17658
|
+
encoding: "utf8",
|
|
17659
|
+
env
|
|
17660
|
+
});
|
|
17661
|
+
if (result.status !== 0)
|
|
17662
|
+
return { ok: false, provider, error: `${bin} is missing or not authenticated` };
|
|
17663
|
+
try {
|
|
17664
|
+
const user = JSON.parse(result.stdout ?? "");
|
|
17665
|
+
const username = provider === "gitlab" ? user.username : user.login;
|
|
17666
|
+
if (typeof username !== "string" || !username)
|
|
17667
|
+
return { ok: false, provider, error: `invalid ${bin} identity response` };
|
|
17668
|
+
const expected = resolveRuntimeWorkspaceVcs(root)?.vcs?.account;
|
|
17669
|
+
if (expected && username.toLowerCase() !== expected.toLowerCase())
|
|
17670
|
+
return {
|
|
17671
|
+
ok: false,
|
|
17672
|
+
provider,
|
|
17673
|
+
error: `${bin} account ${username} does not match ${expected}`
|
|
17674
|
+
};
|
|
17675
|
+
return { ok: true, provider, host, username, name: user.name };
|
|
17676
|
+
} catch {
|
|
17677
|
+
return { ok: false, provider, error: `invalid JSON from ${bin} api user` };
|
|
17678
|
+
}
|
|
17679
|
+
}
|
|
17112
17680
|
function mergedPrStyle(limit = 6, cwd) {
|
|
17113
17681
|
const cfg = vcsConfig("load", cwd);
|
|
17114
|
-
if (!cfg.ok
|
|
17682
|
+
if (!cfg.ok)
|
|
17115
17683
|
return { ok: false, error: "vcs not configured" };
|
|
17116
17684
|
const provider = cfg.provider;
|
|
17117
|
-
const
|
|
17685
|
+
const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
|
|
17686
|
+
const env = {
|
|
17687
|
+
...process.env,
|
|
17688
|
+
PATH: process.env.PATH ?? "",
|
|
17689
|
+
...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
|
|
17690
|
+
};
|
|
17118
17691
|
const examples = [];
|
|
17119
17692
|
const descInfo = (desc, caseInsensitiveNotes = false) => ({
|
|
17120
17693
|
hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
|
|
@@ -17122,23 +17695,30 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
17122
17695
|
`).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
|
|
17123
17696
|
descriptionPreview: desc.slice(0, 600)
|
|
17124
17697
|
});
|
|
17698
|
+
const remoteResult = spawnSync("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
17699
|
+
cwd,
|
|
17700
|
+
encoding: "utf8"
|
|
17701
|
+
});
|
|
17702
|
+
const remotes = (remoteResult.stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
|
|
17703
|
+
if (remotes.length === 0)
|
|
17704
|
+
return { ok: false, error: "no origin remote" };
|
|
17705
|
+
if (remotes.length !== 1)
|
|
17706
|
+
return { ok: false, error: "origin has ambiguous push destinations" };
|
|
17707
|
+
const remote = remotes[0];
|
|
17708
|
+
const endpoint = remoteEndpoint(remote);
|
|
17709
|
+
if (!endpoint?.path)
|
|
17710
|
+
return { ok: false, error: "hosting repository could not be resolved" };
|
|
17711
|
+
if (!hostingApiHostMatches(remote, host))
|
|
17712
|
+
return { ok: false, error: "PR style target does not match the provider host" };
|
|
17125
17713
|
if (provider === "gitlab") {
|
|
17126
|
-
const
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
if (!m)
|
|
17132
|
-
return { ok: false, error: "not a gitlab.com origin" };
|
|
17133
|
-
const project = m[1];
|
|
17134
|
-
const env = { ...process.env, GITLAB_TOKEN: token };
|
|
17135
|
-
const run = (args) => spawnSync("glab", ["api", ...args], { cwd, encoding: "utf8", env });
|
|
17714
|
+
const run = (args) => spawnSync("glab", ["api", ...args], {
|
|
17715
|
+
cwd,
|
|
17716
|
+
encoding: "utf8",
|
|
17717
|
+
env
|
|
17718
|
+
});
|
|
17136
17719
|
let r = run([
|
|
17137
|
-
`projects/${
|
|
17720
|
+
`projects/${endpoint.path.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
|
|
17138
17721
|
]);
|
|
17139
|
-
if (r.status !== 0) {
|
|
17140
|
-
r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
|
|
17141
|
-
}
|
|
17142
17722
|
if (r.status !== 0)
|
|
17143
17723
|
return { ok: false, error: "could not list merge requests" };
|
|
17144
17724
|
for (const mr of JSON.parse(r.stdout ?? "[]")) {
|
|
@@ -17151,7 +17731,18 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
17151
17731
|
});
|
|
17152
17732
|
}
|
|
17153
17733
|
} else if (provider === "github") {
|
|
17154
|
-
const r = spawnSync("gh", [
|
|
17734
|
+
const r = spawnSync("gh", [
|
|
17735
|
+
"pr",
|
|
17736
|
+
"list",
|
|
17737
|
+
"--repo",
|
|
17738
|
+
`${host}/${endpoint.path}`,
|
|
17739
|
+
"--state",
|
|
17740
|
+
"merged",
|
|
17741
|
+
"--limit",
|
|
17742
|
+
String(limit),
|
|
17743
|
+
"--json",
|
|
17744
|
+
"title,url,body"
|
|
17745
|
+
], { cwd, encoding: "utf8", env });
|
|
17155
17746
|
if (r.status !== 0)
|
|
17156
17747
|
return { ok: false, error: "could not list pull requests" };
|
|
17157
17748
|
for (const pr of JSON.parse(r.stdout ?? "[]")) {
|
|
@@ -17173,26 +17764,28 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
17173
17764
|
}
|
|
17174
17765
|
|
|
17175
17766
|
// packages/workit-core/src/core/branch.ts
|
|
17176
|
-
|
|
17767
|
+
function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
17768
|
+
const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
17769
|
+
const config = readConfig();
|
|
17770
|
+
return kind === "branch" ? {
|
|
17771
|
+
branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
|
|
17772
|
+
provenance: { branchPolicy: selected.source }
|
|
17773
|
+
} : {
|
|
17774
|
+
commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
|
|
17775
|
+
provenance: { commitPolicy: selected.source }
|
|
17776
|
+
};
|
|
17777
|
+
}
|
|
17778
|
+
var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
|
|
17177
17779
|
var verifyPushIdentity = (root, provider, account) => {
|
|
17178
17780
|
if (!account || typeof account !== "string")
|
|
17179
17781
|
return { ok: false, error: "push identity requires a configured area account" };
|
|
17180
|
-
const
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
login = execFileSync2(bin, [...probe], {
|
|
17185
|
-
cwd: root,
|
|
17186
|
-
encoding: "utf8",
|
|
17187
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
17188
|
-
}).trim();
|
|
17189
|
-
} catch {
|
|
17190
|
-
return { ok: false, error: `${bin} identity could not be resolved` };
|
|
17191
|
-
}
|
|
17192
|
-
if (!login || login.toLowerCase() !== account.toLowerCase())
|
|
17782
|
+
const identity = vcsCliIdentity(root);
|
|
17783
|
+
if (!identity.ok || identity.provider !== provider)
|
|
17784
|
+
return { ok: false, error: identity.error ?? "hosting CLI identity could not be resolved" };
|
|
17785
|
+
if (identity.username?.toLowerCase() !== account.toLowerCase())
|
|
17193
17786
|
return {
|
|
17194
17787
|
ok: false,
|
|
17195
|
-
error: `push identity ${
|
|
17788
|
+
error: `push identity ${identity.username || "(unknown)"} does not match area account ${account}`
|
|
17196
17789
|
};
|
|
17197
17790
|
return { ok: true };
|
|
17198
17791
|
};
|
|
@@ -17210,7 +17803,7 @@ var operationAutoClass = (operation) => typeof operation === "string" ? OPERATIO
|
|
|
17210
17803
|
var digestOf = (configDirPath) => {
|
|
17211
17804
|
try {
|
|
17212
17805
|
const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
|
|
17213
|
-
return
|
|
17806
|
+
return createHash4("sha256").update(raw).digest("hex");
|
|
17214
17807
|
} catch {
|
|
17215
17808
|
return null;
|
|
17216
17809
|
}
|
|
@@ -17248,14 +17841,30 @@ var resolveAutoApproval = (root, dir = configDir()) => {
|
|
|
17248
17841
|
};
|
|
17249
17842
|
};
|
|
17250
17843
|
var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
|
|
17251
|
-
var
|
|
17844
|
+
var targetOf = (root, descriptor) => {
|
|
17845
|
+
try {
|
|
17846
|
+
const parsed = typeof descriptor === "string" ? JSON.parse(descriptor) : descriptor;
|
|
17847
|
+
const cwd = parsed?.payload?.cwd;
|
|
17848
|
+
if (cwd === undefined)
|
|
17849
|
+
return root;
|
|
17850
|
+
if (typeof cwd !== "string" || !path8.isAbsolute(cwd))
|
|
17851
|
+
return null;
|
|
17852
|
+
return realpathSync3(cwd);
|
|
17853
|
+
} catch {
|
|
17854
|
+
return null;
|
|
17855
|
+
}
|
|
17856
|
+
};
|
|
17857
|
+
var standingApprovalLive = (root, receipt, cls, dir, descriptor) => {
|
|
17252
17858
|
if (!isStandingReceipt(receipt))
|
|
17253
17859
|
return false;
|
|
17254
17860
|
if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
|
|
17255
17861
|
return false;
|
|
17256
17862
|
if (receipt.class !== cls)
|
|
17257
17863
|
return false;
|
|
17258
|
-
const
|
|
17864
|
+
const target = targetOf(root, descriptor);
|
|
17865
|
+
if (!target)
|
|
17866
|
+
return false;
|
|
17867
|
+
const resolved = resolveAutoApproval(target, dir);
|
|
17259
17868
|
if (resolved.status !== "on")
|
|
17260
17869
|
return false;
|
|
17261
17870
|
if (resolved.workspace !== receipt.workspace)
|
|
@@ -17291,16 +17900,19 @@ var verifyStandingApproval = (root, task, caller, binding) => {
|
|
|
17291
17900
|
const cls = operationAutoClass(operation);
|
|
17292
17901
|
if (!cls || cls !== standing.class)
|
|
17293
17902
|
return failure("permission_denied", "standing approval does not cover this operation");
|
|
17294
|
-
const
|
|
17903
|
+
const target = targetOf(root, binding.approvedContent);
|
|
17904
|
+
if (!target)
|
|
17905
|
+
return failure("permission_denied", "standing action target is invalid");
|
|
17906
|
+
const resolved = resolveAutoApproval(target);
|
|
17295
17907
|
if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
|
|
17296
17908
|
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
17297
17909
|
if (cls === "push") {
|
|
17298
|
-
const workspace = resolveWorkspaceFrom(
|
|
17910
|
+
const workspace = resolveWorkspaceFrom(target, configDir());
|
|
17299
17911
|
const provider = workspace?.vcs?.provider;
|
|
17300
17912
|
const account = resolved.account;
|
|
17301
17913
|
if (!provider || !account)
|
|
17302
17914
|
return failure("permission_denied", "auto-push requires a workspace provider and area account");
|
|
17303
|
-
const identity = verifyPushIdentity(
|
|
17915
|
+
const identity = verifyPushIdentity(target, provider, account);
|
|
17304
17916
|
if (!identity.ok)
|
|
17305
17917
|
return failure("permission_denied", identity.error);
|
|
17306
17918
|
}
|
|
@@ -17309,7 +17921,7 @@ var verifyStandingApproval = (root, task, caller, binding) => {
|
|
|
17309
17921
|
host: caller.host,
|
|
17310
17922
|
session: { kind: "host", host: caller.host, handle: caller.actor },
|
|
17311
17923
|
workerId: null,
|
|
17312
|
-
receipts: [standingReceiptFor(
|
|
17924
|
+
receipts: [standingReceiptFor(target, standing.workspace, cls)]
|
|
17313
17925
|
});
|
|
17314
17926
|
};
|
|
17315
17927
|
|
|
@@ -17460,7 +18072,7 @@ function applicableDecision(task, purpose, binding, checkoutRoot) {
|
|
|
17460
18072
|
return [];
|
|
17461
18073
|
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);
|
|
17462
18074
|
}
|
|
17463
|
-
var digestBytes2 = (bytes) =>
|
|
18075
|
+
var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
17464
18076
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
17465
18077
|
let root;
|
|
17466
18078
|
try {
|
|
@@ -17473,23 +18085,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
17473
18085
|
continue;
|
|
17474
18086
|
if (!reference.digest)
|
|
17475
18087
|
return failure("invalid_input", "document references require a byte digest");
|
|
17476
|
-
const target =
|
|
17477
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
18088
|
+
const target = path9.resolve(root, reference.path);
|
|
18089
|
+
if (target !== root && !target.startsWith(`${root}${path9.sep}`))
|
|
17478
18090
|
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
17479
18091
|
fields: [
|
|
17480
18092
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
17481
18093
|
]
|
|
17482
18094
|
});
|
|
17483
18095
|
try {
|
|
17484
|
-
const relative =
|
|
18096
|
+
const relative = path9.relative(root, target);
|
|
17485
18097
|
let current = root;
|
|
17486
|
-
for (const segment of relative.split(
|
|
18098
|
+
for (const segment of relative.split(path9.sep)) {
|
|
17487
18099
|
if (!segment)
|
|
17488
18100
|
continue;
|
|
17489
|
-
current =
|
|
18101
|
+
current = path9.join(current, segment);
|
|
17490
18102
|
const stat = fs6.lstatSync(current);
|
|
17491
18103
|
const real = fs6.realpathSync(current);
|
|
17492
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
18104
|
+
if (real !== root && !real.startsWith(`${root}${path9.sep}`))
|
|
17493
18105
|
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
17494
18106
|
fields: [
|
|
17495
18107
|
{
|
|
@@ -17567,7 +18179,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
|
|
|
17567
18179
|
operation = null;
|
|
17568
18180
|
}
|
|
17569
18181
|
const cls = operationAutoClass(operation);
|
|
17570
|
-
if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
|
|
18182
|
+
if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls, undefined, decision.binding.approvedContent)))
|
|
17571
18183
|
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
17572
18184
|
}
|
|
17573
18185
|
if (decision.revoked)
|
|
@@ -17869,15 +18481,15 @@ var bindingCovers = scopeCovers;
|
|
|
17869
18481
|
var verifyDecisionContent = verifyContentRefs;
|
|
17870
18482
|
|
|
17871
18483
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
17872
|
-
var digestBytes3 = (value) =>
|
|
17873
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
17874
|
-
var relative = (root, candidate) =>
|
|
18484
|
+
var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
18485
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
|
|
18486
|
+
var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
|
|
17875
18487
|
var canonicalPath = (value) => {
|
|
17876
18488
|
if (typeof value !== "string" || !value)
|
|
17877
18489
|
return null;
|
|
17878
18490
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
17879
18491
|
return null;
|
|
17880
|
-
const normalized =
|
|
18492
|
+
const normalized = path10.posix.normalize(value);
|
|
17881
18493
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
17882
18494
|
return null;
|
|
17883
18495
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -17925,14 +18537,14 @@ var gitPaths = (root) => {
|
|
|
17925
18537
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
17926
18538
|
};
|
|
17927
18539
|
}
|
|
17928
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
18540
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
|
|
17929
18541
|
const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
17930
18542
|
if (stagedDeleted.status !== 0) {
|
|
17931
18543
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
17932
18544
|
if (!/not a git repository/i.test(stderr))
|
|
17933
18545
|
return { paths, uncertain: true, git: true };
|
|
17934
18546
|
} else if (stagedDeleted.stdout) {
|
|
17935
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
18547
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
|
|
17936
18548
|
}
|
|
17937
18549
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
17938
18550
|
};
|
|
@@ -17947,7 +18559,7 @@ var walk = (root, directory, output) => {
|
|
|
17947
18559
|
for (const name of names) {
|
|
17948
18560
|
if (name === ".git" || name === ".workit")
|
|
17949
18561
|
continue;
|
|
17950
|
-
const target =
|
|
18562
|
+
const target = path10.join(directory, name);
|
|
17951
18563
|
let stat;
|
|
17952
18564
|
try {
|
|
17953
18565
|
stat = fs7.lstatSync(target);
|
|
@@ -17965,12 +18577,12 @@ var walk = (root, directory, output) => {
|
|
|
17965
18577
|
var scopeRoots = (root, scope) => {
|
|
17966
18578
|
const roots = [];
|
|
17967
18579
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
17968
|
-
const target =
|
|
18580
|
+
const target = path10.resolve(root, item);
|
|
17969
18581
|
if (!inside(root, target))
|
|
17970
18582
|
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` }] });
|
|
17971
18583
|
let ancestor = target;
|
|
17972
18584
|
while (!fs7.existsSync(ancestor) && ancestor !== root)
|
|
17973
|
-
ancestor =
|
|
18585
|
+
ancestor = path10.dirname(ancestor);
|
|
17974
18586
|
try {
|
|
17975
18587
|
if (!inside(root, fs7.realpathSync(ancestor)))
|
|
17976
18588
|
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` }] });
|
|
@@ -18022,7 +18634,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
18022
18634
|
for (const item of [...names].sort()) {
|
|
18023
18635
|
if (!scopeMatches(item, normalizedScope))
|
|
18024
18636
|
continue;
|
|
18025
|
-
const target =
|
|
18637
|
+
const target = path10.join(checkout, item);
|
|
18026
18638
|
try {
|
|
18027
18639
|
const stat = fs7.lstatSync(target);
|
|
18028
18640
|
if (stat.isSymbolicLink()) {
|
|
@@ -18404,7 +19016,8 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
18404
19016
|
action: _enum(["setup", "reapply_stash"]).optional(),
|
|
18405
19017
|
sdd_dir: string2().optional(),
|
|
18406
19018
|
target_branch: string2().optional(),
|
|
18407
|
-
stash: _enum(["yes", "no"]).optional()
|
|
19019
|
+
stash: _enum(["yes", "no"]).optional(),
|
|
19020
|
+
cwd: string2().min(1).optional()
|
|
18408
19021
|
}).strict()
|
|
18409
19022
|
}).strict(),
|
|
18410
19023
|
object({
|
|
@@ -18412,12 +19025,13 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
18412
19025
|
payload: object({
|
|
18413
19026
|
message: string2().min(1).optional(),
|
|
18414
19027
|
plan_steps: array(chainStepSchema).min(1).max(32).optional(),
|
|
18415
|
-
plan_branch: string2().min(1).optional()
|
|
19028
|
+
plan_branch: string2().min(1).optional(),
|
|
19029
|
+
cwd: string2().min(1).optional()
|
|
18416
19030
|
}).strict()
|
|
18417
19031
|
}).strict(),
|
|
18418
19032
|
object({
|
|
18419
19033
|
operation: literal("git.push"),
|
|
18420
|
-
payload: object({ branch: string2().min(1).optional() }).strict()
|
|
19034
|
+
payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
|
|
18421
19035
|
}).strict(),
|
|
18422
19036
|
object({
|
|
18423
19037
|
operation: literal("hosting.pull_request"),
|
|
@@ -18426,16 +19040,22 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
18426
19040
|
body: string2().optional(),
|
|
18427
19041
|
draft: boolean2().optional(),
|
|
18428
19042
|
target_branch: string2().optional(),
|
|
18429
|
-
babysit: boolean2().optional()
|
|
19043
|
+
babysit: boolean2().optional(),
|
|
19044
|
+
cwd: string2().min(1).optional()
|
|
18430
19045
|
}).strict()
|
|
18431
19046
|
}).strict(),
|
|
18432
19047
|
object({
|
|
18433
19048
|
operation: literal("hosting.merge"),
|
|
18434
19049
|
payload: object({
|
|
18435
19050
|
target_branch: string2().min(1).optional(),
|
|
18436
|
-
source_branch: string2().min(1).optional()
|
|
19051
|
+
source_branch: string2().min(1).optional(),
|
|
19052
|
+
cwd: string2().min(1).optional()
|
|
18437
19053
|
}).strict()
|
|
18438
19054
|
}).strict(),
|
|
19055
|
+
object({
|
|
19056
|
+
operation: literal("hosting.delete_branch"),
|
|
19057
|
+
payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
|
|
19058
|
+
}).strict(),
|
|
18439
19059
|
object({
|
|
18440
19060
|
operation: literal("youtrack.update"),
|
|
18441
19061
|
payload: object({
|
|
@@ -18491,12 +19111,13 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
18491
19111
|
issueRef: string2().optional(),
|
|
18492
19112
|
mode: string2().optional(),
|
|
18493
19113
|
specPath: string2().optional(),
|
|
18494
|
-
planPath: string2().optional()
|
|
19114
|
+
planPath: string2().optional(),
|
|
19115
|
+
cwd: string2().min(1).optional()
|
|
18495
19116
|
}).strict()
|
|
18496
19117
|
}).strict()
|
|
18497
19118
|
]);
|
|
18498
19119
|
// packages/workit-core/src/core/task-engine.ts
|
|
18499
|
-
import { realpathSync as
|
|
19120
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
18500
19121
|
|
|
18501
19122
|
// packages/workit-core/src/core/policy-resolver.ts
|
|
18502
19123
|
var resolverInputSchema = object({
|
|
@@ -18669,6 +19290,7 @@ function resolveRequirements(input) {
|
|
|
18669
19290
|
dependentAction: null,
|
|
18670
19291
|
acceptanceAllowed: false
|
|
18671
19292
|
}));
|
|
19293
|
+
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
18672
19294
|
if (signals.behaviorChange.value === true)
|
|
18673
19295
|
requirements.push(requirement({
|
|
18674
19296
|
ruleId: "behavioral-verification",
|
|
@@ -18680,14 +19302,14 @@ function resolveRequirements(input) {
|
|
|
18680
19302
|
dependentAction: null,
|
|
18681
19303
|
acceptanceAllowed: false
|
|
18682
19304
|
}), requirement({
|
|
18683
|
-
ruleId: "fresh-context-review",
|
|
19305
|
+
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
18684
19306
|
dimension: "review",
|
|
18685
19307
|
scope,
|
|
18686
|
-
reason: "
|
|
18687
|
-
satisfaction: "A separate review context examines the current candidate and records its result.",
|
|
19308
|
+
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.",
|
|
19309
|
+
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.",
|
|
18688
19310
|
before: "close",
|
|
18689
19311
|
dependentAction: null,
|
|
18690
|
-
acceptanceAllowed:
|
|
19312
|
+
acceptanceAllowed: !independentBehaviorReview
|
|
18691
19313
|
}));
|
|
18692
19314
|
else if (signals.mechanicalLowRisk.value === true)
|
|
18693
19315
|
requirements.push(requirement({
|
|
@@ -19041,6 +19663,18 @@ var portableTask = (task) => {
|
|
|
19041
19663
|
};
|
|
19042
19664
|
for (const entry of clone.assessments)
|
|
19043
19665
|
demote(entry.data.facts, entry.data.signals, entry.data.consequences);
|
|
19666
|
+
const sourceDecisions = new Map(task.decisions.map((entry) => [entry.id, entry]));
|
|
19667
|
+
for (const entry of clone.decisions) {
|
|
19668
|
+
const consumption = sourceDecisions.get(entry.id)?.data.consumption;
|
|
19669
|
+
if (!consumption)
|
|
19670
|
+
continue;
|
|
19671
|
+
entry.data.consumption = {
|
|
19672
|
+
...consumption,
|
|
19673
|
+
state: consumption.state === "reserved" ? "uncertain" : consumption.state,
|
|
19674
|
+
actionRef: { kind: "record", collection: "decisions", id: entry.id },
|
|
19675
|
+
...consumption.historicalActionId ? { historicalActionId: consumption.historicalActionId } : consumption.actionRef.kind === "host" ? { historicalActionId: consumption.actionRef.handle } : {}
|
|
19676
|
+
};
|
|
19677
|
+
}
|
|
19044
19678
|
clone.candidates = [];
|
|
19045
19679
|
return clone;
|
|
19046
19680
|
};
|
|
@@ -19166,7 +19800,11 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
19166
19800
|
taskId: "",
|
|
19167
19801
|
workspaceId: destinationWorkspaceId
|
|
19168
19802
|
},
|
|
19169
|
-
consumption:
|
|
19803
|
+
consumption: data.consumption ? {
|
|
19804
|
+
...data.consumption,
|
|
19805
|
+
state: data.consumption.state === "reserved" ? "uncertain" : data.consumption.state,
|
|
19806
|
+
actionRef: { kind: "record", collection: "decisions", id: entry.id }
|
|
19807
|
+
} : null
|
|
19170
19808
|
}
|
|
19171
19809
|
};
|
|
19172
19810
|
}),
|
|
@@ -19207,7 +19845,7 @@ class WorkitCore {
|
|
|
19207
19845
|
}
|
|
19208
19846
|
contextRootError() {
|
|
19209
19847
|
try {
|
|
19210
|
-
if (
|
|
19848
|
+
if (realpathSync6(this.context.root) !== this.store.root)
|
|
19211
19849
|
return failure("invalid_input", "operation context root does not match the task store root");
|
|
19212
19850
|
} catch {
|
|
19213
19851
|
return failure("invalid_input", "operation context root cannot be resolved");
|
|
@@ -19366,7 +20004,7 @@ class WorkitCore {
|
|
|
19366
20004
|
return parsed;
|
|
19367
20005
|
const input = parsed.data;
|
|
19368
20006
|
if ((this.context.workerId ?? null) !== null) {
|
|
19369
|
-
if (input.action !== "inspect")
|
|
20007
|
+
if (input.action !== "inspect" && input.action !== "list")
|
|
19370
20008
|
return failure("permission_denied", "helpers cannot control task lifecycle or scope");
|
|
19371
20009
|
}
|
|
19372
20010
|
switch (input.action) {
|
|
@@ -19393,7 +20031,18 @@ class WorkitCore {
|
|
|
19393
20031
|
return listed;
|
|
19394
20032
|
const status = input.status ?? "open";
|
|
19395
20033
|
const limit = input.limit ?? 20;
|
|
19396
|
-
const
|
|
20034
|
+
const query = input.query?.toLowerCase().split(/\s+/);
|
|
20035
|
+
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({
|
|
20036
|
+
id: task.id,
|
|
20037
|
+
workspaceId: task.workspaceId,
|
|
20038
|
+
origin: task.origin,
|
|
20039
|
+
createdAt: task.createdAt,
|
|
20040
|
+
updatedAt: task.updatedAt,
|
|
20041
|
+
source: task.intent.provenance,
|
|
20042
|
+
objective: task.intent.data.objective,
|
|
20043
|
+
progress: task.progress,
|
|
20044
|
+
decisions: task.decisions.map(({ data }) => data)
|
|
20045
|
+
}).toLowerCase().includes(term))).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit);
|
|
19397
20046
|
if (selected.length === 0)
|
|
19398
20047
|
return success(null, null, []);
|
|
19399
20048
|
const workspace = this.store.readWorkspace();
|
|
@@ -19412,7 +20061,11 @@ class WorkitCore {
|
|
|
19412
20061
|
status: task.status,
|
|
19413
20062
|
closure: task.closure,
|
|
19414
20063
|
progress: task.progress,
|
|
19415
|
-
writer: task.status === "closed" ? null : workspace.data.writer
|
|
20064
|
+
writer: task.status === "closed" ? null : workspace.data.writer,
|
|
20065
|
+
source: {
|
|
20066
|
+
host: task.intent.provenance.host,
|
|
20067
|
+
kind: task.intent.provenance.kind
|
|
20068
|
+
}
|
|
19416
20069
|
}));
|
|
19417
20070
|
return success(null, null, tasks);
|
|
19418
20071
|
}
|
|
@@ -20508,7 +21161,7 @@ class WorkitCore {
|
|
|
20508
21161
|
if (view.task.workspaceId !== view.workspace.id)
|
|
20509
21162
|
return failure("invalid_input", "task and workspace bindings are invalid");
|
|
20510
21163
|
try {
|
|
20511
|
-
if (
|
|
21164
|
+
if (realpathSync6(view.workspace.root) !== this.store.root)
|
|
20512
21165
|
return failure("invalid_input", "task view root does not match the task store");
|
|
20513
21166
|
} catch {
|
|
20514
21167
|
return failure("invalid_input", "task view root cannot be resolved");
|
|
@@ -20785,22 +21438,22 @@ function resolveGitRevision(root, value) {
|
|
|
20785
21438
|
}
|
|
20786
21439
|
}
|
|
20787
21440
|
function resolveInside(root, candidate) {
|
|
20788
|
-
const base =
|
|
20789
|
-
const target =
|
|
20790
|
-
if (target !== base && !target.startsWith(base +
|
|
21441
|
+
const base = realpathSync7(root);
|
|
21442
|
+
const target = path11.resolve(base, candidate);
|
|
21443
|
+
if (target !== base && !target.startsWith(base + path11.sep)) {
|
|
20791
21444
|
throw new Error("path must stay inside repository root");
|
|
20792
21445
|
}
|
|
20793
21446
|
let ancestor = target;
|
|
20794
21447
|
while (!existsSync5(ancestor))
|
|
20795
|
-
ancestor =
|
|
20796
|
-
const canonicalAncestor =
|
|
20797
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
21448
|
+
ancestor = path11.dirname(ancestor);
|
|
21449
|
+
const canonicalAncestor = realpathSync7(ancestor);
|
|
21450
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path11.sep)) {
|
|
20798
21451
|
throw new Error("path must stay inside repository root");
|
|
20799
21452
|
}
|
|
20800
21453
|
return target;
|
|
20801
21454
|
}
|
|
20802
21455
|
function run2(root, executable, args, env = {}) {
|
|
20803
|
-
const cwd =
|
|
21456
|
+
const cwd = realpathSync7(root);
|
|
20804
21457
|
const result = spawnSync3(executable, args, {
|
|
20805
21458
|
cwd,
|
|
20806
21459
|
encoding: "utf8",
|
|
@@ -20824,8 +21477,8 @@ var SUPPORT_MATRIX = {
|
|
|
20824
21477
|
};
|
|
20825
21478
|
|
|
20826
21479
|
// packages/workit-mcp/src/server.ts
|
|
20827
|
-
import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as
|
|
20828
|
-
import
|
|
21480
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as realpathSync8 } from "node:fs";
|
|
21481
|
+
import path15 from "node:path";
|
|
20829
21482
|
|
|
20830
21483
|
// node_modules/@modelcontextprotocol/sdk/node_modules/zod/v4/core/core.js
|
|
20831
21484
|
var NEVER2 = Object.freeze({
|
|
@@ -24352,7 +25005,7 @@ function check(fn) {
|
|
|
24352
25005
|
ch._zod.check = fn;
|
|
24353
25006
|
return ch;
|
|
24354
25007
|
}
|
|
24355
|
-
function
|
|
25008
|
+
function custom2(fn, _params) {
|
|
24356
25009
|
return _custom2(ZodCustom2, fn ?? (() => true), _params);
|
|
24357
25010
|
}
|
|
24358
25011
|
function refine2(fn, _params = {}) {
|
|
@@ -24389,7 +25042,7 @@ var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
|
24389
25042
|
var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
|
|
24390
25043
|
var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
|
|
24391
25044
|
var JSONRPC_VERSION = "2.0";
|
|
24392
|
-
var AssertObjectSchema =
|
|
25045
|
+
var AssertObjectSchema = custom2((v) => v !== null && (typeof v === "object" || typeof v === "function"));
|
|
24393
25046
|
var ProgressTokenSchema = union2([string4(), number4().int()]);
|
|
24394
25047
|
var CursorSchema = string4();
|
|
24395
25048
|
var TaskCreationParamsSchema = looseObject({
|
|
@@ -26735,18 +27388,19 @@ var redactSecrets = (value) => applyPatterns(value);
|
|
|
26735
27388
|
|
|
26736
27389
|
// packages/workit-core/src/core/pr-create.ts
|
|
26737
27390
|
import fs8 from "node:fs";
|
|
26738
|
-
import
|
|
27391
|
+
import path12 from "node:path";
|
|
27392
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
26739
27393
|
function parseGhIssue(value) {
|
|
26740
27394
|
const m = /issues\/(\d+)/.exec(value);
|
|
26741
27395
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
26742
27396
|
}
|
|
26743
27397
|
function whichOnPath(tool) {
|
|
26744
27398
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
26745
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
27399
|
+
for (const dir of (process.env.PATH ?? "").split(path12.delimiter)) {
|
|
26746
27400
|
if (!dir)
|
|
26747
27401
|
continue;
|
|
26748
27402
|
for (const name of names) {
|
|
26749
|
-
const candidate =
|
|
27403
|
+
const candidate = path12.join(dir, name);
|
|
26750
27404
|
try {
|
|
26751
27405
|
fs8.accessSync(candidate, fs8.constants.X_OK);
|
|
26752
27406
|
return candidate;
|
|
@@ -26756,11 +27410,21 @@ function whichOnPath(tool) {
|
|
|
26756
27410
|
return null;
|
|
26757
27411
|
}
|
|
26758
27412
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
27413
|
+
var pushRemote = (cwd) => {
|
|
27414
|
+
const result = spawnSync4("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
27415
|
+
cwd,
|
|
27416
|
+
encoding: "utf8"
|
|
27417
|
+
});
|
|
27418
|
+
if (result.status !== 0)
|
|
27419
|
+
return null;
|
|
27420
|
+
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
27421
|
+
return urls.length === 1 ? urls[0] : null;
|
|
27422
|
+
};
|
|
26759
27423
|
|
|
26760
27424
|
// packages/workit-core/src/core/repo-context.ts
|
|
26761
|
-
import { spawnSync as
|
|
27425
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
26762
27426
|
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
|
|
26763
|
-
import
|
|
27427
|
+
import path13 from "node:path";
|
|
26764
27428
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26765
27429
|
try {
|
|
26766
27430
|
gitRevisionParts(value);
|
|
@@ -26787,7 +27451,7 @@ var invalidRange = (cwd) => ({
|
|
|
26787
27451
|
cwd
|
|
26788
27452
|
});
|
|
26789
27453
|
var runGit = (cwd, args) => {
|
|
26790
|
-
const result =
|
|
27454
|
+
const result = spawnSync5("git", args, { cwd, encoding: "utf8" });
|
|
26791
27455
|
return {
|
|
26792
27456
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
26793
27457
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -26894,7 +27558,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26894
27558
|
]) {
|
|
26895
27559
|
const parts = rel.split("/");
|
|
26896
27560
|
const base = parts[parts.length - 1];
|
|
26897
|
-
const dir = parts.length > 1 ?
|
|
27561
|
+
const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26898
27562
|
const actual = probeCaseInsensitive(dir, base);
|
|
26899
27563
|
if (actual)
|
|
26900
27564
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26925,7 +27589,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26925
27589
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26926
27590
|
};
|
|
26927
27591
|
var packageScripts = (cwd, keys) => {
|
|
26928
|
-
const pkgPath =
|
|
27592
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
26929
27593
|
if (!existsSync6(pkgPath))
|
|
26930
27594
|
return [];
|
|
26931
27595
|
try {
|
|
@@ -26951,9 +27615,9 @@ var documentationFiles = (cwd) => {
|
|
|
26951
27615
|
if (entry.isDirectory()) {
|
|
26952
27616
|
if (excluded.has(entry.name))
|
|
26953
27617
|
continue;
|
|
26954
|
-
walk(
|
|
27618
|
+
walk(path13.join(dir, entry.name), depth + 1);
|
|
26955
27619
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26956
|
-
matches.push("./" +
|
|
27620
|
+
matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26957
27621
|
}
|
|
26958
27622
|
}
|
|
26959
27623
|
};
|
|
@@ -26969,7 +27633,7 @@ var readTrimmed = (file, maxLines) => {
|
|
|
26969
27633
|
`);
|
|
26970
27634
|
};
|
|
26971
27635
|
function prReadyContext(root, range) {
|
|
26972
|
-
const cwd =
|
|
27636
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
26973
27637
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26974
27638
|
return invalidRange(cwd);
|
|
26975
27639
|
let stdout = "";
|
|
@@ -27027,7 +27691,7 @@ ${status.stderr}` : "");
|
|
|
27027
27691
|
stdout += `template_path: ${template}
|
|
27028
27692
|
|
|
27029
27693
|
`;
|
|
27030
|
-
stdout += readTrimmed(
|
|
27694
|
+
stdout += readTrimmed(path13.join(cwd, template), 220);
|
|
27031
27695
|
} else {
|
|
27032
27696
|
stdout += `template_path: none
|
|
27033
27697
|
|
|
@@ -27037,7 +27701,7 @@ ${status.stderr}` : "");
|
|
|
27037
27701
|
stdout += `
|
|
27038
27702
|
`;
|
|
27039
27703
|
stdout += printSection("Recent Validation Signals");
|
|
27040
|
-
if (existsSync6(
|
|
27704
|
+
if (existsSync6(path13.join(cwd, "package.json"))) {
|
|
27041
27705
|
stdout += `package.json detected. Common scripts:
|
|
27042
27706
|
`;
|
|
27043
27707
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -27046,7 +27710,7 @@ ${status.stderr}` : "");
|
|
|
27046
27710
|
`) + `
|
|
27047
27711
|
`;
|
|
27048
27712
|
}
|
|
27049
|
-
if (existsSync6(
|
|
27713
|
+
if (existsSync6(path13.join(cwd, "Cargo.toml")) || existsSync6(path13.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
27050
27714
|
stdout += `Rust project detected.
|
|
27051
27715
|
`;
|
|
27052
27716
|
}
|
|
@@ -27079,7 +27743,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
27079
27743
|
- Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
|
|
27080
27744
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
27081
27745
|
function changelogContext(root, range) {
|
|
27082
|
-
const cwd =
|
|
27746
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
27083
27747
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
27084
27748
|
return invalidRange(cwd);
|
|
27085
27749
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -27097,7 +27761,7 @@ function changelogContext(root, range) {
|
|
|
27097
27761
|
stdout += CHANGELOG_RULES + `
|
|
27098
27762
|
`;
|
|
27099
27763
|
stdout += printSection("Existing CHANGELOG.md");
|
|
27100
|
-
const changelogPath =
|
|
27764
|
+
const changelogPath = path13.join(cwd, "CHANGELOG.md");
|
|
27101
27765
|
if (existsSync6(changelogPath)) {
|
|
27102
27766
|
stdout += readTrimmed(changelogPath, 260) + `
|
|
27103
27767
|
`;
|
|
@@ -27117,7 +27781,7 @@ function changelogContext(root, range) {
|
|
|
27117
27781
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
27118
27782
|
}
|
|
27119
27783
|
function docsRefreshContext(root, range) {
|
|
27120
|
-
const cwd =
|
|
27784
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
27121
27785
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
27122
27786
|
return invalidRange(cwd);
|
|
27123
27787
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -27139,7 +27803,7 @@ function docsRefreshContext(root, range) {
|
|
|
27139
27803
|
`) + `
|
|
27140
27804
|
`;
|
|
27141
27805
|
stdout += printSection("README Preview");
|
|
27142
|
-
const readme =
|
|
27806
|
+
const readme = path13.join(cwd, "README.md");
|
|
27143
27807
|
if (existsSync6(readme)) {
|
|
27144
27808
|
stdout += readTrimmed(readme, 220) + `
|
|
27145
27809
|
`;
|
|
@@ -27148,7 +27812,7 @@ function docsRefreshContext(root, range) {
|
|
|
27148
27812
|
`;
|
|
27149
27813
|
}
|
|
27150
27814
|
stdout += printSection("Package Scripts");
|
|
27151
|
-
const pkgPath =
|
|
27815
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
27152
27816
|
if (existsSync6(pkgPath)) {
|
|
27153
27817
|
try {
|
|
27154
27818
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
@@ -27162,7 +27826,7 @@ function docsRefreshContext(root, range) {
|
|
|
27162
27826
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
27163
27827
|
}
|
|
27164
27828
|
function releaseNotesContext(root, rangeOrTag) {
|
|
27165
|
-
const cwd =
|
|
27829
|
+
const cwd = path13.resolve(repoRoot(root));
|
|
27166
27830
|
if (!rangeOrTag) {
|
|
27167
27831
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
27168
27832
|
`, exitCode: 1, cwd };
|
|
@@ -27200,7 +27864,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
27200
27864
|
`;
|
|
27201
27865
|
stdout += printSection("Existing Release Files");
|
|
27202
27866
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
27203
|
-
if (existsSync6(
|
|
27867
|
+
if (existsSync6(path13.join(cwd, rel)))
|
|
27204
27868
|
stdout += rel + `
|
|
27205
27869
|
`;
|
|
27206
27870
|
}
|
|
@@ -27228,19 +27892,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
27228
27892
|
}
|
|
27229
27893
|
|
|
27230
27894
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
27231
|
-
import
|
|
27232
|
-
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
27233
|
-
var trackerRequest = async (url, init) => {
|
|
27234
|
-
try {
|
|
27235
|
-
const res = await fetch(url, { method: init.method, headers: init.headers });
|
|
27236
|
-
const text = await res.text();
|
|
27237
|
-
if (!res.ok)
|
|
27238
|
-
return { status: res.status, stdout: "", stderr: text.slice(0, 200) };
|
|
27239
|
-
return { status: 0, stdout: text, stderr: "" };
|
|
27240
|
-
} catch (err) {
|
|
27241
|
-
return { status: 1, stdout: "", stderr: err instanceof Error ? err.message : "network error" };
|
|
27242
|
-
}
|
|
27243
|
-
};
|
|
27895
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
27244
27896
|
var repoPathFromRemote = (remote) => {
|
|
27245
27897
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
27246
27898
|
if (!rest)
|
|
@@ -27261,7 +27913,11 @@ var defaultCli = (bin, available) => {
|
|
|
27261
27913
|
if (!available)
|
|
27262
27914
|
return null;
|
|
27263
27915
|
return (args, root) => {
|
|
27264
|
-
const out =
|
|
27916
|
+
const out = spawnSync6(bin, args, {
|
|
27917
|
+
cwd: root,
|
|
27918
|
+
encoding: "utf8",
|
|
27919
|
+
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
27920
|
+
});
|
|
27265
27921
|
return Promise.resolve({
|
|
27266
27922
|
status: out.status ?? 1,
|
|
27267
27923
|
stdout: out.stdout ?? "",
|
|
@@ -27269,49 +27925,22 @@ var defaultCli = (bin, available) => {
|
|
|
27269
27925
|
});
|
|
27270
27926
|
};
|
|
27271
27927
|
};
|
|
27272
|
-
var tryCliTriple = async (cli, args, root, id, idKeys, bodyKeys) => {
|
|
27928
|
+
var tryCliTriple = async (cli, name, args, root, id, idKeys, bodyKeys) => {
|
|
27273
27929
|
if (!cli)
|
|
27274
|
-
return
|
|
27930
|
+
return { error: `${name} CLI is unavailable`, kind: "creds" };
|
|
27275
27931
|
const run = cli(args, root);
|
|
27276
27932
|
if (!run)
|
|
27277
|
-
return
|
|
27933
|
+
return { error: `${name} CLI is unavailable`, kind: "creds" };
|
|
27278
27934
|
const out = await run;
|
|
27279
27935
|
if (out.status !== 0)
|
|
27280
|
-
return
|
|
27936
|
+
return { error: `${name} CLI is not authenticated or the issue is unavailable`, kind: "creds" };
|
|
27281
27937
|
try {
|
|
27282
|
-
|
|
27938
|
+
const triple = parseTriple(JSON.parse(out.stdout), id, idKeys, bodyKeys);
|
|
27939
|
+
return triple ? { data: triple } : { error: `unexpected ${name} issue response`, kind: "request" };
|
|
27283
27940
|
} catch {
|
|
27284
|
-
return
|
|
27941
|
+
return { error: `invalid ${name} issue response`, kind: "request" };
|
|
27285
27942
|
}
|
|
27286
27943
|
};
|
|
27287
|
-
var originRemote = (root) => {
|
|
27288
|
-
const out = spawnSync5("git", ["remote", "get-url", "origin"], { cwd: root, encoding: "utf8" });
|
|
27289
|
-
return out.status === 0 ? (out.stdout ?? "").trim() || null : null;
|
|
27290
|
-
};
|
|
27291
|
-
var trackerCreds = (root, provider) => {
|
|
27292
|
-
const cfg = vcsConfig("load", root);
|
|
27293
|
-
if (!cfg || cfg.provider !== provider || typeof cfg.tokenPath !== "string" || !cfg.tokenPath)
|
|
27294
|
-
return { error: `unconfigured ${provider} provider` };
|
|
27295
|
-
let token = "";
|
|
27296
|
-
try {
|
|
27297
|
-
token = fs9.readFileSync(cfg.tokenPath, "utf8").trim();
|
|
27298
|
-
} catch {
|
|
27299
|
-
return { error: `missing ${provider} token` };
|
|
27300
|
-
}
|
|
27301
|
-
if (!token)
|
|
27302
|
-
return { error: `empty ${provider} token file` };
|
|
27303
|
-
if (provider === "gitlab") {
|
|
27304
|
-
const gitlab = cfg.gitlab;
|
|
27305
|
-
const apiUrl = String(gitlab?.apiUrl ?? "https://gitlab.com/api/v4").replace(/\/+$/, "");
|
|
27306
|
-
return { token, api: apiUrl };
|
|
27307
|
-
}
|
|
27308
|
-
const github = cfg.github;
|
|
27309
|
-
const host = String(github?.host ?? "github.com").toLowerCase();
|
|
27310
|
-
return {
|
|
27311
|
-
token,
|
|
27312
|
-
api: host === "github.com" ? "https://api.github.com" : `https://${host}/api/v3`
|
|
27313
|
-
};
|
|
27314
|
-
};
|
|
27315
27944
|
var parseTriple = (parsed, id, idKeys, bodyKeys) => {
|
|
27316
27945
|
const title = parsed.title;
|
|
27317
27946
|
if (typeof title !== "string")
|
|
@@ -27334,62 +27963,36 @@ var fetchGitHubIssueBody = async (ref, root, deps = {}) => {
|
|
|
27334
27963
|
const id = parseGhIssue(ref);
|
|
27335
27964
|
if (!/^\d+$/.test(id))
|
|
27336
27965
|
return { error: `invalid GitHub issue ref "${ref}"`, kind: "input" };
|
|
27337
|
-
const
|
|
27966
|
+
const remote = (deps.remote ?? pushRemote)(root);
|
|
27967
|
+
const repo = repoPathFromRemote(remote ?? "");
|
|
27338
27968
|
if (!repo)
|
|
27339
27969
|
return { error: "no origin remote to resolve the repository", kind: "input" };
|
|
27340
|
-
const
|
|
27341
|
-
|
|
27342
|
-
|
|
27343
|
-
|
|
27344
|
-
|
|
27345
|
-
|
|
27346
|
-
const out = await (deps.request ?? trackerRequest)(`${credentials.api}/repos/${repo}/issues/${id}`, {
|
|
27347
|
-
method: "GET",
|
|
27348
|
-
headers: {
|
|
27349
|
-
Authorization: `Bearer ${credentials.token}`,
|
|
27350
|
-
Accept: "application/vnd.github+json"
|
|
27351
|
-
}
|
|
27352
|
-
});
|
|
27353
|
-
if (out.status !== 0)
|
|
27354
|
-
return { error: out.stderr || "GitHub issue fetch failed", kind: "request" };
|
|
27355
|
-
try {
|
|
27356
|
-
const triple = parseTriple(JSON.parse(out.stdout), id, ["number"], ["body"]);
|
|
27357
|
-
if (!triple)
|
|
27358
|
-
return { error: "unexpected GitHub issue shape", kind: "request" };
|
|
27359
|
-
return { data: triple };
|
|
27360
|
-
} catch {
|
|
27361
|
-
return { error: "invalid GitHub issue response", kind: "request" };
|
|
27362
|
-
}
|
|
27970
|
+
const cfg = vcsConfig("load", root);
|
|
27971
|
+
const host = (deps.host ?? (cfg.ok && cfg.provider === "github" ? cfg.github?.host : null) ?? "github.com").toLowerCase();
|
|
27972
|
+
if (!remote || !hostingApiHostMatches(remote, host))
|
|
27973
|
+
return { error: "hosting API host does not match the push destination", kind: "input" };
|
|
27974
|
+
const repoArg = host === "github.com" ? repo : `${host}/${repo}`;
|
|
27975
|
+
return tryCliTriple(deps.cli ?? defaultCli("gh", hostingCliAvailable("github")), "gh", ["issue", "view", id, "--repo", repoArg, "--json", "number,title,body,state"], root, id, ["number"], ["body"]);
|
|
27363
27976
|
};
|
|
27364
27977
|
var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
27365
27978
|
const id = parseGhIssue(ref);
|
|
27366
27979
|
if (!/^\d+$/.test(id))
|
|
27367
27980
|
return { error: `invalid GitLab issue ref "${ref}"`, kind: "input" };
|
|
27368
|
-
const
|
|
27981
|
+
const remote = (deps.remote ?? pushRemote)(root);
|
|
27982
|
+
const repo = repoPathFromRemote(remote ?? "");
|
|
27369
27983
|
if (!repo)
|
|
27370
27984
|
return { error: "no origin remote to resolve the project", kind: "input" };
|
|
27371
|
-
const
|
|
27372
|
-
|
|
27373
|
-
|
|
27374
|
-
|
|
27375
|
-
|
|
27376
|
-
|
|
27377
|
-
const out = await (deps.request ?? trackerRequest)(`${credentials.api}/projects/${encodeURIComponent(repo)}/issues/${id}`, { method: "GET", headers: { "PRIVATE-TOKEN": credentials.token } });
|
|
27378
|
-
if (out.status !== 0)
|
|
27379
|
-
return { error: out.stderr || "GitLab issue fetch failed", kind: "request" };
|
|
27380
|
-
try {
|
|
27381
|
-
const triple = parseTriple(JSON.parse(out.stdout), id, ["iid"], ["description"]);
|
|
27382
|
-
if (!triple)
|
|
27383
|
-
return { error: "unexpected GitLab issue shape", kind: "request" };
|
|
27384
|
-
return { data: triple };
|
|
27385
|
-
} catch {
|
|
27386
|
-
return { error: "invalid GitLab issue response", kind: "request" };
|
|
27387
|
-
}
|
|
27985
|
+
const cfg = vcsConfig("load", root);
|
|
27986
|
+
const host = (deps.host ?? (cfg.ok && cfg.provider === "gitlab" ? cfg.gitlab?.host : null) ?? "gitlab.com").toLowerCase();
|
|
27987
|
+
if (!remote || !hostingApiHostMatches(remote, host))
|
|
27988
|
+
return { error: "hosting API host does not match the push destination", kind: "input" };
|
|
27989
|
+
const repoArg = host === "gitlab.com" ? repo : `https://${host}/${repo}`;
|
|
27990
|
+
return tryCliTriple(deps.cli ?? defaultCli("glab", hostingCliAvailable("gitlab")), "glab", ["issue", "view", id, "-R", repoArg, "-F", "json"], root, id, ["iid"], ["description"]);
|
|
27388
27991
|
};
|
|
27389
27992
|
|
|
27390
27993
|
// packages/workit-core/src/core/youtrack.ts
|
|
27391
|
-
import
|
|
27392
|
-
import
|
|
27994
|
+
import fs9 from "node:fs";
|
|
27995
|
+
import path14 from "node:path";
|
|
27393
27996
|
|
|
27394
27997
|
// packages/workit-core/src/core/templates.ts
|
|
27395
27998
|
var repoRoot2 = assetRoot();
|
|
@@ -27401,23 +28004,23 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
27401
28004
|
|
|
27402
28005
|
// packages/workit-core/src/core/youtrack.ts
|
|
27403
28006
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
27404
|
-
var
|
|
27405
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
27406
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
28007
|
+
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
28008
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(configDir(), "youtrack.json");
|
|
28009
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(resolveConfigDir(), "youtrack.json");
|
|
27407
28010
|
var youTrackTokenModeOk = (p) => {
|
|
27408
28011
|
if (process.platform === "win32")
|
|
27409
28012
|
return true;
|
|
27410
|
-
const mode =
|
|
28013
|
+
const mode = fs9.statSync(p).mode & 511;
|
|
27411
28014
|
return mode === 384;
|
|
27412
28015
|
};
|
|
27413
28016
|
function readYouTrackConfig(required) {
|
|
27414
28017
|
const cfgPath = youTrackConfigPath();
|
|
27415
|
-
if (!
|
|
28018
|
+
if (!fs9.existsSync(cfgPath)) {
|
|
27416
28019
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
27417
28020
|
}
|
|
27418
28021
|
let parsed;
|
|
27419
28022
|
try {
|
|
27420
|
-
parsed = JSON.parse(
|
|
28023
|
+
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
27421
28024
|
} catch {
|
|
27422
28025
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
27423
28026
|
}
|
|
@@ -27428,11 +28031,11 @@ function readYouTrackConfig(required) {
|
|
|
27428
28031
|
}
|
|
27429
28032
|
var readYouTrackContextConfig = () => {
|
|
27430
28033
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
27431
|
-
if (!
|
|
28034
|
+
if (!fs9.existsSync(cfgPath))
|
|
27432
28035
|
return { data: {} };
|
|
27433
28036
|
let parsed;
|
|
27434
28037
|
try {
|
|
27435
|
-
parsed = JSON.parse(
|
|
28038
|
+
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
27436
28039
|
} catch {
|
|
27437
28040
|
return { error: "YouTrack configuration is unavailable" };
|
|
27438
28041
|
}
|
|
@@ -27457,12 +28060,12 @@ function youTrackGreeting(configOverride) {
|
|
|
27457
28060
|
const cfgPath = configOverride ?? youTrackConfigPath();
|
|
27458
28061
|
let parsed;
|
|
27459
28062
|
try {
|
|
27460
|
-
parsed = JSON.parse(
|
|
28063
|
+
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
27461
28064
|
} catch {
|
|
27462
28065
|
return {
|
|
27463
28066
|
stdout: "",
|
|
27464
28067
|
exitCode: 1,
|
|
27465
|
-
stderr:
|
|
28068
|
+
stderr: fs9.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
|
|
27466
28069
|
};
|
|
27467
28070
|
}
|
|
27468
28071
|
if (!isConfigObject(parsed)) {
|
|
@@ -27508,15 +28111,15 @@ var youTrackToken = () => {
|
|
|
27508
28111
|
if ("error" in loaded)
|
|
27509
28112
|
return loaded;
|
|
27510
28113
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
27511
|
-
const tokenPath = tokenFile ?
|
|
27512
|
-
if (!tokenPath || !
|
|
28114
|
+
const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
|
|
28115
|
+
if (!tokenPath || !fs9.existsSync(tokenPath))
|
|
27513
28116
|
return { error: "missing youtrack.token" };
|
|
27514
28117
|
if (!youTrackTokenModeOk(tokenPath))
|
|
27515
28118
|
return { error: "youtrack.token mode must be 0600" };
|
|
27516
|
-
const token =
|
|
28119
|
+
const token = fs9.readFileSync(tokenPath, "utf8").trim();
|
|
27517
28120
|
if (!token)
|
|
27518
28121
|
return { error: "empty token file" };
|
|
27519
|
-
if (token ===
|
|
28122
|
+
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
27520
28123
|
return {
|
|
27521
28124
|
error: "token file still has placeholder YOUR_TOKEN_HERE — edit the file locally, then run /wk-status"
|
|
27522
28125
|
};
|
|
@@ -27579,9 +28182,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
27579
28182
|
} catch {
|
|
27580
28183
|
continue;
|
|
27581
28184
|
}
|
|
27582
|
-
if (!
|
|
28185
|
+
if (!fs9.existsSync(full))
|
|
27583
28186
|
continue;
|
|
27584
|
-
const text =
|
|
28187
|
+
const text = fs9.readFileSync(full, "utf8");
|
|
27585
28188
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
27586
28189
|
if (m)
|
|
27587
28190
|
return m[1];
|
|
@@ -27815,6 +28418,7 @@ var readExternalContext = async (root, payload) => {
|
|
|
27815
28418
|
}
|
|
27816
28419
|
}
|
|
27817
28420
|
};
|
|
28421
|
+
var targetWriterActionLease = Symbol("target-writer-action-lease");
|
|
27818
28422
|
|
|
27819
28423
|
// packages/workit-mcp/src/server.ts
|
|
27820
28424
|
class McpCapabilityUnavailableError extends Error {
|
|
@@ -27859,13 +28463,13 @@ var rootVariants = (workspaceRoot) => {
|
|
|
27859
28463
|
if (!workspaceRoot)
|
|
27860
28464
|
return [];
|
|
27861
28465
|
const roots = new Set;
|
|
27862
|
-
for (const candidate of [workspaceRoot,
|
|
27863
|
-
if (
|
|
28466
|
+
for (const candidate of [workspaceRoot, path15.resolve(workspaceRoot)]) {
|
|
28467
|
+
if (path15.isAbsolute(candidate) && candidate !== path15.parse(candidate).root)
|
|
27864
28468
|
roots.add(candidate);
|
|
27865
28469
|
}
|
|
27866
28470
|
try {
|
|
27867
|
-
const canonical =
|
|
27868
|
-
if (canonical !==
|
|
28471
|
+
const canonical = realpathSync8(workspaceRoot);
|
|
28472
|
+
if (canonical !== path15.parse(canonical).root)
|
|
27869
28473
|
roots.add(canonical);
|
|
27870
28474
|
} catch {}
|
|
27871
28475
|
return [...roots].sort((left, right) => right.length - left.length);
|
|
@@ -27966,11 +28570,11 @@ var thrownResult = (tool, error, workspaceRoot) => {
|
|
|
27966
28570
|
};
|
|
27967
28571
|
function createMcpServer(host, contextProvider) {
|
|
27968
28572
|
assertMcpHost(host);
|
|
27969
|
-
const here =
|
|
28573
|
+
const here = path15.dirname(fileURLToPath2(import.meta.url));
|
|
27970
28574
|
const sourceMarker = markSourcesLoaded([
|
|
27971
|
-
|
|
27972
|
-
|
|
27973
|
-
|
|
28575
|
+
path15.join(here, "server.ts"),
|
|
28576
|
+
path15.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
|
|
28577
|
+
path15.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
|
|
27974
28578
|
].filter((file) => existsSync7(file)));
|
|
27975
28579
|
let staleWarned = false;
|
|
27976
28580
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
@@ -28251,6 +28855,7 @@ var allowedKeys = {
|
|
|
28251
28855
|
"stop_hook_active"
|
|
28252
28856
|
])
|
|
28253
28857
|
};
|
|
28858
|
+
var historyOfferSessions = new Set;
|
|
28254
28859
|
if (false)
|
|
28255
28860
|
;
|
|
28256
28861
|
|
|
@@ -28263,14 +28868,14 @@ var codexQualification = (host) => host === "codex_cli" ? { surface: host, cli:
|
|
|
28263
28868
|
var resolveCodexWorkspaceRoot = (pluginRoot, env) => {
|
|
28264
28869
|
const explicit = env.WORKFLOW_WORKSPACE_ROOT;
|
|
28265
28870
|
const inherited = env.PWD;
|
|
28266
|
-
const candidate = explicit ?? (inherited &&
|
|
28267
|
-
if (!candidate || !
|
|
28871
|
+
const candidate = explicit ?? (inherited && path16.resolve(inherited) !== path16.resolve(pluginRoot) ? inherited : undefined);
|
|
28872
|
+
if (!candidate || !path16.isAbsolute(candidate) || !existsSync8(candidate))
|
|
28268
28873
|
return null;
|
|
28269
28874
|
try {
|
|
28270
|
-
if (!
|
|
28875
|
+
if (!statSync3(candidate).isDirectory())
|
|
28271
28876
|
return null;
|
|
28272
|
-
const canonical =
|
|
28273
|
-
if (canonical ===
|
|
28877
|
+
const canonical = realpathSync9(candidate);
|
|
28878
|
+
if (canonical === realpathSync9(pluginRoot)) {
|
|
28274
28879
|
if (!explicit)
|
|
28275
28880
|
return null;
|
|
28276
28881
|
const workspace = new TaskStore(canonical).readWorkspace();
|