@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.
- package/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +421 -84
- package/dist/mcp-server.js +1701 -1517
- package/dist/workit-hook.js +416 -54
- package/package.json +3 -3
package/dist/workit-hook.js
CHANGED
|
@@ -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
|
|
7
|
-
import
|
|
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
|
|
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
|
|
9686
|
+
import path8 from "node:path";
|
|
9680
9687
|
|
|
9681
9688
|
// packages/workit-core/src/core/authority.ts
|
|
9682
|
-
import { createHash as
|
|
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) =>
|
|
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 =
|
|
9845
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
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 =
|
|
10206
|
+
const relative = path7.relative(root, target);
|
|
9853
10207
|
let current = root;
|
|
9854
|
-
for (const segment of relative.split(
|
|
10208
|
+
for (const segment of relative.split(path7.sep)) {
|
|
9855
10209
|
if (!segment)
|
|
9856
10210
|
continue;
|
|
9857
|
-
current =
|
|
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}${
|
|
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) =>
|
|
10213
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
10214
|
-
var relative = (root, candidate) =>
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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 (!
|
|
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 =
|
|
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
|
|
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
|
|
48
|
-
"@brainervirus/workit-mcp": "^1.0
|
|
47
|
+
"@brainervirus/workit-core": "^1.1.0",
|
|
48
|
+
"@brainervirus/workit-mcp": "^1.1.0"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=24"
|