@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
|
@@ -8834,7 +8834,7 @@ var PREFIXES, branchNames = (workspaceRoot) => {
|
|
|
8834
8834
|
return new Set;
|
|
8835
8835
|
return new Set((r.stdout ?? "").split(`
|
|
8836
8836
|
`).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
|
|
8837
|
-
}, detectBranchPolicy = (workspaceRoot) => {
|
|
8837
|
+
}, productionBranchOf = (names) => names.has("main") ? "main" : names.has("master") ? "master" : "main", detectBranchPolicy = (workspaceRoot) => {
|
|
8838
8838
|
const names = branchNames(workspaceRoot);
|
|
8839
8839
|
const develop = names.has("develop");
|
|
8840
8840
|
const main = names.has("main");
|
|
@@ -9120,8 +9120,8 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
|
|
|
9120
9120
|
profiles: record(nonBlank, profileSchema).optional()
|
|
9121
9121
|
}).passthrough();
|
|
9122
9122
|
return entrySchema;
|
|
9123
|
-
}, resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs"), runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd) => {
|
|
9124
|
-
const file = path3.join(
|
|
9123
|
+
}, resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs"), runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd, dir = configDir()) => {
|
|
9124
|
+
const file = path3.join(dir, "workspaces.json");
|
|
9125
9125
|
let raw;
|
|
9126
9126
|
try {
|
|
9127
9127
|
raw = readFileSync2(file, "utf8");
|
|
@@ -9150,10 +9150,27 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
|
|
|
9150
9150
|
const matches = matchingEntries(cwd, entries ?? []);
|
|
9151
9151
|
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
9152
9152
|
const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
|
|
9153
|
-
return selected ? { file, selected } : null;
|
|
9153
|
+
return selected ? { file, selected, matches } : null;
|
|
9154
9154
|
}, resolveRuntimeReleaseTracks = (cwd) => {
|
|
9155
9155
|
const matched = matchRuntimeWorkspace(cwd);
|
|
9156
9156
|
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
9157
|
+
}, workspaceTrailers = (cwd, dir) => {
|
|
9158
|
+
let matched;
|
|
9159
|
+
try {
|
|
9160
|
+
matched = matchRuntimeWorkspace(cwd, dir);
|
|
9161
|
+
} catch {
|
|
9162
|
+
return { enabled: true, workspace: null, from: null };
|
|
9163
|
+
}
|
|
9164
|
+
if (!matched)
|
|
9165
|
+
return { enabled: true, workspace: null, from: null };
|
|
9166
|
+
const { selected, matches } = matched;
|
|
9167
|
+
const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
9168
|
+
const source = [selected, ...enclosing].find((entry) => typeof entry.trailers === "boolean");
|
|
9169
|
+
return {
|
|
9170
|
+
enabled: source?.trailers !== false,
|
|
9171
|
+
workspace: selected.name,
|
|
9172
|
+
from: source?.name ?? null
|
|
9173
|
+
};
|
|
9157
9174
|
}, resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
9158
9175
|
const matched = matchRuntimeWorkspace(cwd);
|
|
9159
9176
|
if (!matched)
|
|
@@ -9264,6 +9281,7 @@ var init_workspaces = __esm(() => {
|
|
|
9264
9281
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
9265
9282
|
defaultEndpoint: unknown().optional(),
|
|
9266
9283
|
verification: _enum(["self", "independent"]).optional(),
|
|
9284
|
+
trailers: boolean2().optional(),
|
|
9267
9285
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
9268
9286
|
kind,
|
|
9269
9287
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -14783,7 +14801,7 @@ function loadCheckConfig(cwd) {
|
|
|
14783
14801
|
gates[gate] = names;
|
|
14784
14802
|
}
|
|
14785
14803
|
}
|
|
14786
|
-
return { source: CHECKS_FILE, root, checks, gates, error: null };
|
|
14804
|
+
return { source: CHECKS_FILE, root, checks, gates, error: null, resources: parsed.resources };
|
|
14787
14805
|
}
|
|
14788
14806
|
function matchesNamedCheck(config, name, argv, cwd) {
|
|
14789
14807
|
if (config.error || name === null || cwd !== ".")
|
|
@@ -15550,9 +15568,9 @@ Autonomy contract:
|
|
|
15550
15568
|
ship), go to the effective endpoint in \`workit grant show\`: \`pr\` opens
|
|
15551
15569
|
the PR; \`green\` babysits it without asking (workit-ship) to merge-ready,
|
|
15552
15570
|
never merging; \`merged\` also lands it with \`workit pr merge\`.
|
|
15553
|
-
Merge and release need a workspace grant
|
|
15554
|
-
|
|
15555
|
-
|
|
15571
|
+
Merge and release need a workspace grant only the user raises from their
|
|
15572
|
+
own terminal (a blocked verb names how); never edit workspaces.json or route
|
|
15573
|
+
around a grant_required.
|
|
15556
15574
|
- Ask only for a product or preference choice, or for authority you lack, with
|
|
15557
15575
|
your recommended answer. Facts are yours: read, run or prototype.
|
|
15558
15576
|
- Label claims measured (run and seen this session), inferred, or guess.
|
|
@@ -15561,17 +15579,19 @@ Autonomy contract:
|
|
|
15561
15579
|
- Author is not verifier: your own verdict is \`--self\`, shown as
|
|
15562
15580
|
self-reviewed, never verified. High risk, \`verification: independent\` and
|
|
15563
15581
|
merge need a verifier with its own host-derived session (a subagent's
|
|
15564
|
-
\`<lead>:<child
|
|
15582
|
+
\`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
|
|
15565
15583
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
15566
15584
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
15567
15585
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
15568
15586
|
|
|
15569
15587
|
CLI first: if a step has one right answer, use the \`workit\` verb instead of
|
|
15570
15588
|
hand-running git, gh or glab (\`workit git|pr|ci|check\`; \`workit help\` lists
|
|
15571
|
-
the rest;
|
|
15589
|
+
the rest; lacking \`workit\`, \`npx -y @brainervirus/workit-cli\`).
|
|
15590
|
+
Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
|
|
15591
|
+
an amend.
|
|
15572
15592
|
The task families (task, policy, evidence, finding, decision, worker, state)
|
|
15573
15593
|
are optional continuity for tracked work; each branch has one implicit task,
|
|
15574
|
-
|
|
15594
|
+
and a solo edit needs no task. A decision
|
|
15575
15595
|
records a user choice; it never authorizes an effect. Judge work once with
|
|
15576
15596
|
\`workit policy assess --judge risk= behavior= product-choice= plan=\`.
|
|
15577
15597
|
Across repositories, bind each item to its checkout, branch and endpoint,
|
|
@@ -17647,10 +17667,41 @@ var withReleaseTracks = (workspaceRoot, policy) => {
|
|
|
17647
17667
|
...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
|
|
17648
17668
|
])
|
|
17649
17669
|
};
|
|
17670
|
+
}, KIND_SYNONYMS, allowedKinds = (allowed) => [
|
|
17671
|
+
...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
|
|
17672
|
+
], editDistance = (a, b) => {
|
|
17673
|
+
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
17674
|
+
for (let i = 1;i <= a.length; i++) {
|
|
17675
|
+
let previous = row[0];
|
|
17676
|
+
row[0] = i;
|
|
17677
|
+
for (let j = 1;j <= b.length; j++) {
|
|
17678
|
+
const saved = row[j];
|
|
17679
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
17680
|
+
previous = saved;
|
|
17681
|
+
}
|
|
17682
|
+
}
|
|
17683
|
+
return row[b.length];
|
|
17684
|
+
}, suggestBranchName = (name, kinds) => {
|
|
17685
|
+
if (!kinds.length)
|
|
17686
|
+
return null;
|
|
17687
|
+
const slash = name.indexOf("/");
|
|
17688
|
+
const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
|
|
17689
|
+
const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
|
|
17690
|
+
const synonym = KIND_SYNONYMS[prefix];
|
|
17691
|
+
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;
|
|
17692
|
+
const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
|
|
17693
|
+
return `${kind}/${rest}`;
|
|
17694
|
+
}, kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", "), aliasNote = (kinds) => {
|
|
17695
|
+
const notes = [
|
|
17696
|
+
["fix", "bugfix"],
|
|
17697
|
+
["chore", "feature"]
|
|
17698
|
+
].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
|
|
17699
|
+
return notes.length ? ` (${notes.join(", ")})` : "";
|
|
17650
17700
|
}, validateBranchNamePolicy = (name, policy, source) => {
|
|
17651
17701
|
const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
|
|
17702
|
+
const kinds = allowedKinds(policy.allowed);
|
|
17652
17703
|
if (policy.protected.has(name.toLowerCase())) {
|
|
17653
|
-
const correction = `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
|
|
17704
|
+
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(", ")}`;
|
|
17654
17705
|
return {
|
|
17655
17706
|
ok: false,
|
|
17656
17707
|
rule: "protected_ref",
|
|
@@ -17661,7 +17712,8 @@ var withReleaseTracks = (workspaceRoot, policy) => {
|
|
|
17661
17712
|
};
|
|
17662
17713
|
}
|
|
17663
17714
|
if (!policy.allowed.some((pattern) => pattern.test(name))) {
|
|
17664
|
-
const
|
|
17715
|
+
const suggestion = suggestBranchName(name, kinds);
|
|
17716
|
+
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(", ")}`;
|
|
17665
17717
|
return {
|
|
17666
17718
|
ok: false,
|
|
17667
17719
|
rule: "allowed_pattern",
|
|
@@ -17700,7 +17752,52 @@ var withReleaseTracks = (workspaceRoot, policy) => {
|
|
|
17700
17752
|
error: `policy_unavailable: cannot validate branch ${JSON.stringify(name)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
|
|
17701
17753
|
};
|
|
17702
17754
|
}
|
|
17703
|
-
}, resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy
|
|
17755
|
+
}, resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy, gitFlowOf = (root, integration) => {
|
|
17756
|
+
try {
|
|
17757
|
+
if (resolveBranchPolicyFor(root).preset !== "gitflow")
|
|
17758
|
+
return null;
|
|
17759
|
+
const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
17760
|
+
const names = branchNames(root);
|
|
17761
|
+
const production = productionBranchOf(names);
|
|
17762
|
+
const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
|
|
17763
|
+
return { develop, production, developExists: names.has(develop) };
|
|
17764
|
+
} catch {
|
|
17765
|
+
return null;
|
|
17766
|
+
}
|
|
17767
|
+
}, releaseBranchesOf = (cwd) => {
|
|
17768
|
+
try {
|
|
17769
|
+
const resolved = resolveBranchPolicyFor(cwd);
|
|
17770
|
+
const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
|
|
17771
|
+
return {
|
|
17772
|
+
preset: resolved.preset,
|
|
17773
|
+
branches: flow ? [flow.production, flow.develop] : [],
|
|
17774
|
+
production: flow ? flow.production : resolved.defaultTargetBranch
|
|
17775
|
+
};
|
|
17776
|
+
} catch {
|
|
17777
|
+
return { preset: "unknown", branches: [], production: null };
|
|
17778
|
+
}
|
|
17779
|
+
}, releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase()), branchGuidance = (cwd) => {
|
|
17780
|
+
try {
|
|
17781
|
+
const patterns = branchPolicySnapshotFor(cwd).namePolicy.allowed;
|
|
17782
|
+
const kinds = allowedKinds(patterns);
|
|
17783
|
+
const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
|
|
17784
|
+
const anyName = patterns.some((pattern) => pattern.source === "^.*$");
|
|
17785
|
+
const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
|
|
17786
|
+
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.`;
|
|
17787
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
17788
|
+
const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
|
|
17789
|
+
const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
|
|
17790
|
+
if (flow?.developExists)
|
|
17791
|
+
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}.`;
|
|
17792
|
+
const names = branchNames(cwd);
|
|
17793
|
+
const configured = String(resolved.baseBranch ?? integration ?? "") || null;
|
|
17794
|
+
const trunk = productionBranchOf(names);
|
|
17795
|
+
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.";
|
|
17796
|
+
return `${allowed} ${base}`;
|
|
17797
|
+
} catch {
|
|
17798
|
+
return null;
|
|
17799
|
+
}
|
|
17800
|
+
}, globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*", "*").replace(/\\(.)/gu, "$1");
|
|
17704
17801
|
var init_branch = __esm(() => {
|
|
17705
17802
|
init_branch_policy();
|
|
17706
17803
|
init_commit_flavors();
|
|
@@ -17709,33 +17806,63 @@ var init_branch = __esm(() => {
|
|
|
17709
17806
|
init_workspaces();
|
|
17710
17807
|
init_vcs_config();
|
|
17711
17808
|
init_release_tracks();
|
|
17809
|
+
KIND_SYNONYMS = {
|
|
17810
|
+
fix: "bugfix",
|
|
17811
|
+
fixes: "bugfix",
|
|
17812
|
+
bug: "bugfix",
|
|
17813
|
+
bugs: "bugfix",
|
|
17814
|
+
bugfixes: "bugfix",
|
|
17815
|
+
feat: "feature",
|
|
17816
|
+
features: "feature",
|
|
17817
|
+
chore: "feature",
|
|
17818
|
+
docs: "feature",
|
|
17819
|
+
doc: "feature",
|
|
17820
|
+
refactor: "feature",
|
|
17821
|
+
test: "feature",
|
|
17822
|
+
tests: "feature",
|
|
17823
|
+
ci: "feature",
|
|
17824
|
+
build: "feature",
|
|
17825
|
+
perf: "feature",
|
|
17826
|
+
style: "feature",
|
|
17827
|
+
deps: "feature",
|
|
17828
|
+
task: "feature",
|
|
17829
|
+
enhancement: "feature",
|
|
17830
|
+
hot: "hotfix",
|
|
17831
|
+
hotfixes: "hotfix",
|
|
17832
|
+
releases: "release",
|
|
17833
|
+
rel: "release"
|
|
17834
|
+
};
|
|
17712
17835
|
});
|
|
17713
17836
|
|
|
17714
|
-
// packages/workit-core/src/
|
|
17715
|
-
|
|
17716
|
-
|
|
17717
|
-
|
|
17718
|
-
|
|
17719
|
-
|
|
17720
|
-
|
|
17721
|
-
|
|
17722
|
-
|
|
17723
|
-
|
|
17724
|
-
|
|
17725
|
-
|
|
17726
|
-
|
|
17727
|
-
};
|
|
17728
|
-
|
|
17729
|
-
|
|
17730
|
-
|
|
17731
|
-
|
|
17732
|
-
|
|
17733
|
-
|
|
17734
|
-
|
|
17735
|
-
|
|
17837
|
+
// packages/workit-core/src/hooks/session-marker.ts
|
|
17838
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
17839
|
+
import fs15 from "node:fs";
|
|
17840
|
+
import path18 from "node:path";
|
|
17841
|
+
var sessionMarkerFile = (input, session, store) => {
|
|
17842
|
+
let dir = store;
|
|
17843
|
+
if (dir === undefined) {
|
|
17844
|
+
const location = resolveStore(input.cwd);
|
|
17845
|
+
if (location instanceof Error || !location.shared)
|
|
17846
|
+
return null;
|
|
17847
|
+
dir = location.dir;
|
|
17848
|
+
}
|
|
17849
|
+
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
17850
|
+
return path18.join(dir, "hooks", `skills-${name}.json`);
|
|
17851
|
+
}, list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [], readSessionMarker = (file) => {
|
|
17852
|
+
try {
|
|
17853
|
+
const value = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
17854
|
+
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
17855
|
+
} catch {
|
|
17856
|
+
return { nudged: [], loaded: [], raw: [] };
|
|
17857
|
+
}
|
|
17858
|
+
}, writeSessionMarker = (file, marker) => {
|
|
17859
|
+
try {
|
|
17860
|
+
fs15.mkdirSync(path18.dirname(file), { recursive: true });
|
|
17861
|
+
fs15.writeFileSync(file, JSON.stringify(marker));
|
|
17862
|
+
} catch {}
|
|
17736
17863
|
};
|
|
17737
|
-
var
|
|
17738
|
-
|
|
17864
|
+
var init_session_marker = __esm(() => {
|
|
17865
|
+
init_paths();
|
|
17739
17866
|
});
|
|
17740
17867
|
|
|
17741
17868
|
// packages/workit-core/src/hooks/shell-words.ts
|
|
@@ -17859,219 +17986,12 @@ var closingQuote = (command, start, dialect) => {
|
|
|
17859
17986
|
return segments;
|
|
17860
17987
|
};
|
|
17861
17988
|
|
|
17862
|
-
// packages/workit-core/src/hooks/write-gate.ts
|
|
17863
|
-
import { realpathSync as realpathSync5 } from "node:fs";
|
|
17864
|
-
import path18 from "node:path";
|
|
17865
|
-
function writeGate(cwd, paths) {
|
|
17866
|
-
try {
|
|
17867
|
-
const gate = gateFor(cwd);
|
|
17868
|
-
if (!gate || gate.blockers.length === 0)
|
|
17869
|
-
return NONE2;
|
|
17870
|
-
if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
|
|
17871
|
-
return NONE2;
|
|
17872
|
-
const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
|
|
17873
|
-
return {
|
|
17874
|
-
kind: "deny",
|
|
17875
|
-
reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
|
|
17876
|
-
unblock: gate.blockers[0].reason
|
|
17877
|
-
};
|
|
17878
|
-
} catch {
|
|
17879
|
-
return NONE2;
|
|
17880
|
-
}
|
|
17881
|
-
}
|
|
17882
|
-
var NONE2, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
17883
|
-
let rest = "";
|
|
17884
|
-
for (let dir = path18.resolve(file);; dir = path18.dirname(dir)) {
|
|
17885
|
-
try {
|
|
17886
|
-
return path18.join(realpathSync5(dir), rest);
|
|
17887
|
-
} catch {
|
|
17888
|
-
if (path18.dirname(dir) === dir)
|
|
17889
|
-
return path18.resolve(file);
|
|
17890
|
-
rest = rest ? path18.join(path18.basename(dir), rest) : path18.basename(dir);
|
|
17891
|
-
}
|
|
17892
|
-
}
|
|
17893
|
-
}, exempt = (cwd, root, file, plans) => {
|
|
17894
|
-
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
17895
|
-
const relative = path18.relative(fold(canonical2(root)), fold(canonical2(path18.resolve(cwd, file))));
|
|
17896
|
-
if (relative.startsWith("..") || path18.isAbsolute(relative))
|
|
17897
|
-
return true;
|
|
17898
|
-
const posix = relative.split(path18.sep).join("/");
|
|
17899
|
-
const segments = posix.split("/");
|
|
17900
|
-
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
17901
|
-
}, PREFIXES2, operands = (words) => words.filter((item) => !item.startsWith("-")), editsOf = (input) => {
|
|
17902
|
-
let words = input;
|
|
17903
|
-
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
|
|
17904
|
-
words = words.slice(1);
|
|
17905
|
-
const [verb, ...args] = words;
|
|
17906
|
-
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
17907
|
-
if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
|
|
17908
|
-
return [];
|
|
17909
|
-
switch (name) {
|
|
17910
|
-
case "touch":
|
|
17911
|
-
case "mkdir":
|
|
17912
|
-
case "rm":
|
|
17913
|
-
case "rmdir":
|
|
17914
|
-
case "tee":
|
|
17915
|
-
case "mv":
|
|
17916
|
-
case "truncate":
|
|
17917
|
-
return operands(args);
|
|
17918
|
-
case "cp":
|
|
17919
|
-
case "ln":
|
|
17920
|
-
case "install": {
|
|
17921
|
-
const target = args.indexOf("-t");
|
|
17922
|
-
if (target >= 0)
|
|
17923
|
-
return args[target + 1] ? [args[target + 1]] : null;
|
|
17924
|
-
const last = operands(args).at(-1);
|
|
17925
|
-
return last ? [last] : null;
|
|
17926
|
-
}
|
|
17927
|
-
case "dd":
|
|
17928
|
-
return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
|
|
17929
|
-
case "sed":
|
|
17930
|
-
case "perl": {
|
|
17931
|
-
if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
|
|
17932
|
-
return [];
|
|
17933
|
-
const scripted = args.some((item) => item === "-e" || item === "-f");
|
|
17934
|
-
const files = [];
|
|
17935
|
-
for (let index = 0;index < args.length; index++) {
|
|
17936
|
-
if (args[index] === "-e" || args[index] === "-f")
|
|
17937
|
-
index++;
|
|
17938
|
-
else if (!args[index].startsWith("-"))
|
|
17939
|
-
files.push(args[index]);
|
|
17940
|
-
}
|
|
17941
|
-
return scripted ? files : files.slice(1);
|
|
17942
|
-
}
|
|
17943
|
-
case "patch":
|
|
17944
|
-
return null;
|
|
17945
|
-
case "set-content":
|
|
17946
|
-
case "add-content":
|
|
17947
|
-
case "out-file":
|
|
17948
|
-
case "new-item":
|
|
17949
|
-
case "ni":
|
|
17950
|
-
case "remove-item":
|
|
17951
|
-
case "del":
|
|
17952
|
-
case "copy-item":
|
|
17953
|
-
case "move-item": {
|
|
17954
|
-
const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
|
|
17955
|
-
if (flag >= 0)
|
|
17956
|
-
return args[flag + 1] ? [args[flag + 1]] : null;
|
|
17957
|
-
const files = operands(args);
|
|
17958
|
-
if (name === "move-item")
|
|
17959
|
-
return files.length ? files : null;
|
|
17960
|
-
const file = name === "copy-item" ? files.at(-1) : files[0];
|
|
17961
|
-
return file ? [file] : null;
|
|
17962
|
-
}
|
|
17963
|
-
case "git": {
|
|
17964
|
-
const sub = operands(args)[0];
|
|
17965
|
-
if (sub === "apply")
|
|
17966
|
-
return null;
|
|
17967
|
-
if (sub === "restore" || sub === "mv" || sub === "rm")
|
|
17968
|
-
return operands(args).slice(1);
|
|
17969
|
-
if (sub === "checkout" && args.includes("--"))
|
|
17970
|
-
return args.slice(args.indexOf("--") + 1);
|
|
17971
|
-
return [];
|
|
17972
|
-
}
|
|
17973
|
-
default:
|
|
17974
|
-
return [];
|
|
17975
|
-
}
|
|
17976
|
-
}, shellWrites = (command) => {
|
|
17977
|
-
const targets = [];
|
|
17978
|
-
let unknown = false;
|
|
17979
|
-
for (const segment of segmentsOf(command)) {
|
|
17980
|
-
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
17981
|
-
const edits = editsOf(segment.words);
|
|
17982
|
-
if (edits === null)
|
|
17983
|
-
unknown = true;
|
|
17984
|
-
else
|
|
17985
|
-
targets.push(...edits);
|
|
17986
|
-
}
|
|
17987
|
-
if (!unknown && targets.length === 0)
|
|
17988
|
-
return { writes: false, targets: null };
|
|
17989
|
-
return { writes: true, targets: unknown ? null : targets };
|
|
17990
|
-
}, cache2, planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path18.resolve(root, file)) ?? "-").join("\x00"), gateFor = (cwd) => {
|
|
17991
|
-
const store = new TaskStore(cwd);
|
|
17992
|
-
const listed = store.listTaskIndex();
|
|
17993
|
-
if (!listed.ok)
|
|
17994
|
-
return null;
|
|
17995
|
-
const key = store.currentKey();
|
|
17996
|
-
if (!key.ok)
|
|
17997
|
-
return null;
|
|
17998
|
-
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
17999
|
-
if (!entry)
|
|
18000
|
-
return { blockers: [], root: store.root, plans: new Set };
|
|
18001
|
-
const ledger = ledgerPath(store.root);
|
|
18002
|
-
const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
|
|
18003
|
-
const hit = cache2.get(store.root);
|
|
18004
|
-
if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
|
|
18005
|
-
return hit;
|
|
18006
|
-
const task = store.readTask(entry.id);
|
|
18007
|
-
if (!task.ok)
|
|
18008
|
-
return null;
|
|
18009
|
-
const workspace = store.readWorkspace();
|
|
18010
|
-
const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
|
|
18011
|
-
const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
|
|
18012
|
-
const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
|
|
18013
|
-
return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
|
|
18014
|
-
});
|
|
18015
|
-
const gate = {
|
|
18016
|
-
blockers,
|
|
18017
|
-
root: store.root,
|
|
18018
|
-
plans,
|
|
18019
|
-
key: cacheKey,
|
|
18020
|
-
planKey: planKeyOf(store.root, plans)
|
|
18021
|
-
};
|
|
18022
|
-
cache2.set(store.root, gate);
|
|
18023
|
-
if (cache2.size > 32)
|
|
18024
|
-
cache2.delete(cache2.keys().next().value);
|
|
18025
|
-
return gate;
|
|
18026
|
-
};
|
|
18027
|
-
var init_write_gate = __esm(() => {
|
|
18028
|
-
init_task_store();
|
|
18029
|
-
init_derive();
|
|
18030
|
-
init_task_evaluation();
|
|
18031
|
-
init_ledger();
|
|
18032
|
-
NONE2 = { kind: "none" };
|
|
18033
|
-
CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
18034
|
-
PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
18035
|
-
cache2 = new Map;
|
|
18036
|
-
});
|
|
18037
|
-
|
|
18038
|
-
// packages/workit-core/src/hooks/session-marker.ts
|
|
18039
|
-
import { createHash as createHash13 } from "node:crypto";
|
|
18040
|
-
import fs15 from "node:fs";
|
|
18041
|
-
import path19 from "node:path";
|
|
18042
|
-
var sessionMarkerFile = (input, session, store) => {
|
|
18043
|
-
let dir = store;
|
|
18044
|
-
if (dir === undefined) {
|
|
18045
|
-
const location = resolveStore(input.cwd);
|
|
18046
|
-
if (location instanceof Error || !location.shared)
|
|
18047
|
-
return null;
|
|
18048
|
-
dir = location.dir;
|
|
18049
|
-
}
|
|
18050
|
-
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
18051
|
-
return path19.join(dir, "hooks", `skills-${name}.json`);
|
|
18052
|
-
}, list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [], readSessionMarker = (file) => {
|
|
18053
|
-
try {
|
|
18054
|
-
const value = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
18055
|
-
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
18056
|
-
} catch {
|
|
18057
|
-
return { nudged: [], loaded: [], raw: [] };
|
|
18058
|
-
}
|
|
18059
|
-
}, writeSessionMarker = (file, marker) => {
|
|
18060
|
-
try {
|
|
18061
|
-
fs15.mkdirSync(path19.dirname(file), { recursive: true });
|
|
18062
|
-
fs15.writeFileSync(file, JSON.stringify(marker));
|
|
18063
|
-
} catch {}
|
|
18064
|
-
};
|
|
18065
|
-
var init_session_marker = __esm(() => {
|
|
18066
|
-
init_paths();
|
|
18067
|
-
});
|
|
18068
|
-
|
|
18069
17989
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
18070
17990
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
18071
17991
|
import { createHash as createHash14 } from "node:crypto";
|
|
18072
17992
|
import fs16 from "node:fs";
|
|
18073
17993
|
import os4 from "node:os";
|
|
18074
|
-
import
|
|
17994
|
+
import path19 from "node:path";
|
|
18075
17995
|
function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
18076
17996
|
const found = [];
|
|
18077
17997
|
let dir = null;
|
|
@@ -18181,7 +18101,7 @@ function rawAction(invocation) {
|
|
|
18181
18101
|
function rawGitPre(input, command, options = {}) {
|
|
18182
18102
|
try {
|
|
18183
18103
|
if (!mentionsRawTool(command))
|
|
18184
|
-
return
|
|
18104
|
+
return NONE2;
|
|
18185
18105
|
const dialect = dialectOf(input);
|
|
18186
18106
|
const nudges = new Map;
|
|
18187
18107
|
let store = null;
|
|
@@ -18189,25 +18109,36 @@ function rawGitPre(input, command, options = {}) {
|
|
|
18189
18109
|
store ??= repo.location.dir;
|
|
18190
18110
|
if (action.kind === "merge")
|
|
18191
18111
|
return deny(refusal(action, null));
|
|
18192
|
-
if (action.kind === "push")
|
|
18112
|
+
if (action.kind === "push" && toPushRemote(repo, action.remote)) {
|
|
18113
|
+
if (action.mirror)
|
|
18114
|
+
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")}.`);
|
|
18193
18115
|
for (const target of action.targets) {
|
|
18194
18116
|
const branch = target ?? repo.branch;
|
|
18195
|
-
if (branch && guardedBranch(repo.dir, branch)
|
|
18196
|
-
return deny(refusal(action, branch));
|
|
18117
|
+
if (branch && guardedBranch(repo.dir, branch))
|
|
18118
|
+
return deny(refusal(action, branch, repo.dir));
|
|
18197
18119
|
}
|
|
18120
|
+
const remote = action.remote ?? pushRemoteName(repo.dir, repo.branch) ?? "origin";
|
|
18121
|
+
const facts = refFacts(repo, remote);
|
|
18122
|
+
const tagDeny = tagRefusal(action, facts);
|
|
18123
|
+
if (tagDeny)
|
|
18124
|
+
return deny(tagDeny);
|
|
18125
|
+
const nameDeny = action.deletes ? null : offPolicyPush(action, repo, facts, remote);
|
|
18126
|
+
if (nameDeny)
|
|
18127
|
+
return deny(nameDeny);
|
|
18128
|
+
}
|
|
18198
18129
|
const nudge = nudgeFor(action, input.host, input.session.id);
|
|
18199
18130
|
if (nudge && !nudges.has(nudgeKind(action)))
|
|
18200
18131
|
nudges.set(nudgeKind(action), nudge);
|
|
18201
18132
|
}
|
|
18202
18133
|
if (options.nudge === false || !store || nudges.size === 0)
|
|
18203
|
-
return
|
|
18134
|
+
return NONE2;
|
|
18204
18135
|
if (workitInvocations(command, dialect).length > 0)
|
|
18205
|
-
return
|
|
18136
|
+
return NONE2;
|
|
18206
18137
|
const shown = unseenNudges(input, nudges, store);
|
|
18207
18138
|
return shown.length ? { kind: "context", text: shown.join(`
|
|
18208
|
-
`) } :
|
|
18139
|
+
`) } : NONE2;
|
|
18209
18140
|
} catch {
|
|
18210
|
-
return
|
|
18141
|
+
return NONE2;
|
|
18211
18142
|
}
|
|
18212
18143
|
}
|
|
18213
18144
|
function noteRawCommit(input, command, key) {
|
|
@@ -18219,7 +18150,7 @@ function noteRawCommit(input, command, key) {
|
|
|
18219
18150
|
if (action.kind !== "commit")
|
|
18220
18151
|
continue;
|
|
18221
18152
|
const file = markerFile(repo, input.session.id, key);
|
|
18222
|
-
fs16.mkdirSync(
|
|
18153
|
+
fs16.mkdirSync(path19.dirname(file), { recursive: true });
|
|
18223
18154
|
fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
18224
18155
|
`);
|
|
18225
18156
|
}
|
|
@@ -18246,11 +18177,11 @@ function settlePendingCommit(input) {
|
|
|
18246
18177
|
settle2(input, repo, file);
|
|
18247
18178
|
} catch {}
|
|
18248
18179
|
}
|
|
18249
|
-
var
|
|
18180
|
+
var NONE2, SHELLS, PREFIXES2, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\"), joinDir = (base, next) => {
|
|
18250
18181
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
18251
18182
|
return;
|
|
18252
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
18253
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
18183
|
+
const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
|
|
18184
|
+
return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
|
|
18254
18185
|
}, cdTarget = (words) => {
|
|
18255
18186
|
const args = words.slice(1);
|
|
18256
18187
|
const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
|
|
@@ -18265,7 +18196,7 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18265
18196
|
return words;
|
|
18266
18197
|
if (/^\w+=/.test(first))
|
|
18267
18198
|
words = words.slice(1);
|
|
18268
|
-
else if (
|
|
18199
|
+
else if (PREFIXES2.has(first)) {
|
|
18269
18200
|
words = words.slice(1);
|
|
18270
18201
|
while (words[0]?.startsWith("-"))
|
|
18271
18202
|
words = words.slice(words[0] === "-u" ? 2 : 1);
|
|
@@ -18281,6 +18212,10 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18281
18212
|
let force = false;
|
|
18282
18213
|
let lease = false;
|
|
18283
18214
|
let wholesale = false;
|
|
18215
|
+
let allTags = false;
|
|
18216
|
+
let deletes = false;
|
|
18217
|
+
let allBranches = false;
|
|
18218
|
+
let mirror = false;
|
|
18284
18219
|
let remote = null;
|
|
18285
18220
|
const positionals = [];
|
|
18286
18221
|
for (let index = 1;index < args.length; index++) {
|
|
@@ -18301,14 +18236,22 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18301
18236
|
force = true;
|
|
18302
18237
|
else if (arg.startsWith("--force-with-lease") || arg === "--force-if-includes")
|
|
18303
18238
|
lease = true;
|
|
18304
|
-
else if (
|
|
18305
|
-
|
|
18239
|
+
else if (arg === "--delete" || arg === "-d")
|
|
18240
|
+
deletes = true;
|
|
18241
|
+
else if (arg === "--tags")
|
|
18242
|
+
allTags = wholesale = true;
|
|
18243
|
+
else if (arg === "--all" || arg === "--branches")
|
|
18244
|
+
allBranches = wholesale = true;
|
|
18245
|
+
else if (arg === "--mirror")
|
|
18246
|
+
mirror = wholesale = true;
|
|
18306
18247
|
else if (!arg.startsWith("-"))
|
|
18307
18248
|
positionals.push(arg);
|
|
18308
18249
|
}
|
|
18309
18250
|
const specs = remote === null ? positionals.slice(1) : positionals;
|
|
18310
18251
|
remote ??= positionals[0] ?? null;
|
|
18311
18252
|
const targets = [];
|
|
18253
|
+
const tags = [];
|
|
18254
|
+
const deleted = [];
|
|
18312
18255
|
for (const raw of specs) {
|
|
18313
18256
|
if (raw.startsWith("+"))
|
|
18314
18257
|
force = true;
|
|
@@ -18317,16 +18260,29 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18317
18260
|
const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
|
|
18318
18261
|
if (!destination)
|
|
18319
18262
|
continue;
|
|
18320
|
-
|
|
18321
|
-
|
|
18322
|
-
|
|
18323
|
-
|
|
18324
|
-
|
|
18325
|
-
|
|
18263
|
+
const name = destination === "HEAD" ? null : destination.startsWith("refs/heads/") ? destination.slice(11) : destination.startsWith("refs/") ? undefined : destination;
|
|
18264
|
+
if (destination.startsWith("refs/tags/"))
|
|
18265
|
+
tags.push(destination.slice(10));
|
|
18266
|
+
if (name === undefined)
|
|
18267
|
+
continue;
|
|
18268
|
+
targets.push(name);
|
|
18269
|
+
if (colon === 0 && name !== null)
|
|
18270
|
+
deleted.push(name);
|
|
18326
18271
|
}
|
|
18327
18272
|
if (specs.length === 0 && !wholesale)
|
|
18328
18273
|
targets.push(null);
|
|
18329
|
-
return {
|
|
18274
|
+
return {
|
|
18275
|
+
kind: "push",
|
|
18276
|
+
targets,
|
|
18277
|
+
blindForce: force && !lease,
|
|
18278
|
+
remote,
|
|
18279
|
+
...tags.length ? { tags } : {},
|
|
18280
|
+
...allTags ? { allTags: true } : {},
|
|
18281
|
+
...deletes ? { deletes: true } : {},
|
|
18282
|
+
...deleted.length ? { deleted } : {},
|
|
18283
|
+
...allBranches ? { allBranches: true } : {},
|
|
18284
|
+
...mirror ? { mirror: true } : {}
|
|
18285
|
+
};
|
|
18330
18286
|
}, VALUE_OPTIONS, asksHelp = (invocation) => {
|
|
18331
18287
|
const { tool, args } = invocation;
|
|
18332
18288
|
if (args[0] === "help" || tool !== "git" && args[1] === "help")
|
|
@@ -18376,25 +18332,72 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18376
18332
|
if (!git || git.reftable)
|
|
18377
18333
|
return null;
|
|
18378
18334
|
try {
|
|
18379
|
-
const head = fs16.readFileSync(
|
|
18335
|
+
const head = fs16.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
18380
18336
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
18381
18337
|
return head;
|
|
18382
18338
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
18383
18339
|
if (!ref)
|
|
18384
18340
|
return null;
|
|
18385
|
-
const common =
|
|
18341
|
+
const common = path19.dirname(repo.location.dir);
|
|
18386
18342
|
try {
|
|
18387
|
-
return fs16.readFileSync(
|
|
18343
|
+
return fs16.readFileSync(path19.join(common, ref), "utf8").trim() || null;
|
|
18388
18344
|
} catch {}
|
|
18389
|
-
const packed = fs16.readFileSync(
|
|
18345
|
+
const packed = fs16.readFileSync(path19.join(common, "packed-refs"), "utf8");
|
|
18390
18346
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
18391
18347
|
} catch {
|
|
18392
18348
|
return null;
|
|
18393
18349
|
}
|
|
18394
|
-
}, quote = (text) => `\`${text}\``, refusal = (action, branch) => {
|
|
18350
|
+
}, quote = (text) => `\`${text}\``, refusal = (action, branch, dir) => {
|
|
18395
18351
|
if (action.kind === "merge")
|
|
18396
18352
|
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")}).`;
|
|
18353
|
+
const plainPush = action.kind === "push" && !action.blindForce && !action.deletes;
|
|
18354
|
+
if (dir && branch && plainPush && releaseBranch(dir, branch))
|
|
18355
|
+
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")}.`;
|
|
18397
18356
|
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")}.`;
|
|
18357
|
+
}, refFacts = (repo, remote) => {
|
|
18358
|
+
const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
|
|
18359
|
+
const listed = spawnSync11("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
|
|
18360
|
+
const remotePrefix = `refs/remotes/${remote}/`;
|
|
18361
|
+
for (const ref of (listed.stdout ?? "").split(`
|
|
18362
|
+
`)) {
|
|
18363
|
+
if (ref.startsWith("refs/heads/"))
|
|
18364
|
+
facts.heads.add(ref.slice(11));
|
|
18365
|
+
else if (ref.startsWith("refs/tags/"))
|
|
18366
|
+
facts.tags.add(ref.slice(10));
|
|
18367
|
+
else if (ref.startsWith(remotePrefix))
|
|
18368
|
+
facts.remoteHeads.add(ref.slice(remotePrefix.length));
|
|
18369
|
+
}
|
|
18370
|
+
return facts;
|
|
18371
|
+
}, isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name), tagRefusal = (action, facts) => {
|
|
18372
|
+
const tags = [
|
|
18373
|
+
...action.tags ?? [],
|
|
18374
|
+
...action.targets.filter((name) => name !== null && isTag(facts, name))
|
|
18375
|
+
];
|
|
18376
|
+
if (!tags.length && !action.allTags)
|
|
18377
|
+
return null;
|
|
18378
|
+
const named = tags.length ? tags.join(" --release ") : "v<version>";
|
|
18379
|
+
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")}).`;
|
|
18380
|
+
}, remoteHas = (repo, remote, branch) => {
|
|
18381
|
+
const listed = spawnSync11("git", ["ls-remote", "--heads", remote, `refs/heads/${branch}`], {
|
|
18382
|
+
cwd: repo.dir,
|
|
18383
|
+
encoding: "utf8",
|
|
18384
|
+
timeout: 3000,
|
|
18385
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
18386
|
+
});
|
|
18387
|
+
return listed.status !== 0 || (listed.stdout ?? "").trim() !== "";
|
|
18388
|
+
}, offPolicyPush = (action, repo, facts, remote) => {
|
|
18389
|
+
const names = action.allBranches ? [...facts.heads] : action.targets.map((target) => target ?? repo.branch);
|
|
18390
|
+
for (const branch of names) {
|
|
18391
|
+
if (!branch || action.deleted?.includes(branch) || isTag(facts, branch))
|
|
18392
|
+
continue;
|
|
18393
|
+
if (facts.remoteHeads.has(branch))
|
|
18394
|
+
continue;
|
|
18395
|
+
const check = validateBranchNameFor(repo.dir, branch);
|
|
18396
|
+
if (check.ok || check.rule !== "allowed_pattern" || remoteHas(repo, remote, branch))
|
|
18397
|
+
continue;
|
|
18398
|
+
return `workit: raw ${quote("git push")} onto ${branch}: ${check.error}`;
|
|
18399
|
+
}
|
|
18400
|
+
return null;
|
|
18398
18401
|
}, nudgeFor = (action, host, session) => {
|
|
18399
18402
|
switch (action.kind) {
|
|
18400
18403
|
case "commit": {
|
|
@@ -18419,7 +18422,7 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18419
18422
|
const action = rawAction(invocation);
|
|
18420
18423
|
if (!action)
|
|
18421
18424
|
continue;
|
|
18422
|
-
const dir =
|
|
18425
|
+
const dir = path19.resolve(cwd, invocation.dir ?? ".");
|
|
18423
18426
|
if (!repos.has(dir)) {
|
|
18424
18427
|
const repo = repoAt(dir);
|
|
18425
18428
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -18446,7 +18449,7 @@ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, i
|
|
|
18446
18449
|
raw: [...marker.raw, ...fresh.map((kind) => agent + kind)]
|
|
18447
18450
|
});
|
|
18448
18451
|
return fresh.map((kind) => nudges.get(kind));
|
|
18449
|
-
}, deny = (reason) => ({ kind: "deny", reason, unblock: null }), NEXT_COMMAND = "next", MARKER_TTL_SECONDS = 3600, markerFile = (repo, session, key) =>
|
|
18452
|
+
}, deny = (reason) => ({ kind: "deny", reason, unblock: null }), NEXT_COMMAND = "next", MARKER_TTL_SECONDS = 3600, markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`), callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`, headCommit = (dir) => {
|
|
18450
18453
|
const run = spawnSync11("git", [
|
|
18451
18454
|
"log",
|
|
18452
18455
|
"-1",
|
|
@@ -18506,9 +18509,9 @@ var init_raw_git = __esm(() => {
|
|
|
18506
18509
|
init_ledger();
|
|
18507
18510
|
init_paths();
|
|
18508
18511
|
init_session_marker();
|
|
18509
|
-
|
|
18512
|
+
NONE2 = { kind: "none" };
|
|
18510
18513
|
SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
18511
|
-
|
|
18514
|
+
PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
18512
18515
|
SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
|
|
18513
18516
|
CD = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
18514
18517
|
PUSHD = new Set(["pushd", "push-location"]);
|
|
@@ -18566,8 +18569,298 @@ var init_raw_git = __esm(() => {
|
|
|
18566
18569
|
};
|
|
18567
18570
|
});
|
|
18568
18571
|
|
|
18569
|
-
// packages/workit-core/src/
|
|
18572
|
+
// packages/workit-core/src/core/route-intent.ts
|
|
18570
18573
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
18574
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
18575
|
+
import path20 from "node:path";
|
|
18576
|
+
var UNKNOWABLE, SWITCH_CREATE, CHECKOUT_CREATE, WORKTREE_CREATE, BRANCH_NO_CREATE, BRANCH_RENAME, flagValue = (args, flags) => {
|
|
18577
|
+
for (let index = 1;index < args.length; index++) {
|
|
18578
|
+
const arg = args[index];
|
|
18579
|
+
if (arg === "--")
|
|
18580
|
+
return null;
|
|
18581
|
+
if (flags.has(arg))
|
|
18582
|
+
return args[index + 1] ?? null;
|
|
18583
|
+
const eq = arg.indexOf("=");
|
|
18584
|
+
if (arg.startsWith("--") && eq > 0 && flags.has(arg.slice(0, eq)))
|
|
18585
|
+
return arg.slice(eq + 1);
|
|
18586
|
+
if (!arg.startsWith("--") && arg.length > 2 && flags.has(arg.slice(0, 2)))
|
|
18587
|
+
return arg.slice(2);
|
|
18588
|
+
}
|
|
18589
|
+
return null;
|
|
18590
|
+
}, WORKTREE_VALUES, worktreeTarget = (args) => {
|
|
18591
|
+
const named = flagValue(args, WORKTREE_CREATE);
|
|
18592
|
+
if (named !== null)
|
|
18593
|
+
return named;
|
|
18594
|
+
const positionals = [];
|
|
18595
|
+
for (let index = 1;index < args.length; index++) {
|
|
18596
|
+
const arg = args[index];
|
|
18597
|
+
if (arg === "--detach" || arg === "-d")
|
|
18598
|
+
return null;
|
|
18599
|
+
if (WORKTREE_VALUES.has(arg))
|
|
18600
|
+
index++;
|
|
18601
|
+
else if (!arg.startsWith("-"))
|
|
18602
|
+
positionals.push(arg);
|
|
18603
|
+
}
|
|
18604
|
+
if (positionals.length !== 1)
|
|
18605
|
+
return null;
|
|
18606
|
+
const base = positionals[0].split(/[\\/]/u).findLast(Boolean) ?? "";
|
|
18607
|
+
return base && base !== "." && base !== ".." ? base : null;
|
|
18608
|
+
}, branchCommandTarget = (args) => {
|
|
18609
|
+
let rename = false;
|
|
18610
|
+
const positionals = [];
|
|
18611
|
+
for (let index = 1;index < args.length; index++) {
|
|
18612
|
+
const arg = args[index];
|
|
18613
|
+
if (arg === "--") {
|
|
18614
|
+
positionals.push(...args.slice(index + 1));
|
|
18615
|
+
break;
|
|
18616
|
+
}
|
|
18617
|
+
const flag = arg.split("=", 1)[0];
|
|
18618
|
+
if (BRANCH_NO_CREATE.has(flag))
|
|
18619
|
+
return null;
|
|
18620
|
+
if (BRANCH_RENAME.has(flag))
|
|
18621
|
+
rename = true;
|
|
18622
|
+
else if (!arg.startsWith("-"))
|
|
18623
|
+
positionals.push(arg);
|
|
18624
|
+
}
|
|
18625
|
+
if (positionals.length < 1 || positionals.length > 2)
|
|
18626
|
+
return null;
|
|
18627
|
+
return rename ? positionals.at(-1) ?? null : positionals[0];
|
|
18628
|
+
}, gitBranchCreation = (args) => {
|
|
18629
|
+
const [sub, second] = args;
|
|
18630
|
+
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;
|
|
18631
|
+
return target && !target.startsWith("-") && !UNKNOWABLE.test(target) ? target : null;
|
|
18632
|
+
}, shellBranchTargets = (command, dialect = "posix") => {
|
|
18633
|
+
if (!/\bgit\b/u.test(command))
|
|
18634
|
+
return [];
|
|
18635
|
+
const found = [];
|
|
18636
|
+
for (const invocation of rawInvocations(command, dialect)) {
|
|
18637
|
+
if (invocation.tool !== "git")
|
|
18638
|
+
continue;
|
|
18639
|
+
const branch = gitBranchCreation(invocation.args);
|
|
18640
|
+
if (branch)
|
|
18641
|
+
found.push({ branch, dir: invocation.dir });
|
|
18642
|
+
}
|
|
18643
|
+
return found;
|
|
18644
|
+
}, localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
|
|
18645
|
+
cwd: dir,
|
|
18646
|
+
stdio: "ignore",
|
|
18647
|
+
timeout: 5000
|
|
18648
|
+
}).status === 0, shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
18649
|
+
let judged = false;
|
|
18650
|
+
for (const target of shellBranchTargets(command, dialect)) {
|
|
18651
|
+
if (target.dir === undefined)
|
|
18652
|
+
continue;
|
|
18653
|
+
const dir = target.dir === null ? root : path20.resolve(root, target.dir);
|
|
18654
|
+
if (target.dir !== null && (!existsSync6(dir) || !inWorkitWorkspace(dir)))
|
|
18655
|
+
continue;
|
|
18656
|
+
if (localBranchExists(dir, target.branch))
|
|
18657
|
+
continue;
|
|
18658
|
+
judged = true;
|
|
18659
|
+
const check = validateBranchNameFor(dir, target.branch);
|
|
18660
|
+
if (!check.ok)
|
|
18661
|
+
return check;
|
|
18662
|
+
}
|
|
18663
|
+
return judged ? { ok: true } : null;
|
|
18664
|
+
};
|
|
18665
|
+
var init_route_intent = __esm(() => {
|
|
18666
|
+
init_raw_git();
|
|
18667
|
+
init_branch();
|
|
18668
|
+
UNKNOWABLE = /[$`*?[\]{}~]/u;
|
|
18669
|
+
SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
|
|
18670
|
+
CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
|
|
18671
|
+
WORKTREE_CREATE = new Set(["-b", "-B"]);
|
|
18672
|
+
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"]));
|
|
18673
|
+
BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
|
|
18674
|
+
WORKTREE_VALUES = new Set(["--reason"]);
|
|
18675
|
+
});
|
|
18676
|
+
|
|
18677
|
+
// packages/workit-core/src/hooks/policy.ts
|
|
18678
|
+
var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
18679
|
+
const policy = shellBranchPolicyViolation(cwd, command, dialect);
|
|
18680
|
+
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
18681
|
+
};
|
|
18682
|
+
var init_policy = __esm(() => {
|
|
18683
|
+
init_route_intent();
|
|
18684
|
+
});
|
|
18685
|
+
|
|
18686
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
18687
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
18688
|
+
import path21 from "node:path";
|
|
18689
|
+
function writeGate(cwd, paths) {
|
|
18690
|
+
try {
|
|
18691
|
+
const gate = gateFor(cwd);
|
|
18692
|
+
if (!gate || gate.blockers.length === 0)
|
|
18693
|
+
return NONE3;
|
|
18694
|
+
if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
|
|
18695
|
+
return NONE3;
|
|
18696
|
+
const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
|
|
18697
|
+
return {
|
|
18698
|
+
kind: "deny",
|
|
18699
|
+
reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
|
|
18700
|
+
unblock: gate.blockers[0].reason
|
|
18701
|
+
};
|
|
18702
|
+
} catch {
|
|
18703
|
+
return NONE3;
|
|
18704
|
+
}
|
|
18705
|
+
}
|
|
18706
|
+
var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
|
|
18707
|
+
let rest = "";
|
|
18708
|
+
for (let dir = path21.resolve(file);; dir = path21.dirname(dir)) {
|
|
18709
|
+
try {
|
|
18710
|
+
return path21.join(realpathSync5(dir), rest);
|
|
18711
|
+
} catch {
|
|
18712
|
+
if (path21.dirname(dir) === dir)
|
|
18713
|
+
return path21.resolve(file);
|
|
18714
|
+
rest = rest ? path21.join(path21.basename(dir), rest) : path21.basename(dir);
|
|
18715
|
+
}
|
|
18716
|
+
}
|
|
18717
|
+
}, exempt = (cwd, root, file, plans) => {
|
|
18718
|
+
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
18719
|
+
const relative = path21.relative(fold(canonical2(root)), fold(canonical2(path21.resolve(cwd, file))));
|
|
18720
|
+
if (relative.startsWith("..") || path21.isAbsolute(relative))
|
|
18721
|
+
return true;
|
|
18722
|
+
const posix = relative.split(path21.sep).join("/");
|
|
18723
|
+
const segments = posix.split("/");
|
|
18724
|
+
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
18725
|
+
}, PREFIXES3, operands = (words) => words.filter((item) => !item.startsWith("-")), editsOf = (input) => {
|
|
18726
|
+
let words = input;
|
|
18727
|
+
while (words.length && (PREFIXES3.has(words[0]) || /^\w+=/.test(words[0])))
|
|
18728
|
+
words = words.slice(1);
|
|
18729
|
+
const [verb, ...args] = words;
|
|
18730
|
+
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
18731
|
+
if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
|
|
18732
|
+
return [];
|
|
18733
|
+
switch (name) {
|
|
18734
|
+
case "touch":
|
|
18735
|
+
case "mkdir":
|
|
18736
|
+
case "rm":
|
|
18737
|
+
case "rmdir":
|
|
18738
|
+
case "tee":
|
|
18739
|
+
case "mv":
|
|
18740
|
+
case "truncate":
|
|
18741
|
+
return operands(args);
|
|
18742
|
+
case "cp":
|
|
18743
|
+
case "ln":
|
|
18744
|
+
case "install": {
|
|
18745
|
+
const target = args.indexOf("-t");
|
|
18746
|
+
if (target >= 0)
|
|
18747
|
+
return args[target + 1] ? [args[target + 1]] : null;
|
|
18748
|
+
const last = operands(args).at(-1);
|
|
18749
|
+
return last ? [last] : null;
|
|
18750
|
+
}
|
|
18751
|
+
case "dd":
|
|
18752
|
+
return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
|
|
18753
|
+
case "sed":
|
|
18754
|
+
case "perl": {
|
|
18755
|
+
if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
|
|
18756
|
+
return [];
|
|
18757
|
+
const scripted = args.some((item) => item === "-e" || item === "-f");
|
|
18758
|
+
const files = [];
|
|
18759
|
+
for (let index = 0;index < args.length; index++) {
|
|
18760
|
+
if (args[index] === "-e" || args[index] === "-f")
|
|
18761
|
+
index++;
|
|
18762
|
+
else if (!args[index].startsWith("-"))
|
|
18763
|
+
files.push(args[index]);
|
|
18764
|
+
}
|
|
18765
|
+
return scripted ? files : files.slice(1);
|
|
18766
|
+
}
|
|
18767
|
+
case "patch":
|
|
18768
|
+
return null;
|
|
18769
|
+
case "set-content":
|
|
18770
|
+
case "add-content":
|
|
18771
|
+
case "out-file":
|
|
18772
|
+
case "new-item":
|
|
18773
|
+
case "ni":
|
|
18774
|
+
case "remove-item":
|
|
18775
|
+
case "del":
|
|
18776
|
+
case "copy-item":
|
|
18777
|
+
case "move-item": {
|
|
18778
|
+
const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
|
|
18779
|
+
if (flag >= 0)
|
|
18780
|
+
return args[flag + 1] ? [args[flag + 1]] : null;
|
|
18781
|
+
const files = operands(args);
|
|
18782
|
+
if (name === "move-item")
|
|
18783
|
+
return files.length ? files : null;
|
|
18784
|
+
const file = name === "copy-item" ? files.at(-1) : files[0];
|
|
18785
|
+
return file ? [file] : null;
|
|
18786
|
+
}
|
|
18787
|
+
case "git": {
|
|
18788
|
+
const sub = operands(args)[0];
|
|
18789
|
+
if (sub === "apply")
|
|
18790
|
+
return null;
|
|
18791
|
+
if (sub === "restore" || sub === "mv" || sub === "rm")
|
|
18792
|
+
return operands(args).slice(1);
|
|
18793
|
+
if (sub === "checkout" && args.includes("--"))
|
|
18794
|
+
return args.slice(args.indexOf("--") + 1);
|
|
18795
|
+
return [];
|
|
18796
|
+
}
|
|
18797
|
+
default:
|
|
18798
|
+
return [];
|
|
18799
|
+
}
|
|
18800
|
+
}, shellWrites = (command) => {
|
|
18801
|
+
const targets = [];
|
|
18802
|
+
let unknown = false;
|
|
18803
|
+
for (const segment of segmentsOf(command)) {
|
|
18804
|
+
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
18805
|
+
const edits = editsOf(segment.words);
|
|
18806
|
+
if (edits === null)
|
|
18807
|
+
unknown = true;
|
|
18808
|
+
else
|
|
18809
|
+
targets.push(...edits);
|
|
18810
|
+
}
|
|
18811
|
+
if (!unknown && targets.length === 0)
|
|
18812
|
+
return { writes: false, targets: null };
|
|
18813
|
+
return { writes: true, targets: unknown ? null : targets };
|
|
18814
|
+
}, cache2, planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path21.resolve(root, file)) ?? "-").join("\x00"), gateFor = (cwd) => {
|
|
18815
|
+
const store = new TaskStore(cwd);
|
|
18816
|
+
const listed = store.listTaskIndex();
|
|
18817
|
+
if (!listed.ok)
|
|
18818
|
+
return null;
|
|
18819
|
+
const key = store.currentKey();
|
|
18820
|
+
if (!key.ok)
|
|
18821
|
+
return null;
|
|
18822
|
+
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
18823
|
+
if (!entry)
|
|
18824
|
+
return { blockers: [], root: store.root, plans: new Set };
|
|
18825
|
+
const ledger = ledgerPath(store.root);
|
|
18826
|
+
const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
|
|
18827
|
+
const hit = cache2.get(store.root);
|
|
18828
|
+
if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
|
|
18829
|
+
return hit;
|
|
18830
|
+
const task = store.readTask(entry.id);
|
|
18831
|
+
if (!task.ok)
|
|
18832
|
+
return null;
|
|
18833
|
+
const workspace = store.readWorkspace();
|
|
18834
|
+
const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
|
|
18835
|
+
const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
|
|
18836
|
+
const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
|
|
18837
|
+
return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
|
|
18838
|
+
});
|
|
18839
|
+
const gate = {
|
|
18840
|
+
blockers,
|
|
18841
|
+
root: store.root,
|
|
18842
|
+
plans,
|
|
18843
|
+
key: cacheKey,
|
|
18844
|
+
planKey: planKeyOf(store.root, plans)
|
|
18845
|
+
};
|
|
18846
|
+
cache2.set(store.root, gate);
|
|
18847
|
+
if (cache2.size > 32)
|
|
18848
|
+
cache2.delete(cache2.keys().next().value);
|
|
18849
|
+
return gate;
|
|
18850
|
+
};
|
|
18851
|
+
var init_write_gate = __esm(() => {
|
|
18852
|
+
init_task_store();
|
|
18853
|
+
init_derive();
|
|
18854
|
+
init_task_evaluation();
|
|
18855
|
+
init_ledger();
|
|
18856
|
+
NONE3 = { kind: "none" };
|
|
18857
|
+
CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
18858
|
+
PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
18859
|
+
cache2 = new Map;
|
|
18860
|
+
});
|
|
18861
|
+
|
|
18862
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
18863
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
18571
18864
|
function stopObligation(input, descriptor) {
|
|
18572
18865
|
try {
|
|
18573
18866
|
const session = input.session.id;
|
|
@@ -18631,7 +18924,7 @@ var NONE4, QUESTION, OFFER, YES_NO, LIST_ITEM, asksUser = (message) => {
|
|
|
18631
18924
|
`);
|
|
18632
18925
|
return QUESTION.test(tail) || OFFER.test(tail) || YES_NO.test(tail);
|
|
18633
18926
|
}, short = (sha) => (sha ?? "?").slice(0, 12), gitList = (cwd, args) => {
|
|
18634
|
-
const run =
|
|
18927
|
+
const run = spawnSync13("git", args, {
|
|
18635
18928
|
cwd,
|
|
18636
18929
|
encoding: "utf8",
|
|
18637
18930
|
timeout: 3000,
|
|
@@ -18941,10 +19234,10 @@ var init_skill_nudge = __esm(() => {
|
|
|
18941
19234
|
});
|
|
18942
19235
|
|
|
18943
19236
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
18944
|
-
import { existsSync as
|
|
18945
|
-
import
|
|
19237
|
+
import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
19238
|
+
import path22 from "node:path";
|
|
18946
19239
|
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value), nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "", optionalText = (value) => typeof value === "string" ? value : null, existingDirectory = (value) => {
|
|
18947
|
-
if (!nonEmpty2(value) || !
|
|
19240
|
+
if (!nonEmpty2(value) || !path22.isAbsolute(value) || !existsSync7(value))
|
|
18948
19241
|
return null;
|
|
18949
19242
|
try {
|
|
18950
19243
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -19040,7 +19333,7 @@ function handleHook(input, deps) {
|
|
|
19040
19333
|
if (skillRead)
|
|
19041
19334
|
recordSkillLoad(input, skillRead, "read");
|
|
19042
19335
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
19043
|
-
const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE5;
|
|
19336
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE5;
|
|
19044
19337
|
if (policy.kind !== "none")
|
|
19045
19338
|
return policy;
|
|
19046
19339
|
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
@@ -19089,18 +19382,19 @@ var usable2 = (value) => value === "native" || value === "partial", NONE5, resen
|
|
|
19089
19382
|
const prefix = descriptor.subagents.agentPrefix;
|
|
19090
19383
|
const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
|
|
19091
19384
|
return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
|
|
19092
|
-
}, subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128), CLAUDE_WORKTREE,
|
|
19385
|
+
}, subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128), CLAUDE_WORKTREE, 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\`).`, subagentStartText = (input, descriptor, event) => {
|
|
19093
19386
|
const session = input.session.id ?? null;
|
|
19094
19387
|
const role = workitRole(descriptor, event.agentType);
|
|
19095
19388
|
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
19096
19389
|
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
|
|
19390
|
+
const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
|
|
19097
19391
|
if (role === "implementer" && isolated)
|
|
19098
|
-
return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${
|
|
19392
|
+
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.`;
|
|
19099
19393
|
if (isolated)
|
|
19100
|
-
return `${
|
|
19394
|
+
return `${isolatedGit(input.cwd)}
|
|
19101
19395
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
19102
19396
|
if (role === "implementer")
|
|
19103
|
-
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>\` (
|
|
19397
|
+
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.`;
|
|
19104
19398
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
19105
19399
|
if (role !== "judge" || !event.agentId)
|
|
19106
19400
|
return readOnly;
|
|
@@ -19138,6 +19432,8 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
19138
19432
|
};
|
|
19139
19433
|
var init_handle = __esm(() => {
|
|
19140
19434
|
init_context();
|
|
19435
|
+
init_branch();
|
|
19436
|
+
init_workspaces();
|
|
19141
19437
|
init_host_session();
|
|
19142
19438
|
init_policy();
|
|
19143
19439
|
init_raw_git();
|
|
@@ -19682,7 +19978,7 @@ var init_codex = __esm(() => {
|
|
|
19682
19978
|
|
|
19683
19979
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
19684
19980
|
import fs17 from "node:fs";
|
|
19685
|
-
import
|
|
19981
|
+
import path23 from "node:path";
|
|
19686
19982
|
var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
|
|
19687
19983
|
if (!isRecord4(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
|
|
19688
19984
|
return { ok: false, error: "hook_event_name is required" };
|
|
@@ -19763,7 +20059,7 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
|
|
|
19763
20059
|
};
|
|
19764
20060
|
}
|
|
19765
20061
|
}, TRANSCRIPT_TAIL_BYTES, lastAssistantText = (file) => {
|
|
19766
|
-
if (!file || !
|
|
20062
|
+
if (!file || !path23.isAbsolute(file))
|
|
19767
20063
|
return null;
|
|
19768
20064
|
try {
|
|
19769
20065
|
const size = fs17.statSync(file).size;
|
|
@@ -19799,7 +20095,7 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
|
|
|
19799
20095
|
} catch {
|
|
19800
20096
|
return null;
|
|
19801
20097
|
}
|
|
19802
|
-
}, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
20098
|
+
}, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path23.sep) ? root : `${root}${path23.sep}`), hookCwd = (input) => {
|
|
19803
20099
|
const cwd = existingDirectory(input.cwd);
|
|
19804
20100
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
|
19805
20101
|
return cwd;
|