@brainervirus/workit-opencode 8.13.1 → 8.14.1
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.
|
@@ -25,9 +25,9 @@ ceremony; do it yourself. Workit never spawns agents; you do.
|
|
|
25
25
|
it; change the plan or the standing orders and render again.
|
|
26
26
|
4. **Spawn in a rolling window**, in the background, each in its own worktree:
|
|
27
27
|
keep 4-6 in flight and refill from `spawnable` in `workit fanout status` as
|
|
28
|
-
each one finishes.
|
|
29
|
-
Claude Code: the `implementer` agent;
|
|
30
|
-
worktree create <slice>`.
|
|
28
|
+
each one finishes. Model: the brief's `model:` line, else TIER
|
|
29
|
+
(`references/orchestration.md`). Claude Code: the `implementer` agent;
|
|
30
|
+
elsewhere first `workit fanout worktree create <slice>`.
|
|
31
31
|
5. **Judge liveness by side effects only:** `workit fanout status` (head age,
|
|
32
32
|
PR, CI, verdict, landed; STUCK past the TIMEBOX). Stop a stuck worker and
|
|
33
33
|
observe that it exited (a timeout is not proof). Removing a worktree drops
|
|
@@ -53,7 +53,7 @@ ceremony; do it yourself. Workit never spawns agents; you do.
|
|
|
53
53
|
status` order, release the worktrees you made, and `workit ledger standing
|
|
54
54
|
clear`. Only you touch topology: workers never rebase, retarget or merge,
|
|
55
55
|
except the integration-tip merge in integration mode. Then ship
|
|
56
|
-
(call the skill tool with `workit-ship`)
|
|
56
|
+
(call the skill tool with `workit-ship`) up to `defaultEndpoint` from `workit grant show`.
|
|
57
57
|
|
|
58
58
|
## Example
|
|
59
59
|
|
|
@@ -5,8 +5,10 @@ and fans in. Workit records and checks; the host's own subagents run the work.
|
|
|
5
5
|
|
|
6
6
|
## TIER to model
|
|
7
7
|
|
|
8
|
-
TIER is a field of each slice and of its brief.
|
|
9
|
-
|
|
8
|
+
TIER is a field of each slice and of its brief. When the workspace sets
|
|
9
|
+
`fanout.tierModels`, the brief has a `model:` line under TIER and `workit
|
|
10
|
+
fanout status` names the model next to each spawnable slice: use that model.
|
|
11
|
+
Without one, map TIER where the host lets you pick a model per spawn:
|
|
10
12
|
|
|
11
13
|
| TIER | Claude Code (`model` on the Agent call) | Hosts without a per-spawn model |
|
|
12
14
|
| --- | --- | --- |
|
package/dist/plugin.js
CHANGED
|
@@ -7772,6 +7772,8 @@ var text = string2().check((ctx) => {
|
|
|
7772
7772
|
ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
|
|
7773
7773
|
});
|
|
7774
7774
|
var id = text.regex(uuidPattern, "expected lowercase UUID");
|
|
7775
|
+
var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
|
|
7776
|
+
var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
|
|
7775
7777
|
var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
|
|
7776
7778
|
var revision = text.regex(uuidPattern, "expected UUID revision");
|
|
7777
7779
|
var validUtc = (value) => {
|
|
@@ -8149,7 +8151,7 @@ var workerReportSchema = object({
|
|
|
8149
8151
|
outcome: _enum(["completed", "failed", "cancelled"]),
|
|
8150
8152
|
summary: text,
|
|
8151
8153
|
evidenceIds: array(id),
|
|
8152
|
-
findingIds: array(
|
|
8154
|
+
findingIds: array(idRef)
|
|
8153
8155
|
}).strict();
|
|
8154
8156
|
var workerSchema = object({
|
|
8155
8157
|
assignment: assignmentSchema,
|
|
@@ -8395,7 +8397,7 @@ var findingOperations = {
|
|
|
8395
8397
|
action: literal("resolve"),
|
|
8396
8398
|
...taskId,
|
|
8397
8399
|
expectedRevision: revision.optional(),
|
|
8398
|
-
findingId:
|
|
8400
|
+
findingId: idRef,
|
|
8399
8401
|
disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
|
|
8400
8402
|
reason: text,
|
|
8401
8403
|
evidenceIds: array(id),
|
|
@@ -8436,7 +8438,7 @@ var workerOperations = {
|
|
|
8436
8438
|
...taskId,
|
|
8437
8439
|
expectedRevision: revision.optional(),
|
|
8438
8440
|
expectedWorkspaceRevision: revision.optional(),
|
|
8439
|
-
workerId:
|
|
8441
|
+
workerId: idRef,
|
|
8440
8442
|
report: workerReportSchema
|
|
8441
8443
|
}),
|
|
8442
8444
|
cancel: operation({
|
|
@@ -8444,7 +8446,7 @@ var workerOperations = {
|
|
|
8444
8446
|
...taskId,
|
|
8445
8447
|
...revisions,
|
|
8446
8448
|
expectedWorkspaceRevision: revision.optional(),
|
|
8447
|
-
workerId:
|
|
8449
|
+
workerId: idRef,
|
|
8448
8450
|
reason: text
|
|
8449
8451
|
})
|
|
8450
8452
|
};
|
|
@@ -8494,6 +8496,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
|
|
|
8494
8496
|
data
|
|
8495
8497
|
});
|
|
8496
8498
|
var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
|
|
8499
|
+
function resolveIdRef(kind, ref, ids) {
|
|
8500
|
+
if (ids.includes(ref))
|
|
8501
|
+
return success(null, null, ref);
|
|
8502
|
+
const matches = ids.filter((candidate) => candidate.startsWith(ref));
|
|
8503
|
+
if (matches.length === 1)
|
|
8504
|
+
return success(null, null, matches[0]);
|
|
8505
|
+
if (matches.length === 0)
|
|
8506
|
+
return failure2("not_found", `${kind} not found`);
|
|
8507
|
+
return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
|
|
8508
|
+
}
|
|
8497
8509
|
function parseOperation(family, input) {
|
|
8498
8510
|
if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
|
|
8499
8511
|
return failure2("unsupported_version", "unsupported schema version", { operation: family });
|
|
@@ -11003,20 +11015,26 @@ class TaskStore {
|
|
|
11003
11015
|
return success(null, null, { key: key.data, bound: bound.data });
|
|
11004
11016
|
}
|
|
11005
11017
|
taskForWorker(workerId) {
|
|
11006
|
-
if (typeof workerId !== "string" || !
|
|
11018
|
+
if (typeof workerId !== "string" || !idRefPattern.test(workerId))
|
|
11007
11019
|
return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
|
|
11008
11020
|
const listed = this.listTaskIndex();
|
|
11009
11021
|
if (!listed.ok)
|
|
11010
11022
|
return listed;
|
|
11023
|
+
const owners = new Map;
|
|
11011
11024
|
for (const entry of listed.data) {
|
|
11012
11025
|
if (entry.status === "closed")
|
|
11013
11026
|
continue;
|
|
11014
11027
|
const loaded = this.load(entry.id);
|
|
11015
11028
|
if (!loaded.ok)
|
|
11016
11029
|
return loaded;
|
|
11017
|
-
|
|
11018
|
-
|
|
11030
|
+
for (const worker of loaded.data?.record.workers ?? [])
|
|
11031
|
+
owners.set(worker.id, entry.id);
|
|
11019
11032
|
}
|
|
11033
|
+
const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
|
|
11034
|
+
if (resolved.ok)
|
|
11035
|
+
return success(null, null, owners.get(resolved.data) ?? null);
|
|
11036
|
+
if (resolved.code !== "not_found")
|
|
11037
|
+
return resolved;
|
|
11020
11038
|
return success(null, null, null);
|
|
11021
11039
|
}
|
|
11022
11040
|
originTaskEntry() {
|
|
@@ -13161,6 +13179,8 @@ var workspaceConfigSchema = object({
|
|
|
13161
13179
|
defaultEndpoint: unknown().optional(),
|
|
13162
13180
|
verification: _enum(["self", "independent"]).optional(),
|
|
13163
13181
|
trailers: boolean2().optional(),
|
|
13182
|
+
liveSessionGuard: boolean2().optional(),
|
|
13183
|
+
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
13164
13184
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
13165
13185
|
kind,
|
|
13166
13186
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -13478,6 +13498,31 @@ var resolveRuntimeReleaseTracks = (cwd) => {
|
|
|
13478
13498
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13479
13499
|
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
13480
13500
|
};
|
|
13501
|
+
var inheritedSetting = (cwd, dir, has) => {
|
|
13502
|
+
let matched;
|
|
13503
|
+
try {
|
|
13504
|
+
matched = matchRuntimeWorkspace(cwd, dir);
|
|
13505
|
+
} catch {
|
|
13506
|
+
return null;
|
|
13507
|
+
}
|
|
13508
|
+
if (!matched)
|
|
13509
|
+
return null;
|
|
13510
|
+
const { selected, matches } = matched;
|
|
13511
|
+
const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
13512
|
+
return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
|
|
13513
|
+
};
|
|
13514
|
+
var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
|
|
13515
|
+
var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
|
|
13516
|
+
var TIER_HOSTS = new Set([
|
|
13517
|
+
"claude_code",
|
|
13518
|
+
"opencode",
|
|
13519
|
+
"codex",
|
|
13520
|
+
"codex_cli",
|
|
13521
|
+
"codex_desktop",
|
|
13522
|
+
"cursor",
|
|
13523
|
+
"pi",
|
|
13524
|
+
"default"
|
|
13525
|
+
]);
|
|
13481
13526
|
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
13482
13527
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13483
13528
|
if (!matched)
|
|
@@ -18939,6 +18984,8 @@ var TASK_SCOPED_ACTIONS = new Set([
|
|
|
18939
18984
|
"worker.cancel",
|
|
18940
18985
|
"state.export"
|
|
18941
18986
|
]);
|
|
18987
|
+
var ONLY_OWN_REPORT = "workers can submit only their own report";
|
|
18988
|
+
var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
|
|
18942
18989
|
var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
|
|
18943
18990
|
var workerCommand = (taskId, request) => {
|
|
18944
18991
|
const { action, workerId, reason, report } = request;
|
|
@@ -19836,6 +19883,10 @@ class WorkitCore {
|
|
|
19836
19883
|
}
|
|
19837
19884
|
if (helper?.ok)
|
|
19838
19885
|
return failure2("permission_denied", "helpers cannot resolve findings");
|
|
19886
|
+
const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
|
|
19887
|
+
if (!findingRef.ok)
|
|
19888
|
+
return findingRef;
|
|
19889
|
+
input.findingId = findingRef.data;
|
|
19839
19890
|
const finding = task.data.findings.find((entry) => entry.id === input.findingId);
|
|
19840
19891
|
if (!finding)
|
|
19841
19892
|
return failure2("not_found", "finding not found");
|
|
@@ -19987,15 +20038,31 @@ class WorkitCore {
|
|
|
19987
20038
|
return changed;
|
|
19988
20039
|
return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
|
|
19989
20040
|
}
|
|
20041
|
+
const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
|
|
20042
|
+
if (!workerRef.ok)
|
|
20043
|
+
return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
|
|
20044
|
+
input.workerId = workerRef.data;
|
|
19990
20045
|
const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
|
|
19991
20046
|
if (!entry)
|
|
19992
20047
|
return failure2("not_found", "worker not found");
|
|
19993
20048
|
if (input.action === "report") {
|
|
19994
20049
|
if (helperId === null || helperId !== input.workerId)
|
|
19995
|
-
return failure2("permission_denied",
|
|
20050
|
+
return failure2("permission_denied", ONLY_OWN_REPORT);
|
|
19996
20051
|
const helper = this.helperEntry(task.data, true, true);
|
|
19997
20052
|
if (!helper.ok)
|
|
19998
20053
|
return helper;
|
|
20054
|
+
const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
|
|
20055
|
+
const reported = [];
|
|
20056
|
+
for (const findingRef of input.report.findingIds) {
|
|
20057
|
+
const found = resolveIdRef("finding", findingRef, ownFindings);
|
|
20058
|
+
if (found.ok)
|
|
20059
|
+
reported.push(found.data);
|
|
20060
|
+
else if (found.code === "not_found")
|
|
20061
|
+
reported.push(findingRef);
|
|
20062
|
+
else
|
|
20063
|
+
return found;
|
|
20064
|
+
}
|
|
20065
|
+
input.report.findingIds = reported;
|
|
19999
20066
|
const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
|
|
20000
20067
|
const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
|
|
20001
20068
|
if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
|
|
@@ -20023,7 +20090,7 @@ class WorkitCore {
|
|
|
20023
20090
|
return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
|
|
20024
20091
|
}
|
|
20025
20092
|
if (helperId !== null)
|
|
20026
|
-
return failure2("permission_denied",
|
|
20093
|
+
return failure2("permission_denied", NO_HELPER_CONTROL);
|
|
20027
20094
|
if (task.data.status === "closed")
|
|
20028
20095
|
return failure2("invalid_transition", "closed task cannot cancel workers");
|
|
20029
20096
|
if (entry.data.state === "stopped") {
|
|
@@ -21684,27 +21751,231 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
21684
21751
|
};
|
|
21685
21752
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
21686
21753
|
import { realpathSync as realpathSync6 } from "node:fs";
|
|
21754
|
+
import os7 from "node:os";
|
|
21755
|
+
import path27 from "node:path";
|
|
21756
|
+
|
|
21757
|
+
// packages/workit-core/src/core/live-session.ts
|
|
21758
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
21759
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
21760
|
+
import fs18 from "node:fs";
|
|
21761
|
+
import os6 from "node:os";
|
|
21687
21762
|
import path26 from "node:path";
|
|
21763
|
+
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
21764
|
+
var HEARTBEAT_EVERY_MS = 30000;
|
|
21765
|
+
var liveProbe = {
|
|
21766
|
+
platform: process.platform,
|
|
21767
|
+
run: (args) => spawnSync17("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
|
|
21768
|
+
start: (pid) => processStartOf(pid)
|
|
21769
|
+
};
|
|
21770
|
+
var leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session;
|
|
21771
|
+
var familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, "");
|
|
21772
|
+
var processAlive = (pid) => {
|
|
21773
|
+
try {
|
|
21774
|
+
process.kill(pid, 0);
|
|
21775
|
+
return true;
|
|
21776
|
+
} catch (error) {
|
|
21777
|
+
return error.code === "EPERM";
|
|
21778
|
+
}
|
|
21779
|
+
};
|
|
21780
|
+
var processInfo = (pid) => {
|
|
21781
|
+
try {
|
|
21782
|
+
if (liveProbe.platform === "linux") {
|
|
21783
|
+
const stat = fs18.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
21784
|
+
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
21785
|
+
const command = fs18.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
|
|
21786
|
+
return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
|
|
21787
|
+
}
|
|
21788
|
+
if (liveProbe.platform === "win32")
|
|
21789
|
+
return null;
|
|
21790
|
+
const match = /^\s*(\d+)\s+(.*)$/su.exec(liveProbe.run(["-o", "ppid=,command=", "-p", String(pid)]));
|
|
21791
|
+
return match ? { ppid: Number(match[1]), command: match[2].trim() } : null;
|
|
21792
|
+
} catch {
|
|
21793
|
+
return null;
|
|
21794
|
+
}
|
|
21795
|
+
};
|
|
21796
|
+
var WRAPPER = /^(?:\S*\/)?(?:sh|bash|dash|zsh|ash|ksh|fish|env|timeout|npx|npm|bunx|cmd\.exe|pwsh|powershell)(?:\s|$)|launch\.mjs|npx-cli\.js/u;
|
|
21797
|
+
var PIDS = new Map;
|
|
21798
|
+
var hostPid = (transport) => {
|
|
21799
|
+
if (transport !== "hook-process")
|
|
21800
|
+
return process.pid;
|
|
21801
|
+
const key = `${liveProbe.platform}\x00${process.ppid}`;
|
|
21802
|
+
if (PIDS.has(key))
|
|
21803
|
+
return PIDS.get(key) ?? null;
|
|
21804
|
+
if (liveProbe.platform === "win32") {
|
|
21805
|
+
PIDS.set(key, null);
|
|
21806
|
+
return null;
|
|
21807
|
+
}
|
|
21808
|
+
let pid = process.ppid;
|
|
21809
|
+
for (let hop = 0;hop < 4; hop++) {
|
|
21810
|
+
const info = processInfo(pid);
|
|
21811
|
+
if (!info || !WRAPPER.test(info.command) || info.ppid <= 1)
|
|
21812
|
+
break;
|
|
21813
|
+
pid = info.ppid;
|
|
21814
|
+
}
|
|
21815
|
+
PIDS.set(key, pid);
|
|
21816
|
+
return pid;
|
|
21817
|
+
};
|
|
21818
|
+
var sessionSafe = (value) => value.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-");
|
|
21819
|
+
var liveOwner = (input, transport, env = process.env) => {
|
|
21820
|
+
const session = input.session.id?.trim();
|
|
21821
|
+
if (!session)
|
|
21822
|
+
return null;
|
|
21823
|
+
const leads = new Set([leadOf(sessionSafe(session))]);
|
|
21824
|
+
if (input.session.parentId)
|
|
21825
|
+
leads.add(leadOf(sessionSafe(input.session.parentId)));
|
|
21826
|
+
if (env.WORKIT_PI_WORKER_SESSION && env.WORKIT_SESSION_ID)
|
|
21827
|
+
leads.add(leadOf(sessionSafe(env.WORKIT_SESSION_ID)));
|
|
21828
|
+
return {
|
|
21829
|
+
host: input.host,
|
|
21830
|
+
session: sessionSafe(session),
|
|
21831
|
+
leads: [...leads],
|
|
21832
|
+
pid: () => hostPid(transport)
|
|
21833
|
+
};
|
|
21834
|
+
};
|
|
21835
|
+
var checkoutOf = (target) => {
|
|
21836
|
+
let dir = path26.resolve(target);
|
|
21837
|
+
while (!fs18.existsSync(dir) && path26.dirname(dir) !== dir)
|
|
21838
|
+
dir = path26.dirname(dir);
|
|
21839
|
+
if (fs18.existsSync(dir) && !fs18.statSync(dir).isDirectory())
|
|
21840
|
+
dir = path26.dirname(dir);
|
|
21841
|
+
const parts = dir.split(path26.sep);
|
|
21842
|
+
const dotGit = parts.lastIndexOf(".git");
|
|
21843
|
+
if (dotGit > 0)
|
|
21844
|
+
dir = parts.slice(0, dotGit).join(path26.sep) || path26.sep;
|
|
21845
|
+
const location = resolveStore(dir);
|
|
21846
|
+
if (location instanceof Error || !location.shared || !location.top)
|
|
21847
|
+
return null;
|
|
21848
|
+
let top = location.top;
|
|
21849
|
+
try {
|
|
21850
|
+
top = fs18.realpathSync.native(top);
|
|
21851
|
+
} catch {}
|
|
21852
|
+
return top;
|
|
21853
|
+
};
|
|
21854
|
+
var markerFile2 = (top) => path26.join(resolveStateDir(), "live-sessions", `${createHash14("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
21855
|
+
var checkoutKey = (top, platform = process.platform) => {
|
|
21856
|
+
let key = path26.resolve(top);
|
|
21857
|
+
try {
|
|
21858
|
+
key = fs18.realpathSync.native(key);
|
|
21859
|
+
} catch {}
|
|
21860
|
+
return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
|
|
21861
|
+
};
|
|
21862
|
+
var readMarker = (file) => {
|
|
21863
|
+
try {
|
|
21864
|
+
const value = JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
21865
|
+
if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
|
|
21866
|
+
return null;
|
|
21867
|
+
return {
|
|
21868
|
+
version: 1,
|
|
21869
|
+
session: value.session,
|
|
21870
|
+
leads: Array.isArray(value.leads) ? value.leads.filter((item) => typeof item === "string") : [],
|
|
21871
|
+
host: value.host,
|
|
21872
|
+
pid: typeof value.pid === "number" ? value.pid : null,
|
|
21873
|
+
pidStart: typeof value.pidStart === "string" ? value.pidStart : null,
|
|
21874
|
+
machine: typeof value.machine === "string" ? value.machine : "",
|
|
21875
|
+
checkout: typeof value.checkout === "string" ? value.checkout : "",
|
|
21876
|
+
heartbeatAt: value.heartbeatAt
|
|
21877
|
+
};
|
|
21878
|
+
} catch {
|
|
21879
|
+
return null;
|
|
21880
|
+
}
|
|
21881
|
+
};
|
|
21882
|
+
var ageOf2 = (marker, now) => now.getTime() - Date.parse(marker.heartbeatAt);
|
|
21883
|
+
var isLive = (marker, deps) => {
|
|
21884
|
+
const age = ageOf2(marker, deps.now ?? new Date);
|
|
21885
|
+
if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
|
|
21886
|
+
return false;
|
|
21887
|
+
if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
|
|
21888
|
+
return true;
|
|
21889
|
+
if (!(deps.alive ?? processAlive)(marker.pid))
|
|
21890
|
+
return false;
|
|
21891
|
+
if (marker.pidStart === null)
|
|
21892
|
+
return true;
|
|
21893
|
+
const start = liveProbe.start(marker.pid);
|
|
21894
|
+
return start === null || start === marker.pidStart;
|
|
21895
|
+
};
|
|
21896
|
+
var sameLead = (marker, owner) => marker.session === owner.session || owner.leads.some((lead) => lead === marker.session || marker.leads.includes(lead));
|
|
21897
|
+
var sameOwner = (marker, owner, machine) => {
|
|
21898
|
+
if (sameLead(marker, owner))
|
|
21899
|
+
return true;
|
|
21900
|
+
if (marker.machine !== machine)
|
|
21901
|
+
return false;
|
|
21902
|
+
const pid = owner.pid();
|
|
21903
|
+
if (pid === null || marker.pid === null || marker.pidStart === null)
|
|
21904
|
+
return familyOf(marker.host) === familyOf(owner.host);
|
|
21905
|
+
return marker.pid === pid;
|
|
21906
|
+
};
|
|
21907
|
+
var liveConflict = (target, owner, deps = {}) => {
|
|
21908
|
+
try {
|
|
21909
|
+
const checkout = checkoutOf(target);
|
|
21910
|
+
if (!checkout)
|
|
21911
|
+
return null;
|
|
21912
|
+
const file = markerFile2(checkout);
|
|
21913
|
+
const marker = readMarker(file);
|
|
21914
|
+
if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
|
|
21915
|
+
return null;
|
|
21916
|
+
if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
|
|
21917
|
+
return null;
|
|
21918
|
+
return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
|
|
21919
|
+
} catch {
|
|
21920
|
+
return null;
|
|
21921
|
+
}
|
|
21922
|
+
};
|
|
21923
|
+
var claimLiveSession = (target, owner, deps = {}) => {
|
|
21924
|
+
try {
|
|
21925
|
+
const checkout = checkoutOf(target);
|
|
21926
|
+
if (!checkout)
|
|
21927
|
+
return;
|
|
21928
|
+
const file = markerFile2(checkout);
|
|
21929
|
+
const now = deps.now ?? new Date;
|
|
21930
|
+
const machine = deps.machine ?? os6.hostname();
|
|
21931
|
+
const current = readMarker(file);
|
|
21932
|
+
if (current) {
|
|
21933
|
+
const age = ageOf2(current, now);
|
|
21934
|
+
if (sameLead(current, owner) && age >= 0 && age < HEARTBEAT_EVERY_MS)
|
|
21935
|
+
return;
|
|
21936
|
+
if (!sameLead(current, owner) && isLive(current, deps) && !sameOwner(current, owner, machine))
|
|
21937
|
+
return;
|
|
21938
|
+
}
|
|
21939
|
+
const pid = owner.pid();
|
|
21940
|
+
const marker = {
|
|
21941
|
+
version: 1,
|
|
21942
|
+
session: owner.session,
|
|
21943
|
+
leads: owner.leads,
|
|
21944
|
+
host: owner.host,
|
|
21945
|
+
pid,
|
|
21946
|
+
pidStart: pid === null ? null : liveProbe.start(pid),
|
|
21947
|
+
machine,
|
|
21948
|
+
checkout,
|
|
21949
|
+
heartbeatAt: now.toISOString()
|
|
21950
|
+
};
|
|
21951
|
+
fs18.mkdirSync(path26.dirname(file), { recursive: true });
|
|
21952
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
21953
|
+
fs18.writeFileSync(temp, JSON.stringify(marker));
|
|
21954
|
+
fs18.renameSync(temp, file);
|
|
21955
|
+
} catch {}
|
|
21956
|
+
};
|
|
21957
|
+
|
|
21958
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
21688
21959
|
var NONE3 = { kind: "none" };
|
|
21689
21960
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
21690
21961
|
var canonical2 = (file) => {
|
|
21691
21962
|
let rest = "";
|
|
21692
|
-
for (let dir =
|
|
21963
|
+
for (let dir = path27.resolve(file);; dir = path27.dirname(dir)) {
|
|
21693
21964
|
try {
|
|
21694
|
-
return
|
|
21965
|
+
return path27.join(realpathSync6(dir), rest);
|
|
21695
21966
|
} catch {
|
|
21696
|
-
if (
|
|
21697
|
-
return
|
|
21698
|
-
rest = rest ?
|
|
21967
|
+
if (path27.dirname(dir) === dir)
|
|
21968
|
+
return path27.resolve(file);
|
|
21969
|
+
rest = rest ? path27.join(path27.basename(dir), rest) : path27.basename(dir);
|
|
21699
21970
|
}
|
|
21700
21971
|
}
|
|
21701
21972
|
};
|
|
21702
21973
|
var exempt = (cwd, root, file, plans) => {
|
|
21703
21974
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
21704
|
-
const relative =
|
|
21705
|
-
if (relative.startsWith("..") ||
|
|
21975
|
+
const relative = path27.relative(fold(canonical2(root)), fold(canonical2(path27.resolve(cwd, file))));
|
|
21976
|
+
if (relative.startsWith("..") || path27.isAbsolute(relative))
|
|
21706
21977
|
return true;
|
|
21707
|
-
const posix = relative.split(
|
|
21978
|
+
const posix = relative.split(path27.sep).join("/");
|
|
21708
21979
|
const segments = posix.split("/");
|
|
21709
21980
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
21710
21981
|
};
|
|
@@ -21802,7 +22073,7 @@ var shellWrites = (command) => {
|
|
|
21802
22073
|
return { writes: true, targets: unknown ? null : targets };
|
|
21803
22074
|
};
|
|
21804
22075
|
var cache2 = new Map;
|
|
21805
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
22076
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path27.resolve(root, file)) ?? "-").join("\x00");
|
|
21806
22077
|
var gateFor = (cwd) => {
|
|
21807
22078
|
const store = new TaskStore(cwd);
|
|
21808
22079
|
const listed = store.listTaskIndex();
|
|
@@ -21840,7 +22111,218 @@ var gateFor = (cwd) => {
|
|
|
21840
22111
|
cache2.delete(cache2.keys().next().value);
|
|
21841
22112
|
return gate;
|
|
21842
22113
|
};
|
|
21843
|
-
|
|
22114
|
+
var MUTATING_GIT = new Set([
|
|
22115
|
+
"add",
|
|
22116
|
+
"am",
|
|
22117
|
+
"apply",
|
|
22118
|
+
"checkout",
|
|
22119
|
+
"cherry-pick",
|
|
22120
|
+
"clean",
|
|
22121
|
+
"commit",
|
|
22122
|
+
"merge",
|
|
22123
|
+
"mv",
|
|
22124
|
+
"pull",
|
|
22125
|
+
"rebase",
|
|
22126
|
+
"reset",
|
|
22127
|
+
"restore",
|
|
22128
|
+
"revert",
|
|
22129
|
+
"rm",
|
|
22130
|
+
"stash",
|
|
22131
|
+
"switch"
|
|
22132
|
+
]);
|
|
22133
|
+
var DRY_N = new Set(["add", "clean", "rm", "mv"]);
|
|
22134
|
+
var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
|
|
22135
|
+
var DIR_TARGET = ".workit-live-target";
|
|
22136
|
+
var OVERRIDE = "WORKIT_LIVE_SESSION=override";
|
|
22137
|
+
var inert = (args, sub) => args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run" || DRY_N.has(sub) && /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg) && !arg.startsWith("--"));
|
|
22138
|
+
var gitMutationDirs = (cwd, command, dialect) => {
|
|
22139
|
+
let invocations;
|
|
22140
|
+
try {
|
|
22141
|
+
invocations = rawInvocations(command, dialect);
|
|
22142
|
+
} catch {
|
|
22143
|
+
return [];
|
|
22144
|
+
}
|
|
22145
|
+
return invocations.flatMap((invocation) => {
|
|
22146
|
+
if (invocation.tool !== "git" || invocation.dir === undefined)
|
|
22147
|
+
return [];
|
|
22148
|
+
const at = invocation.args.findIndex((arg) => !arg.startsWith("-"));
|
|
22149
|
+
const sub = invocation.args[at] ?? "";
|
|
22150
|
+
const rest = invocation.args.slice(at + 1);
|
|
22151
|
+
if (!MUTATING_GIT.has(sub) || inert(rest, sub))
|
|
22152
|
+
return [];
|
|
22153
|
+
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
22154
|
+
return [];
|
|
22155
|
+
return [path27.resolve(cwd, invocation.dir ?? ".")];
|
|
22156
|
+
});
|
|
22157
|
+
};
|
|
22158
|
+
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
22159
|
+
var PUSHD2 = new Set(["pushd", "push-location"]);
|
|
22160
|
+
var POPD2 = new Set(["popd", "pop-location"]);
|
|
22161
|
+
var cdInto = (dir, words) => {
|
|
22162
|
+
const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
22163
|
+
if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
|
|
22164
|
+
return;
|
|
22165
|
+
return target === undefined ? os7.homedir() : path27.resolve(dir, target);
|
|
22166
|
+
};
|
|
22167
|
+
var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
22168
|
+
var segmentTargets = (cwd, command, dialect) => {
|
|
22169
|
+
const targets = [];
|
|
22170
|
+
let dir = cwd;
|
|
22171
|
+
let stack = [];
|
|
22172
|
+
const groups = [];
|
|
22173
|
+
const at = (file) => path27.isAbsolute(file) ? file : dir === undefined ? null : path27.resolve(dir, file);
|
|
22174
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
22175
|
+
if (segment.group === "open") {
|
|
22176
|
+
groups.push({ dir, stack: [...stack] });
|
|
22177
|
+
continue;
|
|
22178
|
+
}
|
|
22179
|
+
if (segment.group === "close") {
|
|
22180
|
+
const saved = groups.pop();
|
|
22181
|
+
if (saved)
|
|
22182
|
+
({ dir, stack } = saved);
|
|
22183
|
+
continue;
|
|
22184
|
+
}
|
|
22185
|
+
const lead = segment.words.findIndex((word) => !/^\w+=/.test(word));
|
|
22186
|
+
const words = lead < 0 ? [] : segment.words.slice(lead);
|
|
22187
|
+
const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
22188
|
+
if (CD2.has(name)) {
|
|
22189
|
+
dir = cdInto(dir, words);
|
|
22190
|
+
continue;
|
|
22191
|
+
}
|
|
22192
|
+
if (PUSHD2.has(name)) {
|
|
22193
|
+
stack.push(dir);
|
|
22194
|
+
dir = cdInto(dir, words);
|
|
22195
|
+
continue;
|
|
22196
|
+
}
|
|
22197
|
+
if (POPD2.has(name)) {
|
|
22198
|
+
dir = stack.length ? stack.pop() : undefined;
|
|
22199
|
+
continue;
|
|
22200
|
+
}
|
|
22201
|
+
for (const file of segment.redirects)
|
|
22202
|
+
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
22203
|
+
const resolved = at(file);
|
|
22204
|
+
if (resolved)
|
|
22205
|
+
targets.push(resolved);
|
|
22206
|
+
}
|
|
22207
|
+
const edits = editsOf(segment.words);
|
|
22208
|
+
if (edits === null) {
|
|
22209
|
+
if (dir !== undefined)
|
|
22210
|
+
targets.push(path27.join(dir, DIR_TARGET));
|
|
22211
|
+
} else
|
|
22212
|
+
for (const file of edits) {
|
|
22213
|
+
const resolved = at(file);
|
|
22214
|
+
if (resolved)
|
|
22215
|
+
targets.push(resolved);
|
|
22216
|
+
}
|
|
22217
|
+
if (dir !== undefined && workitInvocations(segment.words.map(quoteWord).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
|
|
22218
|
+
targets.push(path27.join(dir, DIR_TARGET));
|
|
22219
|
+
}
|
|
22220
|
+
return targets;
|
|
22221
|
+
};
|
|
22222
|
+
var overridden = (command, dialect) => {
|
|
22223
|
+
const segments = segmentsOf(command, dialect);
|
|
22224
|
+
if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
|
|
22225
|
+
return false;
|
|
22226
|
+
const words = segments[0].words;
|
|
22227
|
+
const lead = words.findIndex((word) => !/^\w+=/.test(word));
|
|
22228
|
+
return lead > 0 && words.slice(0, lead).includes(OVERRIDE);
|
|
22229
|
+
};
|
|
22230
|
+
var HOST_LABELS = {
|
|
22231
|
+
claude_code: "Claude Code",
|
|
22232
|
+
opencode: "OpenCode",
|
|
22233
|
+
codex_cli: "Codex CLI",
|
|
22234
|
+
codex_desktop: "Codex Desktop",
|
|
22235
|
+
cursor: "Cursor",
|
|
22236
|
+
pi: "Pi"
|
|
22237
|
+
};
|
|
22238
|
+
var ageText = (ms) => {
|
|
22239
|
+
const seconds = Math.max(0, Math.round(ms / 1000));
|
|
22240
|
+
return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
|
|
22241
|
+
};
|
|
22242
|
+
var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
22243
|
+
const unblock = `Work in your own worktree (\`git worktree add <path> -b <branch>\`, or \`workit fanout worktree create <slice>\`). Only if the user confirms that session is finished: release it with \`rm '${file}'\`, or prefix one command with ${OVERRIDE}.`;
|
|
22244
|
+
return {
|
|
22245
|
+
kind: "deny",
|
|
22246
|
+
reason: `workit live-session guard: ${checkout} belongs to another live agent session, ${HOST_LABELS[marker.host] ?? marker.host} session ${marker.session}${marker.pid === null ? "" : ` (pid ${marker.pid})`}, last active ${ageText(ageMs)} ago. Do not edit a checkout another session is working in; read-only commands stay allowed. ${unblock}`,
|
|
22247
|
+
unblock
|
|
22248
|
+
};
|
|
22249
|
+
};
|
|
22250
|
+
var guarded = (targets) => {
|
|
22251
|
+
const seen = new Set;
|
|
22252
|
+
return targets.filter((target) => {
|
|
22253
|
+
const dir = path27.dirname(target);
|
|
22254
|
+
if (seen.has(dir))
|
|
22255
|
+
return false;
|
|
22256
|
+
seen.add(dir);
|
|
22257
|
+
return true;
|
|
22258
|
+
});
|
|
22259
|
+
};
|
|
22260
|
+
var liveGate = (owner, targets, deps) => {
|
|
22261
|
+
try {
|
|
22262
|
+
for (const target of guarded(targets)) {
|
|
22263
|
+
const conflict = liveConflict(target, owner, deps);
|
|
22264
|
+
if (conflict && workspaceLiveSessionGuard(conflict.checkout))
|
|
22265
|
+
return liveDeny(conflict);
|
|
22266
|
+
}
|
|
22267
|
+
} catch {}
|
|
22268
|
+
return NONE3;
|
|
22269
|
+
};
|
|
22270
|
+
var claimAll = (owner, targets, deps) => {
|
|
22271
|
+
for (const target of guarded(targets))
|
|
22272
|
+
claimLiveSession(target, owner, deps);
|
|
22273
|
+
};
|
|
22274
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path27.resolve(cwd, file)) : [path27.join(cwd, DIR_TARGET)];
|
|
22275
|
+
function writeGate(cwd, paths, owner, deps) {
|
|
22276
|
+
if (!owner)
|
|
22277
|
+
return beforeWriteGate(cwd, paths);
|
|
22278
|
+
const targets = liveTargets(cwd, paths);
|
|
22279
|
+
const live = liveGate(owner, targets, deps);
|
|
22280
|
+
if (live.kind !== "none")
|
|
22281
|
+
return live;
|
|
22282
|
+
const decision = beforeWriteGate(cwd, paths);
|
|
22283
|
+
if (decision.kind === "none")
|
|
22284
|
+
claimAll(owner, targets, deps);
|
|
22285
|
+
return decision;
|
|
22286
|
+
}
|
|
22287
|
+
function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
22288
|
+
const writes = shellWrites(command);
|
|
22289
|
+
let targets = [];
|
|
22290
|
+
if (owner) {
|
|
22291
|
+
try {
|
|
22292
|
+
targets = [
|
|
22293
|
+
...segmentTargets(cwd, command, dialect),
|
|
22294
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
|
|
22295
|
+
];
|
|
22296
|
+
} catch {
|
|
22297
|
+
targets = [];
|
|
22298
|
+
}
|
|
22299
|
+
}
|
|
22300
|
+
if (owner && targets.length) {
|
|
22301
|
+
const live = liveGate(owner, targets, deps);
|
|
22302
|
+
if (live.kind !== "none") {
|
|
22303
|
+
if (!overridden(command, dialect))
|
|
22304
|
+
return live;
|
|
22305
|
+
noteOverride(cwd, owner, command);
|
|
22306
|
+
}
|
|
22307
|
+
}
|
|
22308
|
+
const decision = writes.writes ? beforeWriteGate(cwd, writes.targets) : NONE3;
|
|
22309
|
+
if (owner && targets.length && decision.kind === "none")
|
|
22310
|
+
claimAll(owner, targets, deps);
|
|
22311
|
+
return decision;
|
|
22312
|
+
}
|
|
22313
|
+
var noteOverride = (cwd, owner, command) => {
|
|
22314
|
+
try {
|
|
22315
|
+
if (!inWorkitWorkspace(cwd))
|
|
22316
|
+
return;
|
|
22317
|
+
appendHookObserved(cwd, {
|
|
22318
|
+
type: "live_session.override",
|
|
22319
|
+
actor: { host: owner.host, session: owner.session, agentId: null },
|
|
22320
|
+
session: owner.session,
|
|
22321
|
+
command: command.slice(0, 200)
|
|
22322
|
+
});
|
|
22323
|
+
} catch {}
|
|
22324
|
+
};
|
|
22325
|
+
function beforeWriteGate(cwd, paths) {
|
|
21844
22326
|
try {
|
|
21845
22327
|
const gate = gateFor(cwd);
|
|
21846
22328
|
if (!gate || gate.blockers.length === 0)
|
|
@@ -21858,7 +22340,7 @@ function writeGate(cwd, paths) {
|
|
|
21858
22340
|
}
|
|
21859
22341
|
}
|
|
21860
22342
|
// packages/workit-core/src/hooks/stop.ts
|
|
21861
|
-
import { spawnSync as
|
|
22343
|
+
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
21862
22344
|
var NONE4 = { kind: "none" };
|
|
21863
22345
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
21864
22346
|
var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
|
|
@@ -21879,7 +22361,7 @@ var asksUser = (message) => {
|
|
|
21879
22361
|
};
|
|
21880
22362
|
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
21881
22363
|
var gitList = (cwd, args) => {
|
|
21882
|
-
const run =
|
|
22364
|
+
const run = spawnSync18("git", args, {
|
|
21883
22365
|
cwd,
|
|
21884
22366
|
encoding: "utf8",
|
|
21885
22367
|
timeout: 3000,
|
|
@@ -22341,11 +22823,11 @@ var OPENCODE_DESCRIPTOR = {
|
|
|
22341
22823
|
]
|
|
22342
22824
|
};
|
|
22343
22825
|
// packages/workit-opencode/src/shared/init-apply.ts
|
|
22344
|
-
import
|
|
22826
|
+
import path32 from "node:path";
|
|
22345
22827
|
|
|
22346
22828
|
// packages/workit-core/src/core/gitignore.ts
|
|
22347
22829
|
import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
22348
|
-
import
|
|
22830
|
+
import path28 from "node:path";
|
|
22349
22831
|
var GITIGNORE_ENTRIES = [
|
|
22350
22832
|
"# workit: SDD working state (never commit)",
|
|
22351
22833
|
"docs/*/sdd/",
|
|
@@ -22365,7 +22847,7 @@ var GITIGNORE_ENTRIES = [
|
|
|
22365
22847
|
var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
22366
22848
|
if (!confirmed)
|
|
22367
22849
|
return { ok: false, error: "confirmed: true required" };
|
|
22368
|
-
const file =
|
|
22850
|
+
const file = path28.join(workspaceRoot, ".gitignore");
|
|
22369
22851
|
const existing = existsSync9(file) ? readFileSync6(file, "utf8") : "";
|
|
22370
22852
|
const existingLines = new Set(existing.split(`
|
|
22371
22853
|
`).map((l) => l.trim()).filter(Boolean));
|
|
@@ -22393,25 +22875,25 @@ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
|
22393
22875
|
|
|
22394
22876
|
// packages/workit-core/src/core/hygiene.ts
|
|
22395
22877
|
import { existsSync as existsSync10, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
22396
|
-
import
|
|
22878
|
+
import path29 from "node:path";
|
|
22397
22879
|
var repoRoot3 = assetRoot();
|
|
22398
|
-
var templatesDir = () =>
|
|
22880
|
+
var templatesDir = () => path29.join(repoRoot3, "templates", "hygiene");
|
|
22399
22881
|
var packageJson = (root) => {
|
|
22400
|
-
if (!existsSync10(
|
|
22882
|
+
if (!existsSync10(path29.join(root, "package.json")))
|
|
22401
22883
|
return null;
|
|
22402
22884
|
try {
|
|
22403
|
-
return JSON.parse(readFileSync7(
|
|
22885
|
+
return JSON.parse(readFileSync7(path29.join(root, "package.json"), "utf8"));
|
|
22404
22886
|
} catch {
|
|
22405
22887
|
return null;
|
|
22406
22888
|
}
|
|
22407
22889
|
};
|
|
22408
22890
|
var isOpenSource = (root) => {
|
|
22409
|
-
if (existsSync10(
|
|
22891
|
+
if (existsSync10(path29.join(root, "LICENSE")))
|
|
22410
22892
|
return true;
|
|
22411
22893
|
const pkg = packageJson(root);
|
|
22412
22894
|
if (pkg && !pkg.private)
|
|
22413
22895
|
return true;
|
|
22414
|
-
return
|
|
22896
|
+
return path29.basename(root) === "workflow-toolkit";
|
|
22415
22897
|
};
|
|
22416
22898
|
var licenseHolder = (root) => {
|
|
22417
22899
|
const pkg = packageJson(root);
|
|
@@ -22434,13 +22916,13 @@ var planHygieneFiles = (root, opts = {}) => {
|
|
|
22434
22916
|
files.push("LICENSE", "CONTRIBUTING.md");
|
|
22435
22917
|
const planned = [];
|
|
22436
22918
|
for (const file of files) {
|
|
22437
|
-
if (existsSync10(
|
|
22919
|
+
if (existsSync10(path29.join(root, file)))
|
|
22438
22920
|
continue;
|
|
22439
|
-
const tpl =
|
|
22921
|
+
const tpl = path29.join(templatesDir(), file);
|
|
22440
22922
|
if (!existsSync10(tpl))
|
|
22441
22923
|
continue;
|
|
22442
|
-
const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g,
|
|
22443
|
-
planned.push({ path:
|
|
22924
|
+
const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g, path29.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
|
|
22925
|
+
planned.push({ path: path29.join(root, file), content });
|
|
22444
22926
|
}
|
|
22445
22927
|
return planned;
|
|
22446
22928
|
};
|
|
@@ -22450,19 +22932,19 @@ var ensureHygieneFiles = (root, opts) => {
|
|
|
22450
22932
|
const created = [];
|
|
22451
22933
|
for (const planned of planHygieneFiles(root, opts)) {
|
|
22452
22934
|
writeFileSync5(planned.path, planned.content, "utf8");
|
|
22453
|
-
created.push(
|
|
22935
|
+
created.push(path29.basename(planned.path));
|
|
22454
22936
|
}
|
|
22455
22937
|
return { ok: true, created };
|
|
22456
22938
|
};
|
|
22457
22939
|
|
|
22458
22940
|
// packages/workit-core/src/core/init.ts
|
|
22459
|
-
import
|
|
22460
|
-
import
|
|
22461
|
-
import { spawnSync as
|
|
22941
|
+
import fs19 from "node:fs";
|
|
22942
|
+
import path31 from "node:path";
|
|
22943
|
+
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
22462
22944
|
|
|
22463
22945
|
// packages/workit-core/src/core/workspace-branch-policy.ts
|
|
22464
22946
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
22465
|
-
import
|
|
22947
|
+
import path30 from "node:path";
|
|
22466
22948
|
import { isDeepStrictEqual } from "node:util";
|
|
22467
22949
|
var readFileSafe = (p) => {
|
|
22468
22950
|
try {
|
|
@@ -22473,7 +22955,7 @@ var readFileSafe = (p) => {
|
|
|
22473
22955
|
};
|
|
22474
22956
|
function applyWorkspaceBranchPolicy(opts) {
|
|
22475
22957
|
const { workspace_root, env = process.env } = opts;
|
|
22476
|
-
const dir =
|
|
22958
|
+
const dir = path30.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
22477
22959
|
const {
|
|
22478
22960
|
status,
|
|
22479
22961
|
path: wsPath,
|
|
@@ -22485,7 +22967,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
22485
22967
|
if (status === "malformed" || status === "invalid")
|
|
22486
22968
|
return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
|
|
22487
22969
|
const detection = detectBranchPolicy(workspace_root);
|
|
22488
|
-
const name = String(env.WORKFLOW_BP_NAME ??
|
|
22970
|
+
const name = String(env.WORKFLOW_BP_NAME ?? path30.basename(workspace_root));
|
|
22489
22971
|
const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
|
|
22490
22972
|
const policy = {
|
|
22491
22973
|
preset: detection.preset,
|
|
@@ -22534,7 +23016,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
22534
23016
|
ok: false,
|
|
22535
23017
|
error: `workspaces.json changed while applying the branch policy: ${wsPath}`
|
|
22536
23018
|
};
|
|
22537
|
-
mkdirSync4(
|
|
23019
|
+
mkdirSync4(path30.dirname(wsPath), { recursive: true });
|
|
22538
23020
|
const tmp = `${wsPath}.${process.pid}.tmp`;
|
|
22539
23021
|
try {
|
|
22540
23022
|
writeFileSync6(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + `
|
|
@@ -22569,7 +23051,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
22569
23051
|
var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
|
|
22570
23052
|
var readJson = (p) => {
|
|
22571
23053
|
try {
|
|
22572
|
-
const parsed = JSON.parse(
|
|
23054
|
+
const parsed = JSON.parse(fs19.readFileSync(p, "utf8"));
|
|
22573
23055
|
return isConfigObject(parsed) ? parsed : null;
|
|
22574
23056
|
} catch {
|
|
22575
23057
|
return null;
|
|
@@ -22581,7 +23063,7 @@ var resolveInitProvider = (cwd) => {
|
|
|
22581
23063
|
return env.toLowerCase();
|
|
22582
23064
|
const root = cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
22583
23065
|
try {
|
|
22584
|
-
const out =
|
|
23066
|
+
const out = spawnSync19("git", ["remote", "get-url", "origin"], {
|
|
22585
23067
|
cwd: root,
|
|
22586
23068
|
encoding: "utf8"
|
|
22587
23069
|
});
|
|
@@ -22599,7 +23081,7 @@ var youtrackJsonContent = (dir) => {
|
|
|
22599
23081
|
const meetingIssue = process.env.WORKFLOW_YT_MEETING_ISSUE;
|
|
22600
23082
|
return {
|
|
22601
23083
|
baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://youtrack.example.com",
|
|
22602
|
-
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ??
|
|
23084
|
+
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path31.join(dir, "youtrack.token"),
|
|
22603
23085
|
...meetingIssue ? {
|
|
22604
23086
|
meetingIssue,
|
|
22605
23087
|
meetingIssues: { general: { issue: meetingIssue, label: "General meetings" } }
|
|
@@ -22629,19 +23111,19 @@ var vcsJsonContent = () => {
|
|
|
22629
23111
|
};
|
|
22630
23112
|
function initApplyData(action, env = process.env) {
|
|
22631
23113
|
const dir = String(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
22632
|
-
|
|
23114
|
+
fs19.mkdirSync(dir, { recursive: true });
|
|
22633
23115
|
switch (action) {
|
|
22634
23116
|
case "youtrack_json": {
|
|
22635
|
-
const out =
|
|
22636
|
-
|
|
23117
|
+
const out = path31.join(dir, "youtrack.json");
|
|
23118
|
+
fs19.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
22637
23119
|
`, "utf8");
|
|
22638
23120
|
return { action, ok: true, path: out };
|
|
22639
23121
|
}
|
|
22640
23122
|
case "youtrack_token_placeholder": {
|
|
22641
|
-
const p =
|
|
23123
|
+
const p = path31.join(dir, "youtrack.token");
|
|
22642
23124
|
const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER2 + `
|
|
22643
23125
|
`, 384) === "preserved";
|
|
22644
|
-
const abs =
|
|
23126
|
+
const abs = path31.resolve(p);
|
|
22645
23127
|
return {
|
|
22646
23128
|
action,
|
|
22647
23129
|
ok: true,
|
|
@@ -22653,14 +23135,14 @@ function initApplyData(action, env = process.env) {
|
|
|
22653
23135
|
};
|
|
22654
23136
|
}
|
|
22655
23137
|
case "youtrack_scaffold": {
|
|
22656
|
-
const jsonOut =
|
|
22657
|
-
const tokenOut =
|
|
22658
|
-
|
|
23138
|
+
const jsonOut = path31.join(dir, "youtrack.json");
|
|
23139
|
+
const tokenOut = path31.join(dir, "youtrack.token");
|
|
23140
|
+
fs19.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
22659
23141
|
`, "utf8");
|
|
22660
23142
|
const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER2 + `
|
|
22661
23143
|
`, 384) === "preserved";
|
|
22662
|
-
const configPath =
|
|
22663
|
-
const tokenPath =
|
|
23144
|
+
const configPath = path31.resolve(jsonOut);
|
|
23145
|
+
const tokenPath = path31.resolve(tokenOut);
|
|
22664
23146
|
const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
|
|
22665
23147
|
process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
|
|
22666
23148
|
let tokenCreate = {};
|
|
@@ -22712,10 +23194,10 @@ function initApplyData(action, env = process.env) {
|
|
|
22712
23194
|
}
|
|
22713
23195
|
}
|
|
22714
23196
|
case "vcs_scaffold": {
|
|
22715
|
-
const jsonOut =
|
|
22716
|
-
|
|
23197
|
+
const jsonOut = path31.join(dir, "vcs.json");
|
|
23198
|
+
fs19.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
|
|
22717
23199
|
`, "utf8");
|
|
22718
|
-
const configPath =
|
|
23200
|
+
const configPath = path31.resolve(jsonOut);
|
|
22719
23201
|
const prev = process.env.WORKFLOW_VCS_CONFIG;
|
|
22720
23202
|
process.env.WORKFLOW_VCS_CONFIG = configPath;
|
|
22721
23203
|
try {
|
|
@@ -22834,7 +23316,7 @@ var executeInitApply = (args, directory, runtime) => {
|
|
|
22834
23316
|
protectedNames: args.branch_policy_protected
|
|
22835
23317
|
}, current);
|
|
22836
23318
|
writeConfig(next);
|
|
22837
|
-
return output(ok({ action: "config", path:
|
|
23319
|
+
return output(ok({ action: "config", path: path32.join(configDir(), "config.json"), ...next }));
|
|
22838
23320
|
}
|
|
22839
23321
|
const env = Object.fromEntries(Object.entries({
|
|
22840
23322
|
WORKFLOW_YT_BASE_URL: args.base_url,
|
|
@@ -22851,14 +23333,14 @@ var executeInitApply = (args, directory, runtime) => {
|
|
|
22851
23333
|
|
|
22852
23334
|
// packages/workit-opencode/src/shared/session.ts
|
|
22853
23335
|
import { realpathSync as realpathSync7 } from "node:fs";
|
|
22854
|
-
import
|
|
23336
|
+
import path33 from "node:path";
|
|
22855
23337
|
var sameWorkspace = (expected, observed) => {
|
|
22856
23338
|
if (typeof observed !== "string" || !observed)
|
|
22857
23339
|
return false;
|
|
22858
23340
|
try {
|
|
22859
23341
|
return realpathSync7(expected) === realpathSync7(observed);
|
|
22860
23342
|
} catch {
|
|
22861
|
-
return
|
|
23343
|
+
return path33.resolve(expected) === path33.resolve(observed);
|
|
22862
23344
|
}
|
|
22863
23345
|
};
|
|
22864
23346
|
|
|
@@ -22930,7 +23412,7 @@ var workitFamilyOf = (toolName) => {
|
|
|
22930
23412
|
|
|
22931
23413
|
// packages/workit-opencode/src/tools/context.ts
|
|
22932
23414
|
import { realpathSync as realpathSync8 } from "node:fs";
|
|
22933
|
-
import
|
|
23415
|
+
import path34 from "node:path";
|
|
22934
23416
|
var createContextTool = () => ({
|
|
22935
23417
|
description: "Read Git, pull request, YouTrack, issue, changelog, release, or affected-file context.",
|
|
22936
23418
|
execute: async (args, context) => {
|
|
@@ -22940,7 +23422,7 @@ var createContextTool = () => ({
|
|
|
22940
23422
|
let root;
|
|
22941
23423
|
try {
|
|
22942
23424
|
const cwd = parsed.data.cwd;
|
|
22943
|
-
root = realpathSync8(cwd ?
|
|
23425
|
+
root = realpathSync8(cwd ? path34.resolve(context.directory, cwd) : context.directory);
|
|
22944
23426
|
} catch {
|
|
22945
23427
|
return JSON.stringify(failure2("invalid_input", "context target directory cannot be resolved"), null, 2);
|
|
22946
23428
|
}
|
|
@@ -23484,8 +23966,9 @@ var evaluateShellPermission = (root, event, workdirs = new Map) => {
|
|
|
23484
23966
|
event.message = reason;
|
|
23485
23967
|
};
|
|
23486
23968
|
const resources = event.resources.filter((resource) => typeof resource === "string");
|
|
23969
|
+
const owner = liveOwner({ host: "opencode", session: { id: event.sessionID ?? "", parentId: null } }, "in-process-plugin");
|
|
23487
23970
|
if (WRITE_ACTIONS.has(event.action)) {
|
|
23488
|
-
const decision = writeGate(root, resources);
|
|
23971
|
+
const decision = writeGate(root, resources, owner);
|
|
23489
23972
|
if (decision.kind === "deny")
|
|
23490
23973
|
deny(decision.reason);
|
|
23491
23974
|
return;
|
|
@@ -23507,9 +23990,8 @@ var evaluateShellPermission = (root, event, workdirs = new Map) => {
|
|
|
23507
23990
|
}, resource, { nudge: false });
|
|
23508
23991
|
if (raw.kind === "deny")
|
|
23509
23992
|
return deny(raw.reason);
|
|
23510
|
-
const
|
|
23511
|
-
|
|
23512
|
-
if (gated?.kind === "deny")
|
|
23993
|
+
const gated = shellGate(cwd, resource, owner);
|
|
23994
|
+
if (gated.kind === "deny")
|
|
23513
23995
|
return deny(gated.reason);
|
|
23514
23996
|
}
|
|
23515
23997
|
};
|
|
@@ -23533,7 +24015,7 @@ var childTaskMissing = (store, input) => {
|
|
|
23533
24015
|
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`;
|
|
23534
24016
|
|
|
23535
24017
|
// packages/workit-opencode/src/v2/shell.ts
|
|
23536
|
-
import
|
|
24018
|
+
import path35 from "node:path";
|
|
23537
24019
|
var commandOf = (input) => {
|
|
23538
24020
|
if (input === null || typeof input !== "object")
|
|
23539
24021
|
return null;
|
|
@@ -23542,7 +24024,7 @@ var commandOf = (input) => {
|
|
|
23542
24024
|
};
|
|
23543
24025
|
var hookInput = (root, sessionID, id, shell) => ({
|
|
23544
24026
|
host: "opencode",
|
|
23545
|
-
cwd: shell.workdir ?
|
|
24027
|
+
cwd: shell.workdir ? path35.resolve(root, shell.workdir) : root,
|
|
23546
24028
|
session: { id: sessionID, agentId: null, agentType: null, parentId: null },
|
|
23547
24029
|
permissionMode: null,
|
|
23548
24030
|
transcriptPath: null,
|
|
@@ -23727,37 +24209,37 @@ var createStopControl = (root, port) => {
|
|
|
23727
24209
|
|
|
23728
24210
|
// packages/workit-opencode/src/v2/registry.ts
|
|
23729
24211
|
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
|
|
23730
|
-
import
|
|
24212
|
+
import path37 from "node:path";
|
|
23731
24213
|
|
|
23732
24214
|
// packages/workit-opencode/src/shared/assets.ts
|
|
23733
24215
|
import { existsSync as existsSync11, realpathSync as realpathSync9 } from "node:fs";
|
|
23734
|
-
import
|
|
24216
|
+
import path36 from "node:path";
|
|
23735
24217
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
23736
24218
|
var moduleDir = () => {
|
|
23737
24219
|
try {
|
|
23738
|
-
return realpathSync9(
|
|
24220
|
+
return realpathSync9(path36.dirname(fileURLToPath2(import.meta.url)));
|
|
23739
24221
|
} catch {
|
|
23740
|
-
return
|
|
24222
|
+
return path36.dirname(fileURLToPath2(import.meta.url));
|
|
23741
24223
|
}
|
|
23742
24224
|
};
|
|
23743
24225
|
var packageRoot2 = () => {
|
|
23744
24226
|
const dir = moduleDir();
|
|
23745
24227
|
for (const candidate of [
|
|
23746
|
-
|
|
23747
|
-
|
|
23748
|
-
|
|
24228
|
+
path36.resolve(dir, ".."),
|
|
24229
|
+
path36.resolve(dir, "..", ".."),
|
|
24230
|
+
path36.resolve(dir, "..", "..", "..")
|
|
23749
24231
|
]) {
|
|
23750
|
-
if (existsSync11(
|
|
24232
|
+
if (existsSync11(path36.join(candidate, "package.json")))
|
|
23751
24233
|
return candidate;
|
|
23752
24234
|
}
|
|
23753
|
-
return
|
|
24235
|
+
return path36.resolve(dir, "..");
|
|
23754
24236
|
};
|
|
23755
24237
|
var skillsRoot = () => {
|
|
23756
24238
|
const root = packageRoot2();
|
|
23757
|
-
const canonical =
|
|
23758
|
-
if (existsSync11(
|
|
24239
|
+
const canonical = path36.resolve(root, "..", "workit-core", "skills");
|
|
24240
|
+
if (existsSync11(path36.join(root, "src")) && existsSync11(canonical))
|
|
23759
24241
|
return canonical;
|
|
23760
|
-
return
|
|
24242
|
+
return path36.join(root, "assets", "skills");
|
|
23761
24243
|
};
|
|
23762
24244
|
|
|
23763
24245
|
// packages/workit-opencode/src/v2/registry.ts
|
|
@@ -23784,11 +24266,11 @@ var registerSkills = async (ctx) => {
|
|
|
23784
24266
|
if (!existsSync12(skillsDir))
|
|
23785
24267
|
return new Set;
|
|
23786
24268
|
const listed = await ctx.skill.list().catch(() => ({ data: [] }));
|
|
23787
|
-
const own = (skill) => typeof skill.path === "string" &&
|
|
24269
|
+
const own = (skill) => typeof skill.path === "string" && path37.resolve(skill.path).startsWith(skillsDir + path37.sep);
|
|
23788
24270
|
const userSkills = new Set((listed?.data ?? []).filter((skill) => !own(skill)).map((skill) => skill.id));
|
|
23789
24271
|
const packaged = new Map;
|
|
23790
24272
|
for (const id of WORKIT_METHOD_SKILLS) {
|
|
23791
|
-
const file =
|
|
24273
|
+
const file = path37.join(skillsDir, id, "SKILL.md");
|
|
23792
24274
|
if (!userSkills.has(id) && existsSync12(file))
|
|
23793
24275
|
packaged.set(id, file);
|
|
23794
24276
|
}
|
|
@@ -23836,22 +24318,22 @@ var registerCommands = async (ctx, registeredSkills) => {
|
|
|
23836
24318
|
|
|
23837
24319
|
// packages/workit-opencode/src/stale-sources.ts
|
|
23838
24320
|
import { existsSync as existsSync13, readdirSync as readdirSync6 } from "node:fs";
|
|
23839
|
-
import
|
|
24321
|
+
import path38 from "node:path";
|
|
23840
24322
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
23841
24323
|
var typescriptFiles = (directory) => {
|
|
23842
24324
|
if (!existsSync13(directory))
|
|
23843
24325
|
return [];
|
|
23844
24326
|
return readdirSync6(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
23845
|
-
const file =
|
|
24327
|
+
const file = path38.join(directory, entry.name);
|
|
23846
24328
|
return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
|
|
23847
24329
|
});
|
|
23848
24330
|
};
|
|
23849
24331
|
var pluginSourceFiles = [
|
|
23850
24332
|
...new Set([
|
|
23851
24333
|
fileURLToPath3(import.meta.url),
|
|
23852
|
-
...typescriptFiles(
|
|
23853
|
-
|
|
23854
|
-
...typescriptFiles(
|
|
24334
|
+
...typescriptFiles(path38.join(packageRoot2(), "src")),
|
|
24335
|
+
path38.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
|
|
24336
|
+
...typescriptFiles(path38.join(packageRoot2(), "..", "workit-core", "src", "core"))
|
|
23855
24337
|
])
|
|
23856
24338
|
].filter((file) => existsSync13(file)).toSorted();
|
|
23857
24339
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-opencode",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.14.1",
|
|
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.14.1",
|
|
43
43
|
"@opencode/plugin": "2.0.18"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|