@brainervirus/workit-cursor 1.0.12 → 1.1.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.
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // packages/workit-cursor/hooks/workit-hook.ts
6
- import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
7
- import path7 from "node:path";
6
+ import { existsSync as existsSync5, realpathSync as realpathSync6 } 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()) {
@@ -10902,27 +11270,6 @@ var exportDigest = (bundle) => sha256({
10902
11270
  sourceWorkspaceId: bundle.sourceWorkspaceId,
10903
11271
  task: bundle.task
10904
11272
  });
10905
- // packages/workit-core/src/core/config.ts
10906
- var PRESETS = {
10907
- gitflow: {
10908
- allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
10909
- protected: ["main", "develop", "master", "prod", "production"]
10910
- },
10911
- "github-flow": { allowed: ["*"], protected: ["main"] },
10912
- "trunk-based": { allowed: ["*"], protected: ["main"] },
10913
- custom: { allowed: [], protected: [] }
10914
- };
10915
- var DEFAULTS = {
10916
- locale: "en",
10917
- localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
10918
- timezone: "America/Santiago",
10919
- branchPolicy: {
10920
- preset: "gitflow",
10921
- allowed: [...PRESETS.gitflow.allowed],
10922
- protected: [...PRESETS.gitflow.protected]
10923
- },
10924
- commitPolicy: { preset: "conventional" }
10925
- };
10926
11273
  // packages/workit-core/src/core/external-action.ts
10927
11274
  var externalActionSchema = discriminatedUnion("operation", [
10928
11275
  object({
@@ -10956,6 +11303,13 @@ var externalActionSchema = discriminatedUnion("operation", [
10956
11303
  babysit: boolean2().optional()
10957
11304
  }).strict()
10958
11305
  }).strict(),
11306
+ object({
11307
+ operation: literal("hosting.merge"),
11308
+ payload: object({
11309
+ target_branch: string2().min(1).optional(),
11310
+ source_branch: string2().min(1).optional()
11311
+ }).strict()
11312
+ }).strict(),
10959
11313
  object({
10960
11314
  operation: literal("youtrack.update"),
10961
11315
  payload: object({
@@ -11016,7 +11370,7 @@ var externalActionSchema = discriminatedUnion("operation", [
11016
11370
  }).strict()
11017
11371
  ]);
11018
11372
  // packages/workit-core/src/core/task-engine.ts
11019
- import { realpathSync as realpathSync4 } from "node:fs";
11373
+ import { realpathSync as realpathSync5 } from "node:fs";
11020
11374
 
11021
11375
  // packages/workit-core/src/core/policy-resolver.ts
11022
11376
  var resolverInputSchema = object({
@@ -11723,7 +12077,7 @@ class WorkitCore {
11723
12077
  }
11724
12078
  contextRootError() {
11725
12079
  try {
11726
- if (realpathSync4(this.context.root) !== this.store.root)
12080
+ if (realpathSync5(this.context.root) !== this.store.root)
11727
12081
  return failure("invalid_input", "operation context root does not match the task store root");
11728
12082
  } catch {
11729
12083
  return failure("invalid_input", "operation context root cannot be resolved");
@@ -12069,7 +12423,10 @@ class WorkitCore {
12069
12423
  observeDecision(request, observation) {
12070
12424
  return this.recordDecision(request, observation, true);
12071
12425
  }
12072
- recordDecision(request, nativeObservation, nativeRequired = false) {
12426
+ observeStandingDecision(request) {
12427
+ return this.recordDecision(request, undefined, true, true);
12428
+ }
12429
+ recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
12073
12430
  const root = this.contextRootError();
12074
12431
  if (!root.ok)
12075
12432
  return root;
@@ -12084,8 +12441,10 @@ class WorkitCore {
12084
12441
  return failure("permission_denied", "stated choices cannot authorize mutating actions");
12085
12442
  if (stated && !input.binding?.statedChoice)
12086
12443
  return failure("invalid_input", "stated choices require binding.statedChoice");
12087
- if (nativeRequired && nativeObservation === undefined && !stated)
12444
+ if (nativeRequired && nativeObservation === undefined && !stated && !standing)
12088
12445
  return failure("permission_denied", "native decision observation is required");
12446
+ if (standing && (!input.binding?.standing || input.purpose !== "action" || input.response !== "approved"))
12447
+ return failure("invalid_input", "standing approvals need an approved action binding with a standing rule");
12089
12448
  const task = this.store.readTask(input.taskId);
12090
12449
  if (!task.ok)
12091
12450
  return task;
@@ -12117,7 +12476,10 @@ class WorkitCore {
12117
12476
  consumption: null
12118
12477
  };
12119
12478
  const data = { ...base, digest: decisionDigest(base) };
12120
- const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
12479
+ const standingApproval = standing && !stated ? verifyStandingApproval(this.store.root, task.data, this.context.caller, input.binding) : null;
12480
+ if (standingApproval && !standingApproval.ok)
12481
+ return standingApproval;
12482
+ const native = nativeRequired && !stated && !standingApproval ? verifyNativeDecision(this.context.nativeAuthority, {
12121
12483
  observation: nativeObservation,
12122
12484
  expected: {
12123
12485
  taskId: task.data.id,
@@ -12148,7 +12510,7 @@ class WorkitCore {
12148
12510
  const entry = {
12149
12511
  id: newId(),
12150
12512
  recordedAt: mutation.now,
12151
- provenance: nativeProvenance ?? provenance(this.context, "agent_reported"),
12513
+ provenance: standingApproval?.ok === true ? standingApproval.data : nativeProvenance ?? provenance(this.context, "agent_reported"),
12152
12514
  data
12153
12515
  };
12154
12516
  return success(mutation.revision, null, {
@@ -12976,7 +13338,7 @@ class WorkitCore {
12976
13338
  if (view.task.workspaceId !== view.workspace.id)
12977
13339
  return failure("invalid_input", "task and workspace bindings are invalid");
12978
13340
  try {
12979
- if (realpathSync4(view.workspace.root) !== this.store.root)
13341
+ if (realpathSync5(view.workspace.root) !== this.store.root)
12980
13342
  return failure("invalid_input", "task view root does not match the task store");
12981
13343
  } catch {
12982
13344
  return failure("invalid_input", "task view root cannot be resolved");
@@ -13314,7 +13676,7 @@ var parseCursorHookInput = (value) => {
13314
13676
  if (!Array.isArray(roots) || roots.length !== 1 || !roots.every(nonEmpty2))
13315
13677
  return { ok: false, error: "exactly one workspace root is required" };
13316
13678
  const root = roots[0];
13317
- if (!path7.isAbsolute(root) || !existsSync4(root))
13679
+ if (!path9.isAbsolute(root) || !existsSync5(root))
13318
13680
  return { ok: false, error: "workspace root must be an existing absolute path" };
13319
13681
  const event = value.hook_event_name;
13320
13682
  const conversationId = nonEmpty2(value.conversation_id) ? value.conversation_id : undefined;
@@ -13458,7 +13820,7 @@ var handleCursorHook = (raw) => {
13458
13820
  return isRecord2(raw) && raw.hook_event_name === "sessionStart" ? {} : deny(parsed.error);
13459
13821
  }
13460
13822
  const input = parsed.data;
13461
- const root = realpathSync5(input.workspace_roots[0]);
13823
+ const root = realpathSync6(input.workspace_roots[0]);
13462
13824
  if (input.hook_event_name === "sessionStart") {
13463
13825
  const actor = input.session_id ?? input.conversation_id;
13464
13826
  let compact = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cursor",
3
- "version": "1.0.12",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "description": "Workit Cursor plugin — shared MCP transport, native hooks, and fourteen method skills",
6
6
  "keywords": [
@@ -44,8 +44,8 @@
44
44
  "build": "bun scripts/build.ts"
45
45
  },
46
46
  "dependencies": {
47
- "@brainervirus/workit-core": "^1.0.12",
48
- "@brainervirus/workit-mcp": "^1.0.12"
47
+ "@brainervirus/workit-core": "^1.1.0",
48
+ "@brainervirus/workit-mcp": "^1.1.0"
49
49
  },
50
50
  "engines": {
51
51
  "node": ">=24"