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