@brainervirus/workit-cursor 8.10.0 → 8.12.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 +586 -290
- package/dist/mcp-server.js +71 -61
- package/dist/workit-hook.js +670 -357
- package/package.json +3 -3
- package/skills/workit-fanout/references/brief.md +23 -8
- package/skills/workit-fanout/references/orchestration.md +9 -4
package/dist/workit-hook.js
CHANGED
|
@@ -8792,6 +8792,7 @@ var branchNames = (workspaceRoot) => {
|
|
|
8792
8792
|
return new Set((r.stdout ?? "").split(`
|
|
8793
8793
|
`).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
|
|
8794
8794
|
};
|
|
8795
|
+
var productionBranchOf = (names) => names.has("main") ? "main" : names.has("master") ? "master" : "main";
|
|
8795
8796
|
var detectBranchPolicy = (workspaceRoot) => {
|
|
8796
8797
|
const names = branchNames(workspaceRoot);
|
|
8797
8798
|
const develop = names.has("develop");
|
|
@@ -8920,6 +8921,7 @@ var workspaceConfigSchema = object({
|
|
|
8920
8921
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
8921
8922
|
defaultEndpoint: unknown().optional(),
|
|
8922
8923
|
verification: _enum(["self", "independent"]).optional(),
|
|
8924
|
+
trailers: boolean2().optional(),
|
|
8923
8925
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
8924
8926
|
kind,
|
|
8925
8927
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -9201,8 +9203,8 @@ var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd,
|
|
|
9201
9203
|
var runtimeWorkspaceIndexSchema = object({
|
|
9202
9204
|
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
9203
9205
|
}).passthrough();
|
|
9204
|
-
var matchRuntimeWorkspace = (cwd) => {
|
|
9205
|
-
const file = path3.join(
|
|
9206
|
+
var matchRuntimeWorkspace = (cwd, dir = configDir()) => {
|
|
9207
|
+
const file = path3.join(dir, "workspaces.json");
|
|
9206
9208
|
let raw;
|
|
9207
9209
|
try {
|
|
9208
9210
|
raw = readFileSync2(file, "utf8");
|
|
@@ -9231,12 +9233,30 @@ var matchRuntimeWorkspace = (cwd) => {
|
|
|
9231
9233
|
const matches = matchingEntries(cwd, entries ?? []);
|
|
9232
9234
|
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
9233
9235
|
const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
|
|
9234
|
-
return selected ? { file, selected } : null;
|
|
9236
|
+
return selected ? { file, selected, matches } : null;
|
|
9235
9237
|
};
|
|
9236
9238
|
var resolveRuntimeReleaseTracks = (cwd) => {
|
|
9237
9239
|
const matched = matchRuntimeWorkspace(cwd);
|
|
9238
9240
|
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
9239
9241
|
};
|
|
9242
|
+
var workspaceTrailers = (cwd, dir) => {
|
|
9243
|
+
let matched;
|
|
9244
|
+
try {
|
|
9245
|
+
matched = matchRuntimeWorkspace(cwd, dir);
|
|
9246
|
+
} catch {
|
|
9247
|
+
return { enabled: true, workspace: null, from: null };
|
|
9248
|
+
}
|
|
9249
|
+
if (!matched)
|
|
9250
|
+
return { enabled: true, workspace: null, from: null };
|
|
9251
|
+
const { selected, matches } = matched;
|
|
9252
|
+
const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
9253
|
+
const source = [selected, ...enclosing].find((entry) => typeof entry.trailers === "boolean");
|
|
9254
|
+
return {
|
|
9255
|
+
enabled: source?.trailers !== false,
|
|
9256
|
+
workspace: selected.name,
|
|
9257
|
+
from: source?.name ?? null
|
|
9258
|
+
};
|
|
9259
|
+
};
|
|
9240
9260
|
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
9241
9261
|
const matched = matchRuntimeWorkspace(cwd);
|
|
9242
9262
|
if (!matched)
|
|
@@ -14837,7 +14857,7 @@ function loadCheckConfig(cwd) {
|
|
|
14837
14857
|
gates[gate] = names;
|
|
14838
14858
|
}
|
|
14839
14859
|
}
|
|
14840
|
-
return { source: CHECKS_FILE, root, checks, gates, error: null };
|
|
14860
|
+
return { source: CHECKS_FILE, root, checks, gates, error: null, resources: parsed.resources };
|
|
14841
14861
|
}
|
|
14842
14862
|
var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
|
|
14843
14863
|
function matchesNamedCheck(config, name, argv, cwd) {
|
|
@@ -15604,9 +15624,9 @@ Autonomy contract:
|
|
|
15604
15624
|
ship), go to the effective endpoint in \`workit grant show\`: \`pr\` opens
|
|
15605
15625
|
the PR; \`green\` babysits it without asking (workit-ship) to merge-ready,
|
|
15606
15626
|
never merging; \`merged\` also lands it with \`workit pr merge\`.
|
|
15607
|
-
Merge and release need a workspace grant
|
|
15608
|
-
|
|
15609
|
-
|
|
15627
|
+
Merge and release need a workspace grant only the user raises from their
|
|
15628
|
+
own terminal (a blocked verb names how); never edit workspaces.json or route
|
|
15629
|
+
around a grant_required.
|
|
15610
15630
|
- Ask only for a product or preference choice, or for authority you lack, with
|
|
15611
15631
|
your recommended answer. Facts are yours: read, run or prototype.
|
|
15612
15632
|
- Label claims measured (run and seen this session), inferred, or guess.
|
|
@@ -15615,17 +15635,19 @@ Autonomy contract:
|
|
|
15615
15635
|
- Author is not verifier: your own verdict is \`--self\`, shown as
|
|
15616
15636
|
self-reviewed, never verified. High risk, \`verification: independent\` and
|
|
15617
15637
|
merge need a verifier with its own host-derived session (a subagent's
|
|
15618
|
-
\`<lead>:<child
|
|
15638
|
+
\`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
|
|
15619
15639
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
15620
15640
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
15621
15641
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
15622
15642
|
|
|
15623
15643
|
CLI first: if a step has one right answer, use the \`workit\` verb instead of
|
|
15624
15644
|
hand-running git, gh or glab (\`workit git|pr|ci|check\`; \`workit help\` lists
|
|
15625
|
-
the rest;
|
|
15645
|
+
the rest; lacking \`workit\`, \`npx -y @brainervirus/workit-cli\`).
|
|
15646
|
+
Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
|
|
15647
|
+
an amend.
|
|
15626
15648
|
The task families (task, policy, evidence, finding, decision, worker, state)
|
|
15627
15649
|
are optional continuity for tracked work; each branch has one implicit task,
|
|
15628
|
-
|
|
15650
|
+
and a solo edit needs no task. A decision
|
|
15629
15651
|
records a user choice; it never authorizes an effect. Judge work once with
|
|
15630
15652
|
\`workit policy assess --judge risk= behavior= product-choice= plan=\`.
|
|
15631
15653
|
Across repositories, bind each item to its checkout, branch and endpoint,
|
|
@@ -17666,6 +17688,18 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
17666
17688
|
else
|
|
17667
17689
|
clearTurnDigest(file);
|
|
17668
17690
|
};
|
|
17691
|
+
// packages/workit-core/src/core/route-intent.ts
|
|
17692
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
17693
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
17694
|
+
import path20 from "node:path";
|
|
17695
|
+
|
|
17696
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
17697
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
17698
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
17699
|
+
import fs16 from "node:fs";
|
|
17700
|
+
import os4 from "node:os";
|
|
17701
|
+
import path19 from "node:path";
|
|
17702
|
+
|
|
17669
17703
|
// packages/workit-core/src/core/branch.ts
|
|
17670
17704
|
import { createHash as createHash12 } from "node:crypto";
|
|
17671
17705
|
var withReleaseTracks = (workspaceRoot, policy) => {
|
|
@@ -17703,10 +17737,72 @@ function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
|
17703
17737
|
provenance: { commitPolicy: selected.source }
|
|
17704
17738
|
};
|
|
17705
17739
|
}
|
|
17740
|
+
var KIND_SYNONYMS = {
|
|
17741
|
+
fix: "bugfix",
|
|
17742
|
+
fixes: "bugfix",
|
|
17743
|
+
bug: "bugfix",
|
|
17744
|
+
bugs: "bugfix",
|
|
17745
|
+
bugfixes: "bugfix",
|
|
17746
|
+
feat: "feature",
|
|
17747
|
+
features: "feature",
|
|
17748
|
+
chore: "feature",
|
|
17749
|
+
docs: "feature",
|
|
17750
|
+
doc: "feature",
|
|
17751
|
+
refactor: "feature",
|
|
17752
|
+
test: "feature",
|
|
17753
|
+
tests: "feature",
|
|
17754
|
+
ci: "feature",
|
|
17755
|
+
build: "feature",
|
|
17756
|
+
perf: "feature",
|
|
17757
|
+
style: "feature",
|
|
17758
|
+
deps: "feature",
|
|
17759
|
+
task: "feature",
|
|
17760
|
+
enhancement: "feature",
|
|
17761
|
+
hot: "hotfix",
|
|
17762
|
+
hotfixes: "hotfix",
|
|
17763
|
+
releases: "release",
|
|
17764
|
+
rel: "release"
|
|
17765
|
+
};
|
|
17766
|
+
var allowedKinds = (allowed) => [
|
|
17767
|
+
...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
|
|
17768
|
+
];
|
|
17769
|
+
var editDistance = (a, b) => {
|
|
17770
|
+
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
17771
|
+
for (let i = 1;i <= a.length; i++) {
|
|
17772
|
+
let previous = row[0];
|
|
17773
|
+
row[0] = i;
|
|
17774
|
+
for (let j = 1;j <= b.length; j++) {
|
|
17775
|
+
const saved = row[j];
|
|
17776
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
17777
|
+
previous = saved;
|
|
17778
|
+
}
|
|
17779
|
+
}
|
|
17780
|
+
return row[b.length];
|
|
17781
|
+
};
|
|
17782
|
+
var suggestBranchName = (name, kinds) => {
|
|
17783
|
+
if (!kinds.length)
|
|
17784
|
+
return null;
|
|
17785
|
+
const slash = name.indexOf("/");
|
|
17786
|
+
const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
|
|
17787
|
+
const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
|
|
17788
|
+
const synonym = KIND_SYNONYMS[prefix];
|
|
17789
|
+
const near = kinds.map((kind) => ({ kind, distance: editDistance(prefix, kind) })).filter((entry) => prefix && entry.distance <= 2).toSorted((a, b) => a.distance - b.distance)[0]?.kind;
|
|
17790
|
+
const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
|
|
17791
|
+
return `${kind}/${rest}`;
|
|
17792
|
+
};
|
|
17793
|
+
var kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", ");
|
|
17794
|
+
var aliasNote = (kinds) => {
|
|
17795
|
+
const notes = [
|
|
17796
|
+
["fix", "bugfix"],
|
|
17797
|
+
["chore", "feature"]
|
|
17798
|
+
].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
|
|
17799
|
+
return notes.length ? ` (${notes.join(", ")})` : "";
|
|
17800
|
+
};
|
|
17706
17801
|
var validateBranchNamePolicy = (name, policy, source) => {
|
|
17707
17802
|
const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
|
|
17803
|
+
const kinds = allowedKinds(policy.allowed);
|
|
17708
17804
|
if (policy.protected.has(name.toLowerCase())) {
|
|
17709
|
-
const correction = `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
|
|
17805
|
+
const correction = kinds.length ? `choose a non-protected branch of an allowed kind (${kindList(kinds)}), e.g. ${kinds[0]}/<slug>: workit git branch --kind ${kinds[0]} --slug <slug>` : `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
|
|
17710
17806
|
return {
|
|
17711
17807
|
ok: false,
|
|
17712
17808
|
rule: "protected_ref",
|
|
@@ -17717,7 +17813,8 @@ var validateBranchNamePolicy = (name, policy, source) => {
|
|
|
17717
17813
|
};
|
|
17718
17814
|
}
|
|
17719
17815
|
if (!policy.allowed.some((pattern) => pattern.test(name))) {
|
|
17720
|
-
const
|
|
17816
|
+
const suggestion = suggestBranchName(name, kinds);
|
|
17817
|
+
const correction = suggestion && policy.allowed.some((pattern) => pattern.test(suggestion)) ? `use ${JSON.stringify(suggestion)}; the allowed kinds are ${kindList(kinds)} (workit git branch ${suggestion})` : `choose a branch matching one of: ${allowed.join(", ")}`;
|
|
17721
17818
|
return {
|
|
17722
17819
|
ok: false,
|
|
17723
17820
|
rule: "allowed_pattern",
|
|
@@ -17760,31 +17857,87 @@ var validateBranchNameFor = (workspaceRoot, name) => {
|
|
|
17760
17857
|
}
|
|
17761
17858
|
};
|
|
17762
17859
|
var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
|
|
17763
|
-
|
|
17764
|
-
|
|
17765
|
-
|
|
17766
|
-
|
|
17767
|
-
|
|
17768
|
-
|
|
17769
|
-
|
|
17860
|
+
var gitFlowOf = (root, integration) => {
|
|
17861
|
+
try {
|
|
17862
|
+
if (resolveBranchPolicyFor(root).preset !== "gitflow")
|
|
17863
|
+
return null;
|
|
17864
|
+
const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
17865
|
+
const names = branchNames(root);
|
|
17866
|
+
const production = productionBranchOf(names);
|
|
17867
|
+
const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
|
|
17868
|
+
return { develop, production, developExists: names.has(develop) };
|
|
17869
|
+
} catch {
|
|
17770
17870
|
return null;
|
|
17771
|
-
|
|
17772
|
-
const target = args[1] === "switch" && ["-c", "--create", "-C", "--force-create"].includes(option ?? "") && (args.length === 4 || args.length === 5) ? args[3] : args[1] === "checkout" && ["-b", "-B"].includes(option ?? "") && (args.length === 4 || args.length === 5) ? args[3] : args[1] === "branch" && (args.length === 3 || args.length === 4) ? args[2] : null;
|
|
17773
|
-
return target && !target.startsWith("-") ? target : null;
|
|
17871
|
+
}
|
|
17774
17872
|
};
|
|
17775
|
-
var
|
|
17776
|
-
|
|
17777
|
-
|
|
17873
|
+
var releaseBranchesOf = (cwd) => {
|
|
17874
|
+
try {
|
|
17875
|
+
const resolved = resolveBranchPolicyFor(cwd);
|
|
17876
|
+
const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
|
|
17877
|
+
return {
|
|
17878
|
+
preset: resolved.preset,
|
|
17879
|
+
branches: flow ? [flow.production, flow.develop] : [],
|
|
17880
|
+
production: flow ? flow.production : resolved.defaultTargetBranch
|
|
17881
|
+
};
|
|
17882
|
+
} catch {
|
|
17883
|
+
return { preset: "unknown", branches: [], production: null };
|
|
17884
|
+
}
|
|
17778
17885
|
};
|
|
17779
|
-
|
|
17780
|
-
|
|
17781
|
-
|
|
17782
|
-
|
|
17783
|
-
|
|
17886
|
+
var releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase());
|
|
17887
|
+
var branchGuidance = (cwd) => {
|
|
17888
|
+
try {
|
|
17889
|
+
const patterns = branchPolicySnapshotFor(cwd).namePolicy.allowed;
|
|
17890
|
+
const kinds = allowedKinds(patterns);
|
|
17891
|
+
const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
|
|
17892
|
+
const anyName = patterns.some((pattern) => pattern.source === "^.*$");
|
|
17893
|
+
const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
|
|
17894
|
+
const allowed = anyName ? "Any non-protected branch name is allowed here." : `${kinds.length ? `Allowed branch kinds here: ${kindList(kinds)}${aliasNote(kinds)}` : "Allowed branch names here:"}${others.length ? `${kinds.length ? "; also" : ""} names matching ${others.join(", ")}` : ""}; anything else is denied.`;
|
|
17895
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
17896
|
+
const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
|
|
17897
|
+
const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
|
|
17898
|
+
if (flow?.developExists)
|
|
17899
|
+
return `${allowed} git-flow: feature/, bugfix/ and release/ start from origin/${flow.develop}, hotfix/ from origin/${flow.production}; feature/ and bugfix/ land on ${flow.develop}, release/ and hotfix/ on ${flow.production}.`;
|
|
17900
|
+
const names = branchNames(cwd);
|
|
17901
|
+
const configured = String(resolved.baseBranch ?? integration ?? "") || null;
|
|
17902
|
+
const trunk = productionBranchOf(names);
|
|
17903
|
+
const base = configured && names.has(configured) ? `Branch from origin/${configured}.` : names.has(trunk) ? `Branch from origin/${trunk}${configured ? ` (the default target ${configured} does not exist here)` : ""}.` : "No base branch exists here yet; pass the base explicitly.";
|
|
17904
|
+
return `${allowed} ${base}`;
|
|
17905
|
+
} catch {
|
|
17906
|
+
return null;
|
|
17907
|
+
}
|
|
17784
17908
|
};
|
|
17785
|
-
|
|
17786
|
-
|
|
17909
|
+
var globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*", "*").replace(/\\(.)/gu, "$1");
|
|
17910
|
+
|
|
17911
|
+
// packages/workit-core/src/hooks/session-marker.ts
|
|
17912
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
17913
|
+
import fs15 from "node:fs";
|
|
17787
17914
|
import path18 from "node:path";
|
|
17915
|
+
var sessionMarkerFile = (input, session, store) => {
|
|
17916
|
+
let dir = store;
|
|
17917
|
+
if (dir === undefined) {
|
|
17918
|
+
const location = resolveStore(input.cwd);
|
|
17919
|
+
if (location instanceof Error || !location.shared)
|
|
17920
|
+
return null;
|
|
17921
|
+
dir = location.dir;
|
|
17922
|
+
}
|
|
17923
|
+
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
17924
|
+
return path18.join(dir, "hooks", `skills-${name}.json`);
|
|
17925
|
+
};
|
|
17926
|
+
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
17927
|
+
var readSessionMarker = (file) => {
|
|
17928
|
+
try {
|
|
17929
|
+
const value = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
17930
|
+
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
17931
|
+
} catch {
|
|
17932
|
+
return { nudged: [], loaded: [], raw: [] };
|
|
17933
|
+
}
|
|
17934
|
+
};
|
|
17935
|
+
var writeSessionMarker = (file, marker) => {
|
|
17936
|
+
try {
|
|
17937
|
+
fs15.mkdirSync(path18.dirname(file), { recursive: true });
|
|
17938
|
+
fs15.writeFileSync(file, JSON.stringify(marker));
|
|
17939
|
+
} catch {}
|
|
17940
|
+
};
|
|
17788
17941
|
|
|
17789
17942
|
// packages/workit-core/src/hooks/shell-words.ts
|
|
17790
17943
|
var closingQuote = (command, start, dialect) => {
|
|
@@ -17909,301 +18062,87 @@ var segmentsOf = (command, dialect = "posix") => {
|
|
|
17909
18062
|
return segments;
|
|
17910
18063
|
};
|
|
17911
18064
|
|
|
17912
|
-
// packages/workit-core/src/hooks/
|
|
18065
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
17913
18066
|
var NONE2 = { kind: "none" };
|
|
17914
|
-
var
|
|
17915
|
-
var
|
|
17916
|
-
|
|
17917
|
-
|
|
17918
|
-
|
|
17919
|
-
|
|
17920
|
-
|
|
17921
|
-
|
|
17922
|
-
|
|
17923
|
-
|
|
17924
|
-
|
|
17925
|
-
|
|
18067
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
18068
|
+
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
18069
|
+
var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
|
|
18070
|
+
var CD = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
18071
|
+
var PUSHD = new Set(["pushd", "push-location"]);
|
|
18072
|
+
var POPD = new Set(["popd", "pop-location"]);
|
|
18073
|
+
var GIT_VALUE_OPTIONS = new Set([
|
|
18074
|
+
"-c",
|
|
18075
|
+
"--git-dir",
|
|
18076
|
+
"--work-tree",
|
|
18077
|
+
"--namespace",
|
|
18078
|
+
"--exec-path",
|
|
18079
|
+
"--config-env",
|
|
18080
|
+
"--super-prefix"
|
|
18081
|
+
]);
|
|
18082
|
+
var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
18083
|
+
var joinDir = (base, next) => {
|
|
18084
|
+
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
18085
|
+
return;
|
|
18086
|
+
const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
|
|
18087
|
+
return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
|
|
17926
18088
|
};
|
|
17927
|
-
var
|
|
17928
|
-
const
|
|
17929
|
-
const
|
|
17930
|
-
if (
|
|
17931
|
-
return
|
|
17932
|
-
|
|
17933
|
-
const segments = posix.split("/");
|
|
17934
|
-
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
18089
|
+
var cdTarget = (words) => {
|
|
18090
|
+
const args = words.slice(1);
|
|
18091
|
+
const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
|
|
18092
|
+
if (flag >= 0)
|
|
18093
|
+
return args[flag + 1] ?? null;
|
|
18094
|
+
return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
|
|
17935
18095
|
};
|
|
17936
|
-
var
|
|
17937
|
-
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
17938
|
-
var editsOf = (input) => {
|
|
18096
|
+
var stripPrefixes = (input) => {
|
|
17939
18097
|
let words = input;
|
|
17940
|
-
|
|
17941
|
-
|
|
17942
|
-
|
|
17943
|
-
|
|
17944
|
-
|
|
17945
|
-
|
|
17946
|
-
|
|
17947
|
-
|
|
17948
|
-
|
|
17949
|
-
|
|
17950
|
-
|
|
17951
|
-
|
|
17952
|
-
|
|
17953
|
-
|
|
17954
|
-
|
|
17955
|
-
|
|
17956
|
-
|
|
17957
|
-
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
|
|
17961
|
-
|
|
17962
|
-
|
|
18098
|
+
for (;; ) {
|
|
18099
|
+
const first = words[0];
|
|
18100
|
+
if (first === undefined)
|
|
18101
|
+
return words;
|
|
18102
|
+
if (/^\w+=/.test(first))
|
|
18103
|
+
words = words.slice(1);
|
|
18104
|
+
else if (PREFIXES2.has(first)) {
|
|
18105
|
+
words = words.slice(1);
|
|
18106
|
+
while (words[0]?.startsWith("-"))
|
|
18107
|
+
words = words.slice(words[0] === "-u" ? 2 : 1);
|
|
18108
|
+
} else if (first === "timeout") {
|
|
18109
|
+
words = words.slice(1);
|
|
18110
|
+
while (words[0]?.startsWith("-"))
|
|
18111
|
+
words = words.slice(1);
|
|
18112
|
+
words = words.slice(1);
|
|
18113
|
+
} else
|
|
18114
|
+
return words;
|
|
18115
|
+
}
|
|
18116
|
+
};
|
|
18117
|
+
var commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, "");
|
|
18118
|
+
function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
18119
|
+
const found = [];
|
|
18120
|
+
let dir = null;
|
|
18121
|
+
const groups = [];
|
|
18122
|
+
let stack = [];
|
|
18123
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
18124
|
+
if (segment.group === "open") {
|
|
18125
|
+
groups.push({ dir, stack: [...stack] });
|
|
18126
|
+
continue;
|
|
17963
18127
|
}
|
|
17964
|
-
|
|
17965
|
-
|
|
17966
|
-
|
|
17967
|
-
|
|
17968
|
-
|
|
17969
|
-
return [];
|
|
17970
|
-
const scripted = args.some((item) => item === "-e" || item === "-f");
|
|
17971
|
-
const files = [];
|
|
17972
|
-
for (let index = 0;index < args.length; index++) {
|
|
17973
|
-
if (args[index] === "-e" || args[index] === "-f")
|
|
17974
|
-
index++;
|
|
17975
|
-
else if (!args[index].startsWith("-"))
|
|
17976
|
-
files.push(args[index]);
|
|
17977
|
-
}
|
|
17978
|
-
return scripted ? files : files.slice(1);
|
|
18128
|
+
if (segment.group === "close") {
|
|
18129
|
+
const saved = groups.pop();
|
|
18130
|
+
if (saved)
|
|
18131
|
+
({ dir, stack } = saved);
|
|
18132
|
+
continue;
|
|
17979
18133
|
}
|
|
17980
|
-
|
|
17981
|
-
|
|
17982
|
-
|
|
17983
|
-
|
|
17984
|
-
|
|
17985
|
-
|
|
17986
|
-
case "ni":
|
|
17987
|
-
case "remove-item":
|
|
17988
|
-
case "del":
|
|
17989
|
-
case "copy-item":
|
|
17990
|
-
case "move-item": {
|
|
17991
|
-
const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
|
|
17992
|
-
if (flag >= 0)
|
|
17993
|
-
return args[flag + 1] ? [args[flag + 1]] : null;
|
|
17994
|
-
const files = operands(args);
|
|
17995
|
-
if (name === "move-item")
|
|
17996
|
-
return files.length ? files : null;
|
|
17997
|
-
const file = name === "copy-item" ? files.at(-1) : files[0];
|
|
17998
|
-
return file ? [file] : null;
|
|
18134
|
+
const words = stripPrefixes(segment.words);
|
|
18135
|
+
const name = commandName(words[0]);
|
|
18136
|
+
if (CD.has(name)) {
|
|
18137
|
+
const target = cdTarget(words);
|
|
18138
|
+
dir = target === null ? os4.homedir() : joinDir(dir, target);
|
|
18139
|
+
continue;
|
|
17999
18140
|
}
|
|
18000
|
-
|
|
18001
|
-
const
|
|
18002
|
-
|
|
18003
|
-
|
|
18004
|
-
|
|
18005
|
-
return operands(args).slice(1);
|
|
18006
|
-
if (sub === "checkout" && args.includes("--"))
|
|
18007
|
-
return args.slice(args.indexOf("--") + 1);
|
|
18008
|
-
return [];
|
|
18009
|
-
}
|
|
18010
|
-
default:
|
|
18011
|
-
return [];
|
|
18012
|
-
}
|
|
18013
|
-
};
|
|
18014
|
-
var shellWrites = (command) => {
|
|
18015
|
-
const targets = [];
|
|
18016
|
-
let unknown = false;
|
|
18017
|
-
for (const segment of segmentsOf(command)) {
|
|
18018
|
-
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
18019
|
-
const edits = editsOf(segment.words);
|
|
18020
|
-
if (edits === null)
|
|
18021
|
-
unknown = true;
|
|
18022
|
-
else
|
|
18023
|
-
targets.push(...edits);
|
|
18024
|
-
}
|
|
18025
|
-
if (!unknown && targets.length === 0)
|
|
18026
|
-
return { writes: false, targets: null };
|
|
18027
|
-
return { writes: true, targets: unknown ? null : targets };
|
|
18028
|
-
};
|
|
18029
|
-
var cache2 = new Map;
|
|
18030
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path18.resolve(root, file)) ?? "-").join("\x00");
|
|
18031
|
-
var gateFor = (cwd) => {
|
|
18032
|
-
const store = new TaskStore(cwd);
|
|
18033
|
-
const listed = store.listTaskIndex();
|
|
18034
|
-
if (!listed.ok)
|
|
18035
|
-
return null;
|
|
18036
|
-
const key = store.currentKey();
|
|
18037
|
-
if (!key.ok)
|
|
18038
|
-
return null;
|
|
18039
|
-
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
18040
|
-
if (!entry)
|
|
18041
|
-
return { blockers: [], root: store.root, plans: new Set };
|
|
18042
|
-
const ledger = ledgerPath(store.root);
|
|
18043
|
-
const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
|
|
18044
|
-
const hit = cache2.get(store.root);
|
|
18045
|
-
if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
|
|
18046
|
-
return hit;
|
|
18047
|
-
const task = store.readTask(entry.id);
|
|
18048
|
-
if (!task.ok)
|
|
18049
|
-
return null;
|
|
18050
|
-
const workspace = store.readWorkspace();
|
|
18051
|
-
const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
|
|
18052
|
-
const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
|
|
18053
|
-
const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
|
|
18054
|
-
return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
|
|
18055
|
-
});
|
|
18056
|
-
const gate = {
|
|
18057
|
-
blockers,
|
|
18058
|
-
root: store.root,
|
|
18059
|
-
plans,
|
|
18060
|
-
key: cacheKey,
|
|
18061
|
-
planKey: planKeyOf(store.root, plans)
|
|
18062
|
-
};
|
|
18063
|
-
cache2.set(store.root, gate);
|
|
18064
|
-
if (cache2.size > 32)
|
|
18065
|
-
cache2.delete(cache2.keys().next().value);
|
|
18066
|
-
return gate;
|
|
18067
|
-
};
|
|
18068
|
-
function writeGate(cwd, paths) {
|
|
18069
|
-
try {
|
|
18070
|
-
const gate = gateFor(cwd);
|
|
18071
|
-
if (!gate || gate.blockers.length === 0)
|
|
18072
|
-
return NONE2;
|
|
18073
|
-
if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
|
|
18074
|
-
return NONE2;
|
|
18075
|
-
const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
|
|
18076
|
-
return {
|
|
18077
|
-
kind: "deny",
|
|
18078
|
-
reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
|
|
18079
|
-
unblock: gate.blockers[0].reason
|
|
18080
|
-
};
|
|
18081
|
-
} catch {
|
|
18082
|
-
return NONE2;
|
|
18083
|
-
}
|
|
18084
|
-
}
|
|
18085
|
-
// packages/workit-core/src/hooks/stop.ts
|
|
18086
|
-
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
18087
|
-
|
|
18088
|
-
// packages/workit-core/src/hooks/raw-git.ts
|
|
18089
|
-
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
18090
|
-
import { createHash as createHash14 } from "node:crypto";
|
|
18091
|
-
import fs16 from "node:fs";
|
|
18092
|
-
import os4 from "node:os";
|
|
18093
|
-
import path20 from "node:path";
|
|
18094
|
-
|
|
18095
|
-
// packages/workit-core/src/hooks/session-marker.ts
|
|
18096
|
-
import { createHash as createHash13 } from "node:crypto";
|
|
18097
|
-
import fs15 from "node:fs";
|
|
18098
|
-
import path19 from "node:path";
|
|
18099
|
-
var sessionMarkerFile = (input, session, store) => {
|
|
18100
|
-
let dir = store;
|
|
18101
|
-
if (dir === undefined) {
|
|
18102
|
-
const location = resolveStore(input.cwd);
|
|
18103
|
-
if (location instanceof Error || !location.shared)
|
|
18104
|
-
return null;
|
|
18105
|
-
dir = location.dir;
|
|
18106
|
-
}
|
|
18107
|
-
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
18108
|
-
return path19.join(dir, "hooks", `skills-${name}.json`);
|
|
18109
|
-
};
|
|
18110
|
-
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
18111
|
-
var readSessionMarker = (file) => {
|
|
18112
|
-
try {
|
|
18113
|
-
const value = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
18114
|
-
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
18115
|
-
} catch {
|
|
18116
|
-
return { nudged: [], loaded: [], raw: [] };
|
|
18117
|
-
}
|
|
18118
|
-
};
|
|
18119
|
-
var writeSessionMarker = (file, marker) => {
|
|
18120
|
-
try {
|
|
18121
|
-
fs15.mkdirSync(path19.dirname(file), { recursive: true });
|
|
18122
|
-
fs15.writeFileSync(file, JSON.stringify(marker));
|
|
18123
|
-
} catch {}
|
|
18124
|
-
};
|
|
18125
|
-
|
|
18126
|
-
// packages/workit-core/src/hooks/raw-git.ts
|
|
18127
|
-
var NONE3 = { kind: "none" };
|
|
18128
|
-
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
18129
|
-
var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
18130
|
-
var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
|
|
18131
|
-
var CD = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
18132
|
-
var PUSHD = new Set(["pushd", "push-location"]);
|
|
18133
|
-
var POPD = new Set(["popd", "pop-location"]);
|
|
18134
|
-
var GIT_VALUE_OPTIONS = new Set([
|
|
18135
|
-
"-c",
|
|
18136
|
-
"--git-dir",
|
|
18137
|
-
"--work-tree",
|
|
18138
|
-
"--namespace",
|
|
18139
|
-
"--exec-path",
|
|
18140
|
-
"--config-env",
|
|
18141
|
-
"--super-prefix"
|
|
18142
|
-
]);
|
|
18143
|
-
var isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
18144
|
-
var joinDir = (base, next) => {
|
|
18145
|
-
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
18146
|
-
return;
|
|
18147
|
-
const expanded = next === "~" || next.startsWith("~/") ? path20.join(os4.homedir(), next.slice(1)) : next;
|
|
18148
|
-
return base === null || isAbsolute(expanded) ? expanded : path20.join(base, expanded);
|
|
18149
|
-
};
|
|
18150
|
-
var cdTarget = (words) => {
|
|
18151
|
-
const args = words.slice(1);
|
|
18152
|
-
const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
|
|
18153
|
-
if (flag >= 0)
|
|
18154
|
-
return args[flag + 1] ?? null;
|
|
18155
|
-
return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
|
|
18156
|
-
};
|
|
18157
|
-
var stripPrefixes = (input) => {
|
|
18158
|
-
let words = input;
|
|
18159
|
-
for (;; ) {
|
|
18160
|
-
const first = words[0];
|
|
18161
|
-
if (first === undefined)
|
|
18162
|
-
return words;
|
|
18163
|
-
if (/^\w+=/.test(first))
|
|
18164
|
-
words = words.slice(1);
|
|
18165
|
-
else if (PREFIXES3.has(first)) {
|
|
18166
|
-
words = words.slice(1);
|
|
18167
|
-
while (words[0]?.startsWith("-"))
|
|
18168
|
-
words = words.slice(words[0] === "-u" ? 2 : 1);
|
|
18169
|
-
} else if (first === "timeout") {
|
|
18170
|
-
words = words.slice(1);
|
|
18171
|
-
while (words[0]?.startsWith("-"))
|
|
18172
|
-
words = words.slice(1);
|
|
18173
|
-
words = words.slice(1);
|
|
18174
|
-
} else
|
|
18175
|
-
return words;
|
|
18176
|
-
}
|
|
18177
|
-
};
|
|
18178
|
-
var commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, "");
|
|
18179
|
-
function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
18180
|
-
const found = [];
|
|
18181
|
-
let dir = null;
|
|
18182
|
-
const groups = [];
|
|
18183
|
-
let stack = [];
|
|
18184
|
-
for (const segment of segmentsOf(command, dialect)) {
|
|
18185
|
-
if (segment.group === "open") {
|
|
18186
|
-
groups.push({ dir, stack: [...stack] });
|
|
18187
|
-
continue;
|
|
18188
|
-
}
|
|
18189
|
-
if (segment.group === "close") {
|
|
18190
|
-
const saved = groups.pop();
|
|
18191
|
-
if (saved)
|
|
18192
|
-
({ dir, stack } = saved);
|
|
18193
|
-
continue;
|
|
18194
|
-
}
|
|
18195
|
-
const words = stripPrefixes(segment.words);
|
|
18196
|
-
const name = commandName(words[0]);
|
|
18197
|
-
if (CD.has(name)) {
|
|
18198
|
-
const target = cdTarget(words);
|
|
18199
|
-
dir = target === null ? os4.homedir() : joinDir(dir, target);
|
|
18200
|
-
continue;
|
|
18201
|
-
}
|
|
18202
|
-
if (PUSHD.has(name)) {
|
|
18203
|
-
const target = cdTarget(words);
|
|
18204
|
-
stack.push(dir);
|
|
18205
|
-
dir = target === null || target === "-" ? undefined : joinDir(dir, target);
|
|
18206
|
-
continue;
|
|
18141
|
+
if (PUSHD.has(name)) {
|
|
18142
|
+
const target = cdTarget(words);
|
|
18143
|
+
stack.push(dir);
|
|
18144
|
+
dir = target === null || target === "-" ? undefined : joinDir(dir, target);
|
|
18145
|
+
continue;
|
|
18207
18146
|
}
|
|
18208
18147
|
if (POPD.has(name)) {
|
|
18209
18148
|
dir = stack.length ? stack.pop() : undefined;
|
|
@@ -18277,6 +18216,10 @@ var pushAction = (args) => {
|
|
|
18277
18216
|
let force = false;
|
|
18278
18217
|
let lease = false;
|
|
18279
18218
|
let wholesale = false;
|
|
18219
|
+
let allTags = false;
|
|
18220
|
+
let deletes = false;
|
|
18221
|
+
let allBranches = false;
|
|
18222
|
+
let mirror = false;
|
|
18280
18223
|
let remote = null;
|
|
18281
18224
|
const positionals = [];
|
|
18282
18225
|
for (let index = 1;index < args.length; index++) {
|
|
@@ -18297,14 +18240,22 @@ var pushAction = (args) => {
|
|
|
18297
18240
|
force = true;
|
|
18298
18241
|
else if (arg.startsWith("--force-with-lease") || arg === "--force-if-includes")
|
|
18299
18242
|
lease = true;
|
|
18300
|
-
else if (
|
|
18301
|
-
|
|
18243
|
+
else if (arg === "--delete" || arg === "-d")
|
|
18244
|
+
deletes = true;
|
|
18245
|
+
else if (arg === "--tags")
|
|
18246
|
+
allTags = wholesale = true;
|
|
18247
|
+
else if (arg === "--all" || arg === "--branches")
|
|
18248
|
+
allBranches = wholesale = true;
|
|
18249
|
+
else if (arg === "--mirror")
|
|
18250
|
+
mirror = wholesale = true;
|
|
18302
18251
|
else if (!arg.startsWith("-"))
|
|
18303
18252
|
positionals.push(arg);
|
|
18304
18253
|
}
|
|
18305
18254
|
const specs = remote === null ? positionals.slice(1) : positionals;
|
|
18306
18255
|
remote ??= positionals[0] ?? null;
|
|
18307
18256
|
const targets = [];
|
|
18257
|
+
const tags = [];
|
|
18258
|
+
const deleted = [];
|
|
18308
18259
|
for (const raw of specs) {
|
|
18309
18260
|
if (raw.startsWith("+"))
|
|
18310
18261
|
force = true;
|
|
@@ -18313,16 +18264,29 @@ var pushAction = (args) => {
|
|
|
18313
18264
|
const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
|
|
18314
18265
|
if (!destination)
|
|
18315
18266
|
continue;
|
|
18316
|
-
|
|
18317
|
-
|
|
18318
|
-
|
|
18319
|
-
|
|
18320
|
-
|
|
18321
|
-
|
|
18267
|
+
const name = destination === "HEAD" ? null : destination.startsWith("refs/heads/") ? destination.slice(11) : destination.startsWith("refs/") ? undefined : destination;
|
|
18268
|
+
if (destination.startsWith("refs/tags/"))
|
|
18269
|
+
tags.push(destination.slice(10));
|
|
18270
|
+
if (name === undefined)
|
|
18271
|
+
continue;
|
|
18272
|
+
targets.push(name);
|
|
18273
|
+
if (colon === 0 && name !== null)
|
|
18274
|
+
deleted.push(name);
|
|
18322
18275
|
}
|
|
18323
18276
|
if (specs.length === 0 && !wholesale)
|
|
18324
18277
|
targets.push(null);
|
|
18325
|
-
return {
|
|
18278
|
+
return {
|
|
18279
|
+
kind: "push",
|
|
18280
|
+
targets,
|
|
18281
|
+
blindForce: force && !lease,
|
|
18282
|
+
remote,
|
|
18283
|
+
...tags.length ? { tags } : {},
|
|
18284
|
+
...allTags ? { allTags: true } : {},
|
|
18285
|
+
...deletes ? { deletes: true } : {},
|
|
18286
|
+
...deleted.length ? { deleted } : {},
|
|
18287
|
+
...allBranches ? { allBranches: true } : {},
|
|
18288
|
+
...mirror ? { mirror: true } : {}
|
|
18289
|
+
};
|
|
18326
18290
|
};
|
|
18327
18291
|
var VALUE_OPTIONS = {
|
|
18328
18292
|
git: new Set([
|
|
@@ -18429,28 +18393,80 @@ var headSha2 = (repo) => {
|
|
|
18429
18393
|
if (!git || git.reftable)
|
|
18430
18394
|
return null;
|
|
18431
18395
|
try {
|
|
18432
|
-
const head = fs16.readFileSync(
|
|
18396
|
+
const head = fs16.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
18433
18397
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
18434
18398
|
return head;
|
|
18435
18399
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
18436
18400
|
if (!ref)
|
|
18437
18401
|
return null;
|
|
18438
|
-
const common =
|
|
18402
|
+
const common = path19.dirname(repo.location.dir);
|
|
18439
18403
|
try {
|
|
18440
|
-
return fs16.readFileSync(
|
|
18404
|
+
return fs16.readFileSync(path19.join(common, ref), "utf8").trim() || null;
|
|
18441
18405
|
} catch {}
|
|
18442
|
-
const packed = fs16.readFileSync(
|
|
18406
|
+
const packed = fs16.readFileSync(path19.join(common, "packed-refs"), "utf8");
|
|
18443
18407
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
18444
18408
|
} catch {
|
|
18445
18409
|
return null;
|
|
18446
18410
|
}
|
|
18447
18411
|
};
|
|
18448
18412
|
var quote = (text) => `\`${text}\``;
|
|
18449
|
-
var refusal = (action, branch) => {
|
|
18413
|
+
var refusal = (action, branch, dir) => {
|
|
18450
18414
|
if (action.kind === "merge")
|
|
18451
18415
|
return `workit: raw ${quote(action.forge === "gh" ? "gh pr merge" : "glab mr merge")} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
|
|
18416
|
+
const plainPush = action.kind === "push" && !action.blindForce && !action.deletes;
|
|
18417
|
+
if (dir && branch && plainPush && releaseBranch(dir, branch))
|
|
18418
|
+
return `workit: raw ${quote("git push")} onto ${branch}, a protected release branch, bypasses workit's protected-branch check. To finish a release or hotfix, push it with ${quote(`workit git push --release ${branch} [--release v<version>]`)}: it pushes ${branch} (fast-forward only) and the release tag, verifies the remote and records it, under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}). Feature work goes through ${quote("workit git push")} and ${quote("workit pr merge")}.`;
|
|
18452
18419
|
return `workit: raw ${quote("git push")}${action.kind === "push" && action.blindForce ? " --force" : ""} onto ${branch}, a protected branch or the default target, bypasses workit's protected-branch check. Push your feature branch with ${quote("workit git push")} (${quote("workit git push --force-with-lease")} to rewrite it) and land it with ${quote("workit pr merge")}.`;
|
|
18453
18420
|
};
|
|
18421
|
+
var refFacts = (repo, remote) => {
|
|
18422
|
+
const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
|
|
18423
|
+
const listed = spawnSync11("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
|
|
18424
|
+
const remotePrefix = `refs/remotes/${remote}/`;
|
|
18425
|
+
for (const ref of (listed.stdout ?? "").split(`
|
|
18426
|
+
`)) {
|
|
18427
|
+
if (ref.startsWith("refs/heads/"))
|
|
18428
|
+
facts.heads.add(ref.slice(11));
|
|
18429
|
+
else if (ref.startsWith("refs/tags/"))
|
|
18430
|
+
facts.tags.add(ref.slice(10));
|
|
18431
|
+
else if (ref.startsWith(remotePrefix))
|
|
18432
|
+
facts.remoteHeads.add(ref.slice(remotePrefix.length));
|
|
18433
|
+
}
|
|
18434
|
+
return facts;
|
|
18435
|
+
};
|
|
18436
|
+
var isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name);
|
|
18437
|
+
var tagRefusal = (action, facts) => {
|
|
18438
|
+
const tags = [
|
|
18439
|
+
...action.tags ?? [],
|
|
18440
|
+
...action.targets.filter((name) => name !== null && isTag(facts, name))
|
|
18441
|
+
];
|
|
18442
|
+
if (!tags.length && !action.allTags)
|
|
18443
|
+
return null;
|
|
18444
|
+
const named = tags.length ? tags.join(" --release ") : "v<version>";
|
|
18445
|
+
return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (v<version> tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
|
|
18446
|
+
};
|
|
18447
|
+
var remoteHas = (repo, remote, branch) => {
|
|
18448
|
+
const listed = spawnSync11("git", ["ls-remote", "--heads", remote, `refs/heads/${branch}`], {
|
|
18449
|
+
cwd: repo.dir,
|
|
18450
|
+
encoding: "utf8",
|
|
18451
|
+
timeout: 3000,
|
|
18452
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
18453
|
+
});
|
|
18454
|
+
return listed.status !== 0 || (listed.stdout ?? "").trim() !== "";
|
|
18455
|
+
};
|
|
18456
|
+
var offPolicyPush = (action, repo, facts, remote) => {
|
|
18457
|
+
const names = action.allBranches ? [...facts.heads] : action.targets.map((target) => target ?? repo.branch);
|
|
18458
|
+
for (const branch of names) {
|
|
18459
|
+
if (!branch || action.deleted?.includes(branch) || isTag(facts, branch))
|
|
18460
|
+
continue;
|
|
18461
|
+
if (facts.remoteHeads.has(branch))
|
|
18462
|
+
continue;
|
|
18463
|
+
const check = validateBranchNameFor(repo.dir, branch);
|
|
18464
|
+
if (check.ok || check.rule !== "allowed_pattern" || remoteHas(repo, remote, branch))
|
|
18465
|
+
continue;
|
|
18466
|
+
return `workit: raw ${quote("git push")} onto ${branch}: ${check.error}`;
|
|
18467
|
+
}
|
|
18468
|
+
return null;
|
|
18469
|
+
};
|
|
18454
18470
|
var nudgeFor = (action, host, session) => {
|
|
18455
18471
|
switch (action.kind) {
|
|
18456
18472
|
case "commit": {
|
|
@@ -18476,7 +18492,7 @@ var actionsIn = (cwd, command, dialect) => {
|
|
|
18476
18492
|
const action = rawAction(invocation);
|
|
18477
18493
|
if (!action)
|
|
18478
18494
|
continue;
|
|
18479
|
-
const dir =
|
|
18495
|
+
const dir = path19.resolve(cwd, invocation.dir ?? ".");
|
|
18480
18496
|
if (!repos.has(dir)) {
|
|
18481
18497
|
const repo = repoAt(dir);
|
|
18482
18498
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -18513,7 +18529,7 @@ var unseenNudges = (input, nudges, store) => {
|
|
|
18513
18529
|
function rawGitPre(input, command, options = {}) {
|
|
18514
18530
|
try {
|
|
18515
18531
|
if (!mentionsRawTool(command))
|
|
18516
|
-
return
|
|
18532
|
+
return NONE2;
|
|
18517
18533
|
const dialect = dialectOf(input);
|
|
18518
18534
|
const nudges = new Map;
|
|
18519
18535
|
let store = null;
|
|
@@ -18521,31 +18537,42 @@ function rawGitPre(input, command, options = {}) {
|
|
|
18521
18537
|
store ??= repo.location.dir;
|
|
18522
18538
|
if (action.kind === "merge")
|
|
18523
18539
|
return deny(refusal(action, null));
|
|
18524
|
-
if (action.kind === "push")
|
|
18540
|
+
if (action.kind === "push" && toPushRemote(repo, action.remote)) {
|
|
18541
|
+
if (action.mirror)
|
|
18542
|
+
return deny(`workit: raw ${quote("git push --mirror")} rewrites and deletes every remote ref, protected branches and tags included. Push one branch with ${quote("workit git push")}.`);
|
|
18525
18543
|
for (const target of action.targets) {
|
|
18526
18544
|
const branch = target ?? repo.branch;
|
|
18527
|
-
if (branch && guardedBranch(repo.dir, branch)
|
|
18528
|
-
return deny(refusal(action, branch));
|
|
18545
|
+
if (branch && guardedBranch(repo.dir, branch))
|
|
18546
|
+
return deny(refusal(action, branch, repo.dir));
|
|
18529
18547
|
}
|
|
18548
|
+
const remote = action.remote ?? pushRemoteName(repo.dir, repo.branch) ?? "origin";
|
|
18549
|
+
const facts = refFacts(repo, remote);
|
|
18550
|
+
const tagDeny = tagRefusal(action, facts);
|
|
18551
|
+
if (tagDeny)
|
|
18552
|
+
return deny(tagDeny);
|
|
18553
|
+
const nameDeny = action.deletes ? null : offPolicyPush(action, repo, facts, remote);
|
|
18554
|
+
if (nameDeny)
|
|
18555
|
+
return deny(nameDeny);
|
|
18556
|
+
}
|
|
18530
18557
|
const nudge = nudgeFor(action, input.host, input.session.id);
|
|
18531
18558
|
if (nudge && !nudges.has(nudgeKind(action)))
|
|
18532
18559
|
nudges.set(nudgeKind(action), nudge);
|
|
18533
18560
|
}
|
|
18534
18561
|
if (options.nudge === false || !store || nudges.size === 0)
|
|
18535
|
-
return
|
|
18562
|
+
return NONE2;
|
|
18536
18563
|
if (workitInvocations(command, dialect).length > 0)
|
|
18537
|
-
return
|
|
18564
|
+
return NONE2;
|
|
18538
18565
|
const shown = unseenNudges(input, nudges, store);
|
|
18539
18566
|
return shown.length ? { kind: "context", text: shown.join(`
|
|
18540
|
-
`) } :
|
|
18567
|
+
`) } : NONE2;
|
|
18541
18568
|
} catch {
|
|
18542
|
-
return
|
|
18569
|
+
return NONE2;
|
|
18543
18570
|
}
|
|
18544
18571
|
}
|
|
18545
18572
|
var deny = (reason) => ({ kind: "deny", reason, unblock: null });
|
|
18546
18573
|
var NEXT_COMMAND = "next";
|
|
18547
18574
|
var MARKER_TTL_SECONDS = 3600;
|
|
18548
|
-
var markerFile = (repo, session, key) =>
|
|
18575
|
+
var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
18549
18576
|
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
|
|
18550
18577
|
function noteRawCommit(input, command, key) {
|
|
18551
18578
|
try {
|
|
@@ -18556,7 +18583,7 @@ function noteRawCommit(input, command, key) {
|
|
|
18556
18583
|
if (action.kind !== "commit")
|
|
18557
18584
|
continue;
|
|
18558
18585
|
const file = markerFile(repo, input.session.id, key);
|
|
18559
|
-
fs16.mkdirSync(
|
|
18586
|
+
fs16.mkdirSync(path19.dirname(file), { recursive: true });
|
|
18560
18587
|
fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
18561
18588
|
`);
|
|
18562
18589
|
}
|
|
@@ -18638,7 +18665,292 @@ function settlePendingCommit(input) {
|
|
|
18638
18665
|
} catch {}
|
|
18639
18666
|
}
|
|
18640
18667
|
|
|
18668
|
+
// packages/workit-core/src/core/route-intent.ts
|
|
18669
|
+
var UNKNOWABLE = /[$`*?[\]{}~]/u;
|
|
18670
|
+
var SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
|
|
18671
|
+
var CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
|
|
18672
|
+
var WORKTREE_CREATE = new Set(["-b", "-B"]);
|
|
18673
|
+
var BRANCH_NO_CREATE = new Set(["-d", "-D", "--delete", "-l", "--list", "-a", "--all", "-r", "--remotes", "--show-current"].concat(["-v", "-vv", "--verbose", "--contains", "--no-contains", "--merged", "--no-merged"]).concat(["--points-at", "--sort", "--format", "--column", "--no-column", "-u"]).concat(["--set-upstream-to", "--unset-upstream", "--edit-description", "-i", "--ignore-case"]));
|
|
18674
|
+
var BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
|
|
18675
|
+
var flagValue = (args, flags) => {
|
|
18676
|
+
for (let index = 1;index < args.length; index++) {
|
|
18677
|
+
const arg = args[index];
|
|
18678
|
+
if (arg === "--")
|
|
18679
|
+
return null;
|
|
18680
|
+
if (flags.has(arg))
|
|
18681
|
+
return args[index + 1] ?? null;
|
|
18682
|
+
const eq = arg.indexOf("=");
|
|
18683
|
+
if (arg.startsWith("--") && eq > 0 && flags.has(arg.slice(0, eq)))
|
|
18684
|
+
return arg.slice(eq + 1);
|
|
18685
|
+
if (!arg.startsWith("--") && arg.length > 2 && flags.has(arg.slice(0, 2)))
|
|
18686
|
+
return arg.slice(2);
|
|
18687
|
+
}
|
|
18688
|
+
return null;
|
|
18689
|
+
};
|
|
18690
|
+
var WORKTREE_VALUES = new Set(["--reason"]);
|
|
18691
|
+
var worktreeTarget = (args) => {
|
|
18692
|
+
const named = flagValue(args, WORKTREE_CREATE);
|
|
18693
|
+
if (named !== null)
|
|
18694
|
+
return named;
|
|
18695
|
+
const positionals = [];
|
|
18696
|
+
for (let index = 1;index < args.length; index++) {
|
|
18697
|
+
const arg = args[index];
|
|
18698
|
+
if (arg === "--detach" || arg === "-d")
|
|
18699
|
+
return null;
|
|
18700
|
+
if (WORKTREE_VALUES.has(arg))
|
|
18701
|
+
index++;
|
|
18702
|
+
else if (!arg.startsWith("-"))
|
|
18703
|
+
positionals.push(arg);
|
|
18704
|
+
}
|
|
18705
|
+
if (positionals.length !== 1)
|
|
18706
|
+
return null;
|
|
18707
|
+
const base = positionals[0].split(/[\\/]/u).findLast(Boolean) ?? "";
|
|
18708
|
+
return base && base !== "." && base !== ".." ? base : null;
|
|
18709
|
+
};
|
|
18710
|
+
var branchCommandTarget = (args) => {
|
|
18711
|
+
let rename = false;
|
|
18712
|
+
const positionals = [];
|
|
18713
|
+
for (let index = 1;index < args.length; index++) {
|
|
18714
|
+
const arg = args[index];
|
|
18715
|
+
if (arg === "--") {
|
|
18716
|
+
positionals.push(...args.slice(index + 1));
|
|
18717
|
+
break;
|
|
18718
|
+
}
|
|
18719
|
+
const flag = arg.split("=", 1)[0];
|
|
18720
|
+
if (BRANCH_NO_CREATE.has(flag))
|
|
18721
|
+
return null;
|
|
18722
|
+
if (BRANCH_RENAME.has(flag))
|
|
18723
|
+
rename = true;
|
|
18724
|
+
else if (!arg.startsWith("-"))
|
|
18725
|
+
positionals.push(arg);
|
|
18726
|
+
}
|
|
18727
|
+
if (positionals.length < 1 || positionals.length > 2)
|
|
18728
|
+
return null;
|
|
18729
|
+
return rename ? positionals.at(-1) ?? null : positionals[0];
|
|
18730
|
+
};
|
|
18731
|
+
var gitBranchCreation = (args) => {
|
|
18732
|
+
const [sub, second] = args;
|
|
18733
|
+
const target = sub === "switch" ? flagValue(args, SWITCH_CREATE) : sub === "checkout" ? flagValue(args, CHECKOUT_CREATE) : sub === "branch" ? branchCommandTarget(args) : sub === "worktree" && second === "add" ? worktreeTarget(args.slice(1)) : null;
|
|
18734
|
+
return target && !target.startsWith("-") && !UNKNOWABLE.test(target) ? target : null;
|
|
18735
|
+
};
|
|
18736
|
+
var shellBranchTargets = (command, dialect = "posix") => {
|
|
18737
|
+
if (!/\bgit\b/u.test(command))
|
|
18738
|
+
return [];
|
|
18739
|
+
const found = [];
|
|
18740
|
+
for (const invocation of rawInvocations(command, dialect)) {
|
|
18741
|
+
if (invocation.tool !== "git")
|
|
18742
|
+
continue;
|
|
18743
|
+
const branch = gitBranchCreation(invocation.args);
|
|
18744
|
+
if (branch)
|
|
18745
|
+
found.push({ branch, dir: invocation.dir });
|
|
18746
|
+
}
|
|
18747
|
+
return found;
|
|
18748
|
+
};
|
|
18749
|
+
var localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
|
|
18750
|
+
cwd: dir,
|
|
18751
|
+
stdio: "ignore",
|
|
18752
|
+
timeout: 5000
|
|
18753
|
+
}).status === 0;
|
|
18754
|
+
var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
18755
|
+
let judged = false;
|
|
18756
|
+
for (const target of shellBranchTargets(command, dialect)) {
|
|
18757
|
+
if (target.dir === undefined)
|
|
18758
|
+
continue;
|
|
18759
|
+
const dir = target.dir === null ? root : path20.resolve(root, target.dir);
|
|
18760
|
+
if (target.dir !== null && (!existsSync6(dir) || !inWorkitWorkspace(dir)))
|
|
18761
|
+
continue;
|
|
18762
|
+
if (localBranchExists(dir, target.branch))
|
|
18763
|
+
continue;
|
|
18764
|
+
judged = true;
|
|
18765
|
+
const check = validateBranchNameFor(dir, target.branch);
|
|
18766
|
+
if (!check.ok)
|
|
18767
|
+
return check;
|
|
18768
|
+
}
|
|
18769
|
+
return judged ? { ok: true } : null;
|
|
18770
|
+
};
|
|
18771
|
+
|
|
18772
|
+
// packages/workit-core/src/hooks/policy.ts
|
|
18773
|
+
var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
18774
|
+
const policy = shellBranchPolicyViolation(cwd, command, dialect);
|
|
18775
|
+
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
18776
|
+
};
|
|
18777
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
18778
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
18779
|
+
import path21 from "node:path";
|
|
18780
|
+
var NONE3 = { kind: "none" };
|
|
18781
|
+
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
18782
|
+
var canonical2 = (file) => {
|
|
18783
|
+
let rest = "";
|
|
18784
|
+
for (let dir = path21.resolve(file);; dir = path21.dirname(dir)) {
|
|
18785
|
+
try {
|
|
18786
|
+
return path21.join(realpathSync5(dir), rest);
|
|
18787
|
+
} catch {
|
|
18788
|
+
if (path21.dirname(dir) === dir)
|
|
18789
|
+
return path21.resolve(file);
|
|
18790
|
+
rest = rest ? path21.join(path21.basename(dir), rest) : path21.basename(dir);
|
|
18791
|
+
}
|
|
18792
|
+
}
|
|
18793
|
+
};
|
|
18794
|
+
var exempt = (cwd, root, file, plans) => {
|
|
18795
|
+
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
18796
|
+
const relative = path21.relative(fold(canonical2(root)), fold(canonical2(path21.resolve(cwd, file))));
|
|
18797
|
+
if (relative.startsWith("..") || path21.isAbsolute(relative))
|
|
18798
|
+
return true;
|
|
18799
|
+
const posix = relative.split(path21.sep).join("/");
|
|
18800
|
+
const segments = posix.split("/");
|
|
18801
|
+
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
18802
|
+
};
|
|
18803
|
+
var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
18804
|
+
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
18805
|
+
var editsOf = (input) => {
|
|
18806
|
+
let words = input;
|
|
18807
|
+
while (words.length && (PREFIXES3.has(words[0]) || /^\w+=/.test(words[0])))
|
|
18808
|
+
words = words.slice(1);
|
|
18809
|
+
const [verb, ...args] = words;
|
|
18810
|
+
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
18811
|
+
if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
|
|
18812
|
+
return [];
|
|
18813
|
+
switch (name) {
|
|
18814
|
+
case "touch":
|
|
18815
|
+
case "mkdir":
|
|
18816
|
+
case "rm":
|
|
18817
|
+
case "rmdir":
|
|
18818
|
+
case "tee":
|
|
18819
|
+
case "mv":
|
|
18820
|
+
case "truncate":
|
|
18821
|
+
return operands(args);
|
|
18822
|
+
case "cp":
|
|
18823
|
+
case "ln":
|
|
18824
|
+
case "install": {
|
|
18825
|
+
const target = args.indexOf("-t");
|
|
18826
|
+
if (target >= 0)
|
|
18827
|
+
return args[target + 1] ? [args[target + 1]] : null;
|
|
18828
|
+
const last = operands(args).at(-1);
|
|
18829
|
+
return last ? [last] : null;
|
|
18830
|
+
}
|
|
18831
|
+
case "dd":
|
|
18832
|
+
return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
|
|
18833
|
+
case "sed":
|
|
18834
|
+
case "perl": {
|
|
18835
|
+
if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
|
|
18836
|
+
return [];
|
|
18837
|
+
const scripted = args.some((item) => item === "-e" || item === "-f");
|
|
18838
|
+
const files = [];
|
|
18839
|
+
for (let index = 0;index < args.length; index++) {
|
|
18840
|
+
if (args[index] === "-e" || args[index] === "-f")
|
|
18841
|
+
index++;
|
|
18842
|
+
else if (!args[index].startsWith("-"))
|
|
18843
|
+
files.push(args[index]);
|
|
18844
|
+
}
|
|
18845
|
+
return scripted ? files : files.slice(1);
|
|
18846
|
+
}
|
|
18847
|
+
case "patch":
|
|
18848
|
+
return null;
|
|
18849
|
+
case "set-content":
|
|
18850
|
+
case "add-content":
|
|
18851
|
+
case "out-file":
|
|
18852
|
+
case "new-item":
|
|
18853
|
+
case "ni":
|
|
18854
|
+
case "remove-item":
|
|
18855
|
+
case "del":
|
|
18856
|
+
case "copy-item":
|
|
18857
|
+
case "move-item": {
|
|
18858
|
+
const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
|
|
18859
|
+
if (flag >= 0)
|
|
18860
|
+
return args[flag + 1] ? [args[flag + 1]] : null;
|
|
18861
|
+
const files = operands(args);
|
|
18862
|
+
if (name === "move-item")
|
|
18863
|
+
return files.length ? files : null;
|
|
18864
|
+
const file = name === "copy-item" ? files.at(-1) : files[0];
|
|
18865
|
+
return file ? [file] : null;
|
|
18866
|
+
}
|
|
18867
|
+
case "git": {
|
|
18868
|
+
const sub = operands(args)[0];
|
|
18869
|
+
if (sub === "apply")
|
|
18870
|
+
return null;
|
|
18871
|
+
if (sub === "restore" || sub === "mv" || sub === "rm")
|
|
18872
|
+
return operands(args).slice(1);
|
|
18873
|
+
if (sub === "checkout" && args.includes("--"))
|
|
18874
|
+
return args.slice(args.indexOf("--") + 1);
|
|
18875
|
+
return [];
|
|
18876
|
+
}
|
|
18877
|
+
default:
|
|
18878
|
+
return [];
|
|
18879
|
+
}
|
|
18880
|
+
};
|
|
18881
|
+
var shellWrites = (command) => {
|
|
18882
|
+
const targets = [];
|
|
18883
|
+
let unknown = false;
|
|
18884
|
+
for (const segment of segmentsOf(command)) {
|
|
18885
|
+
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
18886
|
+
const edits = editsOf(segment.words);
|
|
18887
|
+
if (edits === null)
|
|
18888
|
+
unknown = true;
|
|
18889
|
+
else
|
|
18890
|
+
targets.push(...edits);
|
|
18891
|
+
}
|
|
18892
|
+
if (!unknown && targets.length === 0)
|
|
18893
|
+
return { writes: false, targets: null };
|
|
18894
|
+
return { writes: true, targets: unknown ? null : targets };
|
|
18895
|
+
};
|
|
18896
|
+
var cache2 = new Map;
|
|
18897
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path21.resolve(root, file)) ?? "-").join("\x00");
|
|
18898
|
+
var gateFor = (cwd) => {
|
|
18899
|
+
const store = new TaskStore(cwd);
|
|
18900
|
+
const listed = store.listTaskIndex();
|
|
18901
|
+
if (!listed.ok)
|
|
18902
|
+
return null;
|
|
18903
|
+
const key = store.currentKey();
|
|
18904
|
+
if (!key.ok)
|
|
18905
|
+
return null;
|
|
18906
|
+
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
18907
|
+
if (!entry)
|
|
18908
|
+
return { blockers: [], root: store.root, plans: new Set };
|
|
18909
|
+
const ledger = ledgerPath(store.root);
|
|
18910
|
+
const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
|
|
18911
|
+
const hit = cache2.get(store.root);
|
|
18912
|
+
if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
|
|
18913
|
+
return hit;
|
|
18914
|
+
const task = store.readTask(entry.id);
|
|
18915
|
+
if (!task.ok)
|
|
18916
|
+
return null;
|
|
18917
|
+
const workspace = store.readWorkspace();
|
|
18918
|
+
const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
|
|
18919
|
+
const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
|
|
18920
|
+
const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
|
|
18921
|
+
return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
|
|
18922
|
+
});
|
|
18923
|
+
const gate = {
|
|
18924
|
+
blockers,
|
|
18925
|
+
root: store.root,
|
|
18926
|
+
plans,
|
|
18927
|
+
key: cacheKey,
|
|
18928
|
+
planKey: planKeyOf(store.root, plans)
|
|
18929
|
+
};
|
|
18930
|
+
cache2.set(store.root, gate);
|
|
18931
|
+
if (cache2.size > 32)
|
|
18932
|
+
cache2.delete(cache2.keys().next().value);
|
|
18933
|
+
return gate;
|
|
18934
|
+
};
|
|
18935
|
+
function writeGate(cwd, paths) {
|
|
18936
|
+
try {
|
|
18937
|
+
const gate = gateFor(cwd);
|
|
18938
|
+
if (!gate || gate.blockers.length === 0)
|
|
18939
|
+
return NONE3;
|
|
18940
|
+
if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
|
|
18941
|
+
return NONE3;
|
|
18942
|
+
const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
|
|
18943
|
+
return {
|
|
18944
|
+
kind: "deny",
|
|
18945
|
+
reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
|
|
18946
|
+
unblock: gate.blockers[0].reason
|
|
18947
|
+
};
|
|
18948
|
+
} catch {
|
|
18949
|
+
return NONE3;
|
|
18950
|
+
}
|
|
18951
|
+
}
|
|
18641
18952
|
// packages/workit-core/src/hooks/stop.ts
|
|
18953
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
18642
18954
|
var NONE4 = { kind: "none" };
|
|
18643
18955
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
18644
18956
|
var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
|
|
@@ -18659,7 +18971,7 @@ var asksUser = (message) => {
|
|
|
18659
18971
|
};
|
|
18660
18972
|
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
18661
18973
|
var gitList = (cwd, args) => {
|
|
18662
|
-
const run =
|
|
18974
|
+
const run = spawnSync13("git", args, {
|
|
18663
18975
|
cwd,
|
|
18664
18976
|
encoding: "utf8",
|
|
18665
18977
|
timeout: 3000,
|
|
@@ -19022,13 +19334,13 @@ ${line}` };
|
|
|
19022
19334
|
return decision;
|
|
19023
19335
|
};
|
|
19024
19336
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
19025
|
-
import { existsSync as
|
|
19026
|
-
import
|
|
19337
|
+
import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
19338
|
+
import path22 from "node:path";
|
|
19027
19339
|
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19028
19340
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
19029
19341
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
19030
19342
|
var existingDirectory = (value) => {
|
|
19031
|
-
if (!nonEmpty2(value) || !
|
|
19343
|
+
if (!nonEmpty2(value) || !path22.isAbsolute(value) || !existsSync7(value))
|
|
19032
19344
|
return null;
|
|
19033
19345
|
try {
|
|
19034
19346
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -19111,19 +19423,20 @@ var workitRole = (descriptor, agentType) => {
|
|
|
19111
19423
|
};
|
|
19112
19424
|
var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
|
|
19113
19425
|
var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
|
|
19114
|
-
var
|
|
19426
|
+
var isolatedGit = (cwd) => `Run git as plain, separate commands inside your worktree (\`git switch -c <branch> <base>\`, ${workspaceTrailers(cwd).enabled ? '`git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a prepare-commit-msg hook that adds the trailer' : "`git commit` (this workspace sets trailers: false: add no Workit-Session trailer), `git push`"}; add Co-Authored-By with \`git commit --trailer\` in the same commit, never by amending), never \`workit git …\`: Claude Code's worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (\`workit check\`, \`workit ledger\`, \`workit pr\`).`;
|
|
19115
19427
|
var subagentStartText = (input, descriptor, event) => {
|
|
19116
19428
|
const session = input.session.id ?? null;
|
|
19117
19429
|
const role = workitRole(descriptor, event.agentType);
|
|
19118
19430
|
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
19119
19431
|
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
|
|
19432
|
+
const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
|
|
19120
19433
|
if (role === "implementer" && isolated)
|
|
19121
|
-
return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${
|
|
19434
|
+
return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${isolatedGit(input.cwd)} Plain git is expected here, and the branch name is still policy-checked: a raw \`git switch -c\`, \`git checkout -b\`, \`git branch\` or \`git worktree add -b\` onto a protected or non-compliant name is denied, so use a policy-compliant branch.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
19122
19435
|
if (isolated)
|
|
19123
|
-
return `${
|
|
19436
|
+
return `${isolatedGit(input.cwd)}
|
|
19124
19437
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
19125
19438
|
if (role === "implementer")
|
|
19126
|
-
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (
|
|
19439
|
+
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
19127
19440
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
19128
19441
|
if (role !== "judge" || !event.agentId)
|
|
19129
19442
|
return readOnly;
|
|
@@ -19163,7 +19476,7 @@ function handleHook(input, deps) {
|
|
|
19163
19476
|
if (skillRead)
|
|
19164
19477
|
recordSkillLoad(input, skillRead, "read");
|
|
19165
19478
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
19166
|
-
const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE5;
|
|
19479
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE5;
|
|
19167
19480
|
if (policy.kind !== "none")
|
|
19168
19481
|
return policy;
|
|
19169
19482
|
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
@@ -19766,7 +20079,7 @@ var codexAdapter = {
|
|
|
19766
20079
|
|
|
19767
20080
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
19768
20081
|
import fs17 from "node:fs";
|
|
19769
|
-
import
|
|
20082
|
+
import path23 from "node:path";
|
|
19770
20083
|
var undocumented = { support: "undocumented", native: null };
|
|
19771
20084
|
var none = { support: "none", native: null };
|
|
19772
20085
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -19963,7 +20276,7 @@ var protocolEvent2 = (input) => {
|
|
|
19963
20276
|
};
|
|
19964
20277
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
19965
20278
|
var lastAssistantText = (file) => {
|
|
19966
|
-
if (!file || !
|
|
20279
|
+
if (!file || !path23.isAbsolute(file))
|
|
19967
20280
|
return null;
|
|
19968
20281
|
try {
|
|
19969
20282
|
const size = fs17.statSync(file).size;
|
|
@@ -20000,7 +20313,7 @@ var lastAssistantText = (file) => {
|
|
|
20000
20313
|
return null;
|
|
20001
20314
|
}
|
|
20002
20315
|
};
|
|
20003
|
-
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
20316
|
+
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path23.sep) ? root : `${root}${path23.sep}`);
|
|
20004
20317
|
var hookCwd = (input) => {
|
|
20005
20318
|
const cwd = existingDirectory(input.cwd);
|
|
20006
20319
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|