@brainervirus/workit-opencode 1.1.0 → 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 -355
  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" };
@@ -29224,7 +29443,6 @@ function mergePr(cwd, opts) {
29224
29443
  cmd.push(squash ? "--squash" : "--merge");
29225
29444
  if (removeBranch)
29226
29445
  cmd.push("--delete-branch");
29227
- cmd.push("--yes");
29228
29446
  cmdEnv = { ...process.env, PATH: process.env.PATH ?? "", GH_TOKEN: token };
29229
29447
  }
29230
29448
  const result = spawnSync6(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
@@ -29245,14 +29463,14 @@ function mergePr(cwd, opts) {
29245
29463
 
29246
29464
  // packages/workit-core/src/core/init.ts
29247
29465
  import fs11 from "node:fs";
29248
- import path21 from "node:path";
29466
+ import path23 from "node:path";
29249
29467
  import { spawnSync as spawnSync8 } from "node:child_process";
29250
29468
 
29251
29469
  // packages/workit-core/src/core/safe-write.ts
29252
- import { writeFileSync as writeFileSync6 } from "node:fs";
29470
+ import { writeFileSync as writeFileSync4 } from "node:fs";
29253
29471
  function writeFileExclusive(file, content, mode) {
29254
29472
  try {
29255
- writeFileSync6(file, content, { encoding: "utf8", flag: "wx", mode });
29473
+ writeFileSync4(file, content, { encoding: "utf8", flag: "wx", mode });
29256
29474
  return "created";
29257
29475
  } catch (err) {
29258
29476
  if (err.code === "EEXIST")
@@ -29265,19 +29483,19 @@ function writeFileExclusive(file, content, mode) {
29265
29483
  import {
29266
29484
  copyFileSync as copyFileSync2,
29267
29485
  cpSync as cpSync2,
29268
- existsSync as existsSync12,
29486
+ existsSync as existsSync13,
29269
29487
  lstatSync as lstatSync4,
29270
29488
  mkdirSync as mkdirSync5,
29271
29489
  mkdtempSync,
29272
29490
  readFileSync as readFileSync12,
29273
29491
  readdirSync as readdirSync6,
29274
- realpathSync as realpathSync7,
29492
+ realpathSync as realpathSync8,
29275
29493
  renameSync as renameSync2,
29276
29494
  rmSync,
29277
29495
  statSync as statSync3,
29278
29496
  writeFileSync as writeFileSync7
29279
29497
  } from "node:fs";
29280
- import path19 from "node:path";
29498
+ import path21 from "node:path";
29281
29499
  import { isDeepStrictEqual } from "node:util";
29282
29500
 
29283
29501
  // packages/workit-core/src/core/branch-policy.ts
@@ -29340,6 +29558,118 @@ var detectBranchPolicy = (workspaceRoot) => {
29340
29558
  };
29341
29559
  };
29342
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
+
29343
29673
  // packages/workit-core/src/core/registration.ts
29344
29674
  var CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@latest";
29345
29675
  var CURSOR_HOOK_RUN_COMMAND = `npx -y --prefer-online --min-release-age=0 --package=${CURSOR_RUNTIME_PACKAGE} workit-cursor-hook`;
@@ -29381,12 +29711,12 @@ var VCS_OVERRIDES = [
29381
29711
  var SETUP_OVERRIDES = [...YT_OVERRIDES, ...VCS_OVERRIDES];
29382
29712
  function applyWorkspaceBranchPolicy(opts) {
29383
29713
  const { workspace_root, env = process.env } = opts;
29384
- const dir = path19.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
29714
+ const dir = path21.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
29385
29715
  const { status, path: wsPath, entries } = readWorkspacesResult(dir);
29386
29716
  if (status === "malformed")
29387
29717
  return { ok: false, error: `malformed workspaces.json: ${wsPath}` };
29388
29718
  const detection = detectBranchPolicy(workspace_root);
29389
- const name = String(env.WORKFLOW_BP_NAME ?? path19.basename(workspace_root));
29719
+ const name = String(env.WORKFLOW_BP_NAME ?? path21.basename(workspace_root));
29390
29720
  const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
29391
29721
  const policy = {
29392
29722
  preset: detection.preset,
@@ -29411,7 +29741,7 @@ function applyWorkspaceBranchPolicy(opts) {
29411
29741
  };
29412
29742
  }
29413
29743
  const next = existing ? entries.map((w, i) => i === idx ? { ...w, branchPolicy: policy } : w) : [...entries, { name, glob, branchPolicy: policy }];
29414
- mkdirSync5(path19.dirname(wsPath), { recursive: true });
29744
+ mkdirSync5(path21.dirname(wsPath), { recursive: true });
29415
29745
  writeFileSync7(wsPath, JSON.stringify({ workspaces: next }, null, 2) + `
29416
29746
  `, "utf8");
29417
29747
  return {
@@ -29425,7 +29755,7 @@ function applyWorkspaceBranchPolicy(opts) {
29425
29755
 
29426
29756
  // packages/workit-core/src/core/youtrack.ts
29427
29757
  import fs10 from "node:fs";
29428
- import path20 from "node:path";
29758
+ import path22 from "node:path";
29429
29759
 
29430
29760
  // packages/workit-core/src/core/templates.ts
29431
29761
  var repoRoot4 = assetRoot();
@@ -29433,8 +29763,8 @@ var repoRoot4 = assetRoot();
29433
29763
  // packages/workit-core/src/core/youtrack.ts
29434
29764
  var ISSUE_RE = /^[A-Z]+-\d+$/;
29435
29765
  var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
29436
- var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path20.join(configDir(), "youtrack.json");
29437
- 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");
29438
29768
  var youTrackTokenModeOk = (p) => {
29439
29769
  if (process.platform === "win32")
29440
29770
  return true;
@@ -29478,7 +29808,7 @@ function youTrackConfigLoad() {
29478
29808
  }
29479
29809
  const cfgPath = loaded.path;
29480
29810
  const tokenFile = String(loaded.config.tokenFile ?? "");
29481
- 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) : "";
29482
29812
  if (!tokenPath || !fs10.existsSync(tokenPath)) {
29483
29813
  return { error: "missing youtrack.token" };
29484
29814
  }
@@ -29492,8 +29822,8 @@ function youTrackConfigLoad() {
29492
29822
  const redacted = { ...loaded.config };
29493
29823
  delete redacted.tokenFile;
29494
29824
  redacted.tokenPresent = true;
29495
- redacted.configPath = path20.resolve(cfgPath);
29496
- redacted.tokenPath = path20.resolve(tokenPath);
29825
+ redacted.configPath = path22.resolve(cfgPath);
29826
+ redacted.tokenPath = path22.resolve(tokenPath);
29497
29827
  return { data: redacted };
29498
29828
  }
29499
29829
  function tzParts(date, tz) {
@@ -29606,7 +29936,7 @@ var youTrackToken = () => {
29606
29936
  if ("error" in loaded)
29607
29937
  return loaded;
29608
29938
  const tokenFile = String(loaded.config.tokenFile ?? "");
29609
- 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) : "";
29610
29940
  if (!tokenPath || !fs10.existsSync(tokenPath))
29611
29941
  return { error: "missing youtrack.token" };
29612
29942
  if (!youTrackTokenModeOk(tokenPath))
@@ -29761,7 +30091,7 @@ function youTrackTokenCreateUrl() {
29761
30091
  const desc = String(defaults.description ?? "OpenCode workit — /wk-issue-update and /wk-meetings");
29762
30092
  const scopes = Array.isArray(defaults.scopes) ? defaults.scopes : ["YouTrack"];
29763
30093
  const base = String(config.baseUrl ?? "https://enghouseamg.youtrack.cloud").replace(/\/+$/, "");
29764
- 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"));
29765
30095
  const tab = String(defaults.profileTab ?? "account-security");
29766
30096
  const createUrl = `${base}/users/me?${new URLSearchParams({ tab })}`;
29767
30097
  const docsUrl = "https://www.jetbrains.com/help/youtrack/cloud/manage-permanent-token.html";
@@ -29770,7 +30100,7 @@ function youTrackTokenCreateUrl() {
29770
30100
  tokenName: name,
29771
30101
  tokenDescription: desc,
29772
30102
  scopes,
29773
- tokenFile: path20.resolve(tokenFile),
30103
+ tokenFile: path22.resolve(tokenFile),
29774
30104
  createUrl,
29775
30105
  docsUrl,
29776
30106
  prefillSupported: false,
@@ -30088,12 +30418,12 @@ var resolvePath = (p) => {
30088
30418
  try {
30089
30419
  return fs11.realpathSync(p);
30090
30420
  } catch {
30091
- return path21.resolve(p);
30421
+ return path23.resolve(p);
30092
30422
  }
30093
30423
  };
30094
30424
  function initStatusData(configDirPath = configDir()) {
30095
- const ytJson = path21.join(configDirPath, "youtrack.json");
30096
- const vcsJson = path21.join(configDirPath, "vcs.json");
30425
+ const ytJson = path23.join(configDirPath, "youtrack.json");
30426
+ const vcsJson = path23.join(configDirPath, "vcs.json");
30097
30427
  const items = [];
30098
30428
  let youtrackConfig = null;
30099
30429
  let youtrackTokenCreate = null;
@@ -30116,8 +30446,8 @@ function initStatusData(configDirPath = configDir()) {
30116
30446
  });
30117
30447
  }
30118
30448
  }
30119
- const expanded = tokenFile ? path21.resolve(tokenFile) : null;
30120
- 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;
30121
30451
  youtrackConfig = {
30122
30452
  config_edit_path: resolvePath(ytJson),
30123
30453
  baseUrl: base,
@@ -30164,11 +30494,11 @@ function initStatusData(configDirPath = configDir()) {
30164
30494
  id: "youtrack_json",
30165
30495
  label: "YouTrack config",
30166
30496
  ok: fs11.existsSync(ytJson) && youtrackConfig !== null && !("error" in youtrackConfig),
30167
- path: fs11.existsSync(ytJson) ? resolvePath(ytJson) : path21.resolve(ytJson),
30497
+ path: fs11.existsSync(ytJson) ? resolvePath(ytJson) : path23.resolve(ytJson),
30168
30498
  config_edit_path: resolvePath(ytJson),
30169
30499
  fix: "workit_init_apply action=youtrack_scaffold"
30170
30500
  });
30171
- const ytTokenPath = youtrackConfig?.tokenFile ?? path21.join(configDirPath, "youtrack.token");
30501
+ const ytTokenPath = youtrackConfig?.tokenFile ?? path23.join(configDirPath, "youtrack.token");
30172
30502
  const tokenText = fs11.existsSync(ytTokenPath) ? fs11.readFileSync(ytTokenPath, "utf8").trim() : "";
30173
30503
  const placeholder = fs11.existsSync(ytTokenPath) && isPlaceholder(tokenText);
30174
30504
  const tokenOk = fs11.existsSync(ytTokenPath) && modeOk(ytTokenPath) && Boolean(tokenText) && !isPlaceholder(tokenText);
@@ -30176,8 +30506,8 @@ function initStatusData(configDirPath = configDir()) {
30176
30506
  id: "youtrack_token",
30177
30507
  label: "YouTrack API token (mode 600, not placeholder)",
30178
30508
  ok: tokenOk,
30179
- path: fs11.existsSync(ytTokenPath) ? resolvePath(ytTokenPath) : path21.resolve(ytTokenPath),
30180
- 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),
30181
30511
  placeholder,
30182
30512
  fix: `Open ${resolvePath(ytTokenPath)} — replace ${TOKEN_PLACEHOLDER3} with your permanent token, save, then /wk-status`
30183
30513
  };
@@ -30203,7 +30533,7 @@ function initStatusData(configDirPath = configDir()) {
30203
30533
  const provider = typeof rawProvider === "string" && rawProvider.trim() ? rawProvider.toLowerCase() : null;
30204
30534
  const tokenFiles = {};
30205
30535
  for (const k of ["gitlab", "github"]) {
30206
- tokenFiles[k] = String(vcsParsed[k]?.tokenFile ?? path21.join(configDirPath, `${k}.token`));
30536
+ tokenFiles[k] = String(vcsParsed[k]?.tokenFile ?? path23.join(configDirPath, `${k}.token`));
30207
30537
  }
30208
30538
  vcsCfg = {
30209
30539
  config_edit_path: resolvePath(vcsJson),
@@ -30228,14 +30558,14 @@ function initStatusData(configDirPath = configDir()) {
30228
30558
  id: "vcs_json",
30229
30559
  label: "VCS config (GitLab / GitHub)",
30230
30560
  ok: fs11.existsSync(vcsJson) && vcsCfg !== null && !("error" in vcsCfg),
30231
- path: fs11.existsSync(vcsJson) ? resolvePath(vcsJson) : path21.resolve(vcsJson),
30561
+ path: fs11.existsSync(vcsJson) ? resolvePath(vcsJson) : path23.resolve(vcsJson),
30232
30562
  config_edit_path: resolvePath(vcsJson),
30233
30563
  fix: "workit_init_apply action=vcs_scaffold"
30234
30564
  });
30235
30565
  const provActive = vcsCfg && !("error" in vcsCfg) ? vcsCfg.provider : null;
30236
30566
  const tokenItem = (tid, label, rawPath, providerKey) => {
30237
- const t = path21.isAbsolute(rawPath) ? rawPath : path21.resolve(configDirPath, rawPath);
30238
- 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);
30239
30569
  const text = fs11.existsSync(t) ? fs11.readFileSync(t, "utf8").trim() : "";
30240
30570
  const ph = isPlaceholder(text);
30241
30571
  const ok = fs11.existsSync(t) && modeOk(t) && Boolean(text) && !ph;
@@ -30266,7 +30596,7 @@ function initStatusData(configDirPath = configDir()) {
30266
30596
  };
30267
30597
  const vcsTokenFiles = {};
30268
30598
  for (const k of ["gitlab", "github"]) {
30269
- vcsTokenFiles[k] = String(vcsParsed?.[k]?.tokenFile ?? path21.join(configDirPath, `${k}.token`));
30599
+ vcsTokenFiles[k] = String(vcsParsed?.[k]?.tokenFile ?? path23.join(configDirPath, `${k}.token`));
30270
30600
  }
30271
30601
  items.push(tokenItem("gitlab_token", "GitLab token (mode 600, not placeholder)", vcsTokenFiles.gitlab, "gitlab"));
30272
30602
  items.push(tokenItem("github_token", "GitHub token (mode 600, not placeholder)", vcsTokenFiles.github, "github"));
@@ -30323,7 +30653,7 @@ async function toolkitStatusData(configDirPath = configDir()) {
30323
30653
  }
30324
30654
  var youtrackJsonContent = (dir) => ({
30325
30655
  baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://enghouseamg.youtrack.cloud",
30326
- 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"),
30327
30657
  timezone: process.env.WORKFLOW_YT_TIMEZONE ?? "America/Santiago",
30328
30658
  locale: "es-CL",
30329
30659
  defaultMention: process.env.WORKFLOW_YT_MENTION ?? "Alejandra.Flores",
@@ -30362,11 +30692,11 @@ var vcsJsonContent = (dir, cwd) => {
30362
30692
  gitlab: {
30363
30693
  host: process.env.WORKFLOW_GITLAB_HOST ?? "gitlab.com",
30364
30694
  apiUrl: process.env.WORKFLOW_GITLAB_API_URL ?? "https://gitlab.com/api/v4",
30365
- tokenFile: path21.join(dir, "gitlab.token")
30695
+ tokenFile: path23.join(dir, "gitlab.token")
30366
30696
  },
30367
30697
  github: {
30368
30698
  host: process.env.WORKFLOW_GITHUB_HOST ?? "github.com",
30369
- tokenFile: path21.join(dir, "github.token")
30699
+ tokenFile: path23.join(dir, "github.token")
30370
30700
  },
30371
30701
  pr: { squashOnMerge: true, removeSourceBranch: true, pushBranch: true, confirmSkip: true },
30372
30702
  tokenDefaults: {
@@ -30383,16 +30713,16 @@ function initApplyData(action, env = process.env) {
30383
30713
  fs11.mkdirSync(dir, { recursive: true });
30384
30714
  switch (action) {
30385
30715
  case "youtrack_json": {
30386
- const out = path21.join(dir, "youtrack.json");
30716
+ const out = path23.join(dir, "youtrack.json");
30387
30717
  fs11.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
30388
30718
  `, "utf8");
30389
30719
  return { action, ok: true, path: out };
30390
30720
  }
30391
30721
  case "youtrack_token_placeholder": {
30392
- const p = path21.join(dir, "youtrack.token");
30722
+ const p = path23.join(dir, "youtrack.token");
30393
30723
  const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER3 + `
30394
30724
  `, 384) === "preserved";
30395
- const abs = path21.resolve(p);
30725
+ const abs = path23.resolve(p);
30396
30726
  return {
30397
30727
  action,
30398
30728
  ok: true,
@@ -30404,14 +30734,14 @@ function initApplyData(action, env = process.env) {
30404
30734
  };
30405
30735
  }
30406
30736
  case "youtrack_scaffold": {
30407
- const jsonOut = path21.join(dir, "youtrack.json");
30408
- const tokenOut = path21.join(dir, "youtrack.token");
30737
+ const jsonOut = path23.join(dir, "youtrack.json");
30738
+ const tokenOut = path23.join(dir, "youtrack.token");
30409
30739
  fs11.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
30410
30740
  `, "utf8");
30411
30741
  const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER3 + `
30412
30742
  `, 384) === "preserved";
30413
- const configPath = path21.resolve(jsonOut);
30414
- const tokenPath = path21.resolve(tokenOut);
30743
+ const configPath = path23.resolve(jsonOut);
30744
+ const tokenPath = path23.resolve(tokenOut);
30415
30745
  const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
30416
30746
  process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
30417
30747
  let tokenCreate = {};
@@ -30465,19 +30795,19 @@ function initApplyData(action, env = process.env) {
30465
30795
  }
30466
30796
  }
30467
30797
  case "vcs_scaffold": {
30468
- const jsonOut = path21.join(dir, "vcs.json");
30798
+ const jsonOut = path23.join(dir, "vcs.json");
30469
30799
  fs11.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(dir), null, 2) + `
30470
30800
  `, "utf8");
30471
- const glPath = path21.join(dir, "gitlab.token");
30472
- const ghPath = path21.join(dir, "github.token");
30801
+ const glPath = path23.join(dir, "gitlab.token");
30802
+ const ghPath = path23.join(dir, "github.token");
30473
30803
  const preservedTokens = [];
30474
30804
  for (const p of [glPath, ghPath]) {
30475
30805
  if (writeFileExclusive(p, TOKEN_PLACEHOLDER3 + `
30476
30806
  `, 384) === "preserved") {
30477
- preservedTokens.push(path21.resolve(p));
30807
+ preservedTokens.push(path23.resolve(p));
30478
30808
  }
30479
30809
  }
30480
- const configPath = path21.resolve(jsonOut);
30810
+ const configPath = path23.resolve(jsonOut);
30481
30811
  const prev = process.env.WORKFLOW_VCS_CONFIG;
30482
30812
  process.env.WORKFLOW_VCS_CONFIG = configPath;
30483
30813
  try {
@@ -30486,14 +30816,14 @@ function initApplyData(action, env = process.env) {
30486
30816
  const provider = typeof rawProvider === "string" && rawProvider.trim() ? rawProvider : null;
30487
30817
  const tokenUrls = vcsTokenCreateUrls();
30488
30818
  const active = tokenUrls.active ?? {};
30489
- 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;
30490
30820
  return {
30491
30821
  action,
30492
30822
  ok: true,
30493
30823
  vcs_json: configPath,
30494
30824
  config_edit_path: configPath,
30495
- gitlab_token: path21.resolve(glPath),
30496
- github_token: path21.resolve(ghPath),
30825
+ gitlab_token: path23.resolve(glPath),
30826
+ github_token: path23.resolve(ghPath),
30497
30827
  token_edit_path: activePath,
30498
30828
  token_create_url: active.createUrl,
30499
30829
  token_create_urls: tokenUrls,
@@ -30548,6 +30878,109 @@ var normalizeLegacyResult = (value) => {
30548
30878
  return ok(data);
30549
30879
  };
30550
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
+
30551
30984
  // packages/workit-opencode/src/tools/repo.ts
30552
30985
  var defaultRuntime = {
30553
30986
  git: (root, args) => run2(root, "git", args),
@@ -30565,15 +30998,7 @@ var defaultRuntime = {
30565
30998
  cwd: root
30566
30999
  };
30567
31000
  },
30568
- initApply: (root, action, env) => {
30569
- const out = initApply({ action, confirmed: true, env });
30570
- return {
30571
- exitCode: out.error ? 1 : 0,
30572
- stdout: JSON.stringify(out.data ?? out),
30573
- stderr: "",
30574
- cwd: root
30575
- };
30576
- },
31001
+ ...initApplyRuntime,
30577
31002
  initStatus: (root) => ({
30578
31003
  exitCode: 0,
30579
31004
  stdout: JSON.stringify(initStatusData()),
@@ -30587,36 +31012,7 @@ var defaultRuntime = {
30587
31012
  cwd: root
30588
31013
  })
30589
31014
  };
30590
- var output = (value) => JSON.stringify(value, null, 2);
30591
- var diagnostics = ({ stdout, stderr, exitCode }) => ({ stdout, stderr, exitCode });
30592
- var requireConfirmed = (confirmed) => confirmed === true ? null : output(fail("confirmed: true required"));
30593
- function scriptResult(result, parse) {
30594
- if (result.exitCode !== 0) {
30595
- return fail(result.stderr.trim() || result.stdout.trim() || "workflow script failed", diagnostics(result));
30596
- }
30597
- try {
30598
- return ok({
30599
- ...parse(result.stdout),
30600
- exitCode: 0,
30601
- ...result.stderr ? { stderr: result.stderr } : {}
30602
- });
30603
- } catch (error) {
30604
- return fail(error instanceof Error ? error.message : "workflow output parse failed", diagnostics(result));
30605
- }
30606
- }
30607
31015
  var json2 = (stdout) => JSON.parse(stdout.trim());
30608
- var legacyScriptResult = (result) => {
30609
- let parsed = null;
30610
- try {
30611
- parsed = json2(result.stdout);
30612
- } catch {}
30613
- if (result.exitCode !== 0 || parsed?.error || parsed?.ok === false)
30614
- 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));
30615
- if (!parsed)
30616
- return fail("workflow output parse failed", diagnostics(result));
30617
- const { ok: _legacyOk, ...data } = parsed;
30618
- return ok({ ...data, exitCode: 0, ...result.stderr ? { stderr: result.stderr } : {} });
30619
- };
30620
31016
  var optionalJson = (value) => {
30621
31017
  if (!value?.trim())
30622
31018
  return null;
@@ -30771,7 +31167,7 @@ function createRepoTools(runtime = defaultRuntime) {
30771
31167
  entries,
30772
31168
  path: changelogPath,
30773
31169
  normalize_only,
30774
- workspace_root: realpathSync8(context.directory)
31170
+ workspace_root: realpathSync9(context.directory)
30775
31171
  })));
30776
31172
  }
30777
31173
  }),
@@ -30892,77 +31288,11 @@ function createRepoTools(runtime = defaultRuntime) {
30892
31288
  branch_policy_protected: tool.schema.array(tool.schema.string()).optional(),
30893
31289
  include_open_source: tool.schema.boolean().optional()
30894
31290
  },
30895
- execute: async ({
30896
- confirmed,
30897
- action,
30898
- base_url,
30899
- default_mention,
30900
- meeting_issue,
30901
- vcs_provider,
30902
- vcs_target_branch,
30903
- name,
30904
- develop_branch,
30905
- integration,
30906
- locale,
30907
- locale_options,
30908
- timezone,
30909
- branch_policy_preset,
30910
- branch_policy_allowed,
30911
- branch_policy_protected,
30912
- include_open_source
30913
- }, context) => {
30914
- const rejected = requireConfirmed(confirmed);
30915
- if (rejected)
30916
- return rejected;
30917
- if (action === "hygiene") {
30918
- const result = ensureHygieneFiles(context.directory, {
30919
- confirmed,
30920
- includeOpenSource: include_open_source
30921
- });
30922
- return output(result.ok ? ok(result) : fail(result.error));
30923
- }
30924
- if (action === "gitignore") {
30925
- const result = ensureProjectGitignore(context.directory, confirmed);
30926
- return output(result.ok ? ok(result) : fail(result.error));
30927
- }
30928
- if (action === "config") {
30929
- const LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
30930
- if (locale !== undefined && !LOCALE_RE.test(locale)) {
30931
- return output(fail(`invalid locale: ${JSON.stringify(locale)} — expected BCP-47 like en or es-CL`));
30932
- }
30933
- const current = readConfig();
30934
- const next = mergeConfigValues({
30935
- locale,
30936
- localeOptions: locale_options,
30937
- timezone,
30938
- preset: branch_policy_preset,
30939
- allowed: branch_policy_allowed,
30940
- protectedNames: branch_policy_protected
30941
- }, current);
30942
- writeConfig(next);
30943
- return output(ok({ action: "config", path: path22.join(configDir(), "config.json"), ...next }));
30944
- }
30945
- const env = Object.fromEntries(Object.entries({
30946
- WORKFLOW_YT_BASE_URL: base_url,
30947
- WORKFLOW_YT_MENTION: default_mention,
30948
- WORKFLOW_YT_MEETING_ISSUE: meeting_issue,
30949
- WORKFLOW_VCS_PROVIDER: vcs_provider,
30950
- WORKFLOW_VCS_TARGET_BRANCH: vcs_target_branch,
30951
- WORKFLOW_WORKSPACE_ROOT: context.directory,
30952
- WORKFLOW_BP_NAME: name,
30953
- WORKFLOW_BP_DEVELOP: develop_branch,
30954
- WORKFLOW_BP_INTEGRATION: integration
30955
- }).filter((entry) => entry[1] !== undefined));
30956
- return output(legacyScriptResult(runtime.initApply(context.directory, action, env)));
30957
- }
31291
+ execute: async (args, context) => executeInitApply(args, context.directory, runtime)
30958
31292
  })
30959
31293
  };
30960
31294
  }
30961
31295
 
30962
- // packages/workit-opencode/src/tools/workit.ts
30963
- import { realpathSync as realpathSync9 } from "node:fs";
30964
- import path24 from "node:path";
30965
-
30966
31296
  // packages/workit-core/src/core/tracker-issues.ts
30967
31297
  import fs12 from "node:fs";
30968
31298
  import { spawnSync as spawnSync9 } from "node:child_process";
@@ -31234,7 +31564,7 @@ async function logTimeUpdate(input, operation = defaultOperations) {
31234
31564
 
31235
31565
  // packages/workit-core/src/core/external-action-effects.ts
31236
31566
  import fs13 from "node:fs";
31237
- import path23 from "node:path";
31567
+ import path25 from "node:path";
31238
31568
 
31239
31569
  // packages/workit-core/src/core/commit-flavors.ts
31240
31570
  var CONVENTIONAL_RE = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?(!)?: .+/;
@@ -32061,7 +32391,7 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
32061
32391
  if (preview.error)
32062
32392
  return failure("invalid_input", preview.error);
32063
32393
  const target = String(preview.path);
32064
- 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";
32065
32395
  const normalized = {
32066
32396
  operation: request.operation,
32067
32397
  payload: {
@@ -32317,24 +32647,21 @@ var executeResolvedExternalAction = async (resolved, root, step, caller) => {
32317
32647
  return executeConcreteExternalAction(fresh.data.request, root, fresh.data.marker, resolvedDate, step, typeof workText === "string" ? workText : undefined, caller, beforeDigest, beforeExists);
32318
32648
  };
32319
32649
 
32320
- // packages/workit-opencode/src/tools/workit.ts
32321
- var sessionParent = (session) => {
32322
- if (typeof session !== "object" || session === null)
32323
- return null;
32324
- if (!Object.prototype.hasOwnProperty.call(session, "parentID"))
32325
- return;
32326
- const parentID = session.parentID;
32327
- return typeof parentID === "string" && parentID.length > 0 ? parentID : null;
32328
- };
32329
- var rejectedDescription = "Reject this decision";
32330
- 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;
32331
32656
  try {
32332
- const value = JSON.parse(content);
32333
- return typeof value.operation === "string" && value.operation.length > 0;
32657
+ return realpathSync10(expected) === realpathSync10(observed);
32334
32658
  } catch {
32335
- return /^[a-z][a-z_]*\.[a-z_]+$/.test(content);
32659
+ return path26.resolve(expected) === path26.resolve(observed);
32336
32660
  }
32337
32661
  };
32662
+
32663
+ // packages/workit-opencode/src/shared/decision-content.ts
32664
+ var rejectedDescription = "Reject this decision";
32338
32665
  var decisionContent = (purpose, question, approvedContent) => ({
32339
32666
  header: `Workit decision: ${purpose}`,
32340
32667
  question,
@@ -32343,6 +32670,24 @@ var decisionContent = (purpose, question, approvedContent) => ({
32343
32670
  { label: "rejected", description: rejectedDescription }
32344
32671
  ]
32345
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
+ };
32346
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;
32347
32692
  var normalizeReceiptLabel = (label) => label.trim().replace(/(?:\s*[([]\s*[^()[\]]*\s*[)\]]\s*)+$/u, "").trim().toLowerCase();
32348
32693
  var purposeForQuestion = (question) => {
@@ -32541,15 +32886,6 @@ var sessionData = async (client, sessionID) => {
32541
32886
  return null;
32542
32887
  }
32543
32888
  };
32544
- var sameWorkspace = (expected, observed) => {
32545
- if (typeof observed !== "string" || !observed)
32546
- return false;
32547
- try {
32548
- return realpathSync9(expected) === realpathSync9(observed);
32549
- } catch {
32550
- return path24.resolve(expected) === path24.resolve(observed);
32551
- }
32552
- };
32553
32889
  var hostRef = (handle) => ({ kind: "host", host: "opencode", handle });
32554
32890
  var opencodeCapabilities = () => [
32555
32891
  {
@@ -32963,6 +33299,16 @@ var createWorkitTools = ({
32963
33299
  }
32964
33300
  }
32965
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
+ }
32966
33312
  if (!selected.ok && !planAuthorized && !autoApplies) {
32967
33313
  if (selected.error.startsWith("no approved action")) {
32968
33314
  const proposal = actionProposalQuestion(resolved.data.request, resolved.data.descriptorPayload);
@@ -33091,9 +33437,9 @@ var loadProvenance = (logger, pkgUrl) => {
33091
33437
  }
33092
33438
  };
33093
33439
 
33094
- // packages/workit-opencode/src/plugin.ts
33095
- var root = fileURLToPath3(new URL("../assets/", import.meta.url));
33096
- 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");
33097
33443
  var logger = createLogger({
33098
33444
  appLog: () => {
33099
33445
  return;
@@ -33112,7 +33458,7 @@ var warnStaleSources = () => {
33112
33458
  logger.warn(EVENT.hooks, {
33113
33459
  boundary: "stale_sources",
33114
33460
  reason: "workit sources changed after plugin load; restart the session for latest behavior",
33115
- files: changed.map((file) => path25.basename(file))
33461
+ files: changed.map((file) => path27.basename(file))
33116
33462
  });
33117
33463
  } catch {}
33118
33464
  };
@@ -33141,7 +33487,7 @@ var plugin = async ({ client, directory }) => {
33141
33487
  const dispatches = new Map;
33142
33488
  try {
33143
33489
  logger.info(EVENT.initialization, { host: "opencode", plugin_root: root });
33144
- 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")));
33145
33491
  } catch (error) {
33146
33492
  logger.warn(EVENT.hooks, { boundary: "initialization", ...errorDetail(error) });
33147
33493
  }
@@ -33443,7 +33789,7 @@ var plugin = async ({ client, directory }) => {
33443
33789
  config: async (config) => {
33444
33790
  const mutable = config;
33445
33791
  const paths = [...mutable.skills?.paths ?? []];
33446
- if (existsSync13(skillsPath) && !paths.includes(skillsPath))
33792
+ if (existsSync14(skillsPath) && !paths.includes(skillsPath))
33447
33793
  paths.push(skillsPath);
33448
33794
  mutable.skills ??= {};
33449
33795
  mutable.skills.paths = paths;
@@ -33513,7 +33859,835 @@ var plugin = async ({ client, directory }) => {
33513
33859
  }
33514
33860
  };
33515
33861
  };
33516
- 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 };
33517
34690
  export {
33518
- plugin_default as default
34691
+ src_default as default,
34692
+ server_default as server
33519
34693
  };