@brainervirus/workit-opencode 1.1.1 → 1.2.0

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 +1529 -354
  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) {
@@ -8070,8 +8292,62 @@ function parseOperation(family, input) {
8070
8292
  }));
8071
8293
  return failure("invalid_input", "operation input is invalid", { fields });
8072
8294
  }
8295
+ function operationJsonSchema(family) {
8296
+ return toJSONSchema(operationSchemas[family], { target: "draft-2020-12" });
8297
+ }
8073
8298
  var OPERATION_SCHEMA_DEPTH = 1;
8074
8299
  var canonicalFieldsDescription = (fields) => `Canonical object fields: ${fields.join(", ")}. Workit validates the complete nested value.`;
8300
+ function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, stringifiedObjects = false) {
8301
+ const tolerate = (projected) => {
8302
+ if (!stringifiedObjects)
8303
+ return projected;
8304
+ if (projected.properties && typeof projected.properties === "object")
8305
+ return { ...projected, type: ["object", "string"] };
8306
+ if (projected.items)
8307
+ return { ...projected, type: ["array", "string"] };
8308
+ if ("const" in projected && (typeof projected.const === "number" || typeof projected.const === "boolean"))
8309
+ return { anyOf: [projected, { const: String(projected.const) }] };
8310
+ if (projected.type === "object")
8311
+ return { ...projected, type: ["object", "string"] };
8312
+ if (projected.type === "array")
8313
+ return { ...projected, type: ["array", "string"] };
8314
+ return projected;
8315
+ };
8316
+ const collapse = (node, currentDepth) => {
8317
+ if (Array.isArray(node))
8318
+ return node.map((item) => collapse(item, currentDepth));
8319
+ if (typeof node !== "object" || node === null)
8320
+ return node;
8321
+ const record = node;
8322
+ if (record.properties && typeof record.properties === "object") {
8323
+ const fields = Object.keys(record.properties);
8324
+ if (currentDepth >= maxDepth || fields.length === 0) {
8325
+ const description = stringifiedObjects ? `${canonicalFieldsDescription(fields)} A JSON-encoded string is also accepted.` : canonicalFieldsDescription(fields);
8326
+ return tolerate({ type: "object", description });
8327
+ }
8328
+ return tolerate({
8329
+ ...record,
8330
+ properties: Object.fromEntries(Object.entries(record.properties).map(([key, child]) => [
8331
+ key,
8332
+ collapse(child, currentDepth + 1)
8333
+ ]))
8334
+ });
8335
+ }
8336
+ if (record.items)
8337
+ return tolerate({ ...record, items: collapse(record.items, currentDepth) });
8338
+ const composed = ["anyOf", "oneOf", "allOf"].filter((key) => Array.isArray(record[key]));
8339
+ if (composed.length > 0) {
8340
+ const projected = { ...record };
8341
+ for (const key of composed)
8342
+ projected[key] = record[key].map((item) => collapse(item, currentDepth));
8343
+ return projected;
8344
+ }
8345
+ if ("const" in record)
8346
+ return tolerate({ ...record });
8347
+ return node;
8348
+ };
8349
+ return collapse(operationJsonSchema(family), 0);
8350
+ }
8075
8351
  function canonical(value, seen) {
8076
8352
  if (value === null || typeof value === "boolean" || typeof value === "string") {
8077
8353
  if (typeof value === "string" && invalidUnicode(value))
@@ -12394,6 +12670,12 @@ var externalActionDescriptor = (operation, payload) => canonicalJson({
12394
12670
  payload,
12395
12671
  ...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
12672
  });
12673
+ var planReservationLength = (operation, descriptorPayload) => {
12674
+ if (operation !== "git.commit")
12675
+ return null;
12676
+ const payload = descriptorPayload ?? {};
12677
+ return Array.isArray(payload.plan_steps) ? payload.plan_steps.length : null;
12678
+ };
12397
12679
  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).";
12398
12680
  var externalActionRef = (host, actor, descriptor) => ({
12399
12681
  kind: "host",
@@ -15151,6 +15433,22 @@ function run2(root, executable, args, env = {}) {
15151
15433
  }
15152
15434
 
15153
15435
  // packages/workit-core/src/core/skill-manifests.ts
15436
+ var WORKIT_METHOD_SKILLS = [
15437
+ "workit-challenge",
15438
+ "workit-behavioral-tdd",
15439
+ "workit-review",
15440
+ "workit-plan",
15441
+ "workit-implement",
15442
+ "workit-debug",
15443
+ "workit-handoff",
15444
+ "workit-babysit",
15445
+ "workit-blast-radius",
15446
+ "workit-deslop",
15447
+ "workit-diagram",
15448
+ "workit-mockup",
15449
+ "workit-green-run",
15450
+ "workit-steer"
15451
+ ];
15154
15452
  var WORKIT_SKILL_ALIASES = {
15155
15453
  "wk-challenge": "workit-challenge",
15156
15454
  "wk-babysit": "workit-babysit",
@@ -15407,13 +15705,47 @@ OpenCode Workit decisions use the native question header "Workit decision: <purp
15407
15705
  return cached2;
15408
15706
  };
15409
15707
 
15410
- // packages/workit-opencode/src/stale-sources.ts
15411
- import { existsSync as existsSync7 } from "node:fs";
15708
+ // packages/workit-opencode/src/shared/assets.ts
15709
+ import { existsSync as existsSync7, realpathSync as realpathSync7 } from "node:fs";
15710
+ import path13 from "node:path";
15412
15711
  import { fileURLToPath as fileURLToPath2 } from "node:url";
15712
+ var moduleDir = () => {
15713
+ try {
15714
+ return realpathSync7(path13.dirname(fileURLToPath2(import.meta.url)));
15715
+ } catch {
15716
+ return path13.dirname(fileURLToPath2(import.meta.url));
15717
+ }
15718
+ };
15719
+ var packageRoot2 = () => {
15720
+ const dir = moduleDir();
15721
+ for (const candidate of [
15722
+ path13.resolve(dir, ".."),
15723
+ path13.resolve(dir, "..", ".."),
15724
+ path13.resolve(dir, "..", "..", "..")
15725
+ ]) {
15726
+ if (existsSync7(path13.join(candidate, "package.json")))
15727
+ return candidate;
15728
+ }
15729
+ return path13.resolve(dir, "..");
15730
+ };
15731
+ var assetsRoot = () => path13.join(packageRoot2(), "assets");
15732
+
15733
+ // packages/workit-opencode/src/stale-sources.ts
15734
+ import { existsSync as existsSync8 } from "node:fs";
15735
+ import path14 from "node:path";
15736
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
15737
+ var packageFiles = [
15738
+ "src/index.ts",
15739
+ "src/plugin.ts",
15740
+ "src/v1/server.ts",
15741
+ "src/v2/plugin.ts",
15742
+ "src/shared/tools.ts",
15743
+ "src/tools/workit.ts"
15744
+ ].map((file) => path14.join(packageRoot2(), file));
15745
+ var coreDir = path14.join(packageRoot2(), "..", "workit-core", "src", "core");
15413
15746
  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)),
15747
+ fileURLToPath3(import.meta.url),
15748
+ ...packageFiles,
15417
15749
  ...[
15418
15750
  "task-contract.ts",
15419
15751
  "task-engine.ts",
@@ -15422,12 +15754,11 @@ var pluginSourceFiles = [
15422
15754
  "workers.ts",
15423
15755
  "authority.ts",
15424
15756
  "methods.ts"
15425
- ].map((file) => fileURLToPath2(new URL(`../../workit-core/src/core/${file}`, import.meta.url)))
15426
- ].filter((file) => existsSync7(file));
15757
+ ].map((file) => path14.join(coreDir, file))
15758
+ ].filter((file) => existsSync8(file));
15427
15759
 
15428
15760
  // packages/workit-opencode/src/tools/repo.ts
15429
- import path22 from "node:path";
15430
- import { realpathSync as realpathSync8 } from "node:fs";
15761
+ import { realpathSync as realpathSync9 } from "node:fs";
15431
15762
 
15432
15763
  // node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
15433
15764
  var exports_external = {};
@@ -27751,7 +28082,7 @@ function tool(input) {
27751
28082
  tool.schema = exports_external;
27752
28083
  // packages/workit-core/src/core/changelog.ts
27753
28084
  import fs8 from "node:fs";
27754
- import path13 from "node:path";
28085
+ import path15 from "node:path";
27755
28086
  import { createHash as createHash6 } from "node:crypto";
27756
28087
 
27757
28088
  // packages/workit-core/src/core/scripts.ts
@@ -27948,7 +28279,7 @@ function applyChangelog(target, entries, normalizeOnly) {
27948
28279
  const rendered = renderChangelog(source.text, entries, normalizeOnly);
27949
28280
  if ("error" in rendered)
27950
28281
  return { error: rendered.error };
27951
- fs8.mkdirSync(path13.dirname(target), { recursive: true });
28282
+ fs8.mkdirSync(path15.dirname(target), { recursive: true });
27952
28283
  fs8.writeFileSync(target, rendered.text, "utf8");
27953
28284
  return { ...rendered.result, path: target };
27954
28285
  }
@@ -27983,14 +28314,14 @@ function renderChangelog(source, entries, normalizeOnly) {
27983
28314
  var changelogTarget = (workspaceRoot, changelogPath) => {
27984
28315
  try {
27985
28316
  const cwd = fs8.realpathSync(resolveWorkspaceRoot(workspaceRoot));
27986
- const target = path13.resolve(cwd, changelogPath || "CHANGELOG.md");
27987
- if (target !== cwd && !target.startsWith(cwd + path13.sep))
28317
+ const target = path15.resolve(cwd, changelogPath || "CHANGELOG.md");
28318
+ if (target !== cwd && !target.startsWith(cwd + path15.sep))
27988
28319
  return { error: "changelog path must be inside workspace_root" };
27989
28320
  let ancestor = target;
27990
28321
  while (!fs8.existsSync(ancestor))
27991
- ancestor = path13.dirname(ancestor);
28322
+ ancestor = path15.dirname(ancestor);
27992
28323
  const canonicalAncestor = fs8.realpathSync(ancestor);
27993
- if (canonicalAncestor !== cwd && !canonicalAncestor.startsWith(cwd + path13.sep))
28324
+ if (canonicalAncestor !== cwd && !canonicalAncestor.startsWith(cwd + path15.sep))
27994
28325
  return { error: "changelog path must be inside workspace_root" };
27995
28326
  return { cwd, target: fs8.existsSync(target) ? fs8.realpathSync(target) : target };
27996
28327
  } catch {
@@ -28138,121 +28469,9 @@ function parseVerifyOutput(stdout) {
28138
28469
  return { passed, failed, skipped, commands };
28139
28470
  }
28140
28471
 
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
28472
  // packages/workit-core/src/core/repo-context.ts
28254
28473
  import { spawnSync as spawnSync4 } from "node:child_process";
28255
- import { existsSync as existsSync10, readFileSync as readFileSync10, readdirSync as readdirSync5 } from "node:fs";
28474
+ import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync5 } from "node:fs";
28256
28475
  import path16 from "node:path";
28257
28476
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
28258
28477
  try {
@@ -28287,7 +28506,7 @@ var runGit = (cwd, args) => {
28287
28506
  exitCode: result.status ?? 1
28288
28507
  };
28289
28508
  };
28290
- var repoRoot2 = (cwd) => {
28509
+ var repoRoot = (cwd) => {
28291
28510
  const r = runGit(cwd, ["rev-parse", "--show-toplevel"]);
28292
28511
  return r.exitCode === 0 && r.stdout ? r.stdout : cwd;
28293
28512
  };
@@ -28324,7 +28543,7 @@ function resolvePrBranchContext(cwd) {
28324
28543
  if (branch === "unknown") {
28325
28544
  return {
28326
28545
  ok: false,
28327
- error: `not in a git repository at ${repoRoot2(cwd)} — open the target repository as the OpenCode session directory`
28546
+ error: `not in a git repository at ${repoRoot(cwd)} — open the target repository as the OpenCode session directory`
28328
28547
  };
28329
28548
  }
28330
28549
  return {
@@ -28419,10 +28638,10 @@ var diffStatForRange = (cwd, range) => {
28419
28638
  };
28420
28639
  var packageScripts = (cwd, keys) => {
28421
28640
  const pkgPath = path16.join(cwd, "package.json");
28422
- if (!existsSync10(pkgPath))
28641
+ if (!existsSync9(pkgPath))
28423
28642
  return [];
28424
28643
  try {
28425
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
28644
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
28426
28645
  return keys.filter((k) => pkg.scripts && typeof pkg.scripts[k] === "string").map((k) => `${k}: ${pkg.scripts?.[k]}`);
28427
28646
  } catch {
28428
28647
  return [];
@@ -28454,15 +28673,15 @@ var documentationFiles = (cwd) => {
28454
28673
  return matches.sort().slice(0, 200);
28455
28674
  };
28456
28675
  var readTrimmed = (file, maxLines) => {
28457
- if (!existsSync10(file))
28676
+ if (!existsSync9(file))
28458
28677
  return "";
28459
- const lines = readFileSync10(file, "utf8").split(`
28678
+ const lines = readFileSync8(file, "utf8").split(`
28460
28679
  `);
28461
28680
  return lines.slice(0, maxLines).join(`
28462
28681
  `);
28463
28682
  };
28464
28683
  function prReadyContext(root, range) {
28465
- const cwd = path16.resolve(repoRoot2(root));
28684
+ const cwd = path16.resolve(repoRoot(root));
28466
28685
  if (range !== undefined && !isResolvableContextRange(cwd, range))
28467
28686
  return invalidRange(cwd);
28468
28687
  let stdout = "";
@@ -28530,7 +28749,7 @@ ${status.stderr}` : "");
28530
28749
  stdout += `
28531
28750
  `;
28532
28751
  stdout += printSection("Recent Validation Signals");
28533
- if (existsSync10(path16.join(cwd, "package.json"))) {
28752
+ if (existsSync9(path16.join(cwd, "package.json"))) {
28534
28753
  stdout += `package.json detected. Common scripts:
28535
28754
  `;
28536
28755
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -28539,7 +28758,7 @@ ${status.stderr}` : "");
28539
28758
  `) + `
28540
28759
  `;
28541
28760
  }
28542
- if (existsSync10(path16.join(cwd, "Cargo.toml")) || existsSync10(path16.join(cwd, "src-tauri/Cargo.toml"))) {
28761
+ if (existsSync9(path16.join(cwd, "Cargo.toml")) || existsSync9(path16.join(cwd, "src-tauri/Cargo.toml"))) {
28543
28762
  stdout += `Rust project detected.
28544
28763
  `;
28545
28764
  }
@@ -28572,7 +28791,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
28572
28791
  - Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
28573
28792
  - If Unreleased already has duplicate category headings, normalize_only first.`;
28574
28793
  function changelogContext(root, range) {
28575
- const cwd = path16.resolve(repoRoot2(root));
28794
+ const cwd = path16.resolve(repoRoot(root));
28576
28795
  if (range !== undefined && !isResolvableContextRange(cwd, range))
28577
28796
  return invalidRange(cwd);
28578
28797
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -28591,7 +28810,7 @@ function changelogContext(root, range) {
28591
28810
  `;
28592
28811
  stdout += printSection("Existing CHANGELOG.md");
28593
28812
  const changelogPath = path16.join(cwd, "CHANGELOG.md");
28594
- if (existsSync10(changelogPath)) {
28813
+ if (existsSync9(changelogPath)) {
28595
28814
  stdout += readTrimmed(changelogPath, 260) + `
28596
28815
  `;
28597
28816
  } else {
@@ -28610,7 +28829,7 @@ function changelogContext(root, range) {
28610
28829
  return { stdout, stderr: "", exitCode: 0, cwd };
28611
28830
  }
28612
28831
  function docsRefreshContext(root, range) {
28613
- const cwd = path16.resolve(repoRoot2(root));
28832
+ const cwd = path16.resolve(repoRoot(root));
28614
28833
  if (range !== undefined && !isResolvableContextRange(cwd, range))
28615
28834
  return invalidRange(cwd);
28616
28835
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -28633,7 +28852,7 @@ function docsRefreshContext(root, range) {
28633
28852
  `;
28634
28853
  stdout += printSection("README Preview");
28635
28854
  const readme = path16.join(cwd, "README.md");
28636
- if (existsSync10(readme)) {
28855
+ if (existsSync9(readme)) {
28637
28856
  stdout += readTrimmed(readme, 220) + `
28638
28857
  `;
28639
28858
  } else {
@@ -28642,9 +28861,9 @@ function docsRefreshContext(root, range) {
28642
28861
  }
28643
28862
  stdout += printSection("Package Scripts");
28644
28863
  const pkgPath = path16.join(cwd, "package.json");
28645
- if (existsSync10(pkgPath)) {
28864
+ if (existsSync9(pkgPath)) {
28646
28865
  try {
28647
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
28866
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
28648
28867
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
28649
28868
  `;
28650
28869
  } catch {}
@@ -28655,7 +28874,7 @@ function docsRefreshContext(root, range) {
28655
28874
  return { stdout, stderr: "", exitCode: 0, cwd };
28656
28875
  }
28657
28876
  function releaseNotesContext(root, rangeOrTag) {
28658
- const cwd = path16.resolve(repoRoot2(root));
28877
+ const cwd = path16.resolve(repoRoot(root));
28659
28878
  if (!rangeOrTag) {
28660
28879
  return { stdout: "", stderr: `ERROR: release tag or range required
28661
28880
  `, exitCode: 1, cwd };
@@ -28693,7 +28912,7 @@ function releaseNotesContext(root, rangeOrTag) {
28693
28912
  `;
28694
28913
  stdout += printSection("Existing Release Files");
28695
28914
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
28696
- if (existsSync10(path16.join(cwd, rel)))
28915
+ if (existsSync9(path16.join(cwd, rel)))
28697
28916
  stdout += rel + `
28698
28917
  `;
28699
28918
  }
@@ -28722,7 +28941,7 @@ function releaseNotesContext(root, rangeOrTag) {
28722
28941
 
28723
28942
  // packages/workit-core/src/core/verify-project.ts
28724
28943
  import { spawnSync as spawnSync5 } from "node:child_process";
28725
- import { existsSync as existsSync11, readFileSync as readFileSync11, statSync as statSync2 } from "node:fs";
28944
+ import { existsSync as existsSync10, readFileSync as readFileSync9, statSync as statSync2 } from "node:fs";
28726
28945
  import path17 from "node:path";
28727
28946
  var commandOnPath = (name) => {
28728
28947
  const dirs = (process.env.PATH ?? "").split(path17.delimiter);
@@ -28747,33 +28966,33 @@ var commandOnPath = (name) => {
28747
28966
  };
28748
28967
  var hasScript = (cwd, name) => {
28749
28968
  const pkgPath = path17.join(cwd, "package.json");
28750
- if (!existsSync11(pkgPath))
28969
+ if (!existsSync10(pkgPath))
28751
28970
  return false;
28752
28971
  try {
28753
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
28972
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
28754
28973
  return Boolean(pkg.scripts && typeof pkg.scripts[name] === "string");
28755
28974
  } catch {
28756
28975
  return false;
28757
28976
  }
28758
28977
  };
28759
28978
  var packageRunner = (cwd) => {
28760
- if (existsSync11(path17.join(cwd, "pnpm-lock.yaml")))
28979
+ if (existsSync10(path17.join(cwd, "pnpm-lock.yaml")))
28761
28980
  return "pnpm";
28762
28981
  const pkgPath = path17.join(cwd, "package.json");
28763
- if (existsSync11(pkgPath)) {
28982
+ if (existsSync10(pkgPath)) {
28764
28983
  try {
28765
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
28984
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
28766
28985
  if (typeof pkg.packageManager === "string" && pkg.packageManager.startsWith("pnpm")) {
28767
28986
  return "pnpm";
28768
28987
  }
28769
28988
  } catch {}
28770
28989
  }
28771
- if (existsSync11(path17.join(cwd, "yarn.lock")))
28990
+ if (existsSync10(path17.join(cwd, "yarn.lock")))
28772
28991
  return "yarn";
28773
28992
  return "npm";
28774
28993
  };
28775
28994
  function runVerifyProject(root, dryRun = false) {
28776
- const cwd = repoRoot2(root);
28995
+ const cwd = repoRoot(root);
28777
28996
  let passed = 0;
28778
28997
  let failed = 0;
28779
28998
  let skipped = 0;
@@ -28829,7 +29048,7 @@ root: ${cwd}
28829
29048
  lines.push(`dry_run: ${String(dryRun)}
28830
29049
  `);
28831
29050
  const pkgPath = path17.join(cwd, "package.json");
28832
- if (existsSync11(pkgPath)) {
29051
+ if (existsSync10(pkgPath)) {
28833
29052
  const runner = packageRunner(cwd);
28834
29053
  for (const script of ["lint", "format:check", "test", "build"]) {
28835
29054
  if (hasScript(cwd, script)) {
@@ -28844,7 +29063,7 @@ root: ${cwd}
28844
29063
  } else {
28845
29064
  skipCheck("javascript", "package.json not found");
28846
29065
  }
28847
- const cargoManifest = existsSync11(path17.join(cwd, "Cargo.toml")) ? "Cargo.toml" : existsSync11(path17.join(cwd, "src-tauri/Cargo.toml")) ? "src-tauri/Cargo.toml" : "";
29066
+ const cargoManifest = existsSync10(path17.join(cwd, "Cargo.toml")) ? "Cargo.toml" : existsSync10(path17.join(cwd, "src-tauri/Cargo.toml")) ? "src-tauri/Cargo.toml" : "";
28848
29067
  if (cargoManifest) {
28849
29068
  runCheck("cargo fmt", "cargo", ["fmt", "--manifest-path", cargoManifest, "--", "--check"]);
28850
29069
  runCheck("cargo clippy", "cargo", [
@@ -28860,7 +29079,7 @@ root: ${cwd}
28860
29079
  } else {
28861
29080
  skipCheck("rust", "Cargo.toml not found");
28862
29081
  }
28863
- if (existsSync11(path17.join(cwd, "pyproject.toml")) || existsSync11(path17.join(cwd, "pytest.ini")) || existsSync11(path17.join(cwd, "tests"))) {
29082
+ if (existsSync10(path17.join(cwd, "pyproject.toml")) || existsSync10(path17.join(cwd, "pytest.ini")) || existsSync10(path17.join(cwd, "tests"))) {
28864
29083
  if (commandOnPath("pytest"))
28865
29084
  runCheck("pytest", "pytest", []);
28866
29085
  else
@@ -28882,8 +29101,8 @@ root: ${cwd}
28882
29101
  lines.push(`status: skipped (dry run)
28883
29102
  `);
28884
29103
  skipped += 1;
28885
- } else if (existsSync11(changelogPath)) {
28886
- if (/## \[Unreleased\]/i.test(readFileSync11(changelogPath, "utf8"))) {
29104
+ } else if (existsSync10(changelogPath)) {
29105
+ if (/## \[Unreleased\]/i.test(readFileSync9(changelogPath, "utf8"))) {
28887
29106
  lines.push(`status: pass
28888
29107
  `);
28889
29108
  passed += 1;
@@ -28997,12 +29216,12 @@ function whichOnPath(tool) {
28997
29216
  return null;
28998
29217
  }
28999
29218
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
29000
- function repoRoot3(cwd) {
29219
+ function repoRoot2(cwd) {
29001
29220
  const result = spawnSync6("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
29002
29221
  return result.status === 0 ? (result.stdout ?? "").trim() : cwd;
29003
29222
  }
29004
29223
  function prCreate(env, cwd) {
29005
- const root = cwd ? repoRoot3(cwd) : process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
29224
+ const root = cwd ? repoRoot2(cwd) : process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
29006
29225
  const policy = resolveBranchPolicyFor(root);
29007
29226
  const cfg = vcsConfig("load", root);
29008
29227
  if (!cfg.ok)
@@ -29195,7 +29414,7 @@ function prCreate(env, cwd) {
29195
29414
  };
29196
29415
  }
29197
29416
  function mergePr(cwd, opts) {
29198
- const root = repoRoot3(cwd);
29417
+ const root = repoRoot2(cwd);
29199
29418
  const cfg = vcsConfig("load", root);
29200
29419
  if (!cfg.ok)
29201
29420
  return { error: cfg.error ?? "vcs config missing" };
@@ -29244,14 +29463,14 @@ function mergePr(cwd, opts) {
29244
29463
 
29245
29464
  // packages/workit-core/src/core/init.ts
29246
29465
  import fs11 from "node:fs";
29247
- import path21 from "node:path";
29466
+ import path23 from "node:path";
29248
29467
  import { spawnSync as spawnSync8 } from "node:child_process";
29249
29468
 
29250
29469
  // packages/workit-core/src/core/safe-write.ts
29251
- import { writeFileSync as writeFileSync6 } from "node:fs";
29470
+ import { writeFileSync as writeFileSync4 } from "node:fs";
29252
29471
  function writeFileExclusive(file, content, mode) {
29253
29472
  try {
29254
- writeFileSync6(file, content, { encoding: "utf8", flag: "wx", mode });
29473
+ writeFileSync4(file, content, { encoding: "utf8", flag: "wx", mode });
29255
29474
  return "created";
29256
29475
  } catch (err) {
29257
29476
  if (err.code === "EEXIST")
@@ -29264,19 +29483,19 @@ function writeFileExclusive(file, content, mode) {
29264
29483
  import {
29265
29484
  copyFileSync as copyFileSync2,
29266
29485
  cpSync as cpSync2,
29267
- existsSync as existsSync12,
29486
+ existsSync as existsSync13,
29268
29487
  lstatSync as lstatSync4,
29269
29488
  mkdirSync as mkdirSync5,
29270
29489
  mkdtempSync,
29271
29490
  readFileSync as readFileSync12,
29272
29491
  readdirSync as readdirSync6,
29273
- realpathSync as realpathSync7,
29492
+ realpathSync as realpathSync8,
29274
29493
  renameSync as renameSync2,
29275
29494
  rmSync,
29276
29495
  statSync as statSync3,
29277
29496
  writeFileSync as writeFileSync7
29278
29497
  } from "node:fs";
29279
- import path19 from "node:path";
29498
+ import path21 from "node:path";
29280
29499
  import { isDeepStrictEqual } from "node:util";
29281
29500
 
29282
29501
  // packages/workit-core/src/core/branch-policy.ts
@@ -29339,6 +29558,118 @@ var detectBranchPolicy = (workspaceRoot) => {
29339
29558
  };
29340
29559
  };
29341
29560
 
29561
+ // packages/workit-core/src/core/gitignore.ts
29562
+ import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
29563
+ import path19 from "node:path";
29564
+ var GITIGNORE_ENTRIES = [
29565
+ "# workit: SDD working state (never commit)",
29566
+ "docs/*/sdd/",
29567
+ "",
29568
+ "# OS / editor cruft",
29569
+ ".DS_Store",
29570
+ "Thumbs.db",
29571
+ "*.swp",
29572
+ ".idea/",
29573
+ ".vscode/",
29574
+ ".env",
29575
+ "node_modules/",
29576
+ "dist/",
29577
+ "*.log",
29578
+ ".cache/"
29579
+ ];
29580
+ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
29581
+ if (!confirmed)
29582
+ return { ok: false, error: "confirmed: true required" };
29583
+ const file = path19.join(workspaceRoot, ".gitignore");
29584
+ const existing = existsSync11(file) ? readFileSync10(file, "utf8") : "";
29585
+ const existingLines = new Set(existing.split(`
29586
+ `).map((l) => l.trim()).filter(Boolean));
29587
+ const added = [];
29588
+ const append = [];
29589
+ for (const entry of GITIGNORE_ENTRIES) {
29590
+ if (entry.trim() === "" || existingLines.has(entry.trim()))
29591
+ continue;
29592
+ append.push(entry);
29593
+ added.push(entry);
29594
+ }
29595
+ if (append.length) {
29596
+ const separator = existing && !existing.endsWith(`
29597
+ `) ? `
29598
+ ` : "";
29599
+ writeFileSync5(file, existing + separator + (existing ? `
29600
+ ` : "") + append.join(`
29601
+ `) + `
29602
+ `, "utf8");
29603
+ } else if (!existsSync11(file)) {
29604
+ writeFileSync5(file, "", "utf8");
29605
+ }
29606
+ return { ok: true, path: file, added };
29607
+ };
29608
+
29609
+ // packages/workit-core/src/core/hygiene.ts
29610
+ import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
29611
+ import path20 from "node:path";
29612
+ var repoRoot3 = assetRoot();
29613
+ var templatesDir = () => path20.join(repoRoot3, "templates", "hygiene");
29614
+ var packageJson = (root) => {
29615
+ if (!existsSync12(path20.join(root, "package.json")))
29616
+ return null;
29617
+ try {
29618
+ return JSON.parse(readFileSync11(path20.join(root, "package.json"), "utf8"));
29619
+ } catch {
29620
+ return null;
29621
+ }
29622
+ };
29623
+ var isOpenSource = (root) => {
29624
+ if (existsSync12(path20.join(root, "LICENSE")))
29625
+ return true;
29626
+ const pkg = packageJson(root);
29627
+ if (pkg && !pkg.private)
29628
+ return true;
29629
+ return path20.basename(root) === "workflow-toolkit";
29630
+ };
29631
+ var licenseHolder = (root) => {
29632
+ const pkg = packageJson(root);
29633
+ if (pkg) {
29634
+ const author = pkg.author;
29635
+ if (typeof author === "string" && author.trim())
29636
+ return author.trim();
29637
+ if (author && typeof author === "object") {
29638
+ const name = author.name;
29639
+ if (typeof name === "string" && name.trim())
29640
+ return name.trim();
29641
+ }
29642
+ }
29643
+ return "";
29644
+ };
29645
+ var planHygieneFiles = (root, opts = {}) => {
29646
+ const openSource = opts.includeOpenSource ?? isOpenSource(root);
29647
+ const files = ["CHANGELOG.md", "README.md", ".editorconfig", ".gitattributes"];
29648
+ if (openSource)
29649
+ files.push("LICENSE", "CONTRIBUTING.md");
29650
+ const planned = [];
29651
+ for (const file of files) {
29652
+ if (existsSync12(path20.join(root, file)))
29653
+ continue;
29654
+ const tpl = path20.join(templatesDir(), file);
29655
+ if (!existsSync12(tpl))
29656
+ continue;
29657
+ const content = readFileSync11(tpl, "utf8").replace(/<PROJECT>/g, path20.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
29658
+ planned.push({ path: path20.join(root, file), content });
29659
+ }
29660
+ return planned;
29661
+ };
29662
+ var ensureHygieneFiles = (root, opts) => {
29663
+ if (!opts.confirmed)
29664
+ return { ok: false, error: "confirmed: true required" };
29665
+ const created = [];
29666
+ for (const planned of planHygieneFiles(root, opts)) {
29667
+ writeFileSync6(planned.path, planned.content, "utf8");
29668
+ created.push(path20.basename(planned.path));
29669
+ }
29670
+ return { ok: true, created };
29671
+ };
29672
+
29342
29673
  // packages/workit-core/src/core/registration.ts
29343
29674
  var CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@latest";
29344
29675
  var CURSOR_HOOK_RUN_COMMAND = `npx -y --prefer-online --min-release-age=0 --package=${CURSOR_RUNTIME_PACKAGE} workit-cursor-hook`;
@@ -29380,12 +29711,12 @@ var VCS_OVERRIDES = [
29380
29711
  var SETUP_OVERRIDES = [...YT_OVERRIDES, ...VCS_OVERRIDES];
29381
29712
  function applyWorkspaceBranchPolicy(opts) {
29382
29713
  const { workspace_root, env = process.env } = opts;
29383
- const dir = path19.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
29714
+ const dir = path21.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
29384
29715
  const { status, path: wsPath, entries } = readWorkspacesResult(dir);
29385
29716
  if (status === "malformed")
29386
29717
  return { ok: false, error: `malformed workspaces.json: ${wsPath}` };
29387
29718
  const detection = detectBranchPolicy(workspace_root);
29388
- const name = String(env.WORKFLOW_BP_NAME ?? path19.basename(workspace_root));
29719
+ const name = String(env.WORKFLOW_BP_NAME ?? path21.basename(workspace_root));
29389
29720
  const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
29390
29721
  const policy = {
29391
29722
  preset: detection.preset,
@@ -29410,7 +29741,7 @@ function applyWorkspaceBranchPolicy(opts) {
29410
29741
  };
29411
29742
  }
29412
29743
  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 });
29744
+ mkdirSync5(path21.dirname(wsPath), { recursive: true });
29414
29745
  writeFileSync7(wsPath, JSON.stringify({ workspaces: next }, null, 2) + `
29415
29746
  `, "utf8");
29416
29747
  return {
@@ -29424,7 +29755,7 @@ function applyWorkspaceBranchPolicy(opts) {
29424
29755
 
29425
29756
  // packages/workit-core/src/core/youtrack.ts
29426
29757
  import fs10 from "node:fs";
29427
- import path20 from "node:path";
29758
+ import path22 from "node:path";
29428
29759
 
29429
29760
  // packages/workit-core/src/core/templates.ts
29430
29761
  var repoRoot4 = assetRoot();
@@ -29432,8 +29763,8 @@ var repoRoot4 = assetRoot();
29432
29763
  // packages/workit-core/src/core/youtrack.ts
29433
29764
  var ISSUE_RE = /^[A-Z]+-\d+$/;
29434
29765
  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");
29766
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path22.join(configDir(), "youtrack.json");
29767
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path22.join(resolveConfigDir(), "youtrack.json");
29437
29768
  var youTrackTokenModeOk = (p) => {
29438
29769
  if (process.platform === "win32")
29439
29770
  return true;
@@ -29477,7 +29808,7 @@ function youTrackConfigLoad() {
29477
29808
  }
29478
29809
  const cfgPath = loaded.path;
29479
29810
  const tokenFile = String(loaded.config.tokenFile ?? "");
29480
- const tokenPath = tokenFile ? path20.isAbsolute(tokenFile) ? path20.resolve(tokenFile) : path20.resolve(process.cwd(), tokenFile) : "";
29811
+ const tokenPath = tokenFile ? path22.isAbsolute(tokenFile) ? path22.resolve(tokenFile) : path22.resolve(process.cwd(), tokenFile) : "";
29481
29812
  if (!tokenPath || !fs10.existsSync(tokenPath)) {
29482
29813
  return { error: "missing youtrack.token" };
29483
29814
  }
@@ -29491,8 +29822,8 @@ function youTrackConfigLoad() {
29491
29822
  const redacted = { ...loaded.config };
29492
29823
  delete redacted.tokenFile;
29493
29824
  redacted.tokenPresent = true;
29494
- redacted.configPath = path20.resolve(cfgPath);
29495
- redacted.tokenPath = path20.resolve(tokenPath);
29825
+ redacted.configPath = path22.resolve(cfgPath);
29826
+ redacted.tokenPath = path22.resolve(tokenPath);
29496
29827
  return { data: redacted };
29497
29828
  }
29498
29829
  function tzParts(date, tz) {
@@ -29605,7 +29936,7 @@ var youTrackToken = () => {
29605
29936
  if ("error" in loaded)
29606
29937
  return loaded;
29607
29938
  const tokenFile = String(loaded.config.tokenFile ?? "");
29608
- const tokenPath = tokenFile ? path20.isAbsolute(tokenFile) ? path20.resolve(tokenFile) : path20.resolve(process.cwd(), tokenFile) : "";
29939
+ const tokenPath = tokenFile ? path22.isAbsolute(tokenFile) ? path22.resolve(tokenFile) : path22.resolve(process.cwd(), tokenFile) : "";
29609
29940
  if (!tokenPath || !fs10.existsSync(tokenPath))
29610
29941
  return { error: "missing youtrack.token" };
29611
29942
  if (!youTrackTokenModeOk(tokenPath))
@@ -29760,7 +30091,7 @@ function youTrackTokenCreateUrl() {
29760
30091
  const desc = String(defaults.description ?? "OpenCode workit — /wk-issue-update and /wk-meetings");
29761
30092
  const scopes = Array.isArray(defaults.scopes) ? defaults.scopes : ["YouTrack"];
29762
30093
  const base = String(config.baseUrl ?? "https://enghouseamg.youtrack.cloud").replace(/\/+$/, "");
29763
- const tokenFile = String(config.tokenFile ?? path20.join(path20.dirname(loaded.path), "youtrack.token"));
30094
+ const tokenFile = String(config.tokenFile ?? path22.join(path22.dirname(loaded.path), "youtrack.token"));
29764
30095
  const tab = String(defaults.profileTab ?? "account-security");
29765
30096
  const createUrl = `${base}/users/me?${new URLSearchParams({ tab })}`;
29766
30097
  const docsUrl = "https://www.jetbrains.com/help/youtrack/cloud/manage-permanent-token.html";
@@ -29769,7 +30100,7 @@ function youTrackTokenCreateUrl() {
29769
30100
  tokenName: name,
29770
30101
  tokenDescription: desc,
29771
30102
  scopes,
29772
- tokenFile: path20.resolve(tokenFile),
30103
+ tokenFile: path22.resolve(tokenFile),
29773
30104
  createUrl,
29774
30105
  docsUrl,
29775
30106
  prefillSupported: false,
@@ -30087,12 +30418,12 @@ var resolvePath = (p) => {
30087
30418
  try {
30088
30419
  return fs11.realpathSync(p);
30089
30420
  } catch {
30090
- return path21.resolve(p);
30421
+ return path23.resolve(p);
30091
30422
  }
30092
30423
  };
30093
30424
  function initStatusData(configDirPath = configDir()) {
30094
- const ytJson = path21.join(configDirPath, "youtrack.json");
30095
- const vcsJson = path21.join(configDirPath, "vcs.json");
30425
+ const ytJson = path23.join(configDirPath, "youtrack.json");
30426
+ const vcsJson = path23.join(configDirPath, "vcs.json");
30096
30427
  const items = [];
30097
30428
  let youtrackConfig = null;
30098
30429
  let youtrackTokenCreate = null;
@@ -30115,8 +30446,8 @@ function initStatusData(configDirPath = configDir()) {
30115
30446
  });
30116
30447
  }
30117
30448
  }
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;
30449
+ const expanded = tokenFile ? path23.resolve(tokenFile) : null;
30450
+ const resolvedTokenFile = expanded && fs11.existsSync(expanded) ? resolvePath(expanded) : expanded ? path23.isAbsolute(expanded) ? expanded : path23.resolve(configDirPath, expanded) : null;
30120
30451
  youtrackConfig = {
30121
30452
  config_edit_path: resolvePath(ytJson),
30122
30453
  baseUrl: base,
@@ -30163,11 +30494,11 @@ function initStatusData(configDirPath = configDir()) {
30163
30494
  id: "youtrack_json",
30164
30495
  label: "YouTrack config",
30165
30496
  ok: fs11.existsSync(ytJson) && youtrackConfig !== null && !("error" in youtrackConfig),
30166
- path: fs11.existsSync(ytJson) ? resolvePath(ytJson) : path21.resolve(ytJson),
30497
+ path: fs11.existsSync(ytJson) ? resolvePath(ytJson) : path23.resolve(ytJson),
30167
30498
  config_edit_path: resolvePath(ytJson),
30168
30499
  fix: "workit_init_apply action=youtrack_scaffold"
30169
30500
  });
30170
- const ytTokenPath = youtrackConfig?.tokenFile ?? path21.join(configDirPath, "youtrack.token");
30501
+ const ytTokenPath = youtrackConfig?.tokenFile ?? path23.join(configDirPath, "youtrack.token");
30171
30502
  const tokenText = fs11.existsSync(ytTokenPath) ? fs11.readFileSync(ytTokenPath, "utf8").trim() : "";
30172
30503
  const placeholder = fs11.existsSync(ytTokenPath) && isPlaceholder(tokenText);
30173
30504
  const tokenOk = fs11.existsSync(ytTokenPath) && modeOk(ytTokenPath) && Boolean(tokenText) && !isPlaceholder(tokenText);
@@ -30175,8 +30506,8 @@ function initStatusData(configDirPath = configDir()) {
30175
30506
  id: "youtrack_token",
30176
30507
  label: "YouTrack API token (mode 600, not placeholder)",
30177
30508
  ok: tokenOk,
30178
- path: fs11.existsSync(ytTokenPath) ? resolvePath(ytTokenPath) : path21.resolve(ytTokenPath),
30179
- token_edit_path: fs11.existsSync(ytTokenPath) ? resolvePath(ytTokenPath) : path21.resolve(ytTokenPath),
30509
+ path: fs11.existsSync(ytTokenPath) ? resolvePath(ytTokenPath) : path23.resolve(ytTokenPath),
30510
+ token_edit_path: fs11.existsSync(ytTokenPath) ? resolvePath(ytTokenPath) : path23.resolve(ytTokenPath),
30180
30511
  placeholder,
30181
30512
  fix: `Open ${resolvePath(ytTokenPath)} — replace ${TOKEN_PLACEHOLDER3} with your permanent token, save, then /wk-status`
30182
30513
  };
@@ -30202,7 +30533,7 @@ function initStatusData(configDirPath = configDir()) {
30202
30533
  const provider = typeof rawProvider === "string" && rawProvider.trim() ? rawProvider.toLowerCase() : null;
30203
30534
  const tokenFiles = {};
30204
30535
  for (const k of ["gitlab", "github"]) {
30205
- tokenFiles[k] = String(vcsParsed[k]?.tokenFile ?? path21.join(configDirPath, `${k}.token`));
30536
+ tokenFiles[k] = String(vcsParsed[k]?.tokenFile ?? path23.join(configDirPath, `${k}.token`));
30206
30537
  }
30207
30538
  vcsCfg = {
30208
30539
  config_edit_path: resolvePath(vcsJson),
@@ -30227,14 +30558,14 @@ function initStatusData(configDirPath = configDir()) {
30227
30558
  id: "vcs_json",
30228
30559
  label: "VCS config (GitLab / GitHub)",
30229
30560
  ok: fs11.existsSync(vcsJson) && vcsCfg !== null && !("error" in vcsCfg),
30230
- path: fs11.existsSync(vcsJson) ? resolvePath(vcsJson) : path21.resolve(vcsJson),
30561
+ path: fs11.existsSync(vcsJson) ? resolvePath(vcsJson) : path23.resolve(vcsJson),
30231
30562
  config_edit_path: resolvePath(vcsJson),
30232
30563
  fix: "workit_init_apply action=vcs_scaffold"
30233
30564
  });
30234
30565
  const provActive = vcsCfg && !("error" in vcsCfg) ? vcsCfg.provider : null;
30235
30566
  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);
30567
+ const t = path23.isAbsolute(rawPath) ? rawPath : path23.resolve(configDirPath, rawPath);
30568
+ const abs = fs11.existsSync(t) ? resolvePath(t) : path23.resolve(t);
30238
30569
  const text = fs11.existsSync(t) ? fs11.readFileSync(t, "utf8").trim() : "";
30239
30570
  const ph = isPlaceholder(text);
30240
30571
  const ok = fs11.existsSync(t) && modeOk(t) && Boolean(text) && !ph;
@@ -30265,7 +30596,7 @@ function initStatusData(configDirPath = configDir()) {
30265
30596
  };
30266
30597
  const vcsTokenFiles = {};
30267
30598
  for (const k of ["gitlab", "github"]) {
30268
- vcsTokenFiles[k] = String(vcsParsed?.[k]?.tokenFile ?? path21.join(configDirPath, `${k}.token`));
30599
+ vcsTokenFiles[k] = String(vcsParsed?.[k]?.tokenFile ?? path23.join(configDirPath, `${k}.token`));
30269
30600
  }
30270
30601
  items.push(tokenItem("gitlab_token", "GitLab token (mode 600, not placeholder)", vcsTokenFiles.gitlab, "gitlab"));
30271
30602
  items.push(tokenItem("github_token", "GitHub token (mode 600, not placeholder)", vcsTokenFiles.github, "github"));
@@ -30322,7 +30653,7 @@ async function toolkitStatusData(configDirPath = configDir()) {
30322
30653
  }
30323
30654
  var youtrackJsonContent = (dir) => ({
30324
30655
  baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://enghouseamg.youtrack.cloud",
30325
- tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path21.join(dir, "youtrack.token"),
30656
+ tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path23.join(dir, "youtrack.token"),
30326
30657
  timezone: process.env.WORKFLOW_YT_TIMEZONE ?? "America/Santiago",
30327
30658
  locale: "es-CL",
30328
30659
  defaultMention: process.env.WORKFLOW_YT_MENTION ?? "Alejandra.Flores",
@@ -30361,11 +30692,11 @@ var vcsJsonContent = (dir, cwd) => {
30361
30692
  gitlab: {
30362
30693
  host: process.env.WORKFLOW_GITLAB_HOST ?? "gitlab.com",
30363
30694
  apiUrl: process.env.WORKFLOW_GITLAB_API_URL ?? "https://gitlab.com/api/v4",
30364
- tokenFile: path21.join(dir, "gitlab.token")
30695
+ tokenFile: path23.join(dir, "gitlab.token")
30365
30696
  },
30366
30697
  github: {
30367
30698
  host: process.env.WORKFLOW_GITHUB_HOST ?? "github.com",
30368
- tokenFile: path21.join(dir, "github.token")
30699
+ tokenFile: path23.join(dir, "github.token")
30369
30700
  },
30370
30701
  pr: { squashOnMerge: true, removeSourceBranch: true, pushBranch: true, confirmSkip: true },
30371
30702
  tokenDefaults: {
@@ -30382,16 +30713,16 @@ function initApplyData(action, env = process.env) {
30382
30713
  fs11.mkdirSync(dir, { recursive: true });
30383
30714
  switch (action) {
30384
30715
  case "youtrack_json": {
30385
- const out = path21.join(dir, "youtrack.json");
30716
+ const out = path23.join(dir, "youtrack.json");
30386
30717
  fs11.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
30387
30718
  `, "utf8");
30388
30719
  return { action, ok: true, path: out };
30389
30720
  }
30390
30721
  case "youtrack_token_placeholder": {
30391
- const p = path21.join(dir, "youtrack.token");
30722
+ const p = path23.join(dir, "youtrack.token");
30392
30723
  const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER3 + `
30393
30724
  `, 384) === "preserved";
30394
- const abs = path21.resolve(p);
30725
+ const abs = path23.resolve(p);
30395
30726
  return {
30396
30727
  action,
30397
30728
  ok: true,
@@ -30403,14 +30734,14 @@ function initApplyData(action, env = process.env) {
30403
30734
  };
30404
30735
  }
30405
30736
  case "youtrack_scaffold": {
30406
- const jsonOut = path21.join(dir, "youtrack.json");
30407
- const tokenOut = path21.join(dir, "youtrack.token");
30737
+ const jsonOut = path23.join(dir, "youtrack.json");
30738
+ const tokenOut = path23.join(dir, "youtrack.token");
30408
30739
  fs11.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
30409
30740
  `, "utf8");
30410
30741
  const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER3 + `
30411
30742
  `, 384) === "preserved";
30412
- const configPath = path21.resolve(jsonOut);
30413
- const tokenPath = path21.resolve(tokenOut);
30743
+ const configPath = path23.resolve(jsonOut);
30744
+ const tokenPath = path23.resolve(tokenOut);
30414
30745
  const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
30415
30746
  process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
30416
30747
  let tokenCreate = {};
@@ -30464,19 +30795,19 @@ function initApplyData(action, env = process.env) {
30464
30795
  }
30465
30796
  }
30466
30797
  case "vcs_scaffold": {
30467
- const jsonOut = path21.join(dir, "vcs.json");
30798
+ const jsonOut = path23.join(dir, "vcs.json");
30468
30799
  fs11.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(dir), null, 2) + `
30469
30800
  `, "utf8");
30470
- const glPath = path21.join(dir, "gitlab.token");
30471
- const ghPath = path21.join(dir, "github.token");
30801
+ const glPath = path23.join(dir, "gitlab.token");
30802
+ const ghPath = path23.join(dir, "github.token");
30472
30803
  const preservedTokens = [];
30473
30804
  for (const p of [glPath, ghPath]) {
30474
30805
  if (writeFileExclusive(p, TOKEN_PLACEHOLDER3 + `
30475
30806
  `, 384) === "preserved") {
30476
- preservedTokens.push(path21.resolve(p));
30807
+ preservedTokens.push(path23.resolve(p));
30477
30808
  }
30478
30809
  }
30479
- const configPath = path21.resolve(jsonOut);
30810
+ const configPath = path23.resolve(jsonOut);
30480
30811
  const prev = process.env.WORKFLOW_VCS_CONFIG;
30481
30812
  process.env.WORKFLOW_VCS_CONFIG = configPath;
30482
30813
  try {
@@ -30485,14 +30816,14 @@ function initApplyData(action, env = process.env) {
30485
30816
  const provider = typeof rawProvider === "string" && rawProvider.trim() ? rawProvider : null;
30486
30817
  const tokenUrls = vcsTokenCreateUrls();
30487
30818
  const active = tokenUrls.active ?? {};
30488
- const activePath = provider === "gitlab" ? path21.resolve(glPath) : provider === "github" ? path21.resolve(ghPath) : null;
30819
+ const activePath = provider === "gitlab" ? path23.resolve(glPath) : provider === "github" ? path23.resolve(ghPath) : null;
30489
30820
  return {
30490
30821
  action,
30491
30822
  ok: true,
30492
30823
  vcs_json: configPath,
30493
30824
  config_edit_path: configPath,
30494
- gitlab_token: path21.resolve(glPath),
30495
- github_token: path21.resolve(ghPath),
30825
+ gitlab_token: path23.resolve(glPath),
30826
+ github_token: path23.resolve(ghPath),
30496
30827
  token_edit_path: activePath,
30497
30828
  token_create_url: active.createUrl,
30498
30829
  token_create_urls: tokenUrls,
@@ -30547,6 +30878,109 @@ var normalizeLegacyResult = (value) => {
30547
30878
  return ok(data);
30548
30879
  };
30549
30880
 
30881
+ // packages/workit-opencode/src/shared/init-apply.ts
30882
+ import path24 from "node:path";
30883
+
30884
+ // packages/workit-opencode/src/shared/repo-result.ts
30885
+ var output = (value) => JSON.stringify(value, null, 2);
30886
+ var diagnostics = ({ stdout, stderr, exitCode }) => ({
30887
+ stdout,
30888
+ stderr,
30889
+ exitCode
30890
+ });
30891
+ var requireConfirmed = (confirmed) => {
30892
+ if (confirmed === true)
30893
+ return null;
30894
+ return output(fail("confirmed: true required"));
30895
+ };
30896
+ function scriptResult(result, parse) {
30897
+ if (result.exitCode !== 0) {
30898
+ return fail(result.stderr.trim() || result.stdout.trim() || "workflow script failed", diagnostics(result));
30899
+ }
30900
+ try {
30901
+ return ok({
30902
+ ...parse(result.stdout),
30903
+ exitCode: 0,
30904
+ ...result.stderr ? { stderr: result.stderr } : {}
30905
+ });
30906
+ } catch (error) {
30907
+ return fail(error instanceof Error ? error.message : "workflow output parse failed", diagnostics(result));
30908
+ }
30909
+ }
30910
+ var legacyScriptResult = (result) => {
30911
+ let parsed = null;
30912
+ try {
30913
+ parsed = JSON.parse(result.stdout.trim());
30914
+ } catch {}
30915
+ if (result.exitCode !== 0 || parsed?.error || parsed?.ok === false) {
30916
+ const error = parsed?.error ? String(parsed.error) : parsed?.ok === false ? "legacy operation reported failure" : result.stderr.trim() || result.stdout.trim() || "workflow script failed";
30917
+ return fail(error, diagnostics(result));
30918
+ }
30919
+ if (!parsed)
30920
+ return fail("workflow output parse failed", diagnostics(result));
30921
+ const { ok: _legacyOk, ...data } = parsed;
30922
+ return ok({ ...data, exitCode: 0, ...result.stderr ? { stderr: result.stderr } : {} });
30923
+ };
30924
+
30925
+ // packages/workit-opencode/src/shared/init-apply.ts
30926
+ var initApplyRuntime = {
30927
+ initApply: (root, action, env) => {
30928
+ const out = initApply({ action, confirmed: true, env });
30929
+ return {
30930
+ exitCode: out.error ? 1 : 0,
30931
+ stdout: JSON.stringify(out.data ?? out),
30932
+ stderr: "",
30933
+ cwd: root
30934
+ };
30935
+ }
30936
+ };
30937
+ var executeInitApply = (args, directory, runtime) => {
30938
+ const rejected = requireConfirmed(args.confirmed);
30939
+ if (rejected)
30940
+ return rejected;
30941
+ const action = args.action;
30942
+ if (action === "hygiene") {
30943
+ const result = ensureHygieneFiles(directory, {
30944
+ confirmed: args.confirmed,
30945
+ includeOpenSource: args.include_open_source
30946
+ });
30947
+ return output(result.ok ? ok(result) : fail(result.error));
30948
+ }
30949
+ if (action === "gitignore") {
30950
+ const result = ensureProjectGitignore(directory, args.confirmed);
30951
+ return output(result.ok ? ok(result) : fail(result.error));
30952
+ }
30953
+ if (action === "config") {
30954
+ const LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
30955
+ if (args.locale !== undefined && !LOCALE_RE.test(args.locale)) {
30956
+ return output(fail(`invalid locale: ${JSON.stringify(args.locale)} — expected BCP-47 like en or es-CL`));
30957
+ }
30958
+ const current = readConfig();
30959
+ const next = mergeConfigValues({
30960
+ locale: args.locale,
30961
+ localeOptions: args.locale_options,
30962
+ timezone: args.timezone,
30963
+ preset: args.branch_policy_preset,
30964
+ allowed: args.branch_policy_allowed,
30965
+ protectedNames: args.branch_policy_protected
30966
+ }, current);
30967
+ writeConfig(next);
30968
+ return output(ok({ action: "config", path: path24.join(configDir(), "config.json"), ...next }));
30969
+ }
30970
+ const env = Object.fromEntries(Object.entries({
30971
+ WORKFLOW_YT_BASE_URL: args.base_url,
30972
+ WORKFLOW_YT_MENTION: args.default_mention,
30973
+ WORKFLOW_YT_MEETING_ISSUE: args.meeting_issue,
30974
+ WORKFLOW_VCS_PROVIDER: args.vcs_provider,
30975
+ WORKFLOW_VCS_TARGET_BRANCH: args.vcs_target_branch,
30976
+ WORKFLOW_WORKSPACE_ROOT: directory,
30977
+ WORKFLOW_BP_NAME: args.name,
30978
+ WORKFLOW_BP_DEVELOP: args.develop_branch,
30979
+ WORKFLOW_BP_INTEGRATION: args.integration
30980
+ }).filter((entry) => entry[1] !== undefined));
30981
+ return output(legacyScriptResult(runtime.initApply(directory, action, env)));
30982
+ };
30983
+
30550
30984
  // packages/workit-opencode/src/tools/repo.ts
30551
30985
  var defaultRuntime = {
30552
30986
  git: (root, args) => run2(root, "git", args),
@@ -30564,15 +30998,7 @@ var defaultRuntime = {
30564
30998
  cwd: root
30565
30999
  };
30566
31000
  },
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
- },
31001
+ ...initApplyRuntime,
30576
31002
  initStatus: (root) => ({
30577
31003
  exitCode: 0,
30578
31004
  stdout: JSON.stringify(initStatusData()),
@@ -30586,36 +31012,7 @@ var defaultRuntime = {
30586
31012
  cwd: root
30587
31013
  })
30588
31014
  };
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
31015
  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
31016
  var optionalJson = (value) => {
30620
31017
  if (!value?.trim())
30621
31018
  return null;
@@ -30770,7 +31167,7 @@ function createRepoTools(runtime = defaultRuntime) {
30770
31167
  entries,
30771
31168
  path: changelogPath,
30772
31169
  normalize_only,
30773
- workspace_root: realpathSync8(context.directory)
31170
+ workspace_root: realpathSync9(context.directory)
30774
31171
  })));
30775
31172
  }
30776
31173
  }),
@@ -30891,77 +31288,11 @@ function createRepoTools(runtime = defaultRuntime) {
30891
31288
  branch_policy_protected: tool.schema.array(tool.schema.string()).optional(),
30892
31289
  include_open_source: tool.schema.boolean().optional()
30893
31290
  },
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
- }
31291
+ execute: async (args, context) => executeInitApply(args, context.directory, runtime)
30957
31292
  })
30958
31293
  };
30959
31294
  }
30960
31295
 
30961
- // packages/workit-opencode/src/tools/workit.ts
30962
- import { realpathSync as realpathSync9 } from "node:fs";
30963
- import path24 from "node:path";
30964
-
30965
31296
  // packages/workit-core/src/core/tracker-issues.ts
30966
31297
  import fs12 from "node:fs";
30967
31298
  import { spawnSync as spawnSync9 } from "node:child_process";
@@ -31233,7 +31564,7 @@ async function logTimeUpdate(input, operation = defaultOperations) {
31233
31564
 
31234
31565
  // packages/workit-core/src/core/external-action-effects.ts
31235
31566
  import fs13 from "node:fs";
31236
- import path23 from "node:path";
31567
+ import path25 from "node:path";
31237
31568
 
31238
31569
  // packages/workit-core/src/core/commit-flavors.ts
31239
31570
  var CONVENTIONAL_RE = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?(!)?: .+/;
@@ -32060,7 +32391,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
32060
32391
  if (preview.error)
32061
32392
  return failure("invalid_input", preview.error);
32062
32393
  const target = String(preview.path);
32063
- const relative = path23.relative(fs13.realpathSync(root), target).replaceAll(path23.sep, "/") || "CHANGELOG.md";
32394
+ const relative = path25.relative(fs13.realpathSync(root), target).replaceAll(path25.sep, "/") || "CHANGELOG.md";
32064
32395
  const normalized = {
32065
32396
  operation: request.operation,
32066
32397
  payload: {
@@ -32316,24 +32647,21 @@ var executeResolvedExternalAction = async (resolved, root, step, caller) => {
32316
32647
  return executeConcreteExternalAction(fresh.data.request, root, fresh.data.marker, resolvedDate, step, typeof workText === "string" ? workText : undefined, caller, beforeDigest, beforeExists);
32317
32648
  };
32318
32649
 
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) => {
32650
+ // packages/workit-opencode/src/shared/session.ts
32651
+ import { realpathSync as realpathSync10 } from "node:fs";
32652
+ import path26 from "node:path";
32653
+ var sameWorkspace = (expected, observed) => {
32654
+ if (typeof observed !== "string" || !observed)
32655
+ return false;
32330
32656
  try {
32331
- const value = JSON.parse(content);
32332
- return typeof value.operation === "string" && value.operation.length > 0;
32657
+ return realpathSync10(expected) === realpathSync10(observed);
32333
32658
  } catch {
32334
- return /^[a-z][a-z_]*\.[a-z_]+$/.test(content);
32659
+ return path26.resolve(expected) === path26.resolve(observed);
32335
32660
  }
32336
32661
  };
32662
+
32663
+ // packages/workit-opencode/src/shared/decision-content.ts
32664
+ var rejectedDescription = "Reject this decision";
32337
32665
  var decisionContent = (purpose, question, approvedContent) => ({
32338
32666
  header: `Workit decision: ${purpose}`,
32339
32667
  question,
@@ -32342,6 +32670,24 @@ var decisionContent = (purpose, question, approvedContent) => ({
32342
32670
  { label: "rejected", description: rejectedDescription }
32343
32671
  ]
32344
32672
  });
32673
+ var isSelfAuthorizingActionContent = (content) => {
32674
+ try {
32675
+ const value = JSON.parse(content);
32676
+ return typeof value.operation === "string" && value.operation.length > 0;
32677
+ } catch {
32678
+ return /^[a-z][a-z_]*\.[a-z_]+$/.test(content);
32679
+ }
32680
+ };
32681
+
32682
+ // packages/workit-opencode/src/tools/workit.ts
32683
+ var sessionParent = (session) => {
32684
+ if (typeof session !== "object" || session === null)
32685
+ return null;
32686
+ if (!Object.prototype.hasOwnProperty.call(session, "parentID"))
32687
+ return;
32688
+ const parentID = session.parentID;
32689
+ return typeof parentID === "string" && parentID.length > 0 ? parentID : null;
32690
+ };
32345
32691
  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
32692
  var normalizeReceiptLabel = (label) => label.trim().replace(/(?:\s*[([]\s*[^()[\]]*\s*[)\]]\s*)+$/u, "").trim().toLowerCase();
32347
32693
  var purposeForQuestion = (question) => {
@@ -32540,15 +32886,6 @@ var sessionData = async (client, sessionID) => {
32540
32886
  return null;
32541
32887
  }
32542
32888
  };
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
32889
  var hostRef = (handle) => ({ kind: "host", host: "opencode", handle });
32553
32890
  var opencodeCapabilities = () => [
32554
32891
  {
@@ -32962,6 +33299,16 @@ var createWorkitTools = ({
32962
33299
  }
32963
33300
  }
32964
33301
  const autoApplies = !selected.ok && !planAuthorized && standingAutoApplies(store, "opencode", context.sessionID, descriptor);
33302
+ const planCount = planReservationLength(resolved.data.request.operation, resolved.data.descriptorPayload);
33303
+ if (planCount !== null) {
33304
+ if (selected.ok)
33305
+ return output2(success(null, null, { plan_commits: planCount }));
33306
+ if (autoApplies) {
33307
+ const bound = standingAutoBinding(core, store, "opencode", context.sessionID, descriptor);
33308
+ if (bound)
33309
+ return output2(success(null, null, { plan_commits: planCount }));
33310
+ }
33311
+ }
32965
33312
  if (!selected.ok && !planAuthorized && !autoApplies) {
32966
33313
  if (selected.error.startsWith("no approved action")) {
32967
33314
  const proposal = actionProposalQuestion(resolved.data.request, resolved.data.descriptorPayload);
@@ -33090,9 +33437,9 @@ var loadProvenance = (logger, pkgUrl) => {
33090
33437
  }
33091
33438
  };
33092
33439
 
33093
- // packages/workit-opencode/src/plugin.ts
33094
- var root = fileURLToPath3(new URL("../assets/", import.meta.url));
33095
- var skillsPath = path25.join(root, "skills");
33440
+ // packages/workit-opencode/src/v1/server.ts
33441
+ var root = assetsRoot();
33442
+ var skillsPath = path27.join(root, "skills");
33096
33443
  var logger = createLogger({
33097
33444
  appLog: () => {
33098
33445
  return;
@@ -33111,7 +33458,7 @@ var warnStaleSources = () => {
33111
33458
  logger.warn(EVENT.hooks, {
33112
33459
  boundary: "stale_sources",
33113
33460
  reason: "workit sources changed after plugin load; restart the session for latest behavior",
33114
- files: changed.map((file) => path25.basename(file))
33461
+ files: changed.map((file) => path27.basename(file))
33115
33462
  });
33116
33463
  } catch {}
33117
33464
  };
@@ -33140,7 +33487,7 @@ var plugin = async ({ client, directory }) => {
33140
33487
  const dispatches = new Map;
33141
33488
  try {
33142
33489
  logger.info(EVENT.initialization, { host: "opencode", plugin_root: root });
33143
- logger.info(EVENT.provenance, loadProvenance(logger, new URL("../package.json", import.meta.url)));
33490
+ logger.info(EVENT.provenance, loadProvenance(logger, path27.join(packageRoot2(), "package.json")));
33144
33491
  } catch (error) {
33145
33492
  logger.warn(EVENT.hooks, { boundary: "initialization", ...errorDetail(error) });
33146
33493
  }
@@ -33442,7 +33789,7 @@ var plugin = async ({ client, directory }) => {
33442
33789
  config: async (config) => {
33443
33790
  const mutable = config;
33444
33791
  const paths = [...mutable.skills?.paths ?? []];
33445
- if (existsSync13(skillsPath) && !paths.includes(skillsPath))
33792
+ if (existsSync14(skillsPath) && !paths.includes(skillsPath))
33446
33793
  paths.push(skillsPath);
33447
33794
  mutable.skills ??= {};
33448
33795
  mutable.skills.paths = paths;
@@ -33512,7 +33859,835 @@ var plugin = async ({ client, directory }) => {
33512
33859
  }
33513
33860
  };
33514
33861
  };
33515
- var plugin_default = plugin;
33862
+ var server_default = plugin;
33863
+
33864
+ // node_modules/@opencode/plugin/dist/promise/plugin.js
33865
+ function define(plugin) {
33866
+ return plugin;
33867
+ }
33868
+
33869
+ // packages/workit-opencode/src/shared/tools.ts
33870
+ var objectSchema = (properties, required) => ({
33871
+ type: "object",
33872
+ properties,
33873
+ required,
33874
+ additionalProperties: false
33875
+ });
33876
+ var WORKIT_EXTERNAL_ACTION_OPERATIONS = [
33877
+ "git.branch_setup",
33878
+ "git.commit",
33879
+ "git.push",
33880
+ "hosting.pull_request",
33881
+ "hosting.merge",
33882
+ "youtrack.update",
33883
+ "youtrack.time",
33884
+ "youtrack.meeting",
33885
+ "changelog.apply",
33886
+ "context.read"
33887
+ ];
33888
+ var familyTools = OPERATION_FAMILIES.map((family) => ({
33889
+ name: `workit_${family}`,
33890
+ description: `Workit ${family} operations backed by the shared task contract.`,
33891
+ input: { type: "object", ...boundedOperationJsonSchema(family, OPERATION_SCHEMA_DEPTH) }
33892
+ }));
33893
+ var WORKIT_TOOL_CATALOG = [
33894
+ ...familyTools,
33895
+ {
33896
+ name: "workit_external_action",
33897
+ description: `Run one fixed optional action. ${externalActionHelp}`,
33898
+ input: objectSchema({
33899
+ operation: {
33900
+ type: "string",
33901
+ enum: [...WORKIT_EXTERNAL_ACTION_OPERATIONS],
33902
+ description: "Fixed optional action to run."
33903
+ },
33904
+ payload: {
33905
+ type: "object",
33906
+ description: "Operation-specific payload; Workit validates it.",
33907
+ additionalProperties: true
33908
+ }
33909
+ }, ["operation", "payload"])
33910
+ },
33911
+ {
33912
+ name: "workit_init_apply",
33913
+ description: "Apply a confirmed toolkit initialization action",
33914
+ input: objectSchema({
33915
+ confirmed: { type: "boolean" },
33916
+ action: {
33917
+ type: "string",
33918
+ enum: [
33919
+ "youtrack_scaffold",
33920
+ "youtrack_json",
33921
+ "youtrack_token_placeholder",
33922
+ "vcs_scaffold",
33923
+ "config",
33924
+ "gitignore",
33925
+ "hygiene",
33926
+ "branch_policy"
33927
+ ]
33928
+ },
33929
+ base_url: { type: "string" },
33930
+ default_mention: { type: "string" },
33931
+ meeting_issue: { type: "string" },
33932
+ vcs_provider: { type: "string", enum: ["gitlab", "github"] },
33933
+ vcs_target_branch: { type: "string" },
33934
+ name: { type: "string" },
33935
+ develop_branch: { type: "string" },
33936
+ integration: { type: "string", enum: ["pr", "merge"] },
33937
+ locale: { type: "string" },
33938
+ locale_options: { type: "array", items: { type: "string" } },
33939
+ timezone: { type: "string" },
33940
+ branch_policy_preset: {
33941
+ type: "string",
33942
+ enum: ["gitflow", "github-flow", "trunk-based", "custom"]
33943
+ },
33944
+ branch_policy_allowed: { type: "array", items: { type: "string" } },
33945
+ branch_policy_protected: { type: "array", items: { type: "string" } },
33946
+ include_open_source: { type: "boolean" }
33947
+ }, ["confirmed", "action"])
33948
+ }
33949
+ ];
33950
+ var workitFamilyOf = (toolName) => {
33951
+ for (const family of OPERATION_FAMILIES)
33952
+ if (toolName === `workit_${family}`)
33953
+ return family;
33954
+ return null;
33955
+ };
33956
+
33957
+ // packages/workit-opencode/src/v2/lifecycle.ts
33958
+ var record3 = (value) => typeof value === "object" && value !== null ? value : {};
33959
+ var sessionIDOf = (value) => typeof value === "string" && value.length > 0 ? value : null;
33960
+ var parentOf = (session) => typeof session?.parentID === "string" && session.parentID ? session.parentID : undefined;
33961
+ var contentText = (content) => {
33962
+ if (typeof content === "string")
33963
+ return content;
33964
+ if (!Array.isArray(content))
33965
+ return "";
33966
+ return content.map((part) => record3(part).type === "text" ? String(part.text ?? "") : "").join(`
33967
+ `);
33968
+ };
33969
+ var createV2Lifecycle = (deps) => {
33970
+ const { root } = deps;
33971
+ const directChildren = new Map;
33972
+ const dispatches = new Map;
33973
+ const inflight = new Map;
33974
+ const lifecycleBindings = new Map;
33975
+ const unresolvedTaskLaunches = new Set;
33976
+ const now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
33977
+ const readSession = async (sessionID) => {
33978
+ try {
33979
+ const info = await deps.getSession(sessionID);
33980
+ if (!info || typeof info !== "object" || info.id !== sessionID)
33981
+ return null;
33982
+ return info;
33983
+ } catch {
33984
+ return null;
33985
+ }
33986
+ };
33987
+ const trustedLead = async (coordinator) => {
33988
+ const session = await readSession(coordinator);
33989
+ if (!session || parentOf(session) !== undefined)
33990
+ return null;
33991
+ if (!sameWorkspace(root, session.directory))
33992
+ return null;
33993
+ return session;
33994
+ };
33995
+ const ownsActiveTask = (coordinator) => {
33996
+ try {
33997
+ const store = new TaskStore(root);
33998
+ const workspace = store.readWorkspace();
33999
+ const listed = store.listTasks();
34000
+ if (!workspace.ok || !workspace.data || !listed.ok)
34001
+ return false;
34002
+ return listed.data.some((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);
34003
+ } catch {
34004
+ return false;
34005
+ }
34006
+ };
34007
+ const revisions = (taskId) => {
34008
+ const store = new TaskStore(root);
34009
+ const task = store.readTask(taskId);
34010
+ const workspace = store.readWorkspace();
34011
+ return task.ok && workspace.ok && workspace.data ? {
34012
+ expectedRevision: task.data.revision,
34013
+ expectedWorkspaceRevision: workspace.data.revision
34014
+ } : null;
34015
+ };
34016
+ const oldestOfSingleTask = (items) => {
34017
+ const tasks = new Set(items.map((item) => item.task.id));
34018
+ if (tasks.size !== 1 || items.length === 0)
34019
+ return null;
34020
+ 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];
34021
+ };
34022
+ const prepareDispatch = (coordinator, callID) => {
34023
+ if (dispatches.has(coordinator))
34024
+ return "unsettled";
34025
+ const store = new TaskStore(root);
34026
+ const workspace = store.readWorkspace();
34027
+ const listed = store.listTasks();
34028
+ if (!workspace.ok || !workspace.data || !listed.ok)
34029
+ return "unmanaged";
34030
+ 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);
34031
+ if (owned.length === 0)
34032
+ return "unmanaged";
34033
+ 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 })));
34034
+ const next = oldestOfSingleTask(eligible);
34035
+ if (!next)
34036
+ return "unbound";
34037
+ const generation = {
34038
+ coordinator,
34039
+ callID,
34040
+ childCreated: false,
34041
+ noChild: false
34042
+ };
34043
+ const core = new WorkitCore(store, {
34044
+ root,
34045
+ caller: { host: "opencode", actor: coordinator },
34046
+ capabilities: [],
34047
+ constraints: [],
34048
+ now,
34049
+ nativeWorker: nativeDispatchFor(directChildren, coordinator, generation)
34050
+ });
34051
+ const prepared = core.prepareWorkerDispatch({
34052
+ taskId: next.task.id,
34053
+ workerId: next.entry.id,
34054
+ expectedRevision: next.task.revision,
34055
+ expectedWorkspaceRevision: workspace.data.revision,
34056
+ observation: { stage: "prepare", sessionID: coordinator, callID }
34057
+ });
34058
+ if (!prepared.ok)
34059
+ return "unbound";
34060
+ dispatches.set(coordinator, {
34061
+ generation,
34062
+ dispatch: prepared.data,
34063
+ core,
34064
+ taskId: next.task.id,
34065
+ workerId: next.entry.id,
34066
+ callID,
34067
+ childID: null
34068
+ });
34069
+ return "prepared";
34070
+ };
34071
+ const commitDispatchStart = (coordinator, childID) => {
34072
+ const pending = dispatches.get(coordinator);
34073
+ if (!pending)
34074
+ return false;
34075
+ pending.generation.childCreated = true;
34076
+ pending.childID = childID;
34077
+ const current = revisions(pending.taskId);
34078
+ if (!current)
34079
+ return false;
34080
+ const committed = pending.core.commitWorkerDispatch({
34081
+ ...current,
34082
+ dispatch: pending.dispatch,
34083
+ taskId: pending.taskId,
34084
+ workerId: pending.workerId,
34085
+ outcome: "started",
34086
+ session: { kind: "host", host: "opencode", handle: childID },
34087
+ observation: { event: "running", sessionID: childID }
34088
+ });
34089
+ if (!committed.ok)
34090
+ return false;
34091
+ dispatches.delete(coordinator);
34092
+ if (inflight.get(coordinator) === pending.callID)
34093
+ inflight.delete(coordinator);
34094
+ lifecycleBindings.set(childID, {
34095
+ parentID: coordinator,
34096
+ taskId: pending.taskId,
34097
+ workerId: pending.workerId
34098
+ });
34099
+ return true;
34100
+ };
34101
+ const observeLifecycle = async (sessionID, parentID, state, binding, initial = false, deleted = false) => {
34102
+ const store = new TaskStore(root);
34103
+ const listed = store.listTasks();
34104
+ const workspace = store.readWorkspace();
34105
+ if (!listed.ok || !workspace.ok || !workspace.data)
34106
+ return;
34107
+ 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 })) : []);
34108
+ const matches = persisted.filter(({ entry }) => entry.data.session?.kind === "host");
34109
+ if (binding && !initial && matches.length !== 1)
34110
+ return;
34111
+ const selected = binding ? persisted.find(({ task, entry }) => task.id === binding.taskId && entry.id === binding.workerId) : matches.length === 1 ? matches[0] : undefined;
34112
+ if (!selected)
34113
+ return;
34114
+ if (!initial && selected.entry.data.session?.kind !== "host")
34115
+ return;
34116
+ if (selected.entry.provenance.session?.kind !== "host")
34117
+ return;
34118
+ if (!binding) {
34119
+ const coordinator = selected.task.intent.provenance.session;
34120
+ if (coordinator?.kind !== "host" || coordinator.host !== "opencode")
34121
+ return;
34122
+ parentID = coordinator.handle;
34123
+ }
34124
+ if (!initial && !deleted) {
34125
+ const observed = await readSession(sessionID);
34126
+ if (!observed || !sameWorkspace(root, observed.directory) || parentOf(observed) !== parentID)
34127
+ return;
34128
+ }
34129
+ directChildren.set(sessionID, parentID);
34130
+ const core = new WorkitCore(store, {
34131
+ root,
34132
+ caller: { host: "opencode", actor: parentID },
34133
+ capabilities: [],
34134
+ constraints: [],
34135
+ now,
34136
+ nativeWorker: nativeWorkerFor(directChildren, parentID)
34137
+ });
34138
+ const result = core.observeWorkerLifecycle({
34139
+ taskId: selected.task.id,
34140
+ workerId: selected.entry.id,
34141
+ expectedRevision: selected.task.revision,
34142
+ expectedWorkspaceRevision: workspace.data.revision,
34143
+ state,
34144
+ session: { kind: "host", host: "opencode", handle: sessionID },
34145
+ observation: { event: state, sessionID }
34146
+ });
34147
+ if (!result.ok)
34148
+ return;
34149
+ lifecycleBindings.set(sessionID, {
34150
+ parentID,
34151
+ taskId: selected.task.id,
34152
+ workerId: selected.entry.id
34153
+ });
34154
+ };
34155
+ const reconcilePending = async (coordinator) => {
34156
+ const pending = dispatches.get(coordinator);
34157
+ if (!pending?.childID)
34158
+ return;
34159
+ const info = await readSession(pending.childID);
34160
+ if (!info || info.id !== pending.childID || !info.outcome)
34161
+ return;
34162
+ await observeLifecycle(pending.childID, coordinator, "stopped", {
34163
+ taskId: pending.taskId,
34164
+ workerId: pending.workerId
34165
+ });
34166
+ dispatches.delete(coordinator);
34167
+ if (inflight.get(coordinator) === pending.callID)
34168
+ inflight.delete(coordinator);
34169
+ };
34170
+ const coordinatorVeto = (coordinator) => {
34171
+ const listed = new TaskStore(root).listTasks();
34172
+ 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)))
34173
+ return "recovery_required: a cancelled worker remains uncertain; repeat worker.cancel on the ended worker to confirm its stop, then retry";
34174
+ if (unresolvedTaskLaunches.delete(coordinator))
34175
+ return "recovery_required: a cancelled worker remains uncertain";
34176
+ return null;
34177
+ };
34178
+ const executeBefore = async (event) => {
34179
+ if (event.tool !== "subagent")
34180
+ return;
34181
+ const coordinator = event.sessionID;
34182
+ const continuation = record3(event.input).sessionID;
34183
+ if (continuation !== undefined) {
34184
+ const lead = await trustedLead(coordinator);
34185
+ if (!lead)
34186
+ throw new Error("delegation_lineage_denied: native subagent workers must be direct children of the coordinator");
34187
+ const childID = sessionIDOf(continuation);
34188
+ const child = childID ? await readSession(childID) : null;
34189
+ if (!child || parentOf(child) !== coordinator)
34190
+ throw new Error("delegation_lineage_denied: a continuation sessionID must name an existing direct child of the coordinator");
34191
+ return;
34192
+ }
34193
+ if (!ownsActiveTask(coordinator)) {
34194
+ const lead = await trustedLead(coordinator);
34195
+ if (!lead)
34196
+ throw new Error("delegation_lineage_denied: native subagent workers must be direct children of the coordinator");
34197
+ return;
34198
+ }
34199
+ if (inflight.has(coordinator)) {
34200
+ await reconcilePending(coordinator);
34201
+ if (inflight.has(coordinator))
34202
+ throw new Error("recovery_required: a previous fresh subagent launch is unsettled; wait for it to settle or reconcile its worker before launching again");
34203
+ }
34204
+ inflight.set(coordinator, event.id);
34205
+ try {
34206
+ const veto = coordinatorVeto(coordinator);
34207
+ if (veto)
34208
+ throw new Error(veto);
34209
+ const lead = await trustedLead(coordinator);
34210
+ if (!lead)
34211
+ throw new Error("delegation_lineage_denied: native subagent workers must be direct children of the coordinator");
34212
+ const prepared = prepareDispatch(coordinator, event.id);
34213
+ if (prepared === "unmanaged") {
34214
+ inflight.delete(coordinator);
34215
+ return;
34216
+ }
34217
+ if (prepared === "unsettled") {
34218
+ throw new Error("recovery_required: a previous native task launch is unsettled; wait for it to settle or reconcile its worker before launching again");
34219
+ }
34220
+ if (prepared === "unbound") {
34221
+ throw new Error("recovery_required: no assigned Workit worker is attributable to this coordinator; assign one with workit_worker before launching a native task");
34222
+ }
34223
+ } catch (error) {
34224
+ if (inflight.get(coordinator) === event.id)
34225
+ inflight.delete(coordinator);
34226
+ throw error;
34227
+ }
34228
+ };
34229
+ const executeAfter = async (event) => {
34230
+ if (event.tool !== "subagent")
34231
+ return;
34232
+ const coordinator = event.sessionID;
34233
+ const pending = dispatches.get(coordinator);
34234
+ const generation = pending?.callID === event.id ? pending : undefined;
34235
+ if (event.status === "completed") {
34236
+ const metadata = record3(event.result.metadata);
34237
+ const child = sessionIDOf(metadata.sessionID ?? metadata.sessionId);
34238
+ if (child) {
34239
+ const childSession = await readSession(child);
34240
+ if (parentOf(childSession) === coordinator) {
34241
+ directChildren.set(child, coordinator);
34242
+ if (generation)
34243
+ commitDispatchStart(coordinator, child);
34244
+ }
34245
+ }
34246
+ const output = contentText(event.result.content);
34247
+ const completedChild = child && /<subagent\b[^>]*\bstate="completed"/.test(output) && lifecycleBindings.has(child) ? child : null;
34248
+ if (completedChild) {
34249
+ const binding = lifecycleBindings.get(completedChild);
34250
+ if (binding)
34251
+ await observeLifecycle(completedChild, coordinator, "stopped", binding);
34252
+ }
34253
+ } else {
34254
+ const message = String(event.error?.message ?? event.error ?? "").toLowerCase();
34255
+ if (generation && /(?:cancel|interrupt|unknown|uncertain)/.test(message))
34256
+ unresolvedTaskLaunches.add(coordinator);
34257
+ }
34258
+ if (generation) {
34259
+ dispatches.delete(coordinator);
34260
+ if (inflight.get(coordinator) === event.id)
34261
+ inflight.delete(coordinator);
34262
+ } else if (inflight.get(coordinator) === event.id) {
34263
+ inflight.delete(coordinator);
34264
+ }
34265
+ };
34266
+ const bindCreatedSession = async (data) => {
34267
+ if (!sameWorkspace(root, data.directory))
34268
+ return;
34269
+ const live = dispatches.get(data.parentID);
34270
+ if (live) {
34271
+ live.generation.childCreated = true;
34272
+ live.childID = data.sessionID;
34273
+ }
34274
+ const store = new TaskStore(root);
34275
+ const workspace = store.readWorkspace();
34276
+ const listed = store.listTasks();
34277
+ if (!workspace.ok || !workspace.data || !listed.ok)
34278
+ return;
34279
+ 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 === data.parentID).map((entry) => ({ task, entry })) : []);
34280
+ const next = oldestOfSingleTask(candidates);
34281
+ if (!next)
34282
+ return;
34283
+ directChildren.set(data.sessionID, data.parentID);
34284
+ if (live && live.taskId === next.task.id && live.workerId === next.entry.id && commitDispatchStart(data.parentID, data.sessionID))
34285
+ return;
34286
+ await observeLifecycle(data.sessionID, data.parentID, "running", { taskId: next.task.id, workerId: next.entry.id }, true);
34287
+ };
34288
+ const handleEvent = async (event) => {
34289
+ const type = event.type;
34290
+ if (typeof type !== "string")
34291
+ return;
34292
+ if (type === "session.created") {
34293
+ const data = record3(event.data);
34294
+ const sessionID = sessionIDOf(data.sessionID);
34295
+ const parentID = sessionIDOf(data.parentID);
34296
+ const directory = record3(data.location).directory;
34297
+ if (!sessionID || !parentID || typeof directory !== "string")
34298
+ return;
34299
+ await bindCreatedSession({ sessionID, parentID, directory });
34300
+ return;
34301
+ }
34302
+ const terminal = type === "session.execution.succeeded" || type === "session.execution.failed" || type === "session.execution.interrupted";
34303
+ if (type !== "session.execution.started" && !terminal && type !== "session.deleted")
34304
+ return;
34305
+ const sessionID = sessionIDOf(record3(event.data).sessionID);
34306
+ if (!sessionID)
34307
+ return;
34308
+ const binding = lifecycleBindings.get(sessionID);
34309
+ if (type === "session.deleted") {
34310
+ const directory = event.location?.directory;
34311
+ if (typeof directory === "string" && !sameWorkspace(root, directory))
34312
+ return;
34313
+ await observeLifecycle(sessionID, binding?.parentID ?? "", "stopped", binding, false, true);
34314
+ return;
34315
+ }
34316
+ const state = type === "session.execution.started" ? "running" : "stopped";
34317
+ await observeLifecycle(sessionID, binding?.parentID ?? "", state, binding);
34318
+ };
34319
+ return {
34320
+ directChildren,
34321
+ executeBefore,
34322
+ executeAfter,
34323
+ handleEvent,
34324
+ pendingLaunch: (coordinator) => inflight.has(coordinator)
34325
+ };
34326
+ };
34327
+
34328
+ // packages/workit-opencode/src/v2/injection.ts
34329
+ var textPart = (text) => ({ type: "text", text });
34330
+ var hasMarker = (system, marker) => system.some((part) => part.type === "text" && typeof part.text === "string" && part.text.includes(marker));
34331
+ var injectAgentContext = (root, session, directChildren, system) => {
34332
+ if (!session || !sameWorkspace(root, session.directory))
34333
+ return;
34334
+ const bootstrap = session.parentID === undefined ? getWorkitBootstrap() : null;
34335
+ const taskContext = compactContextFor(root, session.id);
34336
+ const workerContext = session.parentID ? workerContextFor(root, session.id, session.parentID, directChildren) : null;
34337
+ if (bootstrap && !hasMarker(system, "<workit-contract>"))
34338
+ system.unshift(textPart(bootstrap));
34339
+ if (taskContext && !hasMarker(system, "<workit-task-context>"))
34340
+ system.unshift(textPart(`<workit-task-context>${taskContext}</workit-task-context>`));
34341
+ if (workerContext && !hasMarker(system, "<workit-worker-context>"))
34342
+ system.unshift(textPart(`<workit-worker-context>${workerContext}</workit-worker-context>`));
34343
+ };
34344
+ var injectCompactionContext = (root, sessionID, system) => {
34345
+ const context = compactContextFor(root, sessionID);
34346
+ if (!context || hasMarker(system, "<workit-task-context>"))
34347
+ return;
34348
+ system.push(textPart(`<workit-task-context>${context}</workit-task-context>`));
34349
+ };
34350
+
34351
+ // packages/workit-opencode/src/v2/permissions.ts
34352
+ var GIT_WORKTREE = /git [\s\S]*worktree/u;
34353
+ var WORKTREE_MESSAGE = "Workit does not use git worktrees; use the guarded in-place git.branch_setup route instead";
34354
+ var evaluateShellPermission = (root, event) => {
34355
+ if (event.action !== "shell" || event.effect === "deny")
34356
+ return;
34357
+ for (const resource of event.resources) {
34358
+ if (typeof resource !== "string")
34359
+ continue;
34360
+ const route = shouldDenyShellRoute(root, resource);
34361
+ if (route) {
34362
+ event.effect = "deny";
34363
+ event.message = `direct branch or PR creation bypasses the Workit route; ${route.guidance}`;
34364
+ return;
34365
+ }
34366
+ if (GIT_WORKTREE.test(resource)) {
34367
+ event.effect = "deny";
34368
+ event.message = WORKTREE_MESSAGE;
34369
+ return;
34370
+ }
34371
+ }
34372
+ };
34373
+
34374
+ // packages/workit-opencode/src/v2/receipts.ts
34375
+ var normalizeQuestionAnswers = (answers) => {
34376
+ const singleValue = (value) => typeof value === "string" && value.trim() ? value : null;
34377
+ if (answers === null || typeof answers !== "object")
34378
+ return answers;
34379
+ if (Array.isArray(answers)) {
34380
+ if (answers.length === 0)
34381
+ return answers;
34382
+ const first = answers[0];
34383
+ const direct = singleValue(first);
34384
+ if (direct)
34385
+ return [[direct]];
34386
+ if (Array.isArray(first)) {
34387
+ if (first.length !== 1)
34388
+ return answers;
34389
+ const only = singleValue(first[0]);
34390
+ return only ? [[only]] : answers;
34391
+ }
34392
+ if (first && typeof first === "object") {
34393
+ const values = Object.values(first);
34394
+ if (values.length > 0 && values.every((entry) => singleValue(entry) !== null))
34395
+ return [[singleValue(values[0])]];
34396
+ }
34397
+ return answers;
34398
+ }
34399
+ const values = Object.values(answers);
34400
+ if (values.length > 0 && values.every((entry) => singleValue(entry) !== null))
34401
+ return [[singleValue(values[0])]];
34402
+ return answers;
34403
+ };
34404
+
34405
+ // packages/workit-opencode/src/v2/registry.ts
34406
+ import { existsSync as existsSync15, readFileSync as readFileSync14 } from "node:fs";
34407
+ import path28 from "node:path";
34408
+ var parseFrontmatter = (markdown) => {
34409
+ const match = markdown.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
34410
+ if (!match)
34411
+ return { name: "", body: markdown };
34412
+ const meta = {};
34413
+ for (const line of match[1].split(`
34414
+ `)) {
34415
+ const index = line.indexOf(":");
34416
+ if (index > 0)
34417
+ meta[line.slice(0, index).trim()] = line.slice(index + 1).trim();
34418
+ }
34419
+ return {
34420
+ name: (meta.name ?? "").replace(/^["']|["']$/g, "").trim(),
34421
+ description: (meta.description ?? "").replace(/^["']|["']$/g, "").trim() || undefined,
34422
+ body: `${match[2].trim()}
34423
+ `
34424
+ };
34425
+ };
34426
+ var registerSkills = async (ctx) => {
34427
+ const skillsDir = path28.join(assetsRoot(), "skills");
34428
+ if (!existsSync15(skillsDir))
34429
+ return;
34430
+ await ctx.skill.transform((editor) => {
34431
+ const existing = new Set(editor.list().map((skill) => skill.id));
34432
+ for (const id of WORKIT_METHOD_SKILLS) {
34433
+ if (existing.has(id))
34434
+ continue;
34435
+ const file = path28.join(skillsDir, id, "SKILL.md");
34436
+ if (!existsSync15(file))
34437
+ continue;
34438
+ const parsed = parseFrontmatter(readFileSync14(file, "utf8"));
34439
+ editor.add({
34440
+ id,
34441
+ name: parsed.name || id,
34442
+ ...parsed.description ? { description: parsed.description } : {},
34443
+ location: file,
34444
+ content: parsed.body
34445
+ });
34446
+ }
34447
+ });
34448
+ };
34449
+ var registerCommands = async (ctx) => {
34450
+ const listed = await ctx.command.list();
34451
+ const existing = new Set((listed?.data ?? []).map((command) => command.name));
34452
+ await ctx.command.transform((editor) => {
34453
+ for (const [alias, skill] of Object.entries(WORKIT_SKILL_ALIASES)) {
34454
+ if (existing.has(alias))
34455
+ continue;
34456
+ editor.add({
34457
+ name: alias,
34458
+ description: `Apply the ${skill} method skill to the current task.`,
34459
+ execute: async ({ sessionID, prompt, delivery }) => {
34460
+ await ctx.session.prompt({
34461
+ sessionID,
34462
+ text: `Load the ${skill} skill with the skill tool and apply it to the current task. Arguments: ${prompt.text}`,
34463
+ ...prompt.files ? { files: prompt.files } : {},
34464
+ ...prompt.agents ? { agents: prompt.agents } : {},
34465
+ ...prompt.skills ? { skills: prompt.skills } : {},
34466
+ delivery
34467
+ });
34468
+ }
34469
+ });
34470
+ }
34471
+ });
34472
+ };
34473
+
34474
+ // packages/workit-opencode/src/v2/plugin.ts
34475
+ var sessionFacts = async (ctx, sessionID) => {
34476
+ try {
34477
+ const session = await ctx.session.get({ sessionID });
34478
+ const id = session?.id;
34479
+ const directory = session?.location?.directory;
34480
+ if (typeof id !== "string" || id !== sessionID || typeof directory !== "string")
34481
+ return null;
34482
+ const parentID = typeof session.parentID === "string" ? session.parentID : undefined;
34483
+ const outcome = session.outcome === "succeeded" || session.outcome === "failed" || session.outcome === "interrupted" ? session.outcome : undefined;
34484
+ return { id, parentID, directory, outcome };
34485
+ } catch {
34486
+ return null;
34487
+ }
34488
+ };
34489
+ var resultContent = (value) => ({
34490
+ content: JSON.stringify(value, null, 2)
34491
+ });
34492
+ var v2Capabilities = () => [
34493
+ {
34494
+ name: "interactive_decision",
34495
+ surface: "question",
34496
+ assurance: "enforced",
34497
+ reason: "native question answers are observed by tool.execute.after and consumed once",
34498
+ refs: [{ kind: "host", host: "opencode", handle: "question" }]
34499
+ },
34500
+ {
34501
+ name: "known_product_writes",
34502
+ surface: "edit/shell",
34503
+ assurance: "unavailable",
34504
+ reason: "file writes are host-policy; OpenCode native permissions govern them, workit no longer gates write tools",
34505
+ refs: [{ kind: "host", host: "opencode", handle: "permission.evaluate" }]
34506
+ },
34507
+ {
34508
+ name: "direct_child_workers",
34509
+ surface: "subagent",
34510
+ assurance: "enforced",
34511
+ reason: "nested subagent launches are denied and observed child sessions are parent-bound before they may own a worker",
34512
+ refs: [{ kind: "host", host: "opencode", handle: "subagent" }]
34513
+ },
34514
+ {
34515
+ name: "fresh-context-review",
34516
+ surface: "subagent",
34517
+ assurance: "agent_guided",
34518
+ reason: "independent review runs as a native child session; evidence evaluation enforces creator and duplicate-reviewer exclusion",
34519
+ refs: [{ kind: "host", host: "opencode", handle: "subagent" }]
34520
+ }
34521
+ ];
34522
+ var workerIdFor2 = (store, actor, parentID, directChildren) => {
34523
+ if (parentID === undefined)
34524
+ return null;
34525
+ const tasks = store.listTasks();
34526
+ if (!tasks.ok)
34527
+ return null;
34528
+ const matches = tasks.data.flatMap((task) => {
34529
+ if (task.status !== "active")
34530
+ return [];
34531
+ const coordinator = task.intent.provenance.session;
34532
+ if (coordinator?.kind !== "host" || coordinator.host !== "opencode" || coordinator.handle !== parentID || directChildren.get(actor) !== parentID)
34533
+ return [];
34534
+ return task.workers.filter((worker) => worker.data.state === "running" && worker.data.session?.kind === "host" && worker.data.session.host === "opencode" && worker.data.session.handle === actor);
34535
+ });
34536
+ return matches.length === 1 ? matches[0].id : null;
34537
+ };
34538
+ var recordDecision = (core, receipts, request, sessionID) => {
34539
+ const decision = request;
34540
+ if (decision.response === "stated")
34541
+ return core.observeDecision(request, undefined);
34542
+ const content = decisionContent(decision.purpose, decision.binding.presented, decision.binding.approvedContent);
34543
+ const budgetIssue = workitBindingQuestionIssue([content]);
34544
+ if (budgetIssue)
34545
+ return failure("invalid_input", budgetIssue);
34546
+ const observed = receipts.consume(sessionID, "decision", {
34547
+ selectedLabel: decision.response,
34548
+ decisionPurpose: decision.purpose,
34549
+ contentDigest: sha256(canonicalJson(content)),
34550
+ question: decision.binding.presented,
34551
+ ...decision.response === "approved" ? { selectedDescription: decision.binding.approvedContent } : {}
34552
+ });
34553
+ if (!observed.ok)
34554
+ return failure("permission_denied", observed.error);
34555
+ if (decision.purpose === "action" && decision.response === "approved" && !isSelfAuthorizingActionContent(decision.binding.approvedContent))
34556
+ return failure("invalid_input", "no matching action proposal; resolve the action through the action tool first");
34557
+ return core.observeDecision(request, observed.observation);
34558
+ };
34559
+ var setup = async (ctx) => {
34560
+ const root = ctx.location.directory;
34561
+ const receipts = new NativeReceiptStore;
34562
+ const lifecycle = createV2Lifecycle({
34563
+ root,
34564
+ getSession: async (sessionID) => {
34565
+ const session = await sessionFacts(ctx, sessionID);
34566
+ return session ? {
34567
+ id: session.id,
34568
+ parentID: session.parentID,
34569
+ directory: session.directory,
34570
+ outcome: session.outcome
34571
+ } : null;
34572
+ }
34573
+ });
34574
+ const runFamily = (family, input, session, store, workerId) => {
34575
+ const parsed = parseOperation(family, input);
34576
+ if (!parsed.ok)
34577
+ return parsed;
34578
+ const core = new WorkitCore(store, {
34579
+ root,
34580
+ caller: { host: "opencode", actor: session.id },
34581
+ capabilities: v2Capabilities(),
34582
+ constraints: [],
34583
+ now: () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
34584
+ workerId,
34585
+ nativeAuthority: nativeAuthority(receipts, session.id),
34586
+ nativeWorker: nativeWorkerFor(lifecycle.directChildren, session.id)
34587
+ });
34588
+ if (family === "decision" && parsed.data.action === "record")
34589
+ return recordDecision(core, receipts, parsed.data, session.id);
34590
+ const run = core[family];
34591
+ return run.call(core, parsed.data);
34592
+ };
34593
+ await ctx.tool.transform((editor) => {
34594
+ for (const spec of WORKIT_TOOL_CATALOG) {
34595
+ editor.add({
34596
+ name: spec.name,
34597
+ description: spec.description,
34598
+ input: spec.input,
34599
+ options: { codemode: false },
34600
+ execute: async (input, tool) => {
34601
+ const session = await sessionFacts(ctx, tool.sessionID);
34602
+ if (session === null || !sameWorkspace(root, session.directory))
34603
+ return resultContent(failure("permission_denied", "OpenCode session location does not match the plugin checkout"));
34604
+ const store = new TaskStore(root);
34605
+ const workerId = workerIdFor2(store, session.id, session.parentID, lifecycle.directChildren);
34606
+ if (session.parentID !== undefined && workerId === null)
34607
+ return resultContent(failure("permission_denied", "OpenCode child session has no validated Workit worker"));
34608
+ const family = workitFamilyOf(spec.name);
34609
+ if (family !== null)
34610
+ return resultContent(runFamily(family, input, session, store, workerId));
34611
+ if (spec.name === "workit_external_action") {
34612
+ const parsed = externalActionRequest(input);
34613
+ if (!parsed.ok)
34614
+ return resultContent(parsed);
34615
+ if (parsed.data.operation !== "context.read")
34616
+ return resultContent(failure("capability_unavailable", "OpenCode V2 external action approvals are not available yet; only context.read is", { capability: "external_action", outcome: "not_started" }));
34617
+ const resolved = resolveExternalActionRequest(root, parsed.data);
34618
+ if (!resolved.ok)
34619
+ return resultContent(resolved);
34620
+ return resultContent(await executeResolvedExternalAction(resolved.data, root));
34621
+ }
34622
+ if (spec.name === "workit_init_apply") {
34623
+ return { content: executeInitApply(input, root, initApplyRuntime) };
34624
+ }
34625
+ return resultContent(failure("not_found", `unknown Workit tool ${spec.name}`));
34626
+ }
34627
+ });
34628
+ }
34629
+ });
34630
+ await ctx.tool.hook("execute.before", async (event) => {
34631
+ if (event.tool !== "subagent")
34632
+ return;
34633
+ await lifecycle.executeBefore({
34634
+ tool: event.tool,
34635
+ sessionID: String(event.sessionID),
34636
+ id: String(event.id),
34637
+ input: event.input
34638
+ });
34639
+ });
34640
+ await ctx.tool.hook("execute.after", async (event) => {
34641
+ if (event.tool === "question") {
34642
+ if (event.status !== "completed")
34643
+ return;
34644
+ const metadata = event.result.metadata;
34645
+ const answers = metadata?.answers;
34646
+ receipts.record({ sessionID: String(event.sessionID), callID: String(event.id), args: event.input }, {
34647
+ metadata: metadata === undefined ? undefined : { answers: normalizeQuestionAnswers(answers) }
34648
+ });
34649
+ return;
34650
+ }
34651
+ if (event.tool !== "subagent")
34652
+ return;
34653
+ await lifecycle.executeAfter({
34654
+ tool: event.tool,
34655
+ sessionID: String(event.sessionID),
34656
+ id: String(event.id),
34657
+ input: event.input,
34658
+ ...event.status === "completed" ? { status: "completed", result: event.result } : { status: "error", error: event.error }
34659
+ });
34660
+ });
34661
+ await ctx.permission.hook("evaluate", (event) => {
34662
+ evaluateShellPermission(root, event);
34663
+ });
34664
+ await ctx.session.hook("context", async (event) => {
34665
+ const session = await sessionFacts(ctx, String(event.sessionID));
34666
+ injectAgentContext(root, session, lifecycle.directChildren, event.system);
34667
+ });
34668
+ await ctx.session.hook("compaction", (event) => {
34669
+ injectCompactionContext(root, String(event.sessionID), event.system);
34670
+ });
34671
+ await registerSkills(ctx);
34672
+ await registerCommands(ctx);
34673
+ const subscription = new AbortController;
34674
+ (async () => {
34675
+ try {
34676
+ for await (const event of ctx.event.subscribe({ signal: subscription.signal })) {
34677
+ await lifecycle.handleEvent(event);
34678
+ }
34679
+ } catch {}
34680
+ })();
34681
+ return () => {
34682
+ subscription.abort();
34683
+ };
34684
+ };
34685
+ var definition = define({ id: "workit", setup });
34686
+ var plugin_default = definition;
34687
+
34688
+ // packages/workit-opencode/src/index.ts
34689
+ var src_default = { ...plugin_default, server: server_default };
33516
34690
  export {
33517
- plugin_default as default
34691
+ src_default as default,
34692
+ server_default as server
33518
34693
  };