@brainervirus/workit-pi 8.25.0 → 8.26.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/worker.js +427 -50
- package/dist/workit.js +123 -1
- package/package.json +1 -1
- package/skills/workit-fanout/SKILL.md +4 -4
- package/skills/workit-fanout/references/orchestration.md +26 -17
- package/skills/workit-implement/SKILL.md +1 -1
- package/skills/workit-review/SKILL.md +2 -1
- package/skills/workit-ship/SKILL.md +1 -1
package/dist/worker.js
CHANGED
|
@@ -6669,6 +6669,41 @@ var ENVELOPE = new Set([
|
|
|
6669
6669
|
"judgment"
|
|
6670
6670
|
]);
|
|
6671
6671
|
// packages/workit-core/src/core/config.ts
|
|
6672
|
+
import {
|
|
6673
|
+
copyFileSync,
|
|
6674
|
+
cpSync,
|
|
6675
|
+
existsSync,
|
|
6676
|
+
mkdirSync,
|
|
6677
|
+
readFileSync,
|
|
6678
|
+
readdirSync,
|
|
6679
|
+
writeFileSync
|
|
6680
|
+
} from "node:fs";
|
|
6681
|
+
import os from "node:os";
|
|
6682
|
+
import path from "node:path";
|
|
6683
|
+
|
|
6684
|
+
// packages/workit-core/src/core/boundary.ts
|
|
6685
|
+
var EVENT = {
|
|
6686
|
+
initialization: "initialization",
|
|
6687
|
+
provenance: "provenance",
|
|
6688
|
+
assets: "assets",
|
|
6689
|
+
configurationSource: "configuration_source",
|
|
6690
|
+
hooks: "hooks",
|
|
6691
|
+
mcpConnection: "mcp_connection",
|
|
6692
|
+
migration: "migration",
|
|
6693
|
+
installSteps: "install_steps",
|
|
6694
|
+
uncaughtFailure: "uncaught_failure",
|
|
6695
|
+
toolsFailed: "tools_failed",
|
|
6696
|
+
doctor: "doctor"
|
|
6697
|
+
};
|
|
6698
|
+
var errorDetail = (err) => {
|
|
6699
|
+
if (err instanceof Error) {
|
|
6700
|
+
return { error_name: err.name, error: err.message };
|
|
6701
|
+
}
|
|
6702
|
+
return { error_name: "unknown", error: String(err) };
|
|
6703
|
+
};
|
|
6704
|
+
|
|
6705
|
+
// packages/workit-core/src/core/config.ts
|
|
6706
|
+
var diagnosticLogger;
|
|
6672
6707
|
var PRESETS = {
|
|
6673
6708
|
gitflow: {
|
|
6674
6709
|
allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
|
|
@@ -6678,6 +6713,49 @@ var PRESETS = {
|
|
|
6678
6713
|
"trunk-based": { allowed: ["*"], protected: ["main"] },
|
|
6679
6714
|
custom: { allowed: [], protected: [] }
|
|
6680
6715
|
};
|
|
6716
|
+
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "workit");
|
|
6717
|
+
var migratedDir = null;
|
|
6718
|
+
var migrationFailed = false;
|
|
6719
|
+
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
6720
|
+
if (migratedDir === dir)
|
|
6721
|
+
return dir;
|
|
6722
|
+
if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
|
|
6723
|
+
migratedDir = dir;
|
|
6724
|
+
return dir;
|
|
6725
|
+
}
|
|
6726
|
+
const legacy = path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "workflow-toolkit");
|
|
6727
|
+
if (!existsSync(legacy)) {
|
|
6728
|
+
migratedDir = dir;
|
|
6729
|
+
return dir;
|
|
6730
|
+
}
|
|
6731
|
+
if (!migrationFailed && existsSync(dir)) {
|
|
6732
|
+
migratedDir = dir;
|
|
6733
|
+
return dir;
|
|
6734
|
+
}
|
|
6735
|
+
migrationFailed = false;
|
|
6736
|
+
mkdirSync(dir, { recursive: true });
|
|
6737
|
+
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
6738
|
+
for (const entry of readdirSync(legacy, { withFileTypes: true })) {
|
|
6739
|
+
const src = path.join(legacy, entry.name);
|
|
6740
|
+
const dest = path.join(dir, entry.name);
|
|
6741
|
+
if (existsSync(dest))
|
|
6742
|
+
continue;
|
|
6743
|
+
try {
|
|
6744
|
+
if (entry.isDirectory())
|
|
6745
|
+
cpSync(src, dest, { recursive: true });
|
|
6746
|
+
else if (entry.isFile())
|
|
6747
|
+
copyFileSync(src, dest);
|
|
6748
|
+
} catch (err) {
|
|
6749
|
+
migrationFailed = true;
|
|
6750
|
+
diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
|
|
6751
|
+
console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
|
|
6752
|
+
}
|
|
6753
|
+
}
|
|
6754
|
+
if (!migrationFailed)
|
|
6755
|
+
migratedDir = dir;
|
|
6756
|
+
return dir;
|
|
6757
|
+
};
|
|
6758
|
+
var configDir = () => ensureConfigDir();
|
|
6681
6759
|
var DEFAULTS = {
|
|
6682
6760
|
locale: "en",
|
|
6683
6761
|
localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
|
|
@@ -6699,7 +6777,9 @@ var COMMIT_PRESETS = [
|
|
|
6699
6777
|
];
|
|
6700
6778
|
|
|
6701
6779
|
// packages/workit-core/src/core/workspaces.ts
|
|
6702
|
-
import
|
|
6780
|
+
import { spawnSync } from "node:child_process";
|
|
6781
|
+
import { readFileSync as readFileSync2, realpathSync } from "node:fs";
|
|
6782
|
+
import path2 from "node:path";
|
|
6703
6783
|
var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
|
|
6704
6784
|
var branchPreset = custom((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
|
|
6705
6785
|
var commitPreset = custom((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
|
|
@@ -6757,7 +6837,7 @@ var releaseTrackSchema = object({
|
|
|
6757
6837
|
context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
|
|
6758
6838
|
}
|
|
6759
6839
|
}
|
|
6760
|
-
if (track.versionSource?.kind === "package-json" && (
|
|
6840
|
+
if (track.versionSource?.kind === "package-json" && (path2.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
|
|
6761
6841
|
context.addIssue({
|
|
6762
6842
|
code: "custom",
|
|
6763
6843
|
path: ["versionSource", "path"],
|
|
@@ -6781,6 +6861,7 @@ var workspaceConfigSchema = object({
|
|
|
6781
6861
|
trailers: boolean2().optional(),
|
|
6782
6862
|
liveSessionGuard: boolean2().optional(),
|
|
6783
6863
|
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
6864
|
+
models: object({ roles: unknown().optional() }).passthrough().optional(),
|
|
6784
6865
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
6785
6866
|
kind,
|
|
6786
6867
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -6807,7 +6888,10 @@ var workspaceConfigSchema = object({
|
|
|
6807
6888
|
});
|
|
6808
6889
|
}
|
|
6809
6890
|
});
|
|
6810
|
-
var workspacesFileSchema = object({
|
|
6891
|
+
var workspacesFileSchema = object({
|
|
6892
|
+
workspaces: array(workspaceConfigSchema).optional(),
|
|
6893
|
+
models: object({ roles: unknown().optional() }).passthrough().optional()
|
|
6894
|
+
}).passthrough().superRefine((file, context) => {
|
|
6811
6895
|
const names = new Set;
|
|
6812
6896
|
for (const [index, workspace] of (file.workspaces ?? []).entries()) {
|
|
6813
6897
|
if (names.has(workspace.name)) {
|
|
@@ -6820,10 +6904,213 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
|
|
|
6820
6904
|
names.add(workspace.name);
|
|
6821
6905
|
}
|
|
6822
6906
|
});
|
|
6907
|
+
var globToRegExp = (glob) => {
|
|
6908
|
+
let out = "";
|
|
6909
|
+
for (let i = 0;i < glob.length; i++) {
|
|
6910
|
+
const c = glob[i];
|
|
6911
|
+
if (c === "*") {
|
|
6912
|
+
if (glob[i + 1] === "*") {
|
|
6913
|
+
if (glob[i + 2] === "/") {
|
|
6914
|
+
if (out === "")
|
|
6915
|
+
out += "/?";
|
|
6916
|
+
out += "(?:[^/]+/)*";
|
|
6917
|
+
i += 2;
|
|
6918
|
+
} else {
|
|
6919
|
+
if (i + 2 >= glob.length) {
|
|
6920
|
+
if (out.endsWith("/")) {
|
|
6921
|
+
out = out.slice(0, -1);
|
|
6922
|
+
out += "(?:/.*)?";
|
|
6923
|
+
} else {
|
|
6924
|
+
out += ".*";
|
|
6925
|
+
}
|
|
6926
|
+
} else {
|
|
6927
|
+
out += ".*";
|
|
6928
|
+
}
|
|
6929
|
+
i++;
|
|
6930
|
+
}
|
|
6931
|
+
} else {
|
|
6932
|
+
out += "[^/]*";
|
|
6933
|
+
}
|
|
6934
|
+
} else {
|
|
6935
|
+
out += c.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
6936
|
+
}
|
|
6937
|
+
}
|
|
6938
|
+
return new RegExp(`^${out}$`);
|
|
6939
|
+
};
|
|
6940
|
+
var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
|
|
6941
|
+
var UNSUPPORTED_GLOB = /[?[\]{}]/;
|
|
6942
|
+
var UNSUPPORTED_EXTGLOB = /[@+*]\(|!/;
|
|
6943
|
+
var validateWorkspaceGlob = (glob) => {
|
|
6944
|
+
const trimmed = glob.trim();
|
|
6945
|
+
if (!trimmed)
|
|
6946
|
+
return { ok: false, error: "workspace pattern is required" };
|
|
6947
|
+
const m = UNSUPPORTED_GLOB.exec(trimmed);
|
|
6948
|
+
if (m) {
|
|
6949
|
+
return {
|
|
6950
|
+
ok: false,
|
|
6951
|
+
error: `unsupported glob character ${JSON.stringify(m[0])} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
|
|
6952
|
+
};
|
|
6953
|
+
}
|
|
6954
|
+
const ext = UNSUPPORTED_EXTGLOB.exec(trimmed);
|
|
6955
|
+
if (ext) {
|
|
6956
|
+
const token = ext[0];
|
|
6957
|
+
const kind = token === "!" ? "negation (!)" : `extglob (${token})`;
|
|
6958
|
+
return {
|
|
6959
|
+
ok: false,
|
|
6960
|
+
error: `unsupported glob ${kind} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
|
|
6961
|
+
};
|
|
6962
|
+
}
|
|
6963
|
+
return { ok: true };
|
|
6964
|
+
};
|
|
6965
|
+
|
|
6966
|
+
class WorkspaceConfigError extends Error {
|
|
6967
|
+
name = "WorkspaceConfigError";
|
|
6968
|
+
}
|
|
6823
6969
|
var mainCheckouts = new Map;
|
|
6970
|
+
var mainCheckoutOf = (cwd) => {
|
|
6971
|
+
const cached = mainCheckouts.get(cwd);
|
|
6972
|
+
if (cached !== undefined)
|
|
6973
|
+
return cached;
|
|
6974
|
+
const env = { ...process.env };
|
|
6975
|
+
for (const name of ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"])
|
|
6976
|
+
delete env[name];
|
|
6977
|
+
const git = (args) => {
|
|
6978
|
+
const run = spawnSync("git", args, {
|
|
6979
|
+
cwd,
|
|
6980
|
+
env,
|
|
6981
|
+
encoding: "utf8",
|
|
6982
|
+
timeout: 5000,
|
|
6983
|
+
windowsHide: true
|
|
6984
|
+
});
|
|
6985
|
+
return run.status === 0 ? run.stdout : null;
|
|
6986
|
+
};
|
|
6987
|
+
let main = null;
|
|
6988
|
+
const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
|
|
6989
|
+
const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
|
|
6990
|
+
if (gitDir && common && path2.resolve(gitDir) !== path2.resolve(common)) {
|
|
6991
|
+
const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
|
|
6992
|
+
main = listed?.trim() || (path2.basename(common) === ".git" ? path2.dirname(common) : common);
|
|
6993
|
+
}
|
|
6994
|
+
mainCheckouts.set(cwd, main);
|
|
6995
|
+
return main;
|
|
6996
|
+
};
|
|
6997
|
+
var matchingEntries = (cwd, entries) => {
|
|
6998
|
+
const match = (where) => {
|
|
6999
|
+
const targets = [where, realpathOf(where)].map((value) => value.replaceAll("\\", "/"));
|
|
7000
|
+
return entries.filter((entry) => {
|
|
7001
|
+
const glob = entry.glob.replaceAll("\\", "/");
|
|
7002
|
+
const canonical = canonicalGlob(glob);
|
|
7003
|
+
return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
|
|
7004
|
+
});
|
|
7005
|
+
};
|
|
7006
|
+
const direct = match(cwd);
|
|
7007
|
+
if (direct.length)
|
|
7008
|
+
return direct;
|
|
7009
|
+
const main = mainCheckoutOf(cwd);
|
|
7010
|
+
return main ? match(main) : direct;
|
|
7011
|
+
};
|
|
7012
|
+
var realpathOf = (p) => {
|
|
7013
|
+
const real = realpathSync.native ?? realpathSync;
|
|
7014
|
+
try {
|
|
7015
|
+
return real(p);
|
|
7016
|
+
} catch {
|
|
7017
|
+
return p;
|
|
7018
|
+
}
|
|
7019
|
+
};
|
|
7020
|
+
var canonicalGlob = (glob) => {
|
|
7021
|
+
const m = /[*?[\]{]/.exec(glob);
|
|
7022
|
+
const prefix = m ? glob.slice(0, m.index) : glob;
|
|
7023
|
+
const rest = m ? glob.slice(m.index) : "";
|
|
7024
|
+
if (!prefix)
|
|
7025
|
+
return glob;
|
|
7026
|
+
const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
|
|
7027
|
+
return real + (prefix.endsWith("/") ? "/" : "") + rest;
|
|
7028
|
+
};
|
|
7029
|
+
var globSpecificity = (glob) => glob.replaceAll("\\", "/").split("/").filter((segment) => segment.length > 0 && !/[*?[\]{]/.test(segment)).length;
|
|
7030
|
+
var selectWorkspaceMatch = (matches, cwd, workspaceName) => {
|
|
7031
|
+
if (workspaceName !== undefined) {
|
|
7032
|
+
const named = matches.filter((entry) => entry.name === workspaceName);
|
|
7033
|
+
if (named.length === 1)
|
|
7034
|
+
return named[0];
|
|
7035
|
+
if (named.length > 1)
|
|
7036
|
+
throw new WorkspaceConfigError(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
|
|
7037
|
+
if (matches.length > 0) {
|
|
7038
|
+
throw new WorkspaceConfigError(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
|
|
7039
|
+
}
|
|
7040
|
+
throw new WorkspaceConfigError(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
|
|
7041
|
+
}
|
|
7042
|
+
if (matches.length < 2)
|
|
7043
|
+
return matches[0];
|
|
7044
|
+
const highestSpecificity = Math.max(...matches.map((entry) => globSpecificity(entry.glob)));
|
|
7045
|
+
const mostSpecific = matches.filter((entry) => globSpecificity(entry.glob) === highestSpecificity);
|
|
7046
|
+
if (mostSpecific.length > 1) {
|
|
7047
|
+
throw new WorkspaceConfigError(`ambiguous workspace for ${cwd}; choose one of: ${mostSpecific.map((entry) => entry.name).join(", ")}`);
|
|
7048
|
+
}
|
|
7049
|
+
return mostSpecific[0];
|
|
7050
|
+
};
|
|
6824
7051
|
var runtimeWorkspaceIndexSchema = object({
|
|
6825
7052
|
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
6826
7053
|
}).passthrough();
|
|
7054
|
+
var matchRuntimeWorkspace = (cwd, dir = configDir()) => {
|
|
7055
|
+
const file = path2.join(dir, "workspaces.json");
|
|
7056
|
+
let raw;
|
|
7057
|
+
try {
|
|
7058
|
+
raw = readFileSync2(file, "utf8");
|
|
7059
|
+
} catch (error) {
|
|
7060
|
+
if (error.code === "ENOENT")
|
|
7061
|
+
return null;
|
|
7062
|
+
throw new WorkspaceConfigError(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
7063
|
+
}
|
|
7064
|
+
let parsed;
|
|
7065
|
+
try {
|
|
7066
|
+
parsed = JSON.parse(raw);
|
|
7067
|
+
} catch {
|
|
7068
|
+
throw new WorkspaceConfigError(`${file} is not valid JSON`);
|
|
7069
|
+
}
|
|
7070
|
+
const validated = runtimeWorkspaceIndexSchema.safeParse(parsed);
|
|
7071
|
+
if (!validated.success) {
|
|
7072
|
+
const detail = validated.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}`).join("; ");
|
|
7073
|
+
throw new WorkspaceConfigError(`${file} has invalid workspace matching data: ${detail}`);
|
|
7074
|
+
}
|
|
7075
|
+
const entries = validated.data.workspaces;
|
|
7076
|
+
for (const entry of entries ?? []) {
|
|
7077
|
+
const glob = validateWorkspaceGlob(entry.glob);
|
|
7078
|
+
if (!glob.ok)
|
|
7079
|
+
throw new WorkspaceConfigError(`${file} workspace ${entry.name}: ${glob.error}`);
|
|
7080
|
+
}
|
|
7081
|
+
const matches = matchingEntries(cwd, entries ?? []);
|
|
7082
|
+
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
7083
|
+
const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
|
|
7084
|
+
return selected ? { file, selected, matches } : null;
|
|
7085
|
+
};
|
|
7086
|
+
var modelSources = (cwd, dir) => {
|
|
7087
|
+
const out = [];
|
|
7088
|
+
try {
|
|
7089
|
+
const matched = matchRuntimeWorkspace(cwd, dir);
|
|
7090
|
+
if (matched) {
|
|
7091
|
+
const enclosing = matched.matches.filter((entry) => entry !== matched.selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
7092
|
+
for (const entry of [matched.selected, ...enclosing])
|
|
7093
|
+
out.push({
|
|
7094
|
+
name: entry.name,
|
|
7095
|
+
global: false,
|
|
7096
|
+
models: entry.models,
|
|
7097
|
+
tierModels: entry.fanout?.tierModels
|
|
7098
|
+
});
|
|
7099
|
+
}
|
|
7100
|
+
} catch {}
|
|
7101
|
+
try {
|
|
7102
|
+
const document = JSON.parse(readFileSync2(path2.join(dir ?? configDir(), "workspaces.json"), "utf8"));
|
|
7103
|
+
if (document && typeof document === "object" && !Array.isArray(document))
|
|
7104
|
+
out.push({
|
|
7105
|
+
name: "global",
|
|
7106
|
+
global: true,
|
|
7107
|
+
models: document.models,
|
|
7108
|
+
tierModels: undefined
|
|
7109
|
+
});
|
|
7110
|
+
} catch {}
|
|
7111
|
+
return out;
|
|
7112
|
+
};
|
|
7113
|
+
var hostFamily = (host) => host.replace(/_(?:cli|desktop)$/u, "");
|
|
6827
7114
|
var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
|
|
6828
7115
|
var TIER_HOSTS = new Set([
|
|
6829
7116
|
"claude_code",
|
|
@@ -6837,11 +7124,11 @@ var TIER_HOSTS = new Set([
|
|
|
6837
7124
|
]);
|
|
6838
7125
|
|
|
6839
7126
|
// packages/workit-core/src/git/rev.ts
|
|
6840
|
-
import { spawnSync } from "node:child_process";
|
|
7127
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
6841
7128
|
var MAX_BUFFER = 256 * 1024 * 1024;
|
|
6842
7129
|
var GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
|
|
6843
7130
|
var git = (cwd, args, options = {}) => {
|
|
6844
|
-
const result =
|
|
7131
|
+
const result = spawnSync2("git", args, {
|
|
6845
7132
|
cwd,
|
|
6846
7133
|
encoding: "utf8",
|
|
6847
7134
|
env: options.env ?? process.env,
|
|
@@ -6890,7 +7177,7 @@ var memo2 = new Map;
|
|
|
6890
7177
|
var TRACKERS = new Set(["github", "gitlab", "youtrack"]);
|
|
6891
7178
|
|
|
6892
7179
|
// packages/workit-core/src/core/logger.ts
|
|
6893
|
-
import
|
|
7180
|
+
import os2 from "node:os";
|
|
6894
7181
|
var SENSITIVE_WORDS = new Set([
|
|
6895
7182
|
"token",
|
|
6896
7183
|
"secret",
|
|
@@ -6929,15 +7216,15 @@ var SENSITIVE_WORDS = new Set([
|
|
|
6929
7216
|
]);
|
|
6930
7217
|
var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
|
|
6931
7218
|
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6932
|
-
var homePattern = new RegExp(`${escapeRegExp(
|
|
7219
|
+
var homePattern = new RegExp(`${escapeRegExp(os2.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
6933
7220
|
|
|
6934
7221
|
// packages/workit-core/src/store/paths.ts
|
|
6935
|
-
import { spawnSync as
|
|
7222
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
6936
7223
|
import fs from "node:fs";
|
|
6937
|
-
import
|
|
7224
|
+
import path3 from "node:path";
|
|
6938
7225
|
var NOT_A_REPO = /not a git repository \(or any (of the parent directories|parent up to)/u;
|
|
6939
7226
|
var gitRun = (cwd, args) => {
|
|
6940
|
-
const run =
|
|
7227
|
+
const run = spawnSync3("git", args, {
|
|
6941
7228
|
cwd,
|
|
6942
7229
|
encoding: "utf8",
|
|
6943
7230
|
timeout: 20000,
|
|
@@ -6975,14 +7262,14 @@ var DISCOVERY_ENV = [
|
|
|
6975
7262
|
];
|
|
6976
7263
|
var isGitDir = (dir) => {
|
|
6977
7264
|
try {
|
|
6978
|
-
return fs.statSync(
|
|
7265
|
+
return fs.statSync(path3.join(dir, "HEAD")).isFile() && fs.statSync(path3.join(dir, "objects")).isDirectory() && fs.statSync(path3.join(dir, "refs")).isDirectory();
|
|
6979
7266
|
} catch {
|
|
6980
7267
|
return false;
|
|
6981
7268
|
}
|
|
6982
7269
|
};
|
|
6983
7270
|
var reftableRefs = (common) => {
|
|
6984
7271
|
try {
|
|
6985
|
-
return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs.readFileSync(
|
|
7272
|
+
return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs.readFileSync(path3.join(common, "config"), "utf8"));
|
|
6986
7273
|
} catch {
|
|
6987
7274
|
return false;
|
|
6988
7275
|
}
|
|
@@ -7002,7 +7289,7 @@ function fastGit(root) {
|
|
|
7002
7289
|
if (device !== null && stat.dev !== device)
|
|
7003
7290
|
return { kind: "none" };
|
|
7004
7291
|
device = stat.dev;
|
|
7005
|
-
const dotGit =
|
|
7292
|
+
const dotGit = path3.join(dir, ".git");
|
|
7006
7293
|
let entry = null;
|
|
7007
7294
|
try {
|
|
7008
7295
|
entry = fs.statSync(dotGit);
|
|
@@ -7023,19 +7310,19 @@ function fastGit(root) {
|
|
|
7023
7310
|
const match = /^gitdir: (.+)$/mu.exec(text);
|
|
7024
7311
|
if (!match)
|
|
7025
7312
|
return null;
|
|
7026
|
-
gitDir =
|
|
7313
|
+
gitDir = path3.resolve(dir, match[1].trim());
|
|
7027
7314
|
} else
|
|
7028
7315
|
return null;
|
|
7029
|
-
if (!fs.existsSync(
|
|
7316
|
+
if (!fs.existsSync(path3.join(gitDir, "HEAD")))
|
|
7030
7317
|
return null;
|
|
7031
7318
|
} else if (isGitDir(dir))
|
|
7032
7319
|
gitDir = dir;
|
|
7033
7320
|
if (gitDir) {
|
|
7034
7321
|
let common = gitDir;
|
|
7035
7322
|
try {
|
|
7036
|
-
common =
|
|
7323
|
+
common = path3.resolve(gitDir, fs.readFileSync(path3.join(gitDir, "commondir"), "utf8").trim());
|
|
7037
7324
|
} catch {}
|
|
7038
|
-
if (!fs.existsSync(
|
|
7325
|
+
if (!fs.existsSync(path3.join(common, "objects")))
|
|
7039
7326
|
return null;
|
|
7040
7327
|
return {
|
|
7041
7328
|
kind: "repo",
|
|
@@ -7045,26 +7332,26 @@ function fastGit(root) {
|
|
|
7045
7332
|
reftable: reftableRefs(common)
|
|
7046
7333
|
};
|
|
7047
7334
|
}
|
|
7048
|
-
const parent =
|
|
7335
|
+
const parent = path3.dirname(dir);
|
|
7049
7336
|
if (parent === dir)
|
|
7050
7337
|
return { kind: "none" };
|
|
7051
7338
|
dir = parent;
|
|
7052
7339
|
}
|
|
7053
7340
|
}
|
|
7054
7341
|
function resolveStore(root) {
|
|
7055
|
-
root =
|
|
7342
|
+
root = path3.resolve(root);
|
|
7056
7343
|
let directory = false;
|
|
7057
7344
|
try {
|
|
7058
7345
|
directory = fs.statSync(root).isDirectory();
|
|
7059
7346
|
} catch {}
|
|
7060
7347
|
if (!directory)
|
|
7061
|
-
return { dir:
|
|
7348
|
+
return { dir: path3.join(root, ".workit"), shared: false, top: null };
|
|
7062
7349
|
const fast = fastGit(root);
|
|
7063
7350
|
if (fast?.kind === "none")
|
|
7064
|
-
return { dir:
|
|
7351
|
+
return { dir: path3.join(root, ".workit"), shared: false, top: null };
|
|
7065
7352
|
if (fast?.kind === "repo")
|
|
7066
7353
|
return {
|
|
7067
|
-
dir:
|
|
7354
|
+
dir: path3.join(fast.common, "workit"),
|
|
7068
7355
|
shared: true,
|
|
7069
7356
|
top: fast.top,
|
|
7070
7357
|
git: { gitDir: fast.gitDir, top: fast.top, reftable: fast.reftable }
|
|
@@ -7074,13 +7361,13 @@ function resolveStore(root) {
|
|
|
7074
7361
|
if (run.ok && common) {
|
|
7075
7362
|
const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
|
|
7076
7363
|
return {
|
|
7077
|
-
dir:
|
|
7364
|
+
dir: path3.join(real(path3.resolve(root, common)), "workit"),
|
|
7078
7365
|
shared: true,
|
|
7079
7366
|
top: top.ok && top.stdout.trim() ? real(top.stdout.trim()) : null
|
|
7080
7367
|
};
|
|
7081
7368
|
}
|
|
7082
7369
|
if (run.missing || !run.ok && NOT_A_REPO.test(run.stderr))
|
|
7083
|
-
return { dir:
|
|
7370
|
+
return { dir: path3.join(root, ".workit"), shared: false, top: null };
|
|
7084
7371
|
return new Error(`cannot locate the workit store: git rev-parse failed (${run.stderr.trim().split(`
|
|
7085
7372
|
`)[0] || "no output"}); fix the repository (git status should work here)`);
|
|
7086
7373
|
}
|
|
@@ -7095,22 +7382,22 @@ function childSessionId(lead, child) {
|
|
|
7095
7382
|
}
|
|
7096
7383
|
|
|
7097
7384
|
// packages/workit-core/src/core/package-root.ts
|
|
7098
|
-
import { existsSync } from "node:fs";
|
|
7099
|
-
import
|
|
7385
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
7386
|
+
import path4 from "node:path";
|
|
7100
7387
|
import { fileURLToPath } from "node:url";
|
|
7101
7388
|
var packageRoot = () => {
|
|
7102
|
-
let dir =
|
|
7389
|
+
let dir = path4.dirname(fileURLToPath(import.meta.url));
|
|
7103
7390
|
while (true) {
|
|
7104
|
-
const parent =
|
|
7105
|
-
if (
|
|
7391
|
+
const parent = path4.dirname(dir);
|
|
7392
|
+
if (existsSync2(path4.join(dir, "package.json")) || parent === dir)
|
|
7106
7393
|
return dir;
|
|
7107
7394
|
dir = parent;
|
|
7108
7395
|
}
|
|
7109
7396
|
};
|
|
7110
7397
|
var assetRoot = () => {
|
|
7111
7398
|
const root = packageRoot();
|
|
7112
|
-
const assets =
|
|
7113
|
-
return
|
|
7399
|
+
const assets = path4.join(root, "assets");
|
|
7400
|
+
return existsSync2(assets) ? assets : root;
|
|
7114
7401
|
};
|
|
7115
7402
|
|
|
7116
7403
|
// packages/workit-core/src/core/store-lock.ts
|
|
@@ -7162,10 +7449,10 @@ var STALE_TEMPORARY_MS = 60 * 60000;
|
|
|
7162
7449
|
var origins = new Map;
|
|
7163
7450
|
var heldInProcess = new Map;
|
|
7164
7451
|
// packages/workit-core/src/ledger.ts
|
|
7165
|
-
import { spawnSync as
|
|
7452
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
7166
7453
|
import { createHash as createHash3, randomBytes } from "node:crypto";
|
|
7167
7454
|
import fs3 from "node:fs";
|
|
7168
|
-
import
|
|
7455
|
+
import path6 from "node:path";
|
|
7169
7456
|
|
|
7170
7457
|
// packages/workit-core/src/core/policy/substance.ts
|
|
7171
7458
|
var SLASH_BLOCK = [["/*", "*/"]];
|
|
@@ -7268,33 +7555,33 @@ var TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
|
|
|
7268
7555
|
// packages/workit-core/src/hooks/standing-signal.ts
|
|
7269
7556
|
import { createHash as createHash2 } from "node:crypto";
|
|
7270
7557
|
import fs2 from "node:fs";
|
|
7271
|
-
import
|
|
7558
|
+
import path5 from "node:path";
|
|
7272
7559
|
var gitStoreDir = (cwd) => {
|
|
7273
7560
|
try {
|
|
7274
|
-
for (let dir =
|
|
7275
|
-
const dotGit =
|
|
7561
|
+
for (let dir = path5.resolve(cwd);; dir = path5.dirname(dir)) {
|
|
7562
|
+
const dotGit = path5.join(dir, ".git");
|
|
7276
7563
|
if (fs2.existsSync(dotGit)) {
|
|
7277
7564
|
let gitDir = dotGit;
|
|
7278
7565
|
if (fs2.statSync(dotGit).isFile()) {
|
|
7279
7566
|
const target = /^gitdir:\s*(.+)$/mu.exec(fs2.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
7280
7567
|
if (!target)
|
|
7281
7568
|
return null;
|
|
7282
|
-
gitDir =
|
|
7283
|
-
const common =
|
|
7569
|
+
gitDir = path5.resolve(dir, target);
|
|
7570
|
+
const common = path5.join(gitDir, "commondir");
|
|
7284
7571
|
if (fs2.existsSync(common))
|
|
7285
|
-
gitDir =
|
|
7572
|
+
gitDir = path5.resolve(gitDir, fs2.readFileSync(common, "utf8").trim());
|
|
7286
7573
|
}
|
|
7287
|
-
return
|
|
7574
|
+
return path5.join(gitDir, "workit");
|
|
7288
7575
|
}
|
|
7289
|
-
if (
|
|
7576
|
+
if (path5.dirname(dir) === dir)
|
|
7290
7577
|
return null;
|
|
7291
7578
|
}
|
|
7292
7579
|
} catch {
|
|
7293
7580
|
return null;
|
|
7294
7581
|
}
|
|
7295
7582
|
};
|
|
7296
|
-
var standingRevFile = (store) =>
|
|
7297
|
-
var standingSeenFile = (store, session, agentId) =>
|
|
7583
|
+
var standingRevFile = (store) => path5.join(store, "standing", "rev");
|
|
7584
|
+
var standingSeenFile = (store, session, agentId) => path5.join(store, "standing", "seen", `${createHash2("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`);
|
|
7298
7585
|
var readStandingRev = (store) => {
|
|
7299
7586
|
try {
|
|
7300
7587
|
return fs2.readFileSync(standingRevFile(store), "utf8").trim() || null;
|
|
@@ -7304,7 +7591,7 @@ var readStandingRev = (store) => {
|
|
|
7304
7591
|
};
|
|
7305
7592
|
var writeStandingSeen = (file, seen) => {
|
|
7306
7593
|
try {
|
|
7307
|
-
fs2.mkdirSync(
|
|
7594
|
+
fs2.mkdirSync(path5.dirname(file), { recursive: true });
|
|
7308
7595
|
fs2.writeFileSync(file, JSON.stringify(seen));
|
|
7309
7596
|
} catch {}
|
|
7310
7597
|
};
|
|
@@ -7327,7 +7614,7 @@ var err = (code, error, unblock) => ({
|
|
|
7327
7614
|
...unblock ? { unblock } : {}
|
|
7328
7615
|
});
|
|
7329
7616
|
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
7330
|
-
const run =
|
|
7617
|
+
const run = spawnSync4("git", args, {
|
|
7331
7618
|
cwd,
|
|
7332
7619
|
encoding: "utf8",
|
|
7333
7620
|
timeout: timeoutMs,
|
|
@@ -7355,14 +7642,14 @@ var git2 = (cwd, args) => {
|
|
|
7355
7642
|
};
|
|
7356
7643
|
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
7357
7644
|
function storeRoot(cwd) {
|
|
7358
|
-
const location = resolveStore(
|
|
7645
|
+
const location = resolveStore(path6.resolve(cwd));
|
|
7359
7646
|
if (location instanceof Error)
|
|
7360
7647
|
return err("unavailable", location.message.replace(/; fix the repository.*$/u, ""), "fix the repository (git status should work here) or run from a non-git directory");
|
|
7361
7648
|
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
7362
7649
|
}
|
|
7363
7650
|
function ledgerPath(cwd) {
|
|
7364
7651
|
const root = storeRoot(cwd);
|
|
7365
|
-
return root.ok ? { ok: true, value:
|
|
7652
|
+
return root.ok ? { ok: true, value: path6.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
7366
7653
|
}
|
|
7367
7654
|
var NETWORK_FS_TYPES = new Set([
|
|
7368
7655
|
26985,
|
|
@@ -7821,11 +8108,97 @@ var contextReadSchema = object({
|
|
|
7821
8108
|
planPath: string2().optional(),
|
|
7822
8109
|
cwd: string2().min(1).optional()
|
|
7823
8110
|
}).strict();
|
|
8111
|
+
// packages/workit-core/src/core/role-models.ts
|
|
8112
|
+
var MODEL_ROLES = [
|
|
8113
|
+
"decider",
|
|
8114
|
+
"implementer",
|
|
8115
|
+
"verifier",
|
|
8116
|
+
"reviewer",
|
|
8117
|
+
"explore",
|
|
8118
|
+
"babysit"
|
|
8119
|
+
];
|
|
8120
|
+
var MODEL_TIERS = ["mundane", "standard", "hard"];
|
|
8121
|
+
var HOST_KEYS = new Set([
|
|
8122
|
+
"claude_code",
|
|
8123
|
+
"opencode",
|
|
8124
|
+
"codex",
|
|
8125
|
+
"codex_cli",
|
|
8126
|
+
"codex_desktop",
|
|
8127
|
+
"cursor",
|
|
8128
|
+
"pi",
|
|
8129
|
+
"default"
|
|
8130
|
+
]);
|
|
8131
|
+
var BUILT_IN_CLAUDE_CODE = {
|
|
8132
|
+
decider: { model: "fable", effort: "high" },
|
|
8133
|
+
"implementer.hard": { model: "opus" },
|
|
8134
|
+
"implementer.standard": { model: "sonnet" },
|
|
8135
|
+
"implementer.mundane": { model: "haiku" },
|
|
8136
|
+
verifier: { model: "sonnet" },
|
|
8137
|
+
reviewer: { model: "opus" },
|
|
8138
|
+
explore: { model: "haiku" },
|
|
8139
|
+
babysit: { model: "haiku" }
|
|
8140
|
+
};
|
|
8141
|
+
var isObject2 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
8142
|
+
var name = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
8143
|
+
var leaf = (value) => {
|
|
8144
|
+
const text = name(value);
|
|
8145
|
+
if (text)
|
|
8146
|
+
return { model: text, effort: null };
|
|
8147
|
+
if (!isObject2(value))
|
|
8148
|
+
return null;
|
|
8149
|
+
const model = name(value.model);
|
|
8150
|
+
return model ? { model, effort: name(value.effort) } : null;
|
|
8151
|
+
};
|
|
8152
|
+
var isFlat = (value) => typeof value === "string" || isObject2(value) && Object.hasOwn(value, "model");
|
|
8153
|
+
var forHost = (value, host) => {
|
|
8154
|
+
if (isFlat(value))
|
|
8155
|
+
return leaf(value);
|
|
8156
|
+
if (!isObject2(value))
|
|
8157
|
+
return null;
|
|
8158
|
+
const family = host ? hostFamily(host) : null;
|
|
8159
|
+
for (const key of [family, host, "default"])
|
|
8160
|
+
if (key && Object.hasOwn(value, key)) {
|
|
8161
|
+
const found = leaf(value[key]);
|
|
8162
|
+
if (found)
|
|
8163
|
+
return found;
|
|
8164
|
+
}
|
|
8165
|
+
return null;
|
|
8166
|
+
};
|
|
8167
|
+
var rolesOf = (models) => isObject2(models) && isObject2(models.roles) ? models.roles : null;
|
|
8168
|
+
var resolveRoleModel = (cwd, selector, host, dir) => {
|
|
8169
|
+
const { role, tier } = selector;
|
|
8170
|
+
for (const source of modelSources(cwd, dir)) {
|
|
8171
|
+
const where = source.global ? "the global default" : `workspace ${source.name}`;
|
|
8172
|
+
const roles = rolesOf(source.models);
|
|
8173
|
+
const configured = roles && Object.hasOwn(roles, role) ? roles[role] : undefined;
|
|
8174
|
+
const fromRoles = role === "implementer" ? tier && isObject2(configured) && Object.hasOwn(configured, tier) ? forHost(configured[tier], host) : null : forHost(configured, host);
|
|
8175
|
+
if (fromRoles)
|
|
8176
|
+
return { ...fromRoles, source: `models.roles in ${where}` };
|
|
8177
|
+
if (role === "implementer" && tier && isObject2(source.tierModels)) {
|
|
8178
|
+
const fromTiers = Object.hasOwn(source.tierModels, tier) ? forHost(source.tierModels[tier], host) : null;
|
|
8179
|
+
if (fromTiers)
|
|
8180
|
+
return { model: fromTiers.model, effort: null, source: `fanout.tierModels in ${where}` };
|
|
8181
|
+
}
|
|
8182
|
+
}
|
|
8183
|
+
if (host && hostFamily(host) === "claude_code") {
|
|
8184
|
+
const builtIn = BUILT_IN_CLAUDE_CODE[tier ? `${role}.${tier}` : role];
|
|
8185
|
+
if (builtIn)
|
|
8186
|
+
return { model: builtIn.model, effort: builtIn.effort ?? null, source: "built-in" };
|
|
8187
|
+
}
|
|
8188
|
+
return null;
|
|
8189
|
+
};
|
|
8190
|
+
var uniformImplementerModel = (cwd, host, dir) => {
|
|
8191
|
+
const tiers = MODEL_TIERS.map((tier) => resolveRoleModel(cwd, { role: "implementer", tier }, host, dir));
|
|
8192
|
+
const [first] = tiers;
|
|
8193
|
+
return first && tiers.every((t) => t?.model === first.model && t.effort === first.effort) ? first : null;
|
|
8194
|
+
};
|
|
8195
|
+
var ROLE_SELECTORS = MODEL_ROLES.flatMap((role) => role === "implementer" ? MODEL_TIERS.map((tier) => ({ role, tier })) : [{ role }]);
|
|
8196
|
+
|
|
7824
8197
|
// packages/workit-core/src/hooks/standing.ts
|
|
7825
8198
|
import fs4 from "node:fs";
|
|
7826
|
-
import
|
|
8199
|
+
import path7 from "node:path";
|
|
7827
8200
|
var readPlans = (store) => {
|
|
7828
|
-
const dir =
|
|
8201
|
+
const dir = path7.join(store, "fanouts");
|
|
7829
8202
|
let names;
|
|
7830
8203
|
try {
|
|
7831
8204
|
names = fs4.readdirSync(dir).filter((name) => name.endsWith(".json"));
|
|
@@ -7835,7 +8208,7 @@ var readPlans = (store) => {
|
|
|
7835
8208
|
const plans = [];
|
|
7836
8209
|
for (const name of names) {
|
|
7837
8210
|
try {
|
|
7838
|
-
const raw = JSON.parse(fs4.readFileSync(
|
|
8211
|
+
const raw = JSON.parse(fs4.readFileSync(path7.join(dir, name), "utf8"));
|
|
7839
8212
|
if (typeof raw.name !== "string" || !Array.isArray(raw.slices))
|
|
7840
8213
|
continue;
|
|
7841
8214
|
const texts = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item !== "") : [];
|
|
@@ -7962,7 +8335,9 @@ var roleTools = (role) => {
|
|
|
7962
8335
|
return [...readOnly, "write", "edit", ...familyTools];
|
|
7963
8336
|
return [...readOnly, ...familyTools];
|
|
7964
8337
|
};
|
|
8338
|
+
var roleModel = (cwd, role) => role === "implementer" ? uniformImplementerModel(cwd, "pi") : resolveRoleModel(cwd, { role: role === "investigator" ? "explore" : "reviewer" }, "pi");
|
|
7965
8339
|
function workerCommand(runtime, assignment) {
|
|
8340
|
+
const configured = roleModel(runtime.root, assignment.role);
|
|
7966
8341
|
return {
|
|
7967
8342
|
command: runtime.node,
|
|
7968
8343
|
args: [
|
|
@@ -7973,6 +8348,8 @@ function workerCommand(runtime, assignment) {
|
|
|
7973
8348
|
...runtime.sessionId ? ["--session-id", runtime.sessionId] : [],
|
|
7974
8349
|
"--approve",
|
|
7975
8350
|
"--offline",
|
|
8351
|
+
...configured ? ["--model", configured.model] : [],
|
|
8352
|
+
...configured?.effort ? ["--thinking", configured.effort] : [],
|
|
7976
8353
|
"--no-context-files",
|
|
7977
8354
|
"--tools",
|
|
7978
8355
|
roleTools(assignment.role).join(","),
|
package/dist/workit.js
CHANGED
|
@@ -8407,6 +8407,7 @@ var workspaceConfigSchema = object({
|
|
|
8407
8407
|
trailers: boolean2().optional(),
|
|
8408
8408
|
liveSessionGuard: boolean2().optional(),
|
|
8409
8409
|
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
8410
|
+
models: object({ roles: unknown().optional() }).passthrough().optional(),
|
|
8410
8411
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
8411
8412
|
kind,
|
|
8412
8413
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -8433,7 +8434,10 @@ var workspaceConfigSchema = object({
|
|
|
8433
8434
|
});
|
|
8434
8435
|
}
|
|
8435
8436
|
});
|
|
8436
|
-
var workspacesFileSchema = object({
|
|
8437
|
+
var workspacesFileSchema = object({
|
|
8438
|
+
workspaces: array(workspaceConfigSchema).optional(),
|
|
8439
|
+
models: object({ roles: unknown().optional() }).passthrough().optional()
|
|
8440
|
+
}).passthrough().superRefine((file, context) => {
|
|
8437
8441
|
const names = new Set;
|
|
8438
8442
|
for (const [index, workspace] of (file.workspaces ?? []).entries()) {
|
|
8439
8443
|
if (names.has(workspace.name)) {
|
|
@@ -8745,6 +8749,34 @@ var workspaceTrailers = (cwd, dir) => {
|
|
|
8745
8749
|
};
|
|
8746
8750
|
};
|
|
8747
8751
|
var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
|
|
8752
|
+
var modelSources = (cwd, dir) => {
|
|
8753
|
+
const out = [];
|
|
8754
|
+
try {
|
|
8755
|
+
const matched = matchRuntimeWorkspace(cwd, dir);
|
|
8756
|
+
if (matched) {
|
|
8757
|
+
const enclosing = matched.matches.filter((entry) => entry !== matched.selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
8758
|
+
for (const entry of [matched.selected, ...enclosing])
|
|
8759
|
+
out.push({
|
|
8760
|
+
name: entry.name,
|
|
8761
|
+
global: false,
|
|
8762
|
+
models: entry.models,
|
|
8763
|
+
tierModels: entry.fanout?.tierModels
|
|
8764
|
+
});
|
|
8765
|
+
}
|
|
8766
|
+
} catch {}
|
|
8767
|
+
try {
|
|
8768
|
+
const document = JSON.parse(readFileSync2(path4.join(dir ?? configDir(), "workspaces.json"), "utf8"));
|
|
8769
|
+
if (document && typeof document === "object" && !Array.isArray(document))
|
|
8770
|
+
out.push({
|
|
8771
|
+
name: "global",
|
|
8772
|
+
global: true,
|
|
8773
|
+
models: document.models,
|
|
8774
|
+
tierModels: undefined
|
|
8775
|
+
});
|
|
8776
|
+
} catch {}
|
|
8777
|
+
return out;
|
|
8778
|
+
};
|
|
8779
|
+
var hostFamily = (host) => host.replace(/_(?:cli|desktop)$/u, "");
|
|
8748
8780
|
var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
|
|
8749
8781
|
var TIER_HOSTS = new Set([
|
|
8750
8782
|
"claude_code",
|
|
@@ -24669,6 +24701,92 @@ var cancelHint = (workerId) => `cancel it first: workit_worker {"action":"cancel
|
|
|
24669
24701
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
24670
24702
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
24671
24703
|
|
|
24704
|
+
// packages/workit-core/src/core/role-models.ts
|
|
24705
|
+
var MODEL_ROLES = [
|
|
24706
|
+
"decider",
|
|
24707
|
+
"implementer",
|
|
24708
|
+
"verifier",
|
|
24709
|
+
"reviewer",
|
|
24710
|
+
"explore",
|
|
24711
|
+
"babysit"
|
|
24712
|
+
];
|
|
24713
|
+
var MODEL_TIERS = ["mundane", "standard", "hard"];
|
|
24714
|
+
var HOST_KEYS = new Set([
|
|
24715
|
+
"claude_code",
|
|
24716
|
+
"opencode",
|
|
24717
|
+
"codex",
|
|
24718
|
+
"codex_cli",
|
|
24719
|
+
"codex_desktop",
|
|
24720
|
+
"cursor",
|
|
24721
|
+
"pi",
|
|
24722
|
+
"default"
|
|
24723
|
+
]);
|
|
24724
|
+
var BUILT_IN_CLAUDE_CODE = {
|
|
24725
|
+
decider: { model: "fable", effort: "high" },
|
|
24726
|
+
"implementer.hard": { model: "opus" },
|
|
24727
|
+
"implementer.standard": { model: "sonnet" },
|
|
24728
|
+
"implementer.mundane": { model: "haiku" },
|
|
24729
|
+
verifier: { model: "sonnet" },
|
|
24730
|
+
reviewer: { model: "opus" },
|
|
24731
|
+
explore: { model: "haiku" },
|
|
24732
|
+
babysit: { model: "haiku" }
|
|
24733
|
+
};
|
|
24734
|
+
var isObject7 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
24735
|
+
var name = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
24736
|
+
var leaf = (value) => {
|
|
24737
|
+
const text = name(value);
|
|
24738
|
+
if (text)
|
|
24739
|
+
return { model: text, effort: null };
|
|
24740
|
+
if (!isObject7(value))
|
|
24741
|
+
return null;
|
|
24742
|
+
const model = name(value.model);
|
|
24743
|
+
return model ? { model, effort: name(value.effort) } : null;
|
|
24744
|
+
};
|
|
24745
|
+
var isFlat = (value) => typeof value === "string" || isObject7(value) && Object.hasOwn(value, "model");
|
|
24746
|
+
var forHost = (value, host) => {
|
|
24747
|
+
if (isFlat(value))
|
|
24748
|
+
return leaf(value);
|
|
24749
|
+
if (!isObject7(value))
|
|
24750
|
+
return null;
|
|
24751
|
+
const family = host ? hostFamily(host) : null;
|
|
24752
|
+
for (const key of [family, host, "default"])
|
|
24753
|
+
if (key && Object.hasOwn(value, key)) {
|
|
24754
|
+
const found = leaf(value[key]);
|
|
24755
|
+
if (found)
|
|
24756
|
+
return found;
|
|
24757
|
+
}
|
|
24758
|
+
return null;
|
|
24759
|
+
};
|
|
24760
|
+
var rolesOf = (models) => isObject7(models) && isObject7(models.roles) ? models.roles : null;
|
|
24761
|
+
var resolveRoleModel = (cwd, selector, host, dir) => {
|
|
24762
|
+
const { role, tier } = selector;
|
|
24763
|
+
for (const source of modelSources(cwd, dir)) {
|
|
24764
|
+
const where = source.global ? "the global default" : `workspace ${source.name}`;
|
|
24765
|
+
const roles = rolesOf(source.models);
|
|
24766
|
+
const configured = roles && Object.hasOwn(roles, role) ? roles[role] : undefined;
|
|
24767
|
+
const fromRoles = role === "implementer" ? tier && isObject7(configured) && Object.hasOwn(configured, tier) ? forHost(configured[tier], host) : null : forHost(configured, host);
|
|
24768
|
+
if (fromRoles)
|
|
24769
|
+
return { ...fromRoles, source: `models.roles in ${where}` };
|
|
24770
|
+
if (role === "implementer" && tier && isObject7(source.tierModels)) {
|
|
24771
|
+
const fromTiers = Object.hasOwn(source.tierModels, tier) ? forHost(source.tierModels[tier], host) : null;
|
|
24772
|
+
if (fromTiers)
|
|
24773
|
+
return { model: fromTiers.model, effort: null, source: `fanout.tierModels in ${where}` };
|
|
24774
|
+
}
|
|
24775
|
+
}
|
|
24776
|
+
if (host && hostFamily(host) === "claude_code") {
|
|
24777
|
+
const builtIn = BUILT_IN_CLAUDE_CODE[tier ? `${role}.${tier}` : role];
|
|
24778
|
+
if (builtIn)
|
|
24779
|
+
return { model: builtIn.model, effort: builtIn.effort ?? null, source: "built-in" };
|
|
24780
|
+
}
|
|
24781
|
+
return null;
|
|
24782
|
+
};
|
|
24783
|
+
var uniformImplementerModel = (cwd, host, dir) => {
|
|
24784
|
+
const tiers = MODEL_TIERS.map((tier) => resolveRoleModel(cwd, { role: "implementer", tier }, host, dir));
|
|
24785
|
+
const [first] = tiers;
|
|
24786
|
+
return first && tiers.every((t) => t?.model === first.model && t.effort === first.effort) ? first : null;
|
|
24787
|
+
};
|
|
24788
|
+
var ROLE_SELECTORS = MODEL_ROLES.flatMap((role) => role === "implementer" ? MODEL_TIERS.map((tier) => ({ role, tier })) : [{ role }]);
|
|
24789
|
+
|
|
24672
24790
|
// packages/workit-pi/src/worker-protocol.ts
|
|
24673
24791
|
var MAX_WORKER_LINE_BYTES = 64 * 1024;
|
|
24674
24792
|
var MAX_WORKER_STDERR_BYTES = 64 * 1024;
|
|
@@ -24740,7 +24858,9 @@ var roleTools = (role) => {
|
|
|
24740
24858
|
return [...readOnly, "write", "edit", ...familyTools];
|
|
24741
24859
|
return [...readOnly, ...familyTools];
|
|
24742
24860
|
};
|
|
24861
|
+
var roleModel = (cwd, role) => role === "implementer" ? uniformImplementerModel(cwd, "pi") : resolveRoleModel(cwd, { role: role === "investigator" ? "explore" : "reviewer" }, "pi");
|
|
24743
24862
|
function workerCommand2(runtime, assignment) {
|
|
24863
|
+
const configured = roleModel(runtime.root, assignment.role);
|
|
24744
24864
|
return {
|
|
24745
24865
|
command: runtime.node,
|
|
24746
24866
|
args: [
|
|
@@ -24751,6 +24871,8 @@ function workerCommand2(runtime, assignment) {
|
|
|
24751
24871
|
...runtime.sessionId ? ["--session-id", runtime.sessionId] : [],
|
|
24752
24872
|
"--approve",
|
|
24753
24873
|
"--offline",
|
|
24874
|
+
...configured ? ["--model", configured.model] : [],
|
|
24875
|
+
...configured?.effort ? ["--thinking", configured.effort] : [],
|
|
24754
24876
|
"--no-context-files",
|
|
24755
24877
|
"--tools",
|
|
24756
24878
|
roleTools(assignment.role).join(","),
|
package/package.json
CHANGED
|
@@ -25,8 +25,8 @@ units of ~100+ lines each. Coupled work stays with one owner. Workit never spawn
|
|
|
25
25
|
Never hand-edit 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. Model: the brief's `model:`
|
|
29
|
-
(`references/orchestration.md`). Claude Code: the `implementer` agent, never
|
|
28
|
+
each one finishes. Model: the brief's `model:` and `effort:` lines (`workit models show`),
|
|
29
|
+
else TIER (`references/orchestration.md`). Claude Code: the `implementer` agent, never
|
|
30
30
|
with `isolation: "worktree"`; Workit's hooks confine it to its WORKTREE.
|
|
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
|
|
@@ -40,12 +40,12 @@ units of ~100+ lines each. Coupled work stays with one owner. Workit never spawn
|
|
|
40
40
|
chain resumes. A fix round and recheck that both fail: a decider, not a
|
|
41
41
|
third round; product choices go to the user (`references/orchestration.md`).
|
|
42
42
|
7. **Verify by the workspace `verification` setting** (`workit grant show`).
|
|
43
|
-
A report is a pointer, never evidence. `proportional` (default): each slice's lane (`workit pr status`) decides; none needs no verdict, self the author's `--self`, focused
|
|
43
|
+
A report is a pointer, never evidence. `proportional` (default): each slice's lane (`workit pr status`) decides; none needs no verdict, self the author's `--self`, focused and full a verifier (Claude Code: the `model` and `effort` of `workit models show --role verifier`) after the last push; one verdict at a stack's top covers its unchanged lower PRs, and a stale one with `reverify: focused` re-checks only the new diff, reusing green CI. `self`: a verifier that wrote none
|
|
44
44
|
of the slices, or you if you wrote none. `independent` or high risk: a
|
|
45
45
|
separate verifier session, never yours (doctrine: the ledger only refuses
|
|
46
46
|
authors). One verifier may take a batch of slices, one verdict per branch
|
|
47
47
|
(`workit ledger verdict <result> --branch <b> --how "<evidence>"`), in a
|
|
48
|
-
session never chosen by the author. A review panel on separate models only at high risk.
|
|
48
|
+
session never chosen by the author. Spawn it with the brief's `verifier:` model (`workit models show --role verifier`). A review panel on separate models only at high risk.
|
|
49
49
|
8. **Fan in** with `workit fanout check`: out-of-scope files (any file outside
|
|
50
50
|
it stops the fan-in) and `git merge-tree` conflicts with trunk and between
|
|
51
51
|
siblings. Fix what it names until it exits 0; a merged slice reads as
|
|
@@ -5,22 +5,31 @@ 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
|
-
`fanout.tierModels
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
8
|
+
TIER is a field of each slice and of its brief. Role models come from the
|
|
9
|
+
workspace (`models.roles`; `fanout.tierModels` still feeds the implementer
|
|
10
|
+
tiers) and, on Claude Code, built-in defaults: the brief has a `model:` line
|
|
11
|
+
(with `effort:` when set) under TIER and a `verifier:` line; `workit fanout
|
|
12
|
+
status` names the model next to each spawnable slice; `workit models show
|
|
13
|
+
[--role r] [--host h] --json` lists every role (decider, implementer tiers,
|
|
14
|
+
verifier, reviewer, explore, babysit). Pass exactly that `model` (and `effort`
|
|
15
|
+
where the host's spawn call takes one) on the spawn call: a subagent that
|
|
16
|
+
passes none inherits your model. Scouting and PR babysitting use the `explore`
|
|
17
|
+
and `babysit` roles. A role with no model (other hosts without config) runs
|
|
18
|
+
on the host's model, and TIER only sizes the timebox and decides whether to
|
|
19
|
+
race. Claude Code's built-in defaults, when nothing is configured:
|
|
20
|
+
|
|
21
|
+
| Role or TIER | `model` on the Agent call |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| decider | `fable`, effort `high` |
|
|
24
|
+
| hard | `opus` |
|
|
25
|
+
| standard | `sonnet` |
|
|
26
|
+
| mundane | `haiku` |
|
|
27
|
+
| verifier | `sonnet` |
|
|
28
|
+
| reviewer | `opus` |
|
|
29
|
+
| explore, babysit | `haiku` |
|
|
30
|
+
|
|
31
|
+
OpenCode takes a subagent's model from its agent config: pick a
|
|
32
|
+
configured agent whose model fits the TIER, else the default.
|
|
24
33
|
|
|
25
34
|
## Rolling window
|
|
26
35
|
|
|
@@ -53,7 +62,7 @@ reversibility (the option cheapest to undo wins a tie), and the exact change
|
|
|
53
62
|
list. The fixer applies that list once, then one focused recheck follows.
|
|
54
63
|
|
|
55
64
|
- Hard call (shared contract, security, migration): two deciders on different
|
|
56
|
-
models (Claude Code: a different `model` on the Agent call; OpenCode: a
|
|
65
|
+
models (Claude Code: the `decider` role model, the other a different `model` on the Agent call; OpenCode: a
|
|
57
66
|
child session on another configured agent; Codex, Cursor and Pi: one
|
|
58
67
|
decider, since the host picks no model). If they disagree, you take the
|
|
59
68
|
more reversible option.
|
|
@@ -40,7 +40,7 @@ description: Build a requested change in small verified steps - follow local pat
|
|
|
40
40
|
status`). Lane none: no verdict. Lane self: after `workit check test` passes,
|
|
41
41
|
record `workit ledger verdict tests-verified --self --how "<what you ran>"`;
|
|
42
42
|
it reads self-reviewed, never verified, and is enough. Focused, full, high
|
|
43
|
-
risk or `verification: self|independent` merges: one fresh verifier per PR after the last push, its own host-derived session (Claude Code: the `verifier` agent
|
|
43
|
+
risk or `verification: self|independent` merges: one fresh verifier per PR after the last push, its own host-derived session (Claude Code: the `verifier` agent with the `model` (and `effort`) of `workit models show --role verifier`; Codex:
|
|
44
44
|
the `verifier` agent; OpenCode: a child session; Pi: a supervised worker),
|
|
45
45
|
never a hand-minted id. Before saying done, reconcile
|
|
46
46
|
every named deliverable against the target checkout and observe it (for a push:
|
|
@@ -9,7 +9,8 @@ Review the candidate, never the author's summary of it. A session that wrote a
|
|
|
9
9
|
commit on the branch cannot record a passing verdict: if that is you, hand the
|
|
10
10
|
review to a fresh agent with its own host-derived session: Claude Code and
|
|
11
11
|
Codex, the `reviewer` or `verifier` agent (`--session` and `--token` from SubagentStart);
|
|
12
|
-
OpenCode, a child session; Pi, a supervised worker.
|
|
12
|
+
OpenCode, a child session; Pi, a supervised worker. On Claude Code pass the
|
|
13
|
+
`model` and `effort` of `workit models show --role <verifier|reviewer>`. Never hand-mint one such
|
|
13
14
|
as `<lead>-v1`: it is refused as `forged_session`.
|
|
14
15
|
An author's `--self` verdict reads self-reviewed and never counts as
|
|
15
16
|
independent; `type-check-only` never proves a behavior change.
|
|
@@ -51,7 +51,7 @@ the same check. Fix round and recheck both failed: a decider (read the `workit-f
|
|
|
51
51
|
allows; no sleep loop. Red: read `logTail`, classify. Flake or infra: one
|
|
52
52
|
`workit ci rerun --failed --reason flake|infra` per head. Real: reproduce
|
|
53
53
|
with `workit check`, fix the root cause, batch every fix in one push.
|
|
54
|
-
6. **Verified.** The lane in `workit pr status` decides: none, no verdict; self, your `--self` verdict; focused or full (or `verification: self|independent`), one non-author verifier per PR after the last push, never per push (read the `workit-review` skill's SKILL.md and follow it), on Claude Code `model
|
|
54
|
+
6. **Verified.** The lane in `workit pr status` decides: none, no verdict; self, your `--self` verdict; focused or full (or `verification: self|independent`), one non-author verifier per PR after the last push, never per push (read the `workit-review` skill's SKILL.md and follow it), on Claude Code with the `model` and `effort` of `workit models show --role verifier`. A docs-only push (or test-only under a unit verdict) keeps the verdict (`carried`); `reverify: focused` re-checks only the new diff, reusing green CI; one verdict at a stack's top covers its unchanged lower PRs. Land only when
|
|
55
55
|
granted: `workit stack land` or `workit pr merge`; `--unverified --reason` if user-asked.
|
|
56
56
|
7. **Observe it landed:** `workit verify-delivery pr` or `merge`. After a failed
|
|
57
57
|
verdict or a check red 3+ times, at any endpoint: offer `/wk-retro` in one
|