@brainervirus/workit-cli 1.0.9 → 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 +623 -129
- 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,
|
|
@@ -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)
|
|
@@ -58949,7 +59112,7 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
58949
59112
|
var METHODS = {
|
|
58950
59113
|
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
58951
59114
|
"workit-behavioral-tdd": { dimensions: ["testing"] },
|
|
58952
|
-
"workit-review": { dimensions: ["review"] },
|
|
59115
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
|
|
58953
59116
|
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
58954
59117
|
"workit-implement": { dimensions: ["delegation"] },
|
|
58955
59118
|
"workit-debug": { ruleIds: ["root-cause-investigation"] },
|
|
@@ -59000,7 +59163,7 @@ function reconcileResume(view, observations = []) {
|
|
|
59000
59163
|
return candidate;
|
|
59001
59164
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
59002
59165
|
const blockers = [...view.task.progress.blockers];
|
|
59003
|
-
if (view.task.workers.some((entry) =>
|
|
59166
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
59004
59167
|
blockers.push({
|
|
59005
59168
|
reason: "worker state requires reconciliation",
|
|
59006
59169
|
dependentAction: "resume",
|
|
@@ -59092,6 +59255,7 @@ var exportDigest = (bundle) => sha256({
|
|
|
59092
59255
|
task: bundle.task
|
|
59093
59256
|
});
|
|
59094
59257
|
// packages/workit-core/src/core/external-action.ts
|
|
59258
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
59095
59259
|
var externalActionSchema = discriminatedUnion("operation", [
|
|
59096
59260
|
object({
|
|
59097
59261
|
operation: literal("git.branch_setup"),
|
|
@@ -59104,7 +59268,11 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
59104
59268
|
}).strict(),
|
|
59105
59269
|
object({
|
|
59106
59270
|
operation: literal("git.commit"),
|
|
59107
|
-
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()
|
|
59108
59276
|
}).strict(),
|
|
59109
59277
|
object({
|
|
59110
59278
|
operation: literal("git.push"),
|
|
@@ -59193,7 +59361,7 @@ var externalActionDescriptor = (operation, payload) => canonicalJson({
|
|
|
59193
59361
|
payload,
|
|
59194
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 } : {}
|
|
59195
59363
|
});
|
|
59196
|
-
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).";
|
|
59197
59365
|
var externalActionRef = (host, actor, descriptor) => ({
|
|
59198
59366
|
kind: "host",
|
|
59199
59367
|
host,
|
|
@@ -59244,6 +59412,92 @@ var priorExternalAction = (store, host, actor, operation, payload) => {
|
|
|
59244
59412
|
return failure("permission_denied", candidates.length === 0 ? "no prior external action matches this request" : "multiple prior external actions match this request");
|
|
59245
59413
|
return success(workspace.data.revision, null, { ...candidates[0], workspace: workspace.data });
|
|
59246
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
|
+
};
|
|
59247
59501
|
var approvedExternalAction = (store, host, actor, operation) => {
|
|
59248
59502
|
const selected = externalActionState(store, host, actor, operation);
|
|
59249
59503
|
if (!selected.ok)
|
|
@@ -59252,6 +59506,23 @@ var approvedExternalAction = (store, host, actor, operation) => {
|
|
|
59252
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");
|
|
59253
59507
|
return selected;
|
|
59254
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
|
+
};
|
|
59255
59526
|
var createAuthorizedExternalActionRunner = (core, bind) => async (operation, effect) => {
|
|
59256
59527
|
let steps = [];
|
|
59257
59528
|
try {
|
|
@@ -59510,7 +59781,7 @@ function resolveRequirements(input) {
|
|
|
59510
59781
|
].join(", ")}`,
|
|
59511
59782
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
59512
59783
|
before: "dependent_action",
|
|
59513
|
-
dependentAction:
|
|
59784
|
+
dependentAction: null,
|
|
59514
59785
|
acceptanceAllowed: false
|
|
59515
59786
|
}));
|
|
59516
59787
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -59521,7 +59792,7 @@ function resolveRequirements(input) {
|
|
|
59521
59792
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
59522
59793
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
59523
59794
|
before: "dependent_action",
|
|
59524
|
-
dependentAction:
|
|
59795
|
+
dependentAction: null,
|
|
59525
59796
|
acceptanceAllowed: false
|
|
59526
59797
|
}));
|
|
59527
59798
|
if (signals.behaviorChange.value === true)
|
|
@@ -59679,59 +59950,6 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
59679
59950
|
return parsed.data;
|
|
59680
59951
|
}
|
|
59681
59952
|
|
|
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
59953
|
// packages/workit-core/src/core/task-engine.ts
|
|
59736
59954
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
59737
59955
|
kind,
|
|
@@ -59862,15 +60080,20 @@ var applyWorkerLifecycle = (input) => {
|
|
|
59862
60080
|
return failure("invalid_transition", "worker has not started");
|
|
59863
60081
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
59864
60082
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
60083
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
59865
60084
|
const nextEntry = {
|
|
59866
60085
|
...entry,
|
|
59867
60086
|
recordedAt: input.now ?? entry.recordedAt,
|
|
59868
60087
|
provenance: authority.provenance,
|
|
59869
|
-
data: {
|
|
60088
|
+
data: {
|
|
60089
|
+
...entry.data,
|
|
60090
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
60091
|
+
session: input.session
|
|
60092
|
+
}
|
|
59870
60093
|
};
|
|
59871
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;
|
|
59872
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;
|
|
59873
|
-
if (entry.data.state ===
|
|
60096
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
59874
60097
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
59875
60098
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
59876
60099
|
taskId: task.data.id,
|
|
@@ -60240,7 +60463,7 @@ class WorkitCore {
|
|
|
60240
60463
|
activeWorkerBlocker(task, workspace) {
|
|
60241
60464
|
if (workspace.writer)
|
|
60242
60465
|
return failure("recovery_required", "writer ownership must be released first");
|
|
60243
|
-
if (task.workers.some((entry) =>
|
|
60466
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
60244
60467
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
60245
60468
|
return null;
|
|
60246
60469
|
}
|
|
@@ -60477,11 +60700,6 @@ class WorkitCore {
|
|
|
60477
60700
|
if (helper && !helper.ok)
|
|
60478
60701
|
return helper;
|
|
60479
60702
|
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
60703
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
60486
60704
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
60487
60705
|
if (evidence.kind === "review") {
|
|
@@ -60498,6 +60716,11 @@ class WorkitCore {
|
|
|
60498
60716
|
evidence.beforeCandidateId ??= null;
|
|
60499
60717
|
evidence.candidateId ??= null;
|
|
60500
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
|
+
}
|
|
60501
60724
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
60502
60725
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
60503
60726
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -61393,6 +61616,9 @@ class WorkitCore {
|
|
|
61393
61616
|
id: task.id,
|
|
61394
61617
|
revision: task.revision,
|
|
61395
61618
|
workspaceRevision: workspace.data.revision,
|
|
61619
|
+
createdAt: task.createdAt,
|
|
61620
|
+
updatedAt: task.updatedAt,
|
|
61621
|
+
runtime: task.runtime ?? null,
|
|
61396
61622
|
objective: task.intent.data.objective,
|
|
61397
61623
|
status: task.status,
|
|
61398
61624
|
closure: task.closure,
|
|
@@ -61478,7 +61704,7 @@ class WorkitCore {
|
|
|
61478
61704
|
const base = reconcileResume(effectiveView);
|
|
61479
61705
|
if (!base.ok)
|
|
61480
61706
|
return base;
|
|
61481
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
61707
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
61482
61708
|
const blockers = [...base.data.blockers];
|
|
61483
61709
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
61484
61710
|
blockers.push({
|
|
@@ -61545,7 +61771,8 @@ class WorkitCore {
|
|
|
61545
61771
|
...current,
|
|
61546
61772
|
status,
|
|
61547
61773
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
61548
|
-
progress:
|
|
61774
|
+
progress: current.progress,
|
|
61775
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
61549
61776
|
})
|
|
61550
61777
|
});
|
|
61551
61778
|
if (!changed.ok)
|
|
@@ -61581,7 +61808,7 @@ class WorkitCore {
|
|
|
61581
61808
|
return workspace;
|
|
61582
61809
|
if (!workspace.data)
|
|
61583
61810
|
return failure("not_found", "workspace not found");
|
|
61584
|
-
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)))
|
|
61585
61812
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
61586
61813
|
this.fillRevisions(input, task.data, workspace.data);
|
|
61587
61814
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -61729,14 +61956,14 @@ function run(root, executable, args, env = {}) {
|
|
|
61729
61956
|
|
|
61730
61957
|
// packages/workit-core/src/core/branch.ts
|
|
61731
61958
|
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "node:fs";
|
|
61732
|
-
import { execFileSync as
|
|
61959
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
61733
61960
|
import path24 from "node:path";
|
|
61734
61961
|
|
|
61735
61962
|
// packages/workit-core/src/core/git.ts
|
|
61736
|
-
import { execFileSync as
|
|
61963
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
61737
61964
|
var run2 = (cwd, args) => {
|
|
61738
61965
|
try {
|
|
61739
|
-
const stdout =
|
|
61966
|
+
const stdout = execFileSync3("git", args, {
|
|
61740
61967
|
cwd,
|
|
61741
61968
|
encoding: "utf8",
|
|
61742
61969
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -61999,7 +62226,7 @@ var baseBranch = (cwd) => {
|
|
|
61999
62226
|
return { base: String(resolved.defaultTargetBranch ?? "develop") };
|
|
62000
62227
|
};
|
|
62001
62228
|
var originBaseReady = (cwd, base) => {
|
|
62002
|
-
const run = (args) =>
|
|
62229
|
+
const run = (args) => execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
62003
62230
|
try {
|
|
62004
62231
|
try {
|
|
62005
62232
|
run(["fetch", "origin", base, "--prune"]);
|
|
@@ -62007,7 +62234,7 @@ var originBaseReady = (cwd, base) => {
|
|
|
62007
62234
|
run(["fetch", "origin", "--prune"]);
|
|
62008
62235
|
}
|
|
62009
62236
|
try {
|
|
62010
|
-
|
|
62237
|
+
execFileSync4("git", ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${base}`], {
|
|
62011
62238
|
cwd,
|
|
62012
62239
|
stdio: "pipe"
|
|
62013
62240
|
});
|
|
@@ -62026,11 +62253,11 @@ var originBaseReady = (cwd, base) => {
|
|
|
62026
62253
|
}
|
|
62027
62254
|
};
|
|
62028
62255
|
var fastForwardBase = (cwd, base) => {
|
|
62029
|
-
const run = (args) =>
|
|
62256
|
+
const run = (args) => execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
62030
62257
|
try {
|
|
62031
62258
|
let hasLocalBase = true;
|
|
62032
62259
|
try {
|
|
62033
|
-
|
|
62260
|
+
execFileSync4("git", ["show-ref", "--verify", "--quiet", `refs/heads/${base}`], {
|
|
62034
62261
|
cwd,
|
|
62035
62262
|
stdio: "pipe"
|
|
62036
62263
|
});
|
|
@@ -62071,10 +62298,10 @@ var branchSetup = ({
|
|
|
62071
62298
|
log
|
|
62072
62299
|
}) => {
|
|
62073
62300
|
const cwd = path24.resolve(workspace_root);
|
|
62074
|
-
const exec = (args) =>
|
|
62301
|
+
const exec = (args) => execFileSync4("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
62075
62302
|
const current = gitContext(cwd).branch;
|
|
62076
62303
|
if (!current || current === "unknown")
|
|
62077
|
-
return { error: "not in a git repository" };
|
|
62304
|
+
return { error: "not in a git repository", phase: "preflight" };
|
|
62078
62305
|
const sdd = sdd_dir ?? "docs";
|
|
62079
62306
|
const manifestPath = path24.isAbsolute(sdd) ? path24.join(sdd, "manifest.json") : path24.join(cwd, sdd, "manifest.json");
|
|
62080
62307
|
const ensureManifestDir = () => {
|
|
@@ -62082,7 +62309,6 @@ var branchSetup = ({
|
|
|
62082
62309
|
if (!existsSync17(dir))
|
|
62083
62310
|
mkdirSync8(dir, { recursive: true, mode: 493 });
|
|
62084
62311
|
};
|
|
62085
|
-
ensureManifestDir();
|
|
62086
62312
|
const readManifest = () => {
|
|
62087
62313
|
try {
|
|
62088
62314
|
return JSON.parse(readFileSync15(manifestPath, "utf8"));
|
|
@@ -62100,13 +62326,16 @@ var branchSetup = ({
|
|
|
62100
62326
|
const manifest = readManifest();
|
|
62101
62327
|
const ref = manifest.stash_ref;
|
|
62102
62328
|
if (!ref)
|
|
62103
|
-
return { error: "no stash_ref in manifest" };
|
|
62329
|
+
return { error: "no stash_ref in manifest", phase: "preflight" };
|
|
62104
62330
|
journal(`pre-pop: ${String(ref)}`);
|
|
62105
62331
|
try {
|
|
62106
62332
|
exec(["stash", "pop", String(ref)]);
|
|
62107
62333
|
} catch (error) {
|
|
62108
62334
|
journal("pop: failed");
|
|
62109
|
-
return {
|
|
62335
|
+
return {
|
|
62336
|
+
error: error instanceof Error ? error.message : "stash pop failed",
|
|
62337
|
+
phase: "post_mutation"
|
|
62338
|
+
};
|
|
62110
62339
|
}
|
|
62111
62340
|
journal("pop: ok");
|
|
62112
62341
|
delete manifest.stash_ref;
|
|
@@ -62116,11 +62345,17 @@ var branchSetup = ({
|
|
|
62116
62345
|
}
|
|
62117
62346
|
const target = target_branch ?? "";
|
|
62118
62347
|
if (!target)
|
|
62119
|
-
return {
|
|
62348
|
+
return {
|
|
62349
|
+
error: "target_branch (the working branch to create or switch to) is required for setup",
|
|
62350
|
+
phase: "preflight"
|
|
62351
|
+
};
|
|
62120
62352
|
if (isProtected(cwd, target))
|
|
62121
|
-
return { error: `protected branch ${target}
|
|
62353
|
+
return { error: `protected branch ${target}`, phase: "preflight" };
|
|
62122
62354
|
if (!allowedBranch(cwd, target))
|
|
62123
|
-
return {
|
|
62355
|
+
return {
|
|
62356
|
+
error: `target branch ${target} is not allowed by the branch policy`,
|
|
62357
|
+
phase: "preflight"
|
|
62358
|
+
};
|
|
62124
62359
|
let base;
|
|
62125
62360
|
let targetExists = true;
|
|
62126
62361
|
try {
|
|
@@ -62131,12 +62366,12 @@ var branchSetup = ({
|
|
|
62131
62366
|
if (!targetExists) {
|
|
62132
62367
|
const baseResolved = baseBranch(cwd);
|
|
62133
62368
|
if ("error" in baseResolved)
|
|
62134
|
-
return { error: baseResolved.error };
|
|
62369
|
+
return { error: baseResolved.error, phase: "preflight" };
|
|
62135
62370
|
base = baseResolved.base;
|
|
62136
62371
|
}
|
|
62137
62372
|
journal(`entry: current=${current} target=${target} base=${base ?? "-"}`);
|
|
62138
62373
|
let stash_ref;
|
|
62139
|
-
const failAfterStash = (message) => {
|
|
62374
|
+
const failAfterStash = (message, phase = "post_mutation") => {
|
|
62140
62375
|
let suffix = "";
|
|
62141
62376
|
if (stash_ref) {
|
|
62142
62377
|
journal(`pre-pop: ${stash_ref}`);
|
|
@@ -62149,27 +62384,31 @@ var branchSetup = ({
|
|
|
62149
62384
|
suffix = " (changes preserved in stash)";
|
|
62150
62385
|
}
|
|
62151
62386
|
}
|
|
62152
|
-
return { error: `${message}${suffix}
|
|
62387
|
+
return { error: `${message}${suffix}`, phase: stash_ref ? "post_mutation" : phase };
|
|
62153
62388
|
};
|
|
62154
62389
|
if (current !== target) {
|
|
62155
62390
|
const dirty = Boolean(gitContext(cwd).status_short.trim());
|
|
62156
62391
|
if (dirty && stash !== "yes") {
|
|
62157
62392
|
return {
|
|
62158
|
-
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"
|
|
62159
62395
|
};
|
|
62160
62396
|
}
|
|
62161
62397
|
let validatedBase;
|
|
62162
62398
|
if (!targetExists && base !== undefined) {
|
|
62163
62399
|
const ready = originBaseReady(cwd, base);
|
|
62164
62400
|
if (!ready.ok)
|
|
62165
|
-
return { error: ready.error ?? "ensure-base-branch failed" };
|
|
62401
|
+
return { error: ready.error ?? "ensure-base-branch failed", phase: "preflight" };
|
|
62166
62402
|
validatedBase = base;
|
|
62167
62403
|
}
|
|
62168
62404
|
if (dirty) {
|
|
62169
62405
|
try {
|
|
62170
62406
|
exec(["stash", "push", "-u", "-m", `workit: pre-checkout ${target}`]);
|
|
62171
62407
|
} catch (error) {
|
|
62172
|
-
return {
|
|
62408
|
+
return {
|
|
62409
|
+
error: error instanceof Error ? error.message : "stash push failed",
|
|
62410
|
+
phase: "post_mutation"
|
|
62411
|
+
};
|
|
62173
62412
|
}
|
|
62174
62413
|
stash_ref = "stash@{0}";
|
|
62175
62414
|
journal(`stash push: ${stash_ref}`);
|
|
@@ -62180,14 +62419,14 @@ var branchSetup = ({
|
|
|
62180
62419
|
} catch (error) {
|
|
62181
62420
|
const message = error instanceof Error ? error.message : "checkout failed";
|
|
62182
62421
|
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)
|
|
62422
|
+
return failAfterStash(`branch ${target} is locked by an existing git worktree — remove it first (we do not use worktrees)`, "preflight");
|
|
62184
62423
|
}
|
|
62185
62424
|
try {
|
|
62186
62425
|
let effectiveBase = base;
|
|
62187
62426
|
if (effectiveBase === undefined) {
|
|
62188
62427
|
const lateResolved = baseBranch(cwd);
|
|
62189
62428
|
if ("error" in lateResolved)
|
|
62190
|
-
return failAfterStash(lateResolved.error);
|
|
62429
|
+
return failAfterStash(lateResolved.error, "preflight");
|
|
62191
62430
|
effectiveBase = lateResolved.base;
|
|
62192
62431
|
}
|
|
62193
62432
|
const baseResult = validatedBase !== undefined ? fastForwardBase(cwd, effectiveBase) : ensureBaseBranch(cwd, effectiveBase);
|
|
@@ -64016,6 +64255,94 @@ var safePath = (root, value) => {
|
|
|
64016
64255
|
return null;
|
|
64017
64256
|
}
|
|
64018
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
|
+
};
|
|
64019
64346
|
var approvedResolvedExternalAction = (content) => {
|
|
64020
64347
|
try {
|
|
64021
64348
|
const value = JSON.parse(content);
|
|
@@ -64279,24 +64606,35 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
64279
64606
|
const sdd_dir = safePath(root, request.payload.sdd_dir ?? "docs");
|
|
64280
64607
|
if (!sdd_dir)
|
|
64281
64608
|
return failure("invalid_input", "sdd_dir must stay inside the workspace");
|
|
64609
|
+
const action = request.payload.action ?? "setup";
|
|
64282
64610
|
const policy = resolveBranchPolicyFor(root);
|
|
64283
|
-
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
|
+
});
|
|
64284
64617
|
const head = gitValue(root, ["rev-parse", "HEAD"]);
|
|
64285
64618
|
if (!head)
|
|
64286
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);
|
|
64287
64622
|
const normalized = {
|
|
64288
64623
|
operation: request.operation,
|
|
64289
64624
|
payload: {
|
|
64290
64625
|
...request.payload,
|
|
64291
|
-
action
|
|
64626
|
+
action,
|
|
64292
64627
|
sdd_dir,
|
|
64293
64628
|
stash: request.payload.stash ?? "no",
|
|
64294
|
-
target_branch
|
|
64629
|
+
...target_branch ? { target_branch } : {}
|
|
64295
64630
|
}
|
|
64296
64631
|
};
|
|
64297
64632
|
return success(null, null, {
|
|
64298
64633
|
request: normalized,
|
|
64299
|
-
descriptorPayload: {
|
|
64634
|
+
descriptorPayload: {
|
|
64635
|
+
...normalized.payload,
|
|
64636
|
+
resolved: { head, dirty, ...creation }
|
|
64637
|
+
}
|
|
64300
64638
|
});
|
|
64301
64639
|
}
|
|
64302
64640
|
case "git.push": {
|
|
@@ -64310,7 +64648,37 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
64310
64648
|
}) : failure("invalid_input", "git push requires a resolvable current branch");
|
|
64311
64649
|
}
|
|
64312
64650
|
case "git.commit": {
|
|
64651
|
+
const planSteps = request.payload.plan_steps;
|
|
64652
|
+
const planBranch = request.payload.plan_branch;
|
|
64313
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
|
+
}
|
|
64314
64682
|
const staged = gitRaw(root, [
|
|
64315
64683
|
"diff",
|
|
64316
64684
|
"--cached",
|
|
@@ -64321,7 +64689,13 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
64321
64689
|
"HEAD"
|
|
64322
64690
|
]);
|
|
64323
64691
|
const paths = stagedPaths(root);
|
|
64324
|
-
|
|
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)
|
|
64325
64699
|
return failure("storage_error", "Git state could not be resolved");
|
|
64326
64700
|
let policy;
|
|
64327
64701
|
try {
|
|
@@ -64336,13 +64710,13 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
64336
64710
|
`).filter(Boolean));
|
|
64337
64711
|
flavor = detected.flavor ?? "conventional";
|
|
64338
64712
|
}
|
|
64339
|
-
if (!matchCommitFlavor(
|
|
64713
|
+
if (!matchCommitFlavor(message, flavor, policy.pattern))
|
|
64340
64714
|
return failure("invalid_input", `Commit message does not match the ${flavor} flavor (commitPolicy.preset)`);
|
|
64341
64715
|
return success(null, null, {
|
|
64342
64716
|
request,
|
|
64343
64717
|
descriptorPayload: {
|
|
64344
64718
|
...request.payload,
|
|
64345
|
-
resolved: { head, staged: sha256(staged), paths }
|
|
64719
|
+
resolved: { head, branch, staged: sha256(staged), paths }
|
|
64346
64720
|
}
|
|
64347
64721
|
});
|
|
64348
64722
|
}
|
|
@@ -64512,10 +64886,24 @@ var executeConcreteExternalAction = async (request, root, marker, dateMs, step,
|
|
|
64512
64886
|
sdd_dir: request.payload.sdd_dir ?? "docs",
|
|
64513
64887
|
workspace_root: root
|
|
64514
64888
|
});
|
|
64515
|
-
|
|
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);
|
|
64516
64898
|
}
|
|
64517
64899
|
case "git.commit": {
|
|
64518
|
-
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]);
|
|
64519
64907
|
return result.exitCode === 0 ? success(null, null, { stdout: result.stdout.trim() }) : unknown2(request.operation);
|
|
64520
64908
|
}
|
|
64521
64909
|
case "git.push": {
|
|
@@ -64921,7 +65309,29 @@ async function runTaskCommand(argv, deps = {}) {
|
|
|
64921
65309
|
}
|
|
64922
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"));
|
|
64923
65311
|
let result;
|
|
64924
|
-
|
|
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) {
|
|
64925
65335
|
const exported = dispatch(core, "state", parsed.parsed.request);
|
|
64926
65336
|
if (!exported.ok)
|
|
64927
65337
|
result = exported;
|
|
@@ -64985,8 +65395,34 @@ var cliActionAuthority = (actor, reconciliationTokens = new WeakSet) => ({
|
|
|
64985
65395
|
var nativeExternalActionRunner = (root, actor, core, step) => createAuthorizedExternalActionRunner(core, (operationValue) => {
|
|
64986
65396
|
const store = new TaskStore(root);
|
|
64987
65397
|
const approved = approvedExternalAction(store, "workit_cli", actor, operationValue);
|
|
64988
|
-
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
|
+
}
|
|
64989
65424
|
return approved;
|
|
65425
|
+
}
|
|
64990
65426
|
const actionRef = externalActionRef("workit_cli", actor, operationValue);
|
|
64991
65427
|
return {
|
|
64992
65428
|
taskId: approved.data.task.id,
|
|
@@ -65183,8 +65619,56 @@ async function runActionCommand(argv, deps = {}) {
|
|
|
65183
65619
|
printHuman(result, deps);
|
|
65184
65620
|
return 1;
|
|
65185
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
|
+
}
|
|
65186
65670
|
if (!json)
|
|
65187
|
-
write2(outOf(deps),
|
|
65671
|
+
write2(outOf(deps), question.presented);
|
|
65188
65672
|
const accepted = await observeConsent(deps);
|
|
65189
65673
|
if (!accepted.accepted || !accepted.observed) {
|
|
65190
65674
|
const result = failure("permission_denied", "external action was not confirmed by the terminal");
|
|
@@ -65210,8 +65694,9 @@ async function runActionCommand(argv, deps = {}) {
|
|
|
65210
65694
|
taskId: task.id,
|
|
65211
65695
|
workspaceId: workspace.data.id,
|
|
65212
65696
|
scope: task.intent.data.scope,
|
|
65213
|
-
presented:
|
|
65697
|
+
presented: question.presented,
|
|
65214
65698
|
approvedContent: descriptor,
|
|
65699
|
+
displayed: question.approvedText,
|
|
65215
65700
|
contentRefs: []
|
|
65216
65701
|
},
|
|
65217
65702
|
response: "approved",
|
|
@@ -65228,6 +65713,15 @@ async function runActionCommand(argv, deps = {}) {
|
|
|
65228
65713
|
printHuman(decision, deps);
|
|
65229
65714
|
return 1;
|
|
65230
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
|
+
}
|
|
65231
65725
|
const runner = nativeExternalActionRunner(root, actor, core);
|
|
65232
65726
|
const result = await runner(descriptor, (step) => executeResolvedExternalAction(resolved.data, root, step, { host: "workit_cli", actor }));
|
|
65233
65727
|
if (json)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
|
|
6
6
|
"keywords": [
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"typecheck": "tsc --noEmit"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@brainervirus/workit-core": "^1.0.
|
|
41
|
-
"@brainervirus/workit-cursor": "^1.0.
|
|
42
|
-
"@brainervirus/workit-opencode": "^1.0.
|
|
40
|
+
"@brainervirus/workit-core": "^1.0.10",
|
|
41
|
+
"@brainervirus/workit-cursor": "^1.0.10",
|
|
42
|
+
"@brainervirus/workit-opencode": "^1.0.10",
|
|
43
43
|
"@inkjs/ui": "2.0.0",
|
|
44
44
|
"ink": "7.1.1",
|
|
45
45
|
"react": "19.2.8"
|