@brainervirus/workit-cli 1.2.0 → 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 +248 -74
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -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(),
@@ -60373,7 +60404,7 @@ var planReservationLength = (operation, descriptorPayload) => {
60373
60404
  const payload = descriptorPayload ?? {};
60374
60405
  return Array.isArray(payload.plan_steps) ? payload.plan_steps.length : null;
60375
60406
  };
60376
- 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).";
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).";
60377
60408
  var externalActionRef = (host, actor, descriptor) => ({
60378
60409
  kind: "host",
60379
60410
  host,
@@ -60387,12 +60418,12 @@ var externalActionState = (store, host, actor, operation) => {
60387
60418
  if (!workspace.data)
60388
60419
  return failure("not_found", "workspace not found");
60389
60420
  const candidates = listed.data.flatMap((task) => {
60390
- 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)
60391
60422
  return [];
60392
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 }));
60393
60424
  });
60394
60425
  if (candidates.length !== 1)
60395
- 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");
60396
60427
  return success(workspace.data.revision, null, { ...candidates[0], workspace: workspace.data });
60397
60428
  };
60398
60429
  var priorExternalAction = (store, host, actor, operation, payload) => {
@@ -60472,8 +60503,6 @@ var approvedPlanCommit = (store, host, actor, message) => {
60472
60503
  const matches = listed.data.flatMap((task) => {
60473
60504
  if (task.status !== "active" || task.workspaceId !== workspace.data.id)
60474
60505
  return [];
60475
- if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
60476
- return [];
60477
60506
  return task.decisions.flatMap((entry) => {
60478
60507
  const decision = entry.data;
60479
60508
  if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
@@ -60515,8 +60544,6 @@ var approvedChainStep = (store, host, actor, query) => {
60515
60544
  const matches = listed.data.flatMap((task) => {
60516
60545
  if (task.status !== "active" || task.workspaceId !== workspace.data.id)
60517
60546
  return [];
60518
- if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
60519
- return [];
60520
60547
  return task.decisions.flatMap((entry) => {
60521
60548
  const decision = entry.data;
60522
60549
  if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
@@ -60673,7 +60700,7 @@ var branchSetupIntent = (descriptor) => {
60673
60700
  var approvedBranchSetupIntent = (store, host, actor, operation) => {
60674
60701
  const current = branchSetupIntent(operation);
60675
60702
  if (!current)
60676
- 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");
60677
60704
  const listed = store.listTasks();
60678
60705
  const workspace = store.readWorkspace();
60679
60706
  if (!listed.ok || !workspace.ok)
@@ -60681,7 +60708,7 @@ var approvedBranchSetupIntent = (store, host, actor, operation) => {
60681
60708
  if (!workspace.data)
60682
60709
  return failure("not_found", "workspace not found");
60683
60710
  const candidates = listed.data.flatMap((task) => {
60684
- 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)
60685
60712
  return [];
60686
60713
  return task.decisions.filter((entry) => entry.data.purpose === "action" && entry.data.response === "approved" && entry.data.revoked === null).map((entry) => ({ task, entry }));
60687
60714
  });
@@ -60690,7 +60717,7 @@ var approvedBranchSetupIntent = (store, host, actor, operation) => {
60690
60717
  return intent !== null && intent.target === current.target && intent.base === current.base && intent.remoteBase === current.remoteBase && intent.targetExists === current.targetExists;
60691
60718
  });
60692
60719
  if (matches.length !== 1)
60693
- 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");
60694
60721
  return success(workspace.data.revision, null, { ...matches[0], workspace: workspace.data });
60695
60722
  };
60696
60723
  var priorResolvedDrift = (store, host, actor, operation, payload, currentResolved) => {
@@ -61228,6 +61255,7 @@ var verifyNativeWorker = (verifier, input, binding) => {
61228
61255
  expectedWorkspaceRevision: expected.expectedWorkspaceRevision,
61229
61256
  state: expected.state,
61230
61257
  session: expected.session,
61258
+ report: expected.report ?? null,
61231
61259
  provenance: result.data,
61232
61260
  ...binding
61233
61261
  });
@@ -61238,7 +61266,7 @@ var takeWorker = (token, binding, expected) => {
61238
61266
  verifiedWorkers.delete(token);
61239
61267
  if (!value)
61240
61268
  return null;
61241
- 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))
61242
61270
  return null;
61243
61271
  return value;
61244
61272
  };
@@ -61279,6 +61307,8 @@ var applyWorkerLifecycle = (input) => {
61279
61307
  return failure("invalid_transition", "worker already has an observed session");
61280
61308
  if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
61281
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");
61282
61312
  if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
61283
61313
  return failure("invalid_input", "running worker observations require a session");
61284
61314
  if (entry.data.session && !sameValue(entry.data.session, input.session))
@@ -61295,12 +61325,13 @@ var applyWorkerLifecycle = (input) => {
61295
61325
  data: {
61296
61326
  ...entry.data,
61297
61327
  state: terminalCancel ? "cancelling" : input.state,
61298
- session: input.session
61328
+ session: input.session,
61329
+ report: entry.data.report ?? input.report ?? null
61299
61330
  }
61300
61331
  };
61301
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;
61302
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;
61303
- 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)
61304
61335
  return success(task.data.revision, workspace.data.revision, entry);
61305
61336
  const changed = input.store.mutateTaskAndWorkspace({
61306
61337
  taskId: task.data.id,
@@ -61718,14 +61749,30 @@ class WorkitCore {
61718
61749
  const listed = this.store.listTasks();
61719
61750
  if (!listed.ok)
61720
61751
  return listed;
61721
- const summaries = [];
61722
- for (const task of listed.data) {
61723
- const summary = this.summary(task);
61724
- if (!summary.ok)
61725
- return summary;
61726
- summaries.push(summary.data);
61727
- }
61728
- 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);
61729
61776
  }
61730
61777
  case "inspect": {
61731
61778
  const task = this.store.readTask(input.taskId);
@@ -61734,7 +61781,7 @@ class WorkitCore {
61734
61781
  const helper = this.helperTaskGuard(task.data, true);
61735
61782
  if (!helper.ok)
61736
61783
  return helper;
61737
- if (input.view === "summary")
61784
+ if ((input.view ?? "summary") === "summary")
61738
61785
  return this.summary(task.data);
61739
61786
  return this.view(task.data);
61740
61787
  }
@@ -62078,7 +62125,7 @@ class WorkitCore {
62078
62125
  const evaluations = evaluateEvidence(task.data, current.data);
62079
62126
  const verified = evidence.some((entry) => {
62080
62127
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
62081
- return findingVerificationPasses(finding.data.candidateId, {
62128
+ return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
62082
62129
  kind: entry.data.kind,
62083
62130
  candidateId: entry.data.candidateId,
62084
62131
  status: evaluation?.status ?? "missing"
@@ -62158,7 +62205,18 @@ class WorkitCore {
62158
62205
  if (task.data.status !== "active")
62159
62206
  return failure("invalid_transition", "paused or closed tasks cannot assign workers");
62160
62207
  if (!bindingCovers(task.data.intent.data.scope, input.assignment.scope))
62161
- 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
+ });
62162
62220
  if (input.assignment.decisionIds.some((id) => !task.data.decisions.some((entry) => entry.id === id)))
62163
62221
  return failure("invalid_input", "worker assignment references an unknown decision");
62164
62222
  if (input.assignment.requirementIds.some((id) => !task.data.policy?.requirements.some((requirement) => requirement.id === id)))
@@ -62365,7 +62423,15 @@ class WorkitCore {
62365
62423
  ...item,
62366
62424
  recordedAt: mutation.now,
62367
62425
  provenance: attested.data,
62368
- 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
+ }
62369
62435
  } : item)
62370
62436
  });
62371
62437
  }
@@ -62441,6 +62507,7 @@ class WorkitCore {
62441
62507
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
62442
62508
  state: "stopped",
62443
62509
  session: null,
62510
+ report: null,
62444
62511
  provenance: attested.data,
62445
62512
  owner: this.authorityOwner,
62446
62513
  store: this.store,
@@ -62748,10 +62815,12 @@ class WorkitCore {
62748
62815
  return workspace;
62749
62816
  if (!workspace.data)
62750
62817
  return failure("not_found", "workspace not found");
62751
- 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());
62752
62820
  if (!current.ok)
62753
62821
  return current;
62754
- 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);
62755
62824
  return success(task.revision, workspace.data.revision, {
62756
62825
  id: task.id,
62757
62826
  revision: task.revision,
@@ -62765,7 +62834,7 @@ class WorkitCore {
62765
62834
  progress: task.progress,
62766
62835
  policy: task.policy,
62767
62836
  requirements,
62768
- writer: workspace.data.writer
62837
+ writer: evaluationWorkspace.writer
62769
62838
  });
62770
62839
  }
62771
62840
  view(task) {
@@ -62774,13 +62843,15 @@ class WorkitCore {
62774
62843
  return workspace;
62775
62844
  if (!workspace.data)
62776
62845
  return failure("not_found", "workspace not found");
62777
- 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());
62778
62848
  if (!current.ok)
62779
62849
  return current;
62780
- 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);
62781
62852
  return success(task.revision, workspace.data.revision, {
62782
62853
  task,
62783
- workspace: workspace.data,
62854
+ workspace: evaluationWorkspace,
62784
62855
  capabilities: this.context.capabilities,
62785
62856
  requirements,
62786
62857
  evidence: evaluateEvidence(task, current.data)
@@ -62871,8 +62942,6 @@ class WorkitCore {
62871
62942
  return helper;
62872
62943
  if (status === "paused" && task.data.status !== "active" || status === "active" && task.data.status !== "paused")
62873
62944
  return failure("invalid_transition", `cannot transition ${task.data.status} to ${status}`);
62874
- if (status === "active" && input.authorityRefs.length === 0)
62875
- return failure("permission_denied", "resuming a task requires authority references");
62876
62945
  const workspace = this.store.readWorkspace();
62877
62946
  if (!workspace.ok)
62878
62947
  return workspace;
@@ -62902,7 +62971,7 @@ class WorkitCore {
62902
62971
  return failure("needs_input", "imported task requires fresh destination evidence");
62903
62972
  if (current.data.blockers.some((blocker) => blocker.dependentAction === "resume" || blocker.reason === "worker state requires reconciliation"))
62904
62973
  return failure("recovery_required", "imported task requires resume reconciliation");
62905
- if (!validResumeApproval(task.data, workspace.data, input.authorityRefs, this.context))
62974
+ if (!validResumeApproval(task.data, workspace.data, input.authorityRefs ?? [], this.context))
62906
62975
  return failure("permission_denied", "imported resume requires native destination approval");
62907
62976
  resumeCandidate = current.data.candidate;
62908
62977
  }
@@ -63032,7 +63101,7 @@ class WorkitCore {
63032
63101
  task: (value, context) => success(context.revision, null, {
63033
63102
  ...value,
63034
63103
  status: "closed",
63035
- candidates: value.candidates.some((item) => item.id === withCandidate.id) ? value.candidates : [...value.candidates, withCandidate],
63104
+ candidates: taskForView.candidates,
63036
63105
  closure: {
63037
63106
  outcome: closure.data.outcome,
63038
63107
  at: context.now,
@@ -64911,7 +64980,7 @@ var readExternalContext = async (root, payload) => {
64911
64980
  }
64912
64981
  }
64913
64982
  };
64914
- 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";
64915
64984
  var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
64916
64985
  const store = new TaskStore(root);
64917
64986
  const listed = store.listTasks();
@@ -64922,7 +64991,7 @@ var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
64922
64991
  return workspace;
64923
64992
  if (!workspace.data)
64924
64993
  return failure("not_found", "workspace not found");
64925
- 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));
64926
64995
  if (candidates.length !== 1)
64927
64996
  return failure("permission_denied", "local external action requires exactly one active task");
64928
64997
  const allowed = assertProductWriteAllowed({
@@ -64932,7 +65001,7 @@ var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
64932
65001
  paths,
64933
65002
  store
64934
65003
  });
64935
- return allowed.ok ? success(null, null, null) : allowed;
65004
+ return allowed;
64936
65005
  };
64937
65006
  var safePath = (root, value) => {
64938
65007
  try {
@@ -64944,13 +65013,20 @@ var safePath = (root, value) => {
64944
65013
  var branchCreationBaseline = (root, target, base) => {
64945
65014
  const exists = gitValue(root, ["rev-parse", "--verify", "--quiet", `refs/heads/${target}`]) !== null;
64946
65015
  if (exists)
64947
- 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
+ };
64948
65022
  const remote = run3(root, ["ls-remote", "origin", `refs/heads/${base}`]);
64949
65023
  const sha = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : "";
65024
+ const remoteBase = /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null;
64950
65025
  return {
64951
65026
  base_branch: base,
64952
65027
  target_exists: false,
64953
- remote_base: /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null
65028
+ remote_base: remoteBase,
65029
+ target_head: remoteBase
64954
65030
  };
64955
65031
  };
64956
65032
  var shortSha = (value) => typeof value === "string" && value ? value.slice(0, 8) : "unknown";
@@ -65061,7 +65137,7 @@ var actionProposalQuestion = (request, descriptorPayload) => {
65061
65137
  var approvedResolvedExternalAction = (content) => {
65062
65138
  try {
65063
65139
  const value = JSON.parse(content);
65064
- 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")
65065
65141
  return failure("invalid_input", "approved action is not a reconciliable external action");
65066
65142
  if (!value.payload || typeof value.payload !== "object")
65067
65143
  return failure("invalid_input", "approved action payload is incomplete");
@@ -65072,18 +65148,24 @@ var approvedResolvedExternalAction = (content) => {
65072
65148
  if (!request.ok || !resolved || typeof resolved !== "object")
65073
65149
  return failure("invalid_input", "approved hosting target is incomplete");
65074
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");
65075
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"))
65076
65158
  return failure("invalid_input", "approved hosting target is incomplete");
65077
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"))
65078
65160
  return failure("invalid_input", "approved hosting target is incomplete");
65079
65161
  if (value.operation === "changelog.apply" && (typeof target.target !== "string" || typeof target.beforeDigest !== "string" || typeof target.beforeExists !== "boolean" || typeof target.afterDigest !== "string"))
65080
65162
  return failure("invalid_input", "approved changelog target is incomplete");
65081
- 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)))
65082
65164
  return failure("invalid_input", "approved YouTrack target is incomplete");
65083
65165
  return success(null, null, {
65084
65166
  request: request.data,
65085
65167
  descriptorPayload: value.payload,
65086
- marker: String(target.marker)
65168
+ ...typeof target.marker === "string" ? { marker: target.marker } : {}
65087
65169
  });
65088
65170
  } catch {
65089
65171
  return failure("invalid_input", "approved action descriptor is invalid");
@@ -65109,12 +65191,13 @@ var unknownHostingEvidence = (operation) => failure("external_outcome_unknown",
65109
65191
  outcome: "unknown"
65110
65192
  });
65111
65193
  var readHostingAction = async (root, resolved, actionRef) => {
65112
- if (resolved.request.operation !== "hosting.pull_request" || !resolved.marker)
65113
- 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);
65114
65197
  try {
65115
65198
  const cfg = vcsConfig("load", root);
65116
65199
  if (!cfg.ok || !cfg.tokenPath || !cfg.provider || cfg.provider !== "github" && cfg.provider !== "gitlab")
65117
- return unknownHostingEvidence("hosting.pull_request");
65200
+ return unknownHostingEvidence(operation);
65118
65201
  const remote = pushRemote(root);
65119
65202
  const repo = remote ? remoteRepo(remote) : null;
65120
65203
  const approved = resolved.descriptorPayload;
@@ -65133,7 +65216,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
65133
65216
  }
65134
65217
  })();
65135
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)
65136
- return unknownHostingEvidence("hosting.pull_request");
65219
+ return unknownHostingEvidence(operation);
65137
65220
  const token = fs14.readFileSync(cfg.tokenPath, "utf8").trim();
65138
65221
  const headers = { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json" };
65139
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`;
@@ -65141,14 +65224,14 @@ var readHostingAction = async (root, resolved, actionRef) => {
65141
65224
  headers: cfg.provider === "gitlab" ? { "PRIVATE-TOKEN": token } : headers
65142
65225
  });
65143
65226
  if (!response.ok)
65144
- return unknownHostingEvidence("hosting.pull_request");
65227
+ return unknownHostingEvidence(operation);
65145
65228
  const values = await response.json();
65146
65229
  if (!Array.isArray(values) || values.length >= 100)
65147
- return unknownHostingEvidence("hosting.pull_request");
65230
+ return unknownHostingEvidence(operation);
65148
65231
  const records = [];
65149
65232
  for (const item of values) {
65150
65233
  if (!item || typeof item !== "object" || Array.isArray(item))
65151
- return unknownHostingEvidence("hosting.pull_request");
65234
+ return unknownHostingEvidence(operation);
65152
65235
  const record = item;
65153
65236
  const body = record.body ?? record.description;
65154
65237
  const base = record.base;
@@ -65160,7 +65243,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
65160
65243
  const shaValue = cfg.provider === "github" && githubShape ? head.sha : record.sha;
65161
65244
  const idValue = record.number ?? record.iid ?? record.id;
65162
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")
65163
- return unknownHostingEvidence("hosting.pull_request");
65246
+ return unknownHostingEvidence(operation);
65164
65247
  records.push(record);
65165
65248
  }
65166
65249
  const matches = records.filter((item) => {
@@ -65170,15 +65253,23 @@ var readHostingAction = async (root, resolved, actionRef) => {
65170
65253
  const itemTarget = cfg.provider === "github" ? base?.ref : item.target_branch;
65171
65254
  const itemSource = cfg.provider === "github" ? head?.ref : item.source_branch;
65172
65255
  const itemSha = cfg.provider === "github" ? head?.sha : item.sha;
65173
- 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;
65174
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);
65175
65265
  if (matches.length !== 1)
65176
- return unknownHostingEvidence("hosting.pull_request");
65266
+ return unknownHostingEvidence(operation);
65177
65267
  const item = matches[0];
65178
65268
  const id = item.number ?? item.iid ?? item.id;
65179
65269
  if (typeof id !== "string" && typeof id !== "number")
65180
- return unknownHostingEvidence("hosting.pull_request");
65270
+ return unknownHostingEvidence(operation);
65181
65271
  const evidenceDigest = sha256({
65272
+ operation,
65182
65273
  provider: cfg.provider,
65183
65274
  id,
65184
65275
  marker: resolved.marker,
@@ -65199,7 +65290,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
65199
65290
  data: { provider: cfg.provider, id }
65200
65291
  });
65201
65292
  } catch {
65202
- return unknownHostingEvidence("hosting.pull_request");
65293
+ return unknownHostingEvidence(operation);
65203
65294
  }
65204
65295
  };
65205
65296
  var appendMarker = (text, marker) => `${text}${text ? `
@@ -65289,7 +65380,70 @@ var readYouTrackAction = async (root, resolved, actionRef) => {
65289
65380
  return unknownHostingEvidence(operation);
65290
65381
  }
65291
65382
  };
65292
- 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);
65293
65447
  var youTrackBase = () => {
65294
65448
  try {
65295
65449
  const config = JSON.parse(fs14.readFileSync(youTrackConfigPath(), "utf8"));
@@ -65347,6 +65501,16 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
65347
65501
  return failure("storage_error", "current Git commit could not be resolved");
65348
65502
  const dirty = Boolean((gitRaw(root, ["status", "--short"]) ?? "").trim());
65349
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
+ }
65350
65514
  const normalized = {
65351
65515
  operation: request.operation,
65352
65516
  payload: {
@@ -65361,7 +65525,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
65361
65525
  request: normalized,
65362
65526
  descriptorPayload: {
65363
65527
  ...normalized.payload,
65364
- resolved: { head, dirty, ...creation }
65528
+ resolved: { head, dirty, ...creation, ...stashTarget }
65365
65529
  }
65366
65530
  });
65367
65531
  }
@@ -65513,7 +65677,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
65513
65677
  outcome: "not_started",
65514
65678
  fields: [{ path: "source_branch", reason: "protected branch" }]
65515
65679
  });
65516
- const source_commit = gitValue(root, ["rev-parse", "HEAD"]);
65680
+ const source_commit = source_branch ? gitValue(root, ["rev-parse", "--verify", `refs/heads/${source_branch}`]) : null;
65517
65681
  const remote = pushRemote(root);
65518
65682
  const identity = remote ? credentialFreeRemote(remote) : null;
65519
65683
  if (!source_branch || !source_commit || !identity || remoteProvider2(identity) !== null && remoteProvider2(identity) !== String(vcsConfig("load", root).provider).toLowerCase())
@@ -65801,7 +65965,17 @@ var executeResolvedExternalAction = async (resolved, root, step, caller) => {
65801
65965
  const workText = fresh.data.descriptorPayload.resolved?.workText;
65802
65966
  const beforeDigest = typeof resolvedDetails.resolved?.beforeDigest === "string" ? resolvedDetails.resolved.beforeDigest : undefined;
65803
65967
  const beforeExists = typeof resolvedDetails.resolved?.beforeExists === "boolean" ? resolvedDetails.resolved.beforeExists : undefined;
65804
- 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;
65805
65979
  };
65806
65980
 
65807
65981
  // packages/workit-cli/src/task.ts
@@ -66361,7 +66535,7 @@ async function runActionCommand(argv, deps = {}) {
66361
66535
  const task = tasks[0];
66362
66536
  const prior = externalActionState(store, "workit_cli", actor, descriptor);
66363
66537
  const priorRequest = priorExternalAction(store, "workit_cli", actor, normalized.operation, normalized.payload);
66364
- 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") {
66365
66539
  const result = failure("permission_denied", "external action was already settled");
66366
66540
  if (json)
66367
66541
  jsonResult(outOf(deps), result);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cli",
3
- "version": "1.2.0",
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.2.0",
41
- "@brainervirus/workit-cursor": "^1.2.0",
42
- "@brainervirus/workit-opencode": "^1.2.0",
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"