@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.
Files changed (2) hide show
  1. package/dist/index.js +623 -129
  2. 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 ? { ...current.data, revision: newRevision(), root: this.root } : {
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 ? { ...current.data, revision: newRevision(), root: this.root } : {
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
- return parsed.success ? success(null, null, parsed.data) : failure("recovery_required", "snapshot does not satisfy its schema");
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", "candidate scope escapes checkout");
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", "candidate scope escapes checkout");
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", "candidate scope cannot be inspected");
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) => ["dispatching", "running", "cancelling", "unknown"].includes(entry.data.state)))
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({ message: string2().min(1) }).strict()
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?,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).";
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: "dependent-action",
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: "dependent-action",
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: { ...entry.data, state: input.state, session: input.session }
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 === input.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
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) => ["dispatching", "running", "cancelling", "unknown"].includes(entry.data.state)))
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) => ["dispatching", "running", "cancelling", "unknown"].includes(observedStates.get(entry.id) ?? entry.data.state));
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: status === "paused" ? { ...current.progress, summary: input.reason } : current.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 !== "stopped"))
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 execFileSync3 } from "node:child_process";
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 execFileSync2 } from "node:child_process";
61963
+ import { execFileSync as execFileSync3 } from "node:child_process";
61737
61964
  var run2 = (cwd, args) => {
61738
61965
  try {
61739
- const stdout = execFileSync2("git", args, {
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) => execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
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
- execFileSync3("git", ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${base}`], {
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) => execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
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
- execFileSync3("git", ["show-ref", "--verify", "--quiet", `refs/heads/${base}`], {
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) => execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
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 { error: error instanceof Error ? error.message : "stash pop failed" };
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 { error: "target branch required" };
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 { error: `target branch ${target} is not allowed by the branch policy` };
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 { error: error instanceof Error ? error.message : "stash push failed" };
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 ?? policy.defaultTargetBranch;
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: request.payload.action ?? "setup",
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: { ...normalized.payload, resolved: { head, target_branch } }
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
- if (!head || staged === null || paths === null)
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(request.payload.message, flavor, policy.pattern))
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
- return result.error ? unknown2(request.operation) : success(null, null, result);
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 result = run3(root, ["commit", "-m", request.payload.message]);
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
- if (parsed.parsed.handoff) {
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), `External action preview: ${descriptor}`);
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: `Approve ${descriptor}`,
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.9",
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.9",
41
- "@brainervirus/workit-cursor": "^1.0.9",
42
- "@brainervirus/workit-opencode": "^1.0.9",
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"