@brainervirus/workit-opencode 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 (3) hide show
  1. package/README.md +10 -2
  2. package/dist/plugin.js +1869 -488
  3. package/package.json +4 -3
package/dist/plugin.js CHANGED
@@ -13,10 +13,9 @@ var __export = (target, all) => {
13
13
  });
14
14
  };
15
15
 
16
- // packages/workit-opencode/src/plugin.ts
17
- import { existsSync as existsSync13 } from "node:fs";
18
- import path25 from "node:path";
19
- import { fileURLToPath as fileURLToPath3 } from "node:url";
16
+ // packages/workit-opencode/src/v1/server.ts
17
+ import { existsSync as existsSync14 } from "node:fs";
18
+ import path27 from "node:path";
20
19
 
21
20
  // packages/workit-core/src/core.ts
22
21
  import { spawnSync as spawnSync3 } from "node:child_process";
@@ -5970,10 +5969,35 @@ var numberProcessor = (schema, ctx, _json, params) => {
5970
5969
  var booleanProcessor = (_schema, _ctx, json, _params) => {
5971
5970
  json.type = "boolean";
5972
5971
  };
5972
+ var bigintProcessor = (schema, ctx, json, params) => {
5973
+ handleUnrepresentable(schema, ctx, json, params, "BigInt cannot be represented in JSON Schema");
5974
+ };
5975
+ var symbolProcessor = (schema, ctx, json, params) => {
5976
+ handleUnrepresentable(schema, ctx, json, params, "Symbols cannot be represented in JSON Schema");
5977
+ };
5978
+ var nullProcessor = (_schema, ctx, json, _params) => {
5979
+ if (ctx.target === "openapi-3.0") {
5980
+ json.type = "string";
5981
+ json.nullable = true;
5982
+ json.enum = [null];
5983
+ } else {
5984
+ json.type = "null";
5985
+ }
5986
+ };
5987
+ var undefinedProcessor = (schema, ctx, json, params) => {
5988
+ handleUnrepresentable(schema, ctx, json, params, "Undefined cannot be represented in JSON Schema");
5989
+ };
5990
+ var voidProcessor = (schema, ctx, json, params) => {
5991
+ handleUnrepresentable(schema, ctx, json, params, "Void cannot be represented in JSON Schema");
5992
+ };
5973
5993
  var neverProcessor = (_schema, _ctx, json, _params) => {
5974
5994
  json.not = {};
5975
5995
  };
5996
+ var anyProcessor = (_schema, _ctx, _json, _params) => {};
5976
5997
  var unknownProcessor = (_schema, _ctx, _json, _params) => {};
5998
+ var dateProcessor = (schema, ctx, json, params) => {
5999
+ handleUnrepresentable(schema, ctx, json, params, "Date cannot be represented in JSON Schema");
6000
+ };
5977
6001
  var enumProcessor = (schema, _ctx, json, _params) => {
5978
6002
  const def = schema._zod.def;
5979
6003
  const values = getEnumValues(def.entries);
@@ -6026,12 +6050,59 @@ var literalProcessor = (schema, ctx, json, params) => {
6026
6050
  json.enum = vals;
6027
6051
  }
6028
6052
  };
6053
+ var nanProcessor = (schema, ctx, json, params) => {
6054
+ handleUnrepresentable(schema, ctx, json, params, "NaN cannot be represented in JSON Schema");
6055
+ };
6056
+ var templateLiteralProcessor = (schema, _ctx, json, _params) => {
6057
+ const _json = json;
6058
+ const pattern = schema._zod.pattern;
6059
+ if (!pattern)
6060
+ throw new Error("Pattern not found in template literal");
6061
+ _json.type = "string";
6062
+ _json.pattern = pattern.source;
6063
+ };
6064
+ var fileProcessor = (schema, _ctx, json, _params) => {
6065
+ const _json = json;
6066
+ const file = {
6067
+ type: "string",
6068
+ format: "binary",
6069
+ contentEncoding: "binary"
6070
+ };
6071
+ const { minimum, maximum, mime } = schema._zod.bag;
6072
+ if (minimum !== undefined)
6073
+ file.minLength = minimum;
6074
+ if (maximum !== undefined)
6075
+ file.maxLength = maximum;
6076
+ if (mime) {
6077
+ if (mime.length === 1) {
6078
+ file.contentMediaType = mime[0];
6079
+ Object.assign(_json, file);
6080
+ } else {
6081
+ Object.assign(_json, file);
6082
+ _json.anyOf = mime.map((m) => ({ contentMediaType: m }));
6083
+ }
6084
+ } else {
6085
+ Object.assign(_json, file);
6086
+ }
6087
+ };
6088
+ var successProcessor = (_schema, _ctx, json, _params) => {
6089
+ json.type = "boolean";
6090
+ };
6029
6091
  var customProcessor = (schema, ctx, json, params) => {
6030
6092
  handleUnrepresentable(schema, ctx, json, params, "Custom types cannot be represented in JSON Schema");
6031
6093
  };
6094
+ var functionProcessor = (schema, ctx, json, params) => {
6095
+ handleUnrepresentable(schema, ctx, json, params, "Function types cannot be represented in JSON Schema");
6096
+ };
6032
6097
  var transformProcessor = (schema, ctx, json, params) => {
6033
6098
  handleUnrepresentable(schema, ctx, json, params, "Transforms cannot be represented in JSON Schema");
6034
6099
  };
6100
+ var mapProcessor = (schema, ctx, json, params) => {
6101
+ handleUnrepresentable(schema, ctx, json, params, "Map cannot be represented in JSON Schema");
6102
+ };
6103
+ var setProcessor = (schema, ctx, json, params) => {
6104
+ handleUnrepresentable(schema, ctx, json, params, "Set cannot be represented in JSON Schema");
6105
+ };
6035
6106
  var arrayProcessor = (schema, ctx, _json, params) => {
6036
6107
  const json = _json;
6037
6108
  const def = schema._zod.def;
@@ -6127,6 +6198,70 @@ var intersectionProcessor = (schema, ctx, json, params) => {
6127
6198
  json.allOf = allOf;
6128
6199
  ctx.intersections.push(allOf);
6129
6200
  };
6201
+ var tupleProcessor = (schema, ctx, _json, params) => {
6202
+ const json = _json;
6203
+ const def = schema._zod.def;
6204
+ json.type = "array";
6205
+ const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
6206
+ const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
6207
+ const prefixItems = def.items.map((x, i) => process2(x, ctx, {
6208
+ ...params,
6209
+ path: [...params.path, prefixPath, i]
6210
+ }));
6211
+ const rest = def.rest ? process2(def.rest, ctx, {
6212
+ ...params,
6213
+ path: [...params.path, restPath, ...ctx.target === "openapi-3.0" ? [def.items.length] : []]
6214
+ }) : null;
6215
+ let minItems = def.items.length;
6216
+ while (minItems > 0) {
6217
+ const item = def.items[minItems - 1];
6218
+ const optional = ctx.io === "input" ? inputOptin(item) !== undefined : item._zod.optout === "optional";
6219
+ if (!optional)
6220
+ break;
6221
+ minItems--;
6222
+ }
6223
+ const maxItems = def.items.length;
6224
+ const isClosed = !def.rest;
6225
+ if (ctx.target === "draft-2020-12") {
6226
+ json.prefixItems = prefixItems;
6227
+ if (isClosed) {
6228
+ json.items = false;
6229
+ } else if (rest) {
6230
+ json.items = rest;
6231
+ }
6232
+ if (minItems > 0)
6233
+ json.minItems = minItems;
6234
+ if (isClosed)
6235
+ json.maxItems = maxItems;
6236
+ } else if (ctx.target === "openapi-3.0") {
6237
+ json.items = {
6238
+ anyOf: prefixItems
6239
+ };
6240
+ if (rest) {
6241
+ json.items.anyOf.push(rest);
6242
+ }
6243
+ if (minItems > 0)
6244
+ json.minItems = minItems;
6245
+ if (isClosed)
6246
+ json.maxItems = maxItems;
6247
+ } else {
6248
+ json.items = prefixItems;
6249
+ if (isClosed) {
6250
+ json.additionalItems = false;
6251
+ } else if (rest) {
6252
+ json.additionalItems = rest;
6253
+ }
6254
+ if (minItems > 0)
6255
+ json.minItems = minItems;
6256
+ if (isClosed)
6257
+ json.maxItems = maxItems;
6258
+ }
6259
+ const { minimum, maximum } = schema._zod.bag;
6260
+ if (typeof minimum === "number")
6261
+ json.minItems = minimum;
6262
+ if (typeof maximum === "number")
6263
+ json.maxItems = maximum;
6264
+ };
6130
6265
  function stringifyKeyNames(bySchema, json, visited) {
6131
6266
  if (json.$ref) {
6132
6267
  if (visited.has(json))
@@ -6314,12 +6449,99 @@ var readonlyProcessor = (schema, ctx, json, params) => {
6314
6449
  seen.ref = def.innerType;
6315
6450
  json.readOnly = true;
6316
6451
  };
6452
+ var promiseProcessor = (schema, ctx, _json, params) => {
6453
+ const def = schema._zod.def;
6454
+ process2(def.innerType, ctx, params);
6455
+ const seen = ctx.seen.get(schema);
6456
+ seen.ref = def.innerType;
6457
+ };
6317
6458
  var optionalProcessor = (schema, ctx, _json, params) => {
6318
6459
  const def = schema._zod.def;
6319
6460
  process2(def.innerType, ctx, params);
6320
6461
  const seen = ctx.seen.get(schema);
6321
6462
  seen.ref = def.innerType;
6322
6463
  };
6464
+ var lazyProcessor = (schema, ctx, _json, params) => {
6465
+ const innerType = schema._zod.innerType;
6466
+ process2(innerType, ctx, params);
6467
+ const seen = ctx.seen.get(schema);
6468
+ seen.ref = innerType;
6469
+ };
6470
+ var allProcessors = {
6471
+ string: stringProcessor,
6472
+ number: numberProcessor,
6473
+ boolean: booleanProcessor,
6474
+ bigint: bigintProcessor,
6475
+ symbol: symbolProcessor,
6476
+ null: nullProcessor,
6477
+ undefined: undefinedProcessor,
6478
+ void: voidProcessor,
6479
+ never: neverProcessor,
6480
+ any: anyProcessor,
6481
+ unknown: unknownProcessor,
6482
+ date: dateProcessor,
6483
+ enum: enumProcessor,
6484
+ literal: literalProcessor,
6485
+ nan: nanProcessor,
6486
+ template_literal: templateLiteralProcessor,
6487
+ file: fileProcessor,
6488
+ success: successProcessor,
6489
+ custom: customProcessor,
6490
+ function: functionProcessor,
6491
+ transform: transformProcessor,
6492
+ map: mapProcessor,
6493
+ set: setProcessor,
6494
+ array: arrayProcessor,
6495
+ object: objectProcessor,
6496
+ union: unionProcessor,
6497
+ intersection: intersectionProcessor,
6498
+ tuple: tupleProcessor,
6499
+ record: recordProcessor,
6500
+ nullable: nullableProcessor,
6501
+ nonoptional: nonoptionalProcessor,
6502
+ default: defaultProcessor,
6503
+ prefault: prefaultProcessor,
6504
+ catch: catchProcessor,
6505
+ pipe: pipeProcessor,
6506
+ readonly: readonlyProcessor,
6507
+ promise: promiseProcessor,
6508
+ optional: optionalProcessor,
6509
+ lazy: lazyProcessor
6510
+ };
6511
+ function toJSONSchema(input, params) {
6512
+ if ("_idmap" in input) {
6513
+ const registry = input;
6514
+ const ctx = initializeContext({ ...params, processors: allProcessors });
6515
+ const defs = {};
6516
+ for (const entry of registry._idmap.entries()) {
6517
+ const [_, schema] = entry;
6518
+ process2(schema, ctx);
6519
+ }
6520
+ const schemas = {};
6521
+ const external = {
6522
+ registry,
6523
+ uri: params?.uri,
6524
+ defs
6525
+ };
6526
+ ctx.external = external;
6527
+ for (const entry of registry._idmap.entries()) {
6528
+ const [key, schema] = entry;
6529
+ extractDefs(ctx, schema);
6530
+ assignProp(schemas, key, finalize(ctx, schema));
6531
+ }
6532
+ if (Object.keys(defs).length > 0) {
6533
+ const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions";
6534
+ schemas.__shared = {
6535
+ [defsSegment]: defs
6536
+ };
6537
+ }
6538
+ return { schemas };
6539
+ }
6540
+ const ctx = initializeContext({ ...params, processors: allProcessors });
6541
+ process2(input, ctx);
6542
+ extractDefs(ctx, input);
6543
+ return finalize(ctx, input);
6544
+ }
6323
6545
  // node_modules/zod/v4/classic/errors.js
6324
6546
  var _installedErrorProtos = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);
6325
6547
  function _lazyMethod(proto, key, make) {
@@ -7743,6 +7965,7 @@ var workerSchema = object({
7743
7965
  assignment: assignmentSchema,
7744
7966
  state: _enum(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
7745
7967
  session: refSchema.nullable(),
7968
+ coordinator: refSchema.optional(),
7746
7969
  report: workerReportSchema.nullable()
7747
7970
  }).strict();
7748
7971
  var ownerSchema = object({
@@ -7854,6 +8077,7 @@ var taskSummarySchema = object({
7854
8077
  requirements: array(requirementEvaluationSchema),
7855
8078
  writer: workspaceRecordSchema.shape.writer
7856
8079
  }).strict();
8080
+ var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true });
7857
8081
  var exportBundleSchema = object({
7858
8082
  schemaVersion: literal(1),
7859
8083
  exportedAt: utc,
@@ -7875,11 +8099,15 @@ var taskOperations = {
7875
8099
  expectedWorkspaceRevision: revision.nullable().optional(),
7876
8100
  intent: intentSchema
7877
8101
  }),
7878
- list: operation({ action: literal("list") }),
8102
+ list: operation({
8103
+ action: literal("list"),
8104
+ status: _enum(["open", "closed", "all"]).optional(),
8105
+ limit: number2().int().min(1).max(50).optional()
8106
+ }),
7879
8107
  inspect: operation({
7880
8108
  action: literal("inspect"),
7881
8109
  ...taskId,
7882
- view: _enum(["summary", "full"])
8110
+ view: _enum(["summary", "full"]).optional()
7883
8111
  }),
7884
8112
  revise: operation({
7885
8113
  action: literal("revise"),
@@ -7907,7 +8135,7 @@ var taskOperations = {
7907
8135
  ...taskId,
7908
8136
  ...revisions,
7909
8137
  expectedWorkspaceRevision: revision.optional(),
7910
- authorityRefs: array(refSchema)
8138
+ authorityRefs: array(refSchema).optional()
7911
8139
  }),
7912
8140
  close: operation({
7913
8141
  action: literal("close"),
@@ -8070,8 +8298,62 @@ function parseOperation(family, input) {
8070
8298
  }));
8071
8299
  return failure("invalid_input", "operation input is invalid", { fields });
8072
8300
  }
8301
+ function operationJsonSchema(family) {
8302
+ return toJSONSchema(operationSchemas[family], { target: "draft-2020-12" });
8303
+ }
8073
8304
  var OPERATION_SCHEMA_DEPTH = 1;
8074
8305
  var canonicalFieldsDescription = (fields) => `Canonical object fields: ${fields.join(", ")}. Workit validates the complete nested value.`;
8306
+ function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, stringifiedObjects = false) {
8307
+ const tolerate = (projected) => {
8308
+ if (!stringifiedObjects)
8309
+ return projected;
8310
+ if (projected.properties && typeof projected.properties === "object")
8311
+ return { ...projected, type: ["object", "string"] };
8312
+ if (projected.items)
8313
+ return { ...projected, type: ["array", "string"] };
8314
+ if ("const" in projected && (typeof projected.const === "number" || typeof projected.const === "boolean"))
8315
+ return { anyOf: [projected, { const: String(projected.const) }] };
8316
+ if (projected.type === "object")
8317
+ return { ...projected, type: ["object", "string"] };
8318
+ if (projected.type === "array")
8319
+ return { ...projected, type: ["array", "string"] };
8320
+ return projected;
8321
+ };
8322
+ const collapse = (node, currentDepth) => {
8323
+ if (Array.isArray(node))
8324
+ return node.map((item) => collapse(item, currentDepth));
8325
+ if (typeof node !== "object" || node === null)
8326
+ return node;
8327
+ const record = node;
8328
+ if (record.properties && typeof record.properties === "object") {
8329
+ const fields = Object.keys(record.properties);
8330
+ if (currentDepth >= maxDepth || fields.length === 0) {
8331
+ const description = stringifiedObjects ? `${canonicalFieldsDescription(fields)} A JSON-encoded string is also accepted.` : canonicalFieldsDescription(fields);
8332
+ return tolerate({ type: "object", description });
8333
+ }
8334
+ return tolerate({
8335
+ ...record,
8336
+ properties: Object.fromEntries(Object.entries(record.properties).map(([key, child]) => [
8337
+ key,
8338
+ collapse(child, currentDepth + 1)
8339
+ ]))
8340
+ });
8341
+ }
8342
+ if (record.items)
8343
+ return tolerate({ ...record, items: collapse(record.items, currentDepth) });
8344
+ const composed = ["anyOf", "oneOf", "allOf"].filter((key) => Array.isArray(record[key]));
8345
+ if (composed.length > 0) {
8346
+ const projected = { ...record };
8347
+ for (const key of composed)
8348
+ projected[key] = record[key].map((item) => collapse(item, currentDepth));
8349
+ return projected;
8350
+ }
8351
+ if ("const" in record)
8352
+ return tolerate({ ...record });
8353
+ return node;
8354
+ };
8355
+ return collapse(operationJsonSchema(family), 0);
8356
+ }
8075
8357
  function canonical(value, seen) {
8076
8358
  if (value === null || typeof value === "boolean" || typeof value === "string") {
8077
8359
  if (typeof value === "string" && invalidUnicode(value))
@@ -9686,6 +9968,10 @@ var validPath = (value) => typeof value === "string" && value.length > 0 && ![..
9686
9968
  }) && !value.split(/[\\/]/).includes("..");
9687
9969
  var callerValue = (value) => ({ host: value.host, actor: value.actor });
9688
9970
  var workerIdOf = (value) => value.workerId ?? null;
9971
+ var currentWriterOwnsTask = (task, workspace, caller) => {
9972
+ const owner = workspace.writer?.owner;
9973
+ return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
9974
+ };
9689
9975
  function assertProductWriteAllowed(input) {
9690
9976
  if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
9691
9977
  return failure("invalid_input", "invalid product write path");
@@ -10868,9 +11154,11 @@ var standingReceiptFor = (root, workspace, cls, dir) => {
10868
11154
  var verifyStandingApproval = (root, task, caller, binding) => {
10869
11155
  if (task.status !== "active")
10870
11156
  return failure("invalid_transition", "only active tasks can authorize actions");
10871
- const session = task.intent?.provenance?.session;
10872
- if (!session || session.kind !== "host" || session.host !== caller.host || session.handle !== caller.actor)
10873
- return failure("permission_denied", "standing approval requires the lead session");
11157
+ const workspace = new TaskStore(root).readWorkspace();
11158
+ if (!workspace.ok)
11159
+ return workspace;
11160
+ if (!workspace.data || !currentWriterOwnsTask(task, workspace.data, caller))
11161
+ return failure("permission_denied", "standing approval requires the current writer session");
10874
11162
  const standing = binding.standing;
10875
11163
  if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
10876
11164
  return failure("invalid_input", "standing approval needs a workspace and class");
@@ -10918,14 +11206,14 @@ var standingAutoApplies = (store, host, actor, operation) => {
10918
11206
  const workspace = store.readWorkspace();
10919
11207
  if (!listed.ok || !workspace.ok || !workspace.data)
10920
11208
  return null;
10921
- 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);
10922
- if (matches.length !== 1)
11209
+ const task = listed.data.find((task) => currentWriterOwnsTask(task, workspace.data, { host, actor }));
11210
+ if (!task)
10923
11211
  return null;
10924
11212
  const resolved = resolveAutoApproval(store.root);
10925
11213
  if (resolved.status !== "on")
10926
11214
  return null;
10927
11215
  return {
10928
- task: matches[0],
11216
+ task,
10929
11217
  workspaceId: workspace.data.id,
10930
11218
  workspaceRevision: workspace.data.revision,
10931
11219
  cls,
@@ -11184,6 +11472,20 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
11184
11472
  };
11185
11473
  var verifyContentRefs = (store, binding) => verifyDecisionContentAtRoot(store.root, binding);
11186
11474
  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);
11475
+ var approvalAuthorityMatches = (store, task, approval, current) => {
11476
+ if (approval.kind !== "host_observed" || approval.receipts.length === 0)
11477
+ return false;
11478
+ if (provenanceMatches(approval, current))
11479
+ return true;
11480
+ const session = current.kind === "host_observed" ? current.session : null;
11481
+ if (!session || session.kind !== "host")
11482
+ return false;
11483
+ const workspace = store.readWorkspace();
11484
+ return workspace.ok && workspace.data !== null && currentWriterOwnsTask(task, workspace.data, {
11485
+ host: session.host,
11486
+ actor: session.handle
11487
+ });
11488
+ };
11187
11489
  var validateAction = (store, task, workspaceId, input, authority) => {
11188
11490
  if (task.status !== "active")
11189
11491
  return failure("invalid_transition", "only active tasks can authorize actions");
@@ -11209,7 +11511,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
11209
11511
  return failure("permission_denied", "native reservation authority does not match decision");
11210
11512
  if (decision.response !== "approved")
11211
11513
  return failure("permission_denied", "decision was rejected");
11212
- if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
11514
+ if (!approvalAuthorityMatches(store, task, entry.provenance, authority.provenance))
11213
11515
  return failure("permission_denied", "action approval lacks native receipt assurance");
11214
11516
  const receipts = entry.provenance.receipts;
11215
11517
  const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
@@ -11376,7 +11678,7 @@ function settleAction(input) {
11376
11678
  return failure("not_found", "decision not found");
11377
11679
  if (!authorityDecisionMatches(authority, entry.data))
11378
11680
  return failure("permission_denied", "native settlement authority does not match decision");
11379
- if (!provenanceMatches(entry.provenance, authority.provenance))
11681
+ if (!approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
11380
11682
  return failure("permission_denied", "native settlement caller does not match approval");
11381
11683
  if (entry.data.purpose !== "action" || entry.data.digest !== decisionDigest(entry.data))
11382
11684
  return failure("permission_denied", "decision binding is invalid");
@@ -11475,7 +11777,7 @@ function reconcileAction(input) {
11475
11777
  const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
11476
11778
  if (!entry || entry.data.purpose !== "action")
11477
11779
  return failure("not_found", "decision not found");
11478
- if (!authorityDecisionMatches(authority, entry.data) || !provenanceMatches(entry.provenance, authority.provenance))
11780
+ if (!authorityDecisionMatches(authority, entry.data) || !approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
11479
11781
  return failure("permission_denied", "native reconciliation caller does not match approval");
11480
11782
  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)
11481
11783
  return failure("permission_denied", "reconciliation decision binding is invalid");
@@ -11812,7 +12114,7 @@ var resolveBinding = (evidence, pin, currentCandidateId) => {
11812
12114
  candidateId: evidence.candidateId ?? fallback
11813
12115
  };
11814
12116
  };
11815
- var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId);
12117
+ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
11816
12118
  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")));
11817
12119
  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 }));
11818
12120
  var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
@@ -12282,6 +12584,11 @@ function shouldDenyShellRoute(dir, command) {
12282
12584
  }
12283
12585
  // packages/workit-core/src/core/external-action.ts
12284
12586
  import { execFileSync as execFileSync3 } from "node:child_process";
12587
+ var chainStepSchema = union([
12588
+ string2().min(1),
12589
+ object({ branch: string2().min(1) }).strict(),
12590
+ object({ pr: literal(true) }).strict()
12591
+ ]);
12285
12592
  var externalActionSchema = discriminatedUnion("operation", [
12286
12593
  object({
12287
12594
  operation: literal("git.branch_setup"),
@@ -12296,7 +12603,7 @@ var externalActionSchema = discriminatedUnion("operation", [
12296
12603
  operation: literal("git.commit"),
12297
12604
  payload: object({
12298
12605
  message: string2().min(1).optional(),
12299
- plan_steps: array(string2().min(1)).optional(),
12606
+ plan_steps: array(chainStepSchema).min(1).max(32).optional(),
12300
12607
  plan_branch: string2().min(1).optional()
12301
12608
  }).strict()
12302
12609
  }).strict(),
@@ -12394,7 +12701,13 @@ var externalActionDescriptor = (operation, payload) => canonicalJson({
12394
12701
  payload,
12395
12702
  ...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 } : {}
12396
12703
  });
12397
- 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).";
12704
+ var planReservationLength = (operation, descriptorPayload) => {
12705
+ if (operation !== "git.commit")
12706
+ return null;
12707
+ const payload = descriptorPayload ?? {};
12708
+ return Array.isArray(payload.plan_steps) ? payload.plan_steps.length : null;
12709
+ };
12710
+ 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).";
12398
12711
  var externalActionRef = (host, actor, descriptor) => ({
12399
12712
  kind: "host",
12400
12713
  host,
@@ -12408,12 +12721,12 @@ var externalActionState = (store, host, actor, operation) => {
12408
12721
  if (!workspace.data)
12409
12722
  return failure("not_found", "workspace not found");
12410
12723
  const candidates = listed.data.flatMap((task) => {
12411
- 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)
12724
+ if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12412
12725
  return [];
12413
12726
  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 }));
12414
12727
  });
12415
12728
  if (candidates.length !== 1)
12416
- return failure("permission_denied", candidates.length === 0 ? "no approved action is bound to this session" : "multiple approved actions match this session");
12729
+ return failure("permission_denied", candidates.length === 0 ? "no approved action is bound to the active task" : "multiple approved actions match the active task");
12417
12730
  return success(workspace.data.revision, null, { ...candidates[0], workspace: workspace.data });
12418
12731
  };
12419
12732
  var priorExternalAction = (store, host, actor, operation, payload) => {
@@ -12493,8 +12806,6 @@ var approvedPlanCommit = (store, host, actor, message) => {
12493
12806
  const matches = listed.data.flatMap((task) => {
12494
12807
  if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12495
12808
  return [];
12496
- if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
12497
- return [];
12498
12809
  return task.decisions.flatMap((entry) => {
12499
12810
  const decision = entry.data;
12500
12811
  if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
@@ -12536,8 +12847,6 @@ var approvedChainStep = (store, host, actor, query) => {
12536
12847
  const matches = listed.data.flatMap((task) => {
12537
12848
  if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12538
12849
  return [];
12539
- if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
12540
- return [];
12541
12850
  return task.decisions.flatMap((entry) => {
12542
12851
  const decision = entry.data;
12543
12852
  if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
@@ -12694,7 +13003,7 @@ var branchSetupIntent = (descriptor) => {
12694
13003
  var approvedBranchSetupIntent = (store, host, actor, operation) => {
12695
13004
  const current = branchSetupIntent(operation);
12696
13005
  if (!current)
12697
- return failure("permission_denied", "no approved action is bound to this session");
13006
+ return failure("permission_denied", "no approved action is bound to the active task");
12698
13007
  const listed = store.listTasks();
12699
13008
  const workspace = store.readWorkspace();
12700
13009
  if (!listed.ok || !workspace.ok)
@@ -12702,7 +13011,7 @@ var approvedBranchSetupIntent = (store, host, actor, operation) => {
12702
13011
  if (!workspace.data)
12703
13012
  return failure("not_found", "workspace not found");
12704
13013
  const candidates = listed.data.flatMap((task) => {
12705
- 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)
13014
+ if (task.status !== "active" || task.workspaceId !== workspace.data.id)
12706
13015
  return [];
12707
13016
  return task.decisions.filter((entry) => entry.data.purpose === "action" && entry.data.response === "approved" && entry.data.revoked === null).map((entry) => ({ task, entry }));
12708
13017
  });
@@ -12711,7 +13020,7 @@ var approvedBranchSetupIntent = (store, host, actor, operation) => {
12711
13020
  return intent !== null && intent.target === current.target && intent.base === current.base && intent.remoteBase === current.remoteBase && intent.targetExists === current.targetExists;
12712
13021
  });
12713
13022
  if (matches.length !== 1)
12714
- return failure("permission_denied", matches.length === 0 ? "no approved action is bound to this session" : "multiple approved actions match this session");
13023
+ return failure("permission_denied", matches.length === 0 ? "no approved action is bound to the active task" : "multiple approved actions match the active task");
12715
13024
  return success(workspace.data.revision, null, { ...matches[0], workspace: workspace.data });
12716
13025
  };
12717
13026
  var priorResolvedDrift = (store, host, actor, operation, payload, currentResolved) => {
@@ -13268,6 +13577,7 @@ var verifyNativeWorker = (verifier, input, binding) => {
13268
13577
  expectedWorkspaceRevision: expected.expectedWorkspaceRevision,
13269
13578
  state: expected.state,
13270
13579
  session: expected.session,
13580
+ report: expected.report ?? null,
13271
13581
  provenance: result.data,
13272
13582
  ...binding
13273
13583
  });
@@ -13278,7 +13588,7 @@ var takeWorker = (token, binding, expected) => {
13278
13588
  verifiedWorkers.delete(token);
13279
13589
  if (!value)
13280
13590
  return null;
13281
- 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))
13591
+ 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))
13282
13592
  return null;
13283
13593
  return value;
13284
13594
  };
@@ -13319,6 +13629,8 @@ var applyWorkerLifecycle = (input) => {
13319
13629
  return failure("invalid_transition", "worker already has an observed session");
13320
13630
  if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
13321
13631
  return failure("invalid_transition", "worker launch was not claimed");
13632
+ if (input.report && input.state !== "stopped")
13633
+ return failure("invalid_input", "worker reports require a stopped worker");
13322
13634
  if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
13323
13635
  return failure("invalid_input", "running worker observations require a session");
13324
13636
  if (entry.data.session && !sameValue(entry.data.session, input.session))
@@ -13335,12 +13647,13 @@ var applyWorkerLifecycle = (input) => {
13335
13647
  data: {
13336
13648
  ...entry.data,
13337
13649
  state: terminalCancel ? "cancelling" : input.state,
13338
- session: input.session
13650
+ session: input.session,
13651
+ report: entry.data.report ?? input.report ?? null
13339
13652
  }
13340
13653
  };
13341
13654
  const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
13342
13655
  const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
13343
- if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
13656
+ if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && sameValue(entry.data.report, nextEntry.data.report) && !shouldClear && !shouldUncertain)
13344
13657
  return success(task.data.revision, workspace.data.revision, entry);
13345
13658
  const changed = input.store.mutateTaskAndWorkspace({
13346
13659
  taskId: task.data.id,
@@ -13758,14 +14071,30 @@ class WorkitCore {
13758
14071
  const listed = this.store.listTasks();
13759
14072
  if (!listed.ok)
13760
14073
  return listed;
13761
- const summaries = [];
13762
- for (const task of listed.data) {
13763
- const summary = this.summary(task);
13764
- if (!summary.ok)
13765
- return summary;
13766
- summaries.push(summary.data);
13767
- }
13768
- return success(null, null, summaries);
14074
+ const status = input.status ?? "open";
14075
+ const limit = input.limit ?? 20;
14076
+ 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);
14077
+ if (selected.length === 0)
14078
+ return success(null, null, []);
14079
+ const workspace = this.store.readWorkspace();
14080
+ if (!workspace.ok)
14081
+ return workspace;
14082
+ if (!workspace.data)
14083
+ return failure("not_found", "workspace not found");
14084
+ const tasks = selected.map((task) => ({
14085
+ id: task.id,
14086
+ revision: task.revision,
14087
+ workspaceRevision: workspace.data.revision,
14088
+ createdAt: task.createdAt,
14089
+ updatedAt: task.updatedAt,
14090
+ runtime: task.runtime ?? null,
14091
+ objective: task.intent.data.objective,
14092
+ status: task.status,
14093
+ closure: task.closure,
14094
+ progress: task.progress,
14095
+ writer: task.status === "closed" ? null : workspace.data.writer
14096
+ }));
14097
+ return success(null, null, tasks);
13769
14098
  }
13770
14099
  case "inspect": {
13771
14100
  const task = this.store.readTask(input.taskId);
@@ -13774,7 +14103,7 @@ class WorkitCore {
13774
14103
  const helper = this.helperTaskGuard(task.data, true);
13775
14104
  if (!helper.ok)
13776
14105
  return helper;
13777
- if (input.view === "summary")
14106
+ if ((input.view ?? "summary") === "summary")
13778
14107
  return this.summary(task.data);
13779
14108
  return this.view(task.data);
13780
14109
  }
@@ -14118,7 +14447,7 @@ class WorkitCore {
14118
14447
  const evaluations = evaluateEvidence(task.data, current.data);
14119
14448
  const verified = evidence.some((entry) => {
14120
14449
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
14121
- return findingVerificationPasses(finding.data.candidateId, {
14450
+ return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
14122
14451
  kind: entry.data.kind,
14123
14452
  candidateId: entry.data.candidateId,
14124
14453
  status: evaluation?.status ?? "missing"
@@ -14198,7 +14527,18 @@ class WorkitCore {
14198
14527
  if (task.data.status !== "active")
14199
14528
  return failure("invalid_transition", "paused or closed tasks cannot assign workers");
14200
14529
  if (!bindingCovers(task.data.intent.data.scope, input.assignment.scope))
14201
- return failure("permission_denied", "worker assignment is outside task scope");
14530
+ return failure("permission_denied", "worker assignment is outside task scope", {
14531
+ fields: [
14532
+ {
14533
+ path: "assignment.scope",
14534
+ reason: `paths ${JSON.stringify(input.assignment.scope.paths)} must be contained by lead paths ${JSON.stringify(task.data.intent.data.scope.paths)}`
14535
+ },
14536
+ {
14537
+ path: "task.intent.scope",
14538
+ reason: `lead paths are ${JSON.stringify(task.data.intent.data.scope.paths)}`
14539
+ }
14540
+ ]
14541
+ });
14202
14542
  if (input.assignment.decisionIds.some((id) => !task.data.decisions.some((entry) => entry.id === id)))
14203
14543
  return failure("invalid_input", "worker assignment references an unknown decision");
14204
14544
  if (input.assignment.requirementIds.some((id) => !task.data.policy?.requirements.some((requirement) => requirement.id === id)))
@@ -14405,7 +14745,15 @@ class WorkitCore {
14405
14745
  ...item,
14406
14746
  recordedAt: mutation.now,
14407
14747
  provenance: attested.data,
14408
- data: { ...item.data, state: "dispatching" }
14748
+ data: {
14749
+ ...item.data,
14750
+ state: "dispatching",
14751
+ coordinator: {
14752
+ kind: "host",
14753
+ host: this.context.caller.host,
14754
+ handle: this.context.caller.actor
14755
+ }
14756
+ }
14409
14757
  } : item)
14410
14758
  });
14411
14759
  }
@@ -14481,6 +14829,7 @@ class WorkitCore {
14481
14829
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
14482
14830
  state: "stopped",
14483
14831
  session: null,
14832
+ report: null,
14484
14833
  provenance: attested.data,
14485
14834
  owner: this.authorityOwner,
14486
14835
  store: this.store,
@@ -14788,10 +15137,12 @@ class WorkitCore {
14788
15137
  return workspace;
14789
15138
  if (!workspace.data)
14790
15139
  return failure("not_found", "workspace not found");
14791
- const current = captureCandidate(this.store.root, task.intent.data.scope, environment());
15140
+ const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
15141
+ const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
14792
15142
  if (!current.ok)
14793
15143
  return current;
14794
- const requirements = evaluateRequirements(task, workspace.data, this.context.capabilities, current.data, this.store.root, this.context.caller);
15144
+ const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
15145
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
14795
15146
  return success(task.revision, workspace.data.revision, {
14796
15147
  id: task.id,
14797
15148
  revision: task.revision,
@@ -14805,7 +15156,7 @@ class WorkitCore {
14805
15156
  progress: task.progress,
14806
15157
  policy: task.policy,
14807
15158
  requirements,
14808
- writer: workspace.data.writer
15159
+ writer: evaluationWorkspace.writer
14809
15160
  });
14810
15161
  }
14811
15162
  view(task) {
@@ -14814,13 +15165,15 @@ class WorkitCore {
14814
15165
  return workspace;
14815
15166
  if (!workspace.data)
14816
15167
  return failure("not_found", "workspace not found");
14817
- const current = captureCandidate(this.store.root, task.intent.data.scope, environment());
15168
+ const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
15169
+ const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
14818
15170
  if (!current.ok)
14819
15171
  return current;
14820
- const requirements = evaluateRequirements(task, workspace.data, this.context.capabilities, current.data, this.store.root, this.context.caller);
15172
+ const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
15173
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
14821
15174
  return success(task.revision, workspace.data.revision, {
14822
15175
  task,
14823
- workspace: workspace.data,
15176
+ workspace: evaluationWorkspace,
14824
15177
  capabilities: this.context.capabilities,
14825
15178
  requirements,
14826
15179
  evidence: evaluateEvidence(task, current.data)
@@ -14911,8 +15264,6 @@ class WorkitCore {
14911
15264
  return helper;
14912
15265
  if (status === "paused" && task.data.status !== "active" || status === "active" && task.data.status !== "paused")
14913
15266
  return failure("invalid_transition", `cannot transition ${task.data.status} to ${status}`);
14914
- if (status === "active" && input.authorityRefs.length === 0)
14915
- return failure("permission_denied", "resuming a task requires authority references");
14916
15267
  const workspace = this.store.readWorkspace();
14917
15268
  if (!workspace.ok)
14918
15269
  return workspace;
@@ -14942,7 +15293,7 @@ class WorkitCore {
14942
15293
  return failure("needs_input", "imported task requires fresh destination evidence");
14943
15294
  if (current.data.blockers.some((blocker) => blocker.dependentAction === "resume" || blocker.reason === "worker state requires reconciliation"))
14944
15295
  return failure("recovery_required", "imported task requires resume reconciliation");
14945
- if (!validResumeApproval(task.data, workspace.data, input.authorityRefs, this.context))
15296
+ if (!validResumeApproval(task.data, workspace.data, input.authorityRefs ?? [], this.context))
14946
15297
  return failure("permission_denied", "imported resume requires native destination approval");
14947
15298
  resumeCandidate = current.data.candidate;
14948
15299
  }
@@ -15072,7 +15423,7 @@ class WorkitCore {
15072
15423
  task: (value, context) => success(context.revision, null, {
15073
15424
  ...value,
15074
15425
  status: "closed",
15075
- candidates: value.candidates.some((item) => item.id === withCandidate.id) ? value.candidates : [...value.candidates, withCandidate],
15426
+ candidates: taskForView.candidates,
15076
15427
  closure: {
15077
15428
  outcome: closure.data.outcome,
15078
15429
  at: context.now,
@@ -15151,6 +15502,22 @@ function run2(root, executable, args, env = {}) {
15151
15502
  }
15152
15503
 
15153
15504
  // packages/workit-core/src/core/skill-manifests.ts
15505
+ var WORKIT_METHOD_SKILLS = [
15506
+ "workit-challenge",
15507
+ "workit-behavioral-tdd",
15508
+ "workit-review",
15509
+ "workit-plan",
15510
+ "workit-implement",
15511
+ "workit-debug",
15512
+ "workit-handoff",
15513
+ "workit-babysit",
15514
+ "workit-blast-radius",
15515
+ "workit-deslop",
15516
+ "workit-diagram",
15517
+ "workit-mockup",
15518
+ "workit-green-run",
15519
+ "workit-steer"
15520
+ ];
15154
15521
  var WORKIT_SKILL_ALIASES = {
15155
15522
  "wk-challenge": "workit-challenge",
15156
15523
  "wk-babysit": "workit-babysit",
@@ -15407,27 +15774,54 @@ OpenCode Workit decisions use the native question header "Workit decision: <purp
15407
15774
  return cached2;
15408
15775
  };
15409
15776
 
15410
- // packages/workit-opencode/src/stale-sources.ts
15411
- import { existsSync as existsSync7 } from "node:fs";
15777
+ // packages/workit-opencode/src/shared/assets.ts
15778
+ import { existsSync as existsSync7, realpathSync as realpathSync7 } from "node:fs";
15779
+ import path13 from "node:path";
15412
15780
  import { fileURLToPath as fileURLToPath2 } from "node:url";
15781
+ var moduleDir = () => {
15782
+ try {
15783
+ return realpathSync7(path13.dirname(fileURLToPath2(import.meta.url)));
15784
+ } catch {
15785
+ return path13.dirname(fileURLToPath2(import.meta.url));
15786
+ }
15787
+ };
15788
+ var packageRoot2 = () => {
15789
+ const dir = moduleDir();
15790
+ for (const candidate of [
15791
+ path13.resolve(dir, ".."),
15792
+ path13.resolve(dir, "..", ".."),
15793
+ path13.resolve(dir, "..", "..", "..")
15794
+ ]) {
15795
+ if (existsSync7(path13.join(candidate, "package.json")))
15796
+ return candidate;
15797
+ }
15798
+ return path13.resolve(dir, "..");
15799
+ };
15800
+ var assetsRoot = () => path13.join(packageRoot2(), "assets");
15801
+
15802
+ // packages/workit-opencode/src/stale-sources.ts
15803
+ import { existsSync as existsSync8, readdirSync as readdirSync5 } from "node:fs";
15804
+ import path14 from "node:path";
15805
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
15806
+ var typescriptFiles = (directory) => {
15807
+ if (!existsSync8(directory))
15808
+ return [];
15809
+ return readdirSync5(directory, { withFileTypes: true }).flatMap((entry) => {
15810
+ const file = path14.join(directory, entry.name);
15811
+ return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
15812
+ });
15813
+ };
15413
15814
  var pluginSourceFiles = [
15414
- fileURLToPath2(import.meta.url),
15415
- fileURLToPath2(new URL("./plugin.ts", import.meta.url)),
15416
- fileURLToPath2(new URL("./tools/workit.ts", import.meta.url)),
15417
- ...[
15418
- "task-contract.ts",
15419
- "task-engine.ts",
15420
- "task-evaluation.ts",
15421
- "task-store.ts",
15422
- "workers.ts",
15423
- "authority.ts",
15424
- "methods.ts"
15425
- ].map((file) => fileURLToPath2(new URL(`../../workit-core/src/core/${file}`, import.meta.url)))
15426
- ].filter((file) => existsSync7(file));
15815
+ ...new Set([
15816
+ fileURLToPath3(import.meta.url),
15817
+ ...typescriptFiles(path14.join(packageRoot2(), "src")),
15818
+ path14.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
15819
+ ...typescriptFiles(path14.join(packageRoot2(), "..", "workit-core", "src", "core"))
15820
+ ])
15821
+ ].filter((file) => existsSync8(file)).sort();
15427
15822
 
15428
15823
  // packages/workit-opencode/src/tools/repo.ts
15429
- import path22 from "node:path";
15430
- import { realpathSync as realpathSync8 } from "node:fs";
15824
+ import { realpathSync as realpathSync9 } from "node:fs";
15431
15825
 
15432
15826
  // node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
15433
15827
  var exports_external = {};
@@ -27751,7 +28145,7 @@ function tool(input) {
27751
28145
  tool.schema = exports_external;
27752
28146
  // packages/workit-core/src/core/changelog.ts
27753
28147
  import fs8 from "node:fs";
27754
- import path13 from "node:path";
28148
+ import path15 from "node:path";
27755
28149
  import { createHash as createHash6 } from "node:crypto";
27756
28150
 
27757
28151
  // packages/workit-core/src/core/scripts.ts
@@ -27948,7 +28342,7 @@ function applyChangelog(target, entries, normalizeOnly) {
27948
28342
  const rendered = renderChangelog(source.text, entries, normalizeOnly);
27949
28343
  if ("error" in rendered)
27950
28344
  return { error: rendered.error };
27951
- fs8.mkdirSync(path13.dirname(target), { recursive: true });
28345
+ fs8.mkdirSync(path15.dirname(target), { recursive: true });
27952
28346
  fs8.writeFileSync(target, rendered.text, "utf8");
27953
28347
  return { ...rendered.result, path: target };
27954
28348
  }
@@ -27983,14 +28377,14 @@ function renderChangelog(source, entries, normalizeOnly) {
27983
28377
  var changelogTarget = (workspaceRoot, changelogPath) => {
27984
28378
  try {
27985
28379
  const cwd = fs8.realpathSync(resolveWorkspaceRoot(workspaceRoot));
27986
- const target = path13.resolve(cwd, changelogPath || "CHANGELOG.md");
27987
- if (target !== cwd && !target.startsWith(cwd + path13.sep))
28380
+ const target = path15.resolve(cwd, changelogPath || "CHANGELOG.md");
28381
+ if (target !== cwd && !target.startsWith(cwd + path15.sep))
27988
28382
  return { error: "changelog path must be inside workspace_root" };
27989
28383
  let ancestor = target;
27990
28384
  while (!fs8.existsSync(ancestor))
27991
- ancestor = path13.dirname(ancestor);
28385
+ ancestor = path15.dirname(ancestor);
27992
28386
  const canonicalAncestor = fs8.realpathSync(ancestor);
27993
- if (canonicalAncestor !== cwd && !canonicalAncestor.startsWith(cwd + path13.sep))
28387
+ if (canonicalAncestor !== cwd && !canonicalAncestor.startsWith(cwd + path15.sep))
27994
28388
  return { error: "changelog path must be inside workspace_root" };
27995
28389
  return { cwd, target: fs8.existsSync(target) ? fs8.realpathSync(target) : target };
27996
28390
  } catch {
@@ -28138,121 +28532,9 @@ function parseVerifyOutput(stdout) {
28138
28532
  return { passed, failed, skipped, commands };
28139
28533
  }
28140
28534
 
28141
- // packages/workit-core/src/core/gitignore.ts
28142
- import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
28143
- import path14 from "node:path";
28144
- var GITIGNORE_ENTRIES = [
28145
- "# workit: SDD working state (never commit)",
28146
- "docs/*/sdd/",
28147
- "",
28148
- "# OS / editor cruft",
28149
- ".DS_Store",
28150
- "Thumbs.db",
28151
- "*.swp",
28152
- ".idea/",
28153
- ".vscode/",
28154
- ".env",
28155
- "node_modules/",
28156
- "dist/",
28157
- "*.log",
28158
- ".cache/"
28159
- ];
28160
- var ensureProjectGitignore = (workspaceRoot, confirmed) => {
28161
- if (!confirmed)
28162
- return { ok: false, error: "confirmed: true required" };
28163
- const file = path14.join(workspaceRoot, ".gitignore");
28164
- const existing = existsSync8(file) ? readFileSync8(file, "utf8") : "";
28165
- const existingLines = new Set(existing.split(`
28166
- `).map((l) => l.trim()).filter(Boolean));
28167
- const added = [];
28168
- const append = [];
28169
- for (const entry of GITIGNORE_ENTRIES) {
28170
- if (entry.trim() === "" || existingLines.has(entry.trim()))
28171
- continue;
28172
- append.push(entry);
28173
- added.push(entry);
28174
- }
28175
- if (append.length) {
28176
- const separator = existing && !existing.endsWith(`
28177
- `) ? `
28178
- ` : "";
28179
- writeFileSync4(file, existing + separator + (existing ? `
28180
- ` : "") + append.join(`
28181
- `) + `
28182
- `, "utf8");
28183
- } else if (!existsSync8(file)) {
28184
- writeFileSync4(file, "", "utf8");
28185
- }
28186
- return { ok: true, path: file, added };
28187
- };
28188
-
28189
- // packages/workit-core/src/core/hygiene.ts
28190
- import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
28191
- import path15 from "node:path";
28192
- var repoRoot = assetRoot();
28193
- var templatesDir = () => path15.join(repoRoot, "templates", "hygiene");
28194
- var packageJson = (root) => {
28195
- if (!existsSync9(path15.join(root, "package.json")))
28196
- return null;
28197
- try {
28198
- return JSON.parse(readFileSync9(path15.join(root, "package.json"), "utf8"));
28199
- } catch {
28200
- return null;
28201
- }
28202
- };
28203
- var isOpenSource = (root) => {
28204
- if (existsSync9(path15.join(root, "LICENSE")))
28205
- return true;
28206
- const pkg = packageJson(root);
28207
- if (pkg && !pkg.private)
28208
- return true;
28209
- return path15.basename(root) === "workflow-toolkit";
28210
- };
28211
- var licenseHolder = (root) => {
28212
- const pkg = packageJson(root);
28213
- if (pkg) {
28214
- const author = pkg.author;
28215
- if (typeof author === "string" && author.trim())
28216
- return author.trim();
28217
- if (author && typeof author === "object") {
28218
- const name = author.name;
28219
- if (typeof name === "string" && name.trim())
28220
- return name.trim();
28221
- }
28222
- }
28223
- return "";
28224
- };
28225
- var planHygieneFiles = (root, opts = {}) => {
28226
- const openSource = opts.includeOpenSource ?? isOpenSource(root);
28227
- const files = ["CHANGELOG.md", "README.md", ".editorconfig", ".gitattributes"];
28228
- if (openSource)
28229
- files.push("LICENSE", "CONTRIBUTING.md");
28230
- const planned = [];
28231
- for (const file of files) {
28232
- if (existsSync9(path15.join(root, file)))
28233
- continue;
28234
- const tpl = path15.join(templatesDir(), file);
28235
- if (!existsSync9(tpl))
28236
- continue;
28237
- const content = readFileSync9(tpl, "utf8").replace(/<PROJECT>/g, path15.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
28238
- planned.push({ path: path15.join(root, file), content });
28239
- }
28240
- return planned;
28241
- };
28242
- var ensureHygieneFiles = (root, opts) => {
28243
- if (!opts.confirmed)
28244
- return { ok: false, error: "confirmed: true required" };
28245
- const created = [];
28246
- for (const planned of planHygieneFiles(root, opts)) {
28247
- writeFileSync5(planned.path, planned.content, "utf8");
28248
- created.push(path15.basename(planned.path));
28249
- }
28250
- return { ok: true, created };
28251
- };
28252
-
28253
28535
  // packages/workit-core/src/core/repo-context.ts
28254
28536
  import { spawnSync as spawnSync4 } from "node:child_process";
28255
- import { existsSync as existsSync10, readFileSync as readFileSync10, readdirSync as readdirSync5 } from "node:fs";
28537
+ import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync6 } from "node:fs";
28256
28538
  import path16 from "node:path";
28257
28539
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
28258
28540
  try {
@@ -28287,7 +28569,7 @@ var runGit = (cwd, args) => {
28287
28569
  exitCode: result.status ?? 1
28288
28570
  };
28289
28571
  };
28290
- var repoRoot2 = (cwd) => {
28572
+ var repoRoot = (cwd) => {
28291
28573
  const r = runGit(cwd, ["rev-parse", "--show-toplevel"]);
28292
28574
  return r.exitCode === 0 && r.stdout ? r.stdout : cwd;
28293
28575
  };
@@ -28324,7 +28606,7 @@ function resolvePrBranchContext(cwd) {
28324
28606
  if (branch === "unknown") {
28325
28607
  return {
28326
28608
  ok: false,
28327
- error: `not in a git repository at ${repoRoot2(cwd)} — open the target repository as the OpenCode session directory`
28609
+ error: `not in a git repository at ${repoRoot(cwd)} — open the target repository as the OpenCode session directory`
28328
28610
  };
28329
28611
  }
28330
28612
  return {
@@ -28367,7 +28649,7 @@ var printSection = (title) => `
28367
28649
  `;
28368
28650
  var probeCaseInsensitive = (dir, wanted) => {
28369
28651
  try {
28370
- for (const entry of readdirSync5(dir)) {
28652
+ for (const entry of readdirSync6(dir)) {
28371
28653
  if (entry.toLowerCase() === wanted.toLowerCase())
28372
28654
  return entry;
28373
28655
  }
@@ -28419,10 +28701,10 @@ var diffStatForRange = (cwd, range) => {
28419
28701
  };
28420
28702
  var packageScripts = (cwd, keys) => {
28421
28703
  const pkgPath = path16.join(cwd, "package.json");
28422
- if (!existsSync10(pkgPath))
28704
+ if (!existsSync9(pkgPath))
28423
28705
  return [];
28424
28706
  try {
28425
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
28707
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
28426
28708
  return keys.filter((k) => pkg.scripts && typeof pkg.scripts[k] === "string").map((k) => `${k}: ${pkg.scripts?.[k]}`);
28427
28709
  } catch {
28428
28710
  return [];
@@ -28436,7 +28718,7 @@ var documentationFiles = (cwd) => {
28436
28718
  return;
28437
28719
  let entries;
28438
28720
  try {
28439
- entries = readdirSync5(dir, { withFileTypes: true });
28721
+ entries = readdirSync6(dir, { withFileTypes: true });
28440
28722
  } catch {
28441
28723
  return;
28442
28724
  }
@@ -28454,15 +28736,15 @@ var documentationFiles = (cwd) => {
28454
28736
  return matches.sort().slice(0, 200);
28455
28737
  };
28456
28738
  var readTrimmed = (file, maxLines) => {
28457
- if (!existsSync10(file))
28739
+ if (!existsSync9(file))
28458
28740
  return "";
28459
- const lines = readFileSync10(file, "utf8").split(`
28741
+ const lines = readFileSync8(file, "utf8").split(`
28460
28742
  `);
28461
28743
  return lines.slice(0, maxLines).join(`
28462
28744
  `);
28463
28745
  };
28464
28746
  function prReadyContext(root, range) {
28465
- const cwd = path16.resolve(repoRoot2(root));
28747
+ const cwd = path16.resolve(repoRoot(root));
28466
28748
  if (range !== undefined && !isResolvableContextRange(cwd, range))
28467
28749
  return invalidRange(cwd);
28468
28750
  let stdout = "";
@@ -28530,7 +28812,7 @@ ${status.stderr}` : "");
28530
28812
  stdout += `
28531
28813
  `;
28532
28814
  stdout += printSection("Recent Validation Signals");
28533
- if (existsSync10(path16.join(cwd, "package.json"))) {
28815
+ if (existsSync9(path16.join(cwd, "package.json"))) {
28534
28816
  stdout += `package.json detected. Common scripts:
28535
28817
  `;
28536
28818
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -28539,7 +28821,7 @@ ${status.stderr}` : "");
28539
28821
  `) + `
28540
28822
  `;
28541
28823
  }
28542
- if (existsSync10(path16.join(cwd, "Cargo.toml")) || existsSync10(path16.join(cwd, "src-tauri/Cargo.toml"))) {
28824
+ if (existsSync9(path16.join(cwd, "Cargo.toml")) || existsSync9(path16.join(cwd, "src-tauri/Cargo.toml"))) {
28543
28825
  stdout += `Rust project detected.
28544
28826
  `;
28545
28827
  }
@@ -28572,7 +28854,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
28572
28854
  - Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
28573
28855
  - If Unreleased already has duplicate category headings, normalize_only first.`;
28574
28856
  function changelogContext(root, range) {
28575
- const cwd = path16.resolve(repoRoot2(root));
28857
+ const cwd = path16.resolve(repoRoot(root));
28576
28858
  if (range !== undefined && !isResolvableContextRange(cwd, range))
28577
28859
  return invalidRange(cwd);
28578
28860
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -28591,7 +28873,7 @@ function changelogContext(root, range) {
28591
28873
  `;
28592
28874
  stdout += printSection("Existing CHANGELOG.md");
28593
28875
  const changelogPath = path16.join(cwd, "CHANGELOG.md");
28594
- if (existsSync10(changelogPath)) {
28876
+ if (existsSync9(changelogPath)) {
28595
28877
  stdout += readTrimmed(changelogPath, 260) + `
28596
28878
  `;
28597
28879
  } else {
@@ -28610,7 +28892,7 @@ function changelogContext(root, range) {
28610
28892
  return { stdout, stderr: "", exitCode: 0, cwd };
28611
28893
  }
28612
28894
  function docsRefreshContext(root, range) {
28613
- const cwd = path16.resolve(repoRoot2(root));
28895
+ const cwd = path16.resolve(repoRoot(root));
28614
28896
  if (range !== undefined && !isResolvableContextRange(cwd, range))
28615
28897
  return invalidRange(cwd);
28616
28898
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -28633,7 +28915,7 @@ function docsRefreshContext(root, range) {
28633
28915
  `;
28634
28916
  stdout += printSection("README Preview");
28635
28917
  const readme = path16.join(cwd, "README.md");
28636
- if (existsSync10(readme)) {
28918
+ if (existsSync9(readme)) {
28637
28919
  stdout += readTrimmed(readme, 220) + `
28638
28920
  `;
28639
28921
  } else {
@@ -28642,9 +28924,9 @@ function docsRefreshContext(root, range) {
28642
28924
  }
28643
28925
  stdout += printSection("Package Scripts");
28644
28926
  const pkgPath = path16.join(cwd, "package.json");
28645
- if (existsSync10(pkgPath)) {
28927
+ if (existsSync9(pkgPath)) {
28646
28928
  try {
28647
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
28929
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
28648
28930
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
28649
28931
  `;
28650
28932
  } catch {}
@@ -28655,7 +28937,7 @@ function docsRefreshContext(root, range) {
28655
28937
  return { stdout, stderr: "", exitCode: 0, cwd };
28656
28938
  }
28657
28939
  function releaseNotesContext(root, rangeOrTag) {
28658
- const cwd = path16.resolve(repoRoot2(root));
28940
+ const cwd = path16.resolve(repoRoot(root));
28659
28941
  if (!rangeOrTag) {
28660
28942
  return { stdout: "", stderr: `ERROR: release tag or range required
28661
28943
  `, exitCode: 1, cwd };
@@ -28693,7 +28975,7 @@ function releaseNotesContext(root, rangeOrTag) {
28693
28975
  `;
28694
28976
  stdout += printSection("Existing Release Files");
28695
28977
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
28696
- if (existsSync10(path16.join(cwd, rel)))
28978
+ if (existsSync9(path16.join(cwd, rel)))
28697
28979
  stdout += rel + `
28698
28980
  `;
28699
28981
  }
@@ -28722,7 +29004,7 @@ function releaseNotesContext(root, rangeOrTag) {
28722
29004
 
28723
29005
  // packages/workit-core/src/core/verify-project.ts
28724
29006
  import { spawnSync as spawnSync5 } from "node:child_process";
28725
- import { existsSync as existsSync11, readFileSync as readFileSync11, statSync as statSync2 } from "node:fs";
29007
+ import { existsSync as existsSync10, readFileSync as readFileSync9, statSync as statSync2 } from "node:fs";
28726
29008
  import path17 from "node:path";
28727
29009
  var commandOnPath = (name) => {
28728
29010
  const dirs = (process.env.PATH ?? "").split(path17.delimiter);
@@ -28747,33 +29029,33 @@ var commandOnPath = (name) => {
28747
29029
  };
28748
29030
  var hasScript = (cwd, name) => {
28749
29031
  const pkgPath = path17.join(cwd, "package.json");
28750
- if (!existsSync11(pkgPath))
29032
+ if (!existsSync10(pkgPath))
28751
29033
  return false;
28752
29034
  try {
28753
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
29035
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
28754
29036
  return Boolean(pkg.scripts && typeof pkg.scripts[name] === "string");
28755
29037
  } catch {
28756
29038
  return false;
28757
29039
  }
28758
29040
  };
28759
29041
  var packageRunner = (cwd) => {
28760
- if (existsSync11(path17.join(cwd, "pnpm-lock.yaml")))
29042
+ if (existsSync10(path17.join(cwd, "pnpm-lock.yaml")))
28761
29043
  return "pnpm";
28762
29044
  const pkgPath = path17.join(cwd, "package.json");
28763
- if (existsSync11(pkgPath)) {
29045
+ if (existsSync10(pkgPath)) {
28764
29046
  try {
28765
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
29047
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
28766
29048
  if (typeof pkg.packageManager === "string" && pkg.packageManager.startsWith("pnpm")) {
28767
29049
  return "pnpm";
28768
29050
  }
28769
29051
  } catch {}
28770
29052
  }
28771
- if (existsSync11(path17.join(cwd, "yarn.lock")))
29053
+ if (existsSync10(path17.join(cwd, "yarn.lock")))
28772
29054
  return "yarn";
28773
29055
  return "npm";
28774
29056
  };
28775
29057
  function runVerifyProject(root, dryRun = false) {
28776
- const cwd = repoRoot2(root);
29058
+ const cwd = repoRoot(root);
28777
29059
  let passed = 0;
28778
29060
  let failed = 0;
28779
29061
  let skipped = 0;
@@ -28829,7 +29111,7 @@ root: ${cwd}
28829
29111
  lines.push(`dry_run: ${String(dryRun)}
28830
29112
  `);
28831
29113
  const pkgPath = path17.join(cwd, "package.json");
28832
- if (existsSync11(pkgPath)) {
29114
+ if (existsSync10(pkgPath)) {
28833
29115
  const runner = packageRunner(cwd);
28834
29116
  for (const script of ["lint", "format:check", "test", "build"]) {
28835
29117
  if (hasScript(cwd, script)) {
@@ -28844,7 +29126,7 @@ root: ${cwd}
28844
29126
  } else {
28845
29127
  skipCheck("javascript", "package.json not found");
28846
29128
  }
28847
- const cargoManifest = existsSync11(path17.join(cwd, "Cargo.toml")) ? "Cargo.toml" : existsSync11(path17.join(cwd, "src-tauri/Cargo.toml")) ? "src-tauri/Cargo.toml" : "";
29129
+ const cargoManifest = existsSync10(path17.join(cwd, "Cargo.toml")) ? "Cargo.toml" : existsSync10(path17.join(cwd, "src-tauri/Cargo.toml")) ? "src-tauri/Cargo.toml" : "";
28848
29130
  if (cargoManifest) {
28849
29131
  runCheck("cargo fmt", "cargo", ["fmt", "--manifest-path", cargoManifest, "--", "--check"]);
28850
29132
  runCheck("cargo clippy", "cargo", [
@@ -28860,7 +29142,7 @@ root: ${cwd}
28860
29142
  } else {
28861
29143
  skipCheck("rust", "Cargo.toml not found");
28862
29144
  }
28863
- if (existsSync11(path17.join(cwd, "pyproject.toml")) || existsSync11(path17.join(cwd, "pytest.ini")) || existsSync11(path17.join(cwd, "tests"))) {
29145
+ if (existsSync10(path17.join(cwd, "pyproject.toml")) || existsSync10(path17.join(cwd, "pytest.ini")) || existsSync10(path17.join(cwd, "tests"))) {
28864
29146
  if (commandOnPath("pytest"))
28865
29147
  runCheck("pytest", "pytest", []);
28866
29148
  else
@@ -28882,8 +29164,8 @@ root: ${cwd}
28882
29164
  lines.push(`status: skipped (dry run)
28883
29165
  `);
28884
29166
  skipped += 1;
28885
- } else if (existsSync11(changelogPath)) {
28886
- if (/## \[Unreleased\]/i.test(readFileSync11(changelogPath, "utf8"))) {
29167
+ } else if (existsSync10(changelogPath)) {
29168
+ if (/## \[Unreleased\]/i.test(readFileSync9(changelogPath, "utf8"))) {
28887
29169
  lines.push(`status: pass
28888
29170
  `);
28889
29171
  passed += 1;
@@ -28997,12 +29279,12 @@ function whichOnPath(tool) {
28997
29279
  return null;
28998
29280
  }
28999
29281
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
29000
- function repoRoot3(cwd) {
29282
+ function repoRoot2(cwd) {
29001
29283
  const result = spawnSync6("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
29002
29284
  return result.status === 0 ? (result.stdout ?? "").trim() : cwd;
29003
29285
  }
29004
29286
  function prCreate(env, cwd) {
29005
- const root = cwd ? repoRoot3(cwd) : process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
29287
+ const root = cwd ? repoRoot2(cwd) : process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
29006
29288
  const policy = resolveBranchPolicyFor(root);
29007
29289
  const cfg = vcsConfig("load", root);
29008
29290
  if (!cfg.ok)
@@ -29195,7 +29477,7 @@ function prCreate(env, cwd) {
29195
29477
  };
29196
29478
  }
29197
29479
  function mergePr(cwd, opts) {
29198
- const root = repoRoot3(cwd);
29480
+ const root = repoRoot2(cwd);
29199
29481
  const cfg = vcsConfig("load", root);
29200
29482
  if (!cfg.ok)
29201
29483
  return { error: cfg.error ?? "vcs config missing" };
@@ -29244,14 +29526,14 @@ function mergePr(cwd, opts) {
29244
29526
 
29245
29527
  // packages/workit-core/src/core/init.ts
29246
29528
  import fs11 from "node:fs";
29247
- import path21 from "node:path";
29529
+ import path23 from "node:path";
29248
29530
  import { spawnSync as spawnSync8 } from "node:child_process";
29249
29531
 
29250
29532
  // packages/workit-core/src/core/safe-write.ts
29251
- import { writeFileSync as writeFileSync6 } from "node:fs";
29533
+ import { writeFileSync as writeFileSync4 } from "node:fs";
29252
29534
  function writeFileExclusive(file, content, mode) {
29253
29535
  try {
29254
- writeFileSync6(file, content, { encoding: "utf8", flag: "wx", mode });
29536
+ writeFileSync4(file, content, { encoding: "utf8", flag: "wx", mode });
29255
29537
  return "created";
29256
29538
  } catch (err) {
29257
29539
  if (err.code === "EEXIST")
@@ -29264,19 +29546,19 @@ function writeFileExclusive(file, content, mode) {
29264
29546
  import {
29265
29547
  copyFileSync as copyFileSync2,
29266
29548
  cpSync as cpSync2,
29267
- existsSync as existsSync12,
29549
+ existsSync as existsSync13,
29268
29550
  lstatSync as lstatSync4,
29269
29551
  mkdirSync as mkdirSync5,
29270
29552
  mkdtempSync,
29271
29553
  readFileSync as readFileSync12,
29272
- readdirSync as readdirSync6,
29273
- realpathSync as realpathSync7,
29554
+ readdirSync as readdirSync7,
29555
+ realpathSync as realpathSync8,
29274
29556
  renameSync as renameSync2,
29275
29557
  rmSync,
29276
29558
  statSync as statSync3,
29277
29559
  writeFileSync as writeFileSync7
29278
29560
  } from "node:fs";
29279
- import path19 from "node:path";
29561
+ import path21 from "node:path";
29280
29562
  import { isDeepStrictEqual } from "node:util";
29281
29563
 
29282
29564
  // packages/workit-core/src/core/branch-policy.ts
@@ -29339,6 +29621,118 @@ var detectBranchPolicy = (workspaceRoot) => {
29339
29621
  };
29340
29622
  };
29341
29623
 
29624
+ // packages/workit-core/src/core/gitignore.ts
29625
+ import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
29626
+ import path19 from "node:path";
29627
+ var GITIGNORE_ENTRIES = [
29628
+ "# workit: SDD working state (never commit)",
29629
+ "docs/*/sdd/",
29630
+ "",
29631
+ "# OS / editor cruft",
29632
+ ".DS_Store",
29633
+ "Thumbs.db",
29634
+ "*.swp",
29635
+ ".idea/",
29636
+ ".vscode/",
29637
+ ".env",
29638
+ "node_modules/",
29639
+ "dist/",
29640
+ "*.log",
29641
+ ".cache/"
29642
+ ];
29643
+ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
29644
+ if (!confirmed)
29645
+ return { ok: false, error: "confirmed: true required" };
29646
+ const file = path19.join(workspaceRoot, ".gitignore");
29647
+ const existing = existsSync11(file) ? readFileSync10(file, "utf8") : "";
29648
+ const existingLines = new Set(existing.split(`
29649
+ `).map((l) => l.trim()).filter(Boolean));
29650
+ const added = [];
29651
+ const append = [];
29652
+ for (const entry of GITIGNORE_ENTRIES) {
29653
+ if (entry.trim() === "" || existingLines.has(entry.trim()))
29654
+ continue;
29655
+ append.push(entry);
29656
+ added.push(entry);
29657
+ }
29658
+ if (append.length) {
29659
+ const separator = existing && !existing.endsWith(`
29660
+ `) ? `
29661
+ ` : "";
29662
+ writeFileSync5(file, existing + separator + (existing ? `
29663
+ ` : "") + append.join(`
29664
+ `) + `
29665
+ `, "utf8");
29666
+ } else if (!existsSync11(file)) {
29667
+ writeFileSync5(file, "", "utf8");
29668
+ }
29669
+ return { ok: true, path: file, added };
29670
+ };
29671
+
29672
+ // packages/workit-core/src/core/hygiene.ts
29673
+ import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
29674
+ import path20 from "node:path";
29675
+ var repoRoot3 = assetRoot();
29676
+ var templatesDir = () => path20.join(repoRoot3, "templates", "hygiene");
29677
+ var packageJson = (root) => {
29678
+ if (!existsSync12(path20.join(root, "package.json")))
29679
+ return null;
29680
+ try {
29681
+ return JSON.parse(readFileSync11(path20.join(root, "package.json"), "utf8"));
29682
+ } catch {
29683
+ return null;
29684
+ }
29685
+ };
29686
+ var isOpenSource = (root) => {
29687
+ if (existsSync12(path20.join(root, "LICENSE")))
29688
+ return true;
29689
+ const pkg = packageJson(root);
29690
+ if (pkg && !pkg.private)
29691
+ return true;
29692
+ return path20.basename(root) === "workflow-toolkit";
29693
+ };
29694
+ var licenseHolder = (root) => {
29695
+ const pkg = packageJson(root);
29696
+ if (pkg) {
29697
+ const author = pkg.author;
29698
+ if (typeof author === "string" && author.trim())
29699
+ return author.trim();
29700
+ if (author && typeof author === "object") {
29701
+ const name = author.name;
29702
+ if (typeof name === "string" && name.trim())
29703
+ return name.trim();
29704
+ }
29705
+ }
29706
+ return "";
29707
+ };
29708
+ var planHygieneFiles = (root, opts = {}) => {
29709
+ const openSource = opts.includeOpenSource ?? isOpenSource(root);
29710
+ const files = ["CHANGELOG.md", "README.md", ".editorconfig", ".gitattributes"];
29711
+ if (openSource)
29712
+ files.push("LICENSE", "CONTRIBUTING.md");
29713
+ const planned = [];
29714
+ for (const file of files) {
29715
+ if (existsSync12(path20.join(root, file)))
29716
+ continue;
29717
+ const tpl = path20.join(templatesDir(), file);
29718
+ if (!existsSync12(tpl))
29719
+ continue;
29720
+ const content = readFileSync11(tpl, "utf8").replace(/<PROJECT>/g, path20.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
29721
+ planned.push({ path: path20.join(root, file), content });
29722
+ }
29723
+ return planned;
29724
+ };
29725
+ var ensureHygieneFiles = (root, opts) => {
29726
+ if (!opts.confirmed)
29727
+ return { ok: false, error: "confirmed: true required" };
29728
+ const created = [];
29729
+ for (const planned of planHygieneFiles(root, opts)) {
29730
+ writeFileSync6(planned.path, planned.content, "utf8");
29731
+ created.push(path20.basename(planned.path));
29732
+ }
29733
+ return { ok: true, created };
29734
+ };
29735
+
29342
29736
  // packages/workit-core/src/core/registration.ts
29343
29737
  var CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@latest";
29344
29738
  var CURSOR_HOOK_RUN_COMMAND = `npx -y --prefer-online --min-release-age=0 --package=${CURSOR_RUNTIME_PACKAGE} workit-cursor-hook`;
@@ -29380,12 +29774,12 @@ var VCS_OVERRIDES = [
29380
29774
  var SETUP_OVERRIDES = [...YT_OVERRIDES, ...VCS_OVERRIDES];
29381
29775
  function applyWorkspaceBranchPolicy(opts) {
29382
29776
  const { workspace_root, env = process.env } = opts;
29383
- const dir = path19.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
29777
+ const dir = path21.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
29384
29778
  const { status, path: wsPath, entries } = readWorkspacesResult(dir);
29385
29779
  if (status === "malformed")
29386
29780
  return { ok: false, error: `malformed workspaces.json: ${wsPath}` };
29387
29781
  const detection = detectBranchPolicy(workspace_root);
29388
- const name = String(env.WORKFLOW_BP_NAME ?? path19.basename(workspace_root));
29782
+ const name = String(env.WORKFLOW_BP_NAME ?? path21.basename(workspace_root));
29389
29783
  const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
29390
29784
  const policy = {
29391
29785
  preset: detection.preset,
@@ -29410,7 +29804,7 @@ function applyWorkspaceBranchPolicy(opts) {
29410
29804
  };
29411
29805
  }
29412
29806
  const next = existing ? entries.map((w, i) => i === idx ? { ...w, branchPolicy: policy } : w) : [...entries, { name, glob, branchPolicy: policy }];
29413
- mkdirSync5(path19.dirname(wsPath), { recursive: true });
29807
+ mkdirSync5(path21.dirname(wsPath), { recursive: true });
29414
29808
  writeFileSync7(wsPath, JSON.stringify({ workspaces: next }, null, 2) + `
29415
29809
  `, "utf8");
29416
29810
  return {
@@ -29424,7 +29818,7 @@ function applyWorkspaceBranchPolicy(opts) {
29424
29818
 
29425
29819
  // packages/workit-core/src/core/youtrack.ts
29426
29820
  import fs10 from "node:fs";
29427
- import path20 from "node:path";
29821
+ import path22 from "node:path";
29428
29822
 
29429
29823
  // packages/workit-core/src/core/templates.ts
29430
29824
  var repoRoot4 = assetRoot();
@@ -29432,8 +29826,8 @@ var repoRoot4 = assetRoot();
29432
29826
  // packages/workit-core/src/core/youtrack.ts
29433
29827
  var ISSUE_RE = /^[A-Z]+-\d+$/;
29434
29828
  var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
29435
- var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path20.join(configDir(), "youtrack.json");
29436
- var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path20.join(resolveConfigDir(), "youtrack.json");
29829
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path22.join(configDir(), "youtrack.json");
29830
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path22.join(resolveConfigDir(), "youtrack.json");
29437
29831
  var youTrackTokenModeOk = (p) => {
29438
29832
  if (process.platform === "win32")
29439
29833
  return true;
@@ -29477,7 +29871,7 @@ function youTrackConfigLoad() {
29477
29871
  }
29478
29872
  const cfgPath = loaded.path;
29479
29873
  const tokenFile = String(loaded.config.tokenFile ?? "");
29480
- const tokenPath = tokenFile ? path20.isAbsolute(tokenFile) ? path20.resolve(tokenFile) : path20.resolve(process.cwd(), tokenFile) : "";
29874
+ const tokenPath = tokenFile ? path22.isAbsolute(tokenFile) ? path22.resolve(tokenFile) : path22.resolve(process.cwd(), tokenFile) : "";
29481
29875
  if (!tokenPath || !fs10.existsSync(tokenPath)) {
29482
29876
  return { error: "missing youtrack.token" };
29483
29877
  }
@@ -29491,8 +29885,8 @@ function youTrackConfigLoad() {
29491
29885
  const redacted = { ...loaded.config };
29492
29886
  delete redacted.tokenFile;
29493
29887
  redacted.tokenPresent = true;
29494
- redacted.configPath = path20.resolve(cfgPath);
29495
- redacted.tokenPath = path20.resolve(tokenPath);
29888
+ redacted.configPath = path22.resolve(cfgPath);
29889
+ redacted.tokenPath = path22.resolve(tokenPath);
29496
29890
  return { data: redacted };
29497
29891
  }
29498
29892
  function tzParts(date, tz) {
@@ -29605,7 +29999,7 @@ var youTrackToken = () => {
29605
29999
  if ("error" in loaded)
29606
30000
  return loaded;
29607
30001
  const tokenFile = String(loaded.config.tokenFile ?? "");
29608
- const tokenPath = tokenFile ? path20.isAbsolute(tokenFile) ? path20.resolve(tokenFile) : path20.resolve(process.cwd(), tokenFile) : "";
30002
+ const tokenPath = tokenFile ? path22.isAbsolute(tokenFile) ? path22.resolve(tokenFile) : path22.resolve(process.cwd(), tokenFile) : "";
29609
30003
  if (!tokenPath || !fs10.existsSync(tokenPath))
29610
30004
  return { error: "missing youtrack.token" };
29611
30005
  if (!youTrackTokenModeOk(tokenPath))
@@ -29760,7 +30154,7 @@ function youTrackTokenCreateUrl() {
29760
30154
  const desc = String(defaults.description ?? "OpenCode workit — /wk-issue-update and /wk-meetings");
29761
30155
  const scopes = Array.isArray(defaults.scopes) ? defaults.scopes : ["YouTrack"];
29762
30156
  const base = String(config.baseUrl ?? "https://enghouseamg.youtrack.cloud").replace(/\/+$/, "");
29763
- const tokenFile = String(config.tokenFile ?? path20.join(path20.dirname(loaded.path), "youtrack.token"));
30157
+ const tokenFile = String(config.tokenFile ?? path22.join(path22.dirname(loaded.path), "youtrack.token"));
29764
30158
  const tab = String(defaults.profileTab ?? "account-security");
29765
30159
  const createUrl = `${base}/users/me?${new URLSearchParams({ tab })}`;
29766
30160
  const docsUrl = "https://www.jetbrains.com/help/youtrack/cloud/manage-permanent-token.html";
@@ -29769,7 +30163,7 @@ function youTrackTokenCreateUrl() {
29769
30163
  tokenName: name,
29770
30164
  tokenDescription: desc,
29771
30165
  scopes,
29772
- tokenFile: path20.resolve(tokenFile),
30166
+ tokenFile: path22.resolve(tokenFile),
29773
30167
  createUrl,
29774
30168
  docsUrl,
29775
30169
  prefillSupported: false,
@@ -30087,12 +30481,12 @@ var resolvePath = (p) => {
30087
30481
  try {
30088
30482
  return fs11.realpathSync(p);
30089
30483
  } catch {
30090
- return path21.resolve(p);
30484
+ return path23.resolve(p);
30091
30485
  }
30092
30486
  };
30093
30487
  function initStatusData(configDirPath = configDir()) {
30094
- const ytJson = path21.join(configDirPath, "youtrack.json");
30095
- const vcsJson = path21.join(configDirPath, "vcs.json");
30488
+ const ytJson = path23.join(configDirPath, "youtrack.json");
30489
+ const vcsJson = path23.join(configDirPath, "vcs.json");
30096
30490
  const items = [];
30097
30491
  let youtrackConfig = null;
30098
30492
  let youtrackTokenCreate = null;
@@ -30115,8 +30509,8 @@ function initStatusData(configDirPath = configDir()) {
30115
30509
  });
30116
30510
  }
30117
30511
  }
30118
- const expanded = tokenFile ? path21.resolve(tokenFile) : null;
30119
- const resolvedTokenFile = expanded && fs11.existsSync(expanded) ? resolvePath(expanded) : expanded ? path21.isAbsolute(expanded) ? expanded : path21.resolve(configDirPath, expanded) : null;
30512
+ const expanded = tokenFile ? path23.resolve(tokenFile) : null;
30513
+ const resolvedTokenFile = expanded && fs11.existsSync(expanded) ? resolvePath(expanded) : expanded ? path23.isAbsolute(expanded) ? expanded : path23.resolve(configDirPath, expanded) : null;
30120
30514
  youtrackConfig = {
30121
30515
  config_edit_path: resolvePath(ytJson),
30122
30516
  baseUrl: base,
@@ -30163,11 +30557,11 @@ function initStatusData(configDirPath = configDir()) {
30163
30557
  id: "youtrack_json",
30164
30558
  label: "YouTrack config",
30165
30559
  ok: fs11.existsSync(ytJson) && youtrackConfig !== null && !("error" in youtrackConfig),
30166
- path: fs11.existsSync(ytJson) ? resolvePath(ytJson) : path21.resolve(ytJson),
30560
+ path: fs11.existsSync(ytJson) ? resolvePath(ytJson) : path23.resolve(ytJson),
30167
30561
  config_edit_path: resolvePath(ytJson),
30168
30562
  fix: "workit_init_apply action=youtrack_scaffold"
30169
30563
  });
30170
- const ytTokenPath = youtrackConfig?.tokenFile ?? path21.join(configDirPath, "youtrack.token");
30564
+ const ytTokenPath = youtrackConfig?.tokenFile ?? path23.join(configDirPath, "youtrack.token");
30171
30565
  const tokenText = fs11.existsSync(ytTokenPath) ? fs11.readFileSync(ytTokenPath, "utf8").trim() : "";
30172
30566
  const placeholder = fs11.existsSync(ytTokenPath) && isPlaceholder(tokenText);
30173
30567
  const tokenOk = fs11.existsSync(ytTokenPath) && modeOk(ytTokenPath) && Boolean(tokenText) && !isPlaceholder(tokenText);
@@ -30175,8 +30569,8 @@ function initStatusData(configDirPath = configDir()) {
30175
30569
  id: "youtrack_token",
30176
30570
  label: "YouTrack API token (mode 600, not placeholder)",
30177
30571
  ok: tokenOk,
30178
- path: fs11.existsSync(ytTokenPath) ? resolvePath(ytTokenPath) : path21.resolve(ytTokenPath),
30179
- token_edit_path: fs11.existsSync(ytTokenPath) ? resolvePath(ytTokenPath) : path21.resolve(ytTokenPath),
30572
+ path: fs11.existsSync(ytTokenPath) ? resolvePath(ytTokenPath) : path23.resolve(ytTokenPath),
30573
+ token_edit_path: fs11.existsSync(ytTokenPath) ? resolvePath(ytTokenPath) : path23.resolve(ytTokenPath),
30180
30574
  placeholder,
30181
30575
  fix: `Open ${resolvePath(ytTokenPath)} — replace ${TOKEN_PLACEHOLDER3} with your permanent token, save, then /wk-status`
30182
30576
  };
@@ -30202,7 +30596,7 @@ function initStatusData(configDirPath = configDir()) {
30202
30596
  const provider = typeof rawProvider === "string" && rawProvider.trim() ? rawProvider.toLowerCase() : null;
30203
30597
  const tokenFiles = {};
30204
30598
  for (const k of ["gitlab", "github"]) {
30205
- tokenFiles[k] = String(vcsParsed[k]?.tokenFile ?? path21.join(configDirPath, `${k}.token`));
30599
+ tokenFiles[k] = String(vcsParsed[k]?.tokenFile ?? path23.join(configDirPath, `${k}.token`));
30206
30600
  }
30207
30601
  vcsCfg = {
30208
30602
  config_edit_path: resolvePath(vcsJson),
@@ -30227,14 +30621,14 @@ function initStatusData(configDirPath = configDir()) {
30227
30621
  id: "vcs_json",
30228
30622
  label: "VCS config (GitLab / GitHub)",
30229
30623
  ok: fs11.existsSync(vcsJson) && vcsCfg !== null && !("error" in vcsCfg),
30230
- path: fs11.existsSync(vcsJson) ? resolvePath(vcsJson) : path21.resolve(vcsJson),
30624
+ path: fs11.existsSync(vcsJson) ? resolvePath(vcsJson) : path23.resolve(vcsJson),
30231
30625
  config_edit_path: resolvePath(vcsJson),
30232
30626
  fix: "workit_init_apply action=vcs_scaffold"
30233
30627
  });
30234
30628
  const provActive = vcsCfg && !("error" in vcsCfg) ? vcsCfg.provider : null;
30235
30629
  const tokenItem = (tid, label, rawPath, providerKey) => {
30236
- const t = path21.isAbsolute(rawPath) ? rawPath : path21.resolve(configDirPath, rawPath);
30237
- const abs = fs11.existsSync(t) ? resolvePath(t) : path21.resolve(t);
30630
+ const t = path23.isAbsolute(rawPath) ? rawPath : path23.resolve(configDirPath, rawPath);
30631
+ const abs = fs11.existsSync(t) ? resolvePath(t) : path23.resolve(t);
30238
30632
  const text = fs11.existsSync(t) ? fs11.readFileSync(t, "utf8").trim() : "";
30239
30633
  const ph = isPlaceholder(text);
30240
30634
  const ok = fs11.existsSync(t) && modeOk(t) && Boolean(text) && !ph;
@@ -30265,7 +30659,7 @@ function initStatusData(configDirPath = configDir()) {
30265
30659
  };
30266
30660
  const vcsTokenFiles = {};
30267
30661
  for (const k of ["gitlab", "github"]) {
30268
- vcsTokenFiles[k] = String(vcsParsed?.[k]?.tokenFile ?? path21.join(configDirPath, `${k}.token`));
30662
+ vcsTokenFiles[k] = String(vcsParsed?.[k]?.tokenFile ?? path23.join(configDirPath, `${k}.token`));
30269
30663
  }
30270
30664
  items.push(tokenItem("gitlab_token", "GitLab token (mode 600, not placeholder)", vcsTokenFiles.gitlab, "gitlab"));
30271
30665
  items.push(tokenItem("github_token", "GitHub token (mode 600, not placeholder)", vcsTokenFiles.github, "github"));
@@ -30322,7 +30716,7 @@ async function toolkitStatusData(configDirPath = configDir()) {
30322
30716
  }
30323
30717
  var youtrackJsonContent = (dir) => ({
30324
30718
  baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://enghouseamg.youtrack.cloud",
30325
- tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path21.join(dir, "youtrack.token"),
30719
+ tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path23.join(dir, "youtrack.token"),
30326
30720
  timezone: process.env.WORKFLOW_YT_TIMEZONE ?? "America/Santiago",
30327
30721
  locale: "es-CL",
30328
30722
  defaultMention: process.env.WORKFLOW_YT_MENTION ?? "Alejandra.Flores",
@@ -30361,11 +30755,11 @@ var vcsJsonContent = (dir, cwd) => {
30361
30755
  gitlab: {
30362
30756
  host: process.env.WORKFLOW_GITLAB_HOST ?? "gitlab.com",
30363
30757
  apiUrl: process.env.WORKFLOW_GITLAB_API_URL ?? "https://gitlab.com/api/v4",
30364
- tokenFile: path21.join(dir, "gitlab.token")
30758
+ tokenFile: path23.join(dir, "gitlab.token")
30365
30759
  },
30366
30760
  github: {
30367
30761
  host: process.env.WORKFLOW_GITHUB_HOST ?? "github.com",
30368
- tokenFile: path21.join(dir, "github.token")
30762
+ tokenFile: path23.join(dir, "github.token")
30369
30763
  },
30370
30764
  pr: { squashOnMerge: true, removeSourceBranch: true, pushBranch: true, confirmSkip: true },
30371
30765
  tokenDefaults: {
@@ -30382,16 +30776,16 @@ function initApplyData(action, env = process.env) {
30382
30776
  fs11.mkdirSync(dir, { recursive: true });
30383
30777
  switch (action) {
30384
30778
  case "youtrack_json": {
30385
- const out = path21.join(dir, "youtrack.json");
30779
+ const out = path23.join(dir, "youtrack.json");
30386
30780
  fs11.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
30387
30781
  `, "utf8");
30388
30782
  return { action, ok: true, path: out };
30389
30783
  }
30390
30784
  case "youtrack_token_placeholder": {
30391
- const p = path21.join(dir, "youtrack.token");
30785
+ const p = path23.join(dir, "youtrack.token");
30392
30786
  const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER3 + `
30393
30787
  `, 384) === "preserved";
30394
- const abs = path21.resolve(p);
30788
+ const abs = path23.resolve(p);
30395
30789
  return {
30396
30790
  action,
30397
30791
  ok: true,
@@ -30403,14 +30797,14 @@ function initApplyData(action, env = process.env) {
30403
30797
  };
30404
30798
  }
30405
30799
  case "youtrack_scaffold": {
30406
- const jsonOut = path21.join(dir, "youtrack.json");
30407
- const tokenOut = path21.join(dir, "youtrack.token");
30800
+ const jsonOut = path23.join(dir, "youtrack.json");
30801
+ const tokenOut = path23.join(dir, "youtrack.token");
30408
30802
  fs11.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
30409
30803
  `, "utf8");
30410
30804
  const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER3 + `
30411
30805
  `, 384) === "preserved";
30412
- const configPath = path21.resolve(jsonOut);
30413
- const tokenPath = path21.resolve(tokenOut);
30806
+ const configPath = path23.resolve(jsonOut);
30807
+ const tokenPath = path23.resolve(tokenOut);
30414
30808
  const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
30415
30809
  process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
30416
30810
  let tokenCreate = {};
@@ -30464,19 +30858,19 @@ function initApplyData(action, env = process.env) {
30464
30858
  }
30465
30859
  }
30466
30860
  case "vcs_scaffold": {
30467
- const jsonOut = path21.join(dir, "vcs.json");
30861
+ const jsonOut = path23.join(dir, "vcs.json");
30468
30862
  fs11.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(dir), null, 2) + `
30469
30863
  `, "utf8");
30470
- const glPath = path21.join(dir, "gitlab.token");
30471
- const ghPath = path21.join(dir, "github.token");
30864
+ const glPath = path23.join(dir, "gitlab.token");
30865
+ const ghPath = path23.join(dir, "github.token");
30472
30866
  const preservedTokens = [];
30473
30867
  for (const p of [glPath, ghPath]) {
30474
30868
  if (writeFileExclusive(p, TOKEN_PLACEHOLDER3 + `
30475
30869
  `, 384) === "preserved") {
30476
- preservedTokens.push(path21.resolve(p));
30870
+ preservedTokens.push(path23.resolve(p));
30477
30871
  }
30478
30872
  }
30479
- const configPath = path21.resolve(jsonOut);
30873
+ const configPath = path23.resolve(jsonOut);
30480
30874
  const prev = process.env.WORKFLOW_VCS_CONFIG;
30481
30875
  process.env.WORKFLOW_VCS_CONFIG = configPath;
30482
30876
  try {
@@ -30485,14 +30879,14 @@ function initApplyData(action, env = process.env) {
30485
30879
  const provider = typeof rawProvider === "string" && rawProvider.trim() ? rawProvider : null;
30486
30880
  const tokenUrls = vcsTokenCreateUrls();
30487
30881
  const active = tokenUrls.active ?? {};
30488
- const activePath = provider === "gitlab" ? path21.resolve(glPath) : provider === "github" ? path21.resolve(ghPath) : null;
30882
+ const activePath = provider === "gitlab" ? path23.resolve(glPath) : provider === "github" ? path23.resolve(ghPath) : null;
30489
30883
  return {
30490
30884
  action,
30491
30885
  ok: true,
30492
30886
  vcs_json: configPath,
30493
30887
  config_edit_path: configPath,
30494
- gitlab_token: path21.resolve(glPath),
30495
- github_token: path21.resolve(ghPath),
30888
+ gitlab_token: path23.resolve(glPath),
30889
+ github_token: path23.resolve(ghPath),
30496
30890
  token_edit_path: activePath,
30497
30891
  token_create_url: active.createUrl,
30498
30892
  token_create_urls: tokenUrls,
@@ -30547,6 +30941,109 @@ var normalizeLegacyResult = (value) => {
30547
30941
  return ok(data);
30548
30942
  };
30549
30943
 
30944
+ // packages/workit-opencode/src/shared/init-apply.ts
30945
+ import path24 from "node:path";
30946
+
30947
+ // packages/workit-opencode/src/shared/repo-result.ts
30948
+ var output = (value) => JSON.stringify(value, null, 2);
30949
+ var diagnostics = ({ stdout, stderr, exitCode }) => ({
30950
+ stdout,
30951
+ stderr,
30952
+ exitCode
30953
+ });
30954
+ var requireConfirmed = (confirmed) => {
30955
+ if (confirmed === true)
30956
+ return null;
30957
+ return output(fail("confirmed: true required"));
30958
+ };
30959
+ function scriptResult(result, parse) {
30960
+ if (result.exitCode !== 0) {
30961
+ return fail(result.stderr.trim() || result.stdout.trim() || "workflow script failed", diagnostics(result));
30962
+ }
30963
+ try {
30964
+ return ok({
30965
+ ...parse(result.stdout),
30966
+ exitCode: 0,
30967
+ ...result.stderr ? { stderr: result.stderr } : {}
30968
+ });
30969
+ } catch (error) {
30970
+ return fail(error instanceof Error ? error.message : "workflow output parse failed", diagnostics(result));
30971
+ }
30972
+ }
30973
+ var legacyScriptResult = (result) => {
30974
+ let parsed = null;
30975
+ try {
30976
+ parsed = JSON.parse(result.stdout.trim());
30977
+ } catch {}
30978
+ if (result.exitCode !== 0 || parsed?.error || parsed?.ok === false) {
30979
+ const error = parsed?.error ? String(parsed.error) : parsed?.ok === false ? "legacy operation reported failure" : result.stderr.trim() || result.stdout.trim() || "workflow script failed";
30980
+ return fail(error, diagnostics(result));
30981
+ }
30982
+ if (!parsed)
30983
+ return fail("workflow output parse failed", diagnostics(result));
30984
+ const { ok: _legacyOk, ...data } = parsed;
30985
+ return ok({ ...data, exitCode: 0, ...result.stderr ? { stderr: result.stderr } : {} });
30986
+ };
30987
+
30988
+ // packages/workit-opencode/src/shared/init-apply.ts
30989
+ var initApplyRuntime = {
30990
+ initApply: (root, action, env) => {
30991
+ const out = initApply({ action, confirmed: true, env });
30992
+ return {
30993
+ exitCode: out.error ? 1 : 0,
30994
+ stdout: JSON.stringify(out.data ?? out),
30995
+ stderr: "",
30996
+ cwd: root
30997
+ };
30998
+ }
30999
+ };
31000
+ var executeInitApply = (args, directory, runtime) => {
31001
+ const rejected = requireConfirmed(args.confirmed);
31002
+ if (rejected)
31003
+ return rejected;
31004
+ const action = args.action;
31005
+ if (action === "hygiene") {
31006
+ const result = ensureHygieneFiles(directory, {
31007
+ confirmed: args.confirmed,
31008
+ includeOpenSource: args.include_open_source
31009
+ });
31010
+ return output(result.ok ? ok(result) : fail(result.error));
31011
+ }
31012
+ if (action === "gitignore") {
31013
+ const result = ensureProjectGitignore(directory, args.confirmed);
31014
+ return output(result.ok ? ok(result) : fail(result.error));
31015
+ }
31016
+ if (action === "config") {
31017
+ const LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
31018
+ if (args.locale !== undefined && !LOCALE_RE.test(args.locale)) {
31019
+ return output(fail(`invalid locale: ${JSON.stringify(args.locale)} — expected BCP-47 like en or es-CL`));
31020
+ }
31021
+ const current = readConfig();
31022
+ const next = mergeConfigValues({
31023
+ locale: args.locale,
31024
+ localeOptions: args.locale_options,
31025
+ timezone: args.timezone,
31026
+ preset: args.branch_policy_preset,
31027
+ allowed: args.branch_policy_allowed,
31028
+ protectedNames: args.branch_policy_protected
31029
+ }, current);
31030
+ writeConfig(next);
31031
+ return output(ok({ action: "config", path: path24.join(configDir(), "config.json"), ...next }));
31032
+ }
31033
+ const env = Object.fromEntries(Object.entries({
31034
+ WORKFLOW_YT_BASE_URL: args.base_url,
31035
+ WORKFLOW_YT_MENTION: args.default_mention,
31036
+ WORKFLOW_YT_MEETING_ISSUE: args.meeting_issue,
31037
+ WORKFLOW_VCS_PROVIDER: args.vcs_provider,
31038
+ WORKFLOW_VCS_TARGET_BRANCH: args.vcs_target_branch,
31039
+ WORKFLOW_WORKSPACE_ROOT: directory,
31040
+ WORKFLOW_BP_NAME: args.name,
31041
+ WORKFLOW_BP_DEVELOP: args.develop_branch,
31042
+ WORKFLOW_BP_INTEGRATION: args.integration
31043
+ }).filter((entry) => entry[1] !== undefined));
31044
+ return output(legacyScriptResult(runtime.initApply(directory, action, env)));
31045
+ };
31046
+
30550
31047
  // packages/workit-opencode/src/tools/repo.ts
30551
31048
  var defaultRuntime = {
30552
31049
  git: (root, args) => run2(root, "git", args),
@@ -30564,15 +31061,7 @@ var defaultRuntime = {
30564
31061
  cwd: root
30565
31062
  };
30566
31063
  },
30567
- initApply: (root, action, env) => {
30568
- const out = initApply({ action, confirmed: true, env });
30569
- return {
30570
- exitCode: out.error ? 1 : 0,
30571
- stdout: JSON.stringify(out.data ?? out),
30572
- stderr: "",
30573
- cwd: root
30574
- };
30575
- },
31064
+ ...initApplyRuntime,
30576
31065
  initStatus: (root) => ({
30577
31066
  exitCode: 0,
30578
31067
  stdout: JSON.stringify(initStatusData()),
@@ -30586,36 +31075,7 @@ var defaultRuntime = {
30586
31075
  cwd: root
30587
31076
  })
30588
31077
  };
30589
- var output = (value) => JSON.stringify(value, null, 2);
30590
- var diagnostics = ({ stdout, stderr, exitCode }) => ({ stdout, stderr, exitCode });
30591
- var requireConfirmed = (confirmed) => confirmed === true ? null : output(fail("confirmed: true required"));
30592
- function scriptResult(result, parse) {
30593
- if (result.exitCode !== 0) {
30594
- return fail(result.stderr.trim() || result.stdout.trim() || "workflow script failed", diagnostics(result));
30595
- }
30596
- try {
30597
- return ok({
30598
- ...parse(result.stdout),
30599
- exitCode: 0,
30600
- ...result.stderr ? { stderr: result.stderr } : {}
30601
- });
30602
- } catch (error) {
30603
- return fail(error instanceof Error ? error.message : "workflow output parse failed", diagnostics(result));
30604
- }
30605
- }
30606
31078
  var json2 = (stdout) => JSON.parse(stdout.trim());
30607
- var legacyScriptResult = (result) => {
30608
- let parsed = null;
30609
- try {
30610
- parsed = json2(result.stdout);
30611
- } catch {}
30612
- if (result.exitCode !== 0 || parsed?.error || parsed?.ok === false)
30613
- return fail(parsed?.error ? String(parsed.error) : parsed?.ok === false ? "legacy operation reported failure" : result.stderr.trim() || result.stdout.trim() || "workflow script failed", diagnostics(result));
30614
- if (!parsed)
30615
- return fail("workflow output parse failed", diagnostics(result));
30616
- const { ok: _legacyOk, ...data } = parsed;
30617
- return ok({ ...data, exitCode: 0, ...result.stderr ? { stderr: result.stderr } : {} });
30618
- };
30619
31079
  var optionalJson = (value) => {
30620
31080
  if (!value?.trim())
30621
31081
  return null;
@@ -30770,7 +31230,7 @@ function createRepoTools(runtime = defaultRuntime) {
30770
31230
  entries,
30771
31231
  path: changelogPath,
30772
31232
  normalize_only,
30773
- workspace_root: realpathSync8(context.directory)
31233
+ workspace_root: realpathSync9(context.directory)
30774
31234
  })));
30775
31235
  }
30776
31236
  }),
@@ -30891,77 +31351,11 @@ function createRepoTools(runtime = defaultRuntime) {
30891
31351
  branch_policy_protected: tool.schema.array(tool.schema.string()).optional(),
30892
31352
  include_open_source: tool.schema.boolean().optional()
30893
31353
  },
30894
- execute: async ({
30895
- confirmed,
30896
- action,
30897
- base_url,
30898
- default_mention,
30899
- meeting_issue,
30900
- vcs_provider,
30901
- vcs_target_branch,
30902
- name,
30903
- develop_branch,
30904
- integration,
30905
- locale,
30906
- locale_options,
30907
- timezone,
30908
- branch_policy_preset,
30909
- branch_policy_allowed,
30910
- branch_policy_protected,
30911
- include_open_source
30912
- }, context) => {
30913
- const rejected = requireConfirmed(confirmed);
30914
- if (rejected)
30915
- return rejected;
30916
- if (action === "hygiene") {
30917
- const result = ensureHygieneFiles(context.directory, {
30918
- confirmed,
30919
- includeOpenSource: include_open_source
30920
- });
30921
- return output(result.ok ? ok(result) : fail(result.error));
30922
- }
30923
- if (action === "gitignore") {
30924
- const result = ensureProjectGitignore(context.directory, confirmed);
30925
- return output(result.ok ? ok(result) : fail(result.error));
30926
- }
30927
- if (action === "config") {
30928
- const LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
30929
- if (locale !== undefined && !LOCALE_RE.test(locale)) {
30930
- return output(fail(`invalid locale: ${JSON.stringify(locale)} — expected BCP-47 like en or es-CL`));
30931
- }
30932
- const current = readConfig();
30933
- const next = mergeConfigValues({
30934
- locale,
30935
- localeOptions: locale_options,
30936
- timezone,
30937
- preset: branch_policy_preset,
30938
- allowed: branch_policy_allowed,
30939
- protectedNames: branch_policy_protected
30940
- }, current);
30941
- writeConfig(next);
30942
- return output(ok({ action: "config", path: path22.join(configDir(), "config.json"), ...next }));
30943
- }
30944
- const env = Object.fromEntries(Object.entries({
30945
- WORKFLOW_YT_BASE_URL: base_url,
30946
- WORKFLOW_YT_MENTION: default_mention,
30947
- WORKFLOW_YT_MEETING_ISSUE: meeting_issue,
30948
- WORKFLOW_VCS_PROVIDER: vcs_provider,
30949
- WORKFLOW_VCS_TARGET_BRANCH: vcs_target_branch,
30950
- WORKFLOW_WORKSPACE_ROOT: context.directory,
30951
- WORKFLOW_BP_NAME: name,
30952
- WORKFLOW_BP_DEVELOP: develop_branch,
30953
- WORKFLOW_BP_INTEGRATION: integration
30954
- }).filter((entry) => entry[1] !== undefined));
30955
- return output(legacyScriptResult(runtime.initApply(context.directory, action, env)));
30956
- }
31354
+ execute: async (args, context) => executeInitApply(args, context.directory, runtime)
30957
31355
  })
30958
31356
  };
30959
31357
  }
30960
31358
 
30961
- // packages/workit-opencode/src/tools/workit.ts
30962
- import { realpathSync as realpathSync9 } from "node:fs";
30963
- import path24 from "node:path";
30964
-
30965
31359
  // packages/workit-core/src/core/tracker-issues.ts
30966
31360
  import fs12 from "node:fs";
30967
31361
  import { spawnSync as spawnSync9 } from "node:child_process";
@@ -31233,7 +31627,7 @@ async function logTimeUpdate(input, operation = defaultOperations) {
31233
31627
 
31234
31628
  // packages/workit-core/src/core/external-action-effects.ts
31235
31629
  import fs13 from "node:fs";
31236
- import path23 from "node:path";
31630
+ import path25 from "node:path";
31237
31631
 
31238
31632
  // packages/workit-core/src/core/commit-flavors.ts
31239
31633
  var CONVENTIONAL_RE = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?(!)?: .+/;
@@ -31423,7 +31817,7 @@ var readExternalContext = async (root, payload) => {
31423
31817
  }
31424
31818
  }
31425
31819
  };
31426
- var localAction = (operation) => operation === "git.branch_setup" || operation === "git.commit" || operation === "git.push" || operation === "hosting.pull_request" || operation === "changelog.apply";
31820
+ var localAction = (operation) => operation === "git.branch_setup" || operation === "git.commit" || operation === "git.push" || operation === "hosting.pull_request" || operation === "hosting.merge" || operation === "changelog.apply";
31427
31821
  var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
31428
31822
  const store = new TaskStore(root);
31429
31823
  const listed = store.listTasks();
@@ -31434,7 +31828,7 @@ var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
31434
31828
  return workspace;
31435
31829
  if (!workspace.data)
31436
31830
  return failure("not_found", "workspace not found");
31437
- 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);
31831
+ const candidates = listed.data.filter((task) => currentWriterOwnsTask(task, workspace.data, caller));
31438
31832
  if (candidates.length !== 1)
31439
31833
  return failure("permission_denied", "local external action requires exactly one active task");
31440
31834
  const allowed = assertProductWriteAllowed({
@@ -31444,7 +31838,7 @@ var assertLocalExternalActionWriter = (root, caller, paths = ["."]) => {
31444
31838
  paths,
31445
31839
  store
31446
31840
  });
31447
- return allowed.ok ? success(null, null, null) : allowed;
31841
+ return allowed;
31448
31842
  };
31449
31843
  var safePath = (root, value) => {
31450
31844
  try {
@@ -31456,13 +31850,20 @@ var safePath = (root, value) => {
31456
31850
  var branchCreationBaseline = (root, target, base) => {
31457
31851
  const exists = gitValue(root, ["rev-parse", "--verify", "--quiet", `refs/heads/${target}`]) !== null;
31458
31852
  if (exists)
31459
- return { base_branch: base, target_exists: true, remote_base: null };
31853
+ return {
31854
+ base_branch: base,
31855
+ target_exists: true,
31856
+ remote_base: null,
31857
+ target_head: gitValue(root, ["rev-parse", `refs/heads/${target}`])
31858
+ };
31460
31859
  const remote = run4(root, ["ls-remote", "origin", `refs/heads/${base}`]);
31461
31860
  const sha = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : "";
31861
+ const remoteBase = /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null;
31462
31862
  return {
31463
31863
  base_branch: base,
31464
31864
  target_exists: false,
31465
- remote_base: /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null
31865
+ remote_base: remoteBase,
31866
+ target_head: remoteBase
31466
31867
  };
31467
31868
  };
31468
31869
  var shortSha = (value) => typeof value === "string" && value ? value.slice(0, 8) : "unknown";
@@ -31573,7 +31974,7 @@ var actionProposalQuestion = (request, descriptorPayload) => {
31573
31974
  var approvedResolvedExternalAction = (content) => {
31574
31975
  try {
31575
31976
  const value = JSON.parse(content);
31576
- 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")
31977
+ 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")
31577
31978
  return failure("invalid_input", "approved action is not a reconciliable external action");
31578
31979
  if (!value.payload || typeof value.payload !== "object")
31579
31980
  return failure("invalid_input", "approved action payload is incomplete");
@@ -31584,18 +31985,24 @@ var approvedResolvedExternalAction = (content) => {
31584
31985
  if (!request.ok || !resolved || typeof resolved !== "object")
31585
31986
  return failure("invalid_input", "approved hosting target is incomplete");
31586
31987
  const target = resolved;
31988
+ if (value.operation === "git.commit" && (typeof target.head !== "string" || typeof target.staged !== "string" || typeof payload.message !== "string"))
31989
+ return failure("invalid_input", "approved Git commit target is incomplete");
31990
+ if (value.operation === "git.push" && (typeof target.branch !== "string" || typeof target.commit !== "string" || typeof target.remote !== "string"))
31991
+ return failure("invalid_input", "approved Git push target is incomplete");
31992
+ 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")))
31993
+ return failure("invalid_input", "approved Git branch target is incomplete");
31587
31994
  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"))
31588
31995
  return failure("invalid_input", "approved hosting target is incomplete");
31589
31996
  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"))
31590
31997
  return failure("invalid_input", "approved hosting target is incomplete");
31591
31998
  if (value.operation === "changelog.apply" && (typeof target.target !== "string" || typeof target.beforeDigest !== "string" || typeof target.beforeExists !== "boolean" || typeof target.afterDigest !== "string"))
31592
31999
  return failure("invalid_input", "approved changelog target is incomplete");
31593
- 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)))
32000
+ 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)))
31594
32001
  return failure("invalid_input", "approved YouTrack target is incomplete");
31595
32002
  return success(null, null, {
31596
32003
  request: request.data,
31597
32004
  descriptorPayload: value.payload,
31598
- marker: String(target.marker)
32005
+ ...typeof target.marker === "string" ? { marker: target.marker } : {}
31599
32006
  });
31600
32007
  } catch {
31601
32008
  return failure("invalid_input", "approved action descriptor is invalid");
@@ -31621,12 +32028,13 @@ var unknownHostingEvidence = (operation) => failure("external_outcome_unknown",
31621
32028
  outcome: "unknown"
31622
32029
  });
31623
32030
  var readHostingAction = async (root, resolved, actionRef) => {
31624
- if (resolved.request.operation !== "hosting.pull_request" || !resolved.marker)
31625
- return unknownHostingEvidence("hosting.pull_request");
32031
+ const operation = resolved.request.operation;
32032
+ if (operation !== "hosting.pull_request" && operation !== "hosting.merge" || !resolved.marker)
32033
+ return unknownHostingEvidence(operation);
31626
32034
  try {
31627
32035
  const cfg = vcsConfig("load", root);
31628
32036
  if (!cfg.ok || !cfg.tokenPath || !cfg.provider || cfg.provider !== "github" && cfg.provider !== "gitlab")
31629
- return unknownHostingEvidence("hosting.pull_request");
32037
+ return unknownHostingEvidence(operation);
31630
32038
  const remote = pushRemote(root);
31631
32039
  const repo = remote ? remoteRepo(remote) : null;
31632
32040
  const approved = resolved.descriptorPayload;
@@ -31645,7 +32053,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
31645
32053
  }
31646
32054
  })();
31647
32055
  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)
31648
- return unknownHostingEvidence("hosting.pull_request");
32056
+ return unknownHostingEvidence(operation);
31649
32057
  const token = fs13.readFileSync(cfg.tokenPath, "utf8").trim();
31650
32058
  const headers = { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json" };
31651
32059
  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`;
@@ -31653,14 +32061,14 @@ var readHostingAction = async (root, resolved, actionRef) => {
31653
32061
  headers: cfg.provider === "gitlab" ? { "PRIVATE-TOKEN": token } : headers
31654
32062
  });
31655
32063
  if (!response.ok)
31656
- return unknownHostingEvidence("hosting.pull_request");
32064
+ return unknownHostingEvidence(operation);
31657
32065
  const values = await response.json();
31658
32066
  if (!Array.isArray(values) || values.length >= 100)
31659
- return unknownHostingEvidence("hosting.pull_request");
32067
+ return unknownHostingEvidence(operation);
31660
32068
  const records = [];
31661
32069
  for (const item of values) {
31662
32070
  if (!item || typeof item !== "object" || Array.isArray(item))
31663
- return unknownHostingEvidence("hosting.pull_request");
32071
+ return unknownHostingEvidence(operation);
31664
32072
  const record = item;
31665
32073
  const body = record.body ?? record.description;
31666
32074
  const base = record.base;
@@ -31672,7 +32080,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
31672
32080
  const shaValue = cfg.provider === "github" && githubShape ? head.sha : record.sha;
31673
32081
  const idValue = record.number ?? record.iid ?? record.id;
31674
32082
  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")
31675
- return unknownHostingEvidence("hosting.pull_request");
32083
+ return unknownHostingEvidence(operation);
31676
32084
  records.push(record);
31677
32085
  }
31678
32086
  const matches = records.filter((item) => {
@@ -31682,15 +32090,23 @@ var readHostingAction = async (root, resolved, actionRef) => {
31682
32090
  const itemTarget = cfg.provider === "github" ? base?.ref : item.target_branch;
31683
32091
  const itemSource = cfg.provider === "github" ? head?.ref : item.source_branch;
31684
32092
  const itemSha = cfg.provider === "github" ? head?.sha : item.sha;
31685
- return body.includes(resolved.marker) && itemTarget === target && itemSource === branch && itemSha === sourceCommit;
32093
+ const merged = cfg.provider === "github" ? typeof item.merged_at === "string" : item.state === "merged" || typeof item.merged_at === "string";
32094
+ return (operation === "hosting.merge" ? merged : body.includes(resolved.marker)) && itemTarget === target && itemSource === branch && itemSha === sourceCommit;
31686
32095
  });
32096
+ if (operation === "hosting.merge" && records.some((item) => {
32097
+ const base = item.base;
32098
+ const head = item.head;
32099
+ 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");
32100
+ }))
32101
+ return unknownHostingEvidence(operation);
31687
32102
  if (matches.length !== 1)
31688
- return unknownHostingEvidence("hosting.pull_request");
32103
+ return unknownHostingEvidence(operation);
31689
32104
  const item = matches[0];
31690
32105
  const id = item.number ?? item.iid ?? item.id;
31691
32106
  if (typeof id !== "string" && typeof id !== "number")
31692
- return unknownHostingEvidence("hosting.pull_request");
32107
+ return unknownHostingEvidence(operation);
31693
32108
  const evidenceDigest = sha256({
32109
+ operation,
31694
32110
  provider: cfg.provider,
31695
32111
  id,
31696
32112
  marker: resolved.marker,
@@ -31711,7 +32127,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
31711
32127
  data: { provider: cfg.provider, id }
31712
32128
  });
31713
32129
  } catch {
31714
- return unknownHostingEvidence("hosting.pull_request");
32130
+ return unknownHostingEvidence(operation);
31715
32131
  }
31716
32132
  };
31717
32133
  var appendMarker = (text, marker) => `${text}${text ? `
@@ -31801,7 +32217,70 @@ var readYouTrackAction = async (root, resolved, actionRef) => {
31801
32217
  return unknownHostingEvidence(operation);
31802
32218
  }
31803
32219
  };
31804
- var readExternalAction = async (root, resolved, actionRef) => resolved.request.operation === "hosting.pull_request" ? readHostingAction(root, resolved, actionRef) : readYouTrackAction(root, resolved, actionRef);
32220
+ var readLocalAction = async (root, resolved, actionRef) => {
32221
+ const operation = resolved.request.operation;
32222
+ const target = resolved.descriptorPayload.resolved;
32223
+ if (!target)
32224
+ return unknownHostingEvidence(operation);
32225
+ let observed = null;
32226
+ if (operation === "git.commit") {
32227
+ const commit = gitValue(root, ["rev-parse", "HEAD"]);
32228
+ const parent = commit ? gitValue(root, ["rev-parse", `${commit}^`]) : null;
32229
+ const message = commit ? gitValue(root, ["show", "-s", "--format=%B", commit]) : null;
32230
+ const diff = commit && parent ? gitRaw(root, ["diff", "--raw", "-z", "--no-ext-diff", "--no-textconv", parent, commit]) : null;
32231
+ if (commit && parent === target.head && message === resolved.request.payload.message && diff !== null && sha256(diff) === target.staged)
32232
+ observed = { commit, parent, message, staged: target.staged };
32233
+ } else if (operation === "git.push") {
32234
+ const branch = target.branch;
32235
+ const commit = target.commit;
32236
+ const approvedRemote = target.remote;
32237
+ const currentRemote = pushRemote(root);
32238
+ const currentIdentity = currentRemote ? credentialFreeRemote(currentRemote) : null;
32239
+ if (typeof branch === "string" && typeof commit === "string" && typeof approvedRemote === "string" && currentIdentity === approvedRemote) {
32240
+ const remote = run4(root, ["ls-remote", currentRemote, `refs/heads/${branch}`]);
32241
+ const remoteCommit = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : null;
32242
+ if (remoteCommit === commit)
32243
+ observed = { branch, commit, remote: approvedRemote };
32244
+ }
32245
+ } else if (operation === "git.branch_setup") {
32246
+ const targetBranch = resolved.request.payload.target_branch;
32247
+ const sdd = resolved.request.payload.sdd_dir;
32248
+ if (resolved.request.payload.action === "reapply_stash" && typeof sdd === "string") {
32249
+ try {
32250
+ const manifest = JSON.parse(fs13.readFileSync(path25.join(sdd, "manifest.json"), "utf8"));
32251
+ const stashes = gitRaw(root, ["stash", "list", "--format=%H"]);
32252
+ if (typeof target.stash_commit === "string" && manifest.stash_ref === undefined && stashes !== null && !stashes.split(`
32253
+ `).includes(target.stash_commit))
32254
+ observed = { stash_commit: target.stash_commit };
32255
+ } catch {}
32256
+ } 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) {
32257
+ try {
32258
+ const manifest = JSON.parse(fs13.readFileSync(path25.join(sdd, "manifest.json"), "utf8"));
32259
+ if (manifest.branch === targetBranch)
32260
+ observed = { branch: targetBranch };
32261
+ } catch {}
32262
+ }
32263
+ } else if (operation === "changelog.apply") {
32264
+ const afterDigest = target.afterDigest;
32265
+ const preview = changelogApplyPreview({ ...resolved.request.payload, workspace_root: root });
32266
+ if (!preview.error && typeof afterDigest === "string" && preview.beforeDigest === afterDigest)
32267
+ observed = { target: target.target, afterDigest };
32268
+ }
32269
+ if (!observed)
32270
+ return unknownHostingEvidence(operation);
32271
+ const evidenceDigest = sha256({ operation, observed });
32272
+ return success(null, null, {
32273
+ outcome: "succeeded",
32274
+ evidenceDigest,
32275
+ observation: {
32276
+ kind: "provider_read",
32277
+ actionRef,
32278
+ outcome: "succeeded",
32279
+ evidenceDigest
32280
+ }
32281
+ });
32282
+ };
32283
+ 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);
31805
32284
  var youTrackBase = () => {
31806
32285
  try {
31807
32286
  const config = JSON.parse(fs13.readFileSync(youTrackConfigPath(), "utf8"));
@@ -31859,6 +32338,16 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
31859
32338
  return failure("storage_error", "current Git commit could not be resolved");
31860
32339
  const dirty = Boolean((gitRaw(root, ["status", "--short"]) ?? "").trim());
31861
32340
  const creation = action === "reapply_stash" || !target_branch ? { base_branch: policy.defaultTargetBranch } : branchCreationBaseline(root, target_branch, policy.defaultTargetBranch);
32341
+ let stashTarget;
32342
+ if (action === "reapply_stash") {
32343
+ try {
32344
+ const manifest = JSON.parse(fs13.readFileSync(path25.join(sdd_dir, "manifest.json"), "utf8"));
32345
+ const stash_ref = typeof manifest.stash_ref === "string" ? manifest.stash_ref : "";
32346
+ const stash_commit = stash_ref ? gitValue(root, ["rev-parse", stash_ref]) : null;
32347
+ if (stash_ref && stash_commit)
32348
+ stashTarget = { stash_ref, stash_commit };
32349
+ } catch {}
32350
+ }
31862
32351
  const normalized = {
31863
32352
  operation: request.operation,
31864
32353
  payload: {
@@ -31873,7 +32362,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
31873
32362
  request: normalized,
31874
32363
  descriptorPayload: {
31875
32364
  ...normalized.payload,
31876
- resolved: { head, dirty, ...creation }
32365
+ resolved: { head, dirty, ...creation, ...stashTarget }
31877
32366
  }
31878
32367
  });
31879
32368
  }
@@ -32025,7 +32514,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
32025
32514
  outcome: "not_started",
32026
32515
  fields: [{ path: "source_branch", reason: "protected branch" }]
32027
32516
  });
32028
- const source_commit = gitValue(root, ["rev-parse", "HEAD"]);
32517
+ const source_commit = source_branch ? gitValue(root, ["rev-parse", "--verify", `refs/heads/${source_branch}`]) : null;
32029
32518
  const remote = pushRemote(root);
32030
32519
  const identity = remote ? credentialFreeRemote(remote) : null;
32031
32520
  if (!source_branch || !source_commit || !identity || remoteProvider2(identity) !== null && remoteProvider2(identity) !== String(vcsConfig("load", root).provider).toLowerCase())
@@ -32060,7 +32549,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
32060
32549
  if (preview.error)
32061
32550
  return failure("invalid_input", preview.error);
32062
32551
  const target = String(preview.path);
32063
- const relative = path23.relative(fs13.realpathSync(root), target).replaceAll(path23.sep, "/") || "CHANGELOG.md";
32552
+ const relative = path25.relative(fs13.realpathSync(root), target).replaceAll(path25.sep, "/") || "CHANGELOG.md";
32064
32553
  const normalized = {
32065
32554
  operation: request.operation,
32066
32555
  payload: {
@@ -32313,27 +32802,34 @@ var executeResolvedExternalAction = async (resolved, root, step, caller) => {
32313
32802
  const workText = fresh.data.descriptorPayload.resolved?.workText;
32314
32803
  const beforeDigest = typeof resolvedDetails.resolved?.beforeDigest === "string" ? resolvedDetails.resolved.beforeDigest : undefined;
32315
32804
  const beforeExists = typeof resolvedDetails.resolved?.beforeExists === "boolean" ? resolvedDetails.resolved.beforeExists : undefined;
32316
- return executeConcreteExternalAction(fresh.data.request, root, fresh.data.marker, resolvedDate, step, typeof workText === "string" ? workText : undefined, caller, beforeDigest, beforeExists);
32805
+ const result = await executeConcreteExternalAction(fresh.data.request, root, fresh.data.marker, resolvedDate, step, typeof workText === "string" ? workText : undefined, caller, beforeDigest, beforeExists);
32806
+ if (!result.ok && result.code === "external_outcome_unknown" && caller) {
32807
+ const observed = await readLocalAction(root, fresh.data, {
32808
+ kind: "host",
32809
+ host: caller.host,
32810
+ handle: `local-recovery:${fresh.data.request.operation}`
32811
+ });
32812
+ if (observed.ok)
32813
+ return success(null, null, { recovered: true });
32814
+ }
32815
+ return result;
32317
32816
  };
32318
32817
 
32319
- // packages/workit-opencode/src/tools/workit.ts
32320
- var sessionParent = (session) => {
32321
- if (typeof session !== "object" || session === null)
32322
- return null;
32323
- if (!Object.prototype.hasOwnProperty.call(session, "parentID"))
32324
- return;
32325
- const parentID = session.parentID;
32326
- return typeof parentID === "string" && parentID.length > 0 ? parentID : null;
32327
- };
32328
- var rejectedDescription = "Reject this decision";
32329
- var isSelfAuthorizingActionContent = (content) => {
32818
+ // packages/workit-opencode/src/shared/session.ts
32819
+ import { realpathSync as realpathSync10 } from "node:fs";
32820
+ import path26 from "node:path";
32821
+ var sameWorkspace = (expected, observed) => {
32822
+ if (typeof observed !== "string" || !observed)
32823
+ return false;
32330
32824
  try {
32331
- const value = JSON.parse(content);
32332
- return typeof value.operation === "string" && value.operation.length > 0;
32825
+ return realpathSync10(expected) === realpathSync10(observed);
32333
32826
  } catch {
32334
- return /^[a-z][a-z_]*\.[a-z_]+$/.test(content);
32827
+ return path26.resolve(expected) === path26.resolve(observed);
32335
32828
  }
32336
32829
  };
32830
+
32831
+ // packages/workit-opencode/src/shared/decision-content.ts
32832
+ var rejectedDescription = "Reject this decision";
32337
32833
  var decisionContent = (purpose, question, approvedContent) => ({
32338
32834
  header: `Workit decision: ${purpose}`,
32339
32835
  question,
@@ -32342,6 +32838,24 @@ var decisionContent = (purpose, question, approvedContent) => ({
32342
32838
  { label: "rejected", description: rejectedDescription }
32343
32839
  ]
32344
32840
  });
32841
+ var isSelfAuthorizingActionContent = (content) => {
32842
+ try {
32843
+ const value = JSON.parse(content);
32844
+ return typeof value.operation === "string" && value.operation.length > 0;
32845
+ } catch {
32846
+ return /^[a-z][a-z_]*\.[a-z_]+$/.test(content);
32847
+ }
32848
+ };
32849
+
32850
+ // packages/workit-opencode/src/tools/workit.ts
32851
+ var sessionParent = (session) => {
32852
+ if (typeof session !== "object" || session === null)
32853
+ return null;
32854
+ if (!Object.prototype.hasOwnProperty.call(session, "parentID"))
32855
+ return;
32856
+ const parentID = session.parentID;
32857
+ return typeof parentID === "string" && parentID.length > 0 ? parentID : null;
32858
+ };
32345
32859
  var decisionOptions = (options) => Array.isArray(options) && options.length === 2 && options.every((option) => typeof option === "object" && option !== null && typeof option.label === "string" && typeof option.description === "string" && Object.keys(option).length === 2 && Object.keys(option).every((key) => key === "label" || key === "description")) ? options : null;
32346
32860
  var normalizeReceiptLabel = (label) => label.trim().replace(/(?:\s*[([]\s*[^()[\]]*\s*[)\]]\s*)+$/u, "").trim().toLowerCase();
32347
32861
  var purposeForQuestion = (question) => {
@@ -32356,6 +32870,7 @@ var purposeForQuestion = (question) => {
32356
32870
  class NativeReceiptStore {
32357
32871
  #receipts = new Map;
32358
32872
  #observations = new WeakSet;
32873
+ #reservations = new WeakMap;
32359
32874
  #pending = new Map;
32360
32875
  #now;
32361
32876
  constructor(options = {}) {
@@ -32428,7 +32943,7 @@ class NativeReceiptStore {
32428
32943
  const args = input.args;
32429
32944
  this.mint(input.sessionID, input.callID, args?.questions, answers);
32430
32945
  }
32431
- consume(sessionID, purpose, expected = {}) {
32946
+ reserve(sessionID, purpose, expected = {}) {
32432
32947
  const queue = this.#receipts.get(sessionID) ?? [];
32433
32948
  for (let i = queue.length - 1;i >= 0; i -= 1) {
32434
32949
  const receipt = queue[i];
@@ -32436,11 +32951,9 @@ class NativeReceiptStore {
32436
32951
  continue;
32437
32952
  if (expected.callID !== undefined && receipt.callID !== expected.callID || expected.selectedLabel !== undefined && normalizeReceiptLabel(receipt.selectedLabel) !== normalizeReceiptLabel(expected.selectedLabel) || expected.selectedDescription !== undefined && receipt.selectedDescription !== expected.selectedDescription || expected.decisionPurpose !== undefined && receipt.decisionPurpose !== expected.decisionPurpose || expected.contentDigest !== undefined && receipt.contentDigest !== expected.contentDigest || expected.question !== undefined && receipt.question !== expected.question)
32438
32953
  continue;
32439
- queue.splice(i, 1);
32440
- if (!queue.length)
32441
- this.#receipts.delete(sessionID);
32442
32954
  const observation = { receipt };
32443
32955
  this.#observations.add(observation);
32956
+ this.#reservations.set(observation, receipt);
32444
32957
  return { ok: true, receipt, observation };
32445
32958
  }
32446
32959
  return {
@@ -32466,6 +32979,26 @@ class NativeReceiptStore {
32466
32979
  })()
32467
32980
  };
32468
32981
  }
32982
+ commit(observation) {
32983
+ const receipt = this.#reservations.get(observation);
32984
+ this.#reservations.delete(observation);
32985
+ if (!receipt)
32986
+ return false;
32987
+ const queue = this.#receipts.get(receipt.sessionID) ?? [];
32988
+ const index = queue.indexOf(receipt);
32989
+ if (index < 0)
32990
+ return false;
32991
+ queue.splice(index, 1);
32992
+ if (!queue.length)
32993
+ this.#receipts.delete(receipt.sessionID);
32994
+ return true;
32995
+ }
32996
+ consume(sessionID, purpose, expected = {}) {
32997
+ const reserved = this.reserve(sessionID, purpose, expected);
32998
+ if (reserved.ok)
32999
+ this.commit(reserved.observation);
33000
+ return reserved;
33001
+ }
32469
33002
  verify(observation, sessionID, purpose) {
32470
33003
  if (typeof observation !== "object" || observation === null || !this.#observations.has(observation))
32471
33004
  return null;
@@ -32540,15 +33073,6 @@ var sessionData = async (client, sessionID) => {
32540
33073
  return null;
32541
33074
  }
32542
33075
  };
32543
- var sameWorkspace = (expected, observed) => {
32544
- if (typeof observed !== "string" || !observed)
32545
- return false;
32546
- try {
32547
- return realpathSync9(expected) === realpathSync9(observed);
32548
- } catch {
32549
- return path24.resolve(expected) === path24.resolve(observed);
32550
- }
32551
- };
32552
33076
  var hostRef = (handle) => ({ kind: "host", host: "opencode", handle });
32553
33077
  var opencodeCapabilities = () => [
32554
33078
  {
@@ -32717,6 +33241,10 @@ var nativeExternalActionRunner = (root, actor, core, step) => createAuthorizedEx
32717
33241
  settleObservation: (outcome, revisions) => nativeExternalActionObservation(actor, actionRef, outcome, actionRef.kind === "host" ? actionRef.handle : "external-action", revisions)
32718
33242
  };
32719
33243
  });
33244
+ var workerCoordinatorFor = (task, worker) => {
33245
+ const coordinator = worker.data.coordinator ?? task.intent.provenance.session;
33246
+ return coordinator?.kind === "host" && coordinator.host === "opencode" ? coordinator.handle : null;
33247
+ };
32720
33248
  var workerIdFor = (store, actor, current, directChildren) => {
32721
33249
  const parentID = sessionParent(current);
32722
33250
  if (!parentID)
@@ -32727,10 +33255,9 @@ var workerIdFor = (store, actor, current, directChildren) => {
32727
33255
  const matches = tasks.data.flatMap((task) => {
32728
33256
  if (task.status !== "active")
32729
33257
  return [];
32730
- const coordinator = task.intent.provenance.session;
32731
- if (coordinator?.kind !== "host" || coordinator.host !== "opencode" || coordinator.handle !== parentID || directChildren.get(actor) !== parentID)
33258
+ if (directChildren.get(actor) !== parentID)
32732
33259
  return [];
32733
- return task.workers.filter((worker) => worker.data.state === "running" && worker.data.session?.kind === "host" && worker.data.session.host === "opencode" && worker.data.session.handle === actor);
33260
+ return task.workers.filter((worker) => worker.data.state === "running" && worker.data.session?.kind === "host" && worker.data.session.host === "opencode" && worker.data.session.handle === actor && workerCoordinatorFor(task, worker) === parentID);
32734
33261
  });
32735
33262
  return matches.length === 1 ? matches[0].id : null;
32736
33263
  };
@@ -32790,10 +33317,11 @@ var createWorkitTools = ({
32790
33317
  };
32791
33318
  if (decision.response === "approved")
32792
33319
  expectation.selectedDescription = decision.binding.approvedContent;
32793
- const observed = receipts.consume(context.sessionID, "decision", expectation);
33320
+ const observed = receipts.reserve(context.sessionID, "decision", expectation);
32794
33321
  if (!observed.ok)
32795
33322
  return output2(failure("permission_denied", observed.error));
32796
33323
  let recordInput = parsed.data;
33324
+ let proposalToCommit;
32797
33325
  if (decision.purpose === "action" && decision.response === "approved") {
32798
33326
  const queue = actionProposals.get(context.sessionID) ?? [];
32799
33327
  const textMatches = queue.filter((pending) => pending.presented === decision.binding.presented && pending.approvedText === decision.binding.approvedContent);
@@ -32805,7 +33333,7 @@ var createWorkitTools = ({
32805
33333
  const fresh = resolveExternalActionRequest(context.directory, pending.request);
32806
33334
  const freshDescriptor = fresh.ok ? externalActionDescriptor(pending.request.operation, fresh.data.descriptorPayload) : null;
32807
33335
  if (freshDescriptor === pending.descriptor) {
32808
- actionProposals.set(context.sessionID, queue.filter((candidate) => candidate !== pending));
33336
+ proposalToCommit = pending;
32809
33337
  recordInput = {
32810
33338
  ...recordInput,
32811
33339
  binding: {
@@ -32824,6 +33352,13 @@ var createWorkitTools = ({
32824
33352
  }
32825
33353
  }
32826
33354
  result = core.observeDecision(recordInput, observed.observation);
33355
+ if (result.ok) {
33356
+ receipts.commit(observed.observation);
33357
+ if (proposalToCommit) {
33358
+ const queue = actionProposals.get(context.sessionID) ?? [];
33359
+ actionProposals.set(context.sessionID, queue.filter((candidate) => candidate !== proposalToCommit));
33360
+ }
33361
+ }
32827
33362
  } else {
32828
33363
  const run = core[family];
32829
33364
  result = run.call(core, parsed.data);
@@ -32879,7 +33414,7 @@ var createWorkitTools = ({
32879
33414
  return output2(failure("permission_denied", "child sessions cannot run external actions"));
32880
33415
  const store = new TaskStore(context.directory);
32881
33416
  const prior = priorExternalAction(store, "opencode", context.sessionID, resolved.data.request.operation, resolved.data.request.payload);
32882
- if (prior.ok && prior.data.entry.data.consumption !== null && prior.data.entry.data.consumption.state !== "uncertain")
33417
+ if (prior.ok && prior.data.entry.data.consumption !== null && prior.data.entry.data.consumption.state !== "uncertain" && resolved.data.request.operation !== "git.commit" && resolved.data.request.operation !== "git.push")
32883
33418
  return output2(failure("permission_denied", "external action was already settled"));
32884
33419
  const reconciliationTokens = new WeakSet;
32885
33420
  const core = new WorkitCore(store, {
@@ -32962,6 +33497,16 @@ var createWorkitTools = ({
32962
33497
  }
32963
33498
  }
32964
33499
  const autoApplies = !selected.ok && !planAuthorized && standingAutoApplies(store, "opencode", context.sessionID, descriptor);
33500
+ const planCount = planReservationLength(resolved.data.request.operation, resolved.data.descriptorPayload);
33501
+ if (planCount !== null) {
33502
+ if (selected.ok)
33503
+ return output2(success(null, null, { plan_commits: planCount }));
33504
+ if (autoApplies) {
33505
+ const bound = standingAutoBinding(core, store, "opencode", context.sessionID, descriptor);
33506
+ if (bound)
33507
+ return output2(success(null, null, { plan_commits: planCount }));
33508
+ }
33509
+ }
32965
33510
  if (!selected.ok && !planAuthorized && !autoApplies) {
32966
33511
  if (selected.error.startsWith("no approved action")) {
32967
33512
  const proposal = actionProposalQuestion(resolved.data.request, resolved.data.descriptorPayload);
@@ -33090,9 +33635,9 @@ var loadProvenance = (logger, pkgUrl) => {
33090
33635
  }
33091
33636
  };
33092
33637
 
33093
- // packages/workit-opencode/src/plugin.ts
33094
- var root = fileURLToPath3(new URL("../assets/", import.meta.url));
33095
- var skillsPath = path25.join(root, "skills");
33638
+ // packages/workit-opencode/src/v1/server.ts
33639
+ var root = assetsRoot();
33640
+ var skillsPath = path27.join(root, "skills");
33096
33641
  var logger = createLogger({
33097
33642
  appLog: () => {
33098
33643
  return;
@@ -33111,7 +33656,7 @@ var warnStaleSources = () => {
33111
33656
  logger.warn(EVENT.hooks, {
33112
33657
  boundary: "stale_sources",
33113
33658
  reason: "workit sources changed after plugin load; restart the session for latest behavior",
33114
- files: changed.map((file) => path25.basename(file))
33659
+ files: changed.map((file) => path27.basename(file))
33115
33660
  });
33116
33661
  } catch {}
33117
33662
  };
@@ -33140,7 +33685,7 @@ var plugin = async ({ client, directory }) => {
33140
33685
  const dispatches = new Map;
33141
33686
  try {
33142
33687
  logger.info(EVENT.initialization, { host: "opencode", plugin_root: root });
33143
- logger.info(EVENT.provenance, loadProvenance(logger, new URL("../package.json", import.meta.url)));
33688
+ logger.info(EVENT.provenance, loadProvenance(logger, path27.join(packageRoot2(), "package.json")));
33144
33689
  } catch (error) {
33145
33690
  logger.warn(EVENT.hooks, { boundary: "initialization", ...errorDetail(error) });
33146
33691
  }
@@ -33172,10 +33717,10 @@ var plugin = async ({ client, directory }) => {
33172
33717
  const listed = store.listTasks();
33173
33718
  if (!workspace.ok || !workspace.data || !listed.ok)
33174
33719
  return "unmanaged";
33175
- const owned = listed.data.filter((task) => task.status === "active" && task.workspaceId === workspace.data?.id && task.intent.provenance.session?.kind === "host" && task.intent.provenance.session.host === "opencode" && task.intent.provenance.session.handle === coordinator);
33176
- if (owned.length === 0)
33720
+ const managed = listed.data.filter((task) => task.status === "active" && task.workspaceId === workspace.data?.id && (task.intent.provenance.session?.kind === "host" && task.intent.provenance.session.host === "opencode" && task.intent.provenance.session.handle === coordinator || task.workers.some((entry) => entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === coordinator) || workspace.data.writer?.owner.taskId === task.id && workspace.data.writer.owner.session.host === "opencode" && workspace.data.writer.owner.session.handle === coordinator));
33721
+ if (managed.length === 0)
33177
33722
  return "unmanaged";
33178
- const eligible = owned.flatMap((task) => task.workers.filter((entry) => entry.data.state === "assigned" && entry.data.session === null && entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === coordinator).map((entry) => ({ task, entry })));
33723
+ const eligible = managed.flatMap((task) => task.workers.filter((entry) => entry.data.state === "assigned" && entry.data.session === null && entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === coordinator).map((entry) => ({ task, entry })));
33179
33724
  const next = oldestOfSingleTask(eligible);
33180
33725
  if (!next)
33181
33726
  return "unbound";
@@ -33259,7 +33804,7 @@ var plugin = async ({ client, directory }) => {
33259
33804
  dispatches.delete(coordinator);
33260
33805
  return committed.ok;
33261
33806
  };
33262
- const observeLifecycle = async (sessionID, parentID, state, binding, initial = false, eventInfo) => {
33807
+ const observeLifecycle = async (sessionID, parentID, state, binding, eventInfo, report) => {
33263
33808
  const store = new TaskStore(directory);
33264
33809
  const listed = store.listTasks();
33265
33810
  const workspace = store.readWorkspace();
@@ -33269,7 +33814,7 @@ var plugin = async ({ client, directory }) => {
33269
33814
  }
33270
33815
  const persisted = listed.data.flatMap((task) => task.status === "active" ? task.workers.filter((entry) => entry.id === binding?.workerId || entry.data.session?.kind === "host" && entry.data.session.handle === sessionID).map((entry) => ({ task, entry })) : []);
33271
33816
  const matches = persisted.filter(({ entry }) => entry.data.session?.kind === "host");
33272
- if (binding && !initial && matches.length !== 1) {
33817
+ if (binding && matches.length !== 1) {
33273
33818
  droppedLifecycle(sessionID, "bound child worker handle is ambiguous");
33274
33819
  return;
33275
33820
  }
@@ -33278,7 +33823,7 @@ var plugin = async ({ client, directory }) => {
33278
33823
  droppedLifecycle(sessionID, binding ? "bound worker is missing from persisted state" : "worker session is ambiguous");
33279
33824
  return;
33280
33825
  }
33281
- if (!initial && selected.entry.data.session?.kind !== "host") {
33826
+ if (selected.entry.data.session?.kind !== "host") {
33282
33827
  droppedLifecycle(sessionID, "selected worker has no bound host session");
33283
33828
  return;
33284
33829
  }
@@ -33286,25 +33831,26 @@ var plugin = async ({ client, directory }) => {
33286
33831
  droppedLifecycle(sessionID, "selected worker provenance is not host-observed");
33287
33832
  return;
33288
33833
  }
33289
- if (!binding) {
33290
- const coordinator = selected.task.intent.provenance.session;
33291
- if (coordinator?.kind !== "host" || coordinator.host !== "opencode") {
33292
- droppedLifecycle(sessionID, "coordinator session is not validated");
33834
+ const observed = eventInfo ?? await sessionData2(client, sessionID);
33835
+ if (binding && eventInfo) {
33836
+ if (!deletedSessionEnd(directory, sessionID, parentID, observed)) {
33837
+ droppedLifecycle(sessionID, "deleted session payload contradicts the binding");
33293
33838
  return;
33294
33839
  }
33295
- parentID = coordinator.handle;
33840
+ } else if (!trustedSession(directory, sessionID, observed)) {
33841
+ droppedLifecycle(sessionID, "live session observation is not trusted");
33842
+ return;
33296
33843
  }
33297
- if (!initial) {
33298
- const observed = eventInfo ?? await sessionData2(client, sessionID);
33299
- if (binding && eventInfo) {
33300
- if (!deletedSessionEnd(directory, sessionID, parentID, observed)) {
33301
- droppedLifecycle(sessionID, "deleted session payload contradicts the binding");
33302
- return;
33303
- }
33304
- } else if (!trustedSession(directory, sessionID, observed) || observed.parentID !== parentID) {
33305
- droppedLifecycle(sessionID, "live session observation is not trusted");
33844
+ if (!binding) {
33845
+ const coordinator = workerCoordinatorFor(selected.task, selected.entry);
33846
+ if (!coordinator || observed.parentID !== coordinator) {
33847
+ droppedLifecycle(sessionID, "live session parent contradicts persisted coordinator");
33306
33848
  return;
33307
33849
  }
33850
+ parentID = coordinator;
33851
+ } else if (!eventInfo && observed.parentID !== parentID) {
33852
+ droppedLifecycle(sessionID, "live session parent contradicts the binding");
33853
+ return;
33308
33854
  }
33309
33855
  directChildren.set(sessionID, parentID);
33310
33856
  const core = new WorkitCore(store, {
@@ -33322,6 +33868,7 @@ var plugin = async ({ client, directory }) => {
33322
33868
  expectedWorkspaceRevision: workspace.data.revision,
33323
33869
  state,
33324
33870
  session: { kind: "host", host: "opencode", handle: sessionID },
33871
+ ...report ? { report } : {},
33325
33872
  observation: { event: state, sessionID }
33326
33873
  });
33327
33874
  if (!result.ok) {
@@ -33337,25 +33884,9 @@ var plugin = async ({ client, directory }) => {
33337
33884
  const bindCreatedSession = async (info) => {
33338
33885
  if (typeof info.id !== "string" || typeof info.parentID !== "string" || !sameWorkspace(directory, info.directory))
33339
33886
  return;
33340
- const live = dispatches.get(info.parentID);
33341
- if (live)
33342
- live.generation.childCreated = true;
33343
- const store = new TaskStore(directory);
33344
- const workspace = store.readWorkspace();
33345
- const listed = store.listTasks();
33346
- if (!workspace.ok || !workspace.data || !listed.ok)
33347
- return;
33348
- const candidates = listed.data.flatMap((task) => task.status === "active" && task.workspaceId === workspace.data?.id ? task.workers.filter((entry) => (entry.data.state === "assigned" || entry.data.state === "dispatching") && entry.data.session === null && entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === info.parentID).map((entry) => ({ task, entry })) : []);
33349
- const next = oldestOfSingleTask(candidates);
33350
- if (!next)
33351
- return;
33352
33887
  directChildren.set(info.id, info.parentID);
33353
- if (live && live.taskId === next.task.id && live.workerId === next.entry.id && commitDispatchStart(info.parentID, info.id))
33354
- return;
33355
- await observeLifecycle(info.id, info.parentID, "running", {
33356
- taskId: next.task.id,
33357
- workerId: next.entry.id
33358
- }, true);
33888
+ if (dispatches.has(info.parentID))
33889
+ commitDispatchStart(info.parentID, info.id);
33359
33890
  };
33360
33891
  return {
33361
33892
  tool: tools,
@@ -33377,7 +33908,7 @@ var plugin = async ({ client, directory }) => {
33377
33908
  return;
33378
33909
  const binding = lifecycleBindings.get(sessionID);
33379
33910
  const state = event.type === "session.status" ? event.properties.status.type === "busy" ? "running" : event.properties.status.type === "idle" ? "stopped" : "unknown" : event.type === "session.idle" ? "stopped" : event.type === "session.deleted" ? "stopped" : "unknown";
33380
- await observeLifecycle(sessionID, binding?.parentID ?? "", state, binding, false, event.type === "session.deleted" ? properties.info : undefined);
33911
+ await observeLifecycle(sessionID, binding?.parentID ?? "", state, binding, event.type === "session.deleted" ? properties.info : undefined);
33381
33912
  },
33382
33913
  "tool.execute.after": async (input, output) => {
33383
33914
  observeQuestion(receipts, input, output);
@@ -33403,7 +33934,12 @@ var plugin = async ({ client, directory }) => {
33403
33934
  if (completedChild) {
33404
33935
  const completionBinding = lifecycleBindings.get(completedChild);
33405
33936
  if (completionBinding)
33406
- await observeLifecycle(completedChild, input.sessionID, "stopped", completionBinding, false, undefined);
33937
+ await observeLifecycle(completedChild, input.sessionID, "stopped", completionBinding, undefined, {
33938
+ outcome: "completed",
33939
+ summary: outputText.match(/<task_result>([\s\S]*?)<\/task_result>/)?.[1]?.trim() || outputText.trim() || "Native worker completed.",
33940
+ evidenceIds: [],
33941
+ findingIds: []
33942
+ });
33407
33943
  } else if (!resolved && /(?:cancel|interrupt|unknown|uncertain)/.test(state))
33408
33944
  unresolvedTaskLaunches.add(input.sessionID);
33409
33945
  }
@@ -33442,7 +33978,7 @@ var plugin = async ({ client, directory }) => {
33442
33978
  config: async (config) => {
33443
33979
  const mutable = config;
33444
33980
  const paths = [...mutable.skills?.paths ?? []];
33445
- if (existsSync13(skillsPath) && !paths.includes(skillsPath))
33981
+ if (existsSync14(skillsPath) && !paths.includes(skillsPath))
33446
33982
  paths.push(skillsPath);
33447
33983
  mutable.skills ??= {};
33448
33984
  mutable.skills.paths = paths;
@@ -33512,7 +34048,852 @@ var plugin = async ({ client, directory }) => {
33512
34048
  }
33513
34049
  };
33514
34050
  };
33515
- var plugin_default = plugin;
34051
+ var server_default = plugin;
34052
+
34053
+ // node_modules/@opencode/plugin/dist/promise/plugin.js
34054
+ function define(plugin) {
34055
+ return plugin;
34056
+ }
34057
+
34058
+ // packages/workit-opencode/src/shared/tools.ts
34059
+ var objectSchema = (properties, required) => ({
34060
+ type: "object",
34061
+ properties,
34062
+ required,
34063
+ additionalProperties: false
34064
+ });
34065
+ var WORKIT_EXTERNAL_ACTION_OPERATIONS = [
34066
+ "git.branch_setup",
34067
+ "git.commit",
34068
+ "git.push",
34069
+ "hosting.pull_request",
34070
+ "hosting.merge",
34071
+ "youtrack.update",
34072
+ "youtrack.time",
34073
+ "youtrack.meeting",
34074
+ "changelog.apply",
34075
+ "context.read"
34076
+ ];
34077
+ var familyTools = OPERATION_FAMILIES.map((family) => ({
34078
+ name: `workit_${family}`,
34079
+ description: `Workit ${family} operations backed by the shared task contract.`,
34080
+ input: { type: "object", ...boundedOperationJsonSchema(family, OPERATION_SCHEMA_DEPTH) }
34081
+ }));
34082
+ var WORKIT_TOOL_CATALOG = [
34083
+ ...familyTools,
34084
+ {
34085
+ name: "workit_external_action",
34086
+ description: `Run one fixed optional action. ${externalActionHelp}`,
34087
+ input: objectSchema({
34088
+ operation: {
34089
+ type: "string",
34090
+ enum: [...WORKIT_EXTERNAL_ACTION_OPERATIONS],
34091
+ description: "Fixed optional action to run."
34092
+ },
34093
+ payload: {
34094
+ type: "object",
34095
+ description: "Operation-specific payload; Workit validates it.",
34096
+ additionalProperties: true
34097
+ }
34098
+ }, ["operation", "payload"])
34099
+ },
34100
+ {
34101
+ name: "workit_init_apply",
34102
+ description: "Apply a confirmed toolkit initialization action",
34103
+ input: objectSchema({
34104
+ confirmed: { type: "boolean" },
34105
+ action: {
34106
+ type: "string",
34107
+ enum: [
34108
+ "youtrack_scaffold",
34109
+ "youtrack_json",
34110
+ "youtrack_token_placeholder",
34111
+ "vcs_scaffold",
34112
+ "config",
34113
+ "gitignore",
34114
+ "hygiene",
34115
+ "branch_policy"
34116
+ ]
34117
+ },
34118
+ base_url: { type: "string" },
34119
+ default_mention: { type: "string" },
34120
+ meeting_issue: { type: "string" },
34121
+ vcs_provider: { type: "string", enum: ["gitlab", "github"] },
34122
+ vcs_target_branch: { type: "string" },
34123
+ name: { type: "string" },
34124
+ develop_branch: { type: "string" },
34125
+ integration: { type: "string", enum: ["pr", "merge"] },
34126
+ locale: { type: "string" },
34127
+ locale_options: { type: "array", items: { type: "string" } },
34128
+ timezone: { type: "string" },
34129
+ branch_policy_preset: {
34130
+ type: "string",
34131
+ enum: ["gitflow", "github-flow", "trunk-based", "custom"]
34132
+ },
34133
+ branch_policy_allowed: { type: "array", items: { type: "string" } },
34134
+ branch_policy_protected: { type: "array", items: { type: "string" } },
34135
+ include_open_source: { type: "boolean" }
34136
+ }, ["confirmed", "action"])
34137
+ }
34138
+ ];
34139
+ var workitFamilyOf = (toolName) => {
34140
+ for (const family of OPERATION_FAMILIES)
34141
+ if (toolName === `workit_${family}`)
34142
+ return family;
34143
+ return null;
34144
+ };
34145
+
34146
+ // packages/workit-opencode/src/v2/lifecycle.ts
34147
+ var record3 = (value) => typeof value === "object" && value !== null ? value : {};
34148
+ var sessionIDOf = (value) => typeof value === "string" && value.length > 0 ? value : null;
34149
+ var parentOf = (session) => typeof session?.parentID === "string" && session.parentID ? session.parentID : undefined;
34150
+ var contentText = (content) => {
34151
+ if (typeof content === "string")
34152
+ return content;
34153
+ if (!Array.isArray(content))
34154
+ return "";
34155
+ return content.map((part) => record3(part).type === "text" ? String(part.text ?? "") : "").join(`
34156
+ `);
34157
+ };
34158
+ var createV2Lifecycle = (deps) => {
34159
+ const { root } = deps;
34160
+ const directChildren = new Map;
34161
+ const dispatches = new Map;
34162
+ const inflight = new Map;
34163
+ const lifecycleBindings = new Map;
34164
+ const unresolvedTaskLaunches = new Set;
34165
+ const now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
34166
+ const readSession = async (sessionID) => {
34167
+ try {
34168
+ const info = await deps.getSession(sessionID);
34169
+ if (!info || typeof info !== "object" || info.id !== sessionID)
34170
+ return null;
34171
+ return info;
34172
+ } catch {
34173
+ return null;
34174
+ }
34175
+ };
34176
+ const trustedLead = async (coordinator) => {
34177
+ const session = await readSession(coordinator);
34178
+ if (!session || parentOf(session) !== undefined)
34179
+ return null;
34180
+ if (!sameWorkspace(root, session.directory))
34181
+ return null;
34182
+ return session;
34183
+ };
34184
+ const managedByCoordinator = (task, workspace, coordinator) => task.status === "active" && task.workspaceId === workspace.id && (task.intent.provenance.session?.kind === "host" && task.intent.provenance.session.host === "opencode" && task.intent.provenance.session.handle === coordinator || task.workers.some((entry) => entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === coordinator) || workspace.writer?.owner.taskId === task.id && workspace.writer.owner.session.host === "opencode" && workspace.writer.owner.session.handle === coordinator);
34185
+ const ownsActiveTask = (coordinator) => {
34186
+ try {
34187
+ const store = new TaskStore(root);
34188
+ const workspace = store.readWorkspace();
34189
+ const listed = store.listTasks();
34190
+ if (!workspace.ok || !workspace.data || !listed.ok)
34191
+ return false;
34192
+ return listed.data.some((task) => managedByCoordinator(task, workspace.data, coordinator));
34193
+ } catch {
34194
+ return false;
34195
+ }
34196
+ };
34197
+ const revisions = (taskId) => {
34198
+ const store = new TaskStore(root);
34199
+ const task = store.readTask(taskId);
34200
+ const workspace = store.readWorkspace();
34201
+ return task.ok && workspace.ok && workspace.data ? {
34202
+ expectedRevision: task.data.revision,
34203
+ expectedWorkspaceRevision: workspace.data.revision
34204
+ } : null;
34205
+ };
34206
+ const oldestOfSingleTask = (items) => {
34207
+ const tasks = new Set(items.map((item) => item.task.id));
34208
+ if (tasks.size !== 1 || items.length === 0)
34209
+ return null;
34210
+ return [...items].sort((a, b) => a.entry.recordedAt < b.entry.recordedAt ? -1 : a.entry.recordedAt > b.entry.recordedAt ? 1 : a.entry.id < b.entry.id ? -1 : 1)[0];
34211
+ };
34212
+ const prepareDispatch = (coordinator, callID) => {
34213
+ if (dispatches.has(coordinator))
34214
+ return "unsettled";
34215
+ const store = new TaskStore(root);
34216
+ const workspace = store.readWorkspace();
34217
+ const listed = store.listTasks();
34218
+ if (!workspace.ok || !workspace.data || !listed.ok)
34219
+ return "unmanaged";
34220
+ const managed = listed.data.filter((task) => managedByCoordinator(task, workspace.data, coordinator));
34221
+ if (managed.length === 0)
34222
+ return "unmanaged";
34223
+ const eligible = managed.flatMap((task) => task.workers.filter((entry) => entry.data.state === "assigned" && entry.data.session === null && entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === coordinator).map((entry) => ({ task, entry })));
34224
+ const next = oldestOfSingleTask(eligible);
34225
+ if (!next)
34226
+ return "unbound";
34227
+ const generation = {
34228
+ coordinator,
34229
+ callID,
34230
+ childCreated: false,
34231
+ noChild: false
34232
+ };
34233
+ const core = new WorkitCore(store, {
34234
+ root,
34235
+ caller: { host: "opencode", actor: coordinator },
34236
+ capabilities: [],
34237
+ constraints: [],
34238
+ now,
34239
+ nativeWorker: nativeDispatchFor(directChildren, coordinator, generation)
34240
+ });
34241
+ const prepared = core.prepareWorkerDispatch({
34242
+ taskId: next.task.id,
34243
+ workerId: next.entry.id,
34244
+ expectedRevision: next.task.revision,
34245
+ expectedWorkspaceRevision: workspace.data.revision,
34246
+ observation: { stage: "prepare", sessionID: coordinator, callID }
34247
+ });
34248
+ if (!prepared.ok)
34249
+ return "unbound";
34250
+ dispatches.set(coordinator, {
34251
+ generation,
34252
+ dispatch: prepared.data,
34253
+ core,
34254
+ taskId: next.task.id,
34255
+ workerId: next.entry.id,
34256
+ callID,
34257
+ childID: null
34258
+ });
34259
+ return "prepared";
34260
+ };
34261
+ const commitDispatchStart = (coordinator, childID) => {
34262
+ const pending = dispatches.get(coordinator);
34263
+ if (!pending)
34264
+ return false;
34265
+ pending.generation.childCreated = true;
34266
+ pending.childID = childID;
34267
+ const current = revisions(pending.taskId);
34268
+ if (!current)
34269
+ return false;
34270
+ const committed = pending.core.commitWorkerDispatch({
34271
+ ...current,
34272
+ dispatch: pending.dispatch,
34273
+ taskId: pending.taskId,
34274
+ workerId: pending.workerId,
34275
+ outcome: "started",
34276
+ session: { kind: "host", host: "opencode", handle: childID },
34277
+ observation: { event: "running", sessionID: childID }
34278
+ });
34279
+ if (!committed.ok)
34280
+ return false;
34281
+ dispatches.delete(coordinator);
34282
+ if (inflight.get(coordinator) === pending.callID)
34283
+ inflight.delete(coordinator);
34284
+ lifecycleBindings.set(childID, {
34285
+ parentID: coordinator,
34286
+ taskId: pending.taskId,
34287
+ workerId: pending.workerId
34288
+ });
34289
+ return true;
34290
+ };
34291
+ const observeLifecycle = async (sessionID, parentID, state, binding, deleted = false, report) => {
34292
+ const store = new TaskStore(root);
34293
+ const listed = store.listTasks();
34294
+ const workspace = store.readWorkspace();
34295
+ if (!listed.ok || !workspace.ok || !workspace.data)
34296
+ return;
34297
+ const persisted = listed.data.flatMap((task) => task.status === "active" ? task.workers.filter((entry) => entry.id === binding?.workerId || entry.data.session?.kind === "host" && entry.data.session.handle === sessionID).map((entry) => ({ task, entry })) : []);
34298
+ const matches = persisted.filter(({ entry }) => entry.data.session?.kind === "host");
34299
+ if (binding && matches.length !== 1)
34300
+ return;
34301
+ const selected = binding ? persisted.find(({ task, entry }) => task.id === binding.taskId && entry.id === binding.workerId) : matches.length === 1 ? matches[0] : undefined;
34302
+ if (!selected)
34303
+ return;
34304
+ if (selected.entry.data.session?.kind !== "host")
34305
+ return;
34306
+ if (selected.entry.provenance.session?.kind !== "host")
34307
+ return;
34308
+ if (!binding && deleted)
34309
+ return;
34310
+ if (!deleted) {
34311
+ const observed = await readSession(sessionID);
34312
+ const observedParent = parentOf(observed);
34313
+ if (!observed || !sameWorkspace(root, observed.directory) || !observedParent)
34314
+ return;
34315
+ if (!binding) {
34316
+ const coordinator = workerCoordinatorFor(selected.task, selected.entry);
34317
+ if (!coordinator || observedParent !== coordinator)
34318
+ return;
34319
+ parentID = coordinator;
34320
+ } else if (observedParent !== parentID)
34321
+ return;
34322
+ }
34323
+ directChildren.set(sessionID, parentID);
34324
+ const core = new WorkitCore(store, {
34325
+ root,
34326
+ caller: { host: "opencode", actor: parentID },
34327
+ capabilities: [],
34328
+ constraints: [],
34329
+ now,
34330
+ nativeWorker: nativeWorkerFor(directChildren, parentID)
34331
+ });
34332
+ const result = core.observeWorkerLifecycle({
34333
+ taskId: selected.task.id,
34334
+ workerId: selected.entry.id,
34335
+ expectedRevision: selected.task.revision,
34336
+ expectedWorkspaceRevision: workspace.data.revision,
34337
+ state,
34338
+ session: { kind: "host", host: "opencode", handle: sessionID },
34339
+ ...report ? { report } : {},
34340
+ observation: { event: state, sessionID }
34341
+ });
34342
+ if (!result.ok)
34343
+ return;
34344
+ lifecycleBindings.set(sessionID, {
34345
+ parentID,
34346
+ taskId: selected.task.id,
34347
+ workerId: selected.entry.id
34348
+ });
34349
+ };
34350
+ const reconcilePending = async (coordinator) => {
34351
+ const pending = dispatches.get(coordinator);
34352
+ if (!pending?.childID)
34353
+ return;
34354
+ const info = await readSession(pending.childID);
34355
+ if (!info || info.id !== pending.childID || !info.outcome)
34356
+ return;
34357
+ await observeLifecycle(pending.childID, coordinator, "stopped", {
34358
+ taskId: pending.taskId,
34359
+ workerId: pending.workerId
34360
+ });
34361
+ dispatches.delete(coordinator);
34362
+ if (inflight.get(coordinator) === pending.callID)
34363
+ inflight.delete(coordinator);
34364
+ };
34365
+ const coordinatorVeto = (coordinator) => {
34366
+ const listed = new TaskStore(root).listTasks();
34367
+ if (listed.ok && listed.data.some((task) => task.workers.some((worker) => (worker.data.state === "cancelling" || worker.data.state === "unknown") && worker.provenance.session?.kind === "host" && worker.provenance.session.handle === coordinator)))
34368
+ return "recovery_required: a cancelled worker remains uncertain; repeat worker.cancel on the ended worker to confirm its stop, then retry";
34369
+ if (unresolvedTaskLaunches.delete(coordinator))
34370
+ return "recovery_required: a cancelled worker remains uncertain";
34371
+ return null;
34372
+ };
34373
+ const executeBefore = async (event) => {
34374
+ if (event.tool !== "subagent")
34375
+ return;
34376
+ const coordinator = event.sessionID;
34377
+ const continuation = record3(event.input).sessionID;
34378
+ if (continuation !== undefined) {
34379
+ const lead = await trustedLead(coordinator);
34380
+ if (!lead)
34381
+ throw new Error("delegation_lineage_denied: native subagent workers must be direct children of the coordinator");
34382
+ const childID = sessionIDOf(continuation);
34383
+ const child = childID ? await readSession(childID) : null;
34384
+ if (!child || parentOf(child) !== coordinator)
34385
+ throw new Error("delegation_lineage_denied: a continuation sessionID must name an existing direct child of the coordinator");
34386
+ const listed = new TaskStore(root).listTasks();
34387
+ const bound = listed.ok ? listed.data.filter((task) => task.workers.some((worker) => worker.data.state === "running" && worker.data.session?.kind === "host" && worker.data.session.handle === childID && workerCoordinatorFor(task, worker) === coordinator)).length : 0;
34388
+ if (bound !== 1 && ownsActiveTask(coordinator))
34389
+ throw new Error("delegation_lineage_denied: a continuation sessionID must name a running Workit worker");
34390
+ return;
34391
+ }
34392
+ if (!ownsActiveTask(coordinator)) {
34393
+ const lead = await trustedLead(coordinator);
34394
+ if (!lead)
34395
+ throw new Error("delegation_lineage_denied: native subagent workers must be direct children of the coordinator");
34396
+ return;
34397
+ }
34398
+ if (inflight.has(coordinator)) {
34399
+ await reconcilePending(coordinator);
34400
+ if (inflight.has(coordinator))
34401
+ throw new Error("recovery_required: a previous fresh subagent launch is unsettled; wait for it to settle or reconcile its worker before launching again");
34402
+ }
34403
+ inflight.set(coordinator, event.id);
34404
+ try {
34405
+ const veto = coordinatorVeto(coordinator);
34406
+ if (veto)
34407
+ throw new Error(veto);
34408
+ const lead = await trustedLead(coordinator);
34409
+ if (!lead)
34410
+ throw new Error("delegation_lineage_denied: native subagent workers must be direct children of the coordinator");
34411
+ const prepared = prepareDispatch(coordinator, event.id);
34412
+ if (prepared === "unmanaged") {
34413
+ inflight.delete(coordinator);
34414
+ return;
34415
+ }
34416
+ if (prepared === "unsettled") {
34417
+ throw new Error("recovery_required: a previous native task launch is unsettled; wait for it to settle or reconcile its worker before launching again");
34418
+ }
34419
+ if (prepared === "unbound") {
34420
+ throw new Error("recovery_required: no assigned Workit worker is attributable to this coordinator; assign one with workit_worker before launching a native task");
34421
+ }
34422
+ } catch (error) {
34423
+ if (inflight.get(coordinator) === event.id)
34424
+ inflight.delete(coordinator);
34425
+ throw error;
34426
+ }
34427
+ };
34428
+ const executeAfter = async (event) => {
34429
+ if (event.tool !== "subagent")
34430
+ return;
34431
+ const coordinator = event.sessionID;
34432
+ const pending = dispatches.get(coordinator);
34433
+ const generation = pending?.callID === event.id ? pending : undefined;
34434
+ if (event.status === "completed") {
34435
+ const metadata = record3(event.result.metadata);
34436
+ const child = sessionIDOf(metadata.sessionID ?? metadata.sessionId);
34437
+ if (child) {
34438
+ const childSession = await readSession(child);
34439
+ if (parentOf(childSession) === coordinator) {
34440
+ directChildren.set(child, coordinator);
34441
+ if (generation)
34442
+ commitDispatchStart(coordinator, child);
34443
+ }
34444
+ }
34445
+ const output = contentText(event.result.content);
34446
+ const completedChild = child && /<subagent\b[^>]*\bstate="completed"/.test(output) && lifecycleBindings.has(child) ? child : null;
34447
+ if (completedChild) {
34448
+ const binding = lifecycleBindings.get(completedChild);
34449
+ if (binding)
34450
+ await observeLifecycle(completedChild, coordinator, "stopped", binding, false, {
34451
+ outcome: "completed",
34452
+ summary: output.trim() || "Native worker completed.",
34453
+ evidenceIds: [],
34454
+ findingIds: []
34455
+ });
34456
+ }
34457
+ } else {
34458
+ const message = String(event.error?.message ?? event.error ?? "").toLowerCase();
34459
+ if (generation && /(?:cancel|interrupt|unknown|uncertain)/.test(message))
34460
+ unresolvedTaskLaunches.add(coordinator);
34461
+ }
34462
+ if (generation) {
34463
+ dispatches.delete(coordinator);
34464
+ if (inflight.get(coordinator) === event.id)
34465
+ inflight.delete(coordinator);
34466
+ } else if (inflight.get(coordinator) === event.id) {
34467
+ inflight.delete(coordinator);
34468
+ }
34469
+ };
34470
+ const bindCreatedSession = async (data) => {
34471
+ if (!sameWorkspace(root, data.directory))
34472
+ return;
34473
+ directChildren.set(data.sessionID, data.parentID);
34474
+ const live = dispatches.get(data.parentID);
34475
+ if (live) {
34476
+ live.childID = data.sessionID;
34477
+ commitDispatchStart(data.parentID, data.sessionID);
34478
+ }
34479
+ };
34480
+ const handleEvent = async (event) => {
34481
+ const type = event.type;
34482
+ if (typeof type !== "string")
34483
+ return;
34484
+ if (type === "session.created") {
34485
+ const data = record3(event.data);
34486
+ const sessionID = sessionIDOf(data.sessionID);
34487
+ const parentID = sessionIDOf(data.parentID);
34488
+ const directory = record3(data.location).directory;
34489
+ if (!sessionID || !parentID || typeof directory !== "string")
34490
+ return;
34491
+ await bindCreatedSession({ sessionID, parentID, directory });
34492
+ return;
34493
+ }
34494
+ const terminal = type === "session.execution.succeeded" || type === "session.execution.failed" || type === "session.execution.interrupted";
34495
+ if (type !== "session.execution.started" && !terminal && type !== "session.deleted")
34496
+ return;
34497
+ const sessionID = sessionIDOf(record3(event.data).sessionID);
34498
+ if (!sessionID)
34499
+ return;
34500
+ const binding = lifecycleBindings.get(sessionID);
34501
+ if (type === "session.deleted") {
34502
+ const directory = event.location?.directory;
34503
+ if (typeof directory === "string" && !sameWorkspace(root, directory))
34504
+ return;
34505
+ await observeLifecycle(sessionID, binding?.parentID ?? "", "stopped", binding, true);
34506
+ return;
34507
+ }
34508
+ const state = type === "session.execution.started" ? "running" : "stopped";
34509
+ await observeLifecycle(sessionID, binding?.parentID ?? "", state, binding);
34510
+ };
34511
+ return {
34512
+ directChildren,
34513
+ executeBefore,
34514
+ executeAfter,
34515
+ handleEvent,
34516
+ pendingLaunch: (coordinator) => inflight.has(coordinator)
34517
+ };
34518
+ };
34519
+
34520
+ // packages/workit-opencode/src/v2/injection.ts
34521
+ var textPart = (text) => ({ type: "text", text });
34522
+ var hasMarker = (system, marker) => system.some((part) => part.type === "text" && typeof part.text === "string" && part.text.includes(marker));
34523
+ var injectAgentContext = (root, session, directChildren, system) => {
34524
+ if (!session || !sameWorkspace(root, session.directory))
34525
+ return;
34526
+ const bootstrap = session.parentID === undefined ? getWorkitBootstrap() : null;
34527
+ const taskContext = compactContextFor(root, session.id);
34528
+ const workerContext = session.parentID ? workerContextFor(root, session.id, session.parentID, directChildren) : null;
34529
+ if (bootstrap && !hasMarker(system, "<workit-contract>"))
34530
+ system.unshift(textPart(bootstrap));
34531
+ if (taskContext && !hasMarker(system, "<workit-task-context>"))
34532
+ system.unshift(textPart(`<workit-task-context>${taskContext}</workit-task-context>`));
34533
+ if (workerContext && !hasMarker(system, "<workit-worker-context>"))
34534
+ system.unshift(textPart(`<workit-worker-context>${workerContext}</workit-worker-context>`));
34535
+ };
34536
+ var injectCompactionContext = (root, sessionID, system) => {
34537
+ const context = compactContextFor(root, sessionID);
34538
+ if (!context || hasMarker(system, "<workit-task-context>"))
34539
+ return;
34540
+ system.push(textPart(`<workit-task-context>${context}</workit-task-context>`));
34541
+ };
34542
+
34543
+ // packages/workit-opencode/src/v2/permissions.ts
34544
+ var GIT_WORKTREE = /git [\s\S]*worktree/u;
34545
+ var WORKTREE_MESSAGE = "Workit does not use git worktrees; use the guarded in-place git.branch_setup route instead";
34546
+ var evaluateShellPermission = (root, event) => {
34547
+ if (event.action !== "shell" || event.effect === "deny")
34548
+ return;
34549
+ for (const resource of event.resources) {
34550
+ if (typeof resource !== "string")
34551
+ continue;
34552
+ const route = shouldDenyShellRoute(root, resource);
34553
+ if (route) {
34554
+ event.effect = "deny";
34555
+ event.message = `direct branch or PR creation bypasses the Workit route; ${route.guidance}`;
34556
+ return;
34557
+ }
34558
+ if (GIT_WORKTREE.test(resource)) {
34559
+ event.effect = "deny";
34560
+ event.message = WORKTREE_MESSAGE;
34561
+ return;
34562
+ }
34563
+ }
34564
+ };
34565
+
34566
+ // packages/workit-opencode/src/v2/receipts.ts
34567
+ var normalizeQuestionAnswers = (answers) => {
34568
+ const singleValue = (value) => typeof value === "string" && value.trim() ? value : null;
34569
+ if (answers === null || typeof answers !== "object")
34570
+ return answers;
34571
+ if (Array.isArray(answers)) {
34572
+ if (answers.length === 0)
34573
+ return answers;
34574
+ const first = answers[0];
34575
+ const direct = singleValue(first);
34576
+ if (direct)
34577
+ return [[direct]];
34578
+ if (Array.isArray(first)) {
34579
+ if (first.length !== 1)
34580
+ return answers;
34581
+ const only = singleValue(first[0]);
34582
+ return only ? [[only]] : answers;
34583
+ }
34584
+ if (first && typeof first === "object") {
34585
+ const values = Object.values(first);
34586
+ if (values.length === 1 && singleValue(values[0]) !== null)
34587
+ return [[singleValue(values[0])]];
34588
+ }
34589
+ return answers;
34590
+ }
34591
+ const values = Object.values(answers);
34592
+ if (values.length === 1 && singleValue(values[0]) !== null)
34593
+ return [[singleValue(values[0])]];
34594
+ return answers;
34595
+ };
34596
+
34597
+ // packages/workit-opencode/src/v2/registry.ts
34598
+ import { existsSync as existsSync15, readFileSync as readFileSync14 } from "node:fs";
34599
+ import path28 from "node:path";
34600
+ var parseFrontmatter = (markdown) => {
34601
+ const match = markdown.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
34602
+ if (!match)
34603
+ return { name: "", body: markdown };
34604
+ const meta = {};
34605
+ for (const line of match[1].split(`
34606
+ `)) {
34607
+ const index = line.indexOf(":");
34608
+ if (index > 0)
34609
+ meta[line.slice(0, index).trim()] = line.slice(index + 1).trim();
34610
+ }
34611
+ return {
34612
+ name: (meta.name ?? "").replace(/^["']|["']$/g, "").trim(),
34613
+ description: (meta.description ?? "").replace(/^["']|["']$/g, "").trim() || undefined,
34614
+ body: `${match[2].trim()}
34615
+ `
34616
+ };
34617
+ };
34618
+ var registerSkills = async (ctx) => {
34619
+ const skillsDir = path28.join(assetsRoot(), "skills");
34620
+ if (!existsSync15(skillsDir))
34621
+ return;
34622
+ await ctx.skill.transform((editor) => {
34623
+ const existing = new Set(editor.list().map((skill) => skill.id));
34624
+ for (const id of WORKIT_METHOD_SKILLS) {
34625
+ if (existing.has(id))
34626
+ continue;
34627
+ const file = path28.join(skillsDir, id, "SKILL.md");
34628
+ if (!existsSync15(file))
34629
+ continue;
34630
+ const parsed = parseFrontmatter(readFileSync14(file, "utf8"));
34631
+ editor.add({
34632
+ id,
34633
+ name: parsed.name || id,
34634
+ ...parsed.description ? { description: parsed.description } : {},
34635
+ location: file,
34636
+ content: parsed.body
34637
+ });
34638
+ }
34639
+ });
34640
+ };
34641
+ var registerCommands = async (ctx) => {
34642
+ const listed = await ctx.command.list();
34643
+ const existing = new Set((listed?.data ?? []).map((command) => command.name));
34644
+ await ctx.command.transform((editor) => {
34645
+ for (const [alias, skill] of Object.entries(WORKIT_SKILL_ALIASES)) {
34646
+ if (existing.has(alias))
34647
+ continue;
34648
+ editor.add({
34649
+ name: alias,
34650
+ description: `Apply the ${skill} method skill to the current task.`,
34651
+ execute: async ({ sessionID, prompt, delivery }) => {
34652
+ await ctx.session.prompt({
34653
+ sessionID,
34654
+ text: `Load the ${skill} skill with the skill tool and apply it to the current task. Arguments: ${prompt.text}`,
34655
+ ...prompt.files ? { files: prompt.files } : {},
34656
+ ...prompt.agents ? { agents: prompt.agents } : {},
34657
+ ...prompt.skills ? { skills: prompt.skills } : {},
34658
+ delivery
34659
+ });
34660
+ }
34661
+ });
34662
+ }
34663
+ });
34664
+ };
34665
+
34666
+ // packages/workit-opencode/src/v2/plugin.ts
34667
+ var sessionFacts = async (ctx, sessionID) => {
34668
+ try {
34669
+ const session = await ctx.session.get({ sessionID });
34670
+ const id = session?.id;
34671
+ const directory = session?.location?.directory;
34672
+ if (typeof id !== "string" || id !== sessionID || typeof directory !== "string")
34673
+ return null;
34674
+ const parentID = typeof session.parentID === "string" ? session.parentID : undefined;
34675
+ const outcome = session.outcome === "succeeded" || session.outcome === "failed" || session.outcome === "interrupted" ? session.outcome : undefined;
34676
+ return { id, parentID, directory, outcome };
34677
+ } catch {
34678
+ return null;
34679
+ }
34680
+ };
34681
+ var resultContent = (value) => ({
34682
+ content: JSON.stringify(value, null, 2)
34683
+ });
34684
+ var v2Capabilities = () => [
34685
+ {
34686
+ name: "interactive_decision",
34687
+ surface: "question",
34688
+ assurance: "enforced",
34689
+ reason: "native question answers are observed by tool.execute.after and consumed once",
34690
+ refs: [{ kind: "host", host: "opencode", handle: "question" }]
34691
+ },
34692
+ {
34693
+ name: "known_product_writes",
34694
+ surface: "edit/shell",
34695
+ assurance: "unavailable",
34696
+ reason: "file writes are host-policy; OpenCode native permissions govern them, workit no longer gates write tools",
34697
+ refs: [{ kind: "host", host: "opencode", handle: "permission.evaluate" }]
34698
+ },
34699
+ {
34700
+ name: "direct_child_workers",
34701
+ surface: "subagent",
34702
+ assurance: "enforced",
34703
+ reason: "nested subagent launches are denied and observed child sessions are parent-bound before they may own a worker",
34704
+ refs: [{ kind: "host", host: "opencode", handle: "subagent" }]
34705
+ },
34706
+ {
34707
+ name: "fresh-context-review",
34708
+ surface: "subagent",
34709
+ assurance: "agent_guided",
34710
+ reason: "independent review runs as a native child session; evidence evaluation enforces creator and duplicate-reviewer exclusion",
34711
+ refs: [{ kind: "host", host: "opencode", handle: "subagent" }]
34712
+ }
34713
+ ];
34714
+ var workerIdFor2 = (store, actor, parentID, directChildren) => {
34715
+ if (parentID === undefined)
34716
+ return null;
34717
+ const tasks = store.listTasks();
34718
+ if (!tasks.ok)
34719
+ return null;
34720
+ const matches = tasks.data.flatMap((task) => {
34721
+ if (task.status !== "active")
34722
+ return [];
34723
+ if (directChildren.get(actor) !== parentID)
34724
+ return [];
34725
+ return task.workers.filter((worker) => worker.data.state === "running" && worker.data.session?.kind === "host" && worker.data.session.host === "opencode" && worker.data.session.handle === actor && workerCoordinatorFor(task, worker) === parentID);
34726
+ });
34727
+ return matches.length === 1 ? matches[0].id : null;
34728
+ };
34729
+ var recordDecision = (core, receipts, request, sessionID) => {
34730
+ const decision = request;
34731
+ if (decision.response === "stated")
34732
+ return core.observeDecision(request, undefined);
34733
+ const content = decisionContent(decision.purpose, decision.binding.presented, decision.binding.approvedContent);
34734
+ const budgetIssue = workitBindingQuestionIssue([content]);
34735
+ if (budgetIssue)
34736
+ return failure("invalid_input", budgetIssue);
34737
+ const observed = receipts.reserve(sessionID, "decision", {
34738
+ selectedLabel: decision.response,
34739
+ decisionPurpose: decision.purpose,
34740
+ contentDigest: sha256(canonicalJson(content)),
34741
+ question: decision.binding.presented,
34742
+ ...decision.response === "approved" ? { selectedDescription: decision.binding.approvedContent } : {}
34743
+ });
34744
+ if (!observed.ok)
34745
+ return failure("permission_denied", observed.error);
34746
+ if (decision.purpose === "action" && decision.response === "approved" && !isSelfAuthorizingActionContent(decision.binding.approvedContent))
34747
+ return failure("invalid_input", "no matching action proposal; resolve the action through the action tool first");
34748
+ const result = core.observeDecision(request, observed.observation);
34749
+ if (result.ok)
34750
+ receipts.commit(observed.observation);
34751
+ return result;
34752
+ };
34753
+ var setup = async (ctx) => {
34754
+ const root = ctx.location.directory;
34755
+ const sourceMarker = markSourcesLoaded(pluginSourceFiles);
34756
+ let staleSourcesWarned = false;
34757
+ const receipts = new NativeReceiptStore;
34758
+ const lifecycle = createV2Lifecycle({
34759
+ root,
34760
+ getSession: async (sessionID) => {
34761
+ const session = await sessionFacts(ctx, sessionID);
34762
+ return session ? {
34763
+ id: session.id,
34764
+ parentID: session.parentID,
34765
+ directory: session.directory,
34766
+ outcome: session.outcome
34767
+ } : null;
34768
+ }
34769
+ });
34770
+ const runFamily = (family, input, session, store, workerId) => {
34771
+ const parsed = parseOperation(family, input);
34772
+ if (!parsed.ok)
34773
+ return parsed;
34774
+ const core = new WorkitCore(store, {
34775
+ root,
34776
+ caller: { host: "opencode", actor: session.id },
34777
+ capabilities: v2Capabilities(),
34778
+ constraints: [],
34779
+ now: () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
34780
+ workerId,
34781
+ nativeAuthority: nativeAuthority(receipts, session.id),
34782
+ nativeWorker: nativeWorkerFor(lifecycle.directChildren, session.id)
34783
+ });
34784
+ if (family === "decision" && parsed.data.action === "record")
34785
+ return recordDecision(core, receipts, parsed.data, session.id);
34786
+ const run = core[family];
34787
+ return run.call(core, parsed.data);
34788
+ };
34789
+ await ctx.tool.transform((editor) => {
34790
+ for (const spec of WORKIT_TOOL_CATALOG) {
34791
+ editor.add({
34792
+ name: spec.name,
34793
+ description: spec.description,
34794
+ input: spec.input,
34795
+ options: { codemode: false },
34796
+ execute: async (input, tool) => {
34797
+ const session = await sessionFacts(ctx, tool.sessionID);
34798
+ if (session === null || !sameWorkspace(root, session.directory))
34799
+ return resultContent(failure("permission_denied", "OpenCode session location does not match the plugin checkout"));
34800
+ const store = new TaskStore(root);
34801
+ const workerId = workerIdFor2(store, session.id, session.parentID, lifecycle.directChildren);
34802
+ if (session.parentID !== undefined && workerId === null)
34803
+ return resultContent(failure("permission_denied", "OpenCode child session has no validated Workit worker"));
34804
+ const family = workitFamilyOf(spec.name);
34805
+ if (family !== null)
34806
+ return resultContent(runFamily(family, input, session, store, workerId));
34807
+ if (spec.name === "workit_external_action") {
34808
+ const parsed = externalActionRequest(input);
34809
+ if (!parsed.ok)
34810
+ return resultContent(parsed);
34811
+ if (parsed.data.operation !== "context.read")
34812
+ return resultContent(failure("capability_unavailable", "OpenCode V2 external action approvals are not available yet; only context.read is", { capability: "external_action", outcome: "not_started" }));
34813
+ const resolved = resolveExternalActionRequest(root, parsed.data);
34814
+ if (!resolved.ok)
34815
+ return resultContent(resolved);
34816
+ return resultContent(await executeResolvedExternalAction(resolved.data, root));
34817
+ }
34818
+ if (spec.name === "workit_init_apply") {
34819
+ return { content: executeInitApply(input, root, initApplyRuntime) };
34820
+ }
34821
+ return resultContent(failure("not_found", `unknown Workit tool ${spec.name}`));
34822
+ }
34823
+ });
34824
+ }
34825
+ });
34826
+ await ctx.tool.hook("execute.before", async (event) => {
34827
+ if (event.tool !== "subagent")
34828
+ return;
34829
+ await lifecycle.executeBefore({
34830
+ tool: event.tool,
34831
+ sessionID: String(event.sessionID),
34832
+ id: String(event.id),
34833
+ input: event.input
34834
+ });
34835
+ });
34836
+ await ctx.tool.hook("execute.after", async (event) => {
34837
+ if (event.tool === "question") {
34838
+ if (event.status !== "completed")
34839
+ return;
34840
+ const metadata = event.result.metadata;
34841
+ const answers = metadata?.answers;
34842
+ receipts.record({ sessionID: String(event.sessionID), callID: String(event.id), args: event.input }, {
34843
+ metadata: metadata === undefined ? undefined : { answers: normalizeQuestionAnswers(answers) }
34844
+ });
34845
+ return;
34846
+ }
34847
+ if (event.tool !== "subagent")
34848
+ return;
34849
+ await lifecycle.executeAfter({
34850
+ tool: event.tool,
34851
+ sessionID: String(event.sessionID),
34852
+ id: String(event.id),
34853
+ input: event.input,
34854
+ ...event.status === "completed" ? { status: "completed", result: event.result } : { status: "error", error: event.error }
34855
+ });
34856
+ });
34857
+ await ctx.permission.hook("evaluate", (event) => {
34858
+ evaluateShellPermission(root, event);
34859
+ });
34860
+ await ctx.session.hook("context", async (event) => {
34861
+ const session = await sessionFacts(ctx, String(event.sessionID));
34862
+ injectAgentContext(root, session, lifecycle.directChildren, event.system);
34863
+ if (!staleSourcesWarned) {
34864
+ const changed = changedSourcesSinceLoad(sourceMarker);
34865
+ if (changed.length > 0) {
34866
+ staleSourcesWarned = true;
34867
+ event.system.push({
34868
+ type: "text",
34869
+ text: `<workit-warning>Workit sources changed after plugin load (${changed.map((file) => file.split(/[\\/]/).at(-1)).join(", ")}); restart the session for latest behavior.</workit-warning>`
34870
+ });
34871
+ }
34872
+ }
34873
+ });
34874
+ await ctx.session.hook("compaction", (event) => {
34875
+ injectCompactionContext(root, String(event.sessionID), event.system);
34876
+ });
34877
+ await registerSkills(ctx);
34878
+ await registerCommands(ctx);
34879
+ const subscription = new AbortController;
34880
+ (async () => {
34881
+ try {
34882
+ for await (const event of ctx.event.subscribe({ signal: subscription.signal })) {
34883
+ await lifecycle.handleEvent(event);
34884
+ }
34885
+ } catch {}
34886
+ })();
34887
+ return () => {
34888
+ subscription.abort();
34889
+ };
34890
+ };
34891
+ var definition = define({ id: "workit", setup });
34892
+ var plugin_default = definition;
34893
+
34894
+ // packages/workit-opencode/src/index.ts
34895
+ var src_default = { ...plugin_default, server: server_default };
33516
34896
  export {
33517
- plugin_default as default
34897
+ src_default as default,
34898
+ server_default as server
33518
34899
  };