@brainervirus/workit-opencode 2.5.0 → 2.7.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
|
|
@@ -13382,7 +13839,7 @@ function reconcileResume(view, observations = []) {
|
|
|
13382
13839
|
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
13383
13840
|
if (!candidate.ok)
|
|
13384
13841
|
return candidate;
|
|
13385
|
-
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
13842
|
+
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data, treeFreshness(view.workspace.root)).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
13386
13843
|
const blockers = [...view.task.progress.blockers];
|
|
13387
13844
|
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
13388
13845
|
blockers.push({
|
|
@@ -14750,10 +15207,7 @@ class WorkitCore {
|
|
|
14750
15207
|
if (finding.data.disposition !== "fixed")
|
|
14751
15208
|
return finding;
|
|
14752
15209
|
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
|
-
]));
|
|
15210
|
+
const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data, treeFreshness(this.store.root)).map((item) => [item.evidenceId, item.status]));
|
|
14757
15211
|
const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
|
|
14758
15212
|
kind: item.data.kind,
|
|
14759
15213
|
candidateId: item.data.candidateId,
|
|
@@ -14768,6 +15222,71 @@ class WorkitCore {
|
|
|
14768
15222
|
return changed;
|
|
14769
15223
|
return success(changed.data.revision, null, changed.data.evidence.at(-1));
|
|
14770
15224
|
}
|
|
15225
|
+
observeCheck(request) {
|
|
15226
|
+
return this.retryOmittedRevisions(request, () => this.observeCheckOnce(request));
|
|
15227
|
+
}
|
|
15228
|
+
observeCheckOnce(request) {
|
|
15229
|
+
const root = this.contextRootError();
|
|
15230
|
+
if (!root.ok)
|
|
15231
|
+
return root;
|
|
15232
|
+
if (this.context.caller.host !== "workit_cli" || (this.context.workerId ?? null) !== null)
|
|
15233
|
+
return failure2("permission_denied", "only the workit CLI records observed checks");
|
|
15234
|
+
const observation = checkObservationSchema.safeParse(request?.observation);
|
|
15235
|
+
if (!observation.success)
|
|
15236
|
+
return failure2("invalid_input", "check observation is invalid", {
|
|
15237
|
+
fields: observation.error.issues.map((issue) => ({
|
|
15238
|
+
path: issue.path.join("."),
|
|
15239
|
+
reason: issue.message
|
|
15240
|
+
}))
|
|
15241
|
+
});
|
|
15242
|
+
const task = this.store.readTask(request.taskId);
|
|
15243
|
+
if (!task.ok)
|
|
15244
|
+
return task;
|
|
15245
|
+
if (task.data.status === "closed")
|
|
15246
|
+
return failure2("invalid_transition", "closed task cannot record evidence");
|
|
15247
|
+
const observed = observation.data;
|
|
15248
|
+
let candidate = null;
|
|
15249
|
+
if (observed.tree === null) {
|
|
15250
|
+
const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
15251
|
+
if (!captured.ok)
|
|
15252
|
+
return captured;
|
|
15253
|
+
candidate = captured.data;
|
|
15254
|
+
}
|
|
15255
|
+
const label = observed.name ?? observed.argv.join(" ");
|
|
15256
|
+
const data = {
|
|
15257
|
+
kind: "check",
|
|
15258
|
+
claim: `workit check ${label}`.slice(0, 500),
|
|
15259
|
+
requirementIds: [],
|
|
15260
|
+
...candidate ? { beforeCandidateId: candidate.id, candidateId: candidate.id } : {},
|
|
15261
|
+
result: observed.exitCode === 0 ? "passed" : "failed",
|
|
15262
|
+
summary: `${observed.configured ? "configured" : "ad-hoc"} check exited ${observed.exitCode}${observed.timedOut ? " (timed out)" : ""} in ${observed.durationMs} ms`,
|
|
15263
|
+
refs: [],
|
|
15264
|
+
exitCode: observed.exitCode,
|
|
15265
|
+
reviewContext: null,
|
|
15266
|
+
observation: observed
|
|
15267
|
+
};
|
|
15268
|
+
const changed = this.store.mutateTask(task.data.id, request.expectedRevision ?? task.data.revision, (current, mutation) => {
|
|
15269
|
+
const candidates = candidate === null || current.candidates.some((item) => item.id === candidate.id) ? current.candidates : [...current.candidates, candidate];
|
|
15270
|
+
const critical = [...new Set([...current.critical ?? [], CHECK_OBSERVATION_PATH])];
|
|
15271
|
+
return success(mutation.revision, null, {
|
|
15272
|
+
...current,
|
|
15273
|
+
critical,
|
|
15274
|
+
candidates,
|
|
15275
|
+
evidence: [
|
|
15276
|
+
...current.evidence,
|
|
15277
|
+
{
|
|
15278
|
+
id: newId(),
|
|
15279
|
+
recordedAt: mutation.now,
|
|
15280
|
+
provenance: provenance(this.context, "host_observed"),
|
|
15281
|
+
data
|
|
15282
|
+
}
|
|
15283
|
+
]
|
|
15284
|
+
});
|
|
15285
|
+
}, trustedNow(this.context));
|
|
15286
|
+
if (!changed.ok)
|
|
15287
|
+
return changed;
|
|
15288
|
+
return success(changed.data.revision, null, changed.data.evidence.at(-1));
|
|
15289
|
+
}
|
|
14771
15290
|
decision(request) {
|
|
14772
15291
|
return this.recordDecision(request);
|
|
14773
15292
|
}
|
|
@@ -15003,7 +15522,7 @@ class WorkitCore {
|
|
|
15003
15522
|
const current = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
15004
15523
|
if (!current.ok)
|
|
15005
15524
|
return current;
|
|
15006
|
-
const evaluations = evaluateEvidence(task.data, current.data);
|
|
15525
|
+
const evaluations = evaluateEvidence(task.data, current.data, treeFreshness(this.store.root));
|
|
15007
15526
|
const verified = evidence.some((entry) => {
|
|
15008
15527
|
const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
|
|
15009
15528
|
return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
|
|
@@ -15707,7 +16226,8 @@ class WorkitCore {
|
|
|
15707
16226
|
if (!current.ok)
|
|
15708
16227
|
return current;
|
|
15709
16228
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
15710
|
-
const
|
|
16229
|
+
const tree = treeFreshness(this.store.root);
|
|
16230
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
|
|
15711
16231
|
return success(task.revision, workspace.data.revision, {
|
|
15712
16232
|
id: task.id,
|
|
15713
16233
|
revision: task.revision,
|
|
@@ -15724,7 +16244,7 @@ class WorkitCore {
|
|
|
15724
16244
|
writer: evaluationWorkspace.writer
|
|
15725
16245
|
});
|
|
15726
16246
|
}
|
|
15727
|
-
compactContext(taskId) {
|
|
16247
|
+
compactContext(taskId, freshness) {
|
|
15728
16248
|
const root = this.contextRootError();
|
|
15729
16249
|
if (!root.ok)
|
|
15730
16250
|
return root;
|
|
@@ -15734,12 +16254,12 @@ class WorkitCore {
|
|
|
15734
16254
|
const helper = this.helperTaskGuard(task.data, true);
|
|
15735
16255
|
if (!helper.ok)
|
|
15736
16256
|
return helper;
|
|
15737
|
-
const view = this.view(task.data, false);
|
|
16257
|
+
const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
|
|
15738
16258
|
if (!view.ok)
|
|
15739
16259
|
return view;
|
|
15740
16260
|
return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
|
|
15741
16261
|
}
|
|
15742
|
-
view(task, capture = true) {
|
|
16262
|
+
view(task, capture = true, freshness = treeFreshness(this.store.root)) {
|
|
15743
16263
|
const workspace = this.store.readWorkspace();
|
|
15744
16264
|
if (!workspace.ok)
|
|
15745
16265
|
return workspace;
|
|
@@ -15750,13 +16270,14 @@ class WorkitCore {
|
|
|
15750
16270
|
if (!current.ok)
|
|
15751
16271
|
return current;
|
|
15752
16272
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
15753
|
-
const
|
|
16273
|
+
const tree = freshness;
|
|
16274
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
|
|
15754
16275
|
return success(task.revision, workspace.data.revision, {
|
|
15755
16276
|
task,
|
|
15756
16277
|
workspace: evaluationWorkspace,
|
|
15757
16278
|
capabilities: this.context.capabilities,
|
|
15758
16279
|
requirements,
|
|
15759
|
-
evidence: evaluateEvidence(task, current.data)
|
|
16280
|
+
evidence: evaluateEvidence(task, current.data, tree)
|
|
15760
16281
|
});
|
|
15761
16282
|
}
|
|
15762
16283
|
reconcileResume(view, observations = []) {
|
|
@@ -15983,13 +16504,14 @@ class WorkitCore {
|
|
|
15983
16504
|
return current;
|
|
15984
16505
|
const withCandidate = current.data;
|
|
15985
16506
|
const taskForView = withCandidate.id === task.data.candidates.at(-1)?.id ? task.data : { ...task.data, candidates: [...task.data.candidates, withCandidate] };
|
|
15986
|
-
const
|
|
16507
|
+
const tree = treeFreshness(this.store.root);
|
|
16508
|
+
const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller, tree);
|
|
15987
16509
|
const view = {
|
|
15988
16510
|
task: taskForView,
|
|
15989
16511
|
workspace: workspace.data,
|
|
15990
16512
|
capabilities: this.context.capabilities,
|
|
15991
16513
|
requirements,
|
|
15992
|
-
evidence: evaluateEvidence(taskForView, withCandidate)
|
|
16514
|
+
evidence: evaluateEvidence(taskForView, withCandidate, tree)
|
|
15993
16515
|
};
|
|
15994
16516
|
const closure = evaluateClosure(input.outcome, view);
|
|
15995
16517
|
if (!closure.ok)
|
|
@@ -16053,22 +16575,22 @@ function resolveGitRevision(root, value) {
|
|
|
16053
16575
|
}
|
|
16054
16576
|
function resolveInside(root, candidate) {
|
|
16055
16577
|
const base = realpathSync7(root);
|
|
16056
|
-
const target =
|
|
16057
|
-
if (target !== base && !target.startsWith(base +
|
|
16578
|
+
const target = path13.resolve(base, candidate);
|
|
16579
|
+
if (target !== base && !target.startsWith(base + path13.sep)) {
|
|
16058
16580
|
throw new Error("path must stay inside repository root");
|
|
16059
16581
|
}
|
|
16060
16582
|
let ancestor = target;
|
|
16061
16583
|
while (!existsSync5(ancestor))
|
|
16062
|
-
ancestor =
|
|
16584
|
+
ancestor = path13.dirname(ancestor);
|
|
16063
16585
|
const canonicalAncestor = realpathSync7(ancestor);
|
|
16064
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
16586
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path13.sep)) {
|
|
16065
16587
|
throw new Error("path must stay inside repository root");
|
|
16066
16588
|
}
|
|
16067
16589
|
return target;
|
|
16068
16590
|
}
|
|
16069
16591
|
function run2(root, executable, args, env = {}) {
|
|
16070
16592
|
const cwd = realpathSync7(root);
|
|
16071
|
-
const result =
|
|
16593
|
+
const result = spawnSync6(executable, args, {
|
|
16072
16594
|
cwd,
|
|
16073
16595
|
encoding: "utf8",
|
|
16074
16596
|
env: { ...process.env, ...env }
|
|
@@ -16118,8 +16640,8 @@ if (false) {}
|
|
|
16118
16640
|
|
|
16119
16641
|
// packages/workit-core/src/core/logger.ts
|
|
16120
16642
|
import { appendFileSync, mkdirSync as mkdirSync3, readdirSync as readdirSync4, unlinkSync as unlinkSync2 } from "node:fs";
|
|
16121
|
-
import
|
|
16122
|
-
import
|
|
16643
|
+
import os3 from "node:os";
|
|
16644
|
+
import path14 from "node:path";
|
|
16123
16645
|
var REDACTED = "[REDACTED]";
|
|
16124
16646
|
var DEFAULT_MAX_RATE = 20;
|
|
16125
16647
|
var DEFAULT_RATE_WINDOW_MS = 1000;
|
|
@@ -16173,19 +16695,19 @@ var resolveStateDir = () => {
|
|
|
16173
16695
|
const override = process.env.WORKFLOW_TOOLKIT_STATE;
|
|
16174
16696
|
if (override)
|
|
16175
16697
|
return override;
|
|
16176
|
-
const home =
|
|
16698
|
+
const home = os3.homedir();
|
|
16177
16699
|
if (process.env.XDG_STATE_HOME)
|
|
16178
|
-
return
|
|
16179
|
-
switch (
|
|
16700
|
+
return path14.join(process.env.XDG_STATE_HOME, "workit");
|
|
16701
|
+
switch (os3.platform()) {
|
|
16180
16702
|
case "darwin":
|
|
16181
|
-
return
|
|
16703
|
+
return path14.join(home, "Library", "Application Support", "workit");
|
|
16182
16704
|
case "win32":
|
|
16183
|
-
return
|
|
16705
|
+
return path14.join(process.env.LOCALAPPDATA ?? path14.join(home, "AppData", "Local"), "workit");
|
|
16184
16706
|
default:
|
|
16185
|
-
return
|
|
16707
|
+
return path14.join(process.env.HOME ?? home, ".local", "state", "workit");
|
|
16186
16708
|
}
|
|
16187
16709
|
};
|
|
16188
|
-
var homePattern = new RegExp(`${escapeRegExp(
|
|
16710
|
+
var homePattern = new RegExp(`${escapeRegExp(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
16189
16711
|
var applyPatterns = (value) => {
|
|
16190
16712
|
let out = value;
|
|
16191
16713
|
out = out.replace(SECRET_VALUE, REDACTED);
|
|
@@ -16255,14 +16777,14 @@ var pruneOldFiles = (dir) => {
|
|
|
16255
16777
|
const files = readdirSync4(dir).filter((name) => DAY_FILE.test(name)).toSorted().toReversed();
|
|
16256
16778
|
for (const file of files.slice(RETAINED_DAYS)) {
|
|
16257
16779
|
try {
|
|
16258
|
-
unlinkSync2(
|
|
16780
|
+
unlinkSync2(path14.join(dir, file));
|
|
16259
16781
|
} catch {}
|
|
16260
16782
|
}
|
|
16261
16783
|
} catch {}
|
|
16262
16784
|
};
|
|
16263
16785
|
var createLogger = (options = {}) => {
|
|
16264
16786
|
const stateDir = options.stateDir ?? resolveStateDir();
|
|
16265
|
-
const logDir =
|
|
16787
|
+
const logDir = path14.join(stateDir, "logs");
|
|
16266
16788
|
const now = options.now ?? (() => new Date);
|
|
16267
16789
|
const maxRate = options.maxRate ?? DEFAULT_MAX_RATE;
|
|
16268
16790
|
const rateWindowMs = options.rateWindowMs ?? DEFAULT_RATE_WINDOW_MS;
|
|
@@ -16296,7 +16818,7 @@ var createLogger = (options = {}) => {
|
|
|
16296
16818
|
const line = `${JSON.stringify(event)}
|
|
16297
16819
|
`;
|
|
16298
16820
|
mkdirSync3(logDir, { recursive: true, mode: 448 });
|
|
16299
|
-
appendFileSync(
|
|
16821
|
+
appendFileSync(path14.join(logDir, dailyFileName(date)), line, { mode: 384 });
|
|
16300
16822
|
pruneOldFiles(logDir);
|
|
16301
16823
|
} catch {
|
|
16302
16824
|
try {
|
|
@@ -16307,7 +16829,7 @@ var createLogger = (options = {}) => {
|
|
|
16307
16829
|
context: { redaction_failed: true }
|
|
16308
16830
|
};
|
|
16309
16831
|
mkdirSync3(logDir, { recursive: true, mode: 448 });
|
|
16310
|
-
appendFileSync(
|
|
16832
|
+
appendFileSync(path14.join(logDir, dailyFileName(date)), `${JSON.stringify(event)}
|
|
16311
16833
|
`, {
|
|
16312
16834
|
mode: 384
|
|
16313
16835
|
});
|
|
@@ -16356,47 +16878,47 @@ OpenCode Workit decisions use the native question header "Workit decision: <purp
|
|
|
16356
16878
|
|
|
16357
16879
|
// packages/workit-opencode/src/shared/assets.ts
|
|
16358
16880
|
import { existsSync as existsSync6, realpathSync as realpathSync8 } from "node:fs";
|
|
16359
|
-
import
|
|
16881
|
+
import path15 from "node:path";
|
|
16360
16882
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
16361
16883
|
var moduleDir = () => {
|
|
16362
16884
|
try {
|
|
16363
|
-
return realpathSync8(
|
|
16885
|
+
return realpathSync8(path15.dirname(fileURLToPath2(import.meta.url)));
|
|
16364
16886
|
} catch {
|
|
16365
|
-
return
|
|
16887
|
+
return path15.dirname(fileURLToPath2(import.meta.url));
|
|
16366
16888
|
}
|
|
16367
16889
|
};
|
|
16368
16890
|
var packageRoot2 = () => {
|
|
16369
16891
|
const dir = moduleDir();
|
|
16370
16892
|
for (const candidate of [
|
|
16371
|
-
|
|
16372
|
-
|
|
16373
|
-
|
|
16893
|
+
path15.resolve(dir, ".."),
|
|
16894
|
+
path15.resolve(dir, "..", ".."),
|
|
16895
|
+
path15.resolve(dir, "..", "..", "..")
|
|
16374
16896
|
]) {
|
|
16375
|
-
if (existsSync6(
|
|
16897
|
+
if (existsSync6(path15.join(candidate, "package.json")))
|
|
16376
16898
|
return candidate;
|
|
16377
16899
|
}
|
|
16378
|
-
return
|
|
16900
|
+
return path15.resolve(dir, "..");
|
|
16379
16901
|
};
|
|
16380
|
-
var assetsRoot = () =>
|
|
16902
|
+
var assetsRoot = () => path15.join(packageRoot2(), "assets");
|
|
16381
16903
|
|
|
16382
16904
|
// packages/workit-opencode/src/stale-sources.ts
|
|
16383
16905
|
import { existsSync as existsSync7, readdirSync as readdirSync5 } from "node:fs";
|
|
16384
|
-
import
|
|
16906
|
+
import path16 from "node:path";
|
|
16385
16907
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
16386
16908
|
var typescriptFiles = (directory) => {
|
|
16387
16909
|
if (!existsSync7(directory))
|
|
16388
16910
|
return [];
|
|
16389
16911
|
return readdirSync5(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
16390
|
-
const file =
|
|
16912
|
+
const file = path16.join(directory, entry.name);
|
|
16391
16913
|
return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
|
|
16392
16914
|
});
|
|
16393
16915
|
};
|
|
16394
16916
|
var pluginSourceFiles = [
|
|
16395
16917
|
...new Set([
|
|
16396
16918
|
fileURLToPath3(import.meta.url),
|
|
16397
|
-
...typescriptFiles(
|
|
16398
|
-
|
|
16399
|
-
...typescriptFiles(
|
|
16919
|
+
...typescriptFiles(path16.join(packageRoot2(), "src")),
|
|
16920
|
+
path16.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
|
|
16921
|
+
...typescriptFiles(path16.join(packageRoot2(), "..", "workit-core", "src", "core"))
|
|
16400
16922
|
])
|
|
16401
16923
|
].filter((file) => existsSync7(file)).toSorted();
|
|
16402
16924
|
|
|
@@ -28721,11 +29243,11 @@ function tool(input) {
|
|
|
28721
29243
|
}
|
|
28722
29244
|
tool.schema = exports_external2;
|
|
28723
29245
|
// packages/workit-opencode/src/shared/init-apply.ts
|
|
28724
|
-
import
|
|
29246
|
+
import path22 from "node:path";
|
|
28725
29247
|
|
|
28726
29248
|
// packages/workit-core/src/core/gitignore.ts
|
|
28727
29249
|
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
28728
|
-
import
|
|
29250
|
+
import path17 from "node:path";
|
|
28729
29251
|
var GITIGNORE_ENTRIES = [
|
|
28730
29252
|
"# workit: SDD working state (never commit)",
|
|
28731
29253
|
"docs/*/sdd/",
|
|
@@ -28745,7 +29267,7 @@ var GITIGNORE_ENTRIES = [
|
|
|
28745
29267
|
var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
28746
29268
|
if (!confirmed)
|
|
28747
29269
|
return { ok: false, error: "confirmed: true required" };
|
|
28748
|
-
const file =
|
|
29270
|
+
const file = path17.join(workspaceRoot, ".gitignore");
|
|
28749
29271
|
const existing = existsSync8(file) ? readFileSync7(file, "utf8") : "";
|
|
28750
29272
|
const existingLines = new Set(existing.split(`
|
|
28751
29273
|
`).map((l) => l.trim()).filter(Boolean));
|
|
@@ -28773,7 +29295,7 @@ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
|
28773
29295
|
|
|
28774
29296
|
// packages/workit-core/src/core/hygiene.ts
|
|
28775
29297
|
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
|
|
28776
|
-
import
|
|
29298
|
+
import path18 from "node:path";
|
|
28777
29299
|
|
|
28778
29300
|
// packages/workit-core/src/core/scripts.ts
|
|
28779
29301
|
var PLUGIN_ROOT = packageRoot();
|
|
@@ -28782,23 +29304,23 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
28782
29304
|
|
|
28783
29305
|
// packages/workit-core/src/core/hygiene.ts
|
|
28784
29306
|
var repoRoot = assetRoot();
|
|
28785
|
-
var templatesDir = () =>
|
|
29307
|
+
var templatesDir = () => path18.join(repoRoot, "templates", "hygiene");
|
|
28786
29308
|
var packageJson = (root) => {
|
|
28787
|
-
if (!existsSync9(
|
|
29309
|
+
if (!existsSync9(path18.join(root, "package.json")))
|
|
28788
29310
|
return null;
|
|
28789
29311
|
try {
|
|
28790
|
-
return JSON.parse(readFileSync8(
|
|
29312
|
+
return JSON.parse(readFileSync8(path18.join(root, "package.json"), "utf8"));
|
|
28791
29313
|
} catch {
|
|
28792
29314
|
return null;
|
|
28793
29315
|
}
|
|
28794
29316
|
};
|
|
28795
29317
|
var isOpenSource = (root) => {
|
|
28796
|
-
if (existsSync9(
|
|
29318
|
+
if (existsSync9(path18.join(root, "LICENSE")))
|
|
28797
29319
|
return true;
|
|
28798
29320
|
const pkg = packageJson(root);
|
|
28799
29321
|
if (pkg && !pkg.private)
|
|
28800
29322
|
return true;
|
|
28801
|
-
return
|
|
29323
|
+
return path18.basename(root) === "workflow-toolkit";
|
|
28802
29324
|
};
|
|
28803
29325
|
var licenseHolder = (root) => {
|
|
28804
29326
|
const pkg = packageJson(root);
|
|
@@ -28821,13 +29343,13 @@ var planHygieneFiles = (root, opts = {}) => {
|
|
|
28821
29343
|
files.push("LICENSE", "CONTRIBUTING.md");
|
|
28822
29344
|
const planned = [];
|
|
28823
29345
|
for (const file of files) {
|
|
28824
|
-
if (existsSync9(
|
|
29346
|
+
if (existsSync9(path18.join(root, file)))
|
|
28825
29347
|
continue;
|
|
28826
|
-
const tpl =
|
|
29348
|
+
const tpl = path18.join(templatesDir(), file);
|
|
28827
29349
|
if (!existsSync9(tpl))
|
|
28828
29350
|
continue;
|
|
28829
|
-
const content = readFileSync8(tpl, "utf8").replace(/<PROJECT>/g,
|
|
28830
|
-
planned.push({ path:
|
|
29351
|
+
const content = readFileSync8(tpl, "utf8").replace(/<PROJECT>/g, path18.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
|
|
29352
|
+
planned.push({ path: path18.join(root, file), content });
|
|
28831
29353
|
}
|
|
28832
29354
|
return planned;
|
|
28833
29355
|
};
|
|
@@ -28837,15 +29359,15 @@ var ensureHygieneFiles = (root, opts) => {
|
|
|
28837
29359
|
const created = [];
|
|
28838
29360
|
for (const planned of planHygieneFiles(root, opts)) {
|
|
28839
29361
|
writeFileSync4(planned.path, planned.content, "utf8");
|
|
28840
|
-
created.push(
|
|
29362
|
+
created.push(path18.basename(planned.path));
|
|
28841
29363
|
}
|
|
28842
29364
|
return { ok: true, created };
|
|
28843
29365
|
};
|
|
28844
29366
|
|
|
28845
29367
|
// packages/workit-core/src/core/init.ts
|
|
28846
|
-
import
|
|
28847
|
-
import
|
|
28848
|
-
import { spawnSync as
|
|
29368
|
+
import fs12 from "node:fs";
|
|
29369
|
+
import path21 from "node:path";
|
|
29370
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
28849
29371
|
|
|
28850
29372
|
// packages/workit-core/src/core/safe-write.ts
|
|
28851
29373
|
import {
|
|
@@ -28886,11 +29408,11 @@ import {
|
|
|
28886
29408
|
statSync as statSync3,
|
|
28887
29409
|
writeFileSync as writeFileSync6
|
|
28888
29410
|
} from "node:fs";
|
|
28889
|
-
import
|
|
29411
|
+
import path19 from "node:path";
|
|
28890
29412
|
import { isDeepStrictEqual } from "node:util";
|
|
28891
29413
|
|
|
28892
29414
|
// packages/workit-core/src/core/branch-policy.ts
|
|
28893
|
-
import { spawnSync as
|
|
29415
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
28894
29416
|
var PREFIXES = {
|
|
28895
29417
|
feature: "feature/*",
|
|
28896
29418
|
bugfix: "bugfix/*",
|
|
@@ -28899,7 +29421,7 @@ var PREFIXES = {
|
|
|
28899
29421
|
};
|
|
28900
29422
|
var detectBranchPolicy = (workspaceRoot) => {
|
|
28901
29423
|
const branchExists = (name) => {
|
|
28902
|
-
const r =
|
|
29424
|
+
const r = spawnSync7("git", ["branch", "--list", name], {
|
|
28903
29425
|
cwd: workspaceRoot,
|
|
28904
29426
|
encoding: "utf8"
|
|
28905
29427
|
});
|
|
@@ -28959,6 +29481,11 @@ function cursorHooksEntry(_packageDir) {
|
|
|
28959
29481
|
};
|
|
28960
29482
|
}
|
|
28961
29483
|
|
|
29484
|
+
// packages/workit-core/src/core/host-install.ts
|
|
29485
|
+
var CLAUDE_MARKETPLACE_NAME = "workit";
|
|
29486
|
+
var CLAUDE_PLUGIN_ID = `workit@${CLAUDE_MARKETPLACE_NAME}`;
|
|
29487
|
+
var CLAUDE_SCOPES = new Set(["user", "project", "local"]);
|
|
29488
|
+
|
|
28962
29489
|
// packages/workit-core/src/core/doctor.ts
|
|
28963
29490
|
var canonicalCursorHook = cursorHooksEntry("").command;
|
|
28964
29491
|
var INSTALLER_REQUIRED = new Set([
|
|
@@ -28995,7 +29522,7 @@ var readFileSafe = (p) => {
|
|
|
28995
29522
|
};
|
|
28996
29523
|
function applyWorkspaceBranchPolicy(opts) {
|
|
28997
29524
|
const { workspace_root, env = process.env } = opts;
|
|
28998
|
-
const dir =
|
|
29525
|
+
const dir = path19.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
28999
29526
|
const {
|
|
29000
29527
|
status,
|
|
29001
29528
|
path: wsPath,
|
|
@@ -29007,7 +29534,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
29007
29534
|
if (status === "malformed" || status === "invalid")
|
|
29008
29535
|
return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
|
|
29009
29536
|
const detection = detectBranchPolicy(workspace_root);
|
|
29010
|
-
const name = String(env.WORKFLOW_BP_NAME ??
|
|
29537
|
+
const name = String(env.WORKFLOW_BP_NAME ?? path19.basename(workspace_root));
|
|
29011
29538
|
const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
|
|
29012
29539
|
const policy = {
|
|
29013
29540
|
preset: detection.preset,
|
|
@@ -29056,7 +29583,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
29056
29583
|
ok: false,
|
|
29057
29584
|
error: `workspaces.json changed while applying the branch policy: ${wsPath}`
|
|
29058
29585
|
};
|
|
29059
|
-
mkdirSync5(
|
|
29586
|
+
mkdirSync5(path19.dirname(wsPath), { recursive: true });
|
|
29060
29587
|
const tmp = `${wsPath}.${process.pid}.tmp`;
|
|
29061
29588
|
try {
|
|
29062
29589
|
writeFileSync6(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + `
|
|
@@ -29088,8 +29615,8 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
29088
29615
|
}
|
|
29089
29616
|
|
|
29090
29617
|
// packages/workit-core/src/core/youtrack.ts
|
|
29091
|
-
import
|
|
29092
|
-
import
|
|
29618
|
+
import fs11 from "node:fs";
|
|
29619
|
+
import path20 from "node:path";
|
|
29093
29620
|
|
|
29094
29621
|
// packages/workit-core/src/core/templates.ts
|
|
29095
29622
|
var repoRoot2 = assetRoot();
|
|
@@ -29097,22 +29624,22 @@ var repoRoot2 = assetRoot();
|
|
|
29097
29624
|
// packages/workit-core/src/core/youtrack.ts
|
|
29098
29625
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
29099
29626
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
29100
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
29101
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
29627
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path20.join(configDir(), "youtrack.json");
|
|
29628
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path20.join(resolveConfigDir(), "youtrack.json");
|
|
29102
29629
|
var youTrackTokenModeOk = (p) => {
|
|
29103
29630
|
if (process.platform === "win32")
|
|
29104
29631
|
return true;
|
|
29105
|
-
const mode =
|
|
29632
|
+
const mode = fs11.statSync(p).mode & 511;
|
|
29106
29633
|
return mode === 384;
|
|
29107
29634
|
};
|
|
29108
29635
|
function readYouTrackConfig(required) {
|
|
29109
29636
|
const cfgPath = youTrackConfigPath();
|
|
29110
|
-
if (!
|
|
29637
|
+
if (!fs11.existsSync(cfgPath)) {
|
|
29111
29638
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
29112
29639
|
}
|
|
29113
29640
|
let parsed;
|
|
29114
29641
|
try {
|
|
29115
|
-
parsed = JSON.parse(
|
|
29642
|
+
parsed = JSON.parse(fs11.readFileSync(cfgPath, "utf8"));
|
|
29116
29643
|
} catch {
|
|
29117
29644
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
29118
29645
|
}
|
|
@@ -29123,11 +29650,11 @@ function readYouTrackConfig(required) {
|
|
|
29123
29650
|
}
|
|
29124
29651
|
var readYouTrackContextConfig = () => {
|
|
29125
29652
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
29126
|
-
if (!
|
|
29653
|
+
if (!fs11.existsSync(cfgPath))
|
|
29127
29654
|
return { data: {} };
|
|
29128
29655
|
let parsed;
|
|
29129
29656
|
try {
|
|
29130
|
-
parsed = JSON.parse(
|
|
29657
|
+
parsed = JSON.parse(fs11.readFileSync(cfgPath, "utf8"));
|
|
29131
29658
|
} catch {
|
|
29132
29659
|
return { error: "YouTrack configuration is unavailable" };
|
|
29133
29660
|
}
|
|
@@ -29166,12 +29693,12 @@ var youTrackToken = () => {
|
|
|
29166
29693
|
if ("error" in loaded)
|
|
29167
29694
|
return loaded;
|
|
29168
29695
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
29169
|
-
const tokenPath = tokenFile ?
|
|
29170
|
-
if (!tokenPath || !
|
|
29696
|
+
const tokenPath = tokenFile ? path20.isAbsolute(tokenFile) ? path20.resolve(tokenFile) : path20.resolve(process.cwd(), tokenFile) : "";
|
|
29697
|
+
if (!tokenPath || !fs11.existsSync(tokenPath))
|
|
29171
29698
|
return { error: "missing youtrack.token" };
|
|
29172
29699
|
if (!youTrackTokenModeOk(tokenPath))
|
|
29173
29700
|
return { error: "youtrack.token mode must be 0600" };
|
|
29174
|
-
const token =
|
|
29701
|
+
const token = fs11.readFileSync(tokenPath, "utf8").trim();
|
|
29175
29702
|
if (!token)
|
|
29176
29703
|
return { error: "empty token file" };
|
|
29177
29704
|
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
@@ -29217,7 +29744,7 @@ function youTrackTokenCreateUrl() {
|
|
|
29217
29744
|
const desc = String(defaults.description ?? "OpenCode workit — /wk-issue-update and /wk-meetings");
|
|
29218
29745
|
const scopes = Array.isArray(defaults.scopes) ? defaults.scopes : ["YouTrack"];
|
|
29219
29746
|
const base = String(config.baseUrl ?? "").replace(/\/+$/, "");
|
|
29220
|
-
const tokenFile = String(config.tokenFile ??
|
|
29747
|
+
const tokenFile = String(config.tokenFile ?? path20.join(path20.dirname(loaded.path), "youtrack.token"));
|
|
29221
29748
|
const tab = String(defaults.profileTab ?? "account-security");
|
|
29222
29749
|
const createUrl = base ? `${base}/users/me?${new URLSearchParams({ tab })}` : null;
|
|
29223
29750
|
const docsUrl = "https://www.jetbrains.com/help/youtrack/cloud/manage-permanent-token.html";
|
|
@@ -29226,7 +29753,7 @@ function youTrackTokenCreateUrl() {
|
|
|
29226
29753
|
tokenName: name,
|
|
29227
29754
|
tokenDescription: desc,
|
|
29228
29755
|
scopes,
|
|
29229
|
-
tokenFile:
|
|
29756
|
+
tokenFile: path20.resolve(tokenFile),
|
|
29230
29757
|
createUrl,
|
|
29231
29758
|
docsUrl,
|
|
29232
29759
|
prefillSupported: false,
|
|
@@ -29272,9 +29799,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
29272
29799
|
} catch {
|
|
29273
29800
|
continue;
|
|
29274
29801
|
}
|
|
29275
|
-
if (!
|
|
29802
|
+
if (!fs11.existsSync(full))
|
|
29276
29803
|
continue;
|
|
29277
|
-
const text =
|
|
29804
|
+
const text = fs11.readFileSync(full, "utf8");
|
|
29278
29805
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
29279
29806
|
if (m)
|
|
29280
29807
|
return m[1];
|
|
@@ -29425,7 +29952,7 @@ var fetchYouTrackIssueBody = async (issue, creds = youTrackToken, request = youT
|
|
|
29425
29952
|
var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
|
|
29426
29953
|
var readJson = (p) => {
|
|
29427
29954
|
try {
|
|
29428
|
-
const parsed = JSON.parse(
|
|
29955
|
+
const parsed = JSON.parse(fs12.readFileSync(p, "utf8"));
|
|
29429
29956
|
return isConfigObject(parsed) ? parsed : null;
|
|
29430
29957
|
} catch {
|
|
29431
29958
|
return null;
|
|
@@ -29437,7 +29964,7 @@ var resolveInitProvider = (cwd) => {
|
|
|
29437
29964
|
return env.toLowerCase();
|
|
29438
29965
|
const root = cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
29439
29966
|
try {
|
|
29440
|
-
const out =
|
|
29967
|
+
const out = spawnSync8("git", ["remote", "get-url", "origin"], {
|
|
29441
29968
|
cwd: root,
|
|
29442
29969
|
encoding: "utf8"
|
|
29443
29970
|
});
|
|
@@ -29455,7 +29982,7 @@ var youtrackJsonContent = (dir) => {
|
|
|
29455
29982
|
const meetingIssue = process.env.WORKFLOW_YT_MEETING_ISSUE;
|
|
29456
29983
|
return {
|
|
29457
29984
|
baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://youtrack.example.com",
|
|
29458
|
-
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ??
|
|
29985
|
+
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path21.join(dir, "youtrack.token"),
|
|
29459
29986
|
...meetingIssue ? {
|
|
29460
29987
|
meetingIssue,
|
|
29461
29988
|
meetingIssues: { general: { issue: meetingIssue, label: "General meetings" } }
|
|
@@ -29485,19 +30012,19 @@ var vcsJsonContent = () => {
|
|
|
29485
30012
|
};
|
|
29486
30013
|
function initApplyData(action, env = process.env) {
|
|
29487
30014
|
const dir = String(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
29488
|
-
|
|
30015
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
29489
30016
|
switch (action) {
|
|
29490
30017
|
case "youtrack_json": {
|
|
29491
|
-
const out =
|
|
29492
|
-
|
|
30018
|
+
const out = path21.join(dir, "youtrack.json");
|
|
30019
|
+
fs12.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
29493
30020
|
`, "utf8");
|
|
29494
30021
|
return { action, ok: true, path: out };
|
|
29495
30022
|
}
|
|
29496
30023
|
case "youtrack_token_placeholder": {
|
|
29497
|
-
const p =
|
|
30024
|
+
const p = path21.join(dir, "youtrack.token");
|
|
29498
30025
|
const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER2 + `
|
|
29499
30026
|
`, 384) === "preserved";
|
|
29500
|
-
const abs =
|
|
30027
|
+
const abs = path21.resolve(p);
|
|
29501
30028
|
return {
|
|
29502
30029
|
action,
|
|
29503
30030
|
ok: true,
|
|
@@ -29509,14 +30036,14 @@ function initApplyData(action, env = process.env) {
|
|
|
29509
30036
|
};
|
|
29510
30037
|
}
|
|
29511
30038
|
case "youtrack_scaffold": {
|
|
29512
|
-
const jsonOut =
|
|
29513
|
-
const tokenOut =
|
|
29514
|
-
|
|
30039
|
+
const jsonOut = path21.join(dir, "youtrack.json");
|
|
30040
|
+
const tokenOut = path21.join(dir, "youtrack.token");
|
|
30041
|
+
fs12.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
29515
30042
|
`, "utf8");
|
|
29516
30043
|
const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER2 + `
|
|
29517
30044
|
`, 384) === "preserved";
|
|
29518
|
-
const configPath =
|
|
29519
|
-
const tokenPath =
|
|
30045
|
+
const configPath = path21.resolve(jsonOut);
|
|
30046
|
+
const tokenPath = path21.resolve(tokenOut);
|
|
29520
30047
|
const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
|
|
29521
30048
|
process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
|
|
29522
30049
|
let tokenCreate = {};
|
|
@@ -29568,10 +30095,10 @@ function initApplyData(action, env = process.env) {
|
|
|
29568
30095
|
}
|
|
29569
30096
|
}
|
|
29570
30097
|
case "vcs_scaffold": {
|
|
29571
|
-
const jsonOut =
|
|
29572
|
-
|
|
30098
|
+
const jsonOut = path21.join(dir, "vcs.json");
|
|
30099
|
+
fs12.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
|
|
29573
30100
|
`, "utf8");
|
|
29574
|
-
const configPath =
|
|
30101
|
+
const configPath = path21.resolve(jsonOut);
|
|
29575
30102
|
const prev = process.env.WORKFLOW_VCS_CONFIG;
|
|
29576
30103
|
process.env.WORKFLOW_VCS_CONFIG = configPath;
|
|
29577
30104
|
try {
|
|
@@ -29690,7 +30217,7 @@ var executeInitApply = (args, directory, runtime) => {
|
|
|
29690
30217
|
protectedNames: args.branch_policy_protected
|
|
29691
30218
|
}, current);
|
|
29692
30219
|
writeConfig(next);
|
|
29693
|
-
return output(ok({ action: "config", path:
|
|
30220
|
+
return output(ok({ action: "config", path: path22.join(configDir(), "config.json"), ...next }));
|
|
29694
30221
|
}
|
|
29695
30222
|
const env = Object.fromEntries(Object.entries({
|
|
29696
30223
|
WORKFLOW_YT_BASE_URL: args.base_url,
|
|
@@ -29743,12 +30270,12 @@ function createRepoTools(runtime = initApplyRuntime) {
|
|
|
29743
30270
|
|
|
29744
30271
|
// packages/workit-opencode/src/tools/context.ts
|
|
29745
30272
|
import { realpathSync as realpathSync10 } from "node:fs";
|
|
29746
|
-
import
|
|
30273
|
+
import path25 from "node:path";
|
|
29747
30274
|
|
|
29748
30275
|
// packages/workit-core/src/core/pr-create.ts
|
|
29749
|
-
import
|
|
29750
|
-
import
|
|
29751
|
-
import { spawnSync as
|
|
30276
|
+
import fs13 from "node:fs";
|
|
30277
|
+
import path23 from "node:path";
|
|
30278
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
29752
30279
|
// packages/workit-core/src/forge/pr-body.ts
|
|
29753
30280
|
function parseGhIssue(value) {
|
|
29754
30281
|
const m = /issues\/(\d+)/.exec(value);
|
|
@@ -29758,13 +30285,13 @@ function parseGhIssue(value) {
|
|
|
29758
30285
|
// packages/workit-core/src/core/pr-create.ts
|
|
29759
30286
|
function whichOnPath(tool) {
|
|
29760
30287
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
29761
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
30288
|
+
for (const dir of (process.env.PATH ?? "").split(path23.delimiter)) {
|
|
29762
30289
|
if (!dir)
|
|
29763
30290
|
continue;
|
|
29764
30291
|
for (const name of names) {
|
|
29765
|
-
const candidate =
|
|
30292
|
+
const candidate = path23.join(dir, name);
|
|
29766
30293
|
try {
|
|
29767
|
-
|
|
30294
|
+
fs13.accessSync(candidate, fs13.constants.X_OK);
|
|
29768
30295
|
return candidate;
|
|
29769
30296
|
} catch {}
|
|
29770
30297
|
}
|
|
@@ -29773,7 +30300,7 @@ function whichOnPath(tool) {
|
|
|
29773
30300
|
}
|
|
29774
30301
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
29775
30302
|
var pushRemote = (cwd) => {
|
|
29776
|
-
const result =
|
|
30303
|
+
const result = spawnSync9("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
29777
30304
|
cwd,
|
|
29778
30305
|
encoding: "utf8"
|
|
29779
30306
|
});
|
|
@@ -29784,9 +30311,9 @@ var pushRemote = (cwd) => {
|
|
|
29784
30311
|
};
|
|
29785
30312
|
|
|
29786
30313
|
// packages/workit-core/src/core/repo-context.ts
|
|
29787
|
-
import { spawnSync as
|
|
30314
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
29788
30315
|
import { existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync7 } from "node:fs";
|
|
29789
|
-
import
|
|
30316
|
+
import path24 from "node:path";
|
|
29790
30317
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
29791
30318
|
try {
|
|
29792
30319
|
gitRevisionParts(value);
|
|
@@ -29813,7 +30340,7 @@ var invalidRange = (cwd) => ({
|
|
|
29813
30340
|
cwd
|
|
29814
30341
|
});
|
|
29815
30342
|
var runGit = (cwd, args) => {
|
|
29816
|
-
const result =
|
|
30343
|
+
const result = spawnSync10("git", args, { cwd, encoding: "utf8" });
|
|
29817
30344
|
return {
|
|
29818
30345
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
29819
30346
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -29920,7 +30447,7 @@ var findPrTemplate = (cwd) => {
|
|
|
29920
30447
|
]) {
|
|
29921
30448
|
const parts = rel.split("/");
|
|
29922
30449
|
const base = parts[parts.length - 1];
|
|
29923
|
-
const dir = parts.length > 1 ?
|
|
30450
|
+
const dir = parts.length > 1 ? path24.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
29924
30451
|
const actual = probeCaseInsensitive(dir, base);
|
|
29925
30452
|
if (actual)
|
|
29926
30453
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -29951,7 +30478,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
29951
30478
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
29952
30479
|
};
|
|
29953
30480
|
var packageScripts = (cwd, keys) => {
|
|
29954
|
-
const pkgPath =
|
|
30481
|
+
const pkgPath = path24.join(cwd, "package.json");
|
|
29955
30482
|
if (!existsSync11(pkgPath))
|
|
29956
30483
|
return [];
|
|
29957
30484
|
try {
|
|
@@ -29977,9 +30504,9 @@ var documentationFiles = (cwd) => {
|
|
|
29977
30504
|
if (entry.isDirectory()) {
|
|
29978
30505
|
if (excluded.has(entry.name))
|
|
29979
30506
|
continue;
|
|
29980
|
-
walk(
|
|
30507
|
+
walk(path24.join(dir, entry.name), depth + 1);
|
|
29981
30508
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
29982
|
-
matches.push("./" +
|
|
30509
|
+
matches.push("./" + path24.relative(cwd, path24.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
29983
30510
|
}
|
|
29984
30511
|
}
|
|
29985
30512
|
};
|
|
@@ -29995,7 +30522,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
29995
30522
|
`);
|
|
29996
30523
|
};
|
|
29997
30524
|
function prReadyContext(root, range) {
|
|
29998
|
-
const cwd =
|
|
30525
|
+
const cwd = path24.resolve(repoRoot3(root));
|
|
29999
30526
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
30000
30527
|
return invalidRange(cwd);
|
|
30001
30528
|
let stdout = "";
|
|
@@ -30053,7 +30580,7 @@ ${status.stderr}` : "");
|
|
|
30053
30580
|
stdout += `template_path: ${template}
|
|
30054
30581
|
|
|
30055
30582
|
`;
|
|
30056
|
-
stdout += readTrimmed2(
|
|
30583
|
+
stdout += readTrimmed2(path24.join(cwd, template), 220);
|
|
30057
30584
|
} else {
|
|
30058
30585
|
stdout += `template_path: none
|
|
30059
30586
|
|
|
@@ -30063,7 +30590,7 @@ ${status.stderr}` : "");
|
|
|
30063
30590
|
stdout += `
|
|
30064
30591
|
`;
|
|
30065
30592
|
stdout += printSection("Recent Validation Signals");
|
|
30066
|
-
if (existsSync11(
|
|
30593
|
+
if (existsSync11(path24.join(cwd, "package.json"))) {
|
|
30067
30594
|
stdout += `package.json detected. Common scripts:
|
|
30068
30595
|
`;
|
|
30069
30596
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -30072,7 +30599,7 @@ ${status.stderr}` : "");
|
|
|
30072
30599
|
`) + `
|
|
30073
30600
|
`;
|
|
30074
30601
|
}
|
|
30075
|
-
if (existsSync11(
|
|
30602
|
+
if (existsSync11(path24.join(cwd, "Cargo.toml")) || existsSync11(path24.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
30076
30603
|
stdout += `Rust project detected.
|
|
30077
30604
|
`;
|
|
30078
30605
|
}
|
|
@@ -30105,7 +30632,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
30105
30632
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
30106
30633
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
30107
30634
|
function changelogContext(root, range) {
|
|
30108
|
-
const cwd =
|
|
30635
|
+
const cwd = path24.resolve(repoRoot3(root));
|
|
30109
30636
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
30110
30637
|
return invalidRange(cwd);
|
|
30111
30638
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -30123,7 +30650,7 @@ function changelogContext(root, range) {
|
|
|
30123
30650
|
stdout += CHANGELOG_RULES + `
|
|
30124
30651
|
`;
|
|
30125
30652
|
stdout += printSection("Existing CHANGELOG.md");
|
|
30126
|
-
const changelogPath =
|
|
30653
|
+
const changelogPath = path24.join(cwd, "CHANGELOG.md");
|
|
30127
30654
|
if (existsSync11(changelogPath)) {
|
|
30128
30655
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
30129
30656
|
`;
|
|
@@ -30143,7 +30670,7 @@ function changelogContext(root, range) {
|
|
|
30143
30670
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
30144
30671
|
}
|
|
30145
30672
|
function docsRefreshContext(root, range) {
|
|
30146
|
-
const cwd =
|
|
30673
|
+
const cwd = path24.resolve(repoRoot3(root));
|
|
30147
30674
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
30148
30675
|
return invalidRange(cwd);
|
|
30149
30676
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -30165,7 +30692,7 @@ function docsRefreshContext(root, range) {
|
|
|
30165
30692
|
`) + `
|
|
30166
30693
|
`;
|
|
30167
30694
|
stdout += printSection("README Preview");
|
|
30168
|
-
const readme =
|
|
30695
|
+
const readme = path24.join(cwd, "README.md");
|
|
30169
30696
|
if (existsSync11(readme)) {
|
|
30170
30697
|
stdout += readTrimmed2(readme, 220) + `
|
|
30171
30698
|
`;
|
|
@@ -30174,7 +30701,7 @@ function docsRefreshContext(root, range) {
|
|
|
30174
30701
|
`;
|
|
30175
30702
|
}
|
|
30176
30703
|
stdout += printSection("Package Scripts");
|
|
30177
|
-
const pkgPath =
|
|
30704
|
+
const pkgPath = path24.join(cwd, "package.json");
|
|
30178
30705
|
if (existsSync11(pkgPath)) {
|
|
30179
30706
|
try {
|
|
30180
30707
|
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
@@ -30188,7 +30715,7 @@ function docsRefreshContext(root, range) {
|
|
|
30188
30715
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
30189
30716
|
}
|
|
30190
30717
|
function releaseNotesContext(root, rangeOrTag) {
|
|
30191
|
-
const cwd =
|
|
30718
|
+
const cwd = path24.resolve(repoRoot3(root));
|
|
30192
30719
|
if (!rangeOrTag) {
|
|
30193
30720
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
30194
30721
|
`, exitCode: 1, cwd };
|
|
@@ -30226,7 +30753,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
30226
30753
|
`;
|
|
30227
30754
|
stdout += printSection("Existing Release Files");
|
|
30228
30755
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
30229
|
-
if (existsSync11(
|
|
30756
|
+
if (existsSync11(path24.join(cwd, rel)))
|
|
30230
30757
|
stdout += rel + `
|
|
30231
30758
|
`;
|
|
30232
30759
|
}
|
|
@@ -30254,7 +30781,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
30254
30781
|
}
|
|
30255
30782
|
|
|
30256
30783
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
30257
|
-
import { spawnSync as
|
|
30784
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
30258
30785
|
var repoPathFromRemote = (remote) => {
|
|
30259
30786
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
30260
30787
|
if (!rest)
|
|
@@ -30275,7 +30802,7 @@ var defaultCli = (bin, available) => {
|
|
|
30275
30802
|
if (!available)
|
|
30276
30803
|
return null;
|
|
30277
30804
|
return (args, root) => {
|
|
30278
|
-
const out =
|
|
30805
|
+
const out = spawnSync11(bin, args, {
|
|
30279
30806
|
cwd: root,
|
|
30280
30807
|
encoding: "utf8",
|
|
30281
30808
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -30358,10 +30885,6 @@ var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\
|
|
|
30358
30885
|
var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
|
|
30359
30886
|
var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
|
|
30360
30887
|
|
|
30361
|
-
// packages/workit-core/src/git/rev.ts
|
|
30362
|
-
var MAX_BUFFER = 256 * 1024 * 1024;
|
|
30363
|
-
var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
|
|
30364
|
-
|
|
30365
30888
|
// packages/workit-core/src/ledger.ts
|
|
30366
30889
|
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
30367
30890
|
var FAILING = new Set(["failed", "blocked"]);
|
|
@@ -30473,7 +30996,7 @@ var createContextTool = () => tool({
|
|
|
30473
30996
|
try {
|
|
30474
30997
|
const payload = parsed.data.payload;
|
|
30475
30998
|
const cwd = payload.cwd;
|
|
30476
|
-
root = realpathSync10(cwd ?
|
|
30999
|
+
root = realpathSync10(cwd ? path25.resolve(context.directory, cwd) : context.directory);
|
|
30477
31000
|
} catch {
|
|
30478
31001
|
return JSON.stringify(failure2("invalid_input", "action target directory cannot be resolved", {
|
|
30479
31002
|
outcome: "not_started"
|
|
@@ -30485,14 +31008,14 @@ var createContextTool = () => tool({
|
|
|
30485
31008
|
|
|
30486
31009
|
// packages/workit-opencode/src/shared/session.ts
|
|
30487
31010
|
import { realpathSync as realpathSync11 } from "node:fs";
|
|
30488
|
-
import
|
|
31011
|
+
import path26 from "node:path";
|
|
30489
31012
|
var sameWorkspace = (expected, observed) => {
|
|
30490
31013
|
if (typeof observed !== "string" || !observed)
|
|
30491
31014
|
return false;
|
|
30492
31015
|
try {
|
|
30493
31016
|
return realpathSync11(expected) === realpathSync11(observed);
|
|
30494
31017
|
} catch {
|
|
30495
|
-
return
|
|
31018
|
+
return path26.resolve(expected) === path26.resolve(observed);
|
|
30496
31019
|
}
|
|
30497
31020
|
};
|
|
30498
31021
|
|
|
@@ -30994,7 +31517,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
30994
31517
|
};
|
|
30995
31518
|
});
|
|
30996
31519
|
// packages/workit-core/src/hooks/context.ts
|
|
30997
|
-
import
|
|
31520
|
+
import path27 from "node:path";
|
|
30998
31521
|
var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
|
|
30999
31522
|
var newestFirst = (left, right) => right.updatedAt.localeCompare(left.updatedAt);
|
|
31000
31523
|
var sessionTaskEntry = (entries, session) => entries.filter((entry) => entry.status !== "closed" && boundTo(entry, session)).toSorted(newestFirst)[0] ?? null;
|
|
@@ -31019,6 +31542,19 @@ var filesKey = (files) => {
|
|
|
31019
31542
|
return signatures.some((item) => item !== "-" && racySignature(item)) ? null : signatures.join("\x00");
|
|
31020
31543
|
};
|
|
31021
31544
|
var CACHE_LIMIT = 64;
|
|
31545
|
+
var SIGNAL_TTL_MS = 1500;
|
|
31546
|
+
var signals = new Map;
|
|
31547
|
+
var recentSignal = (root) => {
|
|
31548
|
+
const now = Date.now();
|
|
31549
|
+
const cached = signals.get(root);
|
|
31550
|
+
if (cached && now - cached.at < SIGNAL_TTL_MS)
|
|
31551
|
+
return cached.value;
|
|
31552
|
+
const value = worktreeSignal(root);
|
|
31553
|
+
signals.set(root, { at: now, value });
|
|
31554
|
+
if (signals.size > CACHE_LIMIT)
|
|
31555
|
+
signals.delete(signals.keys().next().value);
|
|
31556
|
+
return value;
|
|
31557
|
+
};
|
|
31022
31558
|
var cache = new Map;
|
|
31023
31559
|
function sessionCompactContext(store, session, context, selection = "session-bound") {
|
|
31024
31560
|
const listed = store.listTaskIndex();
|
|
@@ -31042,18 +31578,26 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
31042
31578
|
capabilities: context.capabilities
|
|
31043
31579
|
});
|
|
31044
31580
|
const hit = cache.get(slot);
|
|
31045
|
-
if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files))
|
|
31581
|
+
if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files) && (hit.signal === null || hit.signal === recentSignal(store.root)))
|
|
31046
31582
|
return hit.value;
|
|
31047
31583
|
const task = store.readTask(entry.id);
|
|
31048
31584
|
if (!task.ok)
|
|
31049
31585
|
return null;
|
|
31050
|
-
const
|
|
31051
|
-
const observed =
|
|
31052
|
-
const
|
|
31586
|
+
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
31587
|
+
const observed = task.data.evidence.some(isObservedCheck);
|
|
31588
|
+
const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path27.resolve(store.root, ref.path)] : []));
|
|
31589
|
+
const observedFiles = filesKey(files);
|
|
31590
|
+
const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
|
|
31053
31591
|
if (!compact.ok)
|
|
31054
31592
|
return null;
|
|
31055
31593
|
cache.delete(slot);
|
|
31056
|
-
cache.set(slot, {
|
|
31594
|
+
cache.set(slot, {
|
|
31595
|
+
key,
|
|
31596
|
+
files,
|
|
31597
|
+
filesKey: observedFiles,
|
|
31598
|
+
signal: observed ? freshness.current() : null,
|
|
31599
|
+
value: compact.data
|
|
31600
|
+
});
|
|
31057
31601
|
if (cache.size > CACHE_LIMIT)
|
|
31058
31602
|
cache.delete(cache.keys().next().value);
|
|
31059
31603
|
return compact.data;
|
|
@@ -31225,7 +31769,7 @@ var loadProvenance = (logger, pkgUrl) => {
|
|
|
31225
31769
|
|
|
31226
31770
|
// packages/workit-opencode/src/v1/server.ts
|
|
31227
31771
|
var root = assetsRoot();
|
|
31228
|
-
var skillsPath =
|
|
31772
|
+
var skillsPath = path28.join(root, "skills");
|
|
31229
31773
|
var logger = createLogger({
|
|
31230
31774
|
appLog: () => {
|
|
31231
31775
|
return;
|
|
@@ -31244,7 +31788,7 @@ var warnStaleSources = () => {
|
|
|
31244
31788
|
logger.warn(EVENT.hooks, {
|
|
31245
31789
|
boundary: "stale_sources",
|
|
31246
31790
|
reason: "workit sources changed after plugin load; restart the session for latest behavior",
|
|
31247
|
-
files: changed.map((file) =>
|
|
31791
|
+
files: changed.map((file) => path28.basename(file))
|
|
31248
31792
|
});
|
|
31249
31793
|
} catch {}
|
|
31250
31794
|
};
|
|
@@ -31274,7 +31818,7 @@ var plugin = async ({ client, directory }) => {
|
|
|
31274
31818
|
const dispatches = new Map;
|
|
31275
31819
|
try {
|
|
31276
31820
|
logger.info(EVENT.initialization, { host: "opencode", plugin_root: root });
|
|
31277
|
-
logger.info(EVENT.provenance, loadProvenance(logger,
|
|
31821
|
+
logger.info(EVENT.provenance, loadProvenance(logger, path28.join(packageRoot2(), "package.json")));
|
|
31278
31822
|
} catch (error) {
|
|
31279
31823
|
logger.warn(EVENT.hooks, { boundary: "initialization", ...errorDetail(error) });
|
|
31280
31824
|
}
|
|
@@ -32208,7 +32752,7 @@ var normalizeQuestionAnswers = (answers) => {
|
|
|
32208
32752
|
|
|
32209
32753
|
// packages/workit-opencode/src/v2/registry.ts
|
|
32210
32754
|
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "node:fs";
|
|
32211
|
-
import
|
|
32755
|
+
import path29 from "node:path";
|
|
32212
32756
|
var parseFrontmatter = (markdown) => {
|
|
32213
32757
|
const match = markdown.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
32214
32758
|
if (!match)
|
|
@@ -32228,7 +32772,7 @@ var parseFrontmatter = (markdown) => {
|
|
|
32228
32772
|
};
|
|
32229
32773
|
};
|
|
32230
32774
|
var registerSkills = async (ctx) => {
|
|
32231
|
-
const skillsDir =
|
|
32775
|
+
const skillsDir = path29.join(assetsRoot(), "skills");
|
|
32232
32776
|
if (!existsSync13(skillsDir))
|
|
32233
32777
|
return new Set;
|
|
32234
32778
|
const registered = new Set;
|
|
@@ -32237,7 +32781,7 @@ var registerSkills = async (ctx) => {
|
|
|
32237
32781
|
for (const id of WORKIT_METHOD_SKILLS) {
|
|
32238
32782
|
if (existing.has(id))
|
|
32239
32783
|
continue;
|
|
32240
|
-
const file =
|
|
32784
|
+
const file = path29.join(skillsDir, id, "SKILL.md");
|
|
32241
32785
|
if (!existsSync13(file))
|
|
32242
32786
|
continue;
|
|
32243
32787
|
const parsed = parseFrontmatter(readFileSync12(file, "utf8"));
|