@brainervirus/workit-codex 1.0.12 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // packages/workit-codex/hooks/workit-hook.ts
6
- import { existsSync as existsSync4, realpathSync as realpathSync5, statSync } from "node:fs";
7
- import path7 from "node:path";
6
+ import { existsSync as existsSync5, realpathSync as realpathSync6, statSync } from "node:fs";
7
+ import path9 from "node:path";
8
8
 
9
9
  // packages/workit-core/src/core/task-contract.ts
10
10
  import { createHash, randomUUID } from "node:crypto";
@@ -7353,7 +7353,13 @@ var refSchema = discriminatedUnion("kind", [
7353
7353
  id
7354
7354
  }).strict(),
7355
7355
  object({ kind: literal("host"), host: hostSchema, handle: nonEmpty }).strict(),
7356
- object({ kind: literal("external"), url: text.url() }).strict()
7356
+ object({ kind: literal("external"), url: text.url() }).strict(),
7357
+ object({
7358
+ kind: literal("standing"),
7359
+ workspace: nonEmpty,
7360
+ class: nonEmpty,
7361
+ configDigest: nullableDigest
7362
+ }).strict()
7357
7363
  ]);
7358
7364
  var rewriteUnknown = (value, map) => {
7359
7365
  if (Array.isArray(value)) {
@@ -7658,7 +7664,8 @@ var decisionSchema = object({
7658
7664
  approvedContent: text,
7659
7665
  displayed: text.optional(),
7660
7666
  contentRefs: array(refSchema),
7661
- statedChoice: object({ ref: text, text }).strict().optional()
7667
+ statedChoice: object({ ref: text, text }).strict().optional(),
7668
+ standing: object({ workspace: nonEmpty, class: nonEmpty }).strict().optional()
7662
7669
  }).strict(),
7663
7670
  digest,
7664
7671
  response: _enum(["approved", "rejected", "stated"]),
@@ -9673,15 +9680,362 @@ function assertProductWriteAllowed(input) {
9673
9680
  }
9674
9681
 
9675
9682
  // packages/workit-core/src/core/task-evaluation.ts
9676
- import { createHash as createHash4 } from "node:crypto";
9683
+ import { createHash as createHash5 } from "node:crypto";
9677
9684
  import * as fs6 from "node:fs";
9678
9685
  import { spawnSync } from "node:child_process";
9679
- import path6 from "node:path";
9686
+ import path8 from "node:path";
9680
9687
 
9681
9688
  // packages/workit-core/src/core/authority.ts
9682
- import { createHash as createHash3 } from "node:crypto";
9689
+ import { createHash as createHash4 } from "node:crypto";
9683
9690
  import * as fs5 from "node:fs";
9691
+ import path7 from "node:path";
9692
+
9693
+ // packages/workit-core/src/core/auto-approval.ts
9694
+ import { createHash as createHash3 } from "node:crypto";
9695
+ import { readFileSync as readFileSync4 } from "node:fs";
9696
+
9697
+ // packages/workit-core/src/core/workspaces.ts
9698
+ import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
9699
+ import path6 from "node:path";
9700
+
9701
+ // packages/workit-core/src/core/config.ts
9702
+ import {
9703
+ copyFileSync,
9704
+ cpSync,
9705
+ existsSync as existsSync3,
9706
+ mkdirSync as mkdirSync2,
9707
+ readFileSync as readFileSync2,
9708
+ readdirSync as readdirSync2,
9709
+ writeFileSync as writeFileSync2
9710
+ } from "node:fs";
9711
+ import os from "node:os";
9684
9712
  import path5 from "node:path";
9713
+
9714
+ // packages/workit-core/src/core/boundary.ts
9715
+ var EVENT = {
9716
+ initialization: "initialization",
9717
+ provenance: "provenance",
9718
+ assets: "assets",
9719
+ configurationSource: "configuration_source",
9720
+ hooks: "hooks",
9721
+ mcpConnection: "mcp_connection",
9722
+ migration: "migration",
9723
+ installSteps: "install_steps",
9724
+ uncaughtFailure: "uncaught_failure",
9725
+ toolsFailed: "tools_failed",
9726
+ doctor: "doctor"
9727
+ };
9728
+ var errorDetail = (err) => {
9729
+ if (err instanceof Error) {
9730
+ return { error_name: err.name, error: err.message };
9731
+ }
9732
+ return { error_name: "unknown", error: String(err) };
9733
+ };
9734
+
9735
+ // packages/workit-core/src/core/config.ts
9736
+ var diagnosticLogger;
9737
+ var PRESETS = {
9738
+ gitflow: {
9739
+ allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
9740
+ protected: ["main", "develop", "master", "prod", "production"]
9741
+ },
9742
+ "github-flow": { allowed: ["*"], protected: ["main"] },
9743
+ "trunk-based": { allowed: ["*"], protected: ["main"] },
9744
+ custom: { allowed: [], protected: [] }
9745
+ };
9746
+ var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path5.join(process.env.XDG_CONFIG_HOME || path5.join(os.homedir(), ".config"), "workit");
9747
+ var migratedDir = null;
9748
+ var migrationFailed = false;
9749
+ var ensureConfigDir = (dir = resolveConfigDir()) => {
9750
+ if (migratedDir === dir)
9751
+ return dir;
9752
+ if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
9753
+ migratedDir = dir;
9754
+ return dir;
9755
+ }
9756
+ const legacy = path5.join(process.env.XDG_CONFIG_HOME || path5.join(os.homedir(), ".config"), "workflow-toolkit");
9757
+ if (!existsSync3(legacy)) {
9758
+ migratedDir = dir;
9759
+ return dir;
9760
+ }
9761
+ if (!migrationFailed && existsSync3(dir)) {
9762
+ migratedDir = dir;
9763
+ return dir;
9764
+ }
9765
+ migrationFailed = false;
9766
+ mkdirSync2(dir, { recursive: true });
9767
+ diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
9768
+ for (const entry of readdirSync2(legacy, { withFileTypes: true })) {
9769
+ const src = path5.join(legacy, entry.name);
9770
+ const dest = path5.join(dir, entry.name);
9771
+ if (existsSync3(dest))
9772
+ continue;
9773
+ try {
9774
+ if (entry.isDirectory())
9775
+ cpSync(src, dest, { recursive: true });
9776
+ else if (entry.isFile())
9777
+ copyFileSync(src, dest);
9778
+ } catch (err) {
9779
+ migrationFailed = true;
9780
+ diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
9781
+ console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
9782
+ }
9783
+ }
9784
+ if (!migrationFailed)
9785
+ migratedDir = dir;
9786
+ return dir;
9787
+ };
9788
+ var configDir = () => ensureConfigDir();
9789
+ var DEFAULTS = {
9790
+ locale: "en",
9791
+ localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
9792
+ timezone: "America/Santiago",
9793
+ branchPolicy: {
9794
+ preset: "gitflow",
9795
+ allowed: [...PRESETS.gitflow.allowed],
9796
+ protected: [...PRESETS.gitflow.protected]
9797
+ },
9798
+ commitPolicy: { preset: "conventional" }
9799
+ };
9800
+ var isConfigObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
9801
+
9802
+ // packages/workit-core/src/core/workspaces.ts
9803
+ var readWorkspacesResult = (dir = configDir()) => {
9804
+ const file = path6.join(dir, "workspaces.json");
9805
+ let raw;
9806
+ try {
9807
+ raw = readFileSync3(file, "utf8");
9808
+ } catch {
9809
+ return { status: "missing", path: file, entries: [] };
9810
+ }
9811
+ let parsed;
9812
+ try {
9813
+ parsed = JSON.parse(raw);
9814
+ } catch {
9815
+ return { status: "malformed", path: file, entries: [], error: `${file} is not valid JSON` };
9816
+ }
9817
+ if (!isConfigObject(parsed)) {
9818
+ return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
9819
+ }
9820
+ const list = parsed.workspaces;
9821
+ return {
9822
+ status: "valid",
9823
+ path: file,
9824
+ entries: Array.isArray(list) ? list : []
9825
+ };
9826
+ };
9827
+ var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
9828
+ var globToRegExp = (glob) => {
9829
+ let out = "";
9830
+ for (let i = 0;i < glob.length; i++) {
9831
+ const c = glob[i];
9832
+ if (c === "*") {
9833
+ if (glob[i + 1] === "*") {
9834
+ if (glob[i + 2] === "/") {
9835
+ if (out === "")
9836
+ out += "/?";
9837
+ out += "(?:[^/]+/)*";
9838
+ i += 2;
9839
+ } else {
9840
+ if (i + 2 >= glob.length) {
9841
+ if (out.endsWith("/")) {
9842
+ out = out.slice(0, -1);
9843
+ out += "(?:/.*)?";
9844
+ } else {
9845
+ out += ".*";
9846
+ }
9847
+ } else {
9848
+ out += ".*";
9849
+ }
9850
+ i++;
9851
+ }
9852
+ } else {
9853
+ out += "[^/]*";
9854
+ }
9855
+ } else {
9856
+ out += c.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
9857
+ }
9858
+ }
9859
+ return new RegExp(`^${out}$`);
9860
+ };
9861
+ var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
9862
+ var realpathOf = (p) => {
9863
+ const real = realpathSync2.native ?? realpathSync2;
9864
+ try {
9865
+ return real(p);
9866
+ } catch {
9867
+ return p;
9868
+ }
9869
+ };
9870
+ var canonicalGlob = (glob) => {
9871
+ const m = /[*?[\]{]/.exec(glob);
9872
+ const prefix = m ? glob.slice(0, m.index) : glob;
9873
+ const rest = m ? glob.slice(m.index) : "";
9874
+ if (!prefix)
9875
+ return glob;
9876
+ const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
9877
+ return real + (prefix.endsWith("/") ? "/" : "") + rest;
9878
+ };
9879
+ var resolveWorkspaceFrom = (cwd, dir) => {
9880
+ const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
9881
+ for (const entry of loadWorkspacesFrom(dir)) {
9882
+ if (!entry || typeof entry !== "object")
9883
+ continue;
9884
+ const ws = entry;
9885
+ if (typeof ws.glob !== "string" || !ws.glob)
9886
+ continue;
9887
+ const glob = ws.glob.replaceAll("\\", "/");
9888
+ const canonical = canonicalGlob(glob);
9889
+ for (const target of targets) {
9890
+ if (matchWorkspace(glob, target))
9891
+ return ws;
9892
+ if (canonical !== glob && matchWorkspace(canonical, target))
9893
+ return ws;
9894
+ }
9895
+ }
9896
+ return null;
9897
+ };
9898
+
9899
+ // packages/workit-core/src/core/branch.ts
9900
+ import { execFileSync } from "node:child_process";
9901
+ var verifyPushIdentity = (root, provider, account) => {
9902
+ if (!account || typeof account !== "string")
9903
+ return { ok: false, error: "push identity requires a configured area account" };
9904
+ const probe = provider === "gitlab" ? ["api", "user", "--jq", ".username"] : ["api", "user", "--jq", ".login"];
9905
+ const bin = provider === "gitlab" ? "glab" : "gh";
9906
+ let login;
9907
+ try {
9908
+ login = execFileSync(bin, [...probe], {
9909
+ cwd: root,
9910
+ encoding: "utf8",
9911
+ stdio: ["pipe", "pipe", "pipe"]
9912
+ }).trim();
9913
+ } catch {
9914
+ return { ok: false, error: `${bin} identity could not be resolved` };
9915
+ }
9916
+ if (!login || login.toLowerCase() !== account.toLowerCase())
9917
+ return {
9918
+ ok: false,
9919
+ error: `push identity ${login || "(unknown)"} does not match area account ${account}`
9920
+ };
9921
+ return { ok: true };
9922
+ };
9923
+
9924
+ // packages/workit-core/src/core/auto-approval.ts
9925
+ var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
9926
+ var OPERATION_CLASS = {
9927
+ "git.branch_setup": "branch",
9928
+ "git.commit": "commit",
9929
+ "git.push": "push",
9930
+ "hosting.pull_request": "pr",
9931
+ "hosting.merge": "merge"
9932
+ };
9933
+ var operationAutoClass = (operation) => typeof operation === "string" ? OPERATION_CLASS[operation] ?? null : null;
9934
+ var digestOf = (configDirPath) => {
9935
+ try {
9936
+ const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
9937
+ return createHash3("sha256").update(raw).digest("hex");
9938
+ } catch {
9939
+ return null;
9940
+ }
9941
+ };
9942
+ var normalizeClasses = (value) => {
9943
+ if (value === true)
9944
+ return [...AUTO_CLASSES];
9945
+ if (!Array.isArray(value))
9946
+ return [];
9947
+ const seen = new Set;
9948
+ for (const item of value) {
9949
+ if (typeof item === "string" && AUTO_CLASSES.includes(item))
9950
+ seen.add(item);
9951
+ }
9952
+ return [...seen];
9953
+ };
9954
+ var resolveAutoApproval = (root, dir = configDir()) => {
9955
+ let match;
9956
+ try {
9957
+ match = resolveWorkspaceFrom(root, dir);
9958
+ } catch {
9959
+ return { status: "off" };
9960
+ }
9961
+ if (!match)
9962
+ return { status: "off" };
9963
+ const classes = normalizeClasses(match.autoApprove);
9964
+ if (classes.length === 0)
9965
+ return { status: "off" };
9966
+ return {
9967
+ status: "on",
9968
+ workspace: match.name,
9969
+ classes,
9970
+ account: typeof match.vcs?.account === "string" && match.vcs.account ? match.vcs.account : null,
9971
+ configDigest: digestOf(dir)
9972
+ };
9973
+ };
9974
+ var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
9975
+ var standingApprovalLive = (root, receipt, cls, dir) => {
9976
+ if (!isStandingReceipt(receipt))
9977
+ return false;
9978
+ if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
9979
+ return false;
9980
+ if (receipt.class !== cls)
9981
+ return false;
9982
+ const resolved = resolveAutoApproval(root, dir);
9983
+ if (resolved.status !== "on")
9984
+ return false;
9985
+ if (resolved.workspace !== receipt.workspace)
9986
+ return false;
9987
+ return resolved.classes.includes(cls);
9988
+ };
9989
+ var standingReceiptFor = (root, workspace, cls, dir) => {
9990
+ const resolved = resolveAutoApproval(root, dir);
9991
+ return {
9992
+ kind: "standing",
9993
+ workspace,
9994
+ class: cls,
9995
+ configDigest: resolved.status === "on" && resolved.workspace === workspace ? resolved.configDigest : null
9996
+ };
9997
+ };
9998
+ var verifyStandingApproval = (root, task, caller, binding) => {
9999
+ if (task.status !== "active")
10000
+ return failure("invalid_transition", "only active tasks can authorize actions");
10001
+ const session = task.intent?.provenance?.session;
10002
+ if (!session || session.kind !== "host" || session.host !== caller.host || session.handle !== caller.actor)
10003
+ return failure("permission_denied", "standing approval requires the lead session");
10004
+ const standing = binding.standing;
10005
+ if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
10006
+ return failure("invalid_input", "standing approval needs a workspace and class");
10007
+ let operation;
10008
+ try {
10009
+ operation = typeof binding.approvedContent === "string" ? JSON.parse(binding.approvedContent).operation : null;
10010
+ } catch {
10011
+ operation = null;
10012
+ }
10013
+ const cls = operationAutoClass(operation);
10014
+ if (!cls || cls !== standing.class)
10015
+ return failure("permission_denied", "standing approval does not cover this operation");
10016
+ const resolved = resolveAutoApproval(root);
10017
+ if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
10018
+ return failure("permission_denied", "standing auto-approval is not live for this operation");
10019
+ if (cls === "push") {
10020
+ const workspace = resolveWorkspaceFrom(root, configDir());
10021
+ const provider = workspace?.vcs?.provider;
10022
+ const account = resolved.account;
10023
+ if (!provider || !account)
10024
+ return failure("permission_denied", "auto-push requires a workspace provider and area account");
10025
+ const identity = verifyPushIdentity(root, provider, account);
10026
+ if (!identity.ok)
10027
+ return failure("permission_denied", identity.error);
10028
+ }
10029
+ return success(null, null, {
10030
+ kind: "host_observed",
10031
+ host: caller.host,
10032
+ session: { kind: "host", host: caller.host, handle: caller.actor },
10033
+ workerId: null,
10034
+ receipts: [standingReceiptFor(root, standing.workspace, cls)]
10035
+ });
10036
+ };
10037
+
10038
+ // packages/workit-core/src/core/authority.ts
9685
10039
  var verifiedAuthorities = new WeakMap;
9686
10040
  var trustedAuthority = (kind, provenance, expected, binding, caller) => {
9687
10041
  const token = {};
@@ -9828,7 +10182,7 @@ function applicableDecision(task, purpose, binding, checkoutRoot) {
9828
10182
  return [];
9829
10183
  return task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, entry.data.binding).ok : entry.data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map((entry) => entry.data);
9830
10184
  }
9831
- var digestBytes2 = (bytes) => createHash3("sha256").update(bytes).digest("hex");
10185
+ var digestBytes2 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
9832
10186
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
9833
10187
  let root;
9834
10188
  try {
@@ -9841,23 +10195,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
9841
10195
  continue;
9842
10196
  if (!reference.digest)
9843
10197
  return failure("invalid_input", "document references require a byte digest");
9844
- const target = path5.resolve(root, reference.path);
9845
- if (target !== root && !target.startsWith(`${root}${path5.sep}`))
10198
+ const target = path7.resolve(root, reference.path);
10199
+ if (target !== root && !target.startsWith(`${root}${path7.sep}`))
9846
10200
  return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
9847
10201
  fields: [
9848
10202
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
9849
10203
  ]
9850
10204
  });
9851
10205
  try {
9852
- const relative = path5.relative(root, target);
10206
+ const relative = path7.relative(root, target);
9853
10207
  let current = root;
9854
- for (const segment of relative.split(path5.sep)) {
10208
+ for (const segment of relative.split(path7.sep)) {
9855
10209
  if (!segment)
9856
10210
  continue;
9857
- current = path5.join(current, segment);
10211
+ current = path7.join(current, segment);
9858
10212
  const stat = fs5.lstatSync(current);
9859
10213
  const real = fs5.realpathSync(current);
9860
- if (real !== root && !real.startsWith(`${root}${path5.sep}`))
10214
+ if (real !== root && !real.startsWith(`${root}${path7.sep}`))
9861
10215
  return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
9862
10216
  fields: [
9863
10217
  {
@@ -9910,6 +10264,20 @@ var validateAction = (store, task, workspaceId, input, authority) => {
9910
10264
  return failure("permission_denied", "decision was rejected");
9911
10265
  if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
9912
10266
  return failure("permission_denied", "action approval lacks native receipt assurance");
10267
+ const receipts = entry.provenance.receipts;
10268
+ const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
10269
+ const questionBacked = receipts.some((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "host");
10270
+ if (!questionBacked && standing.length > 0) {
10271
+ let operation;
10272
+ try {
10273
+ operation = JSON.parse(decision.binding.approvedContent).operation;
10274
+ } catch {
10275
+ operation = null;
10276
+ }
10277
+ const cls = operationAutoClass(operation);
10278
+ if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
10279
+ return failure("permission_denied", "standing auto-approval is not live for this operation");
10280
+ }
9913
10281
  if (decision.revoked)
9914
10282
  return failure("permission_denied", "decision is revoked");
9915
10283
  if (decision.digest !== decisionDigest(decision))
@@ -10209,15 +10577,15 @@ var bindingCovers = scopeCovers;
10209
10577
  var verifyDecisionContent = verifyContentRefs;
10210
10578
 
10211
10579
  // packages/workit-core/src/core/task-evaluation.ts
10212
- var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
10213
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
10214
- var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
10580
+ var digestBytes3 = (value) => createHash5("sha256").update(value).digest("hex");
10581
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path8.sep}`);
10582
+ var relative = (root, candidate) => path8.relative(root, candidate).split(path8.sep).join("/") || ".";
10215
10583
  var canonicalPath = (value) => {
10216
10584
  if (typeof value !== "string" || !value)
10217
10585
  return null;
10218
10586
  if (value.includes("\\") || value.split("/").includes(".."))
10219
10587
  return null;
10220
- const normalized = path6.posix.normalize(value);
10588
+ const normalized = path8.posix.normalize(value);
10221
10589
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
10222
10590
  return null;
10223
10591
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -10265,14 +10633,14 @@ var gitPaths = (root) => {
10265
10633
  git: /not a git repository/i.test(stderr) ? false : true
10266
10634
  };
10267
10635
  }
10268
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
10636
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path8.sep).join("/")) : [];
10269
10637
  const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
10270
10638
  if (stagedDeleted.status !== 0) {
10271
10639
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
10272
10640
  if (!/not a git repository/i.test(stderr))
10273
10641
  return { paths, uncertain: true, git: true };
10274
10642
  } else if (stagedDeleted.stdout) {
10275
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
10643
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path8.sep).join("/")));
10276
10644
  }
10277
10645
  return { paths: [...new Set(paths)], uncertain: false, git: true };
10278
10646
  };
@@ -10287,7 +10655,7 @@ var walk = (root, directory, output) => {
10287
10655
  for (const name of names) {
10288
10656
  if (name === ".git" || name === ".workit")
10289
10657
  continue;
10290
- const target = path6.join(directory, name);
10658
+ const target = path8.join(directory, name);
10291
10659
  let stat;
10292
10660
  try {
10293
10661
  stat = fs6.lstatSync(target);
@@ -10305,12 +10673,12 @@ var walk = (root, directory, output) => {
10305
10673
  var scopeRoots = (root, scope) => {
10306
10674
  const roots = [];
10307
10675
  for (const item of scope.paths.length ? scope.paths : ["."]) {
10308
- const target = path6.resolve(root, item);
10676
+ const target = path8.resolve(root, item);
10309
10677
  if (!inside(root, target))
10310
10678
  return failure("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
10311
10679
  let ancestor = target;
10312
10680
  while (!fs6.existsSync(ancestor) && ancestor !== root)
10313
- ancestor = path6.dirname(ancestor);
10681
+ ancestor = path8.dirname(ancestor);
10314
10682
  try {
10315
10683
  if (!inside(root, fs6.realpathSync(ancestor)))
10316
10684
  return failure("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
@@ -10362,7 +10730,7 @@ function captureCandidate(root, scope, environment = []) {
10362
10730
  for (const item of [...names].sort()) {
10363
10731
  if (!scopeMatches(item, normalizedScope))
10364
10732
  continue;
10365
- const target = path6.join(checkout, item);
10733
+ const target = path8.join(checkout, item);
10366
10734
  try {
10367
10735
  const stat = fs6.lstatSync(target);
10368
10736
  if (stat.isSymbolicLink()) {
@@ -10905,7 +11273,8 @@ var exportDigest = (bundle) => sha256({
10905
11273
  // packages/workit-core/src/core/route-intent.ts
10906
11274
  var GUIDANCE = {
10907
11275
  "git.branch_setup": "use the Workit git.branch_setup action (workit_external_action in hosts, or `workit action git.branch_setup --payload '{}' --confirm --json` in the CLI)",
10908
- "hosting.pull_request": "use the Workit hosting.pull_request action (workit_external_action in hosts, or `workit action hosting.pull_request --payload '{...}' --confirm --json` in the CLI), then drive the created PR through workit-babysit"
11276
+ "hosting.pull_request": "use the Workit hosting.pull_request action (workit_external_action in hosts, or `workit action hosting.pull_request --payload '{...}' --confirm --json` in the CLI), then drive the created PR through workit-babysit",
11277
+ "hosting.merge": "use the Workit hosting.merge action (workit_external_action in hosts, or `workit action hosting.merge --payload '{...}' --confirm --json` in the CLI)"
10909
11278
  };
10910
11279
  var unbalancedQuotes = (segment) => {
10911
11280
  const text = segment.replace(/\\./gu, "");
@@ -10940,6 +11309,10 @@ function shellRouteIntent(command) {
10940
11309
  return { route: "hosting.pull_request", guidance: GUIDANCE["hosting.pull_request"] };
10941
11310
  if (head === "glab" && second === "mr" && third === "create")
10942
11311
  return { route: "hosting.pull_request", guidance: GUIDANCE["hosting.pull_request"] };
11312
+ if (head === "gh" && second === "pr" && third === "merge")
11313
+ return { route: "hosting.merge", guidance: GUIDANCE["hosting.merge"] };
11314
+ if (head === "glab" && second === "mr" && third === "merge")
11315
+ return { route: "hosting.merge", guidance: GUIDANCE["hosting.merge"] };
10943
11316
  }
10944
11317
  return null;
10945
11318
  }
@@ -10960,27 +11333,6 @@ function shouldDenyShellRoute(dir, command) {
10960
11333
  return null;
10961
11334
  return shellRouteIntent(command);
10962
11335
  }
10963
- // packages/workit-core/src/core/config.ts
10964
- var PRESETS = {
10965
- gitflow: {
10966
- allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
10967
- protected: ["main", "develop", "master", "prod", "production"]
10968
- },
10969
- "github-flow": { allowed: ["*"], protected: ["main"] },
10970
- "trunk-based": { allowed: ["*"], protected: ["main"] },
10971
- custom: { allowed: [], protected: [] }
10972
- };
10973
- var DEFAULTS = {
10974
- locale: "en",
10975
- localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
10976
- timezone: "America/Santiago",
10977
- branchPolicy: {
10978
- preset: "gitflow",
10979
- allowed: [...PRESETS.gitflow.allowed],
10980
- protected: [...PRESETS.gitflow.protected]
10981
- },
10982
- commitPolicy: { preset: "conventional" }
10983
- };
10984
11336
  // packages/workit-core/src/core/external-action.ts
10985
11337
  var externalActionSchema = discriminatedUnion("operation", [
10986
11338
  object({
@@ -11014,6 +11366,13 @@ var externalActionSchema = discriminatedUnion("operation", [
11014
11366
  babysit: boolean2().optional()
11015
11367
  }).strict()
11016
11368
  }).strict(),
11369
+ object({
11370
+ operation: literal("hosting.merge"),
11371
+ payload: object({
11372
+ target_branch: string2().min(1).optional(),
11373
+ source_branch: string2().min(1).optional()
11374
+ }).strict()
11375
+ }).strict(),
11017
11376
  object({
11018
11377
  operation: literal("youtrack.update"),
11019
11378
  payload: object({
@@ -11074,7 +11433,7 @@ var externalActionSchema = discriminatedUnion("operation", [
11074
11433
  }).strict()
11075
11434
  ]);
11076
11435
  // packages/workit-core/src/core/task-engine.ts
11077
- import { realpathSync as realpathSync4 } from "node:fs";
11436
+ import { realpathSync as realpathSync5 } from "node:fs";
11078
11437
 
11079
11438
  // packages/workit-core/src/core/policy-resolver.ts
11080
11439
  var resolverInputSchema = object({
@@ -11781,7 +12140,7 @@ class WorkitCore {
11781
12140
  }
11782
12141
  contextRootError() {
11783
12142
  try {
11784
- if (realpathSync4(this.context.root) !== this.store.root)
12143
+ if (realpathSync5(this.context.root) !== this.store.root)
11785
12144
  return failure("invalid_input", "operation context root does not match the task store root");
11786
12145
  } catch {
11787
12146
  return failure("invalid_input", "operation context root cannot be resolved");
@@ -12127,7 +12486,10 @@ class WorkitCore {
12127
12486
  observeDecision(request, observation) {
12128
12487
  return this.recordDecision(request, observation, true);
12129
12488
  }
12130
- recordDecision(request, nativeObservation, nativeRequired = false) {
12489
+ observeStandingDecision(request) {
12490
+ return this.recordDecision(request, undefined, true, true);
12491
+ }
12492
+ recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
12131
12493
  const root = this.contextRootError();
12132
12494
  if (!root.ok)
12133
12495
  return root;
@@ -12142,8 +12504,10 @@ class WorkitCore {
12142
12504
  return failure("permission_denied", "stated choices cannot authorize mutating actions");
12143
12505
  if (stated && !input.binding?.statedChoice)
12144
12506
  return failure("invalid_input", "stated choices require binding.statedChoice");
12145
- if (nativeRequired && nativeObservation === undefined && !stated)
12507
+ if (nativeRequired && nativeObservation === undefined && !stated && !standing)
12146
12508
  return failure("permission_denied", "native decision observation is required");
12509
+ if (standing && (!input.binding?.standing || input.purpose !== "action" || input.response !== "approved"))
12510
+ return failure("invalid_input", "standing approvals need an approved action binding with a standing rule");
12147
12511
  const task = this.store.readTask(input.taskId);
12148
12512
  if (!task.ok)
12149
12513
  return task;
@@ -12175,7 +12539,10 @@ class WorkitCore {
12175
12539
  consumption: null
12176
12540
  };
12177
12541
  const data = { ...base, digest: decisionDigest(base) };
12178
- const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
12542
+ const standingApproval = standing && !stated ? verifyStandingApproval(this.store.root, task.data, this.context.caller, input.binding) : null;
12543
+ if (standingApproval && !standingApproval.ok)
12544
+ return standingApproval;
12545
+ const native = nativeRequired && !stated && !standingApproval ? verifyNativeDecision(this.context.nativeAuthority, {
12179
12546
  observation: nativeObservation,
12180
12547
  expected: {
12181
12548
  taskId: task.data.id,
@@ -12206,7 +12573,7 @@ class WorkitCore {
12206
12573
  const entry = {
12207
12574
  id: newId(),
12208
12575
  recordedAt: mutation.now,
12209
- provenance: nativeProvenance ?? provenance(this.context, "agent_reported"),
12576
+ provenance: standingApproval?.ok === true ? standingApproval.data : nativeProvenance ?? provenance(this.context, "agent_reported"),
12210
12577
  data
12211
12578
  };
12212
12579
  return success(mutation.revision, null, {
@@ -13034,7 +13401,7 @@ class WorkitCore {
13034
13401
  if (view.task.workspaceId !== view.workspace.id)
13035
13402
  return failure("invalid_input", "task and workspace bindings are invalid");
13036
13403
  try {
13037
- if (realpathSync4(view.workspace.root) !== this.store.root)
13404
+ if (realpathSync5(view.workspace.root) !== this.store.root)
13038
13405
  return failure("invalid_input", "task view root does not match the task store");
13039
13406
  } catch {
13040
13407
  return failure("invalid_input", "task view root cannot be resolved");
@@ -13424,7 +13791,7 @@ var parseCodexHookInput = (value) => {
13424
13791
  return { ok: false, error: "permission_mode is invalid" };
13425
13792
  if (!(value.transcript_path === null || nonEmpty2(value.transcript_path)))
13426
13793
  return { ok: false, error: "transcript_path must be a string or null" };
13427
- if (!nonEmpty2(value.cwd) || !path7.isAbsolute(value.cwd) || !existsSync4(value.cwd))
13794
+ if (!nonEmpty2(value.cwd) || !path9.isAbsolute(value.cwd) || !existsSync5(value.cwd))
13428
13795
  return { ok: false, error: "cwd must be an existing absolute path" };
13429
13796
  try {
13430
13797
  if (!statSync(value.cwd).isDirectory())
@@ -13450,7 +13817,7 @@ var parseCodexHookInput = (value) => {
13450
13817
  model: value.model,
13451
13818
  permission_mode: value.permission_mode,
13452
13819
  transcript_path: value.transcript_path,
13453
- cwd: realpathSync5(value.cwd),
13820
+ cwd: realpathSync6(value.cwd),
13454
13821
  ...event === "SessionStart" ? { source: value.source } : {},
13455
13822
  ...nonEmpty2(value.turn_id) ? { turn_id: value.turn_id } : {},
13456
13823
  ...nonEmpty2(value.tool_name) ? { tool_name: value.tool_name } : {},