@brainervirus/workit-cli 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +268 -78
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -43278,7 +43278,7 @@ import path11 from "node:path";
43278
43278
  var SUPPORT_MATRIX = {
43279
43279
  bun: "1.4.1",
43280
43280
  node: { minimum: "24", current: "24.20.0" },
43281
- opencode: { minimum: "1.15.0", current: "1.18.30" },
43281
+ opencode: { minimum: "1.18.30", current: "1.18.30" },
43282
43282
  codex: { cli: "0.153.4", desktopPackage: "26.901.20858", bundledCodexCli: "0.153.0-alpha.5" },
43283
43283
  os: ["ubuntu-latest", "macos-latest", "windows-latest"]
43284
43284
  };
@@ -51105,6 +51105,7 @@ var workerSchema = object({
51105
51105
  assignment: assignmentSchema,
51106
51106
  state: _enum(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
51107
51107
  session: refSchema.nullable(),
51108
+ coordinator: refSchema.optional(),
51108
51109
  report: workerReportSchema.nullable()
51109
51110
  }).strict();
51110
51111
  var ownerSchema = object({
@@ -51216,6 +51217,7 @@ var taskSummarySchema = object({
51216
51217
  requirements: array(requirementEvaluationSchema),
51217
51218
  writer: workspaceRecordSchema.shape.writer
51218
51219
  }).strict();
51220
+ var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true });
51219
51221
  var exportBundleSchema = object({
51220
51222
  schemaVersion: literal(1),
51221
51223
  exportedAt: utc,
@@ -51237,11 +51239,15 @@ var taskOperations = {
51237
51239
  expectedWorkspaceRevision: revision.nullable().optional(),
51238
51240
  intent: intentSchema
51239
51241
  }),
51240
- list: operation({ action: literal("list") }),
51242
+ list: operation({
51243
+ action: literal("list"),
51244
+ status: _enum(["open", "closed", "all"]).optional(),
51245
+ limit: number2().int().min(1).max(50).optional()
51246
+ }),
51241
51247
  inspect: operation({
51242
51248
  action: literal("inspect"),
51243
51249
  ...taskId,
51244
- view: _enum(["summary", "full"])
51250
+ view: _enum(["summary", "full"]).optional()
51245
51251
  }),
51246
51252
  revise: operation({
51247
51253
  action: literal("revise"),
@@ -51269,7 +51275,7 @@ var taskOperations = {
51269
51275
  ...taskId,
51270
51276
  ...revisions,
51271
51277
  expectedWorkspaceRevision: revision.optional(),
51272
- authorityRefs: array(refSchema)
51278
+ authorityRefs: array(refSchema).optional()
51273
51279
  }),
51274
51280
  close: operation({
51275
51281
  action: literal("close"),
@@ -58253,6 +58259,10 @@ var validPath = (value) => typeof value === "string" && value.length > 0 && ![..
58253
58259
  }) && !value.split(/[\\/]/).includes("..");
58254
58260
  var callerValue = (value) => ({ host: value.host, actor: value.actor });
58255
58261
  var workerIdOf = (value) => value.workerId ?? null;
58262
+ var currentWriterOwnsTask = (task, workspace, caller) => {
58263
+ const owner = workspace.writer?.owner;
58264
+ return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
58265
+ };
58256
58266
  function assertProductWriteAllowed(input) {
58257
58267
  if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
58258
58268
  return failure("invalid_input", "invalid product write path");
@@ -58958,9 +58968,11 @@ var standingReceiptFor = (root, workspace, cls, dir) => {
58958
58968
  var verifyStandingApproval = (root, task, caller, binding) => {
58959
58969
  if (task.status !== "active")
58960
58970
  return failure("invalid_transition", "only active tasks can authorize actions");
58961
- const session = task.intent?.provenance?.session;
58962
- if (!session || session.kind !== "host" || session.host !== caller.host || session.handle !== caller.actor)
58963
- return failure("permission_denied", "standing approval requires the lead session");
58971
+ const workspace = new TaskStore(root).readWorkspace();
58972
+ if (!workspace.ok)
58973
+ return workspace;
58974
+ if (!workspace.data || !currentWriterOwnsTask(task, workspace.data, caller))
58975
+ return failure("permission_denied", "standing approval requires the current writer session");
58964
58976
  const standing = binding.standing;
58965
58977
  if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
58966
58978
  return failure("invalid_input", "standing approval needs a workspace and class");
@@ -59008,14 +59020,14 @@ var standingAutoApplies = (store, host, actor, operation) => {
59008
59020
  const workspace = store.readWorkspace();
59009
59021
  if (!listed.ok || !workspace.ok || !workspace.data)
59010
59022
  return null;
59011
- const matches = listed.data.filter((task) => task.status === "active" && task.workspaceId === workspace.data.id && task.intent?.provenance?.session?.kind === "host" && task.intent.provenance.session.host === host && task.intent.provenance.session.handle === actor);
59012
- if (matches.length !== 1)
59023
+ const task = listed.data.find((task) => currentWriterOwnsTask(task, workspace.data, { host, actor }));
59024
+ if (!task)
59013
59025
  return null;
59014
59026
  const resolved = resolveAutoApproval(store.root);
59015
59027
  if (resolved.status !== "on")
59016
59028
  return null;
59017
59029
  return {
59018
- task: matches[0],
59030
+ task,
59019
59031
  workspaceId: workspace.data.id,
59020
59032
  workspaceRevision: workspace.data.revision,
59021
59033
  cls,
@@ -59274,6 +59286,20 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
59274
59286
  };
59275
59287
  var verifyContentRefs = (store, binding) => verifyDecisionContentAtRoot(store.root, binding);
59276
59288
  var storedDecisionApplicable = (store, task, purpose, binding) => task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && verifyDecisionContentAtRoot(store.root, entry.data.binding).ok);
59289
+ var approvalAuthorityMatches = (store, task, approval, current) => {
59290
+ if (approval.kind !== "host_observed" || approval.receipts.length === 0)
59291
+ return false;
59292
+ if (provenanceMatches(approval, current))
59293
+ return true;
59294
+ const session = current.kind === "host_observed" ? current.session : null;
59295
+ if (!session || session.kind !== "host")
59296
+ return false;
59297
+ const workspace = store.readWorkspace();
59298
+ return workspace.ok && workspace.data !== null && currentWriterOwnsTask(task, workspace.data, {
59299
+ host: session.host,
59300
+ actor: session.handle
59301
+ });
59302
+ };
59277
59303
  var validateAction = (store, task, workspaceId, input, authority) => {
59278
59304
  if (task.status !== "active")
59279
59305
  return failure("invalid_transition", "only active tasks can authorize actions");
@@ -59299,7 +59325,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
59299
59325
  return failure("permission_denied", "native reservation authority does not match decision");
59300
59326
  if (decision.response !== "approved")
59301
59327
  return failure("permission_denied", "decision was rejected");
59302
- if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
59328
+ if (!approvalAuthorityMatches(store, task, entry.provenance, authority.provenance))
59303
59329
  return failure("permission_denied", "action approval lacks native receipt assurance");
59304
59330
  const receipts = entry.provenance.receipts;
59305
59331
  const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
@@ -59466,7 +59492,7 @@ function settleAction(input) {
59466
59492
  return failure("not_found", "decision not found");
59467
59493
  if (!authorityDecisionMatches(authority, entry.data))
59468
59494
  return failure("permission_denied", "native settlement authority does not match decision");
59469
- if (!provenanceMatches(entry.provenance, authority.provenance))
59495
+ if (!approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
59470
59496
  return failure("permission_denied", "native settlement caller does not match approval");
59471
59497
  if (entry.data.purpose !== "action" || entry.data.digest !== decisionDigest(entry.data))
59472
59498
  return failure("permission_denied", "decision binding is invalid");
@@ -59565,7 +59591,7 @@ function reconcileAction(input) {
59565
59591
  const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
59566
59592
  if (!entry || entry.data.purpose !== "action")
59567
59593
  return failure("not_found", "decision not found");
59568
- if (!authorityDecisionMatches(authority, entry.data) || !provenanceMatches(entry.provenance, authority.provenance))
59594
+ if (!authorityDecisionMatches(authority, entry.data) || !approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
59569
59595
  return failure("permission_denied", "native reconciliation caller does not match approval");
59570
59596
  if (entry.data.response !== "approved" || entry.data.revoked !== null || entry.data.digest !== decisionDigest(entry.data) || entry.data.binding.taskId !== current.id || entry.data.binding.workspaceId !== current.workspaceId || entry.data.binding.workspaceId !== workspace.data.id)
59571
59597
  return failure("permission_denied", "reconciliation decision binding is invalid");
@@ -59902,7 +59928,7 @@ var resolveBinding = (evidence, pin, currentCandidateId) => {
59902
59928
  candidateId: evidence.candidateId ?? fallback
59903
59929
  };
59904
59930
  };
59905
- var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId);
59931
+ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
59906
59932
  var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
59907
59933
  var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
59908
59934
  var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
@@ -60255,6 +60281,11 @@ var exportDigest = (bundle) => sha256({
60255
60281
  });
60256
60282
  // packages/workit-core/src/core/external-action.ts
60257
60283
  import { execFileSync as execFileSync4 } from "node:child_process";
60284
+ var chainStepSchema = union2([
60285
+ string2().min(1),
60286
+ object({ branch: string2().min(1) }).strict(),
60287
+ object({ pr: literal(true) }).strict()
60288
+ ]);
60258
60289
  var externalActionSchema = discriminatedUnion("operation", [
60259
60290
  object({
60260
60291
  operation: literal("git.branch_setup"),
@@ -60269,7 +60300,7 @@ var externalActionSchema = discriminatedUnion("operation", [
60269
60300
  operation: literal("git.commit"),
60270
60301
  payload: object({
60271
60302
  message: string2().min(1).optional(),
60272
- plan_steps: array(string2().min(1)).optional(),
60303
+ plan_steps: array(chainStepSchema).min(1).max(32).optional(),
60273
60304
  plan_branch: string2().min(1).optional()
60274
60305
  }).strict()
60275
60306
  }).strict(),
@@ -60367,7 +60398,13 @@ var externalActionDescriptor = (operation, payload) => canonicalJson({
60367
60398
  payload,
60368
60399
  ...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 } : {}
60369
60400
  });
60370
- 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?}; hosting.merge {target_branch?,source_branch?}; 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).";
60401
+ var planReservationLength = (operation, descriptorPayload) => {
60402
+ if (operation !== "git.commit")
60403
+ return null;
60404
+ const payload = descriptorPayload ?? {};
60405
+ return Array.isArray(payload.plan_steps) ? payload.plan_steps.length : null;
60406
+ };
60407
+ 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?; plan_steps?: (string | {branch:string} | {pr:true})[]; plan_branch?}; git.push {branch?}; hosting.pull_request {title,body?,draft?,target_branch?,babysit?}; hosting.merge {target_branch?,source_branch?}; 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: git|pr|youtrack|github_issue|gitlab_issue|changelog|release|affected,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).";
60371
60408
  var externalActionRef = (host, actor, descriptor) => ({
60372
60409
  kind: "host",
60373
60410
  host,
@@ -60381,12 +60418,12 @@ var externalActionState = (store, host, actor, operation) => {
60381
60418
  if (!workspace.data)
60382
60419
  return failure("not_found", "workspace not found");
60383
60420
  const candidates = listed.data.flatMap((task) => {
60384
- if (task.status !== "active" || task.workspaceId !== workspace.data.id || task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
60421
+ if (task.status !== "active" || task.workspaceId !== workspace.data.id)
60385
60422
  return [];
60386
60423
  return task.decisions.filter((entry) => entry.data.purpose === "action" && entry.data.response === "approved" && entry.data.revoked === null && entry.data.binding.approvedContent === operation).map((entry) => ({ task, entry }));
60387
60424
  });
60388
60425
  if (candidates.length !== 1)
60389
- return failure("permission_denied", candidates.length === 0 ? "no approved action is bound to this session" : "multiple approved actions match this session");
60426
+ return failure("permission_denied", candidates.length === 0 ? "no approved action is bound to the active task" : "multiple approved actions match the active task");
60390
60427
  return success(workspace.data.revision, null, { ...candidates[0], workspace: workspace.data });
60391
60428
  };
60392
60429
  var priorExternalAction = (store, host, actor, operation, payload) => {
@@ -60466,8 +60503,6 @@ var approvedPlanCommit = (store, host, actor, message) => {
60466
60503
  const matches = listed.data.flatMap((task) => {
60467
60504
  if (task.status !== "active" || task.workspaceId !== workspace.data.id)
60468
60505
  return [];
60469
- if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
60470
- return [];
60471
60506
  return task.decisions.flatMap((entry) => {
60472
60507
  const decision = entry.data;
60473
60508
  if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
@@ -60509,8 +60544,6 @@ var approvedChainStep = (store, host, actor, query) => {
60509
60544
  const matches = listed.data.flatMap((task) => {
60510
60545
  if (task.status !== "active" || task.workspaceId !== workspace.data.id)
60511
60546
  return [];
60512
- if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
60513
- return [];
60514
60547
  return task.decisions.flatMap((entry) => {
60515
60548
  const decision = entry.data;
60516
60549
  if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
@@ -60667,7 +60700,7 @@ var branchSetupIntent = (descriptor) => {
60667
60700
  var approvedBranchSetupIntent = (store, host, actor, operation) => {
60668
60701
  const current = branchSetupIntent(operation);
60669
60702
  if (!current)
60670
- return failure("permission_denied", "no approved action is bound to this session");
60703
+ return failure("permission_denied", "no approved action is bound to the active task");
60671
60704
  const listed = store.listTasks();
60672
60705
  const workspace = store.readWorkspace();
60673
60706
  if (!listed.ok || !workspace.ok)
@@ -60675,7 +60708,7 @@ var approvedBranchSetupIntent = (store, host, actor, operation) => {
60675
60708
  if (!workspace.data)
60676
60709
  return failure("not_found", "workspace not found");
60677
60710
  const candidates = listed.data.flatMap((task) => {
60678
- if (task.status !== "active" || task.workspaceId !== workspace.data.id || task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
60711
+ if (task.status !== "active" || task.workspaceId !== workspace.data.id)
60679
60712
  return [];
60680
60713
  return task.decisions.filter((entry) => entry.data.purpose === "action" && entry.data.response === "approved" && entry.data.revoked === null).map((entry) => ({ task, entry }));
60681
60714
  });
@@ -60684,7 +60717,7 @@ var approvedBranchSetupIntent = (store, host, actor, operation) => {
60684
60717
  return intent !== null && intent.target === current.target && intent.base === current.base && intent.remoteBase === current.remoteBase && intent.targetExists === current.targetExists;
60685
60718
  });
60686
60719
  if (matches.length !== 1)
60687
- return failure("permission_denied", matches.length === 0 ? "no approved action is bound to this session" : "multiple approved actions match this session");
60720
+ return failure("permission_denied", matches.length === 0 ? "no approved action is bound to the active task" : "multiple approved actions match the active task");
60688
60721
  return success(workspace.data.revision, null, { ...matches[0], workspace: workspace.data });
60689
60722
  };
60690
60723
  var priorResolvedDrift = (store, host, actor, operation, payload, currentResolved) => {
@@ -61222,6 +61255,7 @@ var verifyNativeWorker = (verifier, input, binding) => {
61222
61255
  expectedWorkspaceRevision: expected.expectedWorkspaceRevision,
61223
61256
  state: expected.state,
61224
61257
  session: expected.session,
61258
+ report: expected.report ?? null,
61225
61259
  provenance: result.data,
61226
61260
  ...binding
61227
61261
  });
@@ -61232,7 +61266,7 @@ var takeWorker = (token, binding, expected) => {
61232
61266
  verifiedWorkers.delete(token);
61233
61267
  if (!value)
61234
61268
  return null;
61235
- if (value.owner !== binding.owner || value.store !== binding.store || value.root !== binding.root || value.root !== value.store.root || !sameValue(value.caller, binding.caller) || value.taskId !== expected.taskId || value.workerId !== expected.workerId || value.expectedRevision !== expected.expectedRevision || value.expectedWorkspaceRevision !== expected.expectedWorkspaceRevision || value.state !== expected.state || !sameValue(value.session, expected.session))
61269
+ if (value.owner !== binding.owner || value.store !== binding.store || value.root !== binding.root || value.root !== value.store.root || !sameValue(value.caller, binding.caller) || value.taskId !== expected.taskId || value.workerId !== expected.workerId || value.expectedRevision !== expected.expectedRevision || value.expectedWorkspaceRevision !== expected.expectedWorkspaceRevision || value.state !== expected.state || !sameValue(value.session, expected.session) || !sameValue(value.report, expected.report ?? null))
61236
61270
  return null;
61237
61271
  return value;
61238
61272
  };
@@ -61273,6 +61307,8 @@ var applyWorkerLifecycle = (input) => {
61273
61307
  return failure("invalid_transition", "worker already has an observed session");
61274
61308
  if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
61275
61309
  return failure("invalid_transition", "worker launch was not claimed");
61310
+ if (input.report && input.state !== "stopped")
61311
+ return failure("invalid_input", "worker reports require a stopped worker");
61276
61312
  if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
61277
61313
  return failure("invalid_input", "running worker observations require a session");
61278
61314
  if (entry.data.session && !sameValue(entry.data.session, input.session))
@@ -61289,12 +61325,13 @@ var applyWorkerLifecycle = (input) => {
61289
61325
  data: {
61290
61326
  ...entry.data,
61291
61327
  state: terminalCancel ? "cancelling" : input.state,
61292
- session: input.session
61328
+ session: input.session,
61329
+ report: entry.data.report ?? input.report ?? null
61293
61330
  }
61294
61331
  };
61295
61332
  const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
61296
61333
  const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
61297
- if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
61334
+ if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && sameValue(entry.data.report, nextEntry.data.report) && !shouldClear && !shouldUncertain)
61298
61335
  return success(task.data.revision, workspace.data.revision, entry);
61299
61336
  const changed = input.store.mutateTaskAndWorkspace({
61300
61337
  taskId: task.data.id,
@@ -61712,14 +61749,30 @@ class WorkitCore {
61712
61749
  const listed = this.store.listTasks();
61713
61750
  if (!listed.ok)
61714
61751
  return listed;
61715
- const summaries = [];
61716
- for (const task of listed.data) {
61717
- const summary = this.summary(task);
61718
- if (!summary.ok)
61719
- return summary;
61720
- summaries.push(summary.data);
61721
- }
61722
- return success(null, null, summaries);
61752
+ const status = input.status ?? "open";
61753
+ const limit = input.limit ?? 20;
61754
+ const selected = listed.data.filter((task) => status === "all" ? true : status === "closed" ? task.status === "closed" : task.status !== "closed").sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit);
61755
+ if (selected.length === 0)
61756
+ return success(null, null, []);
61757
+ const workspace = this.store.readWorkspace();
61758
+ if (!workspace.ok)
61759
+ return workspace;
61760
+ if (!workspace.data)
61761
+ return failure("not_found", "workspace not found");
61762
+ const tasks = selected.map((task) => ({
61763
+ id: task.id,
61764
+ revision: task.revision,
61765
+ workspaceRevision: workspace.data.revision,
61766
+ createdAt: task.createdAt,
61767
+ updatedAt: task.updatedAt,
61768
+ runtime: task.runtime ?? null,
61769
+ objective: task.intent.data.objective,
61770
+ status: task.status,
61771
+ closure: task.closure,
61772
+ progress: task.progress,
61773
+ writer: task.status === "closed" ? null : workspace.data.writer
61774
+ }));
61775
+ return success(null, null, tasks);
61723
61776
  }
61724
61777
  case "inspect": {
61725
61778
  const task = this.store.readTask(input.taskId);
@@ -61728,7 +61781,7 @@ class WorkitCore {
61728
61781
  const helper = this.helperTaskGuard(task.data, true);
61729
61782
  if (!helper.ok)
61730
61783
  return helper;
61731
- if (input.view === "summary")
61784
+ if ((input.view ?? "summary") === "summary")
61732
61785
  return this.summary(task.data);
61733
61786
  return this.view(task.data);
61734
61787
  }
@@ -62072,7 +62125,7 @@ class WorkitCore {
62072
62125
  const evaluations = evaluateEvidence(task.data, current.data);
62073
62126
  const verified = evidence.some((entry) => {
62074
62127
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
62075
- return findingVerificationPasses(finding.data.candidateId, {
62128
+ return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
62076
62129
  kind: entry.data.kind,
62077
62130
  candidateId: entry.data.candidateId,
62078
62131
  status: evaluation?.status ?? "missing"
@@ -62152,7 +62205,18 @@ class WorkitCore {
62152
62205
  if (task.data.status !== "active")
62153
62206
  return failure("invalid_transition", "paused or closed tasks cannot assign workers");
62154
62207
  if (!bindingCovers(task.data.intent.data.scope, input.assignment.scope))
62155
- return failure("permission_denied", "worker assignment is outside task scope");
62208
+ return failure("permission_denied", "worker assignment is outside task scope", {
62209
+ fields: [
62210
+ {
62211
+ path: "assignment.scope",
62212
+ reason: `paths ${JSON.stringify(input.assignment.scope.paths)} must be contained by lead paths ${JSON.stringify(task.data.intent.data.scope.paths)}`
62213
+ },
62214
+ {
62215
+ path: "task.intent.scope",
62216
+ reason: `lead paths are ${JSON.stringify(task.data.intent.data.scope.paths)}`
62217
+ }
62218
+ ]
62219
+ });
62156
62220
  if (input.assignment.decisionIds.some((id) => !task.data.decisions.some((entry) => entry.id === id)))
62157
62221
  return failure("invalid_input", "worker assignment references an unknown decision");
62158
62222
  if (input.assignment.requirementIds.some((id) => !task.data.policy?.requirements.some((requirement) => requirement.id === id)))
@@ -62359,7 +62423,15 @@ class WorkitCore {
62359
62423
  ...item,
62360
62424
  recordedAt: mutation.now,
62361
62425
  provenance: attested.data,
62362
- data: { ...item.data, state: "dispatching" }
62426
+ data: {
62427
+ ...item.data,
62428
+ state: "dispatching",
62429
+ coordinator: {
62430
+ kind: "host",
62431
+ host: this.context.caller.host,
62432
+ handle: this.context.caller.actor
62433
+ }
62434
+ }
62363
62435
  } : item)
62364
62436
  });
62365
62437
  }
@@ -62435,6 +62507,7 @@ class WorkitCore {
62435
62507
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
62436
62508
  state: "stopped",
62437
62509
  session: null,
62510
+ report: null,
62438
62511
  provenance: attested.data,
62439
62512
  owner: this.authorityOwner,
62440
62513
  store: this.store,
@@ -62742,10 +62815,12 @@ class WorkitCore {
62742
62815
  return workspace;
62743
62816
  if (!workspace.data)
62744
62817
  return failure("not_found", "workspace not found");
62745
- const current = captureCandidate(this.store.root, task.intent.data.scope, environment());
62818
+ const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
62819
+ const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
62746
62820
  if (!current.ok)
62747
62821
  return current;
62748
- const requirements = evaluateRequirements(task, workspace.data, this.context.capabilities, current.data, this.store.root, this.context.caller);
62822
+ const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
62823
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
62749
62824
  return success(task.revision, workspace.data.revision, {
62750
62825
  id: task.id,
62751
62826
  revision: task.revision,
@@ -62759,7 +62834,7 @@ class WorkitCore {
62759
62834
  progress: task.progress,
62760
62835
  policy: task.policy,
62761
62836
  requirements,
62762
- writer: workspace.data.writer
62837
+ writer: evaluationWorkspace.writer
62763
62838
  });
62764
62839
  }
62765
62840
  view(task) {
@@ -62768,13 +62843,15 @@ class WorkitCore {
62768
62843
  return workspace;
62769
62844
  if (!workspace.data)
62770
62845
  return failure("not_found", "workspace not found");
62771
- const current = captureCandidate(this.store.root, task.intent.data.scope, environment());
62846
+ const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
62847
+ const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
62772
62848
  if (!current.ok)
62773
62849
  return current;
62774
- const requirements = evaluateRequirements(task, workspace.data, this.context.capabilities, current.data, this.store.root, this.context.caller);
62850
+ const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
62851
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
62775
62852
  return success(task.revision, workspace.data.revision, {
62776
62853
  task,
62777
- workspace: workspace.data,
62854
+ workspace: evaluationWorkspace,
62778
62855
  capabilities: this.context.capabilities,
62779
62856
  requirements,
62780
62857
  evidence: evaluateEvidence(task, current.data)
@@ -62865,8 +62942,6 @@ class WorkitCore {
62865
62942
  return helper;
62866
62943
  if (status === "paused" && task.data.status !== "active" || status === "active" && task.data.status !== "paused")
62867
62944
  return failure("invalid_transition", `cannot transition ${task.data.status} to ${status}`);
62868
- if (status === "active" && input.authorityRefs.length === 0)
62869
- return failure("permission_denied", "resuming a task requires authority references");
62870
62945
  const workspace = this.store.readWorkspace();
62871
62946
  if (!workspace.ok)
62872
62947
  return workspace;
@@ -62896,7 +62971,7 @@ class WorkitCore {
62896
62971
  return failure("needs_input", "imported task requires fresh destination evidence");
62897
62972
  if (current.data.blockers.some((blocker) => blocker.dependentAction === "resume" || blocker.reason === "worker state requires reconciliation"))
62898
62973
  return failure("recovery_required", "imported task requires resume reconciliation");
62899
- if (!validResumeApproval(task.data, workspace.data, input.authorityRefs, this.context))
62974
+ if (!validResumeApproval(task.data, workspace.data, input.authorityRefs ?? [], this.context))
62900
62975
  return failure("permission_denied", "imported resume requires native destination approval");
62901
62976
  resumeCandidate = current.data.candidate;
62902
62977
  }
@@ -63026,7 +63101,7 @@ class WorkitCore {
63026
63101
  task: (value, context) => success(context.revision, null, {
63027
63102
  ...value,
63028
63103
  status: "closed",
63029
- candidates: value.candidates.some((item) => item.id === withCandidate.id) ? value.candidates : [...value.candidates, withCandidate],
63104
+ candidates: taskForView.candidates,
63030
63105
  closure: {
63031
63106
  outcome: closure.data.outcome,
63032
63107
  at: context.now,
@@ -64905,7 +64980,7 @@ var readExternalContext = async (root, payload) => {
64905
64980
  }
64906
64981
  }
64907
64982
  };
64908
- var localAction = (operation) => operation === "git.branch_setup" || operation === "git.commit" || operation === "git.push" || operation === "hosting.pull_request" || operation === "changelog.apply";
64983
+ var localAction = (operation) => operation === "git.branch_setup" || operation === "git.commit" || operation === "git.push" || operation === "hosting.pull_request" || operation === "hosting.merge" || operation === "changelog.apply";
64909
64984
  var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
64910
64985
  const store = new TaskStore(root);
64911
64986
  const listed = store.listTasks();
@@ -64916,7 +64991,7 @@ var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
64916
64991
  return workspace;
64917
64992
  if (!workspace.data)
64918
64993
  return failure("not_found", "workspace not found");
64919
- const candidates = listed.data.filter((task) => task.status === "active" && task.workspaceId === workspace.data.id && task.intent.provenance.session?.kind === "host" && task.intent.provenance.session.host === caller.host && task.intent.provenance.session.handle === caller.actor);
64994
+ const candidates = listed.data.filter((task) => currentWriterOwnsTask(task, workspace.data, caller));
64920
64995
  if (candidates.length !== 1)
64921
64996
  return failure("permission_denied", "local external action requires exactly one active task");
64922
64997
  const allowed = assertProductWriteAllowed({
@@ -64926,7 +65001,7 @@ var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
64926
65001
  paths,
64927
65002
  store
64928
65003
  });
64929
- return allowed.ok ? success(null, null, null) : allowed;
65004
+ return allowed;
64930
65005
  };
64931
65006
  var safePath = (root, value) => {
64932
65007
  try {
@@ -64938,13 +65013,20 @@ var safePath = (root, value) => {
64938
65013
  var branchCreationBaseline = (root, target, base) => {
64939
65014
  const exists = gitValue(root, ["rev-parse", "--verify", "--quiet", `refs/heads/${target}`]) !== null;
64940
65015
  if (exists)
64941
- return { base_branch: base, target_exists: true, remote_base: null };
65016
+ return {
65017
+ base_branch: base,
65018
+ target_exists: true,
65019
+ remote_base: null,
65020
+ target_head: gitValue(root, ["rev-parse", `refs/heads/${target}`])
65021
+ };
64942
65022
  const remote = run3(root, ["ls-remote", "origin", `refs/heads/${base}`]);
64943
65023
  const sha = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : "";
65024
+ const remoteBase = /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null;
64944
65025
  return {
64945
65026
  base_branch: base,
64946
65027
  target_exists: false,
64947
- remote_base: /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null
65028
+ remote_base: remoteBase,
65029
+ target_head: remoteBase
64948
65030
  };
64949
65031
  };
64950
65032
  var shortSha = (value) => typeof value === "string" && value ? value.slice(0, 8) : "unknown";
@@ -65055,7 +65137,7 @@ var actionProposalQuestion = (request, descriptorPayload) => {
65055
65137
  var approvedResolvedExternalAction = (content) => {
65056
65138
  try {
65057
65139
  const value = JSON.parse(content);
65058
- if (value.operation !== "hosting.pull_request" && value.operation !== "hosting.merge" && value.operation !== "changelog.apply" && value.operation !== "youtrack.update" && value.operation !== "youtrack.time" && value.operation !== "youtrack.meeting")
65140
+ if (value.operation !== "git.branch_setup" && value.operation !== "git.commit" && value.operation !== "git.push" && value.operation !== "hosting.pull_request" && value.operation !== "hosting.merge" && value.operation !== "changelog.apply" && value.operation !== "youtrack.update" && value.operation !== "youtrack.time" && value.operation !== "youtrack.meeting")
65059
65141
  return failure("invalid_input", "approved action is not a reconciliable external action");
65060
65142
  if (!value.payload || typeof value.payload !== "object")
65061
65143
  return failure("invalid_input", "approved action payload is incomplete");
@@ -65066,18 +65148,24 @@ var approvedResolvedExternalAction = (content) => {
65066
65148
  if (!request.ok || !resolved || typeof resolved !== "object")
65067
65149
  return failure("invalid_input", "approved hosting target is incomplete");
65068
65150
  const target = resolved;
65151
+ if (value.operation === "git.commit" && (typeof target.head !== "string" || typeof target.staged !== "string" || typeof payload.message !== "string"))
65152
+ return failure("invalid_input", "approved Git commit target is incomplete");
65153
+ if (value.operation === "git.push" && (typeof target.branch !== "string" || typeof target.commit !== "string" || typeof target.remote !== "string"))
65154
+ return failure("invalid_input", "approved Git push target is incomplete");
65155
+ if (value.operation === "git.branch_setup" && (typeof target.head !== "string" || (payload.action === "reapply_stash" ? typeof target.stash_commit !== "string" : typeof payload.target_branch !== "string" || typeof target.target_head !== "string")))
65156
+ return failure("invalid_input", "approved Git branch target is incomplete");
65069
65157
  if (value.operation === "hosting.pull_request" && (typeof target.marker !== "string" || typeof target.source_branch !== "string" || typeof target.source_commit !== "string" || typeof target.remote !== "string" || typeof payload.target_branch !== "string"))
65070
65158
  return failure("invalid_input", "approved hosting target is incomplete");
65071
65159
  if (value.operation === "hosting.merge" && (typeof target.marker !== "string" || typeof target.source_branch !== "string" || typeof target.source_commit !== "string" || typeof target.remote !== "string" || typeof payload.target_branch !== "string"))
65072
65160
  return failure("invalid_input", "approved hosting target is incomplete");
65073
65161
  if (value.operation === "changelog.apply" && (typeof target.target !== "string" || typeof target.beforeDigest !== "string" || typeof target.beforeExists !== "boolean" || typeof target.afterDigest !== "string"))
65074
65162
  return failure("invalid_input", "approved changelog target is incomplete");
65075
- if (value.operation !== "hosting.pull_request" && value.operation !== "changelog.apply" && (typeof target.marker !== "string" || typeof target.baseUrl !== "string" || typeof target.issueId !== "string" || !Array.isArray(target.steps)))
65163
+ if (value.operation !== "git.branch_setup" && value.operation !== "git.commit" && value.operation !== "git.push" && value.operation !== "hosting.pull_request" && value.operation !== "hosting.merge" && value.operation !== "changelog.apply" && (typeof target.marker !== "string" || typeof target.baseUrl !== "string" || typeof target.issueId !== "string" || !Array.isArray(target.steps)))
65076
65164
  return failure("invalid_input", "approved YouTrack target is incomplete");
65077
65165
  return success(null, null, {
65078
65166
  request: request.data,
65079
65167
  descriptorPayload: value.payload,
65080
- marker: String(target.marker)
65168
+ ...typeof target.marker === "string" ? { marker: target.marker } : {}
65081
65169
  });
65082
65170
  } catch {
65083
65171
  return failure("invalid_input", "approved action descriptor is invalid");
@@ -65103,12 +65191,13 @@ var unknownHostingEvidence = (operation) => failure("external_outcome_unknown",
65103
65191
  outcome: "unknown"
65104
65192
  });
65105
65193
  var readHostingAction = async (root, resolved, actionRef) => {
65106
- if (resolved.request.operation !== "hosting.pull_request" || !resolved.marker)
65107
- return unknownHostingEvidence("hosting.pull_request");
65194
+ const operation = resolved.request.operation;
65195
+ if (operation !== "hosting.pull_request" && operation !== "hosting.merge" || !resolved.marker)
65196
+ return unknownHostingEvidence(operation);
65108
65197
  try {
65109
65198
  const cfg = vcsConfig("load", root);
65110
65199
  if (!cfg.ok || !cfg.tokenPath || !cfg.provider || cfg.provider !== "github" && cfg.provider !== "gitlab")
65111
- return unknownHostingEvidence("hosting.pull_request");
65200
+ return unknownHostingEvidence(operation);
65112
65201
  const remote = pushRemote(root);
65113
65202
  const repo = remote ? remoteRepo(remote) : null;
65114
65203
  const approved = resolved.descriptorPayload;
@@ -65127,7 +65216,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
65127
65216
  }
65128
65217
  })();
65129
65218
  if (!repo || typeof branch !== "string" || typeof target !== "string" || typeof sourceCommit !== "string" || typeof approvedRemote !== "string" || !currentIdentity || currentIdentity !== approvedIdentity || repo !== remoteRepo(approvedRemote) || remoteProvider2(approvedRemote) !== null && remoteProvider2(approvedRemote) !== cfg.provider || remoteParts(approvedRemote)?.host !== apiHost)
65130
- return unknownHostingEvidence("hosting.pull_request");
65219
+ return unknownHostingEvidence(operation);
65131
65220
  const token = fs14.readFileSync(cfg.tokenPath, "utf8").trim();
65132
65221
  const headers = { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json" };
65133
65222
  const url = cfg.provider === "github" ? `https://api.github.com/repos/${repo}/pulls?state=all&head=${encodeURIComponent(`${repo.split("/")[0]}:${branch}`)}&base=${encodeURIComponent(target)}&per_page=100&page=1` : `${cfg.gitlab?.apiUrl ?? "https://gitlab.com/api/v4"}/projects/${encodeURIComponent(repo)}/merge_requests?state=all&source_branch=${encodeURIComponent(branch)}&target_branch=${encodeURIComponent(target)}&per_page=100&page=1`;
@@ -65135,14 +65224,14 @@ var readHostingAction = async (root, resolved, actionRef) => {
65135
65224
  headers: cfg.provider === "gitlab" ? { "PRIVATE-TOKEN": token } : headers
65136
65225
  });
65137
65226
  if (!response.ok)
65138
- return unknownHostingEvidence("hosting.pull_request");
65227
+ return unknownHostingEvidence(operation);
65139
65228
  const values = await response.json();
65140
65229
  if (!Array.isArray(values) || values.length >= 100)
65141
- return unknownHostingEvidence("hosting.pull_request");
65230
+ return unknownHostingEvidence(operation);
65142
65231
  const records = [];
65143
65232
  for (const item of values) {
65144
65233
  if (!item || typeof item !== "object" || Array.isArray(item))
65145
- return unknownHostingEvidence("hosting.pull_request");
65234
+ return unknownHostingEvidence(operation);
65146
65235
  const record = item;
65147
65236
  const body = record.body ?? record.description;
65148
65237
  const base = record.base;
@@ -65154,7 +65243,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
65154
65243
  const shaValue = cfg.provider === "github" && githubShape ? head.sha : record.sha;
65155
65244
  const idValue = record.number ?? record.iid ?? record.id;
65156
65245
  if (body !== undefined && body !== null && typeof body !== "string" || (cfg.provider === "github" ? !githubShape : !gitlabShape) || typeof targetValue !== "string" || typeof sourceValue !== "string" || typeof shaValue !== "string" || typeof idValue !== "string" && typeof idValue !== "number")
65157
- return unknownHostingEvidence("hosting.pull_request");
65246
+ return unknownHostingEvidence(operation);
65158
65247
  records.push(record);
65159
65248
  }
65160
65249
  const matches = records.filter((item) => {
@@ -65164,15 +65253,23 @@ var readHostingAction = async (root, resolved, actionRef) => {
65164
65253
  const itemTarget = cfg.provider === "github" ? base?.ref : item.target_branch;
65165
65254
  const itemSource = cfg.provider === "github" ? head?.ref : item.source_branch;
65166
65255
  const itemSha = cfg.provider === "github" ? head?.sha : item.sha;
65167
- return body.includes(resolved.marker) && itemTarget === target && itemSource === branch && itemSha === sourceCommit;
65256
+ const merged = cfg.provider === "github" ? typeof item.merged_at === "string" : item.state === "merged" || typeof item.merged_at === "string";
65257
+ return (operation === "hosting.merge" ? merged : body.includes(resolved.marker)) && itemTarget === target && itemSource === branch && itemSha === sourceCommit;
65168
65258
  });
65259
+ if (operation === "hosting.merge" && records.some((item) => {
65260
+ const base = item.base;
65261
+ const head = item.head;
65262
+ return (cfg.provider === "github" ? base?.ref : item.target_branch) === target && (cfg.provider === "github" ? head?.ref : item.source_branch) === branch && (cfg.provider === "github" ? typeof item.merged_at !== "string" : item.state !== "merged" && typeof item.merged_at !== "string");
65263
+ }))
65264
+ return unknownHostingEvidence(operation);
65169
65265
  if (matches.length !== 1)
65170
- return unknownHostingEvidence("hosting.pull_request");
65266
+ return unknownHostingEvidence(operation);
65171
65267
  const item = matches[0];
65172
65268
  const id = item.number ?? item.iid ?? item.id;
65173
65269
  if (typeof id !== "string" && typeof id !== "number")
65174
- return unknownHostingEvidence("hosting.pull_request");
65270
+ return unknownHostingEvidence(operation);
65175
65271
  const evidenceDigest = sha256({
65272
+ operation,
65176
65273
  provider: cfg.provider,
65177
65274
  id,
65178
65275
  marker: resolved.marker,
@@ -65193,7 +65290,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
65193
65290
  data: { provider: cfg.provider, id }
65194
65291
  });
65195
65292
  } catch {
65196
- return unknownHostingEvidence("hosting.pull_request");
65293
+ return unknownHostingEvidence(operation);
65197
65294
  }
65198
65295
  };
65199
65296
  var appendMarker = (text, marker) => `${text}${text ? `
@@ -65283,7 +65380,70 @@ var readYouTrackAction = async (root, resolved, actionRef) => {
65283
65380
  return unknownHostingEvidence(operation);
65284
65381
  }
65285
65382
  };
65286
- var readExternalAction = async (root, resolved, actionRef) => resolved.request.operation === "hosting.pull_request" ? readHostingAction(root, resolved, actionRef) : readYouTrackAction(root, resolved, actionRef);
65383
+ var readLocalAction = async (root, resolved, actionRef) => {
65384
+ const operation = resolved.request.operation;
65385
+ const target = resolved.descriptorPayload.resolved;
65386
+ if (!target)
65387
+ return unknownHostingEvidence(operation);
65388
+ let observed = null;
65389
+ if (operation === "git.commit") {
65390
+ const commit = gitValue(root, ["rev-parse", "HEAD"]);
65391
+ const parent = commit ? gitValue(root, ["rev-parse", `${commit}^`]) : null;
65392
+ const message = commit ? gitValue(root, ["show", "-s", "--format=%B", commit]) : null;
65393
+ const diff = commit && parent ? gitRaw(root, ["diff", "--raw", "-z", "--no-ext-diff", "--no-textconv", parent, commit]) : null;
65394
+ if (commit && parent === target.head && message === resolved.request.payload.message && diff !== null && sha256(diff) === target.staged)
65395
+ observed = { commit, parent, message, staged: target.staged };
65396
+ } else if (operation === "git.push") {
65397
+ const branch = target.branch;
65398
+ const commit = target.commit;
65399
+ const approvedRemote = target.remote;
65400
+ const currentRemote = pushRemote(root);
65401
+ const currentIdentity = currentRemote ? credentialFreeRemote(currentRemote) : null;
65402
+ if (typeof branch === "string" && typeof commit === "string" && typeof approvedRemote === "string" && currentIdentity === approvedRemote) {
65403
+ const remote = run3(root, ["ls-remote", currentRemote, `refs/heads/${branch}`]);
65404
+ const remoteCommit = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : null;
65405
+ if (remoteCommit === commit)
65406
+ observed = { branch, commit, remote: approvedRemote };
65407
+ }
65408
+ } else if (operation === "git.branch_setup") {
65409
+ const targetBranch = resolved.request.payload.target_branch;
65410
+ const sdd = resolved.request.payload.sdd_dir;
65411
+ if (resolved.request.payload.action === "reapply_stash" && typeof sdd === "string") {
65412
+ try {
65413
+ const manifest = JSON.parse(fs14.readFileSync(path28.join(sdd, "manifest.json"), "utf8"));
65414
+ const stashes = gitRaw(root, ["stash", "list", "--format=%H"]);
65415
+ if (typeof target.stash_commit === "string" && manifest.stash_ref === undefined && stashes !== null && !stashes.split(`
65416
+ `).includes(target.stash_commit))
65417
+ observed = { stash_commit: target.stash_commit };
65418
+ } catch {}
65419
+ } else if (typeof targetBranch === "string" && typeof sdd === "string" && gitValue(root, ["branch", "--show-current"]) === targetBranch && typeof target.target_head === "string" && gitValue(root, ["rev-parse", "HEAD"]) === target.target_head) {
65420
+ try {
65421
+ const manifest = JSON.parse(fs14.readFileSync(path28.join(sdd, "manifest.json"), "utf8"));
65422
+ if (manifest.branch === targetBranch)
65423
+ observed = { branch: targetBranch };
65424
+ } catch {}
65425
+ }
65426
+ } else if (operation === "changelog.apply") {
65427
+ const afterDigest = target.afterDigest;
65428
+ const preview = changelogApplyPreview({ ...resolved.request.payload, workspace_root: root });
65429
+ if (!preview.error && typeof afterDigest === "string" && preview.beforeDigest === afterDigest)
65430
+ observed = { target: target.target, afterDigest };
65431
+ }
65432
+ if (!observed)
65433
+ return unknownHostingEvidence(operation);
65434
+ const evidenceDigest = sha256({ operation, observed });
65435
+ return success(null, null, {
65436
+ outcome: "succeeded",
65437
+ evidenceDigest,
65438
+ observation: {
65439
+ kind: "provider_read",
65440
+ actionRef,
65441
+ outcome: "succeeded",
65442
+ evidenceDigest
65443
+ }
65444
+ });
65445
+ };
65446
+ var readExternalAction = async (root, resolved, actionRef) => resolved.request.operation === "git.branch_setup" || resolved.request.operation === "git.commit" || resolved.request.operation === "git.push" || resolved.request.operation === "changelog.apply" ? readLocalAction(root, resolved, actionRef) : resolved.request.operation === "hosting.pull_request" || resolved.request.operation === "hosting.merge" ? readHostingAction(root, resolved, actionRef) : readYouTrackAction(root, resolved, actionRef);
65287
65447
  var youTrackBase = () => {
65288
65448
  try {
65289
65449
  const config = JSON.parse(fs14.readFileSync(youTrackConfigPath(), "utf8"));
@@ -65341,6 +65501,16 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
65341
65501
  return failure("storage_error", "current Git commit could not be resolved");
65342
65502
  const dirty = Boolean((gitRaw(root, ["status", "--short"]) ?? "").trim());
65343
65503
  const creation = action === "reapply_stash" || !target_branch ? { base_branch: policy.defaultTargetBranch } : branchCreationBaseline(root, target_branch, policy.defaultTargetBranch);
65504
+ let stashTarget;
65505
+ if (action === "reapply_stash") {
65506
+ try {
65507
+ const manifest = JSON.parse(fs14.readFileSync(path28.join(sdd_dir, "manifest.json"), "utf8"));
65508
+ const stash_ref = typeof manifest.stash_ref === "string" ? manifest.stash_ref : "";
65509
+ const stash_commit = stash_ref ? gitValue(root, ["rev-parse", stash_ref]) : null;
65510
+ if (stash_ref && stash_commit)
65511
+ stashTarget = { stash_ref, stash_commit };
65512
+ } catch {}
65513
+ }
65344
65514
  const normalized = {
65345
65515
  operation: request.operation,
65346
65516
  payload: {
@@ -65355,7 +65525,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
65355
65525
  request: normalized,
65356
65526
  descriptorPayload: {
65357
65527
  ...normalized.payload,
65358
- resolved: { head, dirty, ...creation }
65528
+ resolved: { head, dirty, ...creation, ...stashTarget }
65359
65529
  }
65360
65530
  });
65361
65531
  }
@@ -65507,7 +65677,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
65507
65677
  outcome: "not_started",
65508
65678
  fields: [{ path: "source_branch", reason: "protected branch" }]
65509
65679
  });
65510
- const source_commit = gitValue(root, ["rev-parse", "HEAD"]);
65680
+ const source_commit = source_branch ? gitValue(root, ["rev-parse", "--verify", `refs/heads/${source_branch}`]) : null;
65511
65681
  const remote = pushRemote(root);
65512
65682
  const identity = remote ? credentialFreeRemote(remote) : null;
65513
65683
  if (!source_branch || !source_commit || !identity || remoteProvider2(identity) !== null && remoteProvider2(identity) !== String(vcsConfig("load", root).provider).toLowerCase())
@@ -65795,7 +65965,17 @@ var executeResolvedExternalAction = async (resolved, root, step, caller) => {
65795
65965
  const workText = fresh.data.descriptorPayload.resolved?.workText;
65796
65966
  const beforeDigest = typeof resolvedDetails.resolved?.beforeDigest === "string" ? resolvedDetails.resolved.beforeDigest : undefined;
65797
65967
  const beforeExists = typeof resolvedDetails.resolved?.beforeExists === "boolean" ? resolvedDetails.resolved.beforeExists : undefined;
65798
- return executeConcreteExternalAction(fresh.data.request, root, fresh.data.marker, resolvedDate, step, typeof workText === "string" ? workText : undefined, caller, beforeDigest, beforeExists);
65968
+ const result = await executeConcreteExternalAction(fresh.data.request, root, fresh.data.marker, resolvedDate, step, typeof workText === "string" ? workText : undefined, caller, beforeDigest, beforeExists);
65969
+ if (!result.ok && result.code === "external_outcome_unknown" && caller) {
65970
+ const observed = await readLocalAction(root, fresh.data, {
65971
+ kind: "host",
65972
+ host: caller.host,
65973
+ handle: `local-recovery:${fresh.data.request.operation}`
65974
+ });
65975
+ if (observed.ok)
65976
+ return success(null, null, { recovered: true });
65977
+ }
65978
+ return result;
65799
65979
  };
65800
65980
 
65801
65981
  // packages/workit-cli/src/task.ts
@@ -66355,7 +66535,7 @@ async function runActionCommand(argv, deps = {}) {
66355
66535
  const task = tasks[0];
66356
66536
  const prior = externalActionState(store, "workit_cli", actor, descriptor);
66357
66537
  const priorRequest = priorExternalAction(store, "workit_cli", actor, normalized.operation, normalized.payload);
66358
- if (priorRequest.ok && priorRequest.data.entry.data.consumption !== null && priorRequest.data.entry.data.consumption.state !== "uncertain") {
66538
+ if (priorRequest.ok && priorRequest.data.entry.data.consumption !== null && priorRequest.data.entry.data.consumption.state !== "uncertain" && normalized.operation !== "git.commit" && normalized.operation !== "git.push") {
66359
66539
  const result = failure("permission_denied", "external action was already settled");
66360
66540
  if (json)
66361
66541
  jsonResult(outOf(deps), result);
@@ -66477,6 +66657,16 @@ async function runActionCommand(argv, deps = {}) {
66477
66657
  nativeAuthority: cliActionAuthority(actor),
66478
66658
  workerId: null
66479
66659
  });
66660
+ const planCount = planReservationLength(normalized.operation, resolved.data.descriptorPayload);
66661
+ if (planCount !== null) {
66662
+ const bound = standingAutoBinding(core, store, "workit_cli", actor, descriptor);
66663
+ const result = bound ? success(null, null, { plan_commits: planCount }) : failure("storage_error", "standing plan approval could not be recorded");
66664
+ if (json)
66665
+ jsonResult(outOf(deps), result);
66666
+ else
66667
+ printHuman(result, deps);
66668
+ return result.ok ? 0 : 1;
66669
+ }
66480
66670
  const runner = nativeExternalActionRunner(root, actor, core);
66481
66671
  const result = await runner(descriptor, (step) => executeResolvedExternalAction(resolved.data, root, step, { host: "workit_cli", actor }));
66482
66672
  if (json)
@@ -66531,9 +66721,9 @@ async function runActionCommand(argv, deps = {}) {
66531
66721
  printHuman(decision, deps);
66532
66722
  return 1;
66533
66723
  }
66534
- const planSteps = resolved.data.descriptorPayload.plan_steps;
66535
- if (normalized.operation === "git.commit" && Array.isArray(planSteps)) {
66536
- const result = success(null, null, { plan_commits: planSteps.length });
66724
+ const planCount = planReservationLength(normalized.operation, resolved.data.descriptorPayload);
66725
+ if (planCount !== null) {
66726
+ const result = success(null, null, { plan_commits: planCount });
66537
66727
  if (json)
66538
66728
  jsonResult(outOf(deps), result);
66539
66729
  else
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cli",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
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.1.1",
41
- "@brainervirus/workit-cursor": "^1.1.1",
42
- "@brainervirus/workit-opencode": "^1.1.1",
40
+ "@brainervirus/workit-core": "^1.2.1",
41
+ "@brainervirus/workit-cursor": "^1.2.1",
42
+ "@brainervirus/workit-opencode": "^1.2.1",
43
43
  "@inkjs/ui": "2.0.0",
44
44
  "ink": "7.1.1",
45
45
  "react": "19.2.8"