@brainervirus/workit-opencode 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugin.js
CHANGED
|
@@ -15,12 +15,12 @@ var __export = (target, all) => {
|
|
|
15
15
|
|
|
16
16
|
// packages/workit-opencode/src/v1/server.ts
|
|
17
17
|
import { existsSync as existsSync12 } from "node:fs";
|
|
18
|
-
import
|
|
18
|
+
import path28 from "node:path";
|
|
19
19
|
|
|
20
20
|
// packages/workit-core/src/core.ts
|
|
21
|
-
import { spawnSync as
|
|
21
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
22
22
|
import { existsSync as existsSync5, realpathSync as realpathSync7 } from "node:fs";
|
|
23
|
-
import
|
|
23
|
+
import path13 from "node:path";
|
|
24
24
|
|
|
25
25
|
// packages/workit-core/src/core/task-contract.ts
|
|
26
26
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -8137,7 +8137,37 @@ var candidateSchema = object({
|
|
|
8137
8137
|
path: ["id"]
|
|
8138
8138
|
});
|
|
8139
8139
|
});
|
|
8140
|
-
var
|
|
8140
|
+
var checkObservationSchema = object({
|
|
8141
|
+
observer: literal("workit_cli"),
|
|
8142
|
+
name: nonEmpty.nullable(),
|
|
8143
|
+
configured: boolean2(),
|
|
8144
|
+
argv: array(text).min(1),
|
|
8145
|
+
shell: boolean2(),
|
|
8146
|
+
cwd: nonEmpty,
|
|
8147
|
+
exitCode: safeInteger,
|
|
8148
|
+
durationMs: safeInteger.min(0),
|
|
8149
|
+
timedOut: boolean2(),
|
|
8150
|
+
head: text.nullable(),
|
|
8151
|
+
tree: text.nullable(),
|
|
8152
|
+
dirty: boolean2().nullable(),
|
|
8153
|
+
signal: text.nullable(),
|
|
8154
|
+
treeAfter: text.nullable(),
|
|
8155
|
+
modifiedWorktree: boolean2(),
|
|
8156
|
+
base: text.nullable(),
|
|
8157
|
+
patchId: text.nullable(),
|
|
8158
|
+
environment: object({
|
|
8159
|
+
platform: nonEmpty,
|
|
8160
|
+
arch: nonEmpty,
|
|
8161
|
+
vars: record(nonEmpty, text.nullable())
|
|
8162
|
+
}).strict(),
|
|
8163
|
+
logDigest: text.nullable(),
|
|
8164
|
+
logRef: text.nullable(),
|
|
8165
|
+
logTail: array(text).max(40),
|
|
8166
|
+
ledgerRowId: text.nullable(),
|
|
8167
|
+
attestation: object({ host: nonEmpty, session: text.nullable(), agentId: text.nullable() }).strict().nullable()
|
|
8168
|
+
}).strict();
|
|
8169
|
+
var CHECK_OBSERVATION_PATH = "evidence.*.data.observation";
|
|
8170
|
+
var evidenceFields = {
|
|
8141
8171
|
kind: _enum(["check", "review", "investigation", "artifact"]),
|
|
8142
8172
|
claim: text,
|
|
8143
8173
|
requirementIds: array(digest),
|
|
@@ -8148,7 +8178,9 @@ var evidenceSchema = object({
|
|
|
8148
8178
|
refs: array(refSchema),
|
|
8149
8179
|
exitCode: safeInteger.nullable(),
|
|
8150
8180
|
reviewContext: refSchema.nullable()
|
|
8151
|
-
}
|
|
8181
|
+
};
|
|
8182
|
+
var evidenceSchema = object({ ...evidenceFields, observation: checkObservationSchema.optional() }).strict();
|
|
8183
|
+
var reportedEvidenceSchema = object(evidenceFields).strict();
|
|
8152
8184
|
var evidenceEvaluationSchema = object({
|
|
8153
8185
|
evidenceId: id,
|
|
8154
8186
|
status: _enum(["passed", "failed", "missing", "skipped", "stale"]),
|
|
@@ -8480,7 +8512,7 @@ var evidenceOperations = {
|
|
|
8480
8512
|
action: literal("record"),
|
|
8481
8513
|
...taskId,
|
|
8482
8514
|
expectedRevision: revision.optional(),
|
|
8483
|
-
evidence:
|
|
8515
|
+
evidence: reportedEvidenceSchema
|
|
8484
8516
|
})
|
|
8485
8517
|
};
|
|
8486
8518
|
var findingOperations = {
|
|
@@ -10980,10 +11012,10 @@ function assertProductWriteAllowed(input) {
|
|
|
10980
11012
|
}
|
|
10981
11013
|
|
|
10982
11014
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
10983
|
-
import { createHash as
|
|
10984
|
-
import * as
|
|
10985
|
-
import { spawnSync as
|
|
10986
|
-
import
|
|
11015
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
11016
|
+
import * as fs10 from "node:fs";
|
|
11017
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
11018
|
+
import path12 from "node:path";
|
|
10987
11019
|
|
|
10988
11020
|
// packages/workit-core/src/core/authority.ts
|
|
10989
11021
|
import { createHash as createHash6 } from "node:crypto";
|
|
@@ -12749,16 +12781,346 @@ function reconcileAction(input) {
|
|
|
12749
12781
|
var bindingCovers = scopeCovers;
|
|
12750
12782
|
var verifyDecisionContent = verifyContentRefs;
|
|
12751
12783
|
|
|
12784
|
+
// packages/workit-core/src/check-config.ts
|
|
12785
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
12786
|
+
import fs8 from "node:fs";
|
|
12787
|
+
import path10 from "node:path";
|
|
12788
|
+
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
12789
|
+
var CHECKS_FILE = "workit.checks.json";
|
|
12790
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12791
|
+
var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
|
|
12792
|
+
function splitCommand(command) {
|
|
12793
|
+
const argv = [];
|
|
12794
|
+
let current = "";
|
|
12795
|
+
let quote = null;
|
|
12796
|
+
let started = false;
|
|
12797
|
+
for (const char of command) {
|
|
12798
|
+
if (quote) {
|
|
12799
|
+
if (char === quote)
|
|
12800
|
+
quote = null;
|
|
12801
|
+
else
|
|
12802
|
+
current += char;
|
|
12803
|
+
continue;
|
|
12804
|
+
}
|
|
12805
|
+
if (char === '"' || char === "'") {
|
|
12806
|
+
quote = char;
|
|
12807
|
+
started = true;
|
|
12808
|
+
continue;
|
|
12809
|
+
}
|
|
12810
|
+
if (/\s/u.test(char)) {
|
|
12811
|
+
if (started)
|
|
12812
|
+
argv.push(current);
|
|
12813
|
+
current = "";
|
|
12814
|
+
started = false;
|
|
12815
|
+
continue;
|
|
12816
|
+
}
|
|
12817
|
+
if (SHELL_META.test(char))
|
|
12818
|
+
return new Error(`"${command}" uses shell syntax (${char}); configure an argv array instead`);
|
|
12819
|
+
current += char;
|
|
12820
|
+
started = true;
|
|
12821
|
+
}
|
|
12822
|
+
if (quote)
|
|
12823
|
+
return new Error(`"${command}" has an unterminated quote`);
|
|
12824
|
+
if (started)
|
|
12825
|
+
argv.push(current);
|
|
12826
|
+
return argv.length ? argv : new Error("empty command");
|
|
12827
|
+
}
|
|
12828
|
+
var git = (cwd, args) => {
|
|
12829
|
+
const run = spawnSync3("git", args, {
|
|
12830
|
+
cwd,
|
|
12831
|
+
encoding: "utf8",
|
|
12832
|
+
timeout: 20000,
|
|
12833
|
+
killSignal: "SIGKILL",
|
|
12834
|
+
windowsHide: true
|
|
12835
|
+
});
|
|
12836
|
+
const value = run.status === 0 ? (run.stdout ?? "").trim() : "";
|
|
12837
|
+
return value || null;
|
|
12838
|
+
};
|
|
12839
|
+
function checkRoot(cwd) {
|
|
12840
|
+
return path10.resolve(git(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
12841
|
+
}
|
|
12842
|
+
var packageManager = (root) => {
|
|
12843
|
+
const has = (file) => fs8.existsSync(path10.join(root, file));
|
|
12844
|
+
if (has("bun.lock") || has("bun.lockb"))
|
|
12845
|
+
return "bun";
|
|
12846
|
+
if (has("pnpm-lock.yaml"))
|
|
12847
|
+
return "pnpm";
|
|
12848
|
+
if (has("yarn.lock"))
|
|
12849
|
+
return "yarn";
|
|
12850
|
+
return "npm";
|
|
12851
|
+
};
|
|
12852
|
+
var scriptCheck = (pm, name) => {
|
|
12853
|
+
const argv = [pm, "run", name];
|
|
12854
|
+
const accepts = [argv];
|
|
12855
|
+
if (pm === "pnpm" || pm === "yarn" || pm === "npm" && name === "test")
|
|
12856
|
+
accepts.push([pm, name]);
|
|
12857
|
+
return { name, argv, accepts };
|
|
12858
|
+
};
|
|
12859
|
+
var CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
|
|
12860
|
+
function detectedChecks(root) {
|
|
12861
|
+
const read = (file) => {
|
|
12862
|
+
try {
|
|
12863
|
+
return fs8.readFileSync(path10.join(root, file), "utf8");
|
|
12864
|
+
} catch {
|
|
12865
|
+
return null;
|
|
12866
|
+
}
|
|
12867
|
+
};
|
|
12868
|
+
const checks = [];
|
|
12869
|
+
const add = (check) => {
|
|
12870
|
+
if (!checks.some((item) => item.name === check.name))
|
|
12871
|
+
checks.push(check);
|
|
12872
|
+
};
|
|
12873
|
+
const manifestText = read("package.json");
|
|
12874
|
+
if (manifestText !== null) {
|
|
12875
|
+
let manifest = null;
|
|
12876
|
+
try {
|
|
12877
|
+
manifest = JSON.parse(manifestText);
|
|
12878
|
+
} catch {}
|
|
12879
|
+
const scripts = isRecord2(manifest) && isRecord2(manifest.scripts) ? manifest.scripts : {};
|
|
12880
|
+
const pm = packageManager(root);
|
|
12881
|
+
for (const name of SCRIPT_CHECKS) {
|
|
12882
|
+
const script = scripts[name];
|
|
12883
|
+
if (typeof script === "string" && script.trim() !== "")
|
|
12884
|
+
add(scriptCheck(pm, name));
|
|
12885
|
+
}
|
|
12886
|
+
}
|
|
12887
|
+
const plain = (name, argv) => ({ name, argv, accepts: [argv] });
|
|
12888
|
+
if (read("go.mod") !== null)
|
|
12889
|
+
add(plain("test", ["go", "test", "./..."]));
|
|
12890
|
+
if (read("Cargo.toml") !== null)
|
|
12891
|
+
add(plain("test", ["cargo", "test"]));
|
|
12892
|
+
if (read("pytest.ini") !== null || /\bpytest\b/u.test(read("pyproject.toml") ?? ""))
|
|
12893
|
+
add(plain("test", ["pytest"]));
|
|
12894
|
+
const makefile = read("Makefile") ?? read("makefile") ?? read("GNUmakefile");
|
|
12895
|
+
if (makefile !== null && /^test\s*:(?!=)/mu.test(makefile))
|
|
12896
|
+
add(plain("test", ["make", "test"]));
|
|
12897
|
+
return checks;
|
|
12898
|
+
}
|
|
12899
|
+
var GATES = ["testing", "verification"];
|
|
12900
|
+
function loadCheckConfig(cwd) {
|
|
12901
|
+
const root = checkRoot(cwd);
|
|
12902
|
+
const file = path10.join(root, CHECKS_FILE);
|
|
12903
|
+
const none = (error = null) => ({
|
|
12904
|
+
source: error ? CHECKS_FILE : "none",
|
|
12905
|
+
root,
|
|
12906
|
+
checks: [],
|
|
12907
|
+
gates: {},
|
|
12908
|
+
error
|
|
12909
|
+
});
|
|
12910
|
+
let text = null;
|
|
12911
|
+
try {
|
|
12912
|
+
text = fs8.readFileSync(file, "utf8");
|
|
12913
|
+
} catch (error) {
|
|
12914
|
+
if (error.code !== "ENOENT")
|
|
12915
|
+
return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
|
|
12916
|
+
}
|
|
12917
|
+
if (text === null) {
|
|
12918
|
+
const checks = detectedChecks(root);
|
|
12919
|
+
return checks.length ? { source: "defaults", root, checks, gates: {}, error: null } : none();
|
|
12920
|
+
}
|
|
12921
|
+
let parsed;
|
|
12922
|
+
try {
|
|
12923
|
+
parsed = JSON.parse(text);
|
|
12924
|
+
} catch {
|
|
12925
|
+
return none(`${CHECKS_FILE} is not valid JSON`);
|
|
12926
|
+
}
|
|
12927
|
+
if (!isRecord2(parsed) || !isRecord2(parsed.checks))
|
|
12928
|
+
return none(`${CHECKS_FILE} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
|
|
12929
|
+
const checks = [];
|
|
12930
|
+
for (const [name, value] of Object.entries(parsed.checks)) {
|
|
12931
|
+
if (!CHECK_NAME.test(name))
|
|
12932
|
+
return none(`${CHECKS_FILE}: check name "${name}" must match ${CHECK_NAME.source}`);
|
|
12933
|
+
const argv = typeof value === "string" ? splitCommand(value) : Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.length > 0) ? value : new Error("expected a command string or a non-empty argv array");
|
|
12934
|
+
if (argv instanceof Error)
|
|
12935
|
+
return none(`${CHECKS_FILE}: check "${name}": ${argv.message}`);
|
|
12936
|
+
checks.push({ name, argv, accepts: [argv] });
|
|
12937
|
+
}
|
|
12938
|
+
const gates = {};
|
|
12939
|
+
if (parsed.gates !== undefined) {
|
|
12940
|
+
if (!isRecord2(parsed.gates))
|
|
12941
|
+
return none(`${CHECKS_FILE}: gates must be {"testing":"<name>"|["<name>",…]}`);
|
|
12942
|
+
for (const [gate, value] of Object.entries(parsed.gates)) {
|
|
12943
|
+
if (!GATES.includes(gate))
|
|
12944
|
+
return none(`${CHECKS_FILE}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
|
|
12945
|
+
const names = typeof value === "string" ? [value] : value;
|
|
12946
|
+
if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
|
|
12947
|
+
return none(`${CHECKS_FILE}: gates.${gate} must name configured checks`);
|
|
12948
|
+
gates[gate] = names;
|
|
12949
|
+
}
|
|
12950
|
+
}
|
|
12951
|
+
return { source: CHECKS_FILE, root, checks, gates, error: null };
|
|
12952
|
+
}
|
|
12953
|
+
var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
|
|
12954
|
+
function matchesNamedCheck(config, name, argv, cwd) {
|
|
12955
|
+
if (config.error || name === null || cwd !== ".")
|
|
12956
|
+
return false;
|
|
12957
|
+
const check = config.checks.find((item) => item.name === name);
|
|
12958
|
+
return Boolean(check?.accepts.some((accepted) => sameArgv(accepted, argv)));
|
|
12959
|
+
}
|
|
12960
|
+
function gateCheckNames(config, dimension) {
|
|
12961
|
+
const explicit = config.gates[dimension];
|
|
12962
|
+
if (explicit)
|
|
12963
|
+
return explicit;
|
|
12964
|
+
const names = config.checks.map((check) => check.name);
|
|
12965
|
+
if (dimension === "testing")
|
|
12966
|
+
return names.includes("test") ? ["test"] : [];
|
|
12967
|
+
return names;
|
|
12968
|
+
}
|
|
12969
|
+
var workitOnPath;
|
|
12970
|
+
function workitAvailable(env = process.env) {
|
|
12971
|
+
if (workitOnPath !== undefined)
|
|
12972
|
+
return workitOnPath;
|
|
12973
|
+
const windows = process.platform === "win32";
|
|
12974
|
+
const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
12975
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path10.delimiter).filter(Boolean);
|
|
12976
|
+
workitOnPath = dirs.some((dir) => exts.some((ext) => {
|
|
12977
|
+
try {
|
|
12978
|
+
return fs8.statSync(path10.join(dir, `workit${ext}`)).isFile();
|
|
12979
|
+
} catch {
|
|
12980
|
+
return false;
|
|
12981
|
+
}
|
|
12982
|
+
}));
|
|
12983
|
+
return workitOnPath;
|
|
12984
|
+
}
|
|
12985
|
+
function checkCommand(args, version, onPath = workitAvailable()) {
|
|
12986
|
+
return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
|
|
12987
|
+
}
|
|
12988
|
+
|
|
12989
|
+
// packages/workit-core/src/git/rev.ts
|
|
12990
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
12991
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
12992
|
+
import fs9 from "node:fs";
|
|
12993
|
+
import os2 from "node:os";
|
|
12994
|
+
import path11 from "node:path";
|
|
12995
|
+
var MAX_BUFFER = 256 * 1024 * 1024;
|
|
12996
|
+
var GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
|
|
12997
|
+
var git2 = (cwd, args, options = {}) => {
|
|
12998
|
+
const result = spawnSync4("git", args, {
|
|
12999
|
+
cwd,
|
|
13000
|
+
encoding: "utf8",
|
|
13001
|
+
env: options.env ?? process.env,
|
|
13002
|
+
input: options.input,
|
|
13003
|
+
maxBuffer: MAX_BUFFER,
|
|
13004
|
+
timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
|
|
13005
|
+
killSignal: "SIGKILL",
|
|
13006
|
+
windowsHide: true
|
|
13007
|
+
});
|
|
13008
|
+
const error = result.error;
|
|
13009
|
+
return {
|
|
13010
|
+
ok: result.status === 0,
|
|
13011
|
+
stdout: result.stdout ?? "",
|
|
13012
|
+
stderr: result.stderr ?? "",
|
|
13013
|
+
timedOut: error?.code === "ETIMEDOUT"
|
|
13014
|
+
};
|
|
13015
|
+
};
|
|
13016
|
+
var line = (run) => {
|
|
13017
|
+
if (!run.ok)
|
|
13018
|
+
return null;
|
|
13019
|
+
const value = run.stdout.trim();
|
|
13020
|
+
return value ? value : null;
|
|
13021
|
+
};
|
|
13022
|
+
function headSha(cwd) {
|
|
13023
|
+
return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
|
|
13024
|
+
}
|
|
13025
|
+
var emptyTree = (cwd) => line(git2(cwd, ["mktree"], { input: "" }));
|
|
13026
|
+
var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
|
|
13027
|
+
var seedIndex = (from, to) => {
|
|
13028
|
+
fs9.copyFileSync(from, to);
|
|
13029
|
+
const { atimeNs, mtimeNs } = fs9.statSync(from, { bigint: true });
|
|
13030
|
+
const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
|
|
13031
|
+
fs9.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
|
|
13032
|
+
};
|
|
13033
|
+
function worktreeTree(cwd, options = {}) {
|
|
13034
|
+
const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
|
|
13035
|
+
const indexPath = line(git2(cwd, ["rev-parse", "--path-format=absolute", "--git-path", "index"]));
|
|
13036
|
+
if (!top || !indexPath)
|
|
13037
|
+
return null;
|
|
13038
|
+
const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
|
|
13039
|
+
const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
|
|
13040
|
+
const scratch = fs9.mkdtempSync(path11.join(os2.tmpdir(), "workit-tree-"));
|
|
13041
|
+
try {
|
|
13042
|
+
const tempIndex = path11.join(scratch, "index");
|
|
13043
|
+
if (fs9.existsSync(indexPath))
|
|
13044
|
+
seedIndex(indexPath, tempIndex);
|
|
13045
|
+
const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
|
|
13046
|
+
const untracked = git2(top, ["ls-files", "--others", "--exclude-standard", "-z"], {
|
|
13047
|
+
env,
|
|
13048
|
+
timeoutMs
|
|
13049
|
+
});
|
|
13050
|
+
if (!untracked.ok)
|
|
13051
|
+
return null;
|
|
13052
|
+
const skipped = [];
|
|
13053
|
+
for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
|
|
13054
|
+
try {
|
|
13055
|
+
const stat = fs9.statSync(path11.join(top, file));
|
|
13056
|
+
if (stat.isFile() && stat.size > cap)
|
|
13057
|
+
skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
|
|
13058
|
+
} catch {}
|
|
13059
|
+
}
|
|
13060
|
+
skipped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
13061
|
+
const added = skipped.length ? git2(top, ["add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], {
|
|
13062
|
+
env,
|
|
13063
|
+
timeoutMs,
|
|
13064
|
+
input: [".", ...skipped.map((file) => `:(exclude,literal)${file.path}`)].join("\x00")
|
|
13065
|
+
}) : git2(top, ["add", "-A"], { env, timeoutMs });
|
|
13066
|
+
if (!added.ok)
|
|
13067
|
+
return null;
|
|
13068
|
+
const tree = line(git2(top, ["write-tree"], { env, timeoutMs }));
|
|
13069
|
+
if (!tree)
|
|
13070
|
+
return null;
|
|
13071
|
+
const base = line(git2(top, ["rev-parse", "--verify", "-q", "HEAD^{tree}"])) ?? emptyTree(top);
|
|
13072
|
+
const key = skipped.length ? `sha256:${createHash7("sha256").update(JSON.stringify({ tree, skipped })).digest("hex")}` : tree;
|
|
13073
|
+
return { tree, key, dirty: tree !== base || skipped.length > 0, skipped };
|
|
13074
|
+
} finally {
|
|
13075
|
+
fs9.rmSync(scratch, { recursive: true, force: true });
|
|
13076
|
+
}
|
|
13077
|
+
}
|
|
13078
|
+
function worktreeSignal(cwd, options = {}) {
|
|
13079
|
+
const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
|
|
13080
|
+
if (!top)
|
|
13081
|
+
return null;
|
|
13082
|
+
const status = git2(top, [
|
|
13083
|
+
"status",
|
|
13084
|
+
"--porcelain=v1",
|
|
13085
|
+
"-z",
|
|
13086
|
+
"--untracked-files=all",
|
|
13087
|
+
"--no-renames",
|
|
13088
|
+
"--ignore-submodules=dirty"
|
|
13089
|
+
], {
|
|
13090
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
13091
|
+
timeoutMs: options.timeoutMs ?? GIT_TIMEOUTS.worktree
|
|
13092
|
+
});
|
|
13093
|
+
if (!status.ok)
|
|
13094
|
+
return null;
|
|
13095
|
+
const hash = createHash7("sha256");
|
|
13096
|
+
hash.update(`${headSha(top) ?? "unborn"}\x00`);
|
|
13097
|
+
for (const entry of status.stdout.split("\x00")) {
|
|
13098
|
+
if (entry.length < 4)
|
|
13099
|
+
continue;
|
|
13100
|
+
const file = entry.slice(3);
|
|
13101
|
+
const absolute = path11.join(top, file);
|
|
13102
|
+
let stat = "-";
|
|
13103
|
+
try {
|
|
13104
|
+
const info = fs9.lstatSync(absolute, { bigint: true });
|
|
13105
|
+
stat = `${info.mode.toString(8)}:${info.size}:${info.mtimeNs}`;
|
|
13106
|
+
if (info.isDirectory())
|
|
13107
|
+
stat += `:${headSha(absolute) ?? "-"}`;
|
|
13108
|
+
} catch {}
|
|
13109
|
+
hash.update(`${entry.slice(0, 2)}\x00${file}\x00${stat}\x00`);
|
|
13110
|
+
}
|
|
13111
|
+
return `sig:${hash.digest("hex")}`;
|
|
13112
|
+
}
|
|
13113
|
+
|
|
12752
13114
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
12753
|
-
var digestBytes3 = (value) =>
|
|
12754
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
12755
|
-
var relative = (root, candidate) =>
|
|
13115
|
+
var digestBytes3 = (value) => createHash8("sha256").update(value).digest("hex");
|
|
13116
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path12.sep}`);
|
|
13117
|
+
var relative = (root, candidate) => path12.relative(root, candidate).split(path12.sep).join("/") || ".";
|
|
12756
13118
|
var canonicalPath = (value) => {
|
|
12757
13119
|
if (typeof value !== "string" || !value)
|
|
12758
13120
|
return null;
|
|
12759
13121
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
12760
13122
|
return null;
|
|
12761
|
-
const normalized =
|
|
13123
|
+
const normalized = path12.posix.normalize(value);
|
|
12762
13124
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
12763
13125
|
return null;
|
|
12764
13126
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -12794,7 +13156,7 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
12794
13156
|
return left.length - right.length;
|
|
12795
13157
|
};
|
|
12796
13158
|
var gitPaths = (root) => {
|
|
12797
|
-
const result =
|
|
13159
|
+
const result = spawnSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
12798
13160
|
cwd: root,
|
|
12799
13161
|
encoding: "buffer"
|
|
12800
13162
|
});
|
|
@@ -12806,21 +13168,21 @@ var gitPaths = (root) => {
|
|
|
12806
13168
|
git: !/not a git repository/i.test(stderr)
|
|
12807
13169
|
};
|
|
12808
13170
|
}
|
|
12809
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
12810
|
-
const stagedDeleted =
|
|
13171
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path12.sep).join("/")) : [];
|
|
13172
|
+
const stagedDeleted = spawnSync5("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
12811
13173
|
if (stagedDeleted.status !== 0) {
|
|
12812
13174
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
12813
13175
|
if (!/not a git repository/i.test(stderr))
|
|
12814
13176
|
return { paths, uncertain: true, git: true };
|
|
12815
13177
|
} else if (stagedDeleted.stdout) {
|
|
12816
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
13178
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path12.sep).join("/")));
|
|
12817
13179
|
}
|
|
12818
13180
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
12819
13181
|
};
|
|
12820
13182
|
var walk = (root, directory, output) => {
|
|
12821
13183
|
let names;
|
|
12822
13184
|
try {
|
|
12823
|
-
names =
|
|
13185
|
+
names = fs10.readdirSync(directory);
|
|
12824
13186
|
} catch {
|
|
12825
13187
|
return true;
|
|
12826
13188
|
}
|
|
@@ -12828,10 +13190,10 @@ var walk = (root, directory, output) => {
|
|
|
12828
13190
|
for (const name of names) {
|
|
12829
13191
|
if (name === ".git" || name === ".workit")
|
|
12830
13192
|
continue;
|
|
12831
|
-
const target =
|
|
13193
|
+
const target = path12.join(directory, name);
|
|
12832
13194
|
let stat;
|
|
12833
13195
|
try {
|
|
12834
|
-
stat =
|
|
13196
|
+
stat = fs10.lstatSync(target);
|
|
12835
13197
|
} catch {
|
|
12836
13198
|
uncertain = true;
|
|
12837
13199
|
continue;
|
|
@@ -12846,14 +13208,14 @@ var walk = (root, directory, output) => {
|
|
|
12846
13208
|
var scopeRoots = (root, scope) => {
|
|
12847
13209
|
const roots = [];
|
|
12848
13210
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
12849
|
-
const target =
|
|
13211
|
+
const target = path12.resolve(root, item);
|
|
12850
13212
|
if (!inside(root, target))
|
|
12851
13213
|
return failure2("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
12852
13214
|
let ancestor = target;
|
|
12853
|
-
while (!
|
|
12854
|
-
ancestor =
|
|
13215
|
+
while (!fs10.existsSync(ancestor) && ancestor !== root)
|
|
13216
|
+
ancestor = path12.dirname(ancestor);
|
|
12855
13217
|
try {
|
|
12856
|
-
if (!inside(root,
|
|
13218
|
+
if (!inside(root, fs10.realpathSync(ancestor)))
|
|
12857
13219
|
return failure2("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
12858
13220
|
} catch {
|
|
12859
13221
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -12865,7 +13227,7 @@ var scopeRoots = (root, scope) => {
|
|
|
12865
13227
|
function captureCandidate(root, scope, environment = []) {
|
|
12866
13228
|
let checkout;
|
|
12867
13229
|
try {
|
|
12868
|
-
checkout =
|
|
13230
|
+
checkout = fs10.realpathSync(root);
|
|
12869
13231
|
} catch {
|
|
12870
13232
|
return failure2("invalid_input", "candidate root does not exist");
|
|
12871
13233
|
}
|
|
@@ -12883,11 +13245,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12883
13245
|
if (item === ".git" || item === ".workit")
|
|
12884
13246
|
continue;
|
|
12885
13247
|
names.add(item);
|
|
12886
|
-
if (!
|
|
13248
|
+
if (!fs10.existsSync(target))
|
|
12887
13249
|
continue;
|
|
12888
13250
|
let stat;
|
|
12889
13251
|
try {
|
|
12890
|
-
stat =
|
|
13252
|
+
stat = fs10.lstatSync(target);
|
|
12891
13253
|
} catch {
|
|
12892
13254
|
uncertain = true;
|
|
12893
13255
|
continue;
|
|
@@ -12903,17 +13265,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12903
13265
|
for (const item of [...names].toSorted()) {
|
|
12904
13266
|
if (!scopeMatches(item, normalizedScope))
|
|
12905
13267
|
continue;
|
|
12906
|
-
const target =
|
|
13268
|
+
const target = path12.join(checkout, item);
|
|
12907
13269
|
try {
|
|
12908
|
-
const stat =
|
|
13270
|
+
const stat = fs10.lstatSync(target);
|
|
12909
13271
|
if (stat.isSymbolicLink()) {
|
|
12910
|
-
const link =
|
|
13272
|
+
const link = fs10.readlinkSync(target);
|
|
12911
13273
|
files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
|
|
12912
13274
|
} else if (stat.isFile()) {
|
|
12913
13275
|
files.push({
|
|
12914
13276
|
path: item,
|
|
12915
13277
|
kind: "file",
|
|
12916
|
-
digest: digestBytes3(
|
|
13278
|
+
digest: digestBytes3(fs10.readFileSync(target)),
|
|
12917
13279
|
executable: (stat.mode & 73) !== 0
|
|
12918
13280
|
});
|
|
12919
13281
|
} else if (!stat.isDirectory()) {
|
|
@@ -12938,7 +13300,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12938
13300
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
12939
13301
|
if (values.some(({ value }) => value === null))
|
|
12940
13302
|
completeness = "uncertain";
|
|
12941
|
-
const headResult =
|
|
13303
|
+
const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
12942
13304
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
12943
13305
|
completeness = "uncertain";
|
|
12944
13306
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -12976,7 +13338,24 @@ var sameSession = (left, right) => {
|
|
|
12976
13338
|
return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
|
|
12977
13339
|
};
|
|
12978
13340
|
var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
|
|
12979
|
-
|
|
13341
|
+
var memo2 = (compute) => {
|
|
13342
|
+
let value;
|
|
13343
|
+
return () => {
|
|
13344
|
+
if (value === undefined)
|
|
13345
|
+
value = compute();
|
|
13346
|
+
return value;
|
|
13347
|
+
};
|
|
13348
|
+
};
|
|
13349
|
+
var treeFreshness = (root) => ({
|
|
13350
|
+
mode: "tree",
|
|
13351
|
+
current: memo2(() => worktreeTree(root)?.key ?? null)
|
|
13352
|
+
});
|
|
13353
|
+
var signalFreshness = (root) => ({
|
|
13354
|
+
mode: "signal",
|
|
13355
|
+
current: memo2(() => worktreeSignal(root))
|
|
13356
|
+
});
|
|
13357
|
+
var isObservedCheck = (entry) => entry.data.kind === "check" && entry.data.observation?.observer === "workit_cli" && entry.provenance?.kind === "host_observed" && entry.provenance.host === "workit_cli";
|
|
13358
|
+
function evaluateEvidence(task, candidate, freshness) {
|
|
12980
13359
|
const current = candidate ?? task.candidates.at(-1) ?? null;
|
|
12981
13360
|
return task.evidence.map((entry) => {
|
|
12982
13361
|
const evidence = entry.data;
|
|
@@ -12986,6 +13365,25 @@ function evaluateEvidence(task, candidate) {
|
|
|
12986
13365
|
status: "failed",
|
|
12987
13366
|
reason: "failed evidence remains historical"
|
|
12988
13367
|
};
|
|
13368
|
+
const observation = isObservedCheck(entry) ? evidence.observation : undefined;
|
|
13369
|
+
if (observation && observation.tree !== null) {
|
|
13370
|
+
if (task.status === "closed")
|
|
13371
|
+
return { evidenceId: entry.id, status: evidence.result, reason: "recorded before close" };
|
|
13372
|
+
if (observation.modifiedWorktree)
|
|
13373
|
+
return {
|
|
13374
|
+
evidenceId: entry.id,
|
|
13375
|
+
status: "stale",
|
|
13376
|
+
reason: "the check changed the worktree while it ran; ignore or clean its outputs"
|
|
13377
|
+
};
|
|
13378
|
+
const recorded = freshness?.mode === "signal" ? observation.signal : observation.tree;
|
|
13379
|
+
if (recorded !== null && freshness && recorded === freshness.current())
|
|
13380
|
+
return { evidenceId: entry.id, status: evidence.result, reason: "worktree is unchanged" };
|
|
13381
|
+
return {
|
|
13382
|
+
evidenceId: entry.id,
|
|
13383
|
+
status: "stale",
|
|
13384
|
+
reason: freshness?.mode === "signal" ? "the worktree may have changed since the check ran (quick signal; inspect re-checks exactly)" : "the worktree changed since the check ran"
|
|
13385
|
+
};
|
|
13386
|
+
}
|
|
12989
13387
|
if (evidence.kind === "check" || evidence.kind === "review") {
|
|
12990
13388
|
if (!evidence.beforeCandidateId || !evidence.candidateId)
|
|
12991
13389
|
return {
|
|
@@ -13069,8 +13467,45 @@ var expectedKinds = (dimension, ruleId) => {
|
|
|
13069
13467
|
return "decision";
|
|
13070
13468
|
return "worker report";
|
|
13071
13469
|
};
|
|
13072
|
-
|
|
13073
|
-
|
|
13470
|
+
var observedGate = (requirement) => (requirement.dimension === "testing" || requirement.dimension === "verification") && requirement.before === "close";
|
|
13471
|
+
function evaluateObservedGate(task, requirement, statuses, config) {
|
|
13472
|
+
const names = gateCheckNames(config, requirement.dimension);
|
|
13473
|
+
const observed = task.evidence.filter(isObservedCheck);
|
|
13474
|
+
const fresh = observed.filter((entry) => statuses.get(entry.id) === "passed" && entry.data.observation?.exitCode === 0);
|
|
13475
|
+
const named = (entry) => {
|
|
13476
|
+
const observation = entry.data.observation;
|
|
13477
|
+
return observation.configured && !observation.shell && names.includes(observation.name ?? "") && matchesNamedCheck(config, observation.name, observation.argv, observation.cwd);
|
|
13478
|
+
};
|
|
13479
|
+
const considered = observed.map((entry) => entry.id);
|
|
13480
|
+
const run = checkCommand(names[0] ?? "<name>", runtimeVersion());
|
|
13481
|
+
if (config.error)
|
|
13482
|
+
return {
|
|
13483
|
+
satisfied: [],
|
|
13484
|
+
considered,
|
|
13485
|
+
reason: `check config is invalid (${config.error}); fix it, then run ${run}`
|
|
13486
|
+
};
|
|
13487
|
+
if (!names.length)
|
|
13488
|
+
return {
|
|
13489
|
+
satisfied: [],
|
|
13490
|
+
considered,
|
|
13491
|
+
reason: requirement.dimension === "testing" && config.checks.length ? `no \`test\` check is configured; add one (or gates.testing) to ${CHECKS_FILE}, then run ${checkCommand("test", runtimeVersion())}` : `no checks are configured or detected; add ${CHECKS_FILE} (committed, so it shows in the diff), e.g. {"checks":{"test":"<your test command>"}}, then run ${checkCommand("test", runtimeVersion())}`
|
|
13492
|
+
};
|
|
13493
|
+
const satisfied = fresh.filter(named);
|
|
13494
|
+
if (satisfied.length)
|
|
13495
|
+
return {
|
|
13496
|
+
satisfied: satisfied.map((entry) => entry.id),
|
|
13497
|
+
considered,
|
|
13498
|
+
reason: `a fresh passing configured check (${names.join("|")}) was observed by workit check`
|
|
13499
|
+
};
|
|
13500
|
+
const reported = task.evidence.some((entry) => entry.data.kind === "check" && !isObservedCheck(entry) && entry.data.requirementIds.includes(requirement.id));
|
|
13501
|
+
const reason = fresh.length ? `ad-hoc checks do not satisfy a configured gate; run ${run}` : observed.some((entry) => statuses.get(entry.id) === "stale") ? `observed checks are stale (the worktree changed); run ${run}` : observed.some((entry) => statuses.get(entry.id) === "failed") ? `the observed check failed; fix it and run ${run}` : reported ? `agent-reported checks are notes, not evidence; run ${run}` : `no CLI-observed check (needs kind:check observed by workit check); run ${run}`;
|
|
13502
|
+
return { satisfied: [], considered, reason };
|
|
13503
|
+
}
|
|
13504
|
+
function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller, freshness = treeFreshness(checkoutRoot)) {
|
|
13505
|
+
const evidence = evaluateEvidence(task, candidate, freshness);
|
|
13506
|
+
const statuses = new Map(evidence.map((item) => [item.evidenceId, item.status]));
|
|
13507
|
+
let checkConfig;
|
|
13508
|
+
const configFor = () => checkConfig ??= loadCheckConfig(checkoutRoot);
|
|
13074
13509
|
if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
|
|
13075
13510
|
return task.policy.requirements.map((requirement) => ({
|
|
13076
13511
|
requirementId: requirement.id,
|
|
@@ -13080,8 +13515,19 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
13080
13515
|
reason: "stored policy version is unsupported; reassessment is required"
|
|
13081
13516
|
}));
|
|
13082
13517
|
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
13518
|
+
const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
|
|
13519
|
+
if (gate?.satisfied.length)
|
|
13520
|
+
return {
|
|
13521
|
+
requirementId: requirement.id,
|
|
13522
|
+
status: "satisfied",
|
|
13523
|
+
evidenceIds: gate.satisfied,
|
|
13524
|
+
decisionIds: [],
|
|
13525
|
+
reason: gate.reason
|
|
13526
|
+
};
|
|
13083
13527
|
const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
|
|
13084
13528
|
const passed = related.filter(({ entry, evaluation }) => {
|
|
13529
|
+
if (gate)
|
|
13530
|
+
return false;
|
|
13085
13531
|
if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
|
|
13086
13532
|
return false;
|
|
13087
13533
|
if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
|
|
@@ -13144,6 +13590,14 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
13144
13590
|
reason: "an applicable approved limitation permits the missing evidence"
|
|
13145
13591
|
};
|
|
13146
13592
|
const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
|
|
13593
|
+
if (gate)
|
|
13594
|
+
return {
|
|
13595
|
+
requirementId: requirement.id,
|
|
13596
|
+
status: unavailable ? "unavailable" : "unsatisfied",
|
|
13597
|
+
evidenceIds: [...new Set([...gate.considered, ...related.map(({ entry }) => entry.id)])],
|
|
13598
|
+
decisionIds: [],
|
|
13599
|
+
reason: unavailable ? "required capability is unavailable" : gate.reason
|
|
13600
|
+
};
|
|
13147
13601
|
if (!unavailable && related.length > 0 && !passed.length)
|
|
13148
13602
|
return {
|
|
13149
13603
|
requirementId: requirement.id,
|
|
@@ -13320,8 +13774,11 @@ policy, and reapplies the call, returning busy under persistent contention. A
|
|
|
13320
13774
|
revision_conflict means a revision you passed is stale: re-read the record
|
|
13321
13775
|
before deciding whether to retry.
|
|
13322
13776
|
A solo edit does not need writer acquisition; use it when concurrent checkout
|
|
13323
|
-
writers need coordination. Record only observed facts and checks.
|
|
13324
|
-
|
|
13777
|
+
writers need coordination. Record only observed facts and checks. Close-time
|
|
13778
|
+
testing and verification gates accept only a configured check the CLI ran:
|
|
13779
|
+
\`workit check <name>\` (\`npx -y @brainervirus/workit-cli check <name>\` when
|
|
13780
|
+
\`workit\` is not on PATH); a recorded check result is a note and an ad-hoc
|
|
13781
|
+
\`workit check -- <cmd>\` never satisfies a gate. Evidence can become stale when its bound candidate or tree changes; reconcile findings against the
|
|
13325
13782
|
current candidate before recording completion.
|
|
13326
13783
|
|
|
13327
13784
|
Workit validates domain policy against the actual action target, configured
|
|
@@ -13370,8 +13827,9 @@ target result; a local commit alone is not evidence of a requested remote push.
|
|
|
13370
13827
|
Skill routing: slash aliases /wk-* load on demand. Use workit-steer for a
|
|
13371
13828
|
substantial interruption or change of direction, workit-deslop when relevant
|
|
13372
13829
|
to a PR-ready endpoint, workit-green-run for failing CI, workit-blast-radius
|
|
13373
|
-
when impact is uncertain,
|
|
13374
|
-
choices
|
|
13830
|
+
when impact is uncertain, workit-challenge for genuinely open consequential
|
|
13831
|
+
choices, workit-bdd to turn acceptance criteria into Given/When/Then tests, and
|
|
13832
|
+
workit-test-audit to check tests for tautologies. Load workit-plan when dependencies or handoff need durable next actions.
|
|
13375
13833
|
Load the skill; never act from memory of it.
|
|
13376
13834
|
`.trim();
|
|
13377
13835
|
|
|
@@ -13382,7 +13840,7 @@ function reconcileResume(view, observations = []) {
|
|
|
13382
13840
|
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
13383
13841
|
if (!candidate.ok)
|
|
13384
13842
|
return candidate;
|
|
13385
|
-
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
13843
|
+
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data, treeFreshness(view.workspace.root)).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
13386
13844
|
const blockers = [...view.task.progress.blockers];
|
|
13387
13845
|
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
13388
13846
|
blockers.push({
|
|
@@ -14750,10 +15208,7 @@ class WorkitCore {
|
|
|
14750
15208
|
if (finding.data.disposition !== "fixed")
|
|
14751
15209
|
return finding;
|
|
14752
15210
|
const withEntry = { ...current, candidates, evidence: [...current.evidence, entry] };
|
|
14753
|
-
const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data).map((item) => [
|
|
14754
|
-
item.evidenceId,
|
|
14755
|
-
item.status
|
|
14756
|
-
]));
|
|
15211
|
+
const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data, treeFreshness(this.store.root)).map((item) => [item.evidenceId, item.status]));
|
|
14757
15212
|
const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
|
|
14758
15213
|
kind: item.data.kind,
|
|
14759
15214
|
candidateId: item.data.candidateId,
|
|
@@ -14768,6 +15223,71 @@ class WorkitCore {
|
|
|
14768
15223
|
return changed;
|
|
14769
15224
|
return success(changed.data.revision, null, changed.data.evidence.at(-1));
|
|
14770
15225
|
}
|
|
15226
|
+
observeCheck(request) {
|
|
15227
|
+
return this.retryOmittedRevisions(request, () => this.observeCheckOnce(request));
|
|
15228
|
+
}
|
|
15229
|
+
observeCheckOnce(request) {
|
|
15230
|
+
const root = this.contextRootError();
|
|
15231
|
+
if (!root.ok)
|
|
15232
|
+
return root;
|
|
15233
|
+
if (this.context.caller.host !== "workit_cli" || (this.context.workerId ?? null) !== null)
|
|
15234
|
+
return failure2("permission_denied", "only the workit CLI records observed checks");
|
|
15235
|
+
const observation = checkObservationSchema.safeParse(request?.observation);
|
|
15236
|
+
if (!observation.success)
|
|
15237
|
+
return failure2("invalid_input", "check observation is invalid", {
|
|
15238
|
+
fields: observation.error.issues.map((issue) => ({
|
|
15239
|
+
path: issue.path.join("."),
|
|
15240
|
+
reason: issue.message
|
|
15241
|
+
}))
|
|
15242
|
+
});
|
|
15243
|
+
const task = this.store.readTask(request.taskId);
|
|
15244
|
+
if (!task.ok)
|
|
15245
|
+
return task;
|
|
15246
|
+
if (task.data.status === "closed")
|
|
15247
|
+
return failure2("invalid_transition", "closed task cannot record evidence");
|
|
15248
|
+
const observed = observation.data;
|
|
15249
|
+
let candidate = null;
|
|
15250
|
+
if (observed.tree === null) {
|
|
15251
|
+
const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
15252
|
+
if (!captured.ok)
|
|
15253
|
+
return captured;
|
|
15254
|
+
candidate = captured.data;
|
|
15255
|
+
}
|
|
15256
|
+
const label = observed.name ?? observed.argv.join(" ");
|
|
15257
|
+
const data = {
|
|
15258
|
+
kind: "check",
|
|
15259
|
+
claim: `workit check ${label}`.slice(0, 500),
|
|
15260
|
+
requirementIds: [],
|
|
15261
|
+
...candidate ? { beforeCandidateId: candidate.id, candidateId: candidate.id } : {},
|
|
15262
|
+
result: observed.exitCode === 0 ? "passed" : "failed",
|
|
15263
|
+
summary: `${observed.configured ? "configured" : "ad-hoc"} check exited ${observed.exitCode}${observed.timedOut ? " (timed out)" : ""} in ${observed.durationMs} ms`,
|
|
15264
|
+
refs: [],
|
|
15265
|
+
exitCode: observed.exitCode,
|
|
15266
|
+
reviewContext: null,
|
|
15267
|
+
observation: observed
|
|
15268
|
+
};
|
|
15269
|
+
const changed = this.store.mutateTask(task.data.id, request.expectedRevision ?? task.data.revision, (current, mutation) => {
|
|
15270
|
+
const candidates = candidate === null || current.candidates.some((item) => item.id === candidate.id) ? current.candidates : [...current.candidates, candidate];
|
|
15271
|
+
const critical = [...new Set([...current.critical ?? [], CHECK_OBSERVATION_PATH])];
|
|
15272
|
+
return success(mutation.revision, null, {
|
|
15273
|
+
...current,
|
|
15274
|
+
critical,
|
|
15275
|
+
candidates,
|
|
15276
|
+
evidence: [
|
|
15277
|
+
...current.evidence,
|
|
15278
|
+
{
|
|
15279
|
+
id: newId(),
|
|
15280
|
+
recordedAt: mutation.now,
|
|
15281
|
+
provenance: provenance(this.context, "host_observed"),
|
|
15282
|
+
data
|
|
15283
|
+
}
|
|
15284
|
+
]
|
|
15285
|
+
});
|
|
15286
|
+
}, trustedNow(this.context));
|
|
15287
|
+
if (!changed.ok)
|
|
15288
|
+
return changed;
|
|
15289
|
+
return success(changed.data.revision, null, changed.data.evidence.at(-1));
|
|
15290
|
+
}
|
|
14771
15291
|
decision(request) {
|
|
14772
15292
|
return this.recordDecision(request);
|
|
14773
15293
|
}
|
|
@@ -15003,7 +15523,7 @@ class WorkitCore {
|
|
|
15003
15523
|
const current = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
15004
15524
|
if (!current.ok)
|
|
15005
15525
|
return current;
|
|
15006
|
-
const evaluations = evaluateEvidence(task.data, current.data);
|
|
15526
|
+
const evaluations = evaluateEvidence(task.data, current.data, treeFreshness(this.store.root));
|
|
15007
15527
|
const verified = evidence.some((entry) => {
|
|
15008
15528
|
const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
|
|
15009
15529
|
return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
|
|
@@ -15707,7 +16227,8 @@ class WorkitCore {
|
|
|
15707
16227
|
if (!current.ok)
|
|
15708
16228
|
return current;
|
|
15709
16229
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
15710
|
-
const
|
|
16230
|
+
const tree = treeFreshness(this.store.root);
|
|
16231
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
|
|
15711
16232
|
return success(task.revision, workspace.data.revision, {
|
|
15712
16233
|
id: task.id,
|
|
15713
16234
|
revision: task.revision,
|
|
@@ -15724,7 +16245,7 @@ class WorkitCore {
|
|
|
15724
16245
|
writer: evaluationWorkspace.writer
|
|
15725
16246
|
});
|
|
15726
16247
|
}
|
|
15727
|
-
compactContext(taskId) {
|
|
16248
|
+
compactContext(taskId, freshness) {
|
|
15728
16249
|
const root = this.contextRootError();
|
|
15729
16250
|
if (!root.ok)
|
|
15730
16251
|
return root;
|
|
@@ -15734,12 +16255,12 @@ class WorkitCore {
|
|
|
15734
16255
|
const helper = this.helperTaskGuard(task.data, true);
|
|
15735
16256
|
if (!helper.ok)
|
|
15736
16257
|
return helper;
|
|
15737
|
-
const view = this.view(task.data, false);
|
|
16258
|
+
const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
|
|
15738
16259
|
if (!view.ok)
|
|
15739
16260
|
return view;
|
|
15740
16261
|
return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
|
|
15741
16262
|
}
|
|
15742
|
-
view(task, capture = true) {
|
|
16263
|
+
view(task, capture = true, freshness = treeFreshness(this.store.root)) {
|
|
15743
16264
|
const workspace = this.store.readWorkspace();
|
|
15744
16265
|
if (!workspace.ok)
|
|
15745
16266
|
return workspace;
|
|
@@ -15750,13 +16271,14 @@ class WorkitCore {
|
|
|
15750
16271
|
if (!current.ok)
|
|
15751
16272
|
return current;
|
|
15752
16273
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
15753
|
-
const
|
|
16274
|
+
const tree = freshness;
|
|
16275
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
|
|
15754
16276
|
return success(task.revision, workspace.data.revision, {
|
|
15755
16277
|
task,
|
|
15756
16278
|
workspace: evaluationWorkspace,
|
|
15757
16279
|
capabilities: this.context.capabilities,
|
|
15758
16280
|
requirements,
|
|
15759
|
-
evidence: evaluateEvidence(task, current.data)
|
|
16281
|
+
evidence: evaluateEvidence(task, current.data, tree)
|
|
15760
16282
|
});
|
|
15761
16283
|
}
|
|
15762
16284
|
reconcileResume(view, observations = []) {
|
|
@@ -15983,13 +16505,14 @@ class WorkitCore {
|
|
|
15983
16505
|
return current;
|
|
15984
16506
|
const withCandidate = current.data;
|
|
15985
16507
|
const taskForView = withCandidate.id === task.data.candidates.at(-1)?.id ? task.data : { ...task.data, candidates: [...task.data.candidates, withCandidate] };
|
|
15986
|
-
const
|
|
16508
|
+
const tree = treeFreshness(this.store.root);
|
|
16509
|
+
const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller, tree);
|
|
15987
16510
|
const view = {
|
|
15988
16511
|
task: taskForView,
|
|
15989
16512
|
workspace: workspace.data,
|
|
15990
16513
|
capabilities: this.context.capabilities,
|
|
15991
16514
|
requirements,
|
|
15992
|
-
evidence: evaluateEvidence(taskForView, withCandidate)
|
|
16515
|
+
evidence: evaluateEvidence(taskForView, withCandidate, tree)
|
|
15993
16516
|
};
|
|
15994
16517
|
const closure = evaluateClosure(input.outcome, view);
|
|
15995
16518
|
if (!closure.ok)
|
|
@@ -16053,22 +16576,22 @@ function resolveGitRevision(root, value) {
|
|
|
16053
16576
|
}
|
|
16054
16577
|
function resolveInside(root, candidate) {
|
|
16055
16578
|
const base = realpathSync7(root);
|
|
16056
|
-
const target =
|
|
16057
|
-
if (target !== base && !target.startsWith(base +
|
|
16579
|
+
const target = path13.resolve(base, candidate);
|
|
16580
|
+
if (target !== base && !target.startsWith(base + path13.sep)) {
|
|
16058
16581
|
throw new Error("path must stay inside repository root");
|
|
16059
16582
|
}
|
|
16060
16583
|
let ancestor = target;
|
|
16061
16584
|
while (!existsSync5(ancestor))
|
|
16062
|
-
ancestor =
|
|
16585
|
+
ancestor = path13.dirname(ancestor);
|
|
16063
16586
|
const canonicalAncestor = realpathSync7(ancestor);
|
|
16064
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
16587
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path13.sep)) {
|
|
16065
16588
|
throw new Error("path must stay inside repository root");
|
|
16066
16589
|
}
|
|
16067
16590
|
return target;
|
|
16068
16591
|
}
|
|
16069
16592
|
function run2(root, executable, args, env = {}) {
|
|
16070
16593
|
const cwd = realpathSync7(root);
|
|
16071
|
-
const result =
|
|
16594
|
+
const result = spawnSync6(executable, args, {
|
|
16072
16595
|
cwd,
|
|
16073
16596
|
encoding: "utf8",
|
|
16074
16597
|
env: { ...process.env, ...env }
|
|
@@ -16096,7 +16619,9 @@ var WORKIT_METHOD_SKILLS = [
|
|
|
16096
16619
|
"workit-diagram",
|
|
16097
16620
|
"workit-mockup",
|
|
16098
16621
|
"workit-green-run",
|
|
16099
|
-
"workit-steer"
|
|
16622
|
+
"workit-steer",
|
|
16623
|
+
"workit-bdd",
|
|
16624
|
+
"workit-test-audit"
|
|
16100
16625
|
];
|
|
16101
16626
|
var WORKIT_SKILL_ALIASES = {
|
|
16102
16627
|
"wk-challenge": "workit-challenge",
|
|
@@ -16112,14 +16637,16 @@ var WORKIT_SKILL_ALIASES = {
|
|
|
16112
16637
|
"wk-diagram": "workit-diagram",
|
|
16113
16638
|
"wk-mockup": "workit-mockup",
|
|
16114
16639
|
"wk-green-run": "workit-green-run",
|
|
16115
|
-
"wk-steer": "workit-steer"
|
|
16640
|
+
"wk-steer": "workit-steer",
|
|
16641
|
+
"wk-bdd": "workit-bdd",
|
|
16642
|
+
"wk-test-audit": "workit-test-audit"
|
|
16116
16643
|
};
|
|
16117
16644
|
if (false) {}
|
|
16118
16645
|
|
|
16119
16646
|
// packages/workit-core/src/core/logger.ts
|
|
16120
16647
|
import { appendFileSync, mkdirSync as mkdirSync3, readdirSync as readdirSync4, unlinkSync as unlinkSync2 } from "node:fs";
|
|
16121
|
-
import
|
|
16122
|
-
import
|
|
16648
|
+
import os3 from "node:os";
|
|
16649
|
+
import path14 from "node:path";
|
|
16123
16650
|
var REDACTED = "[REDACTED]";
|
|
16124
16651
|
var DEFAULT_MAX_RATE = 20;
|
|
16125
16652
|
var DEFAULT_RATE_WINDOW_MS = 1000;
|
|
@@ -16173,19 +16700,19 @@ var resolveStateDir = () => {
|
|
|
16173
16700
|
const override = process.env.WORKFLOW_TOOLKIT_STATE;
|
|
16174
16701
|
if (override)
|
|
16175
16702
|
return override;
|
|
16176
|
-
const home =
|
|
16703
|
+
const home = os3.homedir();
|
|
16177
16704
|
if (process.env.XDG_STATE_HOME)
|
|
16178
|
-
return
|
|
16179
|
-
switch (
|
|
16705
|
+
return path14.join(process.env.XDG_STATE_HOME, "workit");
|
|
16706
|
+
switch (os3.platform()) {
|
|
16180
16707
|
case "darwin":
|
|
16181
|
-
return
|
|
16708
|
+
return path14.join(home, "Library", "Application Support", "workit");
|
|
16182
16709
|
case "win32":
|
|
16183
|
-
return
|
|
16710
|
+
return path14.join(process.env.LOCALAPPDATA ?? path14.join(home, "AppData", "Local"), "workit");
|
|
16184
16711
|
default:
|
|
16185
|
-
return
|
|
16712
|
+
return path14.join(process.env.HOME ?? home, ".local", "state", "workit");
|
|
16186
16713
|
}
|
|
16187
16714
|
};
|
|
16188
|
-
var homePattern = new RegExp(`${escapeRegExp(
|
|
16715
|
+
var homePattern = new RegExp(`${escapeRegExp(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
16189
16716
|
var applyPatterns = (value) => {
|
|
16190
16717
|
let out = value;
|
|
16191
16718
|
out = out.replace(SECRET_VALUE, REDACTED);
|
|
@@ -16255,14 +16782,14 @@ var pruneOldFiles = (dir) => {
|
|
|
16255
16782
|
const files = readdirSync4(dir).filter((name) => DAY_FILE.test(name)).toSorted().toReversed();
|
|
16256
16783
|
for (const file of files.slice(RETAINED_DAYS)) {
|
|
16257
16784
|
try {
|
|
16258
|
-
unlinkSync2(
|
|
16785
|
+
unlinkSync2(path14.join(dir, file));
|
|
16259
16786
|
} catch {}
|
|
16260
16787
|
}
|
|
16261
16788
|
} catch {}
|
|
16262
16789
|
};
|
|
16263
16790
|
var createLogger = (options = {}) => {
|
|
16264
16791
|
const stateDir = options.stateDir ?? resolveStateDir();
|
|
16265
|
-
const logDir =
|
|
16792
|
+
const logDir = path14.join(stateDir, "logs");
|
|
16266
16793
|
const now = options.now ?? (() => new Date);
|
|
16267
16794
|
const maxRate = options.maxRate ?? DEFAULT_MAX_RATE;
|
|
16268
16795
|
const rateWindowMs = options.rateWindowMs ?? DEFAULT_RATE_WINDOW_MS;
|
|
@@ -16296,7 +16823,7 @@ var createLogger = (options = {}) => {
|
|
|
16296
16823
|
const line = `${JSON.stringify(event)}
|
|
16297
16824
|
`;
|
|
16298
16825
|
mkdirSync3(logDir, { recursive: true, mode: 448 });
|
|
16299
|
-
appendFileSync(
|
|
16826
|
+
appendFileSync(path14.join(logDir, dailyFileName(date)), line, { mode: 384 });
|
|
16300
16827
|
pruneOldFiles(logDir);
|
|
16301
16828
|
} catch {
|
|
16302
16829
|
try {
|
|
@@ -16307,7 +16834,7 @@ var createLogger = (options = {}) => {
|
|
|
16307
16834
|
context: { redaction_failed: true }
|
|
16308
16835
|
};
|
|
16309
16836
|
mkdirSync3(logDir, { recursive: true, mode: 448 });
|
|
16310
|
-
appendFileSync(
|
|
16837
|
+
appendFileSync(path14.join(logDir, dailyFileName(date)), `${JSON.stringify(event)}
|
|
16311
16838
|
`, {
|
|
16312
16839
|
mode: 384
|
|
16313
16840
|
});
|
|
@@ -16356,47 +16883,47 @@ OpenCode Workit decisions use the native question header "Workit decision: <purp
|
|
|
16356
16883
|
|
|
16357
16884
|
// packages/workit-opencode/src/shared/assets.ts
|
|
16358
16885
|
import { existsSync as existsSync6, realpathSync as realpathSync8 } from "node:fs";
|
|
16359
|
-
import
|
|
16886
|
+
import path15 from "node:path";
|
|
16360
16887
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
16361
16888
|
var moduleDir = () => {
|
|
16362
16889
|
try {
|
|
16363
|
-
return realpathSync8(
|
|
16890
|
+
return realpathSync8(path15.dirname(fileURLToPath2(import.meta.url)));
|
|
16364
16891
|
} catch {
|
|
16365
|
-
return
|
|
16892
|
+
return path15.dirname(fileURLToPath2(import.meta.url));
|
|
16366
16893
|
}
|
|
16367
16894
|
};
|
|
16368
16895
|
var packageRoot2 = () => {
|
|
16369
16896
|
const dir = moduleDir();
|
|
16370
16897
|
for (const candidate of [
|
|
16371
|
-
|
|
16372
|
-
|
|
16373
|
-
|
|
16898
|
+
path15.resolve(dir, ".."),
|
|
16899
|
+
path15.resolve(dir, "..", ".."),
|
|
16900
|
+
path15.resolve(dir, "..", "..", "..")
|
|
16374
16901
|
]) {
|
|
16375
|
-
if (existsSync6(
|
|
16902
|
+
if (existsSync6(path15.join(candidate, "package.json")))
|
|
16376
16903
|
return candidate;
|
|
16377
16904
|
}
|
|
16378
|
-
return
|
|
16905
|
+
return path15.resolve(dir, "..");
|
|
16379
16906
|
};
|
|
16380
|
-
var assetsRoot = () =>
|
|
16907
|
+
var assetsRoot = () => path15.join(packageRoot2(), "assets");
|
|
16381
16908
|
|
|
16382
16909
|
// packages/workit-opencode/src/stale-sources.ts
|
|
16383
16910
|
import { existsSync as existsSync7, readdirSync as readdirSync5 } from "node:fs";
|
|
16384
|
-
import
|
|
16911
|
+
import path16 from "node:path";
|
|
16385
16912
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
16386
16913
|
var typescriptFiles = (directory) => {
|
|
16387
16914
|
if (!existsSync7(directory))
|
|
16388
16915
|
return [];
|
|
16389
16916
|
return readdirSync5(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
16390
|
-
const file =
|
|
16917
|
+
const file = path16.join(directory, entry.name);
|
|
16391
16918
|
return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
|
|
16392
16919
|
});
|
|
16393
16920
|
};
|
|
16394
16921
|
var pluginSourceFiles = [
|
|
16395
16922
|
...new Set([
|
|
16396
16923
|
fileURLToPath3(import.meta.url),
|
|
16397
|
-
...typescriptFiles(
|
|
16398
|
-
|
|
16399
|
-
...typescriptFiles(
|
|
16924
|
+
...typescriptFiles(path16.join(packageRoot2(), "src")),
|
|
16925
|
+
path16.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
|
|
16926
|
+
...typescriptFiles(path16.join(packageRoot2(), "..", "workit-core", "src", "core"))
|
|
16400
16927
|
])
|
|
16401
16928
|
].filter((file) => existsSync7(file)).toSorted();
|
|
16402
16929
|
|
|
@@ -28721,11 +29248,11 @@ function tool(input) {
|
|
|
28721
29248
|
}
|
|
28722
29249
|
tool.schema = exports_external2;
|
|
28723
29250
|
// packages/workit-opencode/src/shared/init-apply.ts
|
|
28724
|
-
import
|
|
29251
|
+
import path22 from "node:path";
|
|
28725
29252
|
|
|
28726
29253
|
// packages/workit-core/src/core/gitignore.ts
|
|
28727
29254
|
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
28728
|
-
import
|
|
29255
|
+
import path17 from "node:path";
|
|
28729
29256
|
var GITIGNORE_ENTRIES = [
|
|
28730
29257
|
"# workit: SDD working state (never commit)",
|
|
28731
29258
|
"docs/*/sdd/",
|
|
@@ -28745,7 +29272,7 @@ var GITIGNORE_ENTRIES = [
|
|
|
28745
29272
|
var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
28746
29273
|
if (!confirmed)
|
|
28747
29274
|
return { ok: false, error: "confirmed: true required" };
|
|
28748
|
-
const file =
|
|
29275
|
+
const file = path17.join(workspaceRoot, ".gitignore");
|
|
28749
29276
|
const existing = existsSync8(file) ? readFileSync7(file, "utf8") : "";
|
|
28750
29277
|
const existingLines = new Set(existing.split(`
|
|
28751
29278
|
`).map((l) => l.trim()).filter(Boolean));
|
|
@@ -28773,7 +29300,7 @@ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
|
28773
29300
|
|
|
28774
29301
|
// packages/workit-core/src/core/hygiene.ts
|
|
28775
29302
|
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
|
|
28776
|
-
import
|
|
29303
|
+
import path18 from "node:path";
|
|
28777
29304
|
|
|
28778
29305
|
// packages/workit-core/src/core/scripts.ts
|
|
28779
29306
|
var PLUGIN_ROOT = packageRoot();
|
|
@@ -28782,23 +29309,23 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
28782
29309
|
|
|
28783
29310
|
// packages/workit-core/src/core/hygiene.ts
|
|
28784
29311
|
var repoRoot = assetRoot();
|
|
28785
|
-
var templatesDir = () =>
|
|
29312
|
+
var templatesDir = () => path18.join(repoRoot, "templates", "hygiene");
|
|
28786
29313
|
var packageJson = (root) => {
|
|
28787
|
-
if (!existsSync9(
|
|
29314
|
+
if (!existsSync9(path18.join(root, "package.json")))
|
|
28788
29315
|
return null;
|
|
28789
29316
|
try {
|
|
28790
|
-
return JSON.parse(readFileSync8(
|
|
29317
|
+
return JSON.parse(readFileSync8(path18.join(root, "package.json"), "utf8"));
|
|
28791
29318
|
} catch {
|
|
28792
29319
|
return null;
|
|
28793
29320
|
}
|
|
28794
29321
|
};
|
|
28795
29322
|
var isOpenSource = (root) => {
|
|
28796
|
-
if (existsSync9(
|
|
29323
|
+
if (existsSync9(path18.join(root, "LICENSE")))
|
|
28797
29324
|
return true;
|
|
28798
29325
|
const pkg = packageJson(root);
|
|
28799
29326
|
if (pkg && !pkg.private)
|
|
28800
29327
|
return true;
|
|
28801
|
-
return
|
|
29328
|
+
return path18.basename(root) === "workflow-toolkit";
|
|
28802
29329
|
};
|
|
28803
29330
|
var licenseHolder = (root) => {
|
|
28804
29331
|
const pkg = packageJson(root);
|
|
@@ -28821,13 +29348,13 @@ var planHygieneFiles = (root, opts = {}) => {
|
|
|
28821
29348
|
files.push("LICENSE", "CONTRIBUTING.md");
|
|
28822
29349
|
const planned = [];
|
|
28823
29350
|
for (const file of files) {
|
|
28824
|
-
if (existsSync9(
|
|
29351
|
+
if (existsSync9(path18.join(root, file)))
|
|
28825
29352
|
continue;
|
|
28826
|
-
const tpl =
|
|
29353
|
+
const tpl = path18.join(templatesDir(), file);
|
|
28827
29354
|
if (!existsSync9(tpl))
|
|
28828
29355
|
continue;
|
|
28829
|
-
const content = readFileSync8(tpl, "utf8").replace(/<PROJECT>/g,
|
|
28830
|
-
planned.push({ path:
|
|
29356
|
+
const content = readFileSync8(tpl, "utf8").replace(/<PROJECT>/g, path18.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
|
|
29357
|
+
planned.push({ path: path18.join(root, file), content });
|
|
28831
29358
|
}
|
|
28832
29359
|
return planned;
|
|
28833
29360
|
};
|
|
@@ -28837,15 +29364,15 @@ var ensureHygieneFiles = (root, opts) => {
|
|
|
28837
29364
|
const created = [];
|
|
28838
29365
|
for (const planned of planHygieneFiles(root, opts)) {
|
|
28839
29366
|
writeFileSync4(planned.path, planned.content, "utf8");
|
|
28840
|
-
created.push(
|
|
29367
|
+
created.push(path18.basename(planned.path));
|
|
28841
29368
|
}
|
|
28842
29369
|
return { ok: true, created };
|
|
28843
29370
|
};
|
|
28844
29371
|
|
|
28845
29372
|
// packages/workit-core/src/core/init.ts
|
|
28846
|
-
import
|
|
28847
|
-
import
|
|
28848
|
-
import { spawnSync as
|
|
29373
|
+
import fs12 from "node:fs";
|
|
29374
|
+
import path21 from "node:path";
|
|
29375
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
28849
29376
|
|
|
28850
29377
|
// packages/workit-core/src/core/safe-write.ts
|
|
28851
29378
|
import {
|
|
@@ -28886,11 +29413,11 @@ import {
|
|
|
28886
29413
|
statSync as statSync3,
|
|
28887
29414
|
writeFileSync as writeFileSync6
|
|
28888
29415
|
} from "node:fs";
|
|
28889
|
-
import
|
|
29416
|
+
import path19 from "node:path";
|
|
28890
29417
|
import { isDeepStrictEqual } from "node:util";
|
|
28891
29418
|
|
|
28892
29419
|
// packages/workit-core/src/core/branch-policy.ts
|
|
28893
|
-
import { spawnSync as
|
|
29420
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
28894
29421
|
var PREFIXES = {
|
|
28895
29422
|
feature: "feature/*",
|
|
28896
29423
|
bugfix: "bugfix/*",
|
|
@@ -28899,7 +29426,7 @@ var PREFIXES = {
|
|
|
28899
29426
|
};
|
|
28900
29427
|
var detectBranchPolicy = (workspaceRoot) => {
|
|
28901
29428
|
const branchExists = (name) => {
|
|
28902
|
-
const r =
|
|
29429
|
+
const r = spawnSync7("git", ["branch", "--list", name], {
|
|
28903
29430
|
cwd: workspaceRoot,
|
|
28904
29431
|
encoding: "utf8"
|
|
28905
29432
|
});
|
|
@@ -29000,7 +29527,7 @@ var readFileSafe = (p) => {
|
|
|
29000
29527
|
};
|
|
29001
29528
|
function applyWorkspaceBranchPolicy(opts) {
|
|
29002
29529
|
const { workspace_root, env = process.env } = opts;
|
|
29003
|
-
const dir =
|
|
29530
|
+
const dir = path19.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
29004
29531
|
const {
|
|
29005
29532
|
status,
|
|
29006
29533
|
path: wsPath,
|
|
@@ -29012,7 +29539,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
29012
29539
|
if (status === "malformed" || status === "invalid")
|
|
29013
29540
|
return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
|
|
29014
29541
|
const detection = detectBranchPolicy(workspace_root);
|
|
29015
|
-
const name = String(env.WORKFLOW_BP_NAME ??
|
|
29542
|
+
const name = String(env.WORKFLOW_BP_NAME ?? path19.basename(workspace_root));
|
|
29016
29543
|
const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
|
|
29017
29544
|
const policy = {
|
|
29018
29545
|
preset: detection.preset,
|
|
@@ -29061,7 +29588,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
29061
29588
|
ok: false,
|
|
29062
29589
|
error: `workspaces.json changed while applying the branch policy: ${wsPath}`
|
|
29063
29590
|
};
|
|
29064
|
-
mkdirSync5(
|
|
29591
|
+
mkdirSync5(path19.dirname(wsPath), { recursive: true });
|
|
29065
29592
|
const tmp = `${wsPath}.${process.pid}.tmp`;
|
|
29066
29593
|
try {
|
|
29067
29594
|
writeFileSync6(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + `
|
|
@@ -29093,8 +29620,8 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
29093
29620
|
}
|
|
29094
29621
|
|
|
29095
29622
|
// packages/workit-core/src/core/youtrack.ts
|
|
29096
|
-
import
|
|
29097
|
-
import
|
|
29623
|
+
import fs11 from "node:fs";
|
|
29624
|
+
import path20 from "node:path";
|
|
29098
29625
|
|
|
29099
29626
|
// packages/workit-core/src/core/templates.ts
|
|
29100
29627
|
var repoRoot2 = assetRoot();
|
|
@@ -29102,22 +29629,22 @@ var repoRoot2 = assetRoot();
|
|
|
29102
29629
|
// packages/workit-core/src/core/youtrack.ts
|
|
29103
29630
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
29104
29631
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
29105
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
29106
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
29632
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path20.join(configDir(), "youtrack.json");
|
|
29633
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path20.join(resolveConfigDir(), "youtrack.json");
|
|
29107
29634
|
var youTrackTokenModeOk = (p) => {
|
|
29108
29635
|
if (process.platform === "win32")
|
|
29109
29636
|
return true;
|
|
29110
|
-
const mode =
|
|
29637
|
+
const mode = fs11.statSync(p).mode & 511;
|
|
29111
29638
|
return mode === 384;
|
|
29112
29639
|
};
|
|
29113
29640
|
function readYouTrackConfig(required) {
|
|
29114
29641
|
const cfgPath = youTrackConfigPath();
|
|
29115
|
-
if (!
|
|
29642
|
+
if (!fs11.existsSync(cfgPath)) {
|
|
29116
29643
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
29117
29644
|
}
|
|
29118
29645
|
let parsed;
|
|
29119
29646
|
try {
|
|
29120
|
-
parsed = JSON.parse(
|
|
29647
|
+
parsed = JSON.parse(fs11.readFileSync(cfgPath, "utf8"));
|
|
29121
29648
|
} catch {
|
|
29122
29649
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
29123
29650
|
}
|
|
@@ -29128,11 +29655,11 @@ function readYouTrackConfig(required) {
|
|
|
29128
29655
|
}
|
|
29129
29656
|
var readYouTrackContextConfig = () => {
|
|
29130
29657
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
29131
|
-
if (!
|
|
29658
|
+
if (!fs11.existsSync(cfgPath))
|
|
29132
29659
|
return { data: {} };
|
|
29133
29660
|
let parsed;
|
|
29134
29661
|
try {
|
|
29135
|
-
parsed = JSON.parse(
|
|
29662
|
+
parsed = JSON.parse(fs11.readFileSync(cfgPath, "utf8"));
|
|
29136
29663
|
} catch {
|
|
29137
29664
|
return { error: "YouTrack configuration is unavailable" };
|
|
29138
29665
|
}
|
|
@@ -29171,12 +29698,12 @@ var youTrackToken = () => {
|
|
|
29171
29698
|
if ("error" in loaded)
|
|
29172
29699
|
return loaded;
|
|
29173
29700
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
29174
|
-
const tokenPath = tokenFile ?
|
|
29175
|
-
if (!tokenPath || !
|
|
29701
|
+
const tokenPath = tokenFile ? path20.isAbsolute(tokenFile) ? path20.resolve(tokenFile) : path20.resolve(process.cwd(), tokenFile) : "";
|
|
29702
|
+
if (!tokenPath || !fs11.existsSync(tokenPath))
|
|
29176
29703
|
return { error: "missing youtrack.token" };
|
|
29177
29704
|
if (!youTrackTokenModeOk(tokenPath))
|
|
29178
29705
|
return { error: "youtrack.token mode must be 0600" };
|
|
29179
|
-
const token =
|
|
29706
|
+
const token = fs11.readFileSync(tokenPath, "utf8").trim();
|
|
29180
29707
|
if (!token)
|
|
29181
29708
|
return { error: "empty token file" };
|
|
29182
29709
|
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
@@ -29222,7 +29749,7 @@ function youTrackTokenCreateUrl() {
|
|
|
29222
29749
|
const desc = String(defaults.description ?? "OpenCode workit — /wk-issue-update and /wk-meetings");
|
|
29223
29750
|
const scopes = Array.isArray(defaults.scopes) ? defaults.scopes : ["YouTrack"];
|
|
29224
29751
|
const base = String(config.baseUrl ?? "").replace(/\/+$/, "");
|
|
29225
|
-
const tokenFile = String(config.tokenFile ??
|
|
29752
|
+
const tokenFile = String(config.tokenFile ?? path20.join(path20.dirname(loaded.path), "youtrack.token"));
|
|
29226
29753
|
const tab = String(defaults.profileTab ?? "account-security");
|
|
29227
29754
|
const createUrl = base ? `${base}/users/me?${new URLSearchParams({ tab })}` : null;
|
|
29228
29755
|
const docsUrl = "https://www.jetbrains.com/help/youtrack/cloud/manage-permanent-token.html";
|
|
@@ -29231,7 +29758,7 @@ function youTrackTokenCreateUrl() {
|
|
|
29231
29758
|
tokenName: name,
|
|
29232
29759
|
tokenDescription: desc,
|
|
29233
29760
|
scopes,
|
|
29234
|
-
tokenFile:
|
|
29761
|
+
tokenFile: path20.resolve(tokenFile),
|
|
29235
29762
|
createUrl,
|
|
29236
29763
|
docsUrl,
|
|
29237
29764
|
prefillSupported: false,
|
|
@@ -29277,9 +29804,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
29277
29804
|
} catch {
|
|
29278
29805
|
continue;
|
|
29279
29806
|
}
|
|
29280
|
-
if (!
|
|
29807
|
+
if (!fs11.existsSync(full))
|
|
29281
29808
|
continue;
|
|
29282
|
-
const text =
|
|
29809
|
+
const text = fs11.readFileSync(full, "utf8");
|
|
29283
29810
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
29284
29811
|
if (m)
|
|
29285
29812
|
return m[1];
|
|
@@ -29430,7 +29957,7 @@ var fetchYouTrackIssueBody = async (issue, creds = youTrackToken, request = youT
|
|
|
29430
29957
|
var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
|
|
29431
29958
|
var readJson = (p) => {
|
|
29432
29959
|
try {
|
|
29433
|
-
const parsed = JSON.parse(
|
|
29960
|
+
const parsed = JSON.parse(fs12.readFileSync(p, "utf8"));
|
|
29434
29961
|
return isConfigObject(parsed) ? parsed : null;
|
|
29435
29962
|
} catch {
|
|
29436
29963
|
return null;
|
|
@@ -29442,7 +29969,7 @@ var resolveInitProvider = (cwd) => {
|
|
|
29442
29969
|
return env.toLowerCase();
|
|
29443
29970
|
const root = cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
29444
29971
|
try {
|
|
29445
|
-
const out =
|
|
29972
|
+
const out = spawnSync8("git", ["remote", "get-url", "origin"], {
|
|
29446
29973
|
cwd: root,
|
|
29447
29974
|
encoding: "utf8"
|
|
29448
29975
|
});
|
|
@@ -29460,7 +29987,7 @@ var youtrackJsonContent = (dir) => {
|
|
|
29460
29987
|
const meetingIssue = process.env.WORKFLOW_YT_MEETING_ISSUE;
|
|
29461
29988
|
return {
|
|
29462
29989
|
baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://youtrack.example.com",
|
|
29463
|
-
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ??
|
|
29990
|
+
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path21.join(dir, "youtrack.token"),
|
|
29464
29991
|
...meetingIssue ? {
|
|
29465
29992
|
meetingIssue,
|
|
29466
29993
|
meetingIssues: { general: { issue: meetingIssue, label: "General meetings" } }
|
|
@@ -29490,19 +30017,19 @@ var vcsJsonContent = () => {
|
|
|
29490
30017
|
};
|
|
29491
30018
|
function initApplyData(action, env = process.env) {
|
|
29492
30019
|
const dir = String(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
29493
|
-
|
|
30020
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
29494
30021
|
switch (action) {
|
|
29495
30022
|
case "youtrack_json": {
|
|
29496
|
-
const out =
|
|
29497
|
-
|
|
30023
|
+
const out = path21.join(dir, "youtrack.json");
|
|
30024
|
+
fs12.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
29498
30025
|
`, "utf8");
|
|
29499
30026
|
return { action, ok: true, path: out };
|
|
29500
30027
|
}
|
|
29501
30028
|
case "youtrack_token_placeholder": {
|
|
29502
|
-
const p =
|
|
30029
|
+
const p = path21.join(dir, "youtrack.token");
|
|
29503
30030
|
const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER2 + `
|
|
29504
30031
|
`, 384) === "preserved";
|
|
29505
|
-
const abs =
|
|
30032
|
+
const abs = path21.resolve(p);
|
|
29506
30033
|
return {
|
|
29507
30034
|
action,
|
|
29508
30035
|
ok: true,
|
|
@@ -29514,14 +30041,14 @@ function initApplyData(action, env = process.env) {
|
|
|
29514
30041
|
};
|
|
29515
30042
|
}
|
|
29516
30043
|
case "youtrack_scaffold": {
|
|
29517
|
-
const jsonOut =
|
|
29518
|
-
const tokenOut =
|
|
29519
|
-
|
|
30044
|
+
const jsonOut = path21.join(dir, "youtrack.json");
|
|
30045
|
+
const tokenOut = path21.join(dir, "youtrack.token");
|
|
30046
|
+
fs12.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
29520
30047
|
`, "utf8");
|
|
29521
30048
|
const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER2 + `
|
|
29522
30049
|
`, 384) === "preserved";
|
|
29523
|
-
const configPath =
|
|
29524
|
-
const tokenPath =
|
|
30050
|
+
const configPath = path21.resolve(jsonOut);
|
|
30051
|
+
const tokenPath = path21.resolve(tokenOut);
|
|
29525
30052
|
const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
|
|
29526
30053
|
process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
|
|
29527
30054
|
let tokenCreate = {};
|
|
@@ -29573,10 +30100,10 @@ function initApplyData(action, env = process.env) {
|
|
|
29573
30100
|
}
|
|
29574
30101
|
}
|
|
29575
30102
|
case "vcs_scaffold": {
|
|
29576
|
-
const jsonOut =
|
|
29577
|
-
|
|
30103
|
+
const jsonOut = path21.join(dir, "vcs.json");
|
|
30104
|
+
fs12.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
|
|
29578
30105
|
`, "utf8");
|
|
29579
|
-
const configPath =
|
|
30106
|
+
const configPath = path21.resolve(jsonOut);
|
|
29580
30107
|
const prev = process.env.WORKFLOW_VCS_CONFIG;
|
|
29581
30108
|
process.env.WORKFLOW_VCS_CONFIG = configPath;
|
|
29582
30109
|
try {
|
|
@@ -29695,7 +30222,7 @@ var executeInitApply = (args, directory, runtime) => {
|
|
|
29695
30222
|
protectedNames: args.branch_policy_protected
|
|
29696
30223
|
}, current);
|
|
29697
30224
|
writeConfig(next);
|
|
29698
|
-
return output(ok({ action: "config", path:
|
|
30225
|
+
return output(ok({ action: "config", path: path22.join(configDir(), "config.json"), ...next }));
|
|
29699
30226
|
}
|
|
29700
30227
|
const env = Object.fromEntries(Object.entries({
|
|
29701
30228
|
WORKFLOW_YT_BASE_URL: args.base_url,
|
|
@@ -29748,12 +30275,12 @@ function createRepoTools(runtime = initApplyRuntime) {
|
|
|
29748
30275
|
|
|
29749
30276
|
// packages/workit-opencode/src/tools/context.ts
|
|
29750
30277
|
import { realpathSync as realpathSync10 } from "node:fs";
|
|
29751
|
-
import
|
|
30278
|
+
import path25 from "node:path";
|
|
29752
30279
|
|
|
29753
30280
|
// packages/workit-core/src/core/pr-create.ts
|
|
29754
|
-
import
|
|
29755
|
-
import
|
|
29756
|
-
import { spawnSync as
|
|
30281
|
+
import fs13 from "node:fs";
|
|
30282
|
+
import path23 from "node:path";
|
|
30283
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
29757
30284
|
// packages/workit-core/src/forge/pr-body.ts
|
|
29758
30285
|
function parseGhIssue(value) {
|
|
29759
30286
|
const m = /issues\/(\d+)/.exec(value);
|
|
@@ -29763,13 +30290,13 @@ function parseGhIssue(value) {
|
|
|
29763
30290
|
// packages/workit-core/src/core/pr-create.ts
|
|
29764
30291
|
function whichOnPath(tool) {
|
|
29765
30292
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
29766
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
30293
|
+
for (const dir of (process.env.PATH ?? "").split(path23.delimiter)) {
|
|
29767
30294
|
if (!dir)
|
|
29768
30295
|
continue;
|
|
29769
30296
|
for (const name of names) {
|
|
29770
|
-
const candidate =
|
|
30297
|
+
const candidate = path23.join(dir, name);
|
|
29771
30298
|
try {
|
|
29772
|
-
|
|
30299
|
+
fs13.accessSync(candidate, fs13.constants.X_OK);
|
|
29773
30300
|
return candidate;
|
|
29774
30301
|
} catch {}
|
|
29775
30302
|
}
|
|
@@ -29778,7 +30305,7 @@ function whichOnPath(tool) {
|
|
|
29778
30305
|
}
|
|
29779
30306
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
29780
30307
|
var pushRemote = (cwd) => {
|
|
29781
|
-
const result =
|
|
30308
|
+
const result = spawnSync9("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
29782
30309
|
cwd,
|
|
29783
30310
|
encoding: "utf8"
|
|
29784
30311
|
});
|
|
@@ -29789,9 +30316,9 @@ var pushRemote = (cwd) => {
|
|
|
29789
30316
|
};
|
|
29790
30317
|
|
|
29791
30318
|
// packages/workit-core/src/core/repo-context.ts
|
|
29792
|
-
import { spawnSync as
|
|
30319
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
29793
30320
|
import { existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync7 } from "node:fs";
|
|
29794
|
-
import
|
|
30321
|
+
import path24 from "node:path";
|
|
29795
30322
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
29796
30323
|
try {
|
|
29797
30324
|
gitRevisionParts(value);
|
|
@@ -29818,7 +30345,7 @@ var invalidRange = (cwd) => ({
|
|
|
29818
30345
|
cwd
|
|
29819
30346
|
});
|
|
29820
30347
|
var runGit = (cwd, args) => {
|
|
29821
|
-
const result =
|
|
30348
|
+
const result = spawnSync10("git", args, { cwd, encoding: "utf8" });
|
|
29822
30349
|
return {
|
|
29823
30350
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
29824
30351
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -29925,7 +30452,7 @@ var findPrTemplate = (cwd) => {
|
|
|
29925
30452
|
]) {
|
|
29926
30453
|
const parts = rel.split("/");
|
|
29927
30454
|
const base = parts[parts.length - 1];
|
|
29928
|
-
const dir = parts.length > 1 ?
|
|
30455
|
+
const dir = parts.length > 1 ? path24.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
29929
30456
|
const actual = probeCaseInsensitive(dir, base);
|
|
29930
30457
|
if (actual)
|
|
29931
30458
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -29956,7 +30483,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
29956
30483
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
29957
30484
|
};
|
|
29958
30485
|
var packageScripts = (cwd, keys) => {
|
|
29959
|
-
const pkgPath =
|
|
30486
|
+
const pkgPath = path24.join(cwd, "package.json");
|
|
29960
30487
|
if (!existsSync11(pkgPath))
|
|
29961
30488
|
return [];
|
|
29962
30489
|
try {
|
|
@@ -29982,9 +30509,9 @@ var documentationFiles = (cwd) => {
|
|
|
29982
30509
|
if (entry.isDirectory()) {
|
|
29983
30510
|
if (excluded.has(entry.name))
|
|
29984
30511
|
continue;
|
|
29985
|
-
walk(
|
|
30512
|
+
walk(path24.join(dir, entry.name), depth + 1);
|
|
29986
30513
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
29987
|
-
matches.push("./" +
|
|
30514
|
+
matches.push("./" + path24.relative(cwd, path24.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
29988
30515
|
}
|
|
29989
30516
|
}
|
|
29990
30517
|
};
|
|
@@ -30000,7 +30527,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
30000
30527
|
`);
|
|
30001
30528
|
};
|
|
30002
30529
|
function prReadyContext(root, range) {
|
|
30003
|
-
const cwd =
|
|
30530
|
+
const cwd = path24.resolve(repoRoot3(root));
|
|
30004
30531
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
30005
30532
|
return invalidRange(cwd);
|
|
30006
30533
|
let stdout = "";
|
|
@@ -30058,7 +30585,7 @@ ${status.stderr}` : "");
|
|
|
30058
30585
|
stdout += `template_path: ${template}
|
|
30059
30586
|
|
|
30060
30587
|
`;
|
|
30061
|
-
stdout += readTrimmed2(
|
|
30588
|
+
stdout += readTrimmed2(path24.join(cwd, template), 220);
|
|
30062
30589
|
} else {
|
|
30063
30590
|
stdout += `template_path: none
|
|
30064
30591
|
|
|
@@ -30068,7 +30595,7 @@ ${status.stderr}` : "");
|
|
|
30068
30595
|
stdout += `
|
|
30069
30596
|
`;
|
|
30070
30597
|
stdout += printSection("Recent Validation Signals");
|
|
30071
|
-
if (existsSync11(
|
|
30598
|
+
if (existsSync11(path24.join(cwd, "package.json"))) {
|
|
30072
30599
|
stdout += `package.json detected. Common scripts:
|
|
30073
30600
|
`;
|
|
30074
30601
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -30077,7 +30604,7 @@ ${status.stderr}` : "");
|
|
|
30077
30604
|
`) + `
|
|
30078
30605
|
`;
|
|
30079
30606
|
}
|
|
30080
|
-
if (existsSync11(
|
|
30607
|
+
if (existsSync11(path24.join(cwd, "Cargo.toml")) || existsSync11(path24.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
30081
30608
|
stdout += `Rust project detected.
|
|
30082
30609
|
`;
|
|
30083
30610
|
}
|
|
@@ -30110,7 +30637,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
30110
30637
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
30111
30638
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
30112
30639
|
function changelogContext(root, range) {
|
|
30113
|
-
const cwd =
|
|
30640
|
+
const cwd = path24.resolve(repoRoot3(root));
|
|
30114
30641
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
30115
30642
|
return invalidRange(cwd);
|
|
30116
30643
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -30128,7 +30655,7 @@ function changelogContext(root, range) {
|
|
|
30128
30655
|
stdout += CHANGELOG_RULES + `
|
|
30129
30656
|
`;
|
|
30130
30657
|
stdout += printSection("Existing CHANGELOG.md");
|
|
30131
|
-
const changelogPath =
|
|
30658
|
+
const changelogPath = path24.join(cwd, "CHANGELOG.md");
|
|
30132
30659
|
if (existsSync11(changelogPath)) {
|
|
30133
30660
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
30134
30661
|
`;
|
|
@@ -30148,7 +30675,7 @@ function changelogContext(root, range) {
|
|
|
30148
30675
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
30149
30676
|
}
|
|
30150
30677
|
function docsRefreshContext(root, range) {
|
|
30151
|
-
const cwd =
|
|
30678
|
+
const cwd = path24.resolve(repoRoot3(root));
|
|
30152
30679
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
30153
30680
|
return invalidRange(cwd);
|
|
30154
30681
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -30170,7 +30697,7 @@ function docsRefreshContext(root, range) {
|
|
|
30170
30697
|
`) + `
|
|
30171
30698
|
`;
|
|
30172
30699
|
stdout += printSection("README Preview");
|
|
30173
|
-
const readme =
|
|
30700
|
+
const readme = path24.join(cwd, "README.md");
|
|
30174
30701
|
if (existsSync11(readme)) {
|
|
30175
30702
|
stdout += readTrimmed2(readme, 220) + `
|
|
30176
30703
|
`;
|
|
@@ -30179,7 +30706,7 @@ function docsRefreshContext(root, range) {
|
|
|
30179
30706
|
`;
|
|
30180
30707
|
}
|
|
30181
30708
|
stdout += printSection("Package Scripts");
|
|
30182
|
-
const pkgPath =
|
|
30709
|
+
const pkgPath = path24.join(cwd, "package.json");
|
|
30183
30710
|
if (existsSync11(pkgPath)) {
|
|
30184
30711
|
try {
|
|
30185
30712
|
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
@@ -30193,7 +30720,7 @@ function docsRefreshContext(root, range) {
|
|
|
30193
30720
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
30194
30721
|
}
|
|
30195
30722
|
function releaseNotesContext(root, rangeOrTag) {
|
|
30196
|
-
const cwd =
|
|
30723
|
+
const cwd = path24.resolve(repoRoot3(root));
|
|
30197
30724
|
if (!rangeOrTag) {
|
|
30198
30725
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
30199
30726
|
`, exitCode: 1, cwd };
|
|
@@ -30231,7 +30758,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
30231
30758
|
`;
|
|
30232
30759
|
stdout += printSection("Existing Release Files");
|
|
30233
30760
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
30234
|
-
if (existsSync11(
|
|
30761
|
+
if (existsSync11(path24.join(cwd, rel)))
|
|
30235
30762
|
stdout += rel + `
|
|
30236
30763
|
`;
|
|
30237
30764
|
}
|
|
@@ -30259,7 +30786,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
30259
30786
|
}
|
|
30260
30787
|
|
|
30261
30788
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
30262
|
-
import { spawnSync as
|
|
30789
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
30263
30790
|
var repoPathFromRemote = (remote) => {
|
|
30264
30791
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
30265
30792
|
if (!rest)
|
|
@@ -30280,7 +30807,7 @@ var defaultCli = (bin, available) => {
|
|
|
30280
30807
|
if (!available)
|
|
30281
30808
|
return null;
|
|
30282
30809
|
return (args, root) => {
|
|
30283
|
-
const out =
|
|
30810
|
+
const out = spawnSync11(bin, args, {
|
|
30284
30811
|
cwd: root,
|
|
30285
30812
|
encoding: "utf8",
|
|
30286
30813
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -30363,10 +30890,6 @@ var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\
|
|
|
30363
30890
|
var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
|
|
30364
30891
|
var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
|
|
30365
30892
|
|
|
30366
|
-
// packages/workit-core/src/git/rev.ts
|
|
30367
|
-
var MAX_BUFFER = 256 * 1024 * 1024;
|
|
30368
|
-
var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
|
|
30369
|
-
|
|
30370
30893
|
// packages/workit-core/src/ledger.ts
|
|
30371
30894
|
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
30372
30895
|
var FAILING = new Set(["failed", "blocked"]);
|
|
@@ -30478,7 +31001,7 @@ var createContextTool = () => tool({
|
|
|
30478
31001
|
try {
|
|
30479
31002
|
const payload = parsed.data.payload;
|
|
30480
31003
|
const cwd = payload.cwd;
|
|
30481
|
-
root = realpathSync10(cwd ?
|
|
31004
|
+
root = realpathSync10(cwd ? path25.resolve(context.directory, cwd) : context.directory);
|
|
30482
31005
|
} catch {
|
|
30483
31006
|
return JSON.stringify(failure2("invalid_input", "action target directory cannot be resolved", {
|
|
30484
31007
|
outcome: "not_started"
|
|
@@ -30490,14 +31013,14 @@ var createContextTool = () => tool({
|
|
|
30490
31013
|
|
|
30491
31014
|
// packages/workit-opencode/src/shared/session.ts
|
|
30492
31015
|
import { realpathSync as realpathSync11 } from "node:fs";
|
|
30493
|
-
import
|
|
31016
|
+
import path26 from "node:path";
|
|
30494
31017
|
var sameWorkspace = (expected, observed) => {
|
|
30495
31018
|
if (typeof observed !== "string" || !observed)
|
|
30496
31019
|
return false;
|
|
30497
31020
|
try {
|
|
30498
31021
|
return realpathSync11(expected) === realpathSync11(observed);
|
|
30499
31022
|
} catch {
|
|
30500
|
-
return
|
|
31023
|
+
return path26.resolve(expected) === path26.resolve(observed);
|
|
30501
31024
|
}
|
|
30502
31025
|
};
|
|
30503
31026
|
|
|
@@ -30999,7 +31522,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
30999
31522
|
};
|
|
31000
31523
|
});
|
|
31001
31524
|
// packages/workit-core/src/hooks/context.ts
|
|
31002
|
-
import
|
|
31525
|
+
import path27 from "node:path";
|
|
31003
31526
|
var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
|
|
31004
31527
|
var newestFirst = (left, right) => right.updatedAt.localeCompare(left.updatedAt);
|
|
31005
31528
|
var sessionTaskEntry = (entries, session) => entries.filter((entry) => entry.status !== "closed" && boundTo(entry, session)).toSorted(newestFirst)[0] ?? null;
|
|
@@ -31024,6 +31547,19 @@ var filesKey = (files) => {
|
|
|
31024
31547
|
return signatures.some((item) => item !== "-" && racySignature(item)) ? null : signatures.join("\x00");
|
|
31025
31548
|
};
|
|
31026
31549
|
var CACHE_LIMIT = 64;
|
|
31550
|
+
var SIGNAL_TTL_MS = 1500;
|
|
31551
|
+
var signals = new Map;
|
|
31552
|
+
var recentSignal = (root) => {
|
|
31553
|
+
const now = Date.now();
|
|
31554
|
+
const cached = signals.get(root);
|
|
31555
|
+
if (cached && now - cached.at < SIGNAL_TTL_MS)
|
|
31556
|
+
return cached.value;
|
|
31557
|
+
const value = worktreeSignal(root);
|
|
31558
|
+
signals.set(root, { at: now, value });
|
|
31559
|
+
if (signals.size > CACHE_LIMIT)
|
|
31560
|
+
signals.delete(signals.keys().next().value);
|
|
31561
|
+
return value;
|
|
31562
|
+
};
|
|
31027
31563
|
var cache = new Map;
|
|
31028
31564
|
function sessionCompactContext(store, session, context, selection = "session-bound") {
|
|
31029
31565
|
const listed = store.listTaskIndex();
|
|
@@ -31047,18 +31583,26 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
31047
31583
|
capabilities: context.capabilities
|
|
31048
31584
|
});
|
|
31049
31585
|
const hit = cache.get(slot);
|
|
31050
|
-
if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files))
|
|
31586
|
+
if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files) && (hit.signal === null || hit.signal === recentSignal(store.root)))
|
|
31051
31587
|
return hit.value;
|
|
31052
31588
|
const task = store.readTask(entry.id);
|
|
31053
31589
|
if (!task.ok)
|
|
31054
31590
|
return null;
|
|
31055
|
-
const
|
|
31056
|
-
const observed =
|
|
31057
|
-
const
|
|
31591
|
+
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
31592
|
+
const observed = task.data.evidence.some(isObservedCheck);
|
|
31593
|
+
const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path27.resolve(store.root, ref.path)] : []));
|
|
31594
|
+
const observedFiles = filesKey(files);
|
|
31595
|
+
const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
|
|
31058
31596
|
if (!compact.ok)
|
|
31059
31597
|
return null;
|
|
31060
31598
|
cache.delete(slot);
|
|
31061
|
-
cache.set(slot, {
|
|
31599
|
+
cache.set(slot, {
|
|
31600
|
+
key,
|
|
31601
|
+
files,
|
|
31602
|
+
filesKey: observedFiles,
|
|
31603
|
+
signal: observed ? freshness.current() : null,
|
|
31604
|
+
value: compact.data
|
|
31605
|
+
});
|
|
31062
31606
|
if (cache.size > CACHE_LIMIT)
|
|
31063
31607
|
cache.delete(cache.keys().next().value);
|
|
31064
31608
|
return compact.data;
|
|
@@ -31230,7 +31774,7 @@ var loadProvenance = (logger, pkgUrl) => {
|
|
|
31230
31774
|
|
|
31231
31775
|
// packages/workit-opencode/src/v1/server.ts
|
|
31232
31776
|
var root = assetsRoot();
|
|
31233
|
-
var skillsPath =
|
|
31777
|
+
var skillsPath = path28.join(root, "skills");
|
|
31234
31778
|
var logger = createLogger({
|
|
31235
31779
|
appLog: () => {
|
|
31236
31780
|
return;
|
|
@@ -31249,7 +31793,7 @@ var warnStaleSources = () => {
|
|
|
31249
31793
|
logger.warn(EVENT.hooks, {
|
|
31250
31794
|
boundary: "stale_sources",
|
|
31251
31795
|
reason: "workit sources changed after plugin load; restart the session for latest behavior",
|
|
31252
|
-
files: changed.map((file) =>
|
|
31796
|
+
files: changed.map((file) => path28.basename(file))
|
|
31253
31797
|
});
|
|
31254
31798
|
} catch {}
|
|
31255
31799
|
};
|
|
@@ -31279,7 +31823,7 @@ var plugin = async ({ client, directory }) => {
|
|
|
31279
31823
|
const dispatches = new Map;
|
|
31280
31824
|
try {
|
|
31281
31825
|
logger.info(EVENT.initialization, { host: "opencode", plugin_root: root });
|
|
31282
|
-
logger.info(EVENT.provenance, loadProvenance(logger,
|
|
31826
|
+
logger.info(EVENT.provenance, loadProvenance(logger, path28.join(packageRoot2(), "package.json")));
|
|
31283
31827
|
} catch (error) {
|
|
31284
31828
|
logger.warn(EVENT.hooks, { boundary: "initialization", ...errorDetail(error) });
|
|
31285
31829
|
}
|
|
@@ -32213,7 +32757,7 @@ var normalizeQuestionAnswers = (answers) => {
|
|
|
32213
32757
|
|
|
32214
32758
|
// packages/workit-opencode/src/v2/registry.ts
|
|
32215
32759
|
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "node:fs";
|
|
32216
|
-
import
|
|
32760
|
+
import path29 from "node:path";
|
|
32217
32761
|
var parseFrontmatter = (markdown) => {
|
|
32218
32762
|
const match = markdown.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
32219
32763
|
if (!match)
|
|
@@ -32233,7 +32777,7 @@ var parseFrontmatter = (markdown) => {
|
|
|
32233
32777
|
};
|
|
32234
32778
|
};
|
|
32235
32779
|
var registerSkills = async (ctx) => {
|
|
32236
|
-
const skillsDir =
|
|
32780
|
+
const skillsDir = path29.join(assetsRoot(), "skills");
|
|
32237
32781
|
if (!existsSync13(skillsDir))
|
|
32238
32782
|
return new Set;
|
|
32239
32783
|
const registered = new Set;
|
|
@@ -32242,7 +32786,7 @@ var registerSkills = async (ctx) => {
|
|
|
32242
32786
|
for (const id of WORKIT_METHOD_SKILLS) {
|
|
32243
32787
|
if (existing.has(id))
|
|
32244
32788
|
continue;
|
|
32245
|
-
const file =
|
|
32789
|
+
const file = path29.join(skillsDir, id, "SKILL.md");
|
|
32246
32790
|
if (!existsSync13(file))
|
|
32247
32791
|
continue;
|
|
32248
32792
|
const parsed = parseFrontmatter(readFileSync12(file, "utf8"));
|