@brainervirus/workit-cli 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +688 -135
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -50927,6 +50927,7 @@ var decisionSchema = object({
|
|
|
50927
50927
|
scope: scopeSchema,
|
|
50928
50928
|
presented: text,
|
|
50929
50929
|
approvedContent: text,
|
|
50930
|
+
displayed: text.optional(),
|
|
50930
50931
|
contentRefs: array(refSchema)
|
|
50931
50932
|
}).strict(),
|
|
50932
50933
|
digest,
|
|
@@ -50939,6 +50940,27 @@ var decisionSchema = object({
|
|
|
50939
50940
|
actionRef: refSchema
|
|
50940
50941
|
}).strict().nullable()
|
|
50941
50942
|
}).strict();
|
|
50943
|
+
var BINDING_QUESTION_BUDGET = 300;
|
|
50944
|
+
var bindingQuestionHeader = /^Workit decision: (design|action|limitation|preference)$/;
|
|
50945
|
+
var workitBindingQuestionIssue = (questions) => {
|
|
50946
|
+
if (!Array.isArray(questions) || questions.length !== 1)
|
|
50947
|
+
return null;
|
|
50948
|
+
const question = questions[0];
|
|
50949
|
+
if (!question || typeof question !== "object")
|
|
50950
|
+
return null;
|
|
50951
|
+
if (typeof question.header !== "string" || !bindingQuestionHeader.test(question.header.trim()))
|
|
50952
|
+
return null;
|
|
50953
|
+
const over = (label, value) => `invalid_input: ${label} is ${value.length} characters; Workit binding questions must stay within ${BINDING_QUESTION_BUDGET}. present the item in the conversation first, then ask a short scoped question.`;
|
|
50954
|
+
if (typeof question.question === "string" && question.question.length > BINDING_QUESTION_BUDGET)
|
|
50955
|
+
return over("the question text", question.question);
|
|
50956
|
+
if (Array.isArray(question.options))
|
|
50957
|
+
for (const option of question.options) {
|
|
50958
|
+
const description = option.description;
|
|
50959
|
+
if (typeof description === "string" && description.length > BINDING_QUESTION_BUDGET)
|
|
50960
|
+
return over("an option description", description);
|
|
50961
|
+
}
|
|
50962
|
+
return null;
|
|
50963
|
+
};
|
|
50942
50964
|
var findingSchema = object({
|
|
50943
50965
|
claim: text,
|
|
50944
50966
|
consequence: text,
|
|
@@ -50965,7 +50987,7 @@ var workerReportSchema = object({
|
|
|
50965
50987
|
}).strict();
|
|
50966
50988
|
var workerSchema = object({
|
|
50967
50989
|
assignment: assignmentSchema,
|
|
50968
|
-
state: _enum(["assigned", "running", "cancelling", "stopped", "unknown"]),
|
|
50990
|
+
state: _enum(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
|
|
50969
50991
|
session: refSchema.nullable(),
|
|
50970
50992
|
report: workerReportSchema.nullable()
|
|
50971
50993
|
}).strict();
|
|
@@ -51005,6 +51027,10 @@ var actionProgressListSchema = array(actionProgressSchema).check((ctx) => {
|
|
|
51005
51027
|
path: ["decisionId"]
|
|
51006
51028
|
});
|
|
51007
51029
|
});
|
|
51030
|
+
var runtimeSchema = object({
|
|
51031
|
+
createdWith: text.nullable(),
|
|
51032
|
+
updatedWith: text
|
|
51033
|
+
}).strict();
|
|
51008
51034
|
var taskRecordSchema = object({
|
|
51009
51035
|
schemaVersion: literal(1),
|
|
51010
51036
|
id,
|
|
@@ -51018,6 +51044,8 @@ var taskRecordSchema = object({
|
|
|
51018
51044
|
status: _enum(["active", "paused", "closed"]),
|
|
51019
51045
|
closure: closureSchema.nullable(),
|
|
51020
51046
|
progress: progressSchema,
|
|
51047
|
+
pauseReason: text.nullable().optional(),
|
|
51048
|
+
runtime: runtimeSchema.optional(),
|
|
51021
51049
|
assessments: array(entrySchema(assessmentSchema)),
|
|
51022
51050
|
policy: policySchema.nullable(),
|
|
51023
51051
|
policyChanges: array(policyChangeSchema),
|
|
@@ -51033,6 +51061,7 @@ var workspaceRecordSchema = object({
|
|
|
51033
51061
|
id,
|
|
51034
51062
|
revision,
|
|
51035
51063
|
root: nonEmpty,
|
|
51064
|
+
runtime: runtimeSchema.optional(),
|
|
51036
51065
|
writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
51037
51066
|
}).strict();
|
|
51038
51067
|
var capabilitySchema = object({
|
|
@@ -51060,6 +51089,9 @@ var taskSummarySchema = object({
|
|
|
51060
51089
|
id,
|
|
51061
51090
|
revision,
|
|
51062
51091
|
workspaceRevision: revision,
|
|
51092
|
+
createdAt: utc,
|
|
51093
|
+
updatedAt: utc,
|
|
51094
|
+
runtime: runtimeSchema.nullable(),
|
|
51063
51095
|
objective: text,
|
|
51064
51096
|
status: _enum(["active", "paused", "closed"]),
|
|
51065
51097
|
closure: closureSchema.nullable(),
|
|
@@ -51130,7 +51162,7 @@ var taskOperations = {
|
|
|
51130
51162
|
expectedWorkspaceRevision: revision.optional(),
|
|
51131
51163
|
outcome: outcomeSchema,
|
|
51132
51164
|
summary: text,
|
|
51133
|
-
decisionIds: array(id)
|
|
51165
|
+
decisionIds: array(id).optional()
|
|
51134
51166
|
})
|
|
51135
51167
|
};
|
|
51136
51168
|
var policyOperations = {
|
|
@@ -51222,14 +51254,15 @@ var writerOperations = {
|
|
|
51222
51254
|
...taskId,
|
|
51223
51255
|
expectedRevision: revision.optional(),
|
|
51224
51256
|
expectedWorkspaceRevision: revision.optional(),
|
|
51225
|
-
workerId: nullableId.optional()
|
|
51257
|
+
workerId: nullableId.optional(),
|
|
51258
|
+
reason: text.optional()
|
|
51226
51259
|
}),
|
|
51227
51260
|
release: operation({
|
|
51228
51261
|
action: literal("release"),
|
|
51229
51262
|
...taskId,
|
|
51230
51263
|
expectedRevision: revision.optional(),
|
|
51231
51264
|
expectedWorkspaceRevision: revision.optional(),
|
|
51232
|
-
reason: text
|
|
51265
|
+
reason: text.optional()
|
|
51233
51266
|
})
|
|
51234
51267
|
};
|
|
51235
51268
|
var stateOperations = {
|
|
@@ -57208,6 +57241,33 @@ var metadataLockSchema = object({
|
|
|
57208
57241
|
nonce: string2().min(1)
|
|
57209
57242
|
}).strict();
|
|
57210
57243
|
var now2 = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
57244
|
+
var cachedRuntimeVersion = null;
|
|
57245
|
+
var runtimeVersion = () => {
|
|
57246
|
+
if (cachedRuntimeVersion === null) {
|
|
57247
|
+
try {
|
|
57248
|
+
const pkg = JSON.parse(fs7.readFileSync(path19.join(packageRoot(), "package.json"), "utf8"));
|
|
57249
|
+
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
57250
|
+
} catch {
|
|
57251
|
+
cachedRuntimeVersion = "0.0.0";
|
|
57252
|
+
}
|
|
57253
|
+
}
|
|
57254
|
+
return cachedRuntimeVersion;
|
|
57255
|
+
};
|
|
57256
|
+
var stampNew = (record) => ({
|
|
57257
|
+
createdWith: record.runtime?.createdWith ?? null,
|
|
57258
|
+
updatedWith: runtimeVersion()
|
|
57259
|
+
});
|
|
57260
|
+
var isNewerVersion = (candidate, current) => {
|
|
57261
|
+
const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
|
|
57262
|
+
const [next, now] = [parts(candidate), parts(current)];
|
|
57263
|
+
for (let index = 0;index < 3; index += 1) {
|
|
57264
|
+
const left = next[index] ?? 0;
|
|
57265
|
+
const right = now[index] ?? 0;
|
|
57266
|
+
if (left !== right)
|
|
57267
|
+
return left > right;
|
|
57268
|
+
}
|
|
57269
|
+
return false;
|
|
57270
|
+
};
|
|
57211
57271
|
var jsonBytes = (value) => `${canonicalJson(value)}
|
|
57212
57272
|
`;
|
|
57213
57273
|
var digestBytes2 = (value) => createHash4("sha256").update(value).digest("hex");
|
|
@@ -57307,7 +57367,7 @@ class TaskStore {
|
|
|
57307
57367
|
if (!current.ok)
|
|
57308
57368
|
return current;
|
|
57309
57369
|
if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
|
|
57310
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
57370
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
57311
57371
|
expectedWorkspaceRevision: value.expectedWorkspaceRevision,
|
|
57312
57372
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
57313
57373
|
});
|
|
@@ -57319,11 +57379,17 @@ class TaskStore {
|
|
|
57319
57379
|
const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
|
|
57320
57380
|
if (current.data && !previousWorkspaceBytes)
|
|
57321
57381
|
return failure("storage_error", "workspace snapshot disappeared during creation");
|
|
57322
|
-
const workspace = current.data ? {
|
|
57382
|
+
const workspace = current.data ? {
|
|
57383
|
+
...current.data,
|
|
57384
|
+
revision: newRevision(),
|
|
57385
|
+
root: this.root,
|
|
57386
|
+
runtime: stampNew(current.data)
|
|
57387
|
+
} : {
|
|
57323
57388
|
schemaVersion: SCHEMA_VERSION,
|
|
57324
57389
|
id: newId(),
|
|
57325
57390
|
revision: newRevision(),
|
|
57326
57391
|
root: this.root,
|
|
57392
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
57327
57393
|
writer: null
|
|
57328
57394
|
};
|
|
57329
57395
|
const timestamp = value.now ?? now2();
|
|
@@ -57345,6 +57411,7 @@ class TaskStore {
|
|
|
57345
57411
|
status: "active",
|
|
57346
57412
|
closure: null,
|
|
57347
57413
|
progress: { summary: "", nextAction: null, blockers: [] },
|
|
57414
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
57348
57415
|
assessments: [],
|
|
57349
57416
|
policy: null,
|
|
57350
57417
|
policyChanges: [],
|
|
@@ -57375,7 +57442,7 @@ class TaskStore {
|
|
|
57375
57442
|
if (!current.ok)
|
|
57376
57443
|
return current;
|
|
57377
57444
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
57378
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
57445
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
57379
57446
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
57380
57447
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
57381
57448
|
});
|
|
@@ -57383,11 +57450,17 @@ class TaskStore {
|
|
|
57383
57450
|
if (current.data && !previousWorkspaceBytes)
|
|
57384
57451
|
return failure("storage_error", "workspace snapshot disappeared during import");
|
|
57385
57452
|
const timestamp = input.now ?? now2();
|
|
57386
|
-
const workspace = current.data ? {
|
|
57453
|
+
const workspace = current.data ? {
|
|
57454
|
+
...current.data,
|
|
57455
|
+
revision: newRevision(),
|
|
57456
|
+
root: this.root,
|
|
57457
|
+
runtime: stampNew(current.data)
|
|
57458
|
+
} : {
|
|
57387
57459
|
schemaVersion: SCHEMA_VERSION,
|
|
57388
57460
|
id: input.workspaceId ?? newId(),
|
|
57389
57461
|
revision: newRevision(),
|
|
57390
57462
|
root: this.root,
|
|
57463
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
57391
57464
|
writer: null
|
|
57392
57465
|
};
|
|
57393
57466
|
const task = {
|
|
@@ -57395,7 +57468,11 @@ class TaskStore {
|
|
|
57395
57468
|
workspaceId: workspace.id,
|
|
57396
57469
|
revision: newRevision(),
|
|
57397
57470
|
createdAt: timestamp,
|
|
57398
|
-
updatedAt: timestamp
|
|
57471
|
+
updatedAt: timestamp,
|
|
57472
|
+
runtime: {
|
|
57473
|
+
createdWith: input.task.runtime?.createdWith ?? null,
|
|
57474
|
+
updatedWith: runtimeVersion()
|
|
57475
|
+
}
|
|
57399
57476
|
};
|
|
57400
57477
|
const validTask = taskRecordSchema.safeParse(task);
|
|
57401
57478
|
if (!validTask.success)
|
|
@@ -57444,7 +57521,8 @@ class TaskStore {
|
|
|
57444
57521
|
workspaceId: current.data.workspaceId,
|
|
57445
57522
|
createdAt: current.data.createdAt,
|
|
57446
57523
|
revision: context.revision,
|
|
57447
|
-
updatedAt: context.now
|
|
57524
|
+
updatedAt: context.now,
|
|
57525
|
+
runtime: stampNew(current.data)
|
|
57448
57526
|
};
|
|
57449
57527
|
const valid = taskRecordSchema.safeParse(record);
|
|
57450
57528
|
if (!valid.success)
|
|
@@ -57478,7 +57556,8 @@ class TaskStore {
|
|
|
57478
57556
|
...changed.data,
|
|
57479
57557
|
id: current.data.id,
|
|
57480
57558
|
root: this.root,
|
|
57481
|
-
revision: context.revision
|
|
57559
|
+
revision: context.revision,
|
|
57560
|
+
runtime: stampNew(current.data)
|
|
57482
57561
|
};
|
|
57483
57562
|
const valid = workspaceRecordSchema.safeParse(record);
|
|
57484
57563
|
if (!valid.success)
|
|
@@ -57521,7 +57600,8 @@ class TaskStore {
|
|
|
57521
57600
|
...changedWorkspace.data,
|
|
57522
57601
|
id: workspace.data.id,
|
|
57523
57602
|
root: this.root,
|
|
57524
|
-
revision: workspaceContext.revision
|
|
57603
|
+
revision: workspaceContext.revision,
|
|
57604
|
+
runtime: stampNew(workspace.data)
|
|
57525
57605
|
});
|
|
57526
57606
|
if (!nextWorkspace.success)
|
|
57527
57607
|
return failure("invalid_input", "workspace mutation produced an invalid record");
|
|
@@ -57546,7 +57626,8 @@ class TaskStore {
|
|
|
57546
57626
|
workspaceId: task.data.workspaceId,
|
|
57547
57627
|
createdAt: task.data.createdAt,
|
|
57548
57628
|
revision: taskContext.revision,
|
|
57549
|
-
updatedAt: taskContext.now
|
|
57629
|
+
updatedAt: taskContext.now,
|
|
57630
|
+
runtime: stampNew(task.data)
|
|
57550
57631
|
});
|
|
57551
57632
|
if (!nextTask.success) {
|
|
57552
57633
|
this.markUncertain(nextWorkspace.data);
|
|
@@ -57766,7 +57847,8 @@ class TaskStore {
|
|
|
57766
57847
|
const value = {
|
|
57767
57848
|
...workspace,
|
|
57768
57849
|
writer: { ...workspace.writer, state: "uncertain" },
|
|
57769
|
-
revision: newRevision()
|
|
57850
|
+
revision: newRevision(),
|
|
57851
|
+
runtime: stampNew(workspace)
|
|
57770
57852
|
};
|
|
57771
57853
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
57772
57854
|
}
|
|
@@ -57980,7 +58062,12 @@ class TaskStore {
|
|
|
57980
58062
|
if (isObject3(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
57981
58063
|
return failure("unsupported_version", "unsupported snapshot schema version");
|
|
57982
58064
|
const parsed = schema.safeParse(value);
|
|
57983
|
-
|
|
58065
|
+
if (parsed.success)
|
|
58066
|
+
return success(null, null, parsed.data);
|
|
58067
|
+
const writerVersion = isObject3(value) && isObject3(value.runtime) ? value.runtime.updatedWith : null;
|
|
58068
|
+
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
58069
|
+
return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
58070
|
+
return failure("recovery_required", "snapshot does not satisfy its schema");
|
|
57984
58071
|
}
|
|
57985
58072
|
processStart(pid) {
|
|
57986
58073
|
try {
|
|
@@ -57990,7 +58077,7 @@ class TaskStore {
|
|
|
57990
58077
|
}
|
|
57991
58078
|
}
|
|
57992
58079
|
conflict(expected, actual) {
|
|
57993
|
-
return failure("revision_conflict", "snapshot revision does not match", {
|
|
58080
|
+
return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
57994
58081
|
expectedRevision: expected,
|
|
57995
58082
|
actualRevision: actual
|
|
57996
58083
|
});
|
|
@@ -58017,6 +58104,61 @@ class TaskStore {
|
|
|
58017
58104
|
return path19.join(this.workitDir, ".gitignore");
|
|
58018
58105
|
}
|
|
58019
58106
|
}
|
|
58107
|
+
// packages/workit-core/src/core/workers.ts
|
|
58108
|
+
var same = (left, right) => {
|
|
58109
|
+
try {
|
|
58110
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
58111
|
+
} catch {
|
|
58112
|
+
return false;
|
|
58113
|
+
}
|
|
58114
|
+
};
|
|
58115
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
58116
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
58117
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
58118
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
58119
|
+
const code = char.charCodeAt(0);
|
|
58120
|
+
return code < 32 || code === 127;
|
|
58121
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
58122
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
58123
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
58124
|
+
function assertProductWriteAllowed(input) {
|
|
58125
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
58126
|
+
return failure("invalid_input", "invalid product write path");
|
|
58127
|
+
if (!input.store)
|
|
58128
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
58129
|
+
const task = input.store.readTask(input.task.id);
|
|
58130
|
+
if (!task.ok)
|
|
58131
|
+
return task;
|
|
58132
|
+
const workspace = input.store.readWorkspace();
|
|
58133
|
+
if (!workspace.ok)
|
|
58134
|
+
return workspace;
|
|
58135
|
+
if (!workspace.data)
|
|
58136
|
+
return failure("not_found", "workspace not found");
|
|
58137
|
+
const workspaceRecord = workspace.data;
|
|
58138
|
+
if (task.data.status !== "active")
|
|
58139
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
58140
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
58141
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
58142
|
+
const writer = workspaceRecord.writer;
|
|
58143
|
+
if (!writer)
|
|
58144
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
58145
|
+
if (writer.state === "uncertain")
|
|
58146
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
58147
|
+
owner: writer.owner
|
|
58148
|
+
});
|
|
58149
|
+
const caller = callerValue(input.caller);
|
|
58150
|
+
const workerId = workerIdOf(input.caller);
|
|
58151
|
+
const owner = writer.owner;
|
|
58152
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
58153
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
58154
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
58155
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
58156
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
58157
|
+
if (workerId !== null && (!worker?.data.session || worker.data.state === "cancelling" || worker.data.state === "unknown" || worker.data.state === "stopped" || !same(worker.data.session, callerSession(input.caller))))
|
|
58158
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
58159
|
+
return success(null, workspaceRecord.revision, owner);
|
|
58160
|
+
}
|
|
58161
|
+
|
|
58020
58162
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
58021
58163
|
import { createHash as createHash6 } from "node:crypto";
|
|
58022
58164
|
import * as fs9 from "node:fs";
|
|
@@ -58155,7 +58297,11 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
58155
58297
|
return failure("invalid_input", "document references require a byte digest");
|
|
58156
58298
|
const target = path20.resolve(root, reference.path);
|
|
58157
58299
|
if (target !== root && !target.startsWith(`${root}${path20.sep}`))
|
|
58158
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
58300
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
58301
|
+
fields: [
|
|
58302
|
+
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
58303
|
+
]
|
|
58304
|
+
});
|
|
58159
58305
|
try {
|
|
58160
58306
|
const relative = path20.relative(root, target);
|
|
58161
58307
|
let current = root;
|
|
@@ -58166,7 +58312,14 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
58166
58312
|
const stat = fs8.lstatSync(current);
|
|
58167
58313
|
const real = fs8.realpathSync(current);
|
|
58168
58314
|
if (real !== root && !real.startsWith(`${root}${path20.sep}`))
|
|
58169
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
58315
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
58316
|
+
fields: [
|
|
58317
|
+
{
|
|
58318
|
+
path: "contentRefs.path",
|
|
58319
|
+
reason: `${reference.path} resolves outside the checkout`
|
|
58320
|
+
}
|
|
58321
|
+
]
|
|
58322
|
+
});
|
|
58170
58323
|
if (stat.isSymbolicLink())
|
|
58171
58324
|
return failure("invalid_input", "document reference uses a symlink");
|
|
58172
58325
|
if (current === target && !stat.isFile())
|
|
@@ -58610,15 +58763,15 @@ var scopeRoots = (root, scope) => {
|
|
|
58610
58763
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
58611
58764
|
const target = path21.resolve(root, item);
|
|
58612
58765
|
if (!inside(root, target))
|
|
58613
|
-
return failure("invalid_input",
|
|
58766
|
+
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` }] });
|
|
58614
58767
|
let ancestor = target;
|
|
58615
58768
|
while (!fs9.existsSync(ancestor) && ancestor !== root)
|
|
58616
58769
|
ancestor = path21.dirname(ancestor);
|
|
58617
58770
|
try {
|
|
58618
58771
|
if (!inside(root, fs9.realpathSync(ancestor)))
|
|
58619
|
-
return failure("invalid_input",
|
|
58772
|
+
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` }] });
|
|
58620
58773
|
} catch {
|
|
58621
|
-
return failure("invalid_input",
|
|
58774
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
58622
58775
|
}
|
|
58623
58776
|
roots.push(target);
|
|
58624
58777
|
}
|
|
@@ -58633,7 +58786,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
58633
58786
|
}
|
|
58634
58787
|
const normalizedScope = canonicalScope(scope);
|
|
58635
58788
|
if (!normalizedScope)
|
|
58636
|
-
return failure("invalid_input", "candidate scope is invalid");
|
|
58789
|
+
return failure("invalid_input", "candidate scope is invalid: paths must be checkout-relative — to work in another repository, keep this task here and coordinate a separate linked task in that checkout", { fields: [{ path: "scope.paths", reason: "must be checkout-relative" }] });
|
|
58637
58790
|
const roots = scopeRoots(checkout, normalizedScope);
|
|
58638
58791
|
if (!roots.ok)
|
|
58639
58792
|
return roots;
|
|
@@ -58822,7 +58975,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
58822
58975
|
return true;
|
|
58823
58976
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
58824
58977
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
58825
|
-
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
|
|
58978
|
+
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
|
|
58826
58979
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
58827
58980
|
});
|
|
58828
58981
|
if (passed.length) {
|
|
@@ -58918,7 +59071,17 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
58918
59071
|
const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
58919
59072
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
58920
59073
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
58921
|
-
requirementIds: blocking.map((item) => item.requirementId)
|
|
59074
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
59075
|
+
requirements: blocking.map((item) => {
|
|
59076
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
59077
|
+
return {
|
|
59078
|
+
requirementId: item.requirementId,
|
|
59079
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
59080
|
+
reason: item.reason,
|
|
59081
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
59082
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
59083
|
+
};
|
|
59084
|
+
})
|
|
58922
59085
|
});
|
|
58923
59086
|
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
58924
59087
|
if (requestedOutcome === "verified" && accepted.length)
|
|
@@ -58945,6 +59108,52 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
58945
59108
|
});
|
|
58946
59109
|
}
|
|
58947
59110
|
|
|
59111
|
+
// packages/workit-core/src/core/methods.ts
|
|
59112
|
+
var METHODS = {
|
|
59113
|
+
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
59114
|
+
"workit-behavioral-tdd": { dimensions: ["testing"] },
|
|
59115
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
|
|
59116
|
+
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
59117
|
+
"workit-implement": { dimensions: ["delegation"] },
|
|
59118
|
+
"workit-debug": { ruleIds: ["root-cause-investigation"] },
|
|
59119
|
+
"workit-handoff": { ruleIds: ["durable-handoff"] },
|
|
59120
|
+
"workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
|
|
59121
|
+
};
|
|
59122
|
+
var methodMatches = (id, definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true && !(id === "workit-review" && requirement.ruleId === "self-review") || definition.ruleIds?.includes(requirement.ruleId) === true;
|
|
59123
|
+
var assuranceFor = (id, definition, capabilities) => {
|
|
59124
|
+
const keys = new Set([
|
|
59125
|
+
id,
|
|
59126
|
+
...definition.dimensions ?? [],
|
|
59127
|
+
...definition.ruleIds ?? []
|
|
59128
|
+
]);
|
|
59129
|
+
const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
|
|
59130
|
+
if (id === "workit-review" && matches.length === 0)
|
|
59131
|
+
return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
|
|
59132
|
+
if (matches.some((capability) => capability.assurance === "unavailable"))
|
|
59133
|
+
return {
|
|
59134
|
+
assurance: "unavailable",
|
|
59135
|
+
reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
|
|
59136
|
+
};
|
|
59137
|
+
if (matches.some((capability) => capability.assurance === "enforced"))
|
|
59138
|
+
return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
|
|
59139
|
+
return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
|
|
59140
|
+
};
|
|
59141
|
+
function selectMethods(policy, capabilities) {
|
|
59142
|
+
return Object.entries(METHODS).flatMap(([id, definition]) => {
|
|
59143
|
+
const requirements = policy.requirements.filter((requirement) => methodMatches(id, definition, requirement));
|
|
59144
|
+
if (requirements.length === 0)
|
|
59145
|
+
return [];
|
|
59146
|
+
const capability = assuranceFor(id, definition, capabilities);
|
|
59147
|
+
const reasons = [
|
|
59148
|
+
...new Set(requirements.map((requirement) => requirement.reason)),
|
|
59149
|
+
...capability.reasons
|
|
59150
|
+
];
|
|
59151
|
+
if (capability.assurance === "unavailable")
|
|
59152
|
+
reasons.unshift("required capability is unavailable");
|
|
59153
|
+
return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
|
|
59154
|
+
});
|
|
59155
|
+
}
|
|
59156
|
+
|
|
58948
59157
|
// packages/workit-core/src/core/task-context.ts
|
|
58949
59158
|
function reconcileResume(view, observations = []) {
|
|
58950
59159
|
if (observations.length > 0)
|
|
@@ -58954,7 +59163,7 @@ function reconcileResume(view, observations = []) {
|
|
|
58954
59163
|
return candidate;
|
|
58955
59164
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
58956
59165
|
const blockers = [...view.task.progress.blockers];
|
|
58957
|
-
if (view.task.workers.some((entry) =>
|
|
59166
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
58958
59167
|
blockers.push({
|
|
58959
59168
|
reason: "worker state requires reconciliation",
|
|
58960
59169
|
dependentAction: "resume",
|
|
@@ -59004,6 +59213,11 @@ function compactTaskContext(view) {
|
|
|
59004
59213
|
digest: entry.data.digest,
|
|
59005
59214
|
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
|
|
59006
59215
|
}));
|
|
59216
|
+
const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
59217
|
+
id: method.id,
|
|
59218
|
+
assurance: method.assurance,
|
|
59219
|
+
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
59220
|
+
})) : [];
|
|
59007
59221
|
const gaps = Array.from(new Set([
|
|
59008
59222
|
...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
|
|
59009
59223
|
...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
@@ -59013,15 +59227,18 @@ function compactTaskContext(view) {
|
|
|
59013
59227
|
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
59014
59228
|
status: view.task.status,
|
|
59015
59229
|
decisions,
|
|
59230
|
+
methods,
|
|
59016
59231
|
gaps,
|
|
59017
59232
|
nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
|
|
59018
59233
|
};
|
|
59019
59234
|
let encoded = JSON.stringify(context);
|
|
59020
|
-
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.gaps.length)) {
|
|
59235
|
+
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
|
|
59021
59236
|
if (context.gaps.length)
|
|
59022
59237
|
context.gaps.pop();
|
|
59023
|
-
else
|
|
59238
|
+
else if (context.decisions.length)
|
|
59024
59239
|
context.decisions.pop();
|
|
59240
|
+
else
|
|
59241
|
+
context.methods.pop();
|
|
59025
59242
|
encoded = JSON.stringify(context);
|
|
59026
59243
|
}
|
|
59027
59244
|
if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
|
|
@@ -59038,6 +59255,7 @@ var exportDigest = (bundle) => sha256({
|
|
|
59038
59255
|
task: bundle.task
|
|
59039
59256
|
});
|
|
59040
59257
|
// packages/workit-core/src/core/external-action.ts
|
|
59258
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
59041
59259
|
var externalActionSchema = discriminatedUnion("operation", [
|
|
59042
59260
|
object({
|
|
59043
59261
|
operation: literal("git.branch_setup"),
|
|
@@ -59050,7 +59268,11 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
59050
59268
|
}).strict(),
|
|
59051
59269
|
object({
|
|
59052
59270
|
operation: literal("git.commit"),
|
|
59053
|
-
payload: object({
|
|
59271
|
+
payload: object({
|
|
59272
|
+
message: string2().min(1).optional(),
|
|
59273
|
+
plan_steps: array(string2().min(1)).optional(),
|
|
59274
|
+
plan_branch: string2().min(1).optional()
|
|
59275
|
+
}).strict()
|
|
59054
59276
|
}).strict(),
|
|
59055
59277
|
object({
|
|
59056
59278
|
operation: literal("git.push"),
|
|
@@ -59139,7 +59361,7 @@ var externalActionDescriptor = (operation, payload) => canonicalJson({
|
|
|
59139
59361
|
payload,
|
|
59140
59362
|
...payload && typeof payload === "object" && !Array.isArray(payload) && "resolved" in payload && payload.resolved && typeof payload.resolved === "object" && Array.isArray(payload.resolved.steps) ? { steps: payload.resolved.steps } : {}
|
|
59141
59363
|
});
|
|
59142
|
-
var externalActionHelp = "Fixed actions: git.branch_setup {action?,sdd_dir?,target_branch
|
|
59364
|
+
var externalActionHelp = "Fixed actions: git.branch_setup {action?,sdd_dir?,target_branch(required unless reapply_stash; the working branch to create or switch to),stash?}; git.commit {message}; git.push {branch?}; hosting.pull_request {title,body?,draft?,target_branch?,babysit?}; youtrack.update {issueId,markdown,minutes?}; youtrack.time {issueId,minutes,text?,dateMs?}; youtrack.meeting {issueId,minutes,text}; changelog.apply {entries?,path?,normalize_only?}; context.read {kind,range?,issueId?,issueUrl?,issueRef?,mode?,specPath?,planPath?}. context.read is read-only and needs no approval; all other operations require native approval (CLI uses a TTY; caller-unattested MCP cannot mutate).";
|
|
59143
59365
|
var externalActionRef = (host, actor, descriptor) => ({
|
|
59144
59366
|
kind: "host",
|
|
59145
59367
|
host,
|
|
@@ -59190,6 +59412,92 @@ var priorExternalAction = (store, host, actor, operation, payload) => {
|
|
|
59190
59412
|
return failure("permission_denied", candidates.length === 0 ? "no prior external action matches this request" : "multiple prior external actions match this request");
|
|
59191
59413
|
return success(workspace.data.revision, null, { ...candidates[0], workspace: workspace.data });
|
|
59192
59414
|
};
|
|
59415
|
+
var planCommitDescriptor = (content) => {
|
|
59416
|
+
try {
|
|
59417
|
+
const value = JSON.parse(content);
|
|
59418
|
+
if (value.operation !== "git.commit" || !value.payload)
|
|
59419
|
+
return null;
|
|
59420
|
+
const steps = value.payload.plan_steps;
|
|
59421
|
+
const branch = value.payload.plan_branch;
|
|
59422
|
+
if (!Array.isArray(steps) || steps.some((step) => typeof step !== "string" || !step) || typeof branch !== "string" || !branch)
|
|
59423
|
+
return null;
|
|
59424
|
+
return { steps, branch };
|
|
59425
|
+
} catch {
|
|
59426
|
+
return null;
|
|
59427
|
+
}
|
|
59428
|
+
};
|
|
59429
|
+
var approvedPlanCommit = (store, host, actor, message) => {
|
|
59430
|
+
if (typeof message !== "string" || !message)
|
|
59431
|
+
return failure("invalid_input", "commit message is required");
|
|
59432
|
+
const listed = store.listTasks();
|
|
59433
|
+
const workspace = store.readWorkspace();
|
|
59434
|
+
if (!listed.ok || !workspace.ok)
|
|
59435
|
+
return failure("storage_error", "external action state is unavailable");
|
|
59436
|
+
if (!workspace.data)
|
|
59437
|
+
return failure("not_found", "workspace not found");
|
|
59438
|
+
const matches = listed.data.flatMap((task) => {
|
|
59439
|
+
if (task.status !== "active" || task.workspaceId !== workspace.data.id)
|
|
59440
|
+
return [];
|
|
59441
|
+
if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
|
|
59442
|
+
return [];
|
|
59443
|
+
return task.decisions.flatMap((entry) => {
|
|
59444
|
+
const decision = entry.data;
|
|
59445
|
+
if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
|
|
59446
|
+
return [];
|
|
59447
|
+
if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0)
|
|
59448
|
+
return [];
|
|
59449
|
+
const plan = planCommitDescriptor(decision.binding.approvedContent);
|
|
59450
|
+
if (!plan)
|
|
59451
|
+
return [];
|
|
59452
|
+
const completed = task.actionProgress?.find((progress) => progress.decisionId === entry.id)?.completedSteps ?? [];
|
|
59453
|
+
if (plan.steps[completed.length] !== message)
|
|
59454
|
+
return [];
|
|
59455
|
+
return [{ task, entry, steps: plan.steps, branch: plan.branch, completed: [...completed] }];
|
|
59456
|
+
});
|
|
59457
|
+
});
|
|
59458
|
+
if (matches.length !== 1)
|
|
59459
|
+
return failure("permission_denied", matches.length === 0 ? "no plan commit authorization matches this message" : "multiple plan authorizations match this message");
|
|
59460
|
+
return success(workspace.data.revision, null, matches[0]);
|
|
59461
|
+
};
|
|
59462
|
+
var commitMessageFromDescriptor = (operation) => {
|
|
59463
|
+
try {
|
|
59464
|
+
const descriptor = JSON.parse(operation);
|
|
59465
|
+
return descriptor.operation === "git.commit" && typeof descriptor.payload?.message === "string" ? descriptor.payload.message : undefined;
|
|
59466
|
+
} catch {
|
|
59467
|
+
return;
|
|
59468
|
+
}
|
|
59469
|
+
};
|
|
59470
|
+
var planCommitBinding = (store, host, actor, operation) => {
|
|
59471
|
+
const message = commitMessageFromDescriptor(operation);
|
|
59472
|
+
if (!message)
|
|
59473
|
+
return null;
|
|
59474
|
+
const plan = approvedPlanCommit(store, host, actor, message);
|
|
59475
|
+
if (!plan.ok)
|
|
59476
|
+
return null;
|
|
59477
|
+
let branch;
|
|
59478
|
+
try {
|
|
59479
|
+
branch = execFileSync2("git", ["branch", "--show-current"], {
|
|
59480
|
+
cwd: store.root,
|
|
59481
|
+
encoding: "utf8",
|
|
59482
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
59483
|
+
}).trim();
|
|
59484
|
+
} catch {
|
|
59485
|
+
return null;
|
|
59486
|
+
}
|
|
59487
|
+
if (!branch || branch !== plan.data.branch)
|
|
59488
|
+
return null;
|
|
59489
|
+
const workspace = store.readWorkspace();
|
|
59490
|
+
if (!workspace.ok || !workspace.data)
|
|
59491
|
+
return null;
|
|
59492
|
+
return {
|
|
59493
|
+
taskId: plan.data.task.id,
|
|
59494
|
+
decisionId: plan.data.entry.id,
|
|
59495
|
+
binding: plan.data.entry.data.binding,
|
|
59496
|
+
expectedRevision: plan.data.task.revision,
|
|
59497
|
+
expectedWorkspaceRevision: workspace.data.revision,
|
|
59498
|
+
step: message
|
|
59499
|
+
};
|
|
59500
|
+
};
|
|
59193
59501
|
var approvedExternalAction = (store, host, actor, operation) => {
|
|
59194
59502
|
const selected = externalActionState(store, host, actor, operation);
|
|
59195
59503
|
if (!selected.ok)
|
|
@@ -59198,6 +59506,23 @@ var approvedExternalAction = (store, host, actor, operation) => {
|
|
|
59198
59506
|
return failure(selected.data.entry.data.consumption.state === "uncertain" ? "external_outcome_unknown" : "permission_denied", selected.data.entry.data.consumption.state === "uncertain" ? "previous external action outcome is unknown" : "external action was already settled");
|
|
59199
59507
|
return selected;
|
|
59200
59508
|
};
|
|
59509
|
+
var priorResolvedDrift = (store, host, actor, operation, payload, currentResolved) => {
|
|
59510
|
+
const prior = priorExternalAction(store, host, actor, operation, payload);
|
|
59511
|
+
if (!prior.ok)
|
|
59512
|
+
return success(null, null, null);
|
|
59513
|
+
let baseline;
|
|
59514
|
+
try {
|
|
59515
|
+
const parsed = JSON.parse(prior.data.entry.data.binding.approvedContent);
|
|
59516
|
+
baseline = parsed.payload?.resolved;
|
|
59517
|
+
} catch {
|
|
59518
|
+
return success(null, null, null);
|
|
59519
|
+
}
|
|
59520
|
+
if (baseline === undefined || baseline === null)
|
|
59521
|
+
return success(null, null, null);
|
|
59522
|
+
if (canonicalJson(baseline) === canonicalJson(currentResolved))
|
|
59523
|
+
return success(null, null, null);
|
|
59524
|
+
return failure("invalid_input", "repository state changed since approval; show the current state and approve the action again", { outcome: "not_started", operation });
|
|
59525
|
+
};
|
|
59201
59526
|
var createAuthorizedExternalActionRunner = (core, bind) => async (operation, effect) => {
|
|
59202
59527
|
let steps = [];
|
|
59203
59528
|
try {
|
|
@@ -59456,7 +59781,7 @@ function resolveRequirements(input) {
|
|
|
59456
59781
|
].join(", ")}`,
|
|
59457
59782
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
59458
59783
|
before: "dependent_action",
|
|
59459
|
-
dependentAction:
|
|
59784
|
+
dependentAction: null,
|
|
59460
59785
|
acceptanceAllowed: false
|
|
59461
59786
|
}));
|
|
59462
59787
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -59467,7 +59792,7 @@ function resolveRequirements(input) {
|
|
|
59467
59792
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
59468
59793
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
59469
59794
|
before: "dependent_action",
|
|
59470
|
-
dependentAction:
|
|
59795
|
+
dependentAction: null,
|
|
59471
59796
|
acceptanceAllowed: false
|
|
59472
59797
|
}));
|
|
59473
59798
|
if (signals.behaviorChange.value === true)
|
|
@@ -59625,59 +59950,6 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
59625
59950
|
return parsed.data;
|
|
59626
59951
|
}
|
|
59627
59952
|
|
|
59628
|
-
// packages/workit-core/src/core/workers.ts
|
|
59629
|
-
var same = (left, right) => {
|
|
59630
|
-
try {
|
|
59631
|
-
return canonicalJson(left) === canonicalJson(right);
|
|
59632
|
-
} catch {
|
|
59633
|
-
return false;
|
|
59634
|
-
}
|
|
59635
|
-
};
|
|
59636
|
-
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
59637
|
-
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
59638
|
-
const code = char.charCodeAt(0);
|
|
59639
|
-
return code < 32 || code === 127;
|
|
59640
|
-
}) && !value.split(/[\\/]/).includes("..");
|
|
59641
|
-
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
59642
|
-
var workerIdOf = (value) => value.workerId ?? null;
|
|
59643
|
-
function assertProductWriteAllowed(input) {
|
|
59644
|
-
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
59645
|
-
return failure("invalid_input", "invalid product write path");
|
|
59646
|
-
if (!input.store)
|
|
59647
|
-
return failure("permission_denied", "write authorization requires core state");
|
|
59648
|
-
const task = input.store.readTask(input.task.id);
|
|
59649
|
-
if (!task.ok)
|
|
59650
|
-
return task;
|
|
59651
|
-
const workspace = input.store.readWorkspace();
|
|
59652
|
-
if (!workspace.ok)
|
|
59653
|
-
return workspace;
|
|
59654
|
-
if (!workspace.data)
|
|
59655
|
-
return failure("not_found", "workspace not found");
|
|
59656
|
-
const workspaceRecord = workspace.data;
|
|
59657
|
-
if (task.data.status !== "active")
|
|
59658
|
-
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
59659
|
-
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
59660
|
-
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
59661
|
-
const writer = workspaceRecord.writer;
|
|
59662
|
-
if (!writer)
|
|
59663
|
-
return failure("permission_denied", "checkout has no writer owner");
|
|
59664
|
-
if (writer.state === "uncertain")
|
|
59665
|
-
return failure("recovery_required", "checkout writer requires recovery", {
|
|
59666
|
-
owner: writer.owner
|
|
59667
|
-
});
|
|
59668
|
-
const caller = callerValue(input.caller);
|
|
59669
|
-
const workerId = workerIdOf(input.caller);
|
|
59670
|
-
const owner = writer.owner;
|
|
59671
|
-
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
59672
|
-
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
59673
|
-
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
59674
|
-
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
59675
|
-
return failure("permission_denied", "only implementers can own product writes");
|
|
59676
|
-
if (workerId !== null && (!worker?.data.session || worker.data.state === "cancelling" || worker.data.state === "unknown" || worker.data.state === "stopped" || !same(worker.data.session, callerSession(input.caller))))
|
|
59677
|
-
return failure("recovery_required", "worker session is not an active writer");
|
|
59678
|
-
return success(null, workspaceRecord.revision, owner);
|
|
59679
|
-
}
|
|
59680
|
-
|
|
59681
59953
|
// packages/workit-core/src/core/task-engine.ts
|
|
59682
59954
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
59683
59955
|
kind,
|
|
@@ -59796,10 +60068,10 @@ var applyWorkerLifecycle = (input) => {
|
|
|
59796
60068
|
const notStarted = input.dispatch === true;
|
|
59797
60069
|
if (notStarted && (input.state !== "stopped" || input.session !== null))
|
|
59798
60070
|
return failure("invalid_input", "a never-dispatched worker stops with no session");
|
|
59799
|
-
if (notStarted &&
|
|
59800
|
-
return failure("invalid_transition", "worker is no longer awaiting dispatch");
|
|
59801
|
-
if (notStarted && (entry.data.session !== null || entry.data.state === "running"))
|
|
60071
|
+
if (notStarted && entry.data.session !== null)
|
|
59802
60072
|
return failure("invalid_transition", "worker already has an observed session");
|
|
60073
|
+
if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
|
|
60074
|
+
return failure("invalid_transition", "worker launch was not claimed");
|
|
59803
60075
|
if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
|
|
59804
60076
|
return failure("invalid_input", "running worker observations require a session");
|
|
59805
60077
|
if (entry.data.session && !sameValue(entry.data.session, input.session))
|
|
@@ -59808,15 +60080,20 @@ var applyWorkerLifecycle = (input) => {
|
|
|
59808
60080
|
return failure("invalid_transition", "worker has not started");
|
|
59809
60081
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
59810
60082
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
60083
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
59811
60084
|
const nextEntry = {
|
|
59812
60085
|
...entry,
|
|
59813
60086
|
recordedAt: input.now ?? entry.recordedAt,
|
|
59814
60087
|
provenance: authority.provenance,
|
|
59815
|
-
data: {
|
|
60088
|
+
data: {
|
|
60089
|
+
...entry.data,
|
|
60090
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
60091
|
+
session: input.session
|
|
60092
|
+
}
|
|
59816
60093
|
};
|
|
59817
60094
|
const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
59818
60095
|
const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
59819
|
-
if (entry.data.state ===
|
|
60096
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
59820
60097
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
59821
60098
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
59822
60099
|
taskId: task.data.id,
|
|
@@ -60186,7 +60463,7 @@ class WorkitCore {
|
|
|
60186
60463
|
activeWorkerBlocker(task, workspace) {
|
|
60187
60464
|
if (workspace.writer)
|
|
60188
60465
|
return failure("recovery_required", "writer ownership must be released first");
|
|
60189
|
-
if (task.workers.some((entry) =>
|
|
60466
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
60190
60467
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
60191
60468
|
return null;
|
|
60192
60469
|
}
|
|
@@ -60423,11 +60700,6 @@ class WorkitCore {
|
|
|
60423
60700
|
if (helper && !helper.ok)
|
|
60424
60701
|
return helper;
|
|
60425
60702
|
const evidence = input.evidence;
|
|
60426
|
-
if (helper?.ok) {
|
|
60427
|
-
const assignment = helper.data.data.assignment;
|
|
60428
|
-
if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || assignment.candidateId !== null && evidence.candidateId !== assignment.candidateId && evidence.beforeCandidateId !== assignment.candidateId || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
|
|
60429
|
-
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
60430
|
-
}
|
|
60431
60703
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
60432
60704
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
60433
60705
|
if (evidence.kind === "review") {
|
|
@@ -60444,6 +60716,11 @@ class WorkitCore {
|
|
|
60444
60716
|
evidence.beforeCandidateId ??= null;
|
|
60445
60717
|
evidence.candidateId ??= null;
|
|
60446
60718
|
}
|
|
60719
|
+
if (helper?.ok) {
|
|
60720
|
+
const assignment = helper.data.data.assignment;
|
|
60721
|
+
if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || assignment.candidateId !== null && evidence.candidateId !== assignment.candidateId && evidence.beforeCandidateId !== assignment.candidateId || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
|
|
60722
|
+
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
60723
|
+
}
|
|
60447
60724
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
60448
60725
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
60449
60726
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -60950,7 +61227,12 @@ class WorkitCore {
|
|
|
60950
61227
|
return failure("invalid_transition", "worker is not awaiting dispatch");
|
|
60951
61228
|
return success(mutation.revision, null, {
|
|
60952
61229
|
...current,
|
|
60953
|
-
workers: current.workers.map((item) => item.id === input.workerId ? {
|
|
61230
|
+
workers: current.workers.map((item) => item.id === input.workerId ? {
|
|
61231
|
+
...item,
|
|
61232
|
+
recordedAt: mutation.now,
|
|
61233
|
+
provenance: attested.data,
|
|
61234
|
+
data: { ...item.data, state: "dispatching" }
|
|
61235
|
+
} : item)
|
|
60954
61236
|
});
|
|
60955
61237
|
}
|
|
60956
61238
|
});
|
|
@@ -61334,6 +61616,9 @@ class WorkitCore {
|
|
|
61334
61616
|
id: task.id,
|
|
61335
61617
|
revision: task.revision,
|
|
61336
61618
|
workspaceRevision: workspace.data.revision,
|
|
61619
|
+
createdAt: task.createdAt,
|
|
61620
|
+
updatedAt: task.updatedAt,
|
|
61621
|
+
runtime: task.runtime ?? null,
|
|
61337
61622
|
objective: task.intent.data.objective,
|
|
61338
61623
|
status: task.status,
|
|
61339
61624
|
closure: task.closure,
|
|
@@ -61419,7 +61704,7 @@ class WorkitCore {
|
|
|
61419
61704
|
const base = reconcileResume(effectiveView);
|
|
61420
61705
|
if (!base.ok)
|
|
61421
61706
|
return base;
|
|
61422
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
61707
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
61423
61708
|
const blockers = [...base.data.blockers];
|
|
61424
61709
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
61425
61710
|
blockers.push({
|
|
@@ -61486,7 +61771,8 @@ class WorkitCore {
|
|
|
61486
61771
|
...current,
|
|
61487
61772
|
status,
|
|
61488
61773
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
61489
|
-
progress:
|
|
61774
|
+
progress: current.progress,
|
|
61775
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
61490
61776
|
})
|
|
61491
61777
|
});
|
|
61492
61778
|
if (!changed.ok)
|
|
@@ -61522,7 +61808,7 @@ class WorkitCore {
|
|
|
61522
61808
|
return workspace;
|
|
61523
61809
|
if (!workspace.data)
|
|
61524
61810
|
return failure("not_found", "workspace not found");
|
|
61525
|
-
if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state
|
|
61811
|
+
if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
61526
61812
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
61527
61813
|
this.fillRevisions(input, task.data, workspace.data);
|
|
61528
61814
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -61670,14 +61956,14 @@ function run(root, executable, args, env = {}) {
|
|
|
61670
61956
|
|
|
61671
61957
|
// packages/workit-core/src/core/branch.ts
|
|
61672
61958
|
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "node:fs";
|
|
61673
|
-
import { execFileSync as
|
|
61959
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
61674
61960
|
import path24 from "node:path";
|
|
61675
61961
|
|
|
61676
61962
|
// packages/workit-core/src/core/git.ts
|
|
61677
|
-
import { execFileSync as
|
|
61963
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
61678
61964
|
var run2 = (cwd, args) => {
|
|
61679
61965
|
try {
|
|
61680
|
-
const stdout =
|
|
61966
|
+
const stdout = execFileSync3("git", args, {
|
|
61681
61967
|
cwd,
|
|
61682
61968
|
encoding: "utf8",
|
|
61683
61969
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -61940,7 +62226,7 @@ var baseBranch = (cwd) => {
|
|
|
61940
62226
|
return { base: String(resolved.defaultTargetBranch ?? "develop") };
|
|
61941
62227
|
};
|
|
61942
62228
|
var originBaseReady = (cwd, base) => {
|
|
61943
|
-
const run = (args) =>
|
|
62229
|
+
const run = (args) => execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
61944
62230
|
try {
|
|
61945
62231
|
try {
|
|
61946
62232
|
run(["fetch", "origin", base, "--prune"]);
|
|
@@ -61948,7 +62234,7 @@ var originBaseReady = (cwd, base) => {
|
|
|
61948
62234
|
run(["fetch", "origin", "--prune"]);
|
|
61949
62235
|
}
|
|
61950
62236
|
try {
|
|
61951
|
-
|
|
62237
|
+
execFileSync4("git", ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${base}`], {
|
|
61952
62238
|
cwd,
|
|
61953
62239
|
stdio: "pipe"
|
|
61954
62240
|
});
|
|
@@ -61967,11 +62253,11 @@ var originBaseReady = (cwd, base) => {
|
|
|
61967
62253
|
}
|
|
61968
62254
|
};
|
|
61969
62255
|
var fastForwardBase = (cwd, base) => {
|
|
61970
|
-
const run = (args) =>
|
|
62256
|
+
const run = (args) => execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
61971
62257
|
try {
|
|
61972
62258
|
let hasLocalBase = true;
|
|
61973
62259
|
try {
|
|
61974
|
-
|
|
62260
|
+
execFileSync4("git", ["show-ref", "--verify", "--quiet", `refs/heads/${base}`], {
|
|
61975
62261
|
cwd,
|
|
61976
62262
|
stdio: "pipe"
|
|
61977
62263
|
});
|
|
@@ -62012,10 +62298,10 @@ var branchSetup = ({
|
|
|
62012
62298
|
log
|
|
62013
62299
|
}) => {
|
|
62014
62300
|
const cwd = path24.resolve(workspace_root);
|
|
62015
|
-
const exec = (args) =>
|
|
62301
|
+
const exec = (args) => execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
62016
62302
|
const current = gitContext(cwd).branch;
|
|
62017
62303
|
if (!current || current === "unknown")
|
|
62018
|
-
return { error: "not in a git repository" };
|
|
62304
|
+
return { error: "not in a git repository", phase: "preflight" };
|
|
62019
62305
|
const sdd = sdd_dir ?? "docs";
|
|
62020
62306
|
const manifestPath = path24.isAbsolute(sdd) ? path24.join(sdd, "manifest.json") : path24.join(cwd, sdd, "manifest.json");
|
|
62021
62307
|
const ensureManifestDir = () => {
|
|
@@ -62023,7 +62309,6 @@ var branchSetup = ({
|
|
|
62023
62309
|
if (!existsSync17(dir))
|
|
62024
62310
|
mkdirSync8(dir, { recursive: true, mode: 493 });
|
|
62025
62311
|
};
|
|
62026
|
-
ensureManifestDir();
|
|
62027
62312
|
const readManifest = () => {
|
|
62028
62313
|
try {
|
|
62029
62314
|
return JSON.parse(readFileSync15(manifestPath, "utf8"));
|
|
@@ -62041,13 +62326,16 @@ var branchSetup = ({
|
|
|
62041
62326
|
const manifest = readManifest();
|
|
62042
62327
|
const ref = manifest.stash_ref;
|
|
62043
62328
|
if (!ref)
|
|
62044
|
-
return { error: "no stash_ref in manifest" };
|
|
62329
|
+
return { error: "no stash_ref in manifest", phase: "preflight" };
|
|
62045
62330
|
journal(`pre-pop: ${String(ref)}`);
|
|
62046
62331
|
try {
|
|
62047
62332
|
exec(["stash", "pop", String(ref)]);
|
|
62048
62333
|
} catch (error) {
|
|
62049
62334
|
journal("pop: failed");
|
|
62050
|
-
return {
|
|
62335
|
+
return {
|
|
62336
|
+
error: error instanceof Error ? error.message : "stash pop failed",
|
|
62337
|
+
phase: "post_mutation"
|
|
62338
|
+
};
|
|
62051
62339
|
}
|
|
62052
62340
|
journal("pop: ok");
|
|
62053
62341
|
delete manifest.stash_ref;
|
|
@@ -62057,11 +62345,17 @@ var branchSetup = ({
|
|
|
62057
62345
|
}
|
|
62058
62346
|
const target = target_branch ?? "";
|
|
62059
62347
|
if (!target)
|
|
62060
|
-
return {
|
|
62348
|
+
return {
|
|
62349
|
+
error: "target_branch (the working branch to create or switch to) is required for setup",
|
|
62350
|
+
phase: "preflight"
|
|
62351
|
+
};
|
|
62061
62352
|
if (isProtected(cwd, target))
|
|
62062
|
-
return { error: `protected branch ${target}
|
|
62353
|
+
return { error: `protected branch ${target}`, phase: "preflight" };
|
|
62063
62354
|
if (!allowedBranch(cwd, target))
|
|
62064
|
-
return {
|
|
62355
|
+
return {
|
|
62356
|
+
error: `target branch ${target} is not allowed by the branch policy`,
|
|
62357
|
+
phase: "preflight"
|
|
62358
|
+
};
|
|
62065
62359
|
let base;
|
|
62066
62360
|
let targetExists = true;
|
|
62067
62361
|
try {
|
|
@@ -62072,12 +62366,12 @@ var branchSetup = ({
|
|
|
62072
62366
|
if (!targetExists) {
|
|
62073
62367
|
const baseResolved = baseBranch(cwd);
|
|
62074
62368
|
if ("error" in baseResolved)
|
|
62075
|
-
return { error: baseResolved.error };
|
|
62369
|
+
return { error: baseResolved.error, phase: "preflight" };
|
|
62076
62370
|
base = baseResolved.base;
|
|
62077
62371
|
}
|
|
62078
62372
|
journal(`entry: current=${current} target=${target} base=${base ?? "-"}`);
|
|
62079
62373
|
let stash_ref;
|
|
62080
|
-
const failAfterStash = (message) => {
|
|
62374
|
+
const failAfterStash = (message, phase = "post_mutation") => {
|
|
62081
62375
|
let suffix = "";
|
|
62082
62376
|
if (stash_ref) {
|
|
62083
62377
|
journal(`pre-pop: ${stash_ref}`);
|
|
@@ -62090,27 +62384,31 @@ var branchSetup = ({
|
|
|
62090
62384
|
suffix = " (changes preserved in stash)";
|
|
62091
62385
|
}
|
|
62092
62386
|
}
|
|
62093
|
-
return { error: `${message}${suffix}
|
|
62387
|
+
return { error: `${message}${suffix}`, phase: stash_ref ? "post_mutation" : phase };
|
|
62094
62388
|
};
|
|
62095
62389
|
if (current !== target) {
|
|
62096
62390
|
const dirty = Boolean(gitContext(cwd).status_short.trim());
|
|
62097
62391
|
if (dirty && stash !== "yes") {
|
|
62098
62392
|
return {
|
|
62099
|
-
error: "dirty working tree — ask with native question, then call workit_branch_setup with stash=yes"
|
|
62393
|
+
error: "dirty working tree — ask with native question, then call workit_branch_setup with stash=yes",
|
|
62394
|
+
phase: "preflight"
|
|
62100
62395
|
};
|
|
62101
62396
|
}
|
|
62102
62397
|
let validatedBase;
|
|
62103
62398
|
if (!targetExists && base !== undefined) {
|
|
62104
62399
|
const ready = originBaseReady(cwd, base);
|
|
62105
62400
|
if (!ready.ok)
|
|
62106
|
-
return { error: ready.error ?? "ensure-base-branch failed" };
|
|
62401
|
+
return { error: ready.error ?? "ensure-base-branch failed", phase: "preflight" };
|
|
62107
62402
|
validatedBase = base;
|
|
62108
62403
|
}
|
|
62109
62404
|
if (dirty) {
|
|
62110
62405
|
try {
|
|
62111
62406
|
exec(["stash", "push", "-u", "-m", `workit: pre-checkout ${target}`]);
|
|
62112
62407
|
} catch (error) {
|
|
62113
|
-
return {
|
|
62408
|
+
return {
|
|
62409
|
+
error: error instanceof Error ? error.message : "stash push failed",
|
|
62410
|
+
phase: "post_mutation"
|
|
62411
|
+
};
|
|
62114
62412
|
}
|
|
62115
62413
|
stash_ref = "stash@{0}";
|
|
62116
62414
|
journal(`stash push: ${stash_ref}`);
|
|
@@ -62121,14 +62419,14 @@ var branchSetup = ({
|
|
|
62121
62419
|
} catch (error) {
|
|
62122
62420
|
const message = error instanceof Error ? error.message : "checkout failed";
|
|
62123
62421
|
if (/worktree/i.test(message)) {
|
|
62124
|
-
return failAfterStash(`branch ${target} is locked by an existing git worktree — remove it first (we do not use worktrees)
|
|
62422
|
+
return failAfterStash(`branch ${target} is locked by an existing git worktree — remove it first (we do not use worktrees)`, "preflight");
|
|
62125
62423
|
}
|
|
62126
62424
|
try {
|
|
62127
62425
|
let effectiveBase = base;
|
|
62128
62426
|
if (effectiveBase === undefined) {
|
|
62129
62427
|
const lateResolved = baseBranch(cwd);
|
|
62130
62428
|
if ("error" in lateResolved)
|
|
62131
|
-
return failAfterStash(lateResolved.error);
|
|
62429
|
+
return failAfterStash(lateResolved.error, "preflight");
|
|
62132
62430
|
effectiveBase = lateResolved.base;
|
|
62133
62431
|
}
|
|
62134
62432
|
const baseResult = validatedBase !== undefined ? fastForwardBase(cwd, effectiveBase) : ensureBaseBranch(cwd, effectiveBase);
|
|
@@ -63957,6 +64255,94 @@ var safePath = (root, value) => {
|
|
|
63957
64255
|
return null;
|
|
63958
64256
|
}
|
|
63959
64257
|
};
|
|
64258
|
+
var branchCreationBaseline = (root, target, base) => {
|
|
64259
|
+
const exists = gitValue(root, ["rev-parse", "--verify", "--quiet", `refs/heads/${target}`]) !== null;
|
|
64260
|
+
if (exists)
|
|
64261
|
+
return { base_branch: base, target_exists: true, remote_base: null };
|
|
64262
|
+
const remote = run3(root, ["ls-remote", "origin", `refs/heads/${base}`]);
|
|
64263
|
+
const sha = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : "";
|
|
64264
|
+
return {
|
|
64265
|
+
base_branch: base,
|
|
64266
|
+
target_exists: false,
|
|
64267
|
+
remote_base: /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null
|
|
64268
|
+
};
|
|
64269
|
+
};
|
|
64270
|
+
var shortSha = (value) => typeof value === "string" && value ? value.slice(0, 8) : "unknown";
|
|
64271
|
+
var isProtectedBranch2 = (root, branch) => resolveBranchPolicyFor(root).protected.has(branch.toLowerCase());
|
|
64272
|
+
var actionProposalQuestion = (request, descriptorPayload) => {
|
|
64273
|
+
const payload = descriptorPayload ?? {};
|
|
64274
|
+
const resolved = payload.resolved ?? {};
|
|
64275
|
+
const payloadTarget = String(payload.target_branch ?? "");
|
|
64276
|
+
if (request.operation === "git.commit" && Array.isArray(payload.plan_steps)) {
|
|
64277
|
+
const steps = payload.plan_steps;
|
|
64278
|
+
return {
|
|
64279
|
+
presented: `Workit decision: action — Approve committing the plan's ${steps.length} listed tasks on \`${String(payload.plan_branch ?? "")}\`?`,
|
|
64280
|
+
approvedText: `Approve the ${steps.length} listed plan commits.`
|
|
64281
|
+
};
|
|
64282
|
+
}
|
|
64283
|
+
switch (request.operation) {
|
|
64284
|
+
case "git.branch_setup": {
|
|
64285
|
+
if (request.payload.action === "reapply_stash")
|
|
64286
|
+
return {
|
|
64287
|
+
presented: "Workit decision: action — Reapply the pre-checkout stash recorded in the branch manifest?",
|
|
64288
|
+
approvedText: "Reapply the recorded stash."
|
|
64289
|
+
};
|
|
64290
|
+
const target = String(payload.target_branch ?? "");
|
|
64291
|
+
if (resolved.target_exists === true)
|
|
64292
|
+
return {
|
|
64293
|
+
presented: `Workit decision: action — Switch to branch \`${target}\`?`,
|
|
64294
|
+
approvedText: `Switch to \`${target}\`.`
|
|
64295
|
+
};
|
|
64296
|
+
const base = String(resolved.base_branch ?? "the base branch");
|
|
64297
|
+
return {
|
|
64298
|
+
presented: `Workit decision: action — Create branch \`${target}\` from \`${base}\` (currently ${shortSha(resolved.remote_base)})?`,
|
|
64299
|
+
approvedText: `Create \`${target}\` from \`${base}\` at ${shortSha(resolved.remote_base)}.`
|
|
64300
|
+
};
|
|
64301
|
+
}
|
|
64302
|
+
case "git.commit": {
|
|
64303
|
+
const paths = Array.isArray(resolved.paths) ? resolved.paths : [];
|
|
64304
|
+
return {
|
|
64305
|
+
presented: `Workit decision: action — Commit ${paths.length} staged file(s) as "${String(request.payload.message)}"?`,
|
|
64306
|
+
approvedText: `Commit as "${String(request.payload.message)}".`
|
|
64307
|
+
};
|
|
64308
|
+
}
|
|
64309
|
+
case "git.push":
|
|
64310
|
+
return {
|
|
64311
|
+
presented: `Workit decision: action — Push branch \`${String(payload.branch ?? "")}\` to origin?`,
|
|
64312
|
+
approvedText: `Push \`${String(payload.branch ?? "")}\` to origin.`
|
|
64313
|
+
};
|
|
64314
|
+
case "hosting.pull_request":
|
|
64315
|
+
return {
|
|
64316
|
+
presented: `Workit decision: action — Open a PR from \`${String(resolved.source_branch ?? "")}\` to \`${payloadTarget}\` titled "${String(request.payload.title)}"?`,
|
|
64317
|
+
approvedText: `Open the PR: ${String(resolved.source_branch ?? "")} → ${payloadTarget}, "${String(request.payload.title)}".`
|
|
64318
|
+
};
|
|
64319
|
+
case "changelog.apply":
|
|
64320
|
+
return {
|
|
64321
|
+
presented: `Workit decision: action — Apply changelog changes at \`${String(payload.path ?? "CHANGELOG.md")}\`?`,
|
|
64322
|
+
approvedText: `Apply changelog changes at ${String(payload.path ?? "CHANGELOG.md")}.`
|
|
64323
|
+
};
|
|
64324
|
+
case "youtrack.update":
|
|
64325
|
+
return {
|
|
64326
|
+
presented: `Workit decision: action — Update YouTrack issue ${String(request.payload.issueId)}${request.payload.minutes ? ` and log ${String(request.payload.minutes)}m` : ""}?`,
|
|
64327
|
+
approvedText: `Update YouTrack issue ${String(request.payload.issueId)}.`
|
|
64328
|
+
};
|
|
64329
|
+
case "youtrack.time":
|
|
64330
|
+
return {
|
|
64331
|
+
presented: `Workit decision: action — Log ${String(request.payload.minutes)}m to YouTrack issue ${String(request.payload.issueId)}?`,
|
|
64332
|
+
approvedText: `Log time to YouTrack issue ${String(request.payload.issueId)}.`
|
|
64333
|
+
};
|
|
64334
|
+
case "youtrack.meeting":
|
|
64335
|
+
return {
|
|
64336
|
+
presented: `Workit decision: action — Log meeting time to YouTrack issue ${String(request.payload.issueId)}?`,
|
|
64337
|
+
approvedText: `Log meeting time to YouTrack issue ${String(request.payload.issueId)}.`
|
|
64338
|
+
};
|
|
64339
|
+
default:
|
|
64340
|
+
return {
|
|
64341
|
+
presented: `Workit decision: action — Approve ${request.operation}?`,
|
|
64342
|
+
approvedText: `Approve ${request.operation}.`
|
|
64343
|
+
};
|
|
64344
|
+
}
|
|
64345
|
+
};
|
|
63960
64346
|
var approvedResolvedExternalAction = (content) => {
|
|
63961
64347
|
try {
|
|
63962
64348
|
const value = JSON.parse(content);
|
|
@@ -64220,24 +64606,35 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
64220
64606
|
const sdd_dir = safePath(root, request.payload.sdd_dir ?? "docs");
|
|
64221
64607
|
if (!sdd_dir)
|
|
64222
64608
|
return failure("invalid_input", "sdd_dir must stay inside the workspace");
|
|
64609
|
+
const action = request.payload.action ?? "setup";
|
|
64223
64610
|
const policy = resolveBranchPolicyFor(root);
|
|
64224
|
-
const target_branch = request.payload.target_branch
|
|
64611
|
+
const target_branch = request.payload.target_branch;
|
|
64612
|
+
if (action !== "reapply_stash" && !target_branch)
|
|
64613
|
+
return failure("invalid_input", "target_branch (the working branch to create or switch to) is required for setup", {
|
|
64614
|
+
outcome: "not_started",
|
|
64615
|
+
fields: [{ path: "target_branch", reason: "required for action=setup" }]
|
|
64616
|
+
});
|
|
64225
64617
|
const head = gitValue(root, ["rev-parse", "HEAD"]);
|
|
64226
64618
|
if (!head)
|
|
64227
64619
|
return failure("storage_error", "current Git commit could not be resolved");
|
|
64620
|
+
const dirty = Boolean((gitRaw(root, ["status", "--short"]) ?? "").trim());
|
|
64621
|
+
const creation = action === "reapply_stash" || !target_branch ? { base_branch: policy.defaultTargetBranch } : branchCreationBaseline(root, target_branch, policy.defaultTargetBranch);
|
|
64228
64622
|
const normalized = {
|
|
64229
64623
|
operation: request.operation,
|
|
64230
64624
|
payload: {
|
|
64231
64625
|
...request.payload,
|
|
64232
|
-
action
|
|
64626
|
+
action,
|
|
64233
64627
|
sdd_dir,
|
|
64234
64628
|
stash: request.payload.stash ?? "no",
|
|
64235
|
-
target_branch
|
|
64629
|
+
...target_branch ? { target_branch } : {}
|
|
64236
64630
|
}
|
|
64237
64631
|
};
|
|
64238
64632
|
return success(null, null, {
|
|
64239
64633
|
request: normalized,
|
|
64240
|
-
descriptorPayload: {
|
|
64634
|
+
descriptorPayload: {
|
|
64635
|
+
...normalized.payload,
|
|
64636
|
+
resolved: { head, dirty, ...creation }
|
|
64637
|
+
}
|
|
64241
64638
|
});
|
|
64242
64639
|
}
|
|
64243
64640
|
case "git.push": {
|
|
@@ -64251,7 +64648,37 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
64251
64648
|
}) : failure("invalid_input", "git push requires a resolvable current branch");
|
|
64252
64649
|
}
|
|
64253
64650
|
case "git.commit": {
|
|
64651
|
+
const planSteps = request.payload.plan_steps;
|
|
64652
|
+
const planBranch = request.payload.plan_branch;
|
|
64254
64653
|
const head = gitValue(root, ["rev-parse", "HEAD"]);
|
|
64654
|
+
if (!head)
|
|
64655
|
+
return failure("storage_error", "Git state could not be resolved");
|
|
64656
|
+
const branch = gitValue(root, ["branch", "--show-current"]);
|
|
64657
|
+
if (planSteps !== undefined || planBranch !== undefined) {
|
|
64658
|
+
if (!Array.isArray(planSteps) || planSteps.some((step) => typeof step !== "string" || !step.trim()) || new Set(planSteps).size !== planSteps.length || planSteps.length === 0 || planSteps.length > 32)
|
|
64659
|
+
return failure("invalid_input", "plan_steps must be 1-32 unique non-empty commit messages", {
|
|
64660
|
+
outcome: "not_started",
|
|
64661
|
+
fields: [{ path: "plan_steps", reason: "unique strings" }]
|
|
64662
|
+
});
|
|
64663
|
+
if (typeof planBranch !== "string" || !planBranch.trim() || planBranch !== branch)
|
|
64664
|
+
return failure("invalid_input", "plan_branch must name the current working branch", {
|
|
64665
|
+
outcome: "not_started",
|
|
64666
|
+
fields: [{ path: "plan_branch", reason: "current branch" }]
|
|
64667
|
+
});
|
|
64668
|
+
if (isProtectedBranch2(root, planBranch))
|
|
64669
|
+
return failure("invalid_input", "plan_branch must not be a protected branch", {
|
|
64670
|
+
outcome: "not_started",
|
|
64671
|
+
fields: [{ path: "plan_branch", reason: "protected branch" }]
|
|
64672
|
+
});
|
|
64673
|
+
return success(null, null, {
|
|
64674
|
+
request,
|
|
64675
|
+
descriptorPayload: {
|
|
64676
|
+
plan_steps: planSteps,
|
|
64677
|
+
plan_branch: planBranch,
|
|
64678
|
+
resolved: { head, branch, steps: planSteps }
|
|
64679
|
+
}
|
|
64680
|
+
});
|
|
64681
|
+
}
|
|
64255
64682
|
const staged = gitRaw(root, [
|
|
64256
64683
|
"diff",
|
|
64257
64684
|
"--cached",
|
|
@@ -64262,7 +64689,13 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
64262
64689
|
"HEAD"
|
|
64263
64690
|
]);
|
|
64264
64691
|
const paths = stagedPaths(root);
|
|
64265
|
-
|
|
64692
|
+
const message = request.payload.message;
|
|
64693
|
+
if (typeof message !== "string" || !message)
|
|
64694
|
+
return failure("invalid_input", "git.commit requires a commit message", {
|
|
64695
|
+
outcome: "not_started",
|
|
64696
|
+
fields: [{ path: "message", reason: "required unless plan_steps is provided" }]
|
|
64697
|
+
});
|
|
64698
|
+
if (staged === null || paths === null)
|
|
64266
64699
|
return failure("storage_error", "Git state could not be resolved");
|
|
64267
64700
|
let policy;
|
|
64268
64701
|
try {
|
|
@@ -64277,13 +64710,13 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
64277
64710
|
`).filter(Boolean));
|
|
64278
64711
|
flavor = detected.flavor ?? "conventional";
|
|
64279
64712
|
}
|
|
64280
|
-
if (!matchCommitFlavor(
|
|
64713
|
+
if (!matchCommitFlavor(message, flavor, policy.pattern))
|
|
64281
64714
|
return failure("invalid_input", `Commit message does not match the ${flavor} flavor (commitPolicy.preset)`);
|
|
64282
64715
|
return success(null, null, {
|
|
64283
64716
|
request,
|
|
64284
64717
|
descriptorPayload: {
|
|
64285
64718
|
...request.payload,
|
|
64286
|
-
resolved: { head, staged: sha256(staged), paths }
|
|
64719
|
+
resolved: { head, branch, staged: sha256(staged), paths }
|
|
64287
64720
|
}
|
|
64288
64721
|
});
|
|
64289
64722
|
}
|
|
@@ -64453,10 +64886,24 @@ var executeConcreteExternalAction = async (request, root, marker, dateMs, step,
|
|
|
64453
64886
|
sdd_dir: request.payload.sdd_dir ?? "docs",
|
|
64454
64887
|
workspace_root: root
|
|
64455
64888
|
});
|
|
64456
|
-
|
|
64889
|
+
if ("error" in result) {
|
|
64890
|
+
if (result.phase === "preflight")
|
|
64891
|
+
return failure("invalid_input", result.error, {
|
|
64892
|
+
outcome: "not_started",
|
|
64893
|
+
operation: request.operation
|
|
64894
|
+
});
|
|
64895
|
+
return unknown2(request.operation);
|
|
64896
|
+
}
|
|
64897
|
+
return success(null, null, result);
|
|
64457
64898
|
}
|
|
64458
64899
|
case "git.commit": {
|
|
64459
|
-
const
|
|
64900
|
+
const message = request.payload.message;
|
|
64901
|
+
if (typeof message !== "string" || !message)
|
|
64902
|
+
return failure("invalid_input", "git.commit requires a commit message", {
|
|
64903
|
+
outcome: "not_started",
|
|
64904
|
+
fields: [{ path: "message", reason: "required" }]
|
|
64905
|
+
});
|
|
64906
|
+
const result = run3(root, ["commit", "-m", message]);
|
|
64460
64907
|
return result.exitCode === 0 ? success(null, null, { stdout: result.stdout.trim() }) : unknown2(request.operation);
|
|
64461
64908
|
}
|
|
64462
64909
|
case "git.push": {
|
|
@@ -64862,7 +65309,29 @@ async function runTaskCommand(argv, deps = {}) {
|
|
|
64862
65309
|
}
|
|
64863
65310
|
const core = new WorkitCore(new TaskStore(root), contextFor(root, parsed.parsed.actor !== undefined ? { ...deps, actor: parsed.parsed.actor } : deps, observedConfirmation ? "host_observed" : "agent_reported"));
|
|
64864
65311
|
let result;
|
|
64865
|
-
|
|
65312
|
+
const decisionBudgetIssue = (() => {
|
|
65313
|
+
if (parsed.parsed.family !== "decision" || parsed.parsed.action !== "record")
|
|
65314
|
+
return null;
|
|
65315
|
+
const request = parsed.parsed.request;
|
|
65316
|
+
if (typeof request.purpose !== "string" || !request.binding)
|
|
65317
|
+
return null;
|
|
65318
|
+
return workitBindingQuestionIssue([
|
|
65319
|
+
{
|
|
65320
|
+
header: `Workit decision: ${request.purpose}`,
|
|
65321
|
+
question: typeof request.binding.presented === "string" ? request.binding.presented : "",
|
|
65322
|
+
options: [
|
|
65323
|
+
{
|
|
65324
|
+
label: "approved",
|
|
65325
|
+
description: typeof request.binding.displayed === "string" ? request.binding.displayed : typeof request.binding.approvedContent === "string" ? request.binding.approvedContent : ""
|
|
65326
|
+
},
|
|
65327
|
+
{ label: "rejected", description: "Reject this decision" }
|
|
65328
|
+
]
|
|
65329
|
+
}
|
|
65330
|
+
]);
|
|
65331
|
+
})();
|
|
65332
|
+
if (decisionBudgetIssue)
|
|
65333
|
+
result = failure("invalid_input", decisionBudgetIssue);
|
|
65334
|
+
else if (parsed.parsed.handoff) {
|
|
64866
65335
|
const exported = dispatch(core, "state", parsed.parsed.request);
|
|
64867
65336
|
if (!exported.ok)
|
|
64868
65337
|
result = exported;
|
|
@@ -64926,8 +65395,34 @@ var cliActionAuthority = (actor, reconciliationTokens = new WeakSet) => ({
|
|
|
64926
65395
|
var nativeExternalActionRunner = (root, actor, core, step) => createAuthorizedExternalActionRunner(core, (operationValue) => {
|
|
64927
65396
|
const store = new TaskStore(root);
|
|
64928
65397
|
const approved = approvedExternalAction(store, "workit_cli", actor, operationValue);
|
|
64929
|
-
if (!approved.ok)
|
|
65398
|
+
if (!approved.ok) {
|
|
65399
|
+
const plan = planCommitBinding(store, "workit_cli", actor, operationValue);
|
|
65400
|
+
if (plan) {
|
|
65401
|
+
const actionRef = externalActionRef("workit_cli", actor, operationValue);
|
|
65402
|
+
return {
|
|
65403
|
+
taskId: plan.taskId,
|
|
65404
|
+
decisionId: plan.decisionId,
|
|
65405
|
+
actionRef,
|
|
65406
|
+
expectedRevision: plan.expectedRevision,
|
|
65407
|
+
expectedWorkspaceRevision: plan.expectedWorkspaceRevision,
|
|
65408
|
+
binding: plan.binding,
|
|
65409
|
+
step: plan.step,
|
|
65410
|
+
refresh: () => {
|
|
65411
|
+
const task = store.readTask(plan.taskId);
|
|
65412
|
+
const freshWorkspace = store.readWorkspace();
|
|
65413
|
+
if (!task.ok || !freshWorkspace.ok || !freshWorkspace.data)
|
|
65414
|
+
throw new Error("external action state changed");
|
|
65415
|
+
return {
|
|
65416
|
+
expectedRevision: task.data.revision,
|
|
65417
|
+
expectedWorkspaceRevision: freshWorkspace.data.revision
|
|
65418
|
+
};
|
|
65419
|
+
},
|
|
65420
|
+
reserveObservation: nativeExternalActionObservation(actor, actionRef, "reserve"),
|
|
65421
|
+
settleObservation: (outcome, revisions) => nativeExternalActionObservation(actor, actionRef, outcome, actionRef.kind === "host" ? actionRef.handle : "external-action", revisions)
|
|
65422
|
+
};
|
|
65423
|
+
}
|
|
64930
65424
|
return approved;
|
|
65425
|
+
}
|
|
64931
65426
|
const actionRef = externalActionRef("workit_cli", actor, operationValue);
|
|
64932
65427
|
return {
|
|
64933
65428
|
taskId: approved.data.task.id,
|
|
@@ -65124,8 +65619,56 @@ async function runActionCommand(argv, deps = {}) {
|
|
|
65124
65619
|
printHuman(result, deps);
|
|
65125
65620
|
return 1;
|
|
65126
65621
|
}
|
|
65622
|
+
const drift = normalized.operation === "git.branch_setup" ? priorResolvedDrift(store, "workit_cli", actor, normalized.operation, normalized.payload, resolved.data.descriptorPayload.resolved) : success(null, null, null);
|
|
65623
|
+
if (!drift.ok) {
|
|
65624
|
+
if (json)
|
|
65625
|
+
jsonResult(outOf(deps), drift);
|
|
65626
|
+
else
|
|
65627
|
+
printHuman(drift, deps);
|
|
65628
|
+
return 1;
|
|
65629
|
+
}
|
|
65630
|
+
const localOperation = [
|
|
65631
|
+
"git.branch_setup",
|
|
65632
|
+
"git.commit",
|
|
65633
|
+
"git.push",
|
|
65634
|
+
"hosting.pull_request",
|
|
65635
|
+
"changelog.apply"
|
|
65636
|
+
].includes(normalized.operation);
|
|
65637
|
+
if (localOperation) {
|
|
65638
|
+
const writer = assertLocalExternalActionWriter(root, { host: "workit_cli", actor });
|
|
65639
|
+
if (!writer.ok) {
|
|
65640
|
+
const result = failure("needs_input", "writer ownership is required before this action", {
|
|
65641
|
+
outcome: "not_started",
|
|
65642
|
+
operation: normalized.operation,
|
|
65643
|
+
guidance: "Acquire checkout writer ownership first (writer.acquire) and retry the action."
|
|
65644
|
+
});
|
|
65645
|
+
if (json)
|
|
65646
|
+
jsonResult(outOf(deps), result);
|
|
65647
|
+
else
|
|
65648
|
+
printHuman(result, deps);
|
|
65649
|
+
return 1;
|
|
65650
|
+
}
|
|
65651
|
+
}
|
|
65652
|
+
const question = actionProposalQuestion(normalized, resolved.data.descriptorPayload);
|
|
65653
|
+
const commitMessage = normalized.operation === "git.commit" ? normalized.payload.message : undefined;
|
|
65654
|
+
const currentBranch = resolved.data.descriptorPayload.resolved?.branch;
|
|
65655
|
+
const plan = typeof commitMessage === "string" && commitMessage ? approvedPlanCommit(store, "workit_cli", actor, commitMessage) : null;
|
|
65656
|
+
if (plan?.ok && plan.data.branch === currentBranch) {
|
|
65657
|
+
const core = new WorkitCore(store, {
|
|
65658
|
+
...contextFor(root, { ...deps, actor }, "host_observed"),
|
|
65659
|
+
nativeAuthority: cliActionAuthority(actor),
|
|
65660
|
+
workerId: null
|
|
65661
|
+
});
|
|
65662
|
+
const runner = nativeExternalActionRunner(root, actor, core);
|
|
65663
|
+
const result = await runner(descriptor, (step) => executeResolvedExternalAction(resolved.data, root, step, { host: "workit_cli", actor }));
|
|
65664
|
+
if (json)
|
|
65665
|
+
jsonResult(outOf(deps), result);
|
|
65666
|
+
else
|
|
65667
|
+
printHuman(result, deps);
|
|
65668
|
+
return result.ok ? 0 : 1;
|
|
65669
|
+
}
|
|
65127
65670
|
if (!json)
|
|
65128
|
-
write2(outOf(deps),
|
|
65671
|
+
write2(outOf(deps), question.presented);
|
|
65129
65672
|
const accepted = await observeConsent(deps);
|
|
65130
65673
|
if (!accepted.accepted || !accepted.observed) {
|
|
65131
65674
|
const result = failure("permission_denied", "external action was not confirmed by the terminal");
|
|
@@ -65151,8 +65694,9 @@ async function runActionCommand(argv, deps = {}) {
|
|
|
65151
65694
|
taskId: task.id,
|
|
65152
65695
|
workspaceId: workspace.data.id,
|
|
65153
65696
|
scope: task.intent.data.scope,
|
|
65154
|
-
presented:
|
|
65697
|
+
presented: question.presented,
|
|
65155
65698
|
approvedContent: descriptor,
|
|
65699
|
+
displayed: question.approvedText,
|
|
65156
65700
|
contentRefs: []
|
|
65157
65701
|
},
|
|
65158
65702
|
response: "approved",
|
|
@@ -65169,6 +65713,15 @@ async function runActionCommand(argv, deps = {}) {
|
|
|
65169
65713
|
printHuman(decision, deps);
|
|
65170
65714
|
return 1;
|
|
65171
65715
|
}
|
|
65716
|
+
const planSteps = resolved.data.descriptorPayload.plan_steps;
|
|
65717
|
+
if (normalized.operation === "git.commit" && Array.isArray(planSteps)) {
|
|
65718
|
+
const result = success(null, null, { plan_commits: planSteps.length });
|
|
65719
|
+
if (json)
|
|
65720
|
+
jsonResult(outOf(deps), result);
|
|
65721
|
+
else
|
|
65722
|
+
printHuman(result, deps);
|
|
65723
|
+
return 0;
|
|
65724
|
+
}
|
|
65172
65725
|
const runner = nativeExternalActionRunner(root, actor, core);
|
|
65173
65726
|
const result = await runner(descriptor, (step) => executeResolvedExternalAction(resolved.data, root, step, { host: "workit_cli", actor }));
|
|
65174
65727
|
if (json)
|