@brainervirus/workit-cli 1.0.9 → 1.0.11
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 +722 -139
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -50927,10 +50927,12 @@ var decisionSchema = object({
|
|
|
50927
50927
|
scope: scopeSchema,
|
|
50928
50928
|
presented: text,
|
|
50929
50929
|
approvedContent: text,
|
|
50930
|
-
|
|
50930
|
+
displayed: text.optional(),
|
|
50931
|
+
contentRefs: array(refSchema),
|
|
50932
|
+
statedChoice: object({ ref: text, text }).strict().optional()
|
|
50931
50933
|
}).strict(),
|
|
50932
50934
|
digest,
|
|
50933
|
-
response: _enum(["approved", "rejected"]),
|
|
50935
|
+
response: _enum(["approved", "rejected", "stated"]),
|
|
50934
50936
|
requirementIds: array(digest),
|
|
50935
50937
|
revoked: object({ at: utc, reason: text }).strict().nullable(),
|
|
50936
50938
|
consumption: object({
|
|
@@ -50939,6 +50941,27 @@ var decisionSchema = object({
|
|
|
50939
50941
|
actionRef: refSchema
|
|
50940
50942
|
}).strict().nullable()
|
|
50941
50943
|
}).strict();
|
|
50944
|
+
var BINDING_QUESTION_BUDGET = 300;
|
|
50945
|
+
var bindingQuestionHeader = /^Workit decision: (design|action|limitation|preference)$/;
|
|
50946
|
+
var workitBindingQuestionIssue = (questions) => {
|
|
50947
|
+
if (!Array.isArray(questions) || questions.length !== 1)
|
|
50948
|
+
return null;
|
|
50949
|
+
const question = questions[0];
|
|
50950
|
+
if (!question || typeof question !== "object")
|
|
50951
|
+
return null;
|
|
50952
|
+
if (typeof question.header !== "string" || !bindingQuestionHeader.test(question.header.trim()))
|
|
50953
|
+
return null;
|
|
50954
|
+
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.`;
|
|
50955
|
+
if (typeof question.question === "string" && question.question.length > BINDING_QUESTION_BUDGET)
|
|
50956
|
+
return over("the question text", question.question);
|
|
50957
|
+
if (Array.isArray(question.options))
|
|
50958
|
+
for (const option of question.options) {
|
|
50959
|
+
const description = option.description;
|
|
50960
|
+
if (typeof description === "string" && description.length > BINDING_QUESTION_BUDGET)
|
|
50961
|
+
return over("an option description", description);
|
|
50962
|
+
}
|
|
50963
|
+
return null;
|
|
50964
|
+
};
|
|
50942
50965
|
var findingSchema = object({
|
|
50943
50966
|
claim: text,
|
|
50944
50967
|
consequence: text,
|
|
@@ -51005,6 +51028,10 @@ var actionProgressListSchema = array(actionProgressSchema).check((ctx) => {
|
|
|
51005
51028
|
path: ["decisionId"]
|
|
51006
51029
|
});
|
|
51007
51030
|
});
|
|
51031
|
+
var runtimeSchema = object({
|
|
51032
|
+
createdWith: text.nullable(),
|
|
51033
|
+
updatedWith: text
|
|
51034
|
+
}).strict();
|
|
51008
51035
|
var taskRecordSchema = object({
|
|
51009
51036
|
schemaVersion: literal(1),
|
|
51010
51037
|
id,
|
|
@@ -51018,6 +51045,8 @@ var taskRecordSchema = object({
|
|
|
51018
51045
|
status: _enum(["active", "paused", "closed"]),
|
|
51019
51046
|
closure: closureSchema.nullable(),
|
|
51020
51047
|
progress: progressSchema,
|
|
51048
|
+
pauseReason: text.nullable().optional(),
|
|
51049
|
+
runtime: runtimeSchema.optional(),
|
|
51021
51050
|
assessments: array(entrySchema(assessmentSchema)),
|
|
51022
51051
|
policy: policySchema.nullable(),
|
|
51023
51052
|
policyChanges: array(policyChangeSchema),
|
|
@@ -51033,6 +51062,7 @@ var workspaceRecordSchema = object({
|
|
|
51033
51062
|
id,
|
|
51034
51063
|
revision,
|
|
51035
51064
|
root: nonEmpty,
|
|
51065
|
+
runtime: runtimeSchema.optional(),
|
|
51036
51066
|
writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
51037
51067
|
}).strict();
|
|
51038
51068
|
var capabilitySchema = object({
|
|
@@ -51060,6 +51090,9 @@ var taskSummarySchema = object({
|
|
|
51060
51090
|
id,
|
|
51061
51091
|
revision,
|
|
51062
51092
|
workspaceRevision: revision,
|
|
51093
|
+
createdAt: utc,
|
|
51094
|
+
updatedAt: utc,
|
|
51095
|
+
runtime: runtimeSchema.nullable(),
|
|
51063
51096
|
objective: text,
|
|
51064
51097
|
status: _enum(["active", "paused", "closed"]),
|
|
51065
51098
|
closure: closureSchema.nullable(),
|
|
@@ -51130,7 +51163,7 @@ var taskOperations = {
|
|
|
51130
51163
|
expectedWorkspaceRevision: revision.optional(),
|
|
51131
51164
|
outcome: outcomeSchema,
|
|
51132
51165
|
summary: text,
|
|
51133
|
-
decisionIds: array(id)
|
|
51166
|
+
decisionIds: array(id).optional()
|
|
51134
51167
|
})
|
|
51135
51168
|
};
|
|
51136
51169
|
var policyOperations = {
|
|
@@ -51222,14 +51255,15 @@ var writerOperations = {
|
|
|
51222
51255
|
...taskId,
|
|
51223
51256
|
expectedRevision: revision.optional(),
|
|
51224
51257
|
expectedWorkspaceRevision: revision.optional(),
|
|
51225
|
-
workerId: nullableId.optional()
|
|
51258
|
+
workerId: nullableId.optional(),
|
|
51259
|
+
reason: text.optional()
|
|
51226
51260
|
}),
|
|
51227
51261
|
release: operation({
|
|
51228
51262
|
action: literal("release"),
|
|
51229
51263
|
...taskId,
|
|
51230
51264
|
expectedRevision: revision.optional(),
|
|
51231
51265
|
expectedWorkspaceRevision: revision.optional(),
|
|
51232
|
-
reason: text
|
|
51266
|
+
reason: text.optional()
|
|
51233
51267
|
})
|
|
51234
51268
|
};
|
|
51235
51269
|
var stateOperations = {
|
|
@@ -57208,6 +57242,33 @@ var metadataLockSchema = object({
|
|
|
57208
57242
|
nonce: string2().min(1)
|
|
57209
57243
|
}).strict();
|
|
57210
57244
|
var now2 = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
57245
|
+
var cachedRuntimeVersion = null;
|
|
57246
|
+
var runtimeVersion = () => {
|
|
57247
|
+
if (cachedRuntimeVersion === null) {
|
|
57248
|
+
try {
|
|
57249
|
+
const pkg = JSON.parse(fs7.readFileSync(path19.join(packageRoot(), "package.json"), "utf8"));
|
|
57250
|
+
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
57251
|
+
} catch {
|
|
57252
|
+
cachedRuntimeVersion = "0.0.0";
|
|
57253
|
+
}
|
|
57254
|
+
}
|
|
57255
|
+
return cachedRuntimeVersion;
|
|
57256
|
+
};
|
|
57257
|
+
var stampNew = (record) => ({
|
|
57258
|
+
createdWith: record.runtime?.createdWith ?? null,
|
|
57259
|
+
updatedWith: runtimeVersion()
|
|
57260
|
+
});
|
|
57261
|
+
var isNewerVersion = (candidate, current) => {
|
|
57262
|
+
const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
|
|
57263
|
+
const [next, now] = [parts(candidate), parts(current)];
|
|
57264
|
+
for (let index = 0;index < 3; index += 1) {
|
|
57265
|
+
const left = next[index] ?? 0;
|
|
57266
|
+
const right = now[index] ?? 0;
|
|
57267
|
+
if (left !== right)
|
|
57268
|
+
return left > right;
|
|
57269
|
+
}
|
|
57270
|
+
return false;
|
|
57271
|
+
};
|
|
57211
57272
|
var jsonBytes = (value) => `${canonicalJson(value)}
|
|
57212
57273
|
`;
|
|
57213
57274
|
var digestBytes2 = (value) => createHash4("sha256").update(value).digest("hex");
|
|
@@ -57307,7 +57368,7 @@ class TaskStore {
|
|
|
57307
57368
|
if (!current.ok)
|
|
57308
57369
|
return current;
|
|
57309
57370
|
if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
|
|
57310
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
57371
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
57311
57372
|
expectedWorkspaceRevision: value.expectedWorkspaceRevision,
|
|
57312
57373
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
57313
57374
|
});
|
|
@@ -57319,11 +57380,17 @@ class TaskStore {
|
|
|
57319
57380
|
const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
|
|
57320
57381
|
if (current.data && !previousWorkspaceBytes)
|
|
57321
57382
|
return failure("storage_error", "workspace snapshot disappeared during creation");
|
|
57322
|
-
const workspace = current.data ? {
|
|
57383
|
+
const workspace = current.data ? {
|
|
57384
|
+
...current.data,
|
|
57385
|
+
revision: newRevision(),
|
|
57386
|
+
root: this.root,
|
|
57387
|
+
runtime: stampNew(current.data)
|
|
57388
|
+
} : {
|
|
57323
57389
|
schemaVersion: SCHEMA_VERSION,
|
|
57324
57390
|
id: newId(),
|
|
57325
57391
|
revision: newRevision(),
|
|
57326
57392
|
root: this.root,
|
|
57393
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
57327
57394
|
writer: null
|
|
57328
57395
|
};
|
|
57329
57396
|
const timestamp = value.now ?? now2();
|
|
@@ -57345,6 +57412,7 @@ class TaskStore {
|
|
|
57345
57412
|
status: "active",
|
|
57346
57413
|
closure: null,
|
|
57347
57414
|
progress: { summary: "", nextAction: null, blockers: [] },
|
|
57415
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
57348
57416
|
assessments: [],
|
|
57349
57417
|
policy: null,
|
|
57350
57418
|
policyChanges: [],
|
|
@@ -57375,7 +57443,7 @@ class TaskStore {
|
|
|
57375
57443
|
if (!current.ok)
|
|
57376
57444
|
return current;
|
|
57377
57445
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
57378
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
57446
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
57379
57447
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
57380
57448
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
57381
57449
|
});
|
|
@@ -57383,11 +57451,17 @@ class TaskStore {
|
|
|
57383
57451
|
if (current.data && !previousWorkspaceBytes)
|
|
57384
57452
|
return failure("storage_error", "workspace snapshot disappeared during import");
|
|
57385
57453
|
const timestamp = input.now ?? now2();
|
|
57386
|
-
const workspace = current.data ? {
|
|
57454
|
+
const workspace = current.data ? {
|
|
57455
|
+
...current.data,
|
|
57456
|
+
revision: newRevision(),
|
|
57457
|
+
root: this.root,
|
|
57458
|
+
runtime: stampNew(current.data)
|
|
57459
|
+
} : {
|
|
57387
57460
|
schemaVersion: SCHEMA_VERSION,
|
|
57388
57461
|
id: input.workspaceId ?? newId(),
|
|
57389
57462
|
revision: newRevision(),
|
|
57390
57463
|
root: this.root,
|
|
57464
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
57391
57465
|
writer: null
|
|
57392
57466
|
};
|
|
57393
57467
|
const task = {
|
|
@@ -57395,7 +57469,11 @@ class TaskStore {
|
|
|
57395
57469
|
workspaceId: workspace.id,
|
|
57396
57470
|
revision: newRevision(),
|
|
57397
57471
|
createdAt: timestamp,
|
|
57398
|
-
updatedAt: timestamp
|
|
57472
|
+
updatedAt: timestamp,
|
|
57473
|
+
runtime: {
|
|
57474
|
+
createdWith: input.task.runtime?.createdWith ?? null,
|
|
57475
|
+
updatedWith: runtimeVersion()
|
|
57476
|
+
}
|
|
57399
57477
|
};
|
|
57400
57478
|
const validTask = taskRecordSchema.safeParse(task);
|
|
57401
57479
|
if (!validTask.success)
|
|
@@ -57444,7 +57522,8 @@ class TaskStore {
|
|
|
57444
57522
|
workspaceId: current.data.workspaceId,
|
|
57445
57523
|
createdAt: current.data.createdAt,
|
|
57446
57524
|
revision: context.revision,
|
|
57447
|
-
updatedAt: context.now
|
|
57525
|
+
updatedAt: context.now,
|
|
57526
|
+
runtime: stampNew(current.data)
|
|
57448
57527
|
};
|
|
57449
57528
|
const valid = taskRecordSchema.safeParse(record);
|
|
57450
57529
|
if (!valid.success)
|
|
@@ -57478,7 +57557,8 @@ class TaskStore {
|
|
|
57478
57557
|
...changed.data,
|
|
57479
57558
|
id: current.data.id,
|
|
57480
57559
|
root: this.root,
|
|
57481
|
-
revision: context.revision
|
|
57560
|
+
revision: context.revision,
|
|
57561
|
+
runtime: stampNew(current.data)
|
|
57482
57562
|
};
|
|
57483
57563
|
const valid = workspaceRecordSchema.safeParse(record);
|
|
57484
57564
|
if (!valid.success)
|
|
@@ -57521,7 +57601,8 @@ class TaskStore {
|
|
|
57521
57601
|
...changedWorkspace.data,
|
|
57522
57602
|
id: workspace.data.id,
|
|
57523
57603
|
root: this.root,
|
|
57524
|
-
revision: workspaceContext.revision
|
|
57604
|
+
revision: workspaceContext.revision,
|
|
57605
|
+
runtime: stampNew(workspace.data)
|
|
57525
57606
|
});
|
|
57526
57607
|
if (!nextWorkspace.success)
|
|
57527
57608
|
return failure("invalid_input", "workspace mutation produced an invalid record");
|
|
@@ -57546,7 +57627,8 @@ class TaskStore {
|
|
|
57546
57627
|
workspaceId: task.data.workspaceId,
|
|
57547
57628
|
createdAt: task.data.createdAt,
|
|
57548
57629
|
revision: taskContext.revision,
|
|
57549
|
-
updatedAt: taskContext.now
|
|
57630
|
+
updatedAt: taskContext.now,
|
|
57631
|
+
runtime: stampNew(task.data)
|
|
57550
57632
|
});
|
|
57551
57633
|
if (!nextTask.success) {
|
|
57552
57634
|
this.markUncertain(nextWorkspace.data);
|
|
@@ -57766,7 +57848,8 @@ class TaskStore {
|
|
|
57766
57848
|
const value = {
|
|
57767
57849
|
...workspace,
|
|
57768
57850
|
writer: { ...workspace.writer, state: "uncertain" },
|
|
57769
|
-
revision: newRevision()
|
|
57851
|
+
revision: newRevision(),
|
|
57852
|
+
runtime: stampNew(workspace)
|
|
57770
57853
|
};
|
|
57771
57854
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
57772
57855
|
}
|
|
@@ -57980,7 +58063,12 @@ class TaskStore {
|
|
|
57980
58063
|
if (isObject3(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
57981
58064
|
return failure("unsupported_version", "unsupported snapshot schema version");
|
|
57982
58065
|
const parsed = schema.safeParse(value);
|
|
57983
|
-
|
|
58066
|
+
if (parsed.success)
|
|
58067
|
+
return success(null, null, parsed.data);
|
|
58068
|
+
const writerVersion = isObject3(value) && isObject3(value.runtime) ? value.runtime.updatedWith : null;
|
|
58069
|
+
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
58070
|
+
return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
58071
|
+
return failure("recovery_required", "snapshot does not satisfy its schema");
|
|
57984
58072
|
}
|
|
57985
58073
|
processStart(pid) {
|
|
57986
58074
|
try {
|
|
@@ -57990,7 +58078,7 @@ class TaskStore {
|
|
|
57990
58078
|
}
|
|
57991
58079
|
}
|
|
57992
58080
|
conflict(expected, actual) {
|
|
57993
|
-
return failure("revision_conflict", "snapshot revision does not match", {
|
|
58081
|
+
return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
57994
58082
|
expectedRevision: expected,
|
|
57995
58083
|
actualRevision: actual
|
|
57996
58084
|
});
|
|
@@ -58017,6 +58105,61 @@ class TaskStore {
|
|
|
58017
58105
|
return path19.join(this.workitDir, ".gitignore");
|
|
58018
58106
|
}
|
|
58019
58107
|
}
|
|
58108
|
+
// packages/workit-core/src/core/workers.ts
|
|
58109
|
+
var same = (left, right) => {
|
|
58110
|
+
try {
|
|
58111
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
58112
|
+
} catch {
|
|
58113
|
+
return false;
|
|
58114
|
+
}
|
|
58115
|
+
};
|
|
58116
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
58117
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
58118
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
58119
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
58120
|
+
const code = char.charCodeAt(0);
|
|
58121
|
+
return code < 32 || code === 127;
|
|
58122
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
58123
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
58124
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
58125
|
+
function assertProductWriteAllowed(input) {
|
|
58126
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
58127
|
+
return failure("invalid_input", "invalid product write path");
|
|
58128
|
+
if (!input.store)
|
|
58129
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
58130
|
+
const task = input.store.readTask(input.task.id);
|
|
58131
|
+
if (!task.ok)
|
|
58132
|
+
return task;
|
|
58133
|
+
const workspace = input.store.readWorkspace();
|
|
58134
|
+
if (!workspace.ok)
|
|
58135
|
+
return workspace;
|
|
58136
|
+
if (!workspace.data)
|
|
58137
|
+
return failure("not_found", "workspace not found");
|
|
58138
|
+
const workspaceRecord = workspace.data;
|
|
58139
|
+
if (task.data.status !== "active")
|
|
58140
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
58141
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
58142
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
58143
|
+
const writer = workspaceRecord.writer;
|
|
58144
|
+
if (!writer)
|
|
58145
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
58146
|
+
if (writer.state === "uncertain")
|
|
58147
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
58148
|
+
owner: writer.owner
|
|
58149
|
+
});
|
|
58150
|
+
const caller = callerValue(input.caller);
|
|
58151
|
+
const workerId = workerIdOf(input.caller);
|
|
58152
|
+
const owner = writer.owner;
|
|
58153
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
58154
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
58155
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
58156
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
58157
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
58158
|
+
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))))
|
|
58159
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
58160
|
+
return success(null, workspaceRecord.revision, owner);
|
|
58161
|
+
}
|
|
58162
|
+
|
|
58020
58163
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
58021
58164
|
import { createHash as createHash6 } from "node:crypto";
|
|
58022
58165
|
import * as fs9 from "node:fs";
|
|
@@ -58155,7 +58298,11 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
58155
58298
|
return failure("invalid_input", "document references require a byte digest");
|
|
58156
58299
|
const target = path20.resolve(root, reference.path);
|
|
58157
58300
|
if (target !== root && !target.startsWith(`${root}${path20.sep}`))
|
|
58158
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
58301
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
58302
|
+
fields: [
|
|
58303
|
+
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
58304
|
+
]
|
|
58305
|
+
});
|
|
58159
58306
|
try {
|
|
58160
58307
|
const relative = path20.relative(root, target);
|
|
58161
58308
|
let current = root;
|
|
@@ -58166,7 +58313,14 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
58166
58313
|
const stat = fs8.lstatSync(current);
|
|
58167
58314
|
const real = fs8.realpathSync(current);
|
|
58168
58315
|
if (real !== root && !real.startsWith(`${root}${path20.sep}`))
|
|
58169
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
58316
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
58317
|
+
fields: [
|
|
58318
|
+
{
|
|
58319
|
+
path: "contentRefs.path",
|
|
58320
|
+
reason: `${reference.path} resolves outside the checkout`
|
|
58321
|
+
}
|
|
58322
|
+
]
|
|
58323
|
+
});
|
|
58170
58324
|
if (stat.isSymbolicLink())
|
|
58171
58325
|
return failure("invalid_input", "document reference uses a symlink");
|
|
58172
58326
|
if (current === target && !stat.isFile())
|
|
@@ -58610,15 +58764,15 @@ var scopeRoots = (root, scope) => {
|
|
|
58610
58764
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
58611
58765
|
const target = path21.resolve(root, item);
|
|
58612
58766
|
if (!inside(root, target))
|
|
58613
|
-
return failure("invalid_input",
|
|
58767
|
+
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
58768
|
let ancestor = target;
|
|
58615
58769
|
while (!fs9.existsSync(ancestor) && ancestor !== root)
|
|
58616
58770
|
ancestor = path21.dirname(ancestor);
|
|
58617
58771
|
try {
|
|
58618
58772
|
if (!inside(root, fs9.realpathSync(ancestor)))
|
|
58619
|
-
return failure("invalid_input",
|
|
58773
|
+
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
58774
|
} catch {
|
|
58621
|
-
return failure("invalid_input",
|
|
58775
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
58622
58776
|
}
|
|
58623
58777
|
roots.push(target);
|
|
58624
58778
|
}
|
|
@@ -58633,7 +58787,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
58633
58787
|
}
|
|
58634
58788
|
const normalizedScope = canonicalScope(scope);
|
|
58635
58789
|
if (!normalizedScope)
|
|
58636
|
-
return failure("invalid_input", "candidate scope is invalid");
|
|
58790
|
+
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
58791
|
const roots = scopeRoots(checkout, normalizedScope);
|
|
58638
58792
|
if (!roots.ok)
|
|
58639
58793
|
return roots;
|
|
@@ -58787,7 +58941,7 @@ function evaluateEvidence(task, candidate) {
|
|
|
58787
58941
|
}
|
|
58788
58942
|
var scopeCovers2 = scopeCovers;
|
|
58789
58943
|
var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
|
|
58790
|
-
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && data.response === "approved" && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
58944
|
+
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
58791
58945
|
var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
|
|
58792
58946
|
if (kind !== "passed")
|
|
58793
58947
|
return false;
|
|
@@ -58822,7 +58976,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
58822
58976
|
return true;
|
|
58823
58977
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
58824
58978
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
58825
|
-
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
|
|
58979
|
+
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
|
|
58826
58980
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
58827
58981
|
});
|
|
58828
58982
|
if (passed.length) {
|
|
@@ -58863,7 +59017,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
58863
59017
|
status: "satisfied",
|
|
58864
59018
|
evidenceIds: [],
|
|
58865
59019
|
decisionIds: decisions.map((decision) => decision.id),
|
|
58866
|
-
reason: "an applicable approved decision satisfies the requirement"
|
|
59020
|
+
reason: "an applicable approved or stated decision satisfies the requirement"
|
|
58867
59021
|
};
|
|
58868
59022
|
const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
|
|
58869
59023
|
if (limitations.length)
|
|
@@ -58918,7 +59072,17 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
58918
59072
|
const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
58919
59073
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
58920
59074
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
58921
|
-
requirementIds: blocking.map((item) => item.requirementId)
|
|
59075
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
59076
|
+
requirements: blocking.map((item) => {
|
|
59077
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
59078
|
+
return {
|
|
59079
|
+
requirementId: item.requirementId,
|
|
59080
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
59081
|
+
reason: item.reason,
|
|
59082
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
59083
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
59084
|
+
};
|
|
59085
|
+
})
|
|
58922
59086
|
});
|
|
58923
59087
|
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
58924
59088
|
if (requestedOutcome === "verified" && accepted.length)
|
|
@@ -58949,7 +59113,7 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
58949
59113
|
var METHODS = {
|
|
58950
59114
|
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
58951
59115
|
"workit-behavioral-tdd": { dimensions: ["testing"] },
|
|
58952
|
-
"workit-review": { dimensions: ["review"] },
|
|
59116
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
|
|
58953
59117
|
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
58954
59118
|
"workit-implement": { dimensions: ["delegation"] },
|
|
58955
59119
|
"workit-debug": { ruleIds: ["root-cause-investigation"] },
|
|
@@ -59000,7 +59164,7 @@ function reconcileResume(view, observations = []) {
|
|
|
59000
59164
|
return candidate;
|
|
59001
59165
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
59002
59166
|
const blockers = [...view.task.progress.blockers];
|
|
59003
|
-
if (view.task.workers.some((entry) =>
|
|
59167
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
59004
59168
|
blockers.push({
|
|
59005
59169
|
reason: "worker state requires reconciliation",
|
|
59006
59170
|
dependentAction: "resume",
|
|
@@ -59092,6 +59256,7 @@ var exportDigest = (bundle) => sha256({
|
|
|
59092
59256
|
task: bundle.task
|
|
59093
59257
|
});
|
|
59094
59258
|
// packages/workit-core/src/core/external-action.ts
|
|
59259
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
59095
59260
|
var externalActionSchema = discriminatedUnion("operation", [
|
|
59096
59261
|
object({
|
|
59097
59262
|
operation: literal("git.branch_setup"),
|
|
@@ -59104,7 +59269,11 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
59104
59269
|
}).strict(),
|
|
59105
59270
|
object({
|
|
59106
59271
|
operation: literal("git.commit"),
|
|
59107
|
-
payload: object({
|
|
59272
|
+
payload: object({
|
|
59273
|
+
message: string2().min(1).optional(),
|
|
59274
|
+
plan_steps: array(string2().min(1)).optional(),
|
|
59275
|
+
plan_branch: string2().min(1).optional()
|
|
59276
|
+
}).strict()
|
|
59108
59277
|
}).strict(),
|
|
59109
59278
|
object({
|
|
59110
59279
|
operation: literal("git.push"),
|
|
@@ -59193,7 +59362,7 @@ var externalActionDescriptor = (operation, payload) => canonicalJson({
|
|
|
59193
59362
|
payload,
|
|
59194
59363
|
...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 } : {}
|
|
59195
59364
|
});
|
|
59196
|
-
var externalActionHelp = "Fixed actions: git.branch_setup {action?,sdd_dir?,target_branch
|
|
59365
|
+
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).";
|
|
59197
59366
|
var externalActionRef = (host, actor, descriptor) => ({
|
|
59198
59367
|
kind: "host",
|
|
59199
59368
|
host,
|
|
@@ -59244,14 +59413,185 @@ var priorExternalAction = (store, host, actor, operation, payload) => {
|
|
|
59244
59413
|
return failure("permission_denied", candidates.length === 0 ? "no prior external action matches this request" : "multiple prior external actions match this request");
|
|
59245
59414
|
return success(workspace.data.revision, null, { ...candidates[0], workspace: workspace.data });
|
|
59246
59415
|
};
|
|
59416
|
+
var planCommitDescriptor = (content) => {
|
|
59417
|
+
try {
|
|
59418
|
+
const value = JSON.parse(content);
|
|
59419
|
+
if (value.operation !== "git.commit" || !value.payload)
|
|
59420
|
+
return null;
|
|
59421
|
+
const steps = value.payload.plan_steps;
|
|
59422
|
+
const branch = value.payload.plan_branch;
|
|
59423
|
+
if (!Array.isArray(steps) || steps.some((step) => typeof step !== "string" || !step) || typeof branch !== "string" || !branch)
|
|
59424
|
+
return null;
|
|
59425
|
+
return { steps, branch };
|
|
59426
|
+
} catch {
|
|
59427
|
+
return null;
|
|
59428
|
+
}
|
|
59429
|
+
};
|
|
59430
|
+
var approvedPlanCommit = (store, host, actor, message) => {
|
|
59431
|
+
if (typeof message !== "string" || !message)
|
|
59432
|
+
return failure("invalid_input", "commit message is required");
|
|
59433
|
+
const listed = store.listTasks();
|
|
59434
|
+
const workspace = store.readWorkspace();
|
|
59435
|
+
if (!listed.ok || !workspace.ok)
|
|
59436
|
+
return failure("storage_error", "external action state is unavailable");
|
|
59437
|
+
if (!workspace.data)
|
|
59438
|
+
return failure("not_found", "workspace not found");
|
|
59439
|
+
const matches = listed.data.flatMap((task) => {
|
|
59440
|
+
if (task.status !== "active" || task.workspaceId !== workspace.data.id)
|
|
59441
|
+
return [];
|
|
59442
|
+
if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
|
|
59443
|
+
return [];
|
|
59444
|
+
return task.decisions.flatMap((entry) => {
|
|
59445
|
+
const decision = entry.data;
|
|
59446
|
+
if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
|
|
59447
|
+
return [];
|
|
59448
|
+
if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0)
|
|
59449
|
+
return [];
|
|
59450
|
+
const plan = planCommitDescriptor(decision.binding.approvedContent);
|
|
59451
|
+
if (!plan)
|
|
59452
|
+
return [];
|
|
59453
|
+
const completed = task.actionProgress?.find((progress) => progress.decisionId === entry.id)?.completedSteps ?? [];
|
|
59454
|
+
if (plan.steps[completed.length] !== message)
|
|
59455
|
+
return [];
|
|
59456
|
+
return [{ task, entry, steps: plan.steps, branch: plan.branch, completed: [...completed] }];
|
|
59457
|
+
});
|
|
59458
|
+
});
|
|
59459
|
+
if (matches.length !== 1)
|
|
59460
|
+
return failure("permission_denied", matches.length === 0 ? "no plan commit authorization matches this message" : "multiple plan authorizations match this message");
|
|
59461
|
+
return success(workspace.data.revision, null, matches[0]);
|
|
59462
|
+
};
|
|
59463
|
+
var commitMessageFromDescriptor = (operation) => {
|
|
59464
|
+
try {
|
|
59465
|
+
const descriptor = JSON.parse(operation);
|
|
59466
|
+
return descriptor.operation === "git.commit" && typeof descriptor.payload?.message === "string" ? descriptor.payload.message : undefined;
|
|
59467
|
+
} catch {
|
|
59468
|
+
return;
|
|
59469
|
+
}
|
|
59470
|
+
};
|
|
59471
|
+
var planCommitBinding = (store, host, actor, operation) => {
|
|
59472
|
+
const message = commitMessageFromDescriptor(operation);
|
|
59473
|
+
if (!message)
|
|
59474
|
+
return null;
|
|
59475
|
+
const plan = approvedPlanCommit(store, host, actor, message);
|
|
59476
|
+
if (!plan.ok)
|
|
59477
|
+
return null;
|
|
59478
|
+
let branch;
|
|
59479
|
+
try {
|
|
59480
|
+
branch = execFileSync2("git", ["branch", "--show-current"], {
|
|
59481
|
+
cwd: store.root,
|
|
59482
|
+
encoding: "utf8",
|
|
59483
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
59484
|
+
}).trim();
|
|
59485
|
+
} catch {
|
|
59486
|
+
return null;
|
|
59487
|
+
}
|
|
59488
|
+
if (!branch || branch !== plan.data.branch)
|
|
59489
|
+
return null;
|
|
59490
|
+
const workspace = store.readWorkspace();
|
|
59491
|
+
if (!workspace.ok || !workspace.data)
|
|
59492
|
+
return null;
|
|
59493
|
+
return {
|
|
59494
|
+
taskId: plan.data.task.id,
|
|
59495
|
+
decisionId: plan.data.entry.id,
|
|
59496
|
+
binding: plan.data.entry.data.binding,
|
|
59497
|
+
expectedRevision: plan.data.task.revision,
|
|
59498
|
+
expectedWorkspaceRevision: workspace.data.revision,
|
|
59499
|
+
step: message
|
|
59500
|
+
};
|
|
59501
|
+
};
|
|
59247
59502
|
var approvedExternalAction = (store, host, actor, operation) => {
|
|
59248
59503
|
const selected = externalActionState(store, host, actor, operation);
|
|
59249
|
-
if (
|
|
59504
|
+
if (selected.ok) {
|
|
59505
|
+
if (selected.data.entry.data.consumption !== null)
|
|
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");
|
|
59250
59507
|
return selected;
|
|
59251
|
-
|
|
59252
|
-
|
|
59508
|
+
}
|
|
59509
|
+
const intent = approvedBranchSetupIntent(store, host, actor, operation);
|
|
59510
|
+
if (intent.ok) {
|
|
59511
|
+
if (intent.data.entry.data.consumption !== null)
|
|
59512
|
+
return failure(intent.data.entry.data.consumption.state === "uncertain" ? "external_outcome_unknown" : "permission_denied", intent.data.entry.data.consumption.state === "uncertain" ? "previous external action outcome is unknown" : "external action was already settled");
|
|
59513
|
+
return intent;
|
|
59514
|
+
}
|
|
59253
59515
|
return selected;
|
|
59254
59516
|
};
|
|
59517
|
+
var branchSetupIntent = (descriptor) => {
|
|
59518
|
+
try {
|
|
59519
|
+
const value = JSON.parse(descriptor);
|
|
59520
|
+
if (value.operation !== "git.branch_setup" || !value.payload)
|
|
59521
|
+
return null;
|
|
59522
|
+
const { target_branch, resolved } = value.payload;
|
|
59523
|
+
if (typeof target_branch !== "string" || !target_branch || !resolved)
|
|
59524
|
+
return null;
|
|
59525
|
+
if (typeof resolved.base_branch !== "string" || typeof resolved.target_exists !== "boolean")
|
|
59526
|
+
return null;
|
|
59527
|
+
return {
|
|
59528
|
+
target: target_branch,
|
|
59529
|
+
base: resolved.base_branch,
|
|
59530
|
+
remoteBase: typeof resolved.remote_base === "string" ? resolved.remote_base : null,
|
|
59531
|
+
targetExists: resolved.target_exists
|
|
59532
|
+
};
|
|
59533
|
+
} catch {
|
|
59534
|
+
return null;
|
|
59535
|
+
}
|
|
59536
|
+
};
|
|
59537
|
+
var approvedBranchSetupIntent = (store, host, actor, operation) => {
|
|
59538
|
+
const current = branchSetupIntent(operation);
|
|
59539
|
+
if (!current)
|
|
59540
|
+
return failure("permission_denied", "no approved action is bound to this session");
|
|
59541
|
+
const listed = store.listTasks();
|
|
59542
|
+
const workspace = store.readWorkspace();
|
|
59543
|
+
if (!listed.ok || !workspace.ok)
|
|
59544
|
+
return failure("storage_error", "external action state is unavailable");
|
|
59545
|
+
if (!workspace.data)
|
|
59546
|
+
return failure("not_found", "workspace not found");
|
|
59547
|
+
const candidates = listed.data.flatMap((task) => {
|
|
59548
|
+
if (task.status !== "active" || task.workspaceId !== workspace.data.id || task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
|
|
59549
|
+
return [];
|
|
59550
|
+
return task.decisions.filter((entry) => entry.data.purpose === "action" && entry.data.response === "approved" && entry.data.revoked === null).map((entry) => ({ task, entry }));
|
|
59551
|
+
});
|
|
59552
|
+
const matches = candidates.filter(({ entry }) => {
|
|
59553
|
+
const intent = branchSetupIntent(entry.data.binding.approvedContent);
|
|
59554
|
+
return intent !== null && intent.target === current.target && intent.base === current.base && intent.remoteBase === current.remoteBase && intent.targetExists === current.targetExists;
|
|
59555
|
+
});
|
|
59556
|
+
if (matches.length !== 1)
|
|
59557
|
+
return failure("permission_denied", matches.length === 0 ? "no approved action is bound to this session" : "multiple approved actions match this session");
|
|
59558
|
+
return success(workspace.data.revision, null, { ...matches[0], workspace: workspace.data });
|
|
59559
|
+
};
|
|
59560
|
+
var priorResolvedDrift = (store, host, actor, operation, payload, currentResolved) => {
|
|
59561
|
+
const prior = priorExternalAction(store, host, actor, operation, payload);
|
|
59562
|
+
if (!prior.ok)
|
|
59563
|
+
return success(null, null, null);
|
|
59564
|
+
let baseline;
|
|
59565
|
+
try {
|
|
59566
|
+
const parsed = JSON.parse(prior.data.entry.data.binding.approvedContent);
|
|
59567
|
+
baseline = parsed.payload?.resolved;
|
|
59568
|
+
} catch {
|
|
59569
|
+
return success(null, null, null);
|
|
59570
|
+
}
|
|
59571
|
+
if (baseline === undefined || baseline === null)
|
|
59572
|
+
return success(null, null, null);
|
|
59573
|
+
if (operation === "git.branch_setup") {
|
|
59574
|
+
const before = branchSetupIntent(prior.data.entry.data.binding.approvedContent);
|
|
59575
|
+
let after = null;
|
|
59576
|
+
try {
|
|
59577
|
+
after = branchSetupIntent(JSON.stringify({
|
|
59578
|
+
operation,
|
|
59579
|
+
payload: { ...payload, resolved: currentResolved }
|
|
59580
|
+
}));
|
|
59581
|
+
} catch {
|
|
59582
|
+
after = null;
|
|
59583
|
+
}
|
|
59584
|
+
if (before && after) {
|
|
59585
|
+
const moved = before.target !== after.target ? "target_branch" : before.base !== after.base ? "base_branch" : before.remoteBase !== after.remoteBase ? "remote_base" : before.targetExists !== after.targetExists ? "target_exists" : null;
|
|
59586
|
+
if (moved === null)
|
|
59587
|
+
return success(null, null, null);
|
|
59588
|
+
return failure("invalid_input", `repository state changed since approval (${moved} moved); show the current state and approve the action again`, { outcome: "not_started", operation });
|
|
59589
|
+
}
|
|
59590
|
+
}
|
|
59591
|
+
if (canonicalJson(baseline) === canonicalJson(currentResolved))
|
|
59592
|
+
return success(null, null, null);
|
|
59593
|
+
return failure("invalid_input", "repository state changed since approval; show the current state and approve the action again", { outcome: "not_started", operation });
|
|
59594
|
+
};
|
|
59255
59595
|
var createAuthorizedExternalActionRunner = (core, bind) => async (operation, effect) => {
|
|
59256
59596
|
let steps = [];
|
|
59257
59597
|
try {
|
|
@@ -59510,7 +59850,7 @@ function resolveRequirements(input) {
|
|
|
59510
59850
|
].join(", ")}`,
|
|
59511
59851
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
59512
59852
|
before: "dependent_action",
|
|
59513
|
-
dependentAction:
|
|
59853
|
+
dependentAction: null,
|
|
59514
59854
|
acceptanceAllowed: false
|
|
59515
59855
|
}));
|
|
59516
59856
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -59521,7 +59861,7 @@ function resolveRequirements(input) {
|
|
|
59521
59861
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
59522
59862
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
59523
59863
|
before: "dependent_action",
|
|
59524
|
-
dependentAction:
|
|
59864
|
+
dependentAction: null,
|
|
59525
59865
|
acceptanceAllowed: false
|
|
59526
59866
|
}));
|
|
59527
59867
|
if (signals.behaviorChange.value === true)
|
|
@@ -59679,59 +60019,6 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
59679
60019
|
return parsed.data;
|
|
59680
60020
|
}
|
|
59681
60021
|
|
|
59682
|
-
// packages/workit-core/src/core/workers.ts
|
|
59683
|
-
var same = (left, right) => {
|
|
59684
|
-
try {
|
|
59685
|
-
return canonicalJson(left) === canonicalJson(right);
|
|
59686
|
-
} catch {
|
|
59687
|
-
return false;
|
|
59688
|
-
}
|
|
59689
|
-
};
|
|
59690
|
-
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
59691
|
-
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
59692
|
-
const code = char.charCodeAt(0);
|
|
59693
|
-
return code < 32 || code === 127;
|
|
59694
|
-
}) && !value.split(/[\\/]/).includes("..");
|
|
59695
|
-
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
59696
|
-
var workerIdOf = (value) => value.workerId ?? null;
|
|
59697
|
-
function assertProductWriteAllowed(input) {
|
|
59698
|
-
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
59699
|
-
return failure("invalid_input", "invalid product write path");
|
|
59700
|
-
if (!input.store)
|
|
59701
|
-
return failure("permission_denied", "write authorization requires core state");
|
|
59702
|
-
const task = input.store.readTask(input.task.id);
|
|
59703
|
-
if (!task.ok)
|
|
59704
|
-
return task;
|
|
59705
|
-
const workspace = input.store.readWorkspace();
|
|
59706
|
-
if (!workspace.ok)
|
|
59707
|
-
return workspace;
|
|
59708
|
-
if (!workspace.data)
|
|
59709
|
-
return failure("not_found", "workspace not found");
|
|
59710
|
-
const workspaceRecord = workspace.data;
|
|
59711
|
-
if (task.data.status !== "active")
|
|
59712
|
-
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
59713
|
-
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
59714
|
-
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
59715
|
-
const writer = workspaceRecord.writer;
|
|
59716
|
-
if (!writer)
|
|
59717
|
-
return failure("permission_denied", "checkout has no writer owner");
|
|
59718
|
-
if (writer.state === "uncertain")
|
|
59719
|
-
return failure("recovery_required", "checkout writer requires recovery", {
|
|
59720
|
-
owner: writer.owner
|
|
59721
|
-
});
|
|
59722
|
-
const caller = callerValue(input.caller);
|
|
59723
|
-
const workerId = workerIdOf(input.caller);
|
|
59724
|
-
const owner = writer.owner;
|
|
59725
|
-
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
59726
|
-
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
59727
|
-
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
59728
|
-
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
59729
|
-
return failure("permission_denied", "only implementers can own product writes");
|
|
59730
|
-
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))))
|
|
59731
|
-
return failure("recovery_required", "worker session is not an active writer");
|
|
59732
|
-
return success(null, workspaceRecord.revision, owner);
|
|
59733
|
-
}
|
|
59734
|
-
|
|
59735
60022
|
// packages/workit-core/src/core/task-engine.ts
|
|
59736
60023
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
59737
60024
|
kind,
|
|
@@ -59862,15 +60149,20 @@ var applyWorkerLifecycle = (input) => {
|
|
|
59862
60149
|
return failure("invalid_transition", "worker has not started");
|
|
59863
60150
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
59864
60151
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
60152
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
59865
60153
|
const nextEntry = {
|
|
59866
60154
|
...entry,
|
|
59867
60155
|
recordedAt: input.now ?? entry.recordedAt,
|
|
59868
60156
|
provenance: authority.provenance,
|
|
59869
|
-
data: {
|
|
60157
|
+
data: {
|
|
60158
|
+
...entry.data,
|
|
60159
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
60160
|
+
session: input.session
|
|
60161
|
+
}
|
|
59870
60162
|
};
|
|
59871
60163
|
const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
59872
60164
|
const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
59873
|
-
if (entry.data.state ===
|
|
60165
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
59874
60166
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
59875
60167
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
59876
60168
|
taskId: task.data.id,
|
|
@@ -60240,7 +60532,7 @@ class WorkitCore {
|
|
|
60240
60532
|
activeWorkerBlocker(task, workspace) {
|
|
60241
60533
|
if (workspace.writer)
|
|
60242
60534
|
return failure("recovery_required", "writer ownership must be released first");
|
|
60243
|
-
if (task.workers.some((entry) =>
|
|
60535
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
60244
60536
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
60245
60537
|
return null;
|
|
60246
60538
|
}
|
|
@@ -60477,11 +60769,6 @@ class WorkitCore {
|
|
|
60477
60769
|
if (helper && !helper.ok)
|
|
60478
60770
|
return helper;
|
|
60479
60771
|
const evidence = input.evidence;
|
|
60480
|
-
if (helper?.ok) {
|
|
60481
|
-
const assignment = helper.data.data.assignment;
|
|
60482
|
-
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))
|
|
60483
|
-
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
60484
|
-
}
|
|
60485
60772
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
60486
60773
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
60487
60774
|
if (evidence.kind === "review") {
|
|
@@ -60498,6 +60785,11 @@ class WorkitCore {
|
|
|
60498
60785
|
evidence.beforeCandidateId ??= null;
|
|
60499
60786
|
evidence.candidateId ??= null;
|
|
60500
60787
|
}
|
|
60788
|
+
if (helper?.ok) {
|
|
60789
|
+
const assignment = helper.data.data.assignment;
|
|
60790
|
+
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))
|
|
60791
|
+
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
60792
|
+
}
|
|
60501
60793
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
60502
60794
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
60503
60795
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -60551,7 +60843,12 @@ class WorkitCore {
|
|
|
60551
60843
|
const input = parsed.data;
|
|
60552
60844
|
if (nativeRequired && input.action !== "record")
|
|
60553
60845
|
return failure("permission_denied", "native observation is only valid for recording decisions");
|
|
60554
|
-
|
|
60846
|
+
const stated = input.action === "record" && input.response === "stated";
|
|
60847
|
+
if (stated && input.purpose === "action")
|
|
60848
|
+
return failure("permission_denied", "stated choices cannot authorize mutating actions");
|
|
60849
|
+
if (stated && !input.binding?.statedChoice)
|
|
60850
|
+
return failure("invalid_input", "stated choices require binding.statedChoice");
|
|
60851
|
+
if (nativeRequired && nativeObservation === undefined && !stated)
|
|
60555
60852
|
return failure("permission_denied", "native decision observation is required");
|
|
60556
60853
|
const task = this.store.readTask(input.taskId);
|
|
60557
60854
|
if (!task.ok)
|
|
@@ -60584,7 +60881,7 @@ class WorkitCore {
|
|
|
60584
60881
|
consumption: null
|
|
60585
60882
|
};
|
|
60586
60883
|
const data = { ...base, digest: decisionDigest(base) };
|
|
60587
|
-
const native = nativeRequired ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
60884
|
+
const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
60588
60885
|
observation: nativeObservation,
|
|
60589
60886
|
expected: {
|
|
60590
60887
|
taskId: task.data.id,
|
|
@@ -61393,6 +61690,9 @@ class WorkitCore {
|
|
|
61393
61690
|
id: task.id,
|
|
61394
61691
|
revision: task.revision,
|
|
61395
61692
|
workspaceRevision: workspace.data.revision,
|
|
61693
|
+
createdAt: task.createdAt,
|
|
61694
|
+
updatedAt: task.updatedAt,
|
|
61695
|
+
runtime: task.runtime ?? null,
|
|
61396
61696
|
objective: task.intent.data.objective,
|
|
61397
61697
|
status: task.status,
|
|
61398
61698
|
closure: task.closure,
|
|
@@ -61478,7 +61778,7 @@ class WorkitCore {
|
|
|
61478
61778
|
const base = reconcileResume(effectiveView);
|
|
61479
61779
|
if (!base.ok)
|
|
61480
61780
|
return base;
|
|
61481
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
61781
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
61482
61782
|
const blockers = [...base.data.blockers];
|
|
61483
61783
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
61484
61784
|
blockers.push({
|
|
@@ -61545,7 +61845,8 @@ class WorkitCore {
|
|
|
61545
61845
|
...current,
|
|
61546
61846
|
status,
|
|
61547
61847
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
61548
|
-
progress:
|
|
61848
|
+
progress: current.progress,
|
|
61849
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
61549
61850
|
})
|
|
61550
61851
|
});
|
|
61551
61852
|
if (!changed.ok)
|
|
@@ -61581,7 +61882,7 @@ class WorkitCore {
|
|
|
61581
61882
|
return workspace;
|
|
61582
61883
|
if (!workspace.data)
|
|
61583
61884
|
return failure("not_found", "workspace not found");
|
|
61584
|
-
if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state
|
|
61885
|
+
if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
61585
61886
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
61586
61887
|
this.fillRevisions(input, task.data, workspace.data);
|
|
61587
61888
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -61729,14 +62030,14 @@ function run(root, executable, args, env = {}) {
|
|
|
61729
62030
|
|
|
61730
62031
|
// packages/workit-core/src/core/branch.ts
|
|
61731
62032
|
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "node:fs";
|
|
61732
|
-
import { execFileSync as
|
|
62033
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
61733
62034
|
import path24 from "node:path";
|
|
61734
62035
|
|
|
61735
62036
|
// packages/workit-core/src/core/git.ts
|
|
61736
|
-
import { execFileSync as
|
|
62037
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
61737
62038
|
var run2 = (cwd, args) => {
|
|
61738
62039
|
try {
|
|
61739
|
-
const stdout =
|
|
62040
|
+
const stdout = execFileSync3("git", args, {
|
|
61740
62041
|
cwd,
|
|
61741
62042
|
encoding: "utf8",
|
|
61742
62043
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -61999,7 +62300,7 @@ var baseBranch = (cwd) => {
|
|
|
61999
62300
|
return { base: String(resolved.defaultTargetBranch ?? "develop") };
|
|
62000
62301
|
};
|
|
62001
62302
|
var originBaseReady = (cwd, base) => {
|
|
62002
|
-
const run = (args) =>
|
|
62303
|
+
const run = (args) => execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
62003
62304
|
try {
|
|
62004
62305
|
try {
|
|
62005
62306
|
run(["fetch", "origin", base, "--prune"]);
|
|
@@ -62007,7 +62308,7 @@ var originBaseReady = (cwd, base) => {
|
|
|
62007
62308
|
run(["fetch", "origin", "--prune"]);
|
|
62008
62309
|
}
|
|
62009
62310
|
try {
|
|
62010
|
-
|
|
62311
|
+
execFileSync4("git", ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${base}`], {
|
|
62011
62312
|
cwd,
|
|
62012
62313
|
stdio: "pipe"
|
|
62013
62314
|
});
|
|
@@ -62026,11 +62327,11 @@ var originBaseReady = (cwd, base) => {
|
|
|
62026
62327
|
}
|
|
62027
62328
|
};
|
|
62028
62329
|
var fastForwardBase = (cwd, base) => {
|
|
62029
|
-
const run = (args) =>
|
|
62330
|
+
const run = (args) => execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
62030
62331
|
try {
|
|
62031
62332
|
let hasLocalBase = true;
|
|
62032
62333
|
try {
|
|
62033
|
-
|
|
62334
|
+
execFileSync4("git", ["show-ref", "--verify", "--quiet", `refs/heads/${base}`], {
|
|
62034
62335
|
cwd,
|
|
62035
62336
|
stdio: "pipe"
|
|
62036
62337
|
});
|
|
@@ -62071,10 +62372,10 @@ var branchSetup = ({
|
|
|
62071
62372
|
log
|
|
62072
62373
|
}) => {
|
|
62073
62374
|
const cwd = path24.resolve(workspace_root);
|
|
62074
|
-
const exec = (args) =>
|
|
62375
|
+
const exec = (args) => execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
62075
62376
|
const current = gitContext(cwd).branch;
|
|
62076
62377
|
if (!current || current === "unknown")
|
|
62077
|
-
return { error: "not in a git repository" };
|
|
62378
|
+
return { error: "not in a git repository", phase: "preflight" };
|
|
62078
62379
|
const sdd = sdd_dir ?? "docs";
|
|
62079
62380
|
const manifestPath = path24.isAbsolute(sdd) ? path24.join(sdd, "manifest.json") : path24.join(cwd, sdd, "manifest.json");
|
|
62080
62381
|
const ensureManifestDir = () => {
|
|
@@ -62082,7 +62383,6 @@ var branchSetup = ({
|
|
|
62082
62383
|
if (!existsSync17(dir))
|
|
62083
62384
|
mkdirSync8(dir, { recursive: true, mode: 493 });
|
|
62084
62385
|
};
|
|
62085
|
-
ensureManifestDir();
|
|
62086
62386
|
const readManifest = () => {
|
|
62087
62387
|
try {
|
|
62088
62388
|
return JSON.parse(readFileSync15(manifestPath, "utf8"));
|
|
@@ -62100,13 +62400,16 @@ var branchSetup = ({
|
|
|
62100
62400
|
const manifest = readManifest();
|
|
62101
62401
|
const ref = manifest.stash_ref;
|
|
62102
62402
|
if (!ref)
|
|
62103
|
-
return { error: "no stash_ref in manifest" };
|
|
62403
|
+
return { error: "no stash_ref in manifest", phase: "preflight" };
|
|
62104
62404
|
journal(`pre-pop: ${String(ref)}`);
|
|
62105
62405
|
try {
|
|
62106
62406
|
exec(["stash", "pop", String(ref)]);
|
|
62107
62407
|
} catch (error) {
|
|
62108
62408
|
journal("pop: failed");
|
|
62109
|
-
return {
|
|
62409
|
+
return {
|
|
62410
|
+
error: error instanceof Error ? error.message : "stash pop failed",
|
|
62411
|
+
phase: "post_mutation"
|
|
62412
|
+
};
|
|
62110
62413
|
}
|
|
62111
62414
|
journal("pop: ok");
|
|
62112
62415
|
delete manifest.stash_ref;
|
|
@@ -62116,11 +62419,17 @@ var branchSetup = ({
|
|
|
62116
62419
|
}
|
|
62117
62420
|
const target = target_branch ?? "";
|
|
62118
62421
|
if (!target)
|
|
62119
|
-
return {
|
|
62422
|
+
return {
|
|
62423
|
+
error: "target_branch (the working branch to create or switch to) is required for setup",
|
|
62424
|
+
phase: "preflight"
|
|
62425
|
+
};
|
|
62120
62426
|
if (isProtected(cwd, target))
|
|
62121
|
-
return { error: `protected branch ${target}
|
|
62427
|
+
return { error: `protected branch ${target}`, phase: "preflight" };
|
|
62122
62428
|
if (!allowedBranch(cwd, target))
|
|
62123
|
-
return {
|
|
62429
|
+
return {
|
|
62430
|
+
error: `target branch ${target} is not allowed by the branch policy`,
|
|
62431
|
+
phase: "preflight"
|
|
62432
|
+
};
|
|
62124
62433
|
let base;
|
|
62125
62434
|
let targetExists = true;
|
|
62126
62435
|
try {
|
|
@@ -62131,12 +62440,12 @@ var branchSetup = ({
|
|
|
62131
62440
|
if (!targetExists) {
|
|
62132
62441
|
const baseResolved = baseBranch(cwd);
|
|
62133
62442
|
if ("error" in baseResolved)
|
|
62134
|
-
return { error: baseResolved.error };
|
|
62443
|
+
return { error: baseResolved.error, phase: "preflight" };
|
|
62135
62444
|
base = baseResolved.base;
|
|
62136
62445
|
}
|
|
62137
62446
|
journal(`entry: current=${current} target=${target} base=${base ?? "-"}`);
|
|
62138
62447
|
let stash_ref;
|
|
62139
|
-
const failAfterStash = (message) => {
|
|
62448
|
+
const failAfterStash = (message, phase = "post_mutation") => {
|
|
62140
62449
|
let suffix = "";
|
|
62141
62450
|
if (stash_ref) {
|
|
62142
62451
|
journal(`pre-pop: ${stash_ref}`);
|
|
@@ -62149,27 +62458,31 @@ var branchSetup = ({
|
|
|
62149
62458
|
suffix = " (changes preserved in stash)";
|
|
62150
62459
|
}
|
|
62151
62460
|
}
|
|
62152
|
-
return { error: `${message}${suffix}
|
|
62461
|
+
return { error: `${message}${suffix}`, phase: stash_ref ? "post_mutation" : phase };
|
|
62153
62462
|
};
|
|
62154
62463
|
if (current !== target) {
|
|
62155
62464
|
const dirty = Boolean(gitContext(cwd).status_short.trim());
|
|
62156
62465
|
if (dirty && stash !== "yes") {
|
|
62157
62466
|
return {
|
|
62158
|
-
error: "dirty working tree — ask with native question, then call workit_branch_setup with stash=yes"
|
|
62467
|
+
error: "dirty working tree — ask with native question, then call workit_branch_setup with stash=yes",
|
|
62468
|
+
phase: "preflight"
|
|
62159
62469
|
};
|
|
62160
62470
|
}
|
|
62161
62471
|
let validatedBase;
|
|
62162
62472
|
if (!targetExists && base !== undefined) {
|
|
62163
62473
|
const ready = originBaseReady(cwd, base);
|
|
62164
62474
|
if (!ready.ok)
|
|
62165
|
-
return { error: ready.error ?? "ensure-base-branch failed" };
|
|
62475
|
+
return { error: ready.error ?? "ensure-base-branch failed", phase: "preflight" };
|
|
62166
62476
|
validatedBase = base;
|
|
62167
62477
|
}
|
|
62168
62478
|
if (dirty) {
|
|
62169
62479
|
try {
|
|
62170
62480
|
exec(["stash", "push", "-u", "-m", `workit: pre-checkout ${target}`]);
|
|
62171
62481
|
} catch (error) {
|
|
62172
|
-
return {
|
|
62482
|
+
return {
|
|
62483
|
+
error: error instanceof Error ? error.message : "stash push failed",
|
|
62484
|
+
phase: "post_mutation"
|
|
62485
|
+
};
|
|
62173
62486
|
}
|
|
62174
62487
|
stash_ref = "stash@{0}";
|
|
62175
62488
|
journal(`stash push: ${stash_ref}`);
|
|
@@ -62180,14 +62493,14 @@ var branchSetup = ({
|
|
|
62180
62493
|
} catch (error) {
|
|
62181
62494
|
const message = error instanceof Error ? error.message : "checkout failed";
|
|
62182
62495
|
if (/worktree/i.test(message)) {
|
|
62183
|
-
return failAfterStash(`branch ${target} is locked by an existing git worktree — remove it first (we do not use worktrees)
|
|
62496
|
+
return failAfterStash(`branch ${target} is locked by an existing git worktree — remove it first (we do not use worktrees)`, "preflight");
|
|
62184
62497
|
}
|
|
62185
62498
|
try {
|
|
62186
62499
|
let effectiveBase = base;
|
|
62187
62500
|
if (effectiveBase === undefined) {
|
|
62188
62501
|
const lateResolved = baseBranch(cwd);
|
|
62189
62502
|
if ("error" in lateResolved)
|
|
62190
|
-
return failAfterStash(lateResolved.error);
|
|
62503
|
+
return failAfterStash(lateResolved.error, "preflight");
|
|
62191
62504
|
effectiveBase = lateResolved.base;
|
|
62192
62505
|
}
|
|
62193
62506
|
const baseResult = validatedBase !== undefined ? fastForwardBase(cwd, effectiveBase) : ensureBaseBranch(cwd, effectiveBase);
|
|
@@ -64016,6 +64329,101 @@ var safePath = (root, value) => {
|
|
|
64016
64329
|
return null;
|
|
64017
64330
|
}
|
|
64018
64331
|
};
|
|
64332
|
+
var branchCreationBaseline = (root, target, base) => {
|
|
64333
|
+
const exists = gitValue(root, ["rev-parse", "--verify", "--quiet", `refs/heads/${target}`]) !== null;
|
|
64334
|
+
if (exists)
|
|
64335
|
+
return { base_branch: base, target_exists: true, remote_base: null };
|
|
64336
|
+
const remote = run3(root, ["ls-remote", "origin", `refs/heads/${base}`]);
|
|
64337
|
+
const sha = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : "";
|
|
64338
|
+
return {
|
|
64339
|
+
base_branch: base,
|
|
64340
|
+
target_exists: false,
|
|
64341
|
+
remote_base: /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null
|
|
64342
|
+
};
|
|
64343
|
+
};
|
|
64344
|
+
var shortSha = (value) => typeof value === "string" && value ? value.slice(0, 8) : "unknown";
|
|
64345
|
+
var isProtectedBranch2 = (root, branch) => resolveBranchPolicyFor(root).protected.has(branch.toLowerCase());
|
|
64346
|
+
var actionProposalQuestion = (request, descriptorPayload) => {
|
|
64347
|
+
const payload = descriptorPayload ?? {};
|
|
64348
|
+
const resolved = payload.resolved ?? {};
|
|
64349
|
+
const payloadTarget = String(payload.target_branch ?? "");
|
|
64350
|
+
if (request.operation === "git.commit" && Array.isArray(payload.plan_steps)) {
|
|
64351
|
+
const steps = payload.plan_steps;
|
|
64352
|
+
return {
|
|
64353
|
+
presented: `Workit decision: action — Approve committing the plan's ${steps.length} listed tasks on \`${String(payload.plan_branch ?? "")}\`?`,
|
|
64354
|
+
approvedText: `Approve the ${steps.length} listed plan commits.`
|
|
64355
|
+
};
|
|
64356
|
+
}
|
|
64357
|
+
switch (request.operation) {
|
|
64358
|
+
case "git.branch_setup": {
|
|
64359
|
+
if (request.payload.action === "reapply_stash")
|
|
64360
|
+
return {
|
|
64361
|
+
presented: "Workit decision: action — Reapply the pre-checkout stash recorded in the branch manifest?",
|
|
64362
|
+
approvedText: "Reapply the recorded stash."
|
|
64363
|
+
};
|
|
64364
|
+
const target = String(payload.target_branch ?? "");
|
|
64365
|
+
const stashes = payload.stash === "yes" && resolved.dirty === true;
|
|
64366
|
+
if (resolved.target_exists === true)
|
|
64367
|
+
return stashes ? {
|
|
64368
|
+
presented: `Workit decision: action — Stash the dirty working tree and switch to branch \`${target}\`?`,
|
|
64369
|
+
approvedText: `Stash and switch to \`${target}\`.`
|
|
64370
|
+
} : {
|
|
64371
|
+
presented: `Workit decision: action — Switch to branch \`${target}\`?`,
|
|
64372
|
+
approvedText: `Switch to \`${target}\`.`
|
|
64373
|
+
};
|
|
64374
|
+
const base = String(resolved.base_branch ?? "the base branch");
|
|
64375
|
+
return stashes ? {
|
|
64376
|
+
presented: `Workit decision: action — Stash the dirty working tree and create branch \`${target}\` from \`${base}\` (currently ${shortSha(resolved.remote_base)})?`,
|
|
64377
|
+
approvedText: `Stash and create \`${target}\` from \`${base}\` at ${shortSha(resolved.remote_base)}.`
|
|
64378
|
+
} : {
|
|
64379
|
+
presented: `Workit decision: action — Create branch \`${target}\` from \`${base}\` (currently ${shortSha(resolved.remote_base)})?`,
|
|
64380
|
+
approvedText: `Create \`${target}\` from \`${base}\` at ${shortSha(resolved.remote_base)}.`
|
|
64381
|
+
};
|
|
64382
|
+
}
|
|
64383
|
+
case "git.commit": {
|
|
64384
|
+
const paths = Array.isArray(resolved.paths) ? resolved.paths : [];
|
|
64385
|
+
return {
|
|
64386
|
+
presented: `Workit decision: action — Commit ${paths.length} staged file(s) as "${String(request.payload.message)}"?`,
|
|
64387
|
+
approvedText: `Commit as "${String(request.payload.message)}".`
|
|
64388
|
+
};
|
|
64389
|
+
}
|
|
64390
|
+
case "git.push":
|
|
64391
|
+
return {
|
|
64392
|
+
presented: `Workit decision: action — Push branch \`${String(payload.branch ?? "")}\` to origin?`,
|
|
64393
|
+
approvedText: `Push \`${String(payload.branch ?? "")}\` to origin.`
|
|
64394
|
+
};
|
|
64395
|
+
case "hosting.pull_request":
|
|
64396
|
+
return {
|
|
64397
|
+
presented: `Workit decision: action — Open a PR from \`${String(resolved.source_branch ?? "")}\` to \`${payloadTarget}\` titled "${String(request.payload.title)}"?`,
|
|
64398
|
+
approvedText: `Open the PR: ${String(resolved.source_branch ?? "")} → ${payloadTarget}, "${String(request.payload.title)}".`
|
|
64399
|
+
};
|
|
64400
|
+
case "changelog.apply":
|
|
64401
|
+
return {
|
|
64402
|
+
presented: `Workit decision: action — Apply changelog changes at \`${String(payload.path ?? "CHANGELOG.md")}\`?`,
|
|
64403
|
+
approvedText: `Apply changelog changes at ${String(payload.path ?? "CHANGELOG.md")}.`
|
|
64404
|
+
};
|
|
64405
|
+
case "youtrack.update":
|
|
64406
|
+
return {
|
|
64407
|
+
presented: `Workit decision: action — Update YouTrack issue ${String(request.payload.issueId)}${request.payload.minutes ? ` and log ${String(request.payload.minutes)}m` : ""}?`,
|
|
64408
|
+
approvedText: `Update YouTrack issue ${String(request.payload.issueId)}.`
|
|
64409
|
+
};
|
|
64410
|
+
case "youtrack.time":
|
|
64411
|
+
return {
|
|
64412
|
+
presented: `Workit decision: action — Log ${String(request.payload.minutes)}m to YouTrack issue ${String(request.payload.issueId)}?`,
|
|
64413
|
+
approvedText: `Log time to YouTrack issue ${String(request.payload.issueId)}.`
|
|
64414
|
+
};
|
|
64415
|
+
case "youtrack.meeting":
|
|
64416
|
+
return {
|
|
64417
|
+
presented: `Workit decision: action — Log meeting time to YouTrack issue ${String(request.payload.issueId)}?`,
|
|
64418
|
+
approvedText: `Log meeting time to YouTrack issue ${String(request.payload.issueId)}.`
|
|
64419
|
+
};
|
|
64420
|
+
default:
|
|
64421
|
+
return {
|
|
64422
|
+
presented: `Workit decision: action — Approve ${request.operation}?`,
|
|
64423
|
+
approvedText: `Approve ${request.operation}.`
|
|
64424
|
+
};
|
|
64425
|
+
}
|
|
64426
|
+
};
|
|
64019
64427
|
var approvedResolvedExternalAction = (content) => {
|
|
64020
64428
|
try {
|
|
64021
64429
|
const value = JSON.parse(content);
|
|
@@ -64279,24 +64687,35 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
64279
64687
|
const sdd_dir = safePath(root, request.payload.sdd_dir ?? "docs");
|
|
64280
64688
|
if (!sdd_dir)
|
|
64281
64689
|
return failure("invalid_input", "sdd_dir must stay inside the workspace");
|
|
64690
|
+
const action = request.payload.action ?? "setup";
|
|
64282
64691
|
const policy = resolveBranchPolicyFor(root);
|
|
64283
|
-
const target_branch = request.payload.target_branch
|
|
64692
|
+
const target_branch = request.payload.target_branch;
|
|
64693
|
+
if (action !== "reapply_stash" && !target_branch)
|
|
64694
|
+
return failure("invalid_input", "target_branch (the working branch to create or switch to) is required for setup", {
|
|
64695
|
+
outcome: "not_started",
|
|
64696
|
+
fields: [{ path: "target_branch", reason: "required for action=setup" }]
|
|
64697
|
+
});
|
|
64284
64698
|
const head = gitValue(root, ["rev-parse", "HEAD"]);
|
|
64285
64699
|
if (!head)
|
|
64286
64700
|
return failure("storage_error", "current Git commit could not be resolved");
|
|
64701
|
+
const dirty = Boolean((gitRaw(root, ["status", "--short"]) ?? "").trim());
|
|
64702
|
+
const creation = action === "reapply_stash" || !target_branch ? { base_branch: policy.defaultTargetBranch } : branchCreationBaseline(root, target_branch, policy.defaultTargetBranch);
|
|
64287
64703
|
const normalized = {
|
|
64288
64704
|
operation: request.operation,
|
|
64289
64705
|
payload: {
|
|
64290
64706
|
...request.payload,
|
|
64291
|
-
action
|
|
64707
|
+
action,
|
|
64292
64708
|
sdd_dir,
|
|
64293
64709
|
stash: request.payload.stash ?? "no",
|
|
64294
|
-
target_branch
|
|
64710
|
+
...target_branch ? { target_branch } : {}
|
|
64295
64711
|
}
|
|
64296
64712
|
};
|
|
64297
64713
|
return success(null, null, {
|
|
64298
64714
|
request: normalized,
|
|
64299
|
-
descriptorPayload: {
|
|
64715
|
+
descriptorPayload: {
|
|
64716
|
+
...normalized.payload,
|
|
64717
|
+
resolved: { head, dirty, ...creation }
|
|
64718
|
+
}
|
|
64300
64719
|
});
|
|
64301
64720
|
}
|
|
64302
64721
|
case "git.push": {
|
|
@@ -64310,7 +64729,37 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
64310
64729
|
}) : failure("invalid_input", "git push requires a resolvable current branch");
|
|
64311
64730
|
}
|
|
64312
64731
|
case "git.commit": {
|
|
64732
|
+
const planSteps = request.payload.plan_steps;
|
|
64733
|
+
const planBranch = request.payload.plan_branch;
|
|
64313
64734
|
const head = gitValue(root, ["rev-parse", "HEAD"]);
|
|
64735
|
+
if (!head)
|
|
64736
|
+
return failure("storage_error", "Git state could not be resolved");
|
|
64737
|
+
const branch = gitValue(root, ["branch", "--show-current"]);
|
|
64738
|
+
if (planSteps !== undefined || planBranch !== undefined) {
|
|
64739
|
+
if (!Array.isArray(planSteps) || planSteps.some((step) => typeof step !== "string" || !step.trim()) || new Set(planSteps).size !== planSteps.length || planSteps.length === 0 || planSteps.length > 32)
|
|
64740
|
+
return failure("invalid_input", "plan_steps must be 1-32 unique non-empty commit messages", {
|
|
64741
|
+
outcome: "not_started",
|
|
64742
|
+
fields: [{ path: "plan_steps", reason: "unique strings" }]
|
|
64743
|
+
});
|
|
64744
|
+
if (typeof planBranch !== "string" || !planBranch.trim() || planBranch !== branch)
|
|
64745
|
+
return failure("invalid_input", "plan_branch must name the current working branch", {
|
|
64746
|
+
outcome: "not_started",
|
|
64747
|
+
fields: [{ path: "plan_branch", reason: "current branch" }]
|
|
64748
|
+
});
|
|
64749
|
+
if (isProtectedBranch2(root, planBranch))
|
|
64750
|
+
return failure("invalid_input", "plan_branch must not be a protected branch", {
|
|
64751
|
+
outcome: "not_started",
|
|
64752
|
+
fields: [{ path: "plan_branch", reason: "protected branch" }]
|
|
64753
|
+
});
|
|
64754
|
+
return success(null, null, {
|
|
64755
|
+
request,
|
|
64756
|
+
descriptorPayload: {
|
|
64757
|
+
plan_steps: planSteps,
|
|
64758
|
+
plan_branch: planBranch,
|
|
64759
|
+
resolved: { head, branch, steps: planSteps }
|
|
64760
|
+
}
|
|
64761
|
+
});
|
|
64762
|
+
}
|
|
64314
64763
|
const staged = gitRaw(root, [
|
|
64315
64764
|
"diff",
|
|
64316
64765
|
"--cached",
|
|
@@ -64321,7 +64770,13 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
64321
64770
|
"HEAD"
|
|
64322
64771
|
]);
|
|
64323
64772
|
const paths = stagedPaths(root);
|
|
64324
|
-
|
|
64773
|
+
const message = request.payload.message;
|
|
64774
|
+
if (typeof message !== "string" || !message)
|
|
64775
|
+
return failure("invalid_input", "git.commit requires a commit message", {
|
|
64776
|
+
outcome: "not_started",
|
|
64777
|
+
fields: [{ path: "message", reason: "required unless plan_steps is provided" }]
|
|
64778
|
+
});
|
|
64779
|
+
if (staged === null || paths === null)
|
|
64325
64780
|
return failure("storage_error", "Git state could not be resolved");
|
|
64326
64781
|
let policy;
|
|
64327
64782
|
try {
|
|
@@ -64336,13 +64791,13 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
64336
64791
|
`).filter(Boolean));
|
|
64337
64792
|
flavor = detected.flavor ?? "conventional";
|
|
64338
64793
|
}
|
|
64339
|
-
if (!matchCommitFlavor(
|
|
64794
|
+
if (!matchCommitFlavor(message, flavor, policy.pattern))
|
|
64340
64795
|
return failure("invalid_input", `Commit message does not match the ${flavor} flavor (commitPolicy.preset)`);
|
|
64341
64796
|
return success(null, null, {
|
|
64342
64797
|
request,
|
|
64343
64798
|
descriptorPayload: {
|
|
64344
64799
|
...request.payload,
|
|
64345
|
-
resolved: { head, staged: sha256(staged), paths }
|
|
64800
|
+
resolved: { head, branch, staged: sha256(staged), paths }
|
|
64346
64801
|
}
|
|
64347
64802
|
});
|
|
64348
64803
|
}
|
|
@@ -64512,10 +64967,24 @@ var executeConcreteExternalAction = async (request, root, marker, dateMs, step,
|
|
|
64512
64967
|
sdd_dir: request.payload.sdd_dir ?? "docs",
|
|
64513
64968
|
workspace_root: root
|
|
64514
64969
|
});
|
|
64515
|
-
|
|
64970
|
+
if ("error" in result) {
|
|
64971
|
+
if (result.phase === "preflight")
|
|
64972
|
+
return failure("invalid_input", result.error, {
|
|
64973
|
+
outcome: "not_started",
|
|
64974
|
+
operation: request.operation
|
|
64975
|
+
});
|
|
64976
|
+
return unknown2(request.operation);
|
|
64977
|
+
}
|
|
64978
|
+
return success(null, null, result);
|
|
64516
64979
|
}
|
|
64517
64980
|
case "git.commit": {
|
|
64518
|
-
const
|
|
64981
|
+
const message = request.payload.message;
|
|
64982
|
+
if (typeof message !== "string" || !message)
|
|
64983
|
+
return failure("invalid_input", "git.commit requires a commit message", {
|
|
64984
|
+
outcome: "not_started",
|
|
64985
|
+
fields: [{ path: "message", reason: "required" }]
|
|
64986
|
+
});
|
|
64987
|
+
const result = run3(root, ["commit", "-m", message]);
|
|
64519
64988
|
return result.exitCode === 0 ? success(null, null, { stdout: result.stdout.trim() }) : unknown2(request.operation);
|
|
64520
64989
|
}
|
|
64521
64990
|
case "git.push": {
|
|
@@ -64921,7 +65390,29 @@ async function runTaskCommand(argv, deps = {}) {
|
|
|
64921
65390
|
}
|
|
64922
65391
|
const core = new WorkitCore(new TaskStore(root), contextFor(root, parsed.parsed.actor !== undefined ? { ...deps, actor: parsed.parsed.actor } : deps, observedConfirmation ? "host_observed" : "agent_reported"));
|
|
64923
65392
|
let result;
|
|
64924
|
-
|
|
65393
|
+
const decisionBudgetIssue = (() => {
|
|
65394
|
+
if (parsed.parsed.family !== "decision" || parsed.parsed.action !== "record")
|
|
65395
|
+
return null;
|
|
65396
|
+
const request = parsed.parsed.request;
|
|
65397
|
+
if (typeof request.purpose !== "string" || !request.binding)
|
|
65398
|
+
return null;
|
|
65399
|
+
return workitBindingQuestionIssue([
|
|
65400
|
+
{
|
|
65401
|
+
header: `Workit decision: ${request.purpose}`,
|
|
65402
|
+
question: typeof request.binding.presented === "string" ? request.binding.presented : "",
|
|
65403
|
+
options: [
|
|
65404
|
+
{
|
|
65405
|
+
label: "approved",
|
|
65406
|
+
description: typeof request.binding.displayed === "string" ? request.binding.displayed : typeof request.binding.approvedContent === "string" ? request.binding.approvedContent : ""
|
|
65407
|
+
},
|
|
65408
|
+
{ label: "rejected", description: "Reject this decision" }
|
|
65409
|
+
]
|
|
65410
|
+
}
|
|
65411
|
+
]);
|
|
65412
|
+
})();
|
|
65413
|
+
if (decisionBudgetIssue)
|
|
65414
|
+
result = failure("invalid_input", decisionBudgetIssue);
|
|
65415
|
+
else if (parsed.parsed.handoff) {
|
|
64925
65416
|
const exported = dispatch(core, "state", parsed.parsed.request);
|
|
64926
65417
|
if (!exported.ok)
|
|
64927
65418
|
result = exported;
|
|
@@ -64985,8 +65476,34 @@ var cliActionAuthority = (actor, reconciliationTokens = new WeakSet) => ({
|
|
|
64985
65476
|
var nativeExternalActionRunner = (root, actor, core, step) => createAuthorizedExternalActionRunner(core, (operationValue) => {
|
|
64986
65477
|
const store = new TaskStore(root);
|
|
64987
65478
|
const approved = approvedExternalAction(store, "workit_cli", actor, operationValue);
|
|
64988
|
-
if (!approved.ok)
|
|
65479
|
+
if (!approved.ok) {
|
|
65480
|
+
const plan = planCommitBinding(store, "workit_cli", actor, operationValue);
|
|
65481
|
+
if (plan) {
|
|
65482
|
+
const actionRef = externalActionRef("workit_cli", actor, operationValue);
|
|
65483
|
+
return {
|
|
65484
|
+
taskId: plan.taskId,
|
|
65485
|
+
decisionId: plan.decisionId,
|
|
65486
|
+
actionRef,
|
|
65487
|
+
expectedRevision: plan.expectedRevision,
|
|
65488
|
+
expectedWorkspaceRevision: plan.expectedWorkspaceRevision,
|
|
65489
|
+
binding: plan.binding,
|
|
65490
|
+
step: plan.step,
|
|
65491
|
+
refresh: () => {
|
|
65492
|
+
const task = store.readTask(plan.taskId);
|
|
65493
|
+
const freshWorkspace = store.readWorkspace();
|
|
65494
|
+
if (!task.ok || !freshWorkspace.ok || !freshWorkspace.data)
|
|
65495
|
+
throw new Error("external action state changed");
|
|
65496
|
+
return {
|
|
65497
|
+
expectedRevision: task.data.revision,
|
|
65498
|
+
expectedWorkspaceRevision: freshWorkspace.data.revision
|
|
65499
|
+
};
|
|
65500
|
+
},
|
|
65501
|
+
reserveObservation: nativeExternalActionObservation(actor, actionRef, "reserve"),
|
|
65502
|
+
settleObservation: (outcome, revisions) => nativeExternalActionObservation(actor, actionRef, outcome, actionRef.kind === "host" ? actionRef.handle : "external-action", revisions)
|
|
65503
|
+
};
|
|
65504
|
+
}
|
|
64989
65505
|
return approved;
|
|
65506
|
+
}
|
|
64990
65507
|
const actionRef = externalActionRef("workit_cli", actor, operationValue);
|
|
64991
65508
|
return {
|
|
64992
65509
|
taskId: approved.data.task.id,
|
|
@@ -65050,7 +65567,7 @@ async function runActionCommand(argv, deps = {}) {
|
|
|
65050
65567
|
return 2;
|
|
65051
65568
|
}
|
|
65052
65569
|
const root = deps.root ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? deps.cwd ?? process.cwd();
|
|
65053
|
-
|
|
65570
|
+
let resolved = resolveExternalActionRequest(root, parsed.data);
|
|
65054
65571
|
if (!resolved.ok) {
|
|
65055
65572
|
if (json)
|
|
65056
65573
|
jsonResult(outOf(deps), resolved);
|
|
@@ -65058,6 +65575,14 @@ async function runActionCommand(argv, deps = {}) {
|
|
|
65058
65575
|
printHuman(resolved, deps);
|
|
65059
65576
|
return 2;
|
|
65060
65577
|
}
|
|
65578
|
+
if (resolved.data.request.operation === "git.branch_setup" && resolved.data.descriptorPayload.resolved?.dirty === true && resolved.data.descriptorPayload.stash !== "yes") {
|
|
65579
|
+
const upgraded = resolveExternalActionRequest(root, {
|
|
65580
|
+
...parsed.data,
|
|
65581
|
+
payload: { ...parsed.data.payload, stash: "yes" }
|
|
65582
|
+
});
|
|
65583
|
+
if (upgraded.ok)
|
|
65584
|
+
resolved = upgraded;
|
|
65585
|
+
}
|
|
65061
65586
|
const normalized = resolved.data.request;
|
|
65062
65587
|
const descriptor = externalActionDescriptor(normalized.operation, resolved.data.descriptorPayload);
|
|
65063
65588
|
if (preview) {
|
|
@@ -65183,8 +65708,56 @@ async function runActionCommand(argv, deps = {}) {
|
|
|
65183
65708
|
printHuman(result, deps);
|
|
65184
65709
|
return 1;
|
|
65185
65710
|
}
|
|
65711
|
+
const drift = normalized.operation === "git.branch_setup" ? priorResolvedDrift(store, "workit_cli", actor, normalized.operation, normalized.payload, resolved.data.descriptorPayload.resolved) : success(null, null, null);
|
|
65712
|
+
if (!drift.ok) {
|
|
65713
|
+
if (json)
|
|
65714
|
+
jsonResult(outOf(deps), drift);
|
|
65715
|
+
else
|
|
65716
|
+
printHuman(drift, deps);
|
|
65717
|
+
return 1;
|
|
65718
|
+
}
|
|
65719
|
+
const localOperation = [
|
|
65720
|
+
"git.branch_setup",
|
|
65721
|
+
"git.commit",
|
|
65722
|
+
"git.push",
|
|
65723
|
+
"hosting.pull_request",
|
|
65724
|
+
"changelog.apply"
|
|
65725
|
+
].includes(normalized.operation);
|
|
65726
|
+
if (localOperation) {
|
|
65727
|
+
const writer = assertLocalExternalActionWriter(root, { host: "workit_cli", actor });
|
|
65728
|
+
if (!writer.ok) {
|
|
65729
|
+
const result = failure("needs_input", "writer ownership is required before this action", {
|
|
65730
|
+
outcome: "not_started",
|
|
65731
|
+
operation: normalized.operation,
|
|
65732
|
+
guidance: "Acquire checkout writer ownership first (writer.acquire) and retry the action."
|
|
65733
|
+
});
|
|
65734
|
+
if (json)
|
|
65735
|
+
jsonResult(outOf(deps), result);
|
|
65736
|
+
else
|
|
65737
|
+
printHuman(result, deps);
|
|
65738
|
+
return 1;
|
|
65739
|
+
}
|
|
65740
|
+
}
|
|
65741
|
+
const question = actionProposalQuestion(normalized, resolved.data.descriptorPayload);
|
|
65742
|
+
const commitMessage = normalized.operation === "git.commit" ? normalized.payload.message : undefined;
|
|
65743
|
+
const currentBranch = resolved.data.descriptorPayload.resolved?.branch;
|
|
65744
|
+
const plan = typeof commitMessage === "string" && commitMessage ? approvedPlanCommit(store, "workit_cli", actor, commitMessage) : null;
|
|
65745
|
+
if (plan?.ok && plan.data.branch === currentBranch) {
|
|
65746
|
+
const core = new WorkitCore(store, {
|
|
65747
|
+
...contextFor(root, { ...deps, actor }, "host_observed"),
|
|
65748
|
+
nativeAuthority: cliActionAuthority(actor),
|
|
65749
|
+
workerId: null
|
|
65750
|
+
});
|
|
65751
|
+
const runner = nativeExternalActionRunner(root, actor, core);
|
|
65752
|
+
const result = await runner(descriptor, (step) => executeResolvedExternalAction(resolved.data, root, step, { host: "workit_cli", actor }));
|
|
65753
|
+
if (json)
|
|
65754
|
+
jsonResult(outOf(deps), result);
|
|
65755
|
+
else
|
|
65756
|
+
printHuman(result, deps);
|
|
65757
|
+
return result.ok ? 0 : 1;
|
|
65758
|
+
}
|
|
65186
65759
|
if (!json)
|
|
65187
|
-
write2(outOf(deps),
|
|
65760
|
+
write2(outOf(deps), question.presented);
|
|
65188
65761
|
const accepted = await observeConsent(deps);
|
|
65189
65762
|
if (!accepted.accepted || !accepted.observed) {
|
|
65190
65763
|
const result = failure("permission_denied", "external action was not confirmed by the terminal");
|
|
@@ -65210,8 +65783,9 @@ async function runActionCommand(argv, deps = {}) {
|
|
|
65210
65783
|
taskId: task.id,
|
|
65211
65784
|
workspaceId: workspace.data.id,
|
|
65212
65785
|
scope: task.intent.data.scope,
|
|
65213
|
-
presented:
|
|
65786
|
+
presented: question.presented,
|
|
65214
65787
|
approvedContent: descriptor,
|
|
65788
|
+
displayed: question.approvedText,
|
|
65215
65789
|
contentRefs: []
|
|
65216
65790
|
},
|
|
65217
65791
|
response: "approved",
|
|
@@ -65228,6 +65802,15 @@ async function runActionCommand(argv, deps = {}) {
|
|
|
65228
65802
|
printHuman(decision, deps);
|
|
65229
65803
|
return 1;
|
|
65230
65804
|
}
|
|
65805
|
+
const planSteps = resolved.data.descriptorPayload.plan_steps;
|
|
65806
|
+
if (normalized.operation === "git.commit" && Array.isArray(planSteps)) {
|
|
65807
|
+
const result = success(null, null, { plan_commits: planSteps.length });
|
|
65808
|
+
if (json)
|
|
65809
|
+
jsonResult(outOf(deps), result);
|
|
65810
|
+
else
|
|
65811
|
+
printHuman(result, deps);
|
|
65812
|
+
return 0;
|
|
65813
|
+
}
|
|
65231
65814
|
const runner = nativeExternalActionRunner(root, actor, core);
|
|
65232
65815
|
const result = await runner(descriptor, (step) => executeResolvedExternalAction(resolved.data, root, step, { host: "workit_cli", actor }));
|
|
65233
65816
|
if (json)
|