@brainervirus/workit-opencode 8.15.0 → 8.16.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/dist/plugin.js +706 -36
- package/package.json +2 -2
package/dist/plugin.js
CHANGED
|
@@ -12384,6 +12384,7 @@ class TaskStore {
|
|
|
12384
12384
|
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
12385
12385
|
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
12386
12386
|
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
12387
|
+
var holdsLiveState = (state, dirty) => state === "running" || dirty === true;
|
|
12387
12388
|
|
|
12388
12389
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
12389
12390
|
import { createHash as createHash10 } from "node:crypto";
|
|
@@ -12630,6 +12631,36 @@ var line = (run) => {
|
|
|
12630
12631
|
return value ? value : null;
|
|
12631
12632
|
};
|
|
12632
12633
|
var safeArg = (value) => value.length > 0 && !value.startsWith("-");
|
|
12634
|
+
var UNPARSEABLE = "<unparseable remote>";
|
|
12635
|
+
function redactRemote(raw) {
|
|
12636
|
+
const value = raw.trim();
|
|
12637
|
+
if (!value)
|
|
12638
|
+
return UNPARSEABLE;
|
|
12639
|
+
if (/^[A-Za-z][A-Za-z0-9+.-]*::/u.test(value))
|
|
12640
|
+
return UNPARSEABLE;
|
|
12641
|
+
if (/^[A-Za-z]:[\\/]/u.test(value) || value.startsWith("/") || value.startsWith("."))
|
|
12642
|
+
return value;
|
|
12643
|
+
if (!/[:@]/u.test(value))
|
|
12644
|
+
return value;
|
|
12645
|
+
if (!value.includes("://")) {
|
|
12646
|
+
if (/^[^/@]*:[^/]*@/u.test(value))
|
|
12647
|
+
return UNPARSEABLE;
|
|
12648
|
+
const scp = /^(?:([^@\s/:]+)@)?([^:/\s]+):([^?#]+)/u.exec(value);
|
|
12649
|
+
return scp ? `${scp[1] ? `${scp[1]}@` : ""}${scp[2]}:${scp[3]}` : UNPARSEABLE;
|
|
12650
|
+
}
|
|
12651
|
+
try {
|
|
12652
|
+
const url = new URL(value);
|
|
12653
|
+
const ssh = /^(?:ssh|git\+ssh|ssh\+git):$/u.test(url.protocol);
|
|
12654
|
+
url.password = "";
|
|
12655
|
+
if (!ssh)
|
|
12656
|
+
url.username = "";
|
|
12657
|
+
url.search = "";
|
|
12658
|
+
url.hash = "";
|
|
12659
|
+
return url.toString();
|
|
12660
|
+
} catch {
|
|
12661
|
+
return UNPARSEABLE;
|
|
12662
|
+
}
|
|
12663
|
+
}
|
|
12633
12664
|
function headSha(cwd) {
|
|
12634
12665
|
return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
|
|
12635
12666
|
}
|
|
@@ -12776,6 +12807,52 @@ function pushRemoteName(cwd, branch) {
|
|
|
12776
12807
|
return "origin";
|
|
12777
12808
|
return remotes.length === 1 ? remotes[0] : null;
|
|
12778
12809
|
}
|
|
12810
|
+
function pushUrl(cwd, remote) {
|
|
12811
|
+
if (!safeArg(remote))
|
|
12812
|
+
return null;
|
|
12813
|
+
const urls = git2(cwd, ["remote", "get-url", "--push", "--all", remote]).stdout.split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
|
|
12814
|
+
return urls.length === 1 ? urls[0] : null;
|
|
12815
|
+
}
|
|
12816
|
+
var cleanPath = (value) => value.replace(/^\/+/u, "").replace(/\/+$/u, "").replace(/\.git$/u, "");
|
|
12817
|
+
function parseRemoteUrl(raw) {
|
|
12818
|
+
const value = raw.trim();
|
|
12819
|
+
if (!value || /^[A-Za-z][A-Za-z0-9+.-]*::/u.test(value))
|
|
12820
|
+
return null;
|
|
12821
|
+
if (/^[A-Za-z]:[\\/]/u.test(value) || value.startsWith("/") || value.startsWith(".")) {
|
|
12822
|
+
return { protocol: "file", user: null, host: "", port: null, path: value };
|
|
12823
|
+
}
|
|
12824
|
+
if (!value.includes("://")) {
|
|
12825
|
+
if (/^[^/@]*:[^/]*@/u.test(value))
|
|
12826
|
+
return null;
|
|
12827
|
+
const scp = /^(?:([^@\s/:]+)@)?([^:/\s]+):(.+)$/u.exec(value);
|
|
12828
|
+
if (!scp)
|
|
12829
|
+
return null;
|
|
12830
|
+
return {
|
|
12831
|
+
protocol: "ssh",
|
|
12832
|
+
user: scp[1] ?? null,
|
|
12833
|
+
host: scp[2],
|
|
12834
|
+
port: null,
|
|
12835
|
+
path: cleanPath(scp[3])
|
|
12836
|
+
};
|
|
12837
|
+
}
|
|
12838
|
+
let url;
|
|
12839
|
+
try {
|
|
12840
|
+
url = new URL(value);
|
|
12841
|
+
} catch {
|
|
12842
|
+
return null;
|
|
12843
|
+
}
|
|
12844
|
+
const scheme = url.protocol.replace(/:$/u, "");
|
|
12845
|
+
const protocol = scheme === "ssh" || scheme === "git+ssh" || scheme === "ssh+git" ? "ssh" : scheme === "https" || scheme === "http" || scheme === "git" || scheme === "file" ? scheme : null;
|
|
12846
|
+
if (!protocol)
|
|
12847
|
+
return null;
|
|
12848
|
+
return {
|
|
12849
|
+
protocol,
|
|
12850
|
+
user: protocol === "ssh" && url.username ? decodeURIComponent(url.username) : null,
|
|
12851
|
+
host: url.hostname.replace(/\.$/u, ""),
|
|
12852
|
+
port: url.port || null,
|
|
12853
|
+
path: cleanPath(decodeURIComponent(url.pathname))
|
|
12854
|
+
};
|
|
12855
|
+
}
|
|
12779
12856
|
|
|
12780
12857
|
// packages/workit-core/src/core/policy/derive.ts
|
|
12781
12858
|
import { existsSync as existsSync4 } from "node:fs";
|
|
@@ -16009,6 +16086,54 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
16009
16086
|
requirementIds: evaluations.map((item) => item.requirementId)
|
|
16010
16087
|
});
|
|
16011
16088
|
}
|
|
16089
|
+
var BRIEF_TEXT = 250;
|
|
16090
|
+
var BRIEF_BLOCKERS = 3;
|
|
16091
|
+
var BRIEF_OPEN = 8;
|
|
16092
|
+
var clip = (value) => {
|
|
16093
|
+
const fits = (text) => Buffer.byteLength(JSON.stringify(text)) <= BRIEF_TEXT;
|
|
16094
|
+
if (fits(value))
|
|
16095
|
+
return value;
|
|
16096
|
+
let length = Math.min(value.length, BRIEF_TEXT);
|
|
16097
|
+
while (length > 0 && !fits(`${value.slice(0, length)}…`))
|
|
16098
|
+
length -= 1;
|
|
16099
|
+
return `${value.slice(0, length)}…`;
|
|
16100
|
+
};
|
|
16101
|
+
function boundTaskSummary(summary) {
|
|
16102
|
+
const rules = new Map((summary.policy?.requirements ?? []).map((item) => [item.id, item]));
|
|
16103
|
+
const unmet = summary.requirements.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
16104
|
+
const { progress, closure } = summary;
|
|
16105
|
+
const next = progress.nextAction;
|
|
16106
|
+
const open = unmet.slice(0, BRIEF_OPEN).map((item) => ({
|
|
16107
|
+
dimension: rules.get(item.requirementId)?.dimension ?? "verification",
|
|
16108
|
+
before: rules.get(item.requirementId)?.before ?? "close",
|
|
16109
|
+
status: item.status
|
|
16110
|
+
}));
|
|
16111
|
+
const brief = {
|
|
16112
|
+
id: summary.id,
|
|
16113
|
+
revision: summary.revision,
|
|
16114
|
+
workspaceRevision: summary.workspaceRevision,
|
|
16115
|
+
updatedAt: summary.updatedAt,
|
|
16116
|
+
objective: clip(summary.objective),
|
|
16117
|
+
status: summary.status,
|
|
16118
|
+
closure: closure && {
|
|
16119
|
+
outcome: closure.outcome,
|
|
16120
|
+
at: closure.at,
|
|
16121
|
+
summary: clip(closure.summary)
|
|
16122
|
+
},
|
|
16123
|
+
progress: {
|
|
16124
|
+
summary: clip(progress.summary),
|
|
16125
|
+
nextAction: next && clip(next),
|
|
16126
|
+
blockers: progress.blockers.slice(0, BRIEF_BLOCKERS).map((item) => ({
|
|
16127
|
+
reason: clip(item.reason),
|
|
16128
|
+
dependentAction: clip(item.dependentAction)
|
|
16129
|
+
}))
|
|
16130
|
+
},
|
|
16131
|
+
requirements: { total: summary.requirements.length, unmet: unmet.length, open },
|
|
16132
|
+
truncated: false
|
|
16133
|
+
};
|
|
16134
|
+
brief.truncated = JSON.stringify(brief).includes("…") || progress.blockers.length > BRIEF_BLOCKERS || unmet.length > BRIEF_OPEN;
|
|
16135
|
+
return brief;
|
|
16136
|
+
}
|
|
16012
16137
|
|
|
16013
16138
|
// packages/workit-core/src/core/methods.ts
|
|
16014
16139
|
var METHODS = {
|
|
@@ -16502,6 +16627,15 @@ var releaseBranchesOf = (cwd) => {
|
|
|
16502
16627
|
}
|
|
16503
16628
|
};
|
|
16504
16629
|
var releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase());
|
|
16630
|
+
var policy = (root) => resolveBranchPolicyFor(root);
|
|
16631
|
+
var isProtected = (root, name) => policy(root).protected.has(name.toLowerCase());
|
|
16632
|
+
var isProtectedTarget = (root, name) => {
|
|
16633
|
+
try {
|
|
16634
|
+
return isProtected(root, name);
|
|
16635
|
+
} catch {
|
|
16636
|
+
return true;
|
|
16637
|
+
}
|
|
16638
|
+
};
|
|
16505
16639
|
|
|
16506
16640
|
// packages/workit-core/src/hooks/session-marker.ts
|
|
16507
16641
|
import { createHash as createHash12 } from "node:crypto";
|
|
@@ -17951,7 +18085,7 @@ var rewrittenUrl = (url, config) => {
|
|
|
17951
18085
|
}
|
|
17952
18086
|
return url;
|
|
17953
18087
|
};
|
|
17954
|
-
var
|
|
18088
|
+
var pushUrl2 = (config, remote, dir) => {
|
|
17955
18089
|
const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
|
|
17956
18090
|
return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
|
|
17957
18091
|
};
|
|
@@ -17979,7 +18113,7 @@ var toPushRemote = (repo, remote, config) => {
|
|
|
17979
18113
|
const pushRemote = pushRemoteOf(config, repo.branch);
|
|
17980
18114
|
if (pushRemote === null || pushRemote === remote)
|
|
17981
18115
|
return true;
|
|
17982
|
-
return
|
|
18116
|
+
return pushUrl2(config, remote, repo.dir) === pushUrl2(config, pushRemote, repo.dir);
|
|
17983
18117
|
};
|
|
17984
18118
|
var quote = (text) => `\`${text}\``;
|
|
17985
18119
|
var refusal = (action, branch, dir) => {
|
|
@@ -19428,6 +19562,15 @@ class WorkitCore {
|
|
|
19428
19562
|
if (!parsed.ok)
|
|
19429
19563
|
return parsed;
|
|
19430
19564
|
const input = parsed.data;
|
|
19565
|
+
if (this.context.readOnlyChild) {
|
|
19566
|
+
if (input.action !== "inspect")
|
|
19567
|
+
return failure2("permission_denied", "child sessions can only inspect the task summary");
|
|
19568
|
+
const task = this.store.readTask(input.taskId);
|
|
19569
|
+
if (!task.ok)
|
|
19570
|
+
return task;
|
|
19571
|
+
const summary = this.summary(task.data);
|
|
19572
|
+
return summary.ok ? { ...summary, data: boundTaskSummary(summary.data) } : summary;
|
|
19573
|
+
}
|
|
19431
19574
|
if ((this.context.workerId ?? null) !== null) {
|
|
19432
19575
|
if (input.action !== "inspect" && input.action !== "list")
|
|
19433
19576
|
return failure2("permission_denied", "helpers cannot control task lifecycle or scope");
|
|
@@ -21011,6 +21154,7 @@ import { spawnSync as spawnSync15 } from "node:child_process";
|
|
|
21011
21154
|
import fs16 from "node:fs";
|
|
21012
21155
|
import path22 from "node:path";
|
|
21013
21156
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
21157
|
+
import { pathToFileURL } from "node:url";
|
|
21014
21158
|
// packages/workit-core/src/forge/pr-body.ts
|
|
21015
21159
|
function parseGhIssue(value) {
|
|
21016
21160
|
const m = /issues\/(\d+)/.exec(value);
|
|
@@ -21044,6 +21188,39 @@ var pushRemote = (cwd) => {
|
|
|
21044
21188
|
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
21045
21189
|
return urls.length === 1 ? urls[0] : null;
|
|
21046
21190
|
};
|
|
21191
|
+
var safePushUrl = (remote) => {
|
|
21192
|
+
const raw = remote.trim();
|
|
21193
|
+
if (path22.isAbsolute(raw))
|
|
21194
|
+
return pathToFileURL(raw).href;
|
|
21195
|
+
if (!raw.includes("://")) {
|
|
21196
|
+
const scp = /^(?:([^@\s]+)@)?([^:]+):(.+)$/u.exec(raw);
|
|
21197
|
+
if (scp)
|
|
21198
|
+
return `${scp[1] || "git"}@${scp[2]}:${scp[3]}`;
|
|
21199
|
+
}
|
|
21200
|
+
try {
|
|
21201
|
+
const url = new URL(raw);
|
|
21202
|
+
if (!["file:", "git:", "http:", "https:", "ssh:"].includes(url.protocol))
|
|
21203
|
+
return null;
|
|
21204
|
+
url.password = "";
|
|
21205
|
+
if (url.protocol !== "ssh:")
|
|
21206
|
+
url.username = "";
|
|
21207
|
+
url.search = "";
|
|
21208
|
+
url.hash = "";
|
|
21209
|
+
return url.toString();
|
|
21210
|
+
} catch {
|
|
21211
|
+
return null;
|
|
21212
|
+
}
|
|
21213
|
+
};
|
|
21214
|
+
var pushTargetIsStable = (cwd, remote) => {
|
|
21215
|
+
const target = safePushUrl(remote);
|
|
21216
|
+
if (!target)
|
|
21217
|
+
return false;
|
|
21218
|
+
const result = spawnSync14("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
|
|
21219
|
+
if (result.status !== 0 && result.status !== 1)
|
|
21220
|
+
return false;
|
|
21221
|
+
const prefixes = (result.stdout ?? "").split(/\r?\n/u).map((line) => line.trim().slice(line.trim().indexOf(" ") + 1)).filter(Boolean);
|
|
21222
|
+
return !prefixes.some((prefix) => target.startsWith(prefix));
|
|
21223
|
+
};
|
|
21047
21224
|
|
|
21048
21225
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
21049
21226
|
var repoPathFromRemote = (remote) => {
|
|
@@ -23496,10 +23673,494 @@ var workerCoordinatorFor = (task, worker) => {
|
|
|
23496
23673
|
return coordinator?.kind === "host" && coordinator.host === "opencode" ? coordinator.handle : null;
|
|
23497
23674
|
};
|
|
23498
23675
|
|
|
23676
|
+
// packages/workit-core/src/fanout.ts
|
|
23677
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
23678
|
+
import fs21 from "node:fs";
|
|
23679
|
+
import path37 from "node:path";
|
|
23680
|
+
|
|
23681
|
+
// packages/workit-core/src/stack.ts
|
|
23682
|
+
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
23683
|
+
import { createHash as createHash15, randomBytes as randomBytes4 } from "node:crypto";
|
|
23684
|
+
import fs20 from "node:fs";
|
|
23685
|
+
import path36 from "node:path";
|
|
23686
|
+
|
|
23687
|
+
// packages/workit-core/src/git/ops.ts
|
|
23688
|
+
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
23689
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
|
|
23690
|
+
import path35 from "node:path";
|
|
23691
|
+
|
|
23692
|
+
// packages/workit-core/src/forge/redact.ts
|
|
23693
|
+
var KEY = String.raw`[A-Za-z0-9_.-]*?(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|account[_-]?key|private[_-]?key|client[_-]?secret|credentials?|authorization|cookie|signature)[A-Za-z0-9_.-]*`;
|
|
23694
|
+
var ASSIGNMENT2 = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
|
|
23695
|
+
var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
|
|
23696
|
+
var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
|
|
23697
|
+
|
|
23698
|
+
// packages/workit-core/src/forge/types.ts
|
|
23699
|
+
var success2 = (data) => ({ ok: true, data });
|
|
23700
|
+
var failure3 = (code, error, unblock) => ({ ok: false, code, error, ...unblock ? { unblock } : {} });
|
|
23701
|
+
|
|
23702
|
+
// packages/workit-core/src/git/ops.ts
|
|
23703
|
+
var COMMIT_TIMEOUT_MS = 10 * 60000;
|
|
23704
|
+
var git5 = (cwd, args, options = {}) => {
|
|
23705
|
+
const result = spawnSync20("git", args, {
|
|
23706
|
+
cwd,
|
|
23707
|
+
encoding: "utf8",
|
|
23708
|
+
env: { ...options.env ?? process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
23709
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
23710
|
+
timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
|
|
23711
|
+
killSignal: "SIGKILL",
|
|
23712
|
+
windowsHide: true,
|
|
23713
|
+
...options.input === undefined ? { stdio: ["ignore", "pipe", "pipe"] } : { input: options.input, stdio: ["pipe", "pipe", "pipe"] }
|
|
23714
|
+
});
|
|
23715
|
+
return {
|
|
23716
|
+
ok: result.status === 0,
|
|
23717
|
+
status: result.status,
|
|
23718
|
+
stdout: result.stdout ?? "",
|
|
23719
|
+
stderr: result.stderr ?? ""
|
|
23720
|
+
};
|
|
23721
|
+
};
|
|
23722
|
+
function operationInProgress(cwd) {
|
|
23723
|
+
const markers = [
|
|
23724
|
+
["rebase-merge", "rebase"],
|
|
23725
|
+
["rebase-apply", "rebase"],
|
|
23726
|
+
["MERGE_HEAD", "merge"],
|
|
23727
|
+
["CHERRY_PICK_HEAD", "cherry-pick"],
|
|
23728
|
+
["REVERT_HEAD", "revert"]
|
|
23729
|
+
];
|
|
23730
|
+
const run = git5(cwd, ["rev-parse", ...markers.flatMap(([name]) => ["--git-path", name])]);
|
|
23731
|
+
if (!run.ok)
|
|
23732
|
+
return null;
|
|
23733
|
+
const found = run.stdout.split(`
|
|
23734
|
+
`).map((line) => path35.resolve(cwd, line.trim()));
|
|
23735
|
+
const conflicted = () => git5(cwd, ["ls-files", "-u"]).stdout.trim() !== "";
|
|
23736
|
+
for (const [index, [, operation]] of markers.entries()) {
|
|
23737
|
+
const marker = found[index];
|
|
23738
|
+
if (!marker || !existsSync11(marker))
|
|
23739
|
+
continue;
|
|
23740
|
+
if (index === 1 && existsSync11(path35.join(marker, "applying")))
|
|
23741
|
+
return { operation: "am", commitAllowed: false, branch: null };
|
|
23742
|
+
if (index === 0 && existsSync11(path35.join(marker, "amend")) && !conflicted()) {
|
|
23743
|
+
let headName = "";
|
|
23744
|
+
try {
|
|
23745
|
+
headName = readFileSync9(path35.join(marker, "head-name"), "utf8").trim();
|
|
23746
|
+
} catch {}
|
|
23747
|
+
const branch = headName.startsWith("refs/heads/") ? headName.slice("refs/heads/".length) : null;
|
|
23748
|
+
return { operation, commitAllowed: branch !== null, branch };
|
|
23749
|
+
}
|
|
23750
|
+
if ((operation === "revert" || operation === "merge") && !conflicted())
|
|
23751
|
+
return { operation, commitAllowed: true, branch: null };
|
|
23752
|
+
return { operation, commitAllowed: false, branch: null };
|
|
23753
|
+
}
|
|
23754
|
+
return null;
|
|
23755
|
+
}
|
|
23756
|
+
function inProgressFailure(operation, verb) {
|
|
23757
|
+
const command = operation === "am" ? "git am" : `git ${operation}`;
|
|
23758
|
+
return failure3("blocked", `${operation.replace("-", "_")}_in_progress: a ${operation === "am" ? "git am" : operation} is in progress; finish or abort it before workit can ${verb}`, `resolve the conflicts, git add <files>, then ${command} --continue # or ${command} --abort to return to where you started`);
|
|
23759
|
+
}
|
|
23760
|
+
function pushPreflight(cwd, options = {}) {
|
|
23761
|
+
const progress = operationInProgress(cwd);
|
|
23762
|
+
if (progress)
|
|
23763
|
+
return inProgressFailure(progress.operation, "push");
|
|
23764
|
+
const branch = options.branch ?? currentBranch(cwd);
|
|
23765
|
+
if (!branch)
|
|
23766
|
+
return failure3("invalid_input", "HEAD is detached; push a branch");
|
|
23767
|
+
if (isProtectedTarget(cwd, branch))
|
|
23768
|
+
return failure3("blocked", `protected_branch: ${branch} is protected by the workspace branch policy; workit git push never pushes to it`, releaseBranch(cwd, branch) ? `workit git push --release ${branch} # finishing a release or hotfix; needs the release grant` : "workit git branch <feature/name> # push a feature branch and open a PR");
|
|
23769
|
+
const sha = resolveRef(cwd, `refs/heads/${branch}`);
|
|
23770
|
+
if (!sha)
|
|
23771
|
+
return failure3("not_found", `branch ${branch} has no commit to push`);
|
|
23772
|
+
const remote = pushRemoteName(cwd, branch);
|
|
23773
|
+
if (!remote)
|
|
23774
|
+
return failure3("not_found", "no push remote is configured for this branch", "git remote add origin <url>");
|
|
23775
|
+
const raw = pushUrl(cwd, remote);
|
|
23776
|
+
if (!raw)
|
|
23777
|
+
return failure3("blocked", `remote "${remote}" has no single push URL`, `git remote get-url --push --all ${remote} # keep exactly one push URL`);
|
|
23778
|
+
const parsed = parseRemoteUrl(raw);
|
|
23779
|
+
const local = parsed?.protocol === "file";
|
|
23780
|
+
if (!local && !pushTargetIsStable(cwd, raw))
|
|
23781
|
+
return failure3("blocked", `push_target_rewritten: the push URL of "${remote}" is rewritten by a url.*.insteadOf rule, so the destination is not the one checked`, "git config --get-regexp '^url\\.' # remove the rewrite for this remote");
|
|
23782
|
+
return success2({
|
|
23783
|
+
branch,
|
|
23784
|
+
sha,
|
|
23785
|
+
remote,
|
|
23786
|
+
url: redactRemote(raw),
|
|
23787
|
+
rawUrl: raw,
|
|
23788
|
+
local
|
|
23789
|
+
});
|
|
23790
|
+
}
|
|
23791
|
+
|
|
23792
|
+
// packages/workit-core/src/forge/report.ts
|
|
23793
|
+
var GITLAB_MERGEABLE = new Set(["mergeable"]);
|
|
23794
|
+
var GITHUB_MERGEABLE = new Set(["clean", "unstable", "has_hooks"]);
|
|
23795
|
+
|
|
23796
|
+
// packages/workit-core/src/stack.ts
|
|
23797
|
+
var stackFail = (code, error, unblock, data) => ({
|
|
23798
|
+
ok: false,
|
|
23799
|
+
code,
|
|
23800
|
+
error,
|
|
23801
|
+
...unblock ? { unblock } : {},
|
|
23802
|
+
...data ? { data } : {}
|
|
23803
|
+
});
|
|
23804
|
+
var stackDeps = {
|
|
23805
|
+
pushPlan: (cwd, branch) => pushPreflight(cwd, { branch }),
|
|
23806
|
+
adapter: { name: "git", restack: (cwd, input) => gitRestack(cwd, input) },
|
|
23807
|
+
rebaseTimeoutMs: Number(process.env.WORKIT_STACK_REBASE_TIMEOUT_MS) || 10 * 60000
|
|
23808
|
+
};
|
|
23809
|
+
var gitRun3 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
23810
|
+
const run = spawnSync21("git", args, {
|
|
23811
|
+
cwd,
|
|
23812
|
+
encoding: "utf8",
|
|
23813
|
+
timeout: timeoutMs,
|
|
23814
|
+
killSignal: "SIGKILL",
|
|
23815
|
+
windowsHide: true,
|
|
23816
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
23817
|
+
env: {
|
|
23818
|
+
...process.env,
|
|
23819
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
23820
|
+
GIT_EDITOR: "true",
|
|
23821
|
+
GIT_SEQUENCE_EDITOR: "true",
|
|
23822
|
+
LC_ALL: "C",
|
|
23823
|
+
LANGUAGE: "C"
|
|
23824
|
+
}
|
|
23825
|
+
});
|
|
23826
|
+
const failure = run.error;
|
|
23827
|
+
return {
|
|
23828
|
+
ok: run.status === 0,
|
|
23829
|
+
stdout: run.stdout ?? "",
|
|
23830
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? "",
|
|
23831
|
+
timedOut: failure?.code === "ETIMEDOUT"
|
|
23832
|
+
};
|
|
23833
|
+
};
|
|
23834
|
+
var branchTip = (cwd, branch) => resolveRef(cwd, `refs/heads/${branch}`);
|
|
23835
|
+
function worktreeOf(cwd, branch) {
|
|
23836
|
+
const list = gitRun3(cwd, ["worktree", "list", "--porcelain"]);
|
|
23837
|
+
if (!list.ok)
|
|
23838
|
+
return null;
|
|
23839
|
+
let current = null;
|
|
23840
|
+
for (const line of list.stdout.split(`
|
|
23841
|
+
`)) {
|
|
23842
|
+
if (line.startsWith("worktree "))
|
|
23843
|
+
current = line.slice("worktree ".length);
|
|
23844
|
+
else if (line === `branch refs/heads/${branch}` && current)
|
|
23845
|
+
return current;
|
|
23846
|
+
else if (line === "detached" && current && rebasingBranch2(current) === `refs/heads/${branch}`)
|
|
23847
|
+
return current;
|
|
23848
|
+
}
|
|
23849
|
+
return null;
|
|
23850
|
+
}
|
|
23851
|
+
function rebasingBranch2(dir) {
|
|
23852
|
+
for (const name of ["rebase-merge/head-name", "rebase-apply/head-name"]) {
|
|
23853
|
+
const found = gitRun3(dir, ["rev-parse", "--path-format=absolute", "--git-path", name]);
|
|
23854
|
+
if (!found.ok)
|
|
23855
|
+
continue;
|
|
23856
|
+
try {
|
|
23857
|
+
return fs20.readFileSync(found.stdout.trim(), "utf8").trim();
|
|
23858
|
+
} catch {}
|
|
23859
|
+
}
|
|
23860
|
+
return null;
|
|
23861
|
+
}
|
|
23862
|
+
function rebaseInProgress(dir) {
|
|
23863
|
+
for (const name of ["rebase-merge", "rebase-apply"]) {
|
|
23864
|
+
const found = gitRun3(dir, ["rev-parse", "--path-format=absolute", "--git-path", name]);
|
|
23865
|
+
if (found.ok && fs20.existsSync(found.stdout.trim()))
|
|
23866
|
+
return true;
|
|
23867
|
+
}
|
|
23868
|
+
return false;
|
|
23869
|
+
}
|
|
23870
|
+
var scratchDir = (cwd) => {
|
|
23871
|
+
const dir = stacksDir(cwd);
|
|
23872
|
+
return dir.ok ? path36.join(dir.data, "worktrees") : null;
|
|
23873
|
+
};
|
|
23874
|
+
function gitRestack(cwd, input) {
|
|
23875
|
+
const owner = worktreeOf(cwd, input.branch);
|
|
23876
|
+
let dir = owner;
|
|
23877
|
+
if (!dir) {
|
|
23878
|
+
const scratch = scratchDir(cwd);
|
|
23879
|
+
if (!scratch)
|
|
23880
|
+
return { ok: false, conflict: false, worktree: cwd, error: "no store root" };
|
|
23881
|
+
fs20.mkdirSync(scratch, { recursive: true });
|
|
23882
|
+
dir = path36.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${randomBytes4(4).toString("hex")}`);
|
|
23883
|
+
const added = gitRun3(cwd, ["worktree", "add", "-q", dir, input.branch], GIT_TIMEOUTS.worktree);
|
|
23884
|
+
if (!added.ok)
|
|
23885
|
+
return { ok: false, conflict: false, worktree: cwd, error: firstLine(added) };
|
|
23886
|
+
}
|
|
23887
|
+
const run = gitRun3(dir, [
|
|
23888
|
+
"-c",
|
|
23889
|
+
"rebase.autoStash=false",
|
|
23890
|
+
"-c",
|
|
23891
|
+
"rebase.updateRefs=false",
|
|
23892
|
+
"rebase",
|
|
23893
|
+
"--no-autosquash",
|
|
23894
|
+
"--onto",
|
|
23895
|
+
input.onto,
|
|
23896
|
+
input.from
|
|
23897
|
+
], stackDeps.rebaseTimeoutMs);
|
|
23898
|
+
if (!run.ok) {
|
|
23899
|
+
if (run.timedOut)
|
|
23900
|
+
return {
|
|
23901
|
+
ok: false,
|
|
23902
|
+
conflict: false,
|
|
23903
|
+
timedOut: true,
|
|
23904
|
+
worktree: dir,
|
|
23905
|
+
error: `git rebase did not finish within ${stackDeps.rebaseTimeoutMs} ms`
|
|
23906
|
+
};
|
|
23907
|
+
if (rebaseInProgress(dir))
|
|
23908
|
+
return { ok: false, conflict: true, worktree: dir, error: firstLine(run) };
|
|
23909
|
+
if (!owner)
|
|
23910
|
+
gitRun3(cwd, ["worktree", "remove", "--force", dir]);
|
|
23911
|
+
return { ok: false, conflict: false, worktree: dir, error: firstLine(run) };
|
|
23912
|
+
}
|
|
23913
|
+
if (!owner)
|
|
23914
|
+
gitRun3(cwd, ["worktree", "remove", "--force", dir]);
|
|
23915
|
+
const head = branchTip(cwd, input.branch);
|
|
23916
|
+
return head ? { ok: true, head } : { ok: false, conflict: false, worktree: dir, error: `${input.branch} vanished` };
|
|
23917
|
+
}
|
|
23918
|
+
var firstLine = (run) => (`${run.stderr}
|
|
23919
|
+
${run.stdout}`.split(`
|
|
23920
|
+
`).find((line) => line.trim()) ?? "git failed").trim().slice(0, 300);
|
|
23921
|
+
var stackFileName = (name) => `${name.replace(/[^A-Za-z0-9._-]+/gu, "_").replace(/^[._]+/u, "").slice(0, 48).toLowerCase()}-${createHash15("sha256").update(name).digest("hex").slice(0, 12)}.json`;
|
|
23922
|
+
function stacksDir(cwd) {
|
|
23923
|
+
const root = storeRoot(cwd);
|
|
23924
|
+
if (!root.ok)
|
|
23925
|
+
return stackFail(root.code, root.error, root.unblock);
|
|
23926
|
+
return { ok: true, data: path36.join(root.value.root, "stacks") };
|
|
23927
|
+
}
|
|
23928
|
+
var HEARTBEAT_STALE_MS = 2 * 60000;
|
|
23929
|
+
|
|
23930
|
+
// packages/workit-core/src/fanout.ts
|
|
23931
|
+
var FANOUT_VERSION = 1;
|
|
23932
|
+
var TIERS = ["mundane", "standard", "hard"];
|
|
23933
|
+
var fanoutFail = (code, error, unblock, data) => ({
|
|
23934
|
+
ok: false,
|
|
23935
|
+
code,
|
|
23936
|
+
error,
|
|
23937
|
+
...unblock ? { unblock } : {},
|
|
23938
|
+
...data ? { data } : {}
|
|
23939
|
+
});
|
|
23940
|
+
var compiled = new Map;
|
|
23941
|
+
function normalizePattern(raw) {
|
|
23942
|
+
let pattern = raw.trim();
|
|
23943
|
+
if (pattern.includes("\\")) {
|
|
23944
|
+
if (/\\[*?{}]/u.test(pattern))
|
|
23945
|
+
return {
|
|
23946
|
+
error: "has \\*, \\?, \\{ or \\}: only \\[ and \\] are escapes; use / as the separator (src/**/*.ts)"
|
|
23947
|
+
};
|
|
23948
|
+
const brackets = /\\[[\]]/u.test(pattern);
|
|
23949
|
+
if (brackets && pattern.replace(/\\[[\]]/gu, "").includes("\\"))
|
|
23950
|
+
return {
|
|
23951
|
+
error: "mixes \\[ or \\] escapes with other backslashes, which is ambiguous: use / as the separator (app/\\[id\\]/page.tsx)"
|
|
23952
|
+
};
|
|
23953
|
+
if (!brackets)
|
|
23954
|
+
pattern = pattern.replaceAll("\\", "/");
|
|
23955
|
+
}
|
|
23956
|
+
while (pattern.startsWith("./"))
|
|
23957
|
+
pattern = pattern.slice(2);
|
|
23958
|
+
if (pattern === "." || pattern === "")
|
|
23959
|
+
pattern = raw.trim() ? "**" : "";
|
|
23960
|
+
if (pattern.endsWith("/"))
|
|
23961
|
+
pattern = `${pattern}**`;
|
|
23962
|
+
if (!pattern)
|
|
23963
|
+
return { error: "is empty" };
|
|
23964
|
+
if (pattern.startsWith("/") || /^[A-Za-z]:/u.test(pattern))
|
|
23965
|
+
return { error: "must be relative to the repository root" };
|
|
23966
|
+
if (pattern.split("/").includes(".."))
|
|
23967
|
+
return { error: "must not contain .." };
|
|
23968
|
+
const bare = pattern.replace(/\\./gu, "");
|
|
23969
|
+
if (/[[\]]/u.test(bare))
|
|
23970
|
+
return {
|
|
23971
|
+
error: 'has an unescaped [ or ]: write \\[ and \\] for literal brackets (app/\\[id\\]/page.tsx; in JSON "app/\\\\[id\\\\]/page.tsx")'
|
|
23972
|
+
};
|
|
23973
|
+
if (bare.startsWith("!"))
|
|
23974
|
+
return { error: "uses ! (negation is not supported)" };
|
|
23975
|
+
let depth = 0;
|
|
23976
|
+
for (const char of bare) {
|
|
23977
|
+
if (char === "{")
|
|
23978
|
+
depth += 1;
|
|
23979
|
+
if (char === "}")
|
|
23980
|
+
depth -= 1;
|
|
23981
|
+
if (depth < 0 || depth > 1)
|
|
23982
|
+
return { error: "has unbalanced or nested braces" };
|
|
23983
|
+
}
|
|
23984
|
+
if (depth !== 0)
|
|
23985
|
+
return { error: "has unbalanced or nested braces" };
|
|
23986
|
+
return { pattern };
|
|
23987
|
+
}
|
|
23988
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23989
|
+
function findCycle(slices) {
|
|
23990
|
+
const byId = new Map(slices.map((slice) => [slice.id, slice]));
|
|
23991
|
+
const state = new Map;
|
|
23992
|
+
const trail = [];
|
|
23993
|
+
const visit = (id) => {
|
|
23994
|
+
if (state.get(id) === "done")
|
|
23995
|
+
return null;
|
|
23996
|
+
if (state.get(id) === "open")
|
|
23997
|
+
return [...trail.slice(trail.indexOf(id)), id];
|
|
23998
|
+
state.set(id, "open");
|
|
23999
|
+
trail.push(id);
|
|
24000
|
+
for (const dep of byId.get(id)?.dependsOn ?? []) {
|
|
24001
|
+
if (!byId.has(dep) || dep === id)
|
|
24002
|
+
continue;
|
|
24003
|
+
const found = visit(dep);
|
|
24004
|
+
if (found)
|
|
24005
|
+
return found;
|
|
24006
|
+
}
|
|
24007
|
+
trail.pop();
|
|
24008
|
+
state.set(id, "done");
|
|
24009
|
+
return null;
|
|
24010
|
+
};
|
|
24011
|
+
for (const slice of slices) {
|
|
24012
|
+
const found = visit(slice.id);
|
|
24013
|
+
if (found)
|
|
24014
|
+
return found;
|
|
24015
|
+
}
|
|
24016
|
+
return null;
|
|
24017
|
+
}
|
|
24018
|
+
function sliceHash(slice) {
|
|
24019
|
+
const {
|
|
24020
|
+
worktree: _worktree,
|
|
24021
|
+
hash: _hash,
|
|
24022
|
+
hashSince: _since,
|
|
24023
|
+
tier: _tier,
|
|
24024
|
+
timebox: _timebox,
|
|
24025
|
+
context: _context,
|
|
24026
|
+
issues: _issues,
|
|
24027
|
+
...definition
|
|
24028
|
+
} = slice;
|
|
24029
|
+
return createHash16("sha256").update(JSON.stringify(definition)).digest("hex").slice(0, 16);
|
|
24030
|
+
}
|
|
24031
|
+
function fanoutsDir(cwd) {
|
|
24032
|
+
const root = storeRoot(cwd);
|
|
24033
|
+
if (!root.ok)
|
|
24034
|
+
return fanoutFail(root.code, root.error, root.unblock);
|
|
24035
|
+
return { ok: true, data: path37.join(root.value.root, "fanouts") };
|
|
24036
|
+
}
|
|
24037
|
+
function parseStored(raw) {
|
|
24038
|
+
if (!isRecord5(raw) || typeof raw.name !== "string" || typeof raw.trunk !== "string")
|
|
24039
|
+
return null;
|
|
24040
|
+
if (!Array.isArray(raw.slices))
|
|
24041
|
+
return null;
|
|
24042
|
+
const list = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
24043
|
+
const text = (value) => typeof value === "string" ? value : "";
|
|
24044
|
+
let globsValid = true;
|
|
24045
|
+
const globs = (value) => list(value).map((entry) => {
|
|
24046
|
+
const normalized = normalizePattern(entry);
|
|
24047
|
+
if ("error" in normalized) {
|
|
24048
|
+
globsValid = false;
|
|
24049
|
+
return entry;
|
|
24050
|
+
}
|
|
24051
|
+
return normalized.pattern;
|
|
24052
|
+
});
|
|
24053
|
+
const slices = [];
|
|
24054
|
+
for (const item of raw.slices) {
|
|
24055
|
+
if (!isRecord5(item) || typeof item.id !== "string" || typeof item.branch !== "string")
|
|
24056
|
+
return null;
|
|
24057
|
+
slices.push({
|
|
24058
|
+
id: item.id,
|
|
24059
|
+
branch: item.branch,
|
|
24060
|
+
base: text(item.base) || raw.trunk,
|
|
24061
|
+
worktree: text(item.worktree),
|
|
24062
|
+
tier: TIERS.includes(item.tier) ? item.tier : "standard",
|
|
24063
|
+
dependsOn: list(item.dependsOn),
|
|
24064
|
+
scope: globs(item.scope),
|
|
24065
|
+
owns: globs(item.owns),
|
|
24066
|
+
goal: text(item.goal),
|
|
24067
|
+
acceptance: list(item.acceptance),
|
|
24068
|
+
verify: list(item.verify),
|
|
24069
|
+
forbidden: list(item.forbidden),
|
|
24070
|
+
context: typeof item.context === "string" ? item.context : null,
|
|
24071
|
+
timebox: typeof item.timebox === "string" ? item.timebox : null,
|
|
24072
|
+
issues: list(item.issues),
|
|
24073
|
+
hash: text(item.hash),
|
|
24074
|
+
hashSince: text(item.hashSince) || text(raw.createdAt)
|
|
24075
|
+
});
|
|
24076
|
+
}
|
|
24077
|
+
for (const slice of slices)
|
|
24078
|
+
slice.hash ||= sliceHash(slice);
|
|
24079
|
+
const shared = globs(raw.shared);
|
|
24080
|
+
const ids = new Set(slices.map((slice) => slice.id));
|
|
24081
|
+
if (!globsValid || ids.size !== slices.length || slices.some((slice) => slice.dependsOn.some((dep) => dep === slice.id || !ids.has(dep))) || findCycle(slices))
|
|
24082
|
+
return null;
|
|
24083
|
+
const fanIn = raw.fanIn === "integration" ? "integration" : "prs";
|
|
24084
|
+
const createdAt = text(raw.createdAt);
|
|
24085
|
+
return {
|
|
24086
|
+
v: typeof raw.v === "number" ? raw.v : FANOUT_VERSION,
|
|
24087
|
+
name: raw.name,
|
|
24088
|
+
trunk: raw.trunk,
|
|
24089
|
+
fanIn,
|
|
24090
|
+
shared,
|
|
24091
|
+
slices,
|
|
24092
|
+
leadSession: typeof raw.leadSession === "string" && raw.leadSession ? raw.leadSession : null,
|
|
24093
|
+
formerLeads: list(raw.formerLeads),
|
|
24094
|
+
createdAt,
|
|
24095
|
+
updatedAt: text(raw.updatedAt)
|
|
24096
|
+
};
|
|
24097
|
+
}
|
|
24098
|
+
function readFanout(cwd, name) {
|
|
24099
|
+
const dir = fanoutsDir(cwd);
|
|
24100
|
+
if (!dir.ok)
|
|
24101
|
+
return dir;
|
|
24102
|
+
const file = path37.join(dir.data, stackFileName(name));
|
|
24103
|
+
let parsed = null;
|
|
24104
|
+
try {
|
|
24105
|
+
parsed = parseStored(JSON.parse(fs21.readFileSync(file, "utf8")));
|
|
24106
|
+
} catch (error) {
|
|
24107
|
+
if (error.code === "ENOENT")
|
|
24108
|
+
return { ok: true, data: null };
|
|
24109
|
+
parsed = null;
|
|
24110
|
+
}
|
|
24111
|
+
return parsed ? { ok: true, data: parsed } : fanoutFail("failed", `fanout file ${file} is not a valid plan`, `workit fanout plan <plan.json> --name ${name} # rewrite it`);
|
|
24112
|
+
}
|
|
24113
|
+
function listFanouts(cwd) {
|
|
24114
|
+
const dir = fanoutsDir(cwd);
|
|
24115
|
+
if (!dir.ok)
|
|
24116
|
+
return dir;
|
|
24117
|
+
let names = [];
|
|
24118
|
+
try {
|
|
24119
|
+
names = fs21.readdirSync(dir.data).filter((entry) => entry.endsWith(".json"));
|
|
24120
|
+
} catch {
|
|
24121
|
+
return { ok: true, data: [] };
|
|
24122
|
+
}
|
|
24123
|
+
const out = [];
|
|
24124
|
+
for (const entry of names) {
|
|
24125
|
+
let parsed = null;
|
|
24126
|
+
try {
|
|
24127
|
+
parsed = parseStored(JSON.parse(fs21.readFileSync(path37.join(dir.data, entry), "utf8")));
|
|
24128
|
+
} catch {
|
|
24129
|
+
parsed = null;
|
|
24130
|
+
}
|
|
24131
|
+
if (parsed && stackFileName(parsed.name) === entry)
|
|
24132
|
+
out.push(parsed);
|
|
24133
|
+
}
|
|
24134
|
+
return { ok: true, data: out.toSorted((a, b) => a.name.localeCompare(b.name)) };
|
|
24135
|
+
}
|
|
24136
|
+
function selectFanout(cwd, name, current) {
|
|
24137
|
+
if (name) {
|
|
24138
|
+
const read = readFanout(cwd, name);
|
|
24139
|
+
if (!read.ok)
|
|
24140
|
+
return read;
|
|
24141
|
+
return read.data ? { ok: true, data: read.data } : fanoutFail("not_found", `no fanout plan named ${name}`, "workit fanout plan <plan.json>");
|
|
24142
|
+
}
|
|
24143
|
+
const all = listFanouts(cwd);
|
|
24144
|
+
if (!all.ok)
|
|
24145
|
+
return all;
|
|
24146
|
+
const holding = all.data.filter((plan) => plan.slices.some((slice) => slice.branch === current));
|
|
24147
|
+
if (holding.length === 1)
|
|
24148
|
+
return { ok: true, data: holding[0] };
|
|
24149
|
+
if (all.data.length === 1)
|
|
24150
|
+
return { ok: true, data: all.data[0] };
|
|
24151
|
+
return all.data.length === 0 ? fanoutFail("not_found", "no fanout plan is recorded here", "workit fanout plan <plan.json>") : fanoutFail("invalid_input", `several fanout plans match; pick one (${all.data.map((plan) => plan.name).join(", ")})`, "pass --name <plan>");
|
|
24152
|
+
}
|
|
24153
|
+
|
|
23499
24154
|
// packages/workit-opencode/src/v2/lifecycle.ts
|
|
23500
24155
|
var record2 = (value) => typeof value === "object" && value !== null ? value : {};
|
|
23501
24156
|
var sessionIDOf = (value) => typeof value === "string" && value.length > 0 ? value : null;
|
|
23502
24157
|
var parentOf = (session) => typeof session?.parentID === "string" && session.parentID ? session.parentID : undefined;
|
|
24158
|
+
var resumeBriefCommand = (root, directory) => {
|
|
24159
|
+
const branch = directory ? currentBranch(directory) : null;
|
|
24160
|
+
const plan = branch ? selectFanout(root, null, branch) : null;
|
|
24161
|
+
const slice = branch && plan?.ok ? plan.data.slices.find((entry) => entry.branch === branch || branch.startsWith(`${entry.branch}-try`) && /^\d+$/u.test(branch.slice(entry.branch.length + 4))) : undefined;
|
|
24162
|
+
return `workit fanout brief ${slice?.id ?? "<slice>"} --mode resume`;
|
|
24163
|
+
};
|
|
23503
24164
|
var contentText = (content) => {
|
|
23504
24165
|
if (typeof content === "string")
|
|
23505
24166
|
return content;
|
|
@@ -23755,8 +24416,12 @@ var createV2Lifecycle = (deps) => {
|
|
|
23755
24416
|
throw new Error("delegation_lineage_denied: a continuation sessionID must name an existing direct child of the coordinator");
|
|
23756
24417
|
const listed = new TaskStore(root).listTasks();
|
|
23757
24418
|
const bound = listed.ok ? listed.data.filter((task) => task.workers.some((worker) => worker.data.state === "running" && worker.data.session?.kind === "host" && worker.data.session.handle === childID && workerCoordinatorFor(task, worker) === coordinator)).length : 0;
|
|
23758
|
-
if (bound !== 1 && ownsActiveTask(coordinator))
|
|
23759
|
-
|
|
24419
|
+
if (bound !== 1 && ownsActiveTask(coordinator)) {
|
|
24420
|
+
const reported = listed.ok && listed.data.some((task) => task.workers.some((worker) => worker.data.state === "stopped" && worker.data.session?.kind === "host" && worker.data.session.handle === childID && workerCoordinatorFor(task, worker) === coordinator));
|
|
24421
|
+
const dirty = reported && child.directory ? worktreeTree(child.directory)?.dirty ?? null : null;
|
|
24422
|
+
if (!reported || !holdsLiveState("stopped", dirty))
|
|
24423
|
+
throw new Error(reported ? `delegation_lineage_denied: this worker already reported and holds no live state (no running process, no uncommitted changes); continue the slice with a fresh worker: ${resumeBriefCommand(root, child.directory)}` : "delegation_lineage_denied: a continuation sessionID must name a running Workit worker");
|
|
24424
|
+
}
|
|
23760
24425
|
return;
|
|
23761
24426
|
}
|
|
23762
24427
|
if (!ownsActiveTask(coordinator)) {
|
|
@@ -24051,16 +24716,17 @@ var childMayRecord = (family, input) => {
|
|
|
24051
24716
|
const action = fields(input).action;
|
|
24052
24717
|
return family !== null && typeof action === "string" && CHILD_RECORDS[family]?.has(action) === true;
|
|
24053
24718
|
};
|
|
24719
|
+
var childMayInspect = (family, input) => family === "task" && fields(input).action === "inspect";
|
|
24054
24720
|
var childTaskMissing = (store, input) => {
|
|
24055
24721
|
if (fields(input).taskId !== undefined)
|
|
24056
24722
|
return false;
|
|
24057
24723
|
const report = store.keyReport();
|
|
24058
24724
|
return !report.ok || report.data.bound.length === 0;
|
|
24059
24725
|
};
|
|
24060
|
-
var childDenial = (identity) => `OpenCode child session has no validated Workit worker: as ${identity} it may only record evidence or findings (workit_evidence/workit_finding action "record") on an existing task; for anything else the lead assigns it a worker with workit_worker before launching it`;
|
|
24726
|
+
var childDenial = (identity) => `OpenCode child session has no validated Workit worker: as ${identity} it may only inspect the task summary (workit_task action "inspect") or record evidence or findings (workit_evidence/workit_finding action "record") on an existing task; for anything else the lead assigns it a worker with workit_worker before launching it`;
|
|
24061
24727
|
|
|
24062
24728
|
// packages/workit-opencode/src/v2/shell.ts
|
|
24063
|
-
import
|
|
24729
|
+
import path38 from "node:path";
|
|
24064
24730
|
var commandOf = (input) => {
|
|
24065
24731
|
if (input === null || typeof input !== "object")
|
|
24066
24732
|
return null;
|
|
@@ -24069,7 +24735,7 @@ var commandOf = (input) => {
|
|
|
24069
24735
|
};
|
|
24070
24736
|
var hookInput = (root, sessionID, id, shell) => ({
|
|
24071
24737
|
host: "opencode",
|
|
24072
|
-
cwd: shell.workdir ?
|
|
24738
|
+
cwd: shell.workdir ? path38.resolve(root, shell.workdir) : root,
|
|
24073
24739
|
session: { id: sessionID, agentId: null, agentType: null, parentId: null },
|
|
24074
24740
|
permissionMode: null,
|
|
24075
24741
|
transcriptPath: null,
|
|
@@ -24253,38 +24919,38 @@ var createStopControl = (root, port) => {
|
|
|
24253
24919
|
};
|
|
24254
24920
|
|
|
24255
24921
|
// packages/workit-opencode/src/v2/registry.ts
|
|
24256
|
-
import { existsSync as
|
|
24257
|
-
import
|
|
24922
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "node:fs";
|
|
24923
|
+
import path40 from "node:path";
|
|
24258
24924
|
|
|
24259
24925
|
// packages/workit-opencode/src/shared/assets.ts
|
|
24260
|
-
import { existsSync as
|
|
24261
|
-
import
|
|
24926
|
+
import { existsSync as existsSync12, realpathSync as realpathSync9 } from "node:fs";
|
|
24927
|
+
import path39 from "node:path";
|
|
24262
24928
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
24263
24929
|
var moduleDir = () => {
|
|
24264
24930
|
try {
|
|
24265
|
-
return realpathSync9(
|
|
24931
|
+
return realpathSync9(path39.dirname(fileURLToPath2(import.meta.url)));
|
|
24266
24932
|
} catch {
|
|
24267
|
-
return
|
|
24933
|
+
return path39.dirname(fileURLToPath2(import.meta.url));
|
|
24268
24934
|
}
|
|
24269
24935
|
};
|
|
24270
24936
|
var packageRoot2 = () => {
|
|
24271
24937
|
const dir = moduleDir();
|
|
24272
24938
|
for (const candidate of [
|
|
24273
|
-
|
|
24274
|
-
|
|
24275
|
-
|
|
24939
|
+
path39.resolve(dir, ".."),
|
|
24940
|
+
path39.resolve(dir, "..", ".."),
|
|
24941
|
+
path39.resolve(dir, "..", "..", "..")
|
|
24276
24942
|
]) {
|
|
24277
|
-
if (
|
|
24943
|
+
if (existsSync12(path39.join(candidate, "package.json")))
|
|
24278
24944
|
return candidate;
|
|
24279
24945
|
}
|
|
24280
|
-
return
|
|
24946
|
+
return path39.resolve(dir, "..");
|
|
24281
24947
|
};
|
|
24282
24948
|
var skillsRoot = () => {
|
|
24283
24949
|
const root = packageRoot2();
|
|
24284
|
-
const canonical =
|
|
24285
|
-
if (
|
|
24950
|
+
const canonical = path39.resolve(root, "..", "workit-core", "skills");
|
|
24951
|
+
if (existsSync12(path39.join(root, "src")) && existsSync12(canonical))
|
|
24286
24952
|
return canonical;
|
|
24287
|
-
return
|
|
24953
|
+
return path39.join(root, "assets", "skills");
|
|
24288
24954
|
};
|
|
24289
24955
|
|
|
24290
24956
|
// packages/workit-opencode/src/v2/registry.ts
|
|
@@ -24308,15 +24974,15 @@ var parseFrontmatter = (markdown) => {
|
|
|
24308
24974
|
};
|
|
24309
24975
|
var registerSkills = async (ctx) => {
|
|
24310
24976
|
const skillsDir = skillsRoot();
|
|
24311
|
-
if (!
|
|
24977
|
+
if (!existsSync13(skillsDir))
|
|
24312
24978
|
return new Set;
|
|
24313
24979
|
const listed = await ctx.skill.list().catch(() => ({ data: [] }));
|
|
24314
|
-
const own = (skill) => typeof skill.path === "string" &&
|
|
24980
|
+
const own = (skill) => typeof skill.path === "string" && path40.resolve(skill.path).startsWith(skillsDir + path40.sep);
|
|
24315
24981
|
const userSkills = new Set((listed?.data ?? []).filter((skill) => !own(skill)).map((skill) => skill.id));
|
|
24316
24982
|
const packaged = new Map;
|
|
24317
24983
|
for (const id of WORKIT_METHOD_SKILLS) {
|
|
24318
|
-
const file =
|
|
24319
|
-
if (!userSkills.has(id) &&
|
|
24984
|
+
const file = path40.join(skillsDir, id, "SKILL.md");
|
|
24985
|
+
if (!userSkills.has(id) && existsSync13(file))
|
|
24320
24986
|
packaged.set(id, file);
|
|
24321
24987
|
}
|
|
24322
24988
|
await ctx.skill.transform((editor) => {
|
|
@@ -24324,7 +24990,7 @@ var registerSkills = async (ctx) => {
|
|
|
24324
24990
|
for (const [id, file] of packaged) {
|
|
24325
24991
|
if (existing.has(id))
|
|
24326
24992
|
continue;
|
|
24327
|
-
const parsed = parseFrontmatter(
|
|
24993
|
+
const parsed = parseFrontmatter(readFileSync10(file, "utf8"));
|
|
24328
24994
|
editor.add({
|
|
24329
24995
|
id,
|
|
24330
24996
|
name: parsed.name || id,
|
|
@@ -24362,25 +25028,25 @@ var registerCommands = async (ctx, registeredSkills) => {
|
|
|
24362
25028
|
};
|
|
24363
25029
|
|
|
24364
25030
|
// packages/workit-opencode/src/stale-sources.ts
|
|
24365
|
-
import { existsSync as
|
|
24366
|
-
import
|
|
25031
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6 } from "node:fs";
|
|
25032
|
+
import path41 from "node:path";
|
|
24367
25033
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
24368
25034
|
var typescriptFiles = (directory) => {
|
|
24369
|
-
if (!
|
|
25035
|
+
if (!existsSync14(directory))
|
|
24370
25036
|
return [];
|
|
24371
25037
|
return readdirSync6(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
24372
|
-
const file =
|
|
25038
|
+
const file = path41.join(directory, entry.name);
|
|
24373
25039
|
return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
|
|
24374
25040
|
});
|
|
24375
25041
|
};
|
|
24376
25042
|
var pluginSourceFiles = [
|
|
24377
25043
|
...new Set([
|
|
24378
25044
|
fileURLToPath3(import.meta.url),
|
|
24379
|
-
...typescriptFiles(
|
|
24380
|
-
|
|
24381
|
-
...typescriptFiles(
|
|
25045
|
+
...typescriptFiles(path41.join(packageRoot2(), "src")),
|
|
25046
|
+
path41.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
|
|
25047
|
+
...typescriptFiles(path41.join(packageRoot2(), "..", "workit-core", "src", "core"))
|
|
24382
25048
|
])
|
|
24383
|
-
].filter((file) =>
|
|
25049
|
+
].filter((file) => existsSync14(file)).toSorted();
|
|
24384
25050
|
|
|
24385
25051
|
// packages/workit-opencode/src/v2/plugin.ts
|
|
24386
25052
|
var sessionFacts = async (ctx, sessionID) => {
|
|
@@ -24433,7 +25099,7 @@ var setup = async (ctx) => {
|
|
|
24433
25099
|
}
|
|
24434
25100
|
});
|
|
24435
25101
|
const contextTool = createContextTool();
|
|
24436
|
-
const runFamily = (family, input, session, store, workerId) => {
|
|
25102
|
+
const runFamily = (family, input, session, store, workerId, readOnlyChild = false) => {
|
|
24437
25103
|
const parsed = parseAdvertisedOperation(family, input, "opencode");
|
|
24438
25104
|
if (!parsed.ok)
|
|
24439
25105
|
return parsed;
|
|
@@ -24444,6 +25110,7 @@ var setup = async (ctx) => {
|
|
|
24444
25110
|
constraints: [],
|
|
24445
25111
|
now: () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
24446
25112
|
workerId,
|
|
25113
|
+
...readOnlyChild ? { readOnlyChild } : {},
|
|
24447
25114
|
nativeWorker: nativeWorkerFor(lifecycle.directChildren, session.id)
|
|
24448
25115
|
});
|
|
24449
25116
|
const run = core[family];
|
|
@@ -24471,8 +25138,11 @@ var setup = async (ctx) => {
|
|
|
24471
25138
|
const identity = childSessionId(session.parentID, session.id);
|
|
24472
25139
|
const lead = await sessionFacts(ctx, session.parentID);
|
|
24473
25140
|
const directChild = lead !== null && lead.parentID === undefined && sameWorkspace(root, lead.directory);
|
|
24474
|
-
|
|
25141
|
+
const inspecting = childMayInspect(family, input);
|
|
25142
|
+
if (!directChild || family === null || !(inspecting || childMayRecord(family, input)))
|
|
24475
25143
|
return resultContent(failure2("permission_denied", childDenial(identity)));
|
|
25144
|
+
if (inspecting)
|
|
25145
|
+
return resultContent(runFamily(family, input, { ...session, id: identity }, store, null, true));
|
|
24476
25146
|
if (childTaskMissing(store, input))
|
|
24477
25147
|
return resultContent(failure2("not_found", "no task for this branch yet: the lead starts one (workit_task start) before a child records on it, or pass taskId"));
|
|
24478
25148
|
return resultContent(runFamily(family, input, { ...session, id: identity }, store, null));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-opencode",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.16.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit OpenCode plugin — native tools, adaptive policy, and sixteen method skills",
|
|
6
6
|
"keywords": [
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"typecheck": "tsc --noEmit"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@brainervirus/workit-core": "^8.
|
|
42
|
+
"@brainervirus/workit-core": "^8.16.0",
|
|
43
43
|
"@opencode/plugin": "2.0.18"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|