@brainervirus/workit-pi 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/worker.js +38 -3
- package/dist/workit.js +780 -265
- package/package.json +1 -1
- package/skills/workit-bdd/SKILL.md +42 -0
- package/skills/workit-behavioral-tdd/SKILL.md +17 -9
- package/skills/workit-debug/SKILL.md +2 -1
- package/skills/workit-test-audit/SKILL.md +43 -0
package/dist/workit.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3
3
|
|
|
4
4
|
// packages/workit-core/src/core.ts
|
|
5
|
-
import { spawnSync as
|
|
5
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
6
6
|
import { existsSync as existsSync6, realpathSync as realpathSync8 } from "node:fs";
|
|
7
|
-
import
|
|
7
|
+
import path15 from "node:path";
|
|
8
8
|
|
|
9
9
|
// packages/workit-core/src/core/task-contract.ts
|
|
10
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -8121,7 +8121,37 @@ var candidateSchema = object({
|
|
|
8121
8121
|
path: ["id"]
|
|
8122
8122
|
});
|
|
8123
8123
|
});
|
|
8124
|
-
var
|
|
8124
|
+
var checkObservationSchema = object({
|
|
8125
|
+
observer: literal("workit_cli"),
|
|
8126
|
+
name: nonEmpty.nullable(),
|
|
8127
|
+
configured: boolean2(),
|
|
8128
|
+
argv: array(text).min(1),
|
|
8129
|
+
shell: boolean2(),
|
|
8130
|
+
cwd: nonEmpty,
|
|
8131
|
+
exitCode: safeInteger,
|
|
8132
|
+
durationMs: safeInteger.min(0),
|
|
8133
|
+
timedOut: boolean2(),
|
|
8134
|
+
head: text.nullable(),
|
|
8135
|
+
tree: text.nullable(),
|
|
8136
|
+
dirty: boolean2().nullable(),
|
|
8137
|
+
signal: text.nullable(),
|
|
8138
|
+
treeAfter: text.nullable(),
|
|
8139
|
+
modifiedWorktree: boolean2(),
|
|
8140
|
+
base: text.nullable(),
|
|
8141
|
+
patchId: text.nullable(),
|
|
8142
|
+
environment: object({
|
|
8143
|
+
platform: nonEmpty,
|
|
8144
|
+
arch: nonEmpty,
|
|
8145
|
+
vars: record(nonEmpty, text.nullable())
|
|
8146
|
+
}).strict(),
|
|
8147
|
+
logDigest: text.nullable(),
|
|
8148
|
+
logRef: text.nullable(),
|
|
8149
|
+
logTail: array(text).max(40),
|
|
8150
|
+
ledgerRowId: text.nullable(),
|
|
8151
|
+
attestation: object({ host: nonEmpty, session: text.nullable(), agentId: text.nullable() }).strict().nullable()
|
|
8152
|
+
}).strict();
|
|
8153
|
+
var CHECK_OBSERVATION_PATH = "evidence.*.data.observation";
|
|
8154
|
+
var evidenceFields = {
|
|
8125
8155
|
kind: _enum(["check", "review", "investigation", "artifact"]),
|
|
8126
8156
|
claim: text,
|
|
8127
8157
|
requirementIds: array(digest),
|
|
@@ -8132,7 +8162,9 @@ var evidenceSchema = object({
|
|
|
8132
8162
|
refs: array(refSchema),
|
|
8133
8163
|
exitCode: safeInteger.nullable(),
|
|
8134
8164
|
reviewContext: refSchema.nullable()
|
|
8135
|
-
}
|
|
8165
|
+
};
|
|
8166
|
+
var evidenceSchema = object({ ...evidenceFields, observation: checkObservationSchema.optional() }).strict();
|
|
8167
|
+
var reportedEvidenceSchema = object(evidenceFields).strict();
|
|
8136
8168
|
var evidenceEvaluationSchema = object({
|
|
8137
8169
|
evidenceId: id,
|
|
8138
8170
|
status: _enum(["passed", "failed", "missing", "skipped", "stale"]),
|
|
@@ -8464,7 +8496,7 @@ var evidenceOperations = {
|
|
|
8464
8496
|
action: literal("record"),
|
|
8465
8497
|
...taskId,
|
|
8466
8498
|
expectedRevision: revision.optional(),
|
|
8467
|
-
evidence:
|
|
8499
|
+
evidence: reportedEvidenceSchema
|
|
8468
8500
|
})
|
|
8469
8501
|
};
|
|
8470
8502
|
var findingOperations = {
|
|
@@ -10964,10 +10996,10 @@ function assertProductWriteAllowed(input) {
|
|
|
10964
10996
|
}
|
|
10965
10997
|
|
|
10966
10998
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
10967
|
-
import { createHash as
|
|
10968
|
-
import * as
|
|
10969
|
-
import { spawnSync as
|
|
10970
|
-
import
|
|
10999
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
11000
|
+
import * as fs10 from "node:fs";
|
|
11001
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
11002
|
+
import path13 from "node:path";
|
|
10971
11003
|
|
|
10972
11004
|
// packages/workit-core/src/core/authority.ts
|
|
10973
11005
|
import { createHash as createHash6 } from "node:crypto";
|
|
@@ -13174,16 +13206,407 @@ function reconcileAction(input) {
|
|
|
13174
13206
|
var bindingCovers = scopeCovers;
|
|
13175
13207
|
var verifyDecisionContent = verifyContentRefs;
|
|
13176
13208
|
|
|
13209
|
+
// packages/workit-core/src/check-config.ts
|
|
13210
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
13211
|
+
import fs8 from "node:fs";
|
|
13212
|
+
import path11 from "node:path";
|
|
13213
|
+
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
13214
|
+
var CHECKS_FILE = "workit.checks.json";
|
|
13215
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13216
|
+
var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
|
|
13217
|
+
function splitCommand(command) {
|
|
13218
|
+
const argv = [];
|
|
13219
|
+
let current = "";
|
|
13220
|
+
let quote = null;
|
|
13221
|
+
let started = false;
|
|
13222
|
+
for (const char of command) {
|
|
13223
|
+
if (quote) {
|
|
13224
|
+
if (char === quote)
|
|
13225
|
+
quote = null;
|
|
13226
|
+
else
|
|
13227
|
+
current += char;
|
|
13228
|
+
continue;
|
|
13229
|
+
}
|
|
13230
|
+
if (char === '"' || char === "'") {
|
|
13231
|
+
quote = char;
|
|
13232
|
+
started = true;
|
|
13233
|
+
continue;
|
|
13234
|
+
}
|
|
13235
|
+
if (/\s/u.test(char)) {
|
|
13236
|
+
if (started)
|
|
13237
|
+
argv.push(current);
|
|
13238
|
+
current = "";
|
|
13239
|
+
started = false;
|
|
13240
|
+
continue;
|
|
13241
|
+
}
|
|
13242
|
+
if (SHELL_META.test(char))
|
|
13243
|
+
return new Error(`"${command}" uses shell syntax (${char}); configure an argv array instead`);
|
|
13244
|
+
current += char;
|
|
13245
|
+
started = true;
|
|
13246
|
+
}
|
|
13247
|
+
if (quote)
|
|
13248
|
+
return new Error(`"${command}" has an unterminated quote`);
|
|
13249
|
+
if (started)
|
|
13250
|
+
argv.push(current);
|
|
13251
|
+
return argv.length ? argv : new Error("empty command");
|
|
13252
|
+
}
|
|
13253
|
+
var git = (cwd, args) => {
|
|
13254
|
+
const run = spawnSync3("git", args, {
|
|
13255
|
+
cwd,
|
|
13256
|
+
encoding: "utf8",
|
|
13257
|
+
timeout: 20000,
|
|
13258
|
+
killSignal: "SIGKILL",
|
|
13259
|
+
windowsHide: true
|
|
13260
|
+
});
|
|
13261
|
+
const value = run.status === 0 ? (run.stdout ?? "").trim() : "";
|
|
13262
|
+
return value || null;
|
|
13263
|
+
};
|
|
13264
|
+
function checkRoot(cwd) {
|
|
13265
|
+
return path11.resolve(git(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
13266
|
+
}
|
|
13267
|
+
var packageManager = (root) => {
|
|
13268
|
+
const has = (file) => fs8.existsSync(path11.join(root, file));
|
|
13269
|
+
if (has("bun.lock") || has("bun.lockb"))
|
|
13270
|
+
return "bun";
|
|
13271
|
+
if (has("pnpm-lock.yaml"))
|
|
13272
|
+
return "pnpm";
|
|
13273
|
+
if (has("yarn.lock"))
|
|
13274
|
+
return "yarn";
|
|
13275
|
+
return "npm";
|
|
13276
|
+
};
|
|
13277
|
+
var scriptCheck = (pm, name) => {
|
|
13278
|
+
const argv = [pm, "run", name];
|
|
13279
|
+
const accepts = [argv];
|
|
13280
|
+
if (pm === "pnpm" || pm === "yarn" || pm === "npm" && name === "test")
|
|
13281
|
+
accepts.push([pm, name]);
|
|
13282
|
+
return { name, argv, accepts };
|
|
13283
|
+
};
|
|
13284
|
+
var CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
|
|
13285
|
+
function detectedChecks(root) {
|
|
13286
|
+
const read = (file) => {
|
|
13287
|
+
try {
|
|
13288
|
+
return fs8.readFileSync(path11.join(root, file), "utf8");
|
|
13289
|
+
} catch {
|
|
13290
|
+
return null;
|
|
13291
|
+
}
|
|
13292
|
+
};
|
|
13293
|
+
const checks = [];
|
|
13294
|
+
const add = (check) => {
|
|
13295
|
+
if (!checks.some((item) => item.name === check.name))
|
|
13296
|
+
checks.push(check);
|
|
13297
|
+
};
|
|
13298
|
+
const manifestText = read("package.json");
|
|
13299
|
+
if (manifestText !== null) {
|
|
13300
|
+
let manifest = null;
|
|
13301
|
+
try {
|
|
13302
|
+
manifest = JSON.parse(manifestText);
|
|
13303
|
+
} catch {}
|
|
13304
|
+
const scripts = isRecord2(manifest) && isRecord2(manifest.scripts) ? manifest.scripts : {};
|
|
13305
|
+
const pm = packageManager(root);
|
|
13306
|
+
for (const name of SCRIPT_CHECKS) {
|
|
13307
|
+
const script = scripts[name];
|
|
13308
|
+
if (typeof script === "string" && script.trim() !== "")
|
|
13309
|
+
add(scriptCheck(pm, name));
|
|
13310
|
+
}
|
|
13311
|
+
}
|
|
13312
|
+
const plain = (name, argv) => ({ name, argv, accepts: [argv] });
|
|
13313
|
+
if (read("go.mod") !== null)
|
|
13314
|
+
add(plain("test", ["go", "test", "./..."]));
|
|
13315
|
+
if (read("Cargo.toml") !== null)
|
|
13316
|
+
add(plain("test", ["cargo", "test"]));
|
|
13317
|
+
if (read("pytest.ini") !== null || /\bpytest\b/u.test(read("pyproject.toml") ?? ""))
|
|
13318
|
+
add(plain("test", ["pytest"]));
|
|
13319
|
+
const makefile = read("Makefile") ?? read("makefile") ?? read("GNUmakefile");
|
|
13320
|
+
if (makefile !== null && /^test\s*:(?!=)/mu.test(makefile))
|
|
13321
|
+
add(plain("test", ["make", "test"]));
|
|
13322
|
+
return checks;
|
|
13323
|
+
}
|
|
13324
|
+
var GATES = ["testing", "verification"];
|
|
13325
|
+
function loadCheckConfig(cwd) {
|
|
13326
|
+
const root = checkRoot(cwd);
|
|
13327
|
+
const file = path11.join(root, CHECKS_FILE);
|
|
13328
|
+
const none = (error = null) => ({
|
|
13329
|
+
source: error ? CHECKS_FILE : "none",
|
|
13330
|
+
root,
|
|
13331
|
+
checks: [],
|
|
13332
|
+
gates: {},
|
|
13333
|
+
error
|
|
13334
|
+
});
|
|
13335
|
+
let text = null;
|
|
13336
|
+
try {
|
|
13337
|
+
text = fs8.readFileSync(file, "utf8");
|
|
13338
|
+
} catch (error) {
|
|
13339
|
+
if (error.code !== "ENOENT")
|
|
13340
|
+
return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
|
|
13341
|
+
}
|
|
13342
|
+
if (text === null) {
|
|
13343
|
+
const checks = detectedChecks(root);
|
|
13344
|
+
return checks.length ? { source: "defaults", root, checks, gates: {}, error: null } : none();
|
|
13345
|
+
}
|
|
13346
|
+
let parsed;
|
|
13347
|
+
try {
|
|
13348
|
+
parsed = JSON.parse(text);
|
|
13349
|
+
} catch {
|
|
13350
|
+
return none(`${CHECKS_FILE} is not valid JSON`);
|
|
13351
|
+
}
|
|
13352
|
+
if (!isRecord2(parsed) || !isRecord2(parsed.checks))
|
|
13353
|
+
return none(`${CHECKS_FILE} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
|
|
13354
|
+
const checks = [];
|
|
13355
|
+
for (const [name, value] of Object.entries(parsed.checks)) {
|
|
13356
|
+
if (!CHECK_NAME.test(name))
|
|
13357
|
+
return none(`${CHECKS_FILE}: check name "${name}" must match ${CHECK_NAME.source}`);
|
|
13358
|
+
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");
|
|
13359
|
+
if (argv instanceof Error)
|
|
13360
|
+
return none(`${CHECKS_FILE}: check "${name}": ${argv.message}`);
|
|
13361
|
+
checks.push({ name, argv, accepts: [argv] });
|
|
13362
|
+
}
|
|
13363
|
+
const gates = {};
|
|
13364
|
+
if (parsed.gates !== undefined) {
|
|
13365
|
+
if (!isRecord2(parsed.gates))
|
|
13366
|
+
return none(`${CHECKS_FILE}: gates must be {"testing":"<name>"|["<name>",…]}`);
|
|
13367
|
+
for (const [gate, value] of Object.entries(parsed.gates)) {
|
|
13368
|
+
if (!GATES.includes(gate))
|
|
13369
|
+
return none(`${CHECKS_FILE}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
|
|
13370
|
+
const names = typeof value === "string" ? [value] : value;
|
|
13371
|
+
if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
|
|
13372
|
+
return none(`${CHECKS_FILE}: gates.${gate} must name configured checks`);
|
|
13373
|
+
gates[gate] = names;
|
|
13374
|
+
}
|
|
13375
|
+
}
|
|
13376
|
+
return { source: CHECKS_FILE, root, checks, gates, error: null };
|
|
13377
|
+
}
|
|
13378
|
+
var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
|
|
13379
|
+
function matchesNamedCheck(config, name, argv, cwd) {
|
|
13380
|
+
if (config.error || name === null || cwd !== ".")
|
|
13381
|
+
return false;
|
|
13382
|
+
const check = config.checks.find((item) => item.name === name);
|
|
13383
|
+
return Boolean(check?.accepts.some((accepted) => sameArgv(accepted, argv)));
|
|
13384
|
+
}
|
|
13385
|
+
function gateCheckNames(config, dimension) {
|
|
13386
|
+
const explicit = config.gates[dimension];
|
|
13387
|
+
if (explicit)
|
|
13388
|
+
return explicit;
|
|
13389
|
+
const names = config.checks.map((check) => check.name);
|
|
13390
|
+
if (dimension === "testing")
|
|
13391
|
+
return names.includes("test") ? ["test"] : [];
|
|
13392
|
+
return names;
|
|
13393
|
+
}
|
|
13394
|
+
var workitOnPath;
|
|
13395
|
+
function workitAvailable(env = process.env) {
|
|
13396
|
+
if (workitOnPath !== undefined)
|
|
13397
|
+
return workitOnPath;
|
|
13398
|
+
const windows = process.platform === "win32";
|
|
13399
|
+
const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
13400
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path11.delimiter).filter(Boolean);
|
|
13401
|
+
workitOnPath = dirs.some((dir) => exts.some((ext) => {
|
|
13402
|
+
try {
|
|
13403
|
+
return fs8.statSync(path11.join(dir, `workit${ext}`)).isFile();
|
|
13404
|
+
} catch {
|
|
13405
|
+
return false;
|
|
13406
|
+
}
|
|
13407
|
+
}));
|
|
13408
|
+
return workitOnPath;
|
|
13409
|
+
}
|
|
13410
|
+
function checkCommand(args, version, onPath = workitAvailable()) {
|
|
13411
|
+
return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
|
|
13412
|
+
}
|
|
13413
|
+
|
|
13414
|
+
// packages/workit-core/src/git/rev.ts
|
|
13415
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
13416
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
13417
|
+
import fs9 from "node:fs";
|
|
13418
|
+
import os2 from "node:os";
|
|
13419
|
+
import path12 from "node:path";
|
|
13420
|
+
var MAX_BUFFER = 256 * 1024 * 1024;
|
|
13421
|
+
var GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
|
|
13422
|
+
var git2 = (cwd, args, options = {}) => {
|
|
13423
|
+
const result = spawnSync4("git", args, {
|
|
13424
|
+
cwd,
|
|
13425
|
+
encoding: "utf8",
|
|
13426
|
+
env: options.env ?? process.env,
|
|
13427
|
+
input: options.input,
|
|
13428
|
+
maxBuffer: MAX_BUFFER,
|
|
13429
|
+
timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
|
|
13430
|
+
killSignal: "SIGKILL",
|
|
13431
|
+
windowsHide: true
|
|
13432
|
+
});
|
|
13433
|
+
const error = result.error;
|
|
13434
|
+
return {
|
|
13435
|
+
ok: result.status === 0,
|
|
13436
|
+
stdout: result.stdout ?? "",
|
|
13437
|
+
stderr: result.stderr ?? "",
|
|
13438
|
+
timedOut: error?.code === "ETIMEDOUT"
|
|
13439
|
+
};
|
|
13440
|
+
};
|
|
13441
|
+
var line = (run) => {
|
|
13442
|
+
if (!run.ok)
|
|
13443
|
+
return null;
|
|
13444
|
+
const value = run.stdout.trim();
|
|
13445
|
+
return value ? value : null;
|
|
13446
|
+
};
|
|
13447
|
+
function networkGitInvocation(cwd, args, timeoutMs) {
|
|
13448
|
+
const seconds = Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
13449
|
+
const env = {
|
|
13450
|
+
...process.env,
|
|
13451
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
13452
|
+
GIT_ASKPASS: "",
|
|
13453
|
+
SSH_ASKPASS: "",
|
|
13454
|
+
SSH_ASKPASS_REQUIRE: "never",
|
|
13455
|
+
GCM_INTERACTIVE: "never"
|
|
13456
|
+
};
|
|
13457
|
+
const base = process.env.GIT_SSH_COMMAND || line(git2(cwd, ["config", "--get", "core.sshCommand"])) || (process.env.GIT_SSH ? null : "ssh");
|
|
13458
|
+
if (base)
|
|
13459
|
+
env.GIT_SSH_COMMAND = `${base} -o BatchMode=yes -o ConnectTimeout=${Math.min(10, seconds)}`;
|
|
13460
|
+
return {
|
|
13461
|
+
args: ["-c", "http.lowSpeedLimit=1", "-c", `http.lowSpeedTime=${seconds}`, ...args],
|
|
13462
|
+
env
|
|
13463
|
+
};
|
|
13464
|
+
}
|
|
13465
|
+
var WATCHDOG = `
|
|
13466
|
+
const cp = require("node:child_process");
|
|
13467
|
+
const { ms, args } = JSON.parse(process.env.WORKIT_GIT_WATCHDOG);
|
|
13468
|
+
delete process.env.WORKIT_GIT_WATCHDOG;
|
|
13469
|
+
const win = process.platform === "win32";
|
|
13470
|
+
const child = cp.spawn("git", args, { stdio: "inherit", detached: !win, windowsHide: true });
|
|
13471
|
+
const killTree = () => {
|
|
13472
|
+
if (!child.pid) return;
|
|
13473
|
+
if (win) cp.spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
|
|
13474
|
+
else try { process.kill(-child.pid, "SIGKILL"); } catch {}
|
|
13475
|
+
};
|
|
13476
|
+
let timedOut = false;
|
|
13477
|
+
const timer = setTimeout(() => { timedOut = true; killTree(); }, ms);
|
|
13478
|
+
child.on("error", () => { clearTimeout(timer); process.exit(127); });
|
|
13479
|
+
child.on("exit", (code) => {
|
|
13480
|
+
clearTimeout(timer);
|
|
13481
|
+
if (!win) killTree();
|
|
13482
|
+
process.exit(timedOut ? 124 : code ?? 1);
|
|
13483
|
+
});
|
|
13484
|
+
`;
|
|
13485
|
+
var WATCHDOG_TIMEOUT_EXIT = 124;
|
|
13486
|
+
var gitNetwork = (cwd, args, timeoutMs) => {
|
|
13487
|
+
const invocation = networkGitInvocation(cwd, args, timeoutMs);
|
|
13488
|
+
const result = spawnSync4(process.execPath, ["-e", WATCHDOG], {
|
|
13489
|
+
cwd,
|
|
13490
|
+
encoding: "utf8",
|
|
13491
|
+
env: {
|
|
13492
|
+
...invocation.env,
|
|
13493
|
+
WORKIT_GIT_WATCHDOG: JSON.stringify({ ms: timeoutMs, args: invocation.args })
|
|
13494
|
+
},
|
|
13495
|
+
maxBuffer: MAX_BUFFER,
|
|
13496
|
+
timeout: timeoutMs + 1e4,
|
|
13497
|
+
killSignal: "SIGKILL",
|
|
13498
|
+
windowsHide: true
|
|
13499
|
+
});
|
|
13500
|
+
const error = result.error;
|
|
13501
|
+
return {
|
|
13502
|
+
ok: result.status === 0,
|
|
13503
|
+
stdout: result.stdout ?? "",
|
|
13504
|
+
stderr: result.stderr ?? "",
|
|
13505
|
+
timedOut: result.status === WATCHDOG_TIMEOUT_EXIT || error?.code === "ETIMEDOUT"
|
|
13506
|
+
};
|
|
13507
|
+
};
|
|
13508
|
+
function headSha(cwd) {
|
|
13509
|
+
return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
|
|
13510
|
+
}
|
|
13511
|
+
var emptyTree = (cwd) => line(git2(cwd, ["mktree"], { input: "" }));
|
|
13512
|
+
var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
|
|
13513
|
+
var seedIndex = (from, to) => {
|
|
13514
|
+
fs9.copyFileSync(from, to);
|
|
13515
|
+
const { atimeNs, mtimeNs } = fs9.statSync(from, { bigint: true });
|
|
13516
|
+
const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
|
|
13517
|
+
fs9.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
|
|
13518
|
+
};
|
|
13519
|
+
function worktreeTree(cwd, options = {}) {
|
|
13520
|
+
const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
|
|
13521
|
+
const indexPath = line(git2(cwd, ["rev-parse", "--path-format=absolute", "--git-path", "index"]));
|
|
13522
|
+
if (!top || !indexPath)
|
|
13523
|
+
return null;
|
|
13524
|
+
const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
|
|
13525
|
+
const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
|
|
13526
|
+
const scratch = fs9.mkdtempSync(path12.join(os2.tmpdir(), "workit-tree-"));
|
|
13527
|
+
try {
|
|
13528
|
+
const tempIndex = path12.join(scratch, "index");
|
|
13529
|
+
if (fs9.existsSync(indexPath))
|
|
13530
|
+
seedIndex(indexPath, tempIndex);
|
|
13531
|
+
const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
|
|
13532
|
+
const untracked = git2(top, ["ls-files", "--others", "--exclude-standard", "-z"], {
|
|
13533
|
+
env,
|
|
13534
|
+
timeoutMs
|
|
13535
|
+
});
|
|
13536
|
+
if (!untracked.ok)
|
|
13537
|
+
return null;
|
|
13538
|
+
const skipped = [];
|
|
13539
|
+
for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
|
|
13540
|
+
try {
|
|
13541
|
+
const stat = fs9.statSync(path12.join(top, file));
|
|
13542
|
+
if (stat.isFile() && stat.size > cap)
|
|
13543
|
+
skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
|
|
13544
|
+
} catch {}
|
|
13545
|
+
}
|
|
13546
|
+
skipped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
13547
|
+
const added = skipped.length ? git2(top, ["add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], {
|
|
13548
|
+
env,
|
|
13549
|
+
timeoutMs,
|
|
13550
|
+
input: [".", ...skipped.map((file) => `:(exclude,literal)${file.path}`)].join("\x00")
|
|
13551
|
+
}) : git2(top, ["add", "-A"], { env, timeoutMs });
|
|
13552
|
+
if (!added.ok)
|
|
13553
|
+
return null;
|
|
13554
|
+
const tree = line(git2(top, ["write-tree"], { env, timeoutMs }));
|
|
13555
|
+
if (!tree)
|
|
13556
|
+
return null;
|
|
13557
|
+
const base = line(git2(top, ["rev-parse", "--verify", "-q", "HEAD^{tree}"])) ?? emptyTree(top);
|
|
13558
|
+
const key = skipped.length ? `sha256:${createHash7("sha256").update(JSON.stringify({ tree, skipped })).digest("hex")}` : tree;
|
|
13559
|
+
return { tree, key, dirty: tree !== base || skipped.length > 0, skipped };
|
|
13560
|
+
} finally {
|
|
13561
|
+
fs9.rmSync(scratch, { recursive: true, force: true });
|
|
13562
|
+
}
|
|
13563
|
+
}
|
|
13564
|
+
function worktreeSignal(cwd, options = {}) {
|
|
13565
|
+
const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
|
|
13566
|
+
if (!top)
|
|
13567
|
+
return null;
|
|
13568
|
+
const status = git2(top, [
|
|
13569
|
+
"status",
|
|
13570
|
+
"--porcelain=v1",
|
|
13571
|
+
"-z",
|
|
13572
|
+
"--untracked-files=all",
|
|
13573
|
+
"--no-renames",
|
|
13574
|
+
"--ignore-submodules=dirty"
|
|
13575
|
+
], {
|
|
13576
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
13577
|
+
timeoutMs: options.timeoutMs ?? GIT_TIMEOUTS.worktree
|
|
13578
|
+
});
|
|
13579
|
+
if (!status.ok)
|
|
13580
|
+
return null;
|
|
13581
|
+
const hash = createHash7("sha256");
|
|
13582
|
+
hash.update(`${headSha(top) ?? "unborn"}\x00`);
|
|
13583
|
+
for (const entry of status.stdout.split("\x00")) {
|
|
13584
|
+
if (entry.length < 4)
|
|
13585
|
+
continue;
|
|
13586
|
+
const file = entry.slice(3);
|
|
13587
|
+
const absolute = path12.join(top, file);
|
|
13588
|
+
let stat = "-";
|
|
13589
|
+
try {
|
|
13590
|
+
const info = fs9.lstatSync(absolute, { bigint: true });
|
|
13591
|
+
stat = `${info.mode.toString(8)}:${info.size}:${info.mtimeNs}`;
|
|
13592
|
+
if (info.isDirectory())
|
|
13593
|
+
stat += `:${headSha(absolute) ?? "-"}`;
|
|
13594
|
+
} catch {}
|
|
13595
|
+
hash.update(`${entry.slice(0, 2)}\x00${file}\x00${stat}\x00`);
|
|
13596
|
+
}
|
|
13597
|
+
return `sig:${hash.digest("hex")}`;
|
|
13598
|
+
}
|
|
13599
|
+
|
|
13177
13600
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
13178
|
-
var digestBytes3 = (value) =>
|
|
13179
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
13180
|
-
var relative = (root, candidate) =>
|
|
13601
|
+
var digestBytes3 = (value) => createHash8("sha256").update(value).digest("hex");
|
|
13602
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path13.sep}`);
|
|
13603
|
+
var relative = (root, candidate) => path13.relative(root, candidate).split(path13.sep).join("/") || ".";
|
|
13181
13604
|
var canonicalPath = (value) => {
|
|
13182
13605
|
if (typeof value !== "string" || !value)
|
|
13183
13606
|
return null;
|
|
13184
13607
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
13185
13608
|
return null;
|
|
13186
|
-
const normalized =
|
|
13609
|
+
const normalized = path13.posix.normalize(value);
|
|
13187
13610
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
13188
13611
|
return null;
|
|
13189
13612
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -13219,7 +13642,7 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
13219
13642
|
return left.length - right.length;
|
|
13220
13643
|
};
|
|
13221
13644
|
var gitPaths = (root) => {
|
|
13222
|
-
const result =
|
|
13645
|
+
const result = spawnSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
13223
13646
|
cwd: root,
|
|
13224
13647
|
encoding: "buffer"
|
|
13225
13648
|
});
|
|
@@ -13231,21 +13654,21 @@ var gitPaths = (root) => {
|
|
|
13231
13654
|
git: !/not a git repository/i.test(stderr)
|
|
13232
13655
|
};
|
|
13233
13656
|
}
|
|
13234
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
13235
|
-
const stagedDeleted =
|
|
13657
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path13.sep).join("/")) : [];
|
|
13658
|
+
const stagedDeleted = spawnSync5("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
13236
13659
|
if (stagedDeleted.status !== 0) {
|
|
13237
13660
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
13238
13661
|
if (!/not a git repository/i.test(stderr))
|
|
13239
13662
|
return { paths, uncertain: true, git: true };
|
|
13240
13663
|
} else if (stagedDeleted.stdout) {
|
|
13241
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
13664
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path13.sep).join("/")));
|
|
13242
13665
|
}
|
|
13243
13666
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
13244
13667
|
};
|
|
13245
13668
|
var walk = (root, directory, output) => {
|
|
13246
13669
|
let names;
|
|
13247
13670
|
try {
|
|
13248
|
-
names =
|
|
13671
|
+
names = fs10.readdirSync(directory);
|
|
13249
13672
|
} catch {
|
|
13250
13673
|
return true;
|
|
13251
13674
|
}
|
|
@@ -13253,10 +13676,10 @@ var walk = (root, directory, output) => {
|
|
|
13253
13676
|
for (const name of names) {
|
|
13254
13677
|
if (name === ".git" || name === ".workit")
|
|
13255
13678
|
continue;
|
|
13256
|
-
const target =
|
|
13679
|
+
const target = path13.join(directory, name);
|
|
13257
13680
|
let stat;
|
|
13258
13681
|
try {
|
|
13259
|
-
stat =
|
|
13682
|
+
stat = fs10.lstatSync(target);
|
|
13260
13683
|
} catch {
|
|
13261
13684
|
uncertain = true;
|
|
13262
13685
|
continue;
|
|
@@ -13271,14 +13694,14 @@ var walk = (root, directory, output) => {
|
|
|
13271
13694
|
var scopeRoots = (root, scope) => {
|
|
13272
13695
|
const roots = [];
|
|
13273
13696
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
13274
|
-
const target =
|
|
13697
|
+
const target = path13.resolve(root, item);
|
|
13275
13698
|
if (!inside(root, target))
|
|
13276
13699
|
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` }] });
|
|
13277
13700
|
let ancestor = target;
|
|
13278
|
-
while (!
|
|
13279
|
-
ancestor =
|
|
13701
|
+
while (!fs10.existsSync(ancestor) && ancestor !== root)
|
|
13702
|
+
ancestor = path13.dirname(ancestor);
|
|
13280
13703
|
try {
|
|
13281
|
-
if (!inside(root,
|
|
13704
|
+
if (!inside(root, fs10.realpathSync(ancestor)))
|
|
13282
13705
|
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` }] });
|
|
13283
13706
|
} catch {
|
|
13284
13707
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -13290,7 +13713,7 @@ var scopeRoots = (root, scope) => {
|
|
|
13290
13713
|
function captureCandidate(root, scope, environment = []) {
|
|
13291
13714
|
let checkout;
|
|
13292
13715
|
try {
|
|
13293
|
-
checkout =
|
|
13716
|
+
checkout = fs10.realpathSync(root);
|
|
13294
13717
|
} catch {
|
|
13295
13718
|
return failure2("invalid_input", "candidate root does not exist");
|
|
13296
13719
|
}
|
|
@@ -13308,11 +13731,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
13308
13731
|
if (item === ".git" || item === ".workit")
|
|
13309
13732
|
continue;
|
|
13310
13733
|
names.add(item);
|
|
13311
|
-
if (!
|
|
13734
|
+
if (!fs10.existsSync(target))
|
|
13312
13735
|
continue;
|
|
13313
13736
|
let stat;
|
|
13314
13737
|
try {
|
|
13315
|
-
stat =
|
|
13738
|
+
stat = fs10.lstatSync(target);
|
|
13316
13739
|
} catch {
|
|
13317
13740
|
uncertain = true;
|
|
13318
13741
|
continue;
|
|
@@ -13328,17 +13751,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
13328
13751
|
for (const item of [...names].toSorted()) {
|
|
13329
13752
|
if (!scopeMatches(item, normalizedScope))
|
|
13330
13753
|
continue;
|
|
13331
|
-
const target =
|
|
13754
|
+
const target = path13.join(checkout, item);
|
|
13332
13755
|
try {
|
|
13333
|
-
const stat =
|
|
13756
|
+
const stat = fs10.lstatSync(target);
|
|
13334
13757
|
if (stat.isSymbolicLink()) {
|
|
13335
|
-
const link =
|
|
13758
|
+
const link = fs10.readlinkSync(target);
|
|
13336
13759
|
files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
|
|
13337
13760
|
} else if (stat.isFile()) {
|
|
13338
13761
|
files.push({
|
|
13339
13762
|
path: item,
|
|
13340
13763
|
kind: "file",
|
|
13341
|
-
digest: digestBytes3(
|
|
13764
|
+
digest: digestBytes3(fs10.readFileSync(target)),
|
|
13342
13765
|
executable: (stat.mode & 73) !== 0
|
|
13343
13766
|
});
|
|
13344
13767
|
} else if (!stat.isDirectory()) {
|
|
@@ -13363,7 +13786,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
13363
13786
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
13364
13787
|
if (values.some(({ value }) => value === null))
|
|
13365
13788
|
completeness = "uncertain";
|
|
13366
|
-
const headResult =
|
|
13789
|
+
const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
13367
13790
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
13368
13791
|
completeness = "uncertain";
|
|
13369
13792
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -13401,7 +13824,24 @@ var sameSession = (left, right) => {
|
|
|
13401
13824
|
return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
|
|
13402
13825
|
};
|
|
13403
13826
|
var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
|
|
13404
|
-
|
|
13827
|
+
var memo2 = (compute) => {
|
|
13828
|
+
let value;
|
|
13829
|
+
return () => {
|
|
13830
|
+
if (value === undefined)
|
|
13831
|
+
value = compute();
|
|
13832
|
+
return value;
|
|
13833
|
+
};
|
|
13834
|
+
};
|
|
13835
|
+
var treeFreshness = (root) => ({
|
|
13836
|
+
mode: "tree",
|
|
13837
|
+
current: memo2(() => worktreeTree(root)?.key ?? null)
|
|
13838
|
+
});
|
|
13839
|
+
var signalFreshness = (root) => ({
|
|
13840
|
+
mode: "signal",
|
|
13841
|
+
current: memo2(() => worktreeSignal(root))
|
|
13842
|
+
});
|
|
13843
|
+
var isObservedCheck = (entry) => entry.data.kind === "check" && entry.data.observation?.observer === "workit_cli" && entry.provenance?.kind === "host_observed" && entry.provenance.host === "workit_cli";
|
|
13844
|
+
function evaluateEvidence(task, candidate, freshness) {
|
|
13405
13845
|
const current = candidate ?? task.candidates.at(-1) ?? null;
|
|
13406
13846
|
return task.evidence.map((entry) => {
|
|
13407
13847
|
const evidence = entry.data;
|
|
@@ -13411,6 +13851,25 @@ function evaluateEvidence(task, candidate) {
|
|
|
13411
13851
|
status: "failed",
|
|
13412
13852
|
reason: "failed evidence remains historical"
|
|
13413
13853
|
};
|
|
13854
|
+
const observation = isObservedCheck(entry) ? evidence.observation : undefined;
|
|
13855
|
+
if (observation && observation.tree !== null) {
|
|
13856
|
+
if (task.status === "closed")
|
|
13857
|
+
return { evidenceId: entry.id, status: evidence.result, reason: "recorded before close" };
|
|
13858
|
+
if (observation.modifiedWorktree)
|
|
13859
|
+
return {
|
|
13860
|
+
evidenceId: entry.id,
|
|
13861
|
+
status: "stale",
|
|
13862
|
+
reason: "the check changed the worktree while it ran; ignore or clean its outputs"
|
|
13863
|
+
};
|
|
13864
|
+
const recorded = freshness?.mode === "signal" ? observation.signal : observation.tree;
|
|
13865
|
+
if (recorded !== null && freshness && recorded === freshness.current())
|
|
13866
|
+
return { evidenceId: entry.id, status: evidence.result, reason: "worktree is unchanged" };
|
|
13867
|
+
return {
|
|
13868
|
+
evidenceId: entry.id,
|
|
13869
|
+
status: "stale",
|
|
13870
|
+
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"
|
|
13871
|
+
};
|
|
13872
|
+
}
|
|
13414
13873
|
if (evidence.kind === "check" || evidence.kind === "review") {
|
|
13415
13874
|
if (!evidence.beforeCandidateId || !evidence.candidateId)
|
|
13416
13875
|
return {
|
|
@@ -13494,8 +13953,45 @@ var expectedKinds = (dimension, ruleId) => {
|
|
|
13494
13953
|
return "decision";
|
|
13495
13954
|
return "worker report";
|
|
13496
13955
|
};
|
|
13497
|
-
|
|
13498
|
-
|
|
13956
|
+
var observedGate = (requirement) => (requirement.dimension === "testing" || requirement.dimension === "verification") && requirement.before === "close";
|
|
13957
|
+
function evaluateObservedGate(task, requirement, statuses, config) {
|
|
13958
|
+
const names = gateCheckNames(config, requirement.dimension);
|
|
13959
|
+
const observed = task.evidence.filter(isObservedCheck);
|
|
13960
|
+
const fresh = observed.filter((entry) => statuses.get(entry.id) === "passed" && entry.data.observation?.exitCode === 0);
|
|
13961
|
+
const named = (entry) => {
|
|
13962
|
+
const observation = entry.data.observation;
|
|
13963
|
+
return observation.configured && !observation.shell && names.includes(observation.name ?? "") && matchesNamedCheck(config, observation.name, observation.argv, observation.cwd);
|
|
13964
|
+
};
|
|
13965
|
+
const considered = observed.map((entry) => entry.id);
|
|
13966
|
+
const run = checkCommand(names[0] ?? "<name>", runtimeVersion());
|
|
13967
|
+
if (config.error)
|
|
13968
|
+
return {
|
|
13969
|
+
satisfied: [],
|
|
13970
|
+
considered,
|
|
13971
|
+
reason: `check config is invalid (${config.error}); fix it, then run ${run}`
|
|
13972
|
+
};
|
|
13973
|
+
if (!names.length)
|
|
13974
|
+
return {
|
|
13975
|
+
satisfied: [],
|
|
13976
|
+
considered,
|
|
13977
|
+
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())}`
|
|
13978
|
+
};
|
|
13979
|
+
const satisfied = fresh.filter(named);
|
|
13980
|
+
if (satisfied.length)
|
|
13981
|
+
return {
|
|
13982
|
+
satisfied: satisfied.map((entry) => entry.id),
|
|
13983
|
+
considered,
|
|
13984
|
+
reason: `a fresh passing configured check (${names.join("|")}) was observed by workit check`
|
|
13985
|
+
};
|
|
13986
|
+
const reported = task.evidence.some((entry) => entry.data.kind === "check" && !isObservedCheck(entry) && entry.data.requirementIds.includes(requirement.id));
|
|
13987
|
+
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}`;
|
|
13988
|
+
return { satisfied: [], considered, reason };
|
|
13989
|
+
}
|
|
13990
|
+
function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller, freshness = treeFreshness(checkoutRoot)) {
|
|
13991
|
+
const evidence = evaluateEvidence(task, candidate, freshness);
|
|
13992
|
+
const statuses = new Map(evidence.map((item) => [item.evidenceId, item.status]));
|
|
13993
|
+
let checkConfig;
|
|
13994
|
+
const configFor = () => checkConfig ??= loadCheckConfig(checkoutRoot);
|
|
13499
13995
|
if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
|
|
13500
13996
|
return task.policy.requirements.map((requirement) => ({
|
|
13501
13997
|
requirementId: requirement.id,
|
|
@@ -13505,8 +14001,19 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
13505
14001
|
reason: "stored policy version is unsupported; reassessment is required"
|
|
13506
14002
|
}));
|
|
13507
14003
|
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
14004
|
+
const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
|
|
14005
|
+
if (gate?.satisfied.length)
|
|
14006
|
+
return {
|
|
14007
|
+
requirementId: requirement.id,
|
|
14008
|
+
status: "satisfied",
|
|
14009
|
+
evidenceIds: gate.satisfied,
|
|
14010
|
+
decisionIds: [],
|
|
14011
|
+
reason: gate.reason
|
|
14012
|
+
};
|
|
13508
14013
|
const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
|
|
13509
14014
|
const passed = related.filter(({ entry, evaluation }) => {
|
|
14015
|
+
if (gate)
|
|
14016
|
+
return false;
|
|
13510
14017
|
if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
|
|
13511
14018
|
return false;
|
|
13512
14019
|
if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
|
|
@@ -13569,6 +14076,14 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
13569
14076
|
reason: "an applicable approved limitation permits the missing evidence"
|
|
13570
14077
|
};
|
|
13571
14078
|
const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
|
|
14079
|
+
if (gate)
|
|
14080
|
+
return {
|
|
14081
|
+
requirementId: requirement.id,
|
|
14082
|
+
status: unavailable ? "unavailable" : "unsatisfied",
|
|
14083
|
+
evidenceIds: [...new Set([...gate.considered, ...related.map(({ entry }) => entry.id)])],
|
|
14084
|
+
decisionIds: [],
|
|
14085
|
+
reason: unavailable ? "required capability is unavailable" : gate.reason
|
|
14086
|
+
};
|
|
13572
14087
|
if (!unavailable && related.length > 0 && !passed.length)
|
|
13573
14088
|
return {
|
|
13574
14089
|
requirementId: requirement.id,
|
|
@@ -13745,8 +14260,11 @@ policy, and reapplies the call, returning busy under persistent contention. A
|
|
|
13745
14260
|
revision_conflict means a revision you passed is stale: re-read the record
|
|
13746
14261
|
before deciding whether to retry.
|
|
13747
14262
|
A solo edit does not need writer acquisition; use it when concurrent checkout
|
|
13748
|
-
writers need coordination. Record only observed facts and checks.
|
|
13749
|
-
|
|
14263
|
+
writers need coordination. Record only observed facts and checks. Close-time
|
|
14264
|
+
testing and verification gates accept only a configured check the CLI ran:
|
|
14265
|
+
\`workit check <name>\` (\`npx -y @brainervirus/workit-cli check <name>\` when
|
|
14266
|
+
\`workit\` is not on PATH); a recorded check result is a note and an ad-hoc
|
|
14267
|
+
\`workit check -- <cmd>\` never satisfies a gate. Evidence can become stale when its bound candidate or tree changes; reconcile findings against the
|
|
13750
14268
|
current candidate before recording completion.
|
|
13751
14269
|
|
|
13752
14270
|
Workit validates domain policy against the actual action target, configured
|
|
@@ -13795,8 +14313,9 @@ target result; a local commit alone is not evidence of a requested remote push.
|
|
|
13795
14313
|
Skill routing: slash aliases /wk-* load on demand. Use workit-steer for a
|
|
13796
14314
|
substantial interruption or change of direction, workit-deslop when relevant
|
|
13797
14315
|
to a PR-ready endpoint, workit-green-run for failing CI, workit-blast-radius
|
|
13798
|
-
when impact is uncertain,
|
|
13799
|
-
choices
|
|
14316
|
+
when impact is uncertain, workit-challenge for genuinely open consequential
|
|
14317
|
+
choices, workit-bdd to turn acceptance criteria into Given/When/Then tests, and
|
|
14318
|
+
workit-test-audit to check tests for tautologies. Load workit-plan when dependencies or handoff need durable next actions.
|
|
13800
14319
|
Load the skill; never act from memory of it.
|
|
13801
14320
|
`.trim();
|
|
13802
14321
|
|
|
@@ -13807,7 +14326,7 @@ function reconcileResume(view, observations = []) {
|
|
|
13807
14326
|
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
13808
14327
|
if (!candidate.ok)
|
|
13809
14328
|
return candidate;
|
|
13810
|
-
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
14329
|
+
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data, treeFreshness(view.workspace.root)).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
13811
14330
|
const blockers = [...view.task.progress.blockers];
|
|
13812
14331
|
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
13813
14332
|
blockers.push({
|
|
@@ -13932,7 +14451,7 @@ var shellBranchPolicyViolation = (root, command) => {
|
|
|
13932
14451
|
// packages/workit-core/src/core/external-action.ts
|
|
13933
14452
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
13934
14453
|
import { realpathSync as realpathSync6 } from "node:fs";
|
|
13935
|
-
import
|
|
14454
|
+
import path14 from "node:path";
|
|
13936
14455
|
var chainStepSchema = union([
|
|
13937
14456
|
string2().min(1),
|
|
13938
14457
|
object({ branch: string2().min(1) }).strict(),
|
|
@@ -14126,7 +14645,7 @@ var planCommitDescriptor = (content) => {
|
|
|
14126
14645
|
if (!steps || typeof branch !== "string" || !branch)
|
|
14127
14646
|
return null;
|
|
14128
14647
|
const cwd = value.payload.cwd;
|
|
14129
|
-
if (cwd !== undefined && (typeof cwd !== "string" || !
|
|
14648
|
+
if (cwd !== undefined && (typeof cwd !== "string" || !path14.isAbsolute(cwd)))
|
|
14130
14649
|
return null;
|
|
14131
14650
|
const head = value.payload.resolved?.head;
|
|
14132
14651
|
return {
|
|
@@ -14269,7 +14788,7 @@ var descriptorTargetRoot = (store, operation) => {
|
|
|
14269
14788
|
const cwd = descriptor.payload?.cwd;
|
|
14270
14789
|
if (cwd === undefined)
|
|
14271
14790
|
return store.root;
|
|
14272
|
-
return typeof cwd === "string" &&
|
|
14791
|
+
return typeof cwd === "string" && path14.isAbsolute(cwd) ? realpathSync6(cwd) : null;
|
|
14273
14792
|
} catch {
|
|
14274
14793
|
return null;
|
|
14275
14794
|
}
|
|
@@ -14380,7 +14899,7 @@ var branchSetupIntent = (descriptor) => {
|
|
|
14380
14899
|
const { target_branch, resolved } = value.payload;
|
|
14381
14900
|
if (typeof target_branch !== "string" || !target_branch || !resolved)
|
|
14382
14901
|
return null;
|
|
14383
|
-
if (value.payload.cwd !== undefined && (typeof value.payload.cwd !== "string" || !
|
|
14902
|
+
if (value.payload.cwd !== undefined && (typeof value.payload.cwd !== "string" || !path14.isAbsolute(value.payload.cwd)))
|
|
14384
14903
|
return null;
|
|
14385
14904
|
if (typeof resolved.base_branch !== "string" || typeof resolved.target_exists !== "boolean")
|
|
14386
14905
|
return null;
|
|
@@ -14497,7 +15016,7 @@ var externalActionTargetRoot = (operation, coordinationRoot) => {
|
|
|
14497
15016
|
const payload = descriptor.payload;
|
|
14498
15017
|
const cwd = typeof payload.cwd === "string" ? payload.cwd : coordinationRoot;
|
|
14499
15018
|
try {
|
|
14500
|
-
return realpathSync6(
|
|
15019
|
+
return realpathSync6(path14.resolve(coordinationRoot, cwd));
|
|
14501
15020
|
} catch {
|
|
14502
15021
|
return null;
|
|
14503
15022
|
}
|
|
@@ -15746,10 +16265,7 @@ class WorkitCore {
|
|
|
15746
16265
|
if (finding.data.disposition !== "fixed")
|
|
15747
16266
|
return finding;
|
|
15748
16267
|
const withEntry = { ...current, candidates, evidence: [...current.evidence, entry] };
|
|
15749
|
-
const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data).map((item) => [
|
|
15750
|
-
item.evidenceId,
|
|
15751
|
-
item.status
|
|
15752
|
-
]));
|
|
16268
|
+
const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data, treeFreshness(this.store.root)).map((item) => [item.evidenceId, item.status]));
|
|
15753
16269
|
const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
|
|
15754
16270
|
kind: item.data.kind,
|
|
15755
16271
|
candidateId: item.data.candidateId,
|
|
@@ -15764,6 +16280,71 @@ class WorkitCore {
|
|
|
15764
16280
|
return changed;
|
|
15765
16281
|
return success(changed.data.revision, null, changed.data.evidence.at(-1));
|
|
15766
16282
|
}
|
|
16283
|
+
observeCheck(request) {
|
|
16284
|
+
return this.retryOmittedRevisions(request, () => this.observeCheckOnce(request));
|
|
16285
|
+
}
|
|
16286
|
+
observeCheckOnce(request) {
|
|
16287
|
+
const root = this.contextRootError();
|
|
16288
|
+
if (!root.ok)
|
|
16289
|
+
return root;
|
|
16290
|
+
if (this.context.caller.host !== "workit_cli" || (this.context.workerId ?? null) !== null)
|
|
16291
|
+
return failure2("permission_denied", "only the workit CLI records observed checks");
|
|
16292
|
+
const observation = checkObservationSchema.safeParse(request?.observation);
|
|
16293
|
+
if (!observation.success)
|
|
16294
|
+
return failure2("invalid_input", "check observation is invalid", {
|
|
16295
|
+
fields: observation.error.issues.map((issue) => ({
|
|
16296
|
+
path: issue.path.join("."),
|
|
16297
|
+
reason: issue.message
|
|
16298
|
+
}))
|
|
16299
|
+
});
|
|
16300
|
+
const task = this.store.readTask(request.taskId);
|
|
16301
|
+
if (!task.ok)
|
|
16302
|
+
return task;
|
|
16303
|
+
if (task.data.status === "closed")
|
|
16304
|
+
return failure2("invalid_transition", "closed task cannot record evidence");
|
|
16305
|
+
const observed = observation.data;
|
|
16306
|
+
let candidate = null;
|
|
16307
|
+
if (observed.tree === null) {
|
|
16308
|
+
const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
16309
|
+
if (!captured.ok)
|
|
16310
|
+
return captured;
|
|
16311
|
+
candidate = captured.data;
|
|
16312
|
+
}
|
|
16313
|
+
const label = observed.name ?? observed.argv.join(" ");
|
|
16314
|
+
const data = {
|
|
16315
|
+
kind: "check",
|
|
16316
|
+
claim: `workit check ${label}`.slice(0, 500),
|
|
16317
|
+
requirementIds: [],
|
|
16318
|
+
...candidate ? { beforeCandidateId: candidate.id, candidateId: candidate.id } : {},
|
|
16319
|
+
result: observed.exitCode === 0 ? "passed" : "failed",
|
|
16320
|
+
summary: `${observed.configured ? "configured" : "ad-hoc"} check exited ${observed.exitCode}${observed.timedOut ? " (timed out)" : ""} in ${observed.durationMs} ms`,
|
|
16321
|
+
refs: [],
|
|
16322
|
+
exitCode: observed.exitCode,
|
|
16323
|
+
reviewContext: null,
|
|
16324
|
+
observation: observed
|
|
16325
|
+
};
|
|
16326
|
+
const changed = this.store.mutateTask(task.data.id, request.expectedRevision ?? task.data.revision, (current, mutation) => {
|
|
16327
|
+
const candidates = candidate === null || current.candidates.some((item) => item.id === candidate.id) ? current.candidates : [...current.candidates, candidate];
|
|
16328
|
+
const critical = [...new Set([...current.critical ?? [], CHECK_OBSERVATION_PATH])];
|
|
16329
|
+
return success(mutation.revision, null, {
|
|
16330
|
+
...current,
|
|
16331
|
+
critical,
|
|
16332
|
+
candidates,
|
|
16333
|
+
evidence: [
|
|
16334
|
+
...current.evidence,
|
|
16335
|
+
{
|
|
16336
|
+
id: newId(),
|
|
16337
|
+
recordedAt: mutation.now,
|
|
16338
|
+
provenance: provenance(this.context, "host_observed"),
|
|
16339
|
+
data
|
|
16340
|
+
}
|
|
16341
|
+
]
|
|
16342
|
+
});
|
|
16343
|
+
}, trustedNow(this.context));
|
|
16344
|
+
if (!changed.ok)
|
|
16345
|
+
return changed;
|
|
16346
|
+
return success(changed.data.revision, null, changed.data.evidence.at(-1));
|
|
16347
|
+
}
|
|
15767
16348
|
decision(request) {
|
|
15768
16349
|
return this.recordDecision(request);
|
|
15769
16350
|
}
|
|
@@ -15999,7 +16580,7 @@ class WorkitCore {
|
|
|
15999
16580
|
const current = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
16000
16581
|
if (!current.ok)
|
|
16001
16582
|
return current;
|
|
16002
|
-
const evaluations = evaluateEvidence(task.data, current.data);
|
|
16583
|
+
const evaluations = evaluateEvidence(task.data, current.data, treeFreshness(this.store.root));
|
|
16003
16584
|
const verified = evidence.some((entry) => {
|
|
16004
16585
|
const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
|
|
16005
16586
|
return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
|
|
@@ -16703,7 +17284,8 @@ class WorkitCore {
|
|
|
16703
17284
|
if (!current.ok)
|
|
16704
17285
|
return current;
|
|
16705
17286
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
16706
|
-
const
|
|
17287
|
+
const tree = treeFreshness(this.store.root);
|
|
17288
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
|
|
16707
17289
|
return success(task.revision, workspace.data.revision, {
|
|
16708
17290
|
id: task.id,
|
|
16709
17291
|
revision: task.revision,
|
|
@@ -16720,7 +17302,7 @@ class WorkitCore {
|
|
|
16720
17302
|
writer: evaluationWorkspace.writer
|
|
16721
17303
|
});
|
|
16722
17304
|
}
|
|
16723
|
-
compactContext(taskId) {
|
|
17305
|
+
compactContext(taskId, freshness) {
|
|
16724
17306
|
const root = this.contextRootError();
|
|
16725
17307
|
if (!root.ok)
|
|
16726
17308
|
return root;
|
|
@@ -16730,12 +17312,12 @@ class WorkitCore {
|
|
|
16730
17312
|
const helper = this.helperTaskGuard(task.data, true);
|
|
16731
17313
|
if (!helper.ok)
|
|
16732
17314
|
return helper;
|
|
16733
|
-
const view = this.view(task.data, false);
|
|
17315
|
+
const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
|
|
16734
17316
|
if (!view.ok)
|
|
16735
17317
|
return view;
|
|
16736
17318
|
return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
|
|
16737
17319
|
}
|
|
16738
|
-
view(task, capture = true) {
|
|
17320
|
+
view(task, capture = true, freshness = treeFreshness(this.store.root)) {
|
|
16739
17321
|
const workspace = this.store.readWorkspace();
|
|
16740
17322
|
if (!workspace.ok)
|
|
16741
17323
|
return workspace;
|
|
@@ -16746,13 +17328,14 @@ class WorkitCore {
|
|
|
16746
17328
|
if (!current.ok)
|
|
16747
17329
|
return current;
|
|
16748
17330
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
16749
|
-
const
|
|
17331
|
+
const tree = freshness;
|
|
17332
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
|
|
16750
17333
|
return success(task.revision, workspace.data.revision, {
|
|
16751
17334
|
task,
|
|
16752
17335
|
workspace: evaluationWorkspace,
|
|
16753
17336
|
capabilities: this.context.capabilities,
|
|
16754
17337
|
requirements,
|
|
16755
|
-
evidence: evaluateEvidence(task, current.data)
|
|
17338
|
+
evidence: evaluateEvidence(task, current.data, tree)
|
|
16756
17339
|
});
|
|
16757
17340
|
}
|
|
16758
17341
|
reconcileResume(view, observations = []) {
|
|
@@ -16979,13 +17562,14 @@ class WorkitCore {
|
|
|
16979
17562
|
return current;
|
|
16980
17563
|
const withCandidate = current.data;
|
|
16981
17564
|
const taskForView = withCandidate.id === task.data.candidates.at(-1)?.id ? task.data : { ...task.data, candidates: [...task.data.candidates, withCandidate] };
|
|
16982
|
-
const
|
|
17565
|
+
const tree = treeFreshness(this.store.root);
|
|
17566
|
+
const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller, tree);
|
|
16983
17567
|
const view = {
|
|
16984
17568
|
task: taskForView,
|
|
16985
17569
|
workspace: workspace.data,
|
|
16986
17570
|
capabilities: this.context.capabilities,
|
|
16987
17571
|
requirements,
|
|
16988
|
-
evidence: evaluateEvidence(taskForView, withCandidate)
|
|
17572
|
+
evidence: evaluateEvidence(taskForView, withCandidate, tree)
|
|
16989
17573
|
};
|
|
16990
17574
|
const closure = evaluateClosure(input.outcome, view);
|
|
16991
17575
|
if (!closure.ok)
|
|
@@ -17049,22 +17633,22 @@ function resolveGitRevision(root, value) {
|
|
|
17049
17633
|
}
|
|
17050
17634
|
function resolveInside(root, candidate) {
|
|
17051
17635
|
const base = realpathSync8(root);
|
|
17052
|
-
const target =
|
|
17053
|
-
if (target !== base && !target.startsWith(base +
|
|
17636
|
+
const target = path15.resolve(base, candidate);
|
|
17637
|
+
if (target !== base && !target.startsWith(base + path15.sep)) {
|
|
17054
17638
|
throw new Error("path must stay inside repository root");
|
|
17055
17639
|
}
|
|
17056
17640
|
let ancestor = target;
|
|
17057
17641
|
while (!existsSync6(ancestor))
|
|
17058
|
-
ancestor =
|
|
17642
|
+
ancestor = path15.dirname(ancestor);
|
|
17059
17643
|
const canonicalAncestor = realpathSync8(ancestor);
|
|
17060
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
17644
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path15.sep)) {
|
|
17061
17645
|
throw new Error("path must stay inside repository root");
|
|
17062
17646
|
}
|
|
17063
17647
|
return target;
|
|
17064
17648
|
}
|
|
17065
17649
|
function run2(root, executable, args, env = {}) {
|
|
17066
17650
|
const cwd = realpathSync8(root);
|
|
17067
|
-
const result =
|
|
17651
|
+
const result = spawnSync6(executable, args, {
|
|
17068
17652
|
cwd,
|
|
17069
17653
|
encoding: "utf8",
|
|
17070
17654
|
env: { ...process.env, ...env }
|
|
@@ -17101,7 +17685,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
17101
17685
|
};
|
|
17102
17686
|
});
|
|
17103
17687
|
// packages/workit-core/src/hooks/context.ts
|
|
17104
|
-
import
|
|
17688
|
+
import path16 from "node:path";
|
|
17105
17689
|
var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
|
|
17106
17690
|
var newestFirst = (left, right) => right.updatedAt.localeCompare(left.updatedAt);
|
|
17107
17691
|
var sessionTaskEntry = (entries, session) => entries.filter((entry) => entry.status !== "closed" && boundTo(entry, session)).toSorted(newestFirst)[0] ?? null;
|
|
@@ -17126,6 +17710,19 @@ var filesKey = (files) => {
|
|
|
17126
17710
|
return signatures.some((item) => item !== "-" && racySignature(item)) ? null : signatures.join("\x00");
|
|
17127
17711
|
};
|
|
17128
17712
|
var CACHE_LIMIT = 64;
|
|
17713
|
+
var SIGNAL_TTL_MS = 1500;
|
|
17714
|
+
var signals = new Map;
|
|
17715
|
+
var recentSignal = (root) => {
|
|
17716
|
+
const now = Date.now();
|
|
17717
|
+
const cached = signals.get(root);
|
|
17718
|
+
if (cached && now - cached.at < SIGNAL_TTL_MS)
|
|
17719
|
+
return cached.value;
|
|
17720
|
+
const value = worktreeSignal(root);
|
|
17721
|
+
signals.set(root, { at: now, value });
|
|
17722
|
+
if (signals.size > CACHE_LIMIT)
|
|
17723
|
+
signals.delete(signals.keys().next().value);
|
|
17724
|
+
return value;
|
|
17725
|
+
};
|
|
17129
17726
|
var cache = new Map;
|
|
17130
17727
|
function sessionCompactContext(store, session, context, selection = "session-bound") {
|
|
17131
17728
|
const listed = store.listTaskIndex();
|
|
@@ -17149,18 +17746,26 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
17149
17746
|
capabilities: context.capabilities
|
|
17150
17747
|
});
|
|
17151
17748
|
const hit = cache.get(slot);
|
|
17152
|
-
if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files))
|
|
17749
|
+
if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files) && (hit.signal === null || hit.signal === recentSignal(store.root)))
|
|
17153
17750
|
return hit.value;
|
|
17154
17751
|
const task = store.readTask(entry.id);
|
|
17155
17752
|
if (!task.ok)
|
|
17156
17753
|
return null;
|
|
17157
|
-
const
|
|
17158
|
-
const observed =
|
|
17159
|
-
const
|
|
17754
|
+
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
17755
|
+
const observed = task.data.evidence.some(isObservedCheck);
|
|
17756
|
+
const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path16.resolve(store.root, ref.path)] : []));
|
|
17757
|
+
const observedFiles = filesKey(files);
|
|
17758
|
+
const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
|
|
17160
17759
|
if (!compact.ok)
|
|
17161
17760
|
return null;
|
|
17162
17761
|
cache.delete(slot);
|
|
17163
|
-
cache.set(slot, {
|
|
17762
|
+
cache.set(slot, {
|
|
17763
|
+
key,
|
|
17764
|
+
files,
|
|
17765
|
+
filesKey: observedFiles,
|
|
17766
|
+
signal: observed ? freshness.current() : null,
|
|
17767
|
+
value: compact.data
|
|
17768
|
+
});
|
|
17164
17769
|
if (cache.size > CACHE_LIMIT)
|
|
17165
17770
|
cache.delete(cache.keys().next().value);
|
|
17166
17771
|
return compact.data;
|
|
@@ -17322,14 +17927,16 @@ var WORKIT_SKILL_ALIASES = {
|
|
|
17322
17927
|
"wk-diagram": "workit-diagram",
|
|
17323
17928
|
"wk-mockup": "workit-mockup",
|
|
17324
17929
|
"wk-green-run": "workit-green-run",
|
|
17325
|
-
"wk-steer": "workit-steer"
|
|
17930
|
+
"wk-steer": "workit-steer",
|
|
17931
|
+
"wk-bdd": "workit-bdd",
|
|
17932
|
+
"wk-test-audit": "workit-test-audit"
|
|
17326
17933
|
};
|
|
17327
17934
|
if (false) {}
|
|
17328
17935
|
|
|
17329
17936
|
// packages/workit-core/src/core/pr-create.ts
|
|
17330
|
-
import
|
|
17331
|
-
import
|
|
17332
|
-
import { spawnSync as
|
|
17937
|
+
import fs11 from "node:fs";
|
|
17938
|
+
import path17 from "node:path";
|
|
17939
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
17333
17940
|
import { pathToFileURL } from "node:url";
|
|
17334
17941
|
// packages/workit-core/src/forge/pr-body.ts
|
|
17335
17942
|
function parseGhIssue(value) {
|
|
@@ -17419,13 +18026,13 @@ var cliRepository = (remote, provider, cfg) => {
|
|
|
17419
18026
|
};
|
|
17420
18027
|
function whichOnPath(tool) {
|
|
17421
18028
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
17422
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
18029
|
+
for (const dir of (process.env.PATH ?? "").split(path17.delimiter)) {
|
|
17423
18030
|
if (!dir)
|
|
17424
18031
|
continue;
|
|
17425
18032
|
for (const name of names) {
|
|
17426
|
-
const candidate =
|
|
18033
|
+
const candidate = path17.join(dir, name);
|
|
17427
18034
|
try {
|
|
17428
|
-
|
|
18035
|
+
fs11.accessSync(candidate, fs11.constants.X_OK);
|
|
17429
18036
|
return candidate;
|
|
17430
18037
|
} catch {}
|
|
17431
18038
|
}
|
|
@@ -17434,11 +18041,11 @@ function whichOnPath(tool) {
|
|
|
17434
18041
|
}
|
|
17435
18042
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
17436
18043
|
function repoRoot(cwd) {
|
|
17437
|
-
const result =
|
|
18044
|
+
const result = spawnSync7("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
|
|
17438
18045
|
return result.status === 0 ? (result.stdout ?? "").trim() : cwd;
|
|
17439
18046
|
}
|
|
17440
18047
|
var pushRemote = (cwd) => {
|
|
17441
|
-
const result =
|
|
18048
|
+
const result = spawnSync7("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
17442
18049
|
cwd,
|
|
17443
18050
|
encoding: "utf8"
|
|
17444
18051
|
});
|
|
@@ -17449,7 +18056,7 @@ var pushRemote = (cwd) => {
|
|
|
17449
18056
|
};
|
|
17450
18057
|
var safePushUrl = (remote) => {
|
|
17451
18058
|
const raw = remote.trim();
|
|
17452
|
-
if (
|
|
18059
|
+
if (path17.isAbsolute(raw))
|
|
17453
18060
|
return pathToFileURL(raw).href;
|
|
17454
18061
|
if (!raw.includes("://")) {
|
|
17455
18062
|
const scp = /^(?:([^@\s]+)@)?([^:]+):(.+)$/u.exec(raw);
|
|
@@ -17474,7 +18081,7 @@ var pushRemoteIdentity = (remote) => {
|
|
|
17474
18081
|
const raw = remote.trim();
|
|
17475
18082
|
if (!raw)
|
|
17476
18083
|
return null;
|
|
17477
|
-
if (
|
|
18084
|
+
if (path17.isAbsolute(raw))
|
|
17478
18085
|
return pathToFileURL(raw).href;
|
|
17479
18086
|
if (!raw.includes("://")) {
|
|
17480
18087
|
const scp = /^(?:([^@\s]+)@)?([^:]+):(.+)$/u.exec(raw);
|
|
@@ -17498,7 +18105,7 @@ var pushTargetIsStable = (cwd, remote) => {
|
|
|
17498
18105
|
const target = safePushUrl(remote);
|
|
17499
18106
|
if (!target)
|
|
17500
18107
|
return false;
|
|
17501
|
-
const result =
|
|
18108
|
+
const result = spawnSync7("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
|
|
17502
18109
|
if (result.status !== 0 && result.status !== 1)
|
|
17503
18110
|
return false;
|
|
17504
18111
|
const prefixes = (result.stdout ?? "").split(/\r?\n/u).map((line) => line.trim().slice(line.trim().indexOf(" ") + 1)).filter(Boolean);
|
|
@@ -17535,7 +18142,7 @@ function prCreate(env, cwd) {
|
|
|
17535
18142
|
return {
|
|
17536
18143
|
error: "origin must have exactly one supported push URL for a reserved hosting action"
|
|
17537
18144
|
};
|
|
17538
|
-
const localOnlyTarget =
|
|
18145
|
+
const localOnlyTarget = path17.isAbsolute(pushUrl) || pushUrl.startsWith("file://");
|
|
17539
18146
|
if ((!localOnlyTarget || env.WORKIT_EXPECTED_API_HOST) && !hostingApiHostMatches(pushUrl, identity.host))
|
|
17540
18147
|
return { error: "hosting API host does not match the push destination" };
|
|
17541
18148
|
if (env.WORKIT_EXPECTED_REMOTE && env.WORKIT_EXPECTED_REMOTE !== targetIdentity)
|
|
@@ -17546,12 +18153,12 @@ function prCreate(env, cwd) {
|
|
|
17546
18153
|
const resolvedDefault = String(cfg.defaultTargetBranch ?? policy.defaultTargetBranch ?? "develop");
|
|
17547
18154
|
const target = targetOverride || resolvedDefault;
|
|
17548
18155
|
const title = String(env.WF_PR_TITLE ?? "");
|
|
17549
|
-
const br =
|
|
18156
|
+
const br = spawnSync7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
17550
18157
|
cwd: root,
|
|
17551
18158
|
encoding: "utf8"
|
|
17552
18159
|
});
|
|
17553
18160
|
const branch = br.status === 0 ? (br.stdout ?? "").trim() : "";
|
|
17554
|
-
const sourceResult = branch ?
|
|
18161
|
+
const sourceResult = branch ? spawnSync7("git", ["rev-parse", "--verify", `refs/heads/${branch}`], {
|
|
17555
18162
|
cwd: root,
|
|
17556
18163
|
encoding: "utf8"
|
|
17557
18164
|
}) : null;
|
|
@@ -17560,7 +18167,7 @@ function prCreate(env, cwd) {
|
|
|
17560
18167
|
return { error: "approved PR source branch and commit are incomplete" };
|
|
17561
18168
|
if (env.WORKIT_EXPECTED_SOURCE_BRANCH && env.WORKIT_EXPECTED_SOURCE_BRANCH !== branch || env.WORKIT_EXPECTED_SOURCE_COMMIT && env.WORKIT_EXPECTED_SOURCE_COMMIT !== sourceCommit)
|
|
17562
18169
|
return { error: "approved PR source branch or commit changed before execution" };
|
|
17563
|
-
const mergeBaseResult = policy.integration === "merge" ?
|
|
18170
|
+
const mergeBaseResult = policy.integration === "merge" ? spawnSync7("git", ["rev-parse", "--verify", `refs/heads/${target}`], {
|
|
17564
18171
|
cwd: root,
|
|
17565
18172
|
encoding: "utf8"
|
|
17566
18173
|
}) : null;
|
|
@@ -17576,7 +18183,7 @@ function prCreate(env, cwd) {
|
|
|
17576
18183
|
targetBranch: target,
|
|
17577
18184
|
stderr: "source or target branch commit could not be resolved"
|
|
17578
18185
|
};
|
|
17579
|
-
const merge =
|
|
18186
|
+
const merge = spawnSync7("git", ["merge", "--no-ff", sourceCommit, "-m", title], {
|
|
17580
18187
|
cwd: root,
|
|
17581
18188
|
encoding: "utf8"
|
|
17582
18189
|
});
|
|
@@ -17587,16 +18194,16 @@ function prCreate(env, cwd) {
|
|
|
17587
18194
|
targetBranch: target,
|
|
17588
18195
|
stderr: (merge.stderr ?? "").slice(0, 800)
|
|
17589
18196
|
};
|
|
17590
|
-
const mergedCommit =
|
|
18197
|
+
const mergedCommit = spawnSync7("git", ["rev-parse", "HEAD"], {
|
|
17591
18198
|
cwd: root,
|
|
17592
18199
|
encoding: "utf8"
|
|
17593
18200
|
});
|
|
17594
18201
|
if (mergedCommit.status !== 0)
|
|
17595
18202
|
return { error: "merge commit could not be resolved", mode: "merge" };
|
|
17596
|
-
const updateTarget =
|
|
18203
|
+
const updateTarget = spawnSync7("git", ["update-ref", `refs/heads/${target}`, mergedCommit.stdout.trim(), mergeBaseCommit], { cwd: root, encoding: "utf8" });
|
|
17597
18204
|
if (updateTarget.status !== 0)
|
|
17598
18205
|
return { error: "merge target branch changed before update", mode: "merge" };
|
|
17599
|
-
const push =
|
|
18206
|
+
const push = spawnSync7("git", ["push", targetUrl, `${mergedCommit.stdout.trim()}:refs/heads/${target}`], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: process.env.PATH ?? "" } });
|
|
17600
18207
|
if (push.status !== 0)
|
|
17601
18208
|
return {
|
|
17602
18209
|
error: "push failed",
|
|
@@ -17613,7 +18220,7 @@ function prCreate(env, cwd) {
|
|
|
17613
18220
|
output: (push.stdout ?? "").trim()
|
|
17614
18221
|
};
|
|
17615
18222
|
};
|
|
17616
|
-
const co =
|
|
18223
|
+
const co = spawnSync7("git", ["checkout", "--detach", mergeBaseCommit], {
|
|
17617
18224
|
cwd: root,
|
|
17618
18225
|
encoding: "utf8"
|
|
17619
18226
|
});
|
|
@@ -17625,7 +18232,7 @@ function prCreate(env, cwd) {
|
|
|
17625
18232
|
stderr: (co.stderr ?? "").slice(0, 800)
|
|
17626
18233
|
};
|
|
17627
18234
|
const result = finish();
|
|
17628
|
-
|
|
18235
|
+
spawnSync7("git", ["checkout", branch], { cwd: root, encoding: "utf8" });
|
|
17629
18236
|
return result;
|
|
17630
18237
|
}
|
|
17631
18238
|
const pr = cfg.pr ?? {};
|
|
@@ -17633,9 +18240,9 @@ function prCreate(env, cwd) {
|
|
|
17633
18240
|
const draft = String(env.WF_PR_DRAFT ?? "false").toLowerCase() === "true";
|
|
17634
18241
|
let baseUrl = cfg.youtrack_base_url;
|
|
17635
18242
|
if (!baseUrl) {
|
|
17636
|
-
const ytCfg = process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
18243
|
+
const ytCfg = process.env.WORKFLOW_YOUTRACK_CONFIG ?? path17.join(path17.dirname(String(cfg.configPath)), "youtrack.json");
|
|
17637
18244
|
try {
|
|
17638
|
-
const yt = JSON.parse(
|
|
18245
|
+
const yt = JSON.parse(fs11.readFileSync(ytCfg, "utf8"));
|
|
17639
18246
|
if (yt && typeof yt === "object")
|
|
17640
18247
|
baseUrl = yt.baseUrl;
|
|
17641
18248
|
} catch {}
|
|
@@ -17651,7 +18258,7 @@ function prCreate(env, cwd) {
|
|
|
17651
18258
|
const push = pr.pushBranch !== false;
|
|
17652
18259
|
const skipConfirm = pr.confirmSkip !== false;
|
|
17653
18260
|
const remoteBranchTip = () => {
|
|
17654
|
-
const read =
|
|
18261
|
+
const read = spawnSync7("git", ["ls-remote", targetUrl, `refs/heads/${branch}`], {
|
|
17655
18262
|
cwd: root,
|
|
17656
18263
|
encoding: "utf8",
|
|
17657
18264
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -17667,7 +18274,7 @@ function prCreate(env, cwd) {
|
|
|
17667
18274
|
stderr: branch ? "source branch commit could not be resolved" : "empty current branch (detached HEAD or unborn HEAD)"
|
|
17668
18275
|
};
|
|
17669
18276
|
if (push) {
|
|
17670
|
-
const pushed =
|
|
18277
|
+
const pushed = spawnSync7("git", ["push", "-u", targetUrl, `${sourceCommit}:refs/heads/${branch}`], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: process.env.PATH ?? "" } });
|
|
17671
18278
|
if (pushed.status !== 0)
|
|
17672
18279
|
return { error: "push failed", provider, stderr: (pushed.stderr ?? "").slice(0, 800) };
|
|
17673
18280
|
}
|
|
@@ -17712,12 +18319,12 @@ function prCreate(env, cwd) {
|
|
|
17712
18319
|
cmd.push("--draft");
|
|
17713
18320
|
cmdEnv = { ...process.env, PATH: process.env.PATH ?? "", GH_HOST: identity.host };
|
|
17714
18321
|
}
|
|
17715
|
-
const result =
|
|
18322
|
+
const result = spawnSync7(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
|
|
17716
18323
|
if (result.status !== 0) {
|
|
17717
18324
|
const err = (result.stderr ?? result.stdout ?? "").trim();
|
|
17718
18325
|
let hint = null;
|
|
17719
18326
|
if (provider === "gitlab" && (err.includes("409") || err.toLowerCase().includes("already exists"))) {
|
|
17720
|
-
const list =
|
|
18327
|
+
const list = spawnSync7("glab", ["mr", "list", `--source-branch=${branch}`, "--output=json"], {
|
|
17721
18328
|
cwd: root,
|
|
17722
18329
|
encoding: "utf8",
|
|
17723
18330
|
env: cmdEnv
|
|
@@ -17776,7 +18383,7 @@ function mergePr(cwd, opts) {
|
|
|
17776
18383
|
return { error: "approved hosting account changed before execution" };
|
|
17777
18384
|
if (!/^[a-f0-9]{40,64}$/i.test(opts.sourceCommit))
|
|
17778
18385
|
return { error: "approved merge source commit is invalid" };
|
|
17779
|
-
const currentCommit =
|
|
18386
|
+
const currentCommit = spawnSync7("git", ["rev-parse", "--verify", `refs/heads/${opts.source}`], {
|
|
17780
18387
|
cwd: root,
|
|
17781
18388
|
encoding: "utf8"
|
|
17782
18389
|
});
|
|
@@ -17790,7 +18397,7 @@ function mergePr(cwd, opts) {
|
|
|
17790
18397
|
return { error: `workflow CLI missing (required for ${provider})` };
|
|
17791
18398
|
const owner = repoPath.split("/")[0];
|
|
17792
18399
|
const endpoint = provider === "github" ? `repos/${repository.replace(/^[^/]+\//u, "")}/pulls?state=open&head=${encodeURIComponent(`${owner}:${opts.source}`)}&base=${encodeURIComponent(opts.target)}&per_page=100` : `projects/${encodeURIComponent(repoPath)}/merge_requests?state=opened&source_branch=${encodeURIComponent(opts.source)}&target_branch=${encodeURIComponent(opts.target)}&per_page=100`;
|
|
17793
|
-
const listOpen = () =>
|
|
18400
|
+
const listOpen = () => spawnSync7(provider === "github" ? "gh" : "glab", ["api", endpoint], {
|
|
17794
18401
|
cwd: root,
|
|
17795
18402
|
encoding: "utf8",
|
|
17796
18403
|
env: {
|
|
@@ -17850,7 +18457,7 @@ function mergePr(cwd, opts) {
|
|
|
17850
18457
|
cmd.push("--delete-branch");
|
|
17851
18458
|
cmdEnv = { ...process.env, PATH: process.env.PATH ?? "", GH_HOST: identity.host };
|
|
17852
18459
|
}
|
|
17853
|
-
const result =
|
|
18460
|
+
const result = spawnSync7(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
|
|
17854
18461
|
if (result.status !== 0)
|
|
17855
18462
|
return {
|
|
17856
18463
|
error: "merge failed",
|
|
@@ -17867,9 +18474,9 @@ function mergePr(cwd, opts) {
|
|
|
17867
18474
|
}
|
|
17868
18475
|
|
|
17869
18476
|
// packages/workit-core/src/core/repo-context.ts
|
|
17870
|
-
import { spawnSync as
|
|
18477
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
17871
18478
|
import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "node:fs";
|
|
17872
|
-
import
|
|
18479
|
+
import path18 from "node:path";
|
|
17873
18480
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
17874
18481
|
try {
|
|
17875
18482
|
gitRevisionParts(value);
|
|
@@ -17896,7 +18503,7 @@ var invalidRange = (cwd) => ({
|
|
|
17896
18503
|
cwd
|
|
17897
18504
|
});
|
|
17898
18505
|
var runGit = (cwd, args) => {
|
|
17899
|
-
const result =
|
|
18506
|
+
const result = spawnSync8("git", args, { cwd, encoding: "utf8" });
|
|
17900
18507
|
return {
|
|
17901
18508
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
17902
18509
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -18003,7 +18610,7 @@ var findPrTemplate = (cwd) => {
|
|
|
18003
18610
|
]) {
|
|
18004
18611
|
const parts = rel.split("/");
|
|
18005
18612
|
const base = parts[parts.length - 1];
|
|
18006
|
-
const dir = parts.length > 1 ?
|
|
18613
|
+
const dir = parts.length > 1 ? path18.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
18007
18614
|
const actual = probeCaseInsensitive(dir, base);
|
|
18008
18615
|
if (actual)
|
|
18009
18616
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -18034,7 +18641,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
18034
18641
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
18035
18642
|
};
|
|
18036
18643
|
var packageScripts = (cwd, keys) => {
|
|
18037
|
-
const pkgPath =
|
|
18644
|
+
const pkgPath = path18.join(cwd, "package.json");
|
|
18038
18645
|
if (!existsSync7(pkgPath))
|
|
18039
18646
|
return [];
|
|
18040
18647
|
try {
|
|
@@ -18060,9 +18667,9 @@ var documentationFiles = (cwd) => {
|
|
|
18060
18667
|
if (entry.isDirectory()) {
|
|
18061
18668
|
if (excluded.has(entry.name))
|
|
18062
18669
|
continue;
|
|
18063
|
-
walk(
|
|
18670
|
+
walk(path18.join(dir, entry.name), depth + 1);
|
|
18064
18671
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
18065
|
-
matches.push("./" +
|
|
18672
|
+
matches.push("./" + path18.relative(cwd, path18.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
18066
18673
|
}
|
|
18067
18674
|
}
|
|
18068
18675
|
};
|
|
@@ -18078,7 +18685,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
18078
18685
|
`);
|
|
18079
18686
|
};
|
|
18080
18687
|
function prReadyContext(root, range) {
|
|
18081
|
-
const cwd =
|
|
18688
|
+
const cwd = path18.resolve(repoRoot2(root));
|
|
18082
18689
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
18083
18690
|
return invalidRange(cwd);
|
|
18084
18691
|
let stdout = "";
|
|
@@ -18136,7 +18743,7 @@ ${status.stderr}` : "");
|
|
|
18136
18743
|
stdout += `template_path: ${template}
|
|
18137
18744
|
|
|
18138
18745
|
`;
|
|
18139
|
-
stdout += readTrimmed2(
|
|
18746
|
+
stdout += readTrimmed2(path18.join(cwd, template), 220);
|
|
18140
18747
|
} else {
|
|
18141
18748
|
stdout += `template_path: none
|
|
18142
18749
|
|
|
@@ -18146,7 +18753,7 @@ ${status.stderr}` : "");
|
|
|
18146
18753
|
stdout += `
|
|
18147
18754
|
`;
|
|
18148
18755
|
stdout += printSection("Recent Validation Signals");
|
|
18149
|
-
if (existsSync7(
|
|
18756
|
+
if (existsSync7(path18.join(cwd, "package.json"))) {
|
|
18150
18757
|
stdout += `package.json detected. Common scripts:
|
|
18151
18758
|
`;
|
|
18152
18759
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -18155,7 +18762,7 @@ ${status.stderr}` : "");
|
|
|
18155
18762
|
`) + `
|
|
18156
18763
|
`;
|
|
18157
18764
|
}
|
|
18158
|
-
if (existsSync7(
|
|
18765
|
+
if (existsSync7(path18.join(cwd, "Cargo.toml")) || existsSync7(path18.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
18159
18766
|
stdout += `Rust project detected.
|
|
18160
18767
|
`;
|
|
18161
18768
|
}
|
|
@@ -18188,7 +18795,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
18188
18795
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
18189
18796
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
18190
18797
|
function changelogContext(root, range) {
|
|
18191
|
-
const cwd =
|
|
18798
|
+
const cwd = path18.resolve(repoRoot2(root));
|
|
18192
18799
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
18193
18800
|
return invalidRange(cwd);
|
|
18194
18801
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -18206,7 +18813,7 @@ function changelogContext(root, range) {
|
|
|
18206
18813
|
stdout += CHANGELOG_RULES + `
|
|
18207
18814
|
`;
|
|
18208
18815
|
stdout += printSection("Existing CHANGELOG.md");
|
|
18209
|
-
const changelogPath =
|
|
18816
|
+
const changelogPath = path18.join(cwd, "CHANGELOG.md");
|
|
18210
18817
|
if (existsSync7(changelogPath)) {
|
|
18211
18818
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
18212
18819
|
`;
|
|
@@ -18226,7 +18833,7 @@ function changelogContext(root, range) {
|
|
|
18226
18833
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
18227
18834
|
}
|
|
18228
18835
|
function docsRefreshContext(root, range) {
|
|
18229
|
-
const cwd =
|
|
18836
|
+
const cwd = path18.resolve(repoRoot2(root));
|
|
18230
18837
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
18231
18838
|
return invalidRange(cwd);
|
|
18232
18839
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -18248,7 +18855,7 @@ function docsRefreshContext(root, range) {
|
|
|
18248
18855
|
`) + `
|
|
18249
18856
|
`;
|
|
18250
18857
|
stdout += printSection("README Preview");
|
|
18251
|
-
const readme =
|
|
18858
|
+
const readme = path18.join(cwd, "README.md");
|
|
18252
18859
|
if (existsSync7(readme)) {
|
|
18253
18860
|
stdout += readTrimmed2(readme, 220) + `
|
|
18254
18861
|
`;
|
|
@@ -18257,7 +18864,7 @@ function docsRefreshContext(root, range) {
|
|
|
18257
18864
|
`;
|
|
18258
18865
|
}
|
|
18259
18866
|
stdout += printSection("Package Scripts");
|
|
18260
|
-
const pkgPath =
|
|
18867
|
+
const pkgPath = path18.join(cwd, "package.json");
|
|
18261
18868
|
if (existsSync7(pkgPath)) {
|
|
18262
18869
|
try {
|
|
18263
18870
|
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
@@ -18271,7 +18878,7 @@ function docsRefreshContext(root, range) {
|
|
|
18271
18878
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
18272
18879
|
}
|
|
18273
18880
|
function releaseNotesContext(root, rangeOrTag) {
|
|
18274
|
-
const cwd =
|
|
18881
|
+
const cwd = path18.resolve(repoRoot2(root));
|
|
18275
18882
|
if (!rangeOrTag) {
|
|
18276
18883
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
18277
18884
|
`, exitCode: 1, cwd };
|
|
@@ -18309,7 +18916,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
18309
18916
|
`;
|
|
18310
18917
|
stdout += printSection("Existing Release Files");
|
|
18311
18918
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
18312
|
-
if (existsSync7(
|
|
18919
|
+
if (existsSync7(path18.join(cwd, rel)))
|
|
18313
18920
|
stdout += rel + `
|
|
18314
18921
|
`;
|
|
18315
18922
|
}
|
|
@@ -18337,7 +18944,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
18337
18944
|
}
|
|
18338
18945
|
|
|
18339
18946
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
18340
|
-
import { spawnSync as
|
|
18947
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
18341
18948
|
var repoPathFromRemote = (remote) => {
|
|
18342
18949
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
18343
18950
|
if (!rest)
|
|
@@ -18358,7 +18965,7 @@ var defaultCli = (bin, available) => {
|
|
|
18358
18965
|
if (!available)
|
|
18359
18966
|
return null;
|
|
18360
18967
|
return (args, root) => {
|
|
18361
|
-
const out =
|
|
18968
|
+
const out = spawnSync9(bin, args, {
|
|
18362
18969
|
cwd: root,
|
|
18363
18970
|
encoding: "utf8",
|
|
18364
18971
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -18436,8 +19043,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
18436
19043
|
};
|
|
18437
19044
|
|
|
18438
19045
|
// packages/workit-core/src/core/youtrack.ts
|
|
18439
|
-
import
|
|
18440
|
-
import
|
|
19046
|
+
import fs12 from "node:fs";
|
|
19047
|
+
import path19 from "node:path";
|
|
18441
19048
|
|
|
18442
19049
|
// packages/workit-core/src/core/templates.ts
|
|
18443
19050
|
var repoRoot3 = assetRoot();
|
|
@@ -18450,22 +19057,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
18450
19057
|
// packages/workit-core/src/core/youtrack.ts
|
|
18451
19058
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
18452
19059
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
18453
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
18454
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
19060
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path19.join(configDir(), "youtrack.json");
|
|
19061
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path19.join(resolveConfigDir(), "youtrack.json");
|
|
18455
19062
|
var youTrackTokenModeOk = (p) => {
|
|
18456
19063
|
if (process.platform === "win32")
|
|
18457
19064
|
return true;
|
|
18458
|
-
const mode =
|
|
19065
|
+
const mode = fs12.statSync(p).mode & 511;
|
|
18459
19066
|
return mode === 384;
|
|
18460
19067
|
};
|
|
18461
19068
|
function readYouTrackConfig(required) {
|
|
18462
19069
|
const cfgPath = youTrackConfigPath();
|
|
18463
|
-
if (!
|
|
19070
|
+
if (!fs12.existsSync(cfgPath)) {
|
|
18464
19071
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
18465
19072
|
}
|
|
18466
19073
|
let parsed;
|
|
18467
19074
|
try {
|
|
18468
|
-
parsed = JSON.parse(
|
|
19075
|
+
parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
|
|
18469
19076
|
} catch {
|
|
18470
19077
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
18471
19078
|
}
|
|
@@ -18476,11 +19083,11 @@ function readYouTrackConfig(required) {
|
|
|
18476
19083
|
}
|
|
18477
19084
|
var readYouTrackContextConfig = () => {
|
|
18478
19085
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
18479
|
-
if (!
|
|
19086
|
+
if (!fs12.existsSync(cfgPath))
|
|
18480
19087
|
return { data: {} };
|
|
18481
19088
|
let parsed;
|
|
18482
19089
|
try {
|
|
18483
|
-
parsed = JSON.parse(
|
|
19090
|
+
parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
|
|
18484
19091
|
} catch {
|
|
18485
19092
|
return { error: "YouTrack configuration is unavailable" };
|
|
18486
19093
|
}
|
|
@@ -18495,22 +19102,22 @@ function youTrackConfigLoad() {
|
|
|
18495
19102
|
}
|
|
18496
19103
|
const cfgPath = loaded.path;
|
|
18497
19104
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
18498
|
-
const tokenPath = tokenFile ?
|
|
18499
|
-
if (!tokenPath || !
|
|
19105
|
+
const tokenPath = tokenFile ? path19.isAbsolute(tokenFile) ? path19.resolve(tokenFile) : path19.resolve(process.cwd(), tokenFile) : "";
|
|
19106
|
+
if (!tokenPath || !fs12.existsSync(tokenPath)) {
|
|
18500
19107
|
return { error: "missing youtrack.token" };
|
|
18501
19108
|
}
|
|
18502
19109
|
if (!youTrackTokenModeOk(tokenPath)) {
|
|
18503
19110
|
return { error: "youtrack.token mode must be 0600" };
|
|
18504
19111
|
}
|
|
18505
|
-
const token =
|
|
19112
|
+
const token = fs12.readFileSync(tokenPath, "utf8").trim();
|
|
18506
19113
|
if (!token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
18507
19114
|
return { error: "token file still placeholder — edit locally, then /wk-status" };
|
|
18508
19115
|
}
|
|
18509
19116
|
const redacted = { ...loaded.config };
|
|
18510
19117
|
delete redacted.tokenFile;
|
|
18511
19118
|
redacted.tokenPresent = true;
|
|
18512
|
-
redacted.configPath =
|
|
18513
|
-
redacted.tokenPath =
|
|
19119
|
+
redacted.configPath = path19.resolve(cfgPath);
|
|
19120
|
+
redacted.tokenPath = path19.resolve(tokenPath);
|
|
18514
19121
|
return { data: redacted };
|
|
18515
19122
|
}
|
|
18516
19123
|
function tzParts(date, tz) {
|
|
@@ -18557,10 +19164,10 @@ function youTrackParseDuration(text) {
|
|
|
18557
19164
|
function youTrackWorkDateMs(dateRaw) {
|
|
18558
19165
|
const cfgPath = youTrackConfigPath();
|
|
18559
19166
|
let effective = effectiveWorkTimezone(null);
|
|
18560
|
-
if (
|
|
19167
|
+
if (fs12.existsSync(cfgPath)) {
|
|
18561
19168
|
let parsed;
|
|
18562
19169
|
try {
|
|
18563
|
-
parsed = JSON.parse(
|
|
19170
|
+
parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
|
|
18564
19171
|
} catch {
|
|
18565
19172
|
return { error: `${cfgPath} is not valid JSON` };
|
|
18566
19173
|
}
|
|
@@ -18601,12 +19208,12 @@ var youTrackToken = () => {
|
|
|
18601
19208
|
if ("error" in loaded)
|
|
18602
19209
|
return loaded;
|
|
18603
19210
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
18604
|
-
const tokenPath = tokenFile ?
|
|
18605
|
-
if (!tokenPath || !
|
|
19211
|
+
const tokenPath = tokenFile ? path19.isAbsolute(tokenFile) ? path19.resolve(tokenFile) : path19.resolve(process.cwd(), tokenFile) : "";
|
|
19212
|
+
if (!tokenPath || !fs12.existsSync(tokenPath))
|
|
18606
19213
|
return { error: "missing youtrack.token" };
|
|
18607
19214
|
if (!youTrackTokenModeOk(tokenPath))
|
|
18608
19215
|
return { error: "youtrack.token mode must be 0600" };
|
|
18609
|
-
const token =
|
|
19216
|
+
const token = fs12.readFileSync(tokenPath, "utf8").trim();
|
|
18610
19217
|
if (!token)
|
|
18611
19218
|
return { error: "empty token file" };
|
|
18612
19219
|
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
@@ -18733,9 +19340,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
18733
19340
|
} catch {
|
|
18734
19341
|
continue;
|
|
18735
19342
|
}
|
|
18736
|
-
if (!
|
|
19343
|
+
if (!fs12.existsSync(full))
|
|
18737
19344
|
continue;
|
|
18738
|
-
const text =
|
|
19345
|
+
const text = fs12.readFileSync(full, "utf8");
|
|
18739
19346
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
18740
19347
|
if (m)
|
|
18741
19348
|
return m[1];
|
|
@@ -19058,14 +19665,14 @@ async function logTimeUpdate(input, operation = defaultOperations) {
|
|
|
19058
19665
|
}
|
|
19059
19666
|
|
|
19060
19667
|
// packages/workit-core/src/core/external-action-effects.ts
|
|
19061
|
-
import
|
|
19062
|
-
import
|
|
19063
|
-
import { spawnSync as
|
|
19668
|
+
import fs14 from "node:fs";
|
|
19669
|
+
import path21 from "node:path";
|
|
19670
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
19064
19671
|
|
|
19065
19672
|
// packages/workit-core/src/core/changelog.ts
|
|
19066
|
-
import
|
|
19067
|
-
import
|
|
19068
|
-
import { createHash as
|
|
19673
|
+
import fs13 from "node:fs";
|
|
19674
|
+
import path20 from "node:path";
|
|
19675
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
19069
19676
|
var CATEGORIES = ["Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"];
|
|
19070
19677
|
var CAT_RE = /^###\s+(?:Added|Changed|Deprecated|Removed|Fixed|Security)\s*$/i;
|
|
19071
19678
|
var UNRELEASED_RE = /^##\s+\[Unreleased\]\s*$/i;
|
|
@@ -19082,14 +19689,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19082
19689
|
## [Unreleased]
|
|
19083
19690
|
|
|
19084
19691
|
`;
|
|
19085
|
-
var digestBytes4 = (value) =>
|
|
19692
|
+
var digestBytes4 = (value) => createHash9("sha256").update(value).digest("hex");
|
|
19086
19693
|
var readChangelogSource = (target) => {
|
|
19087
|
-
if (!
|
|
19694
|
+
if (!fs13.existsSync(target)) {
|
|
19088
19695
|
const bytes = Buffer.from(SKELETON, "utf8");
|
|
19089
19696
|
return { exists: false, bytes, text: SKELETON };
|
|
19090
19697
|
}
|
|
19091
19698
|
try {
|
|
19092
|
-
const bytes =
|
|
19699
|
+
const bytes = fs13.readFileSync(target);
|
|
19093
19700
|
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
19094
19701
|
return { exists: true, bytes, text };
|
|
19095
19702
|
} catch {
|
|
@@ -19254,8 +19861,8 @@ function applyChangelog(target, entries, normalizeOnly) {
|
|
|
19254
19861
|
const rendered = renderChangelog(source.text, entries, normalizeOnly);
|
|
19255
19862
|
if ("error" in rendered)
|
|
19256
19863
|
return { error: rendered.error };
|
|
19257
|
-
|
|
19258
|
-
|
|
19864
|
+
fs13.mkdirSync(path20.dirname(target), { recursive: true });
|
|
19865
|
+
fs13.writeFileSync(target, rendered.text, "utf8");
|
|
19259
19866
|
return { ...rendered.result, path: target };
|
|
19260
19867
|
}
|
|
19261
19868
|
function renderChangelog(source, entries, normalizeOnly) {
|
|
@@ -19288,17 +19895,17 @@ function renderChangelog(source, entries, normalizeOnly) {
|
|
|
19288
19895
|
}
|
|
19289
19896
|
var changelogTarget = (workspaceRoot, changelogPath) => {
|
|
19290
19897
|
try {
|
|
19291
|
-
const cwd =
|
|
19292
|
-
const target =
|
|
19293
|
-
if (target !== cwd && !target.startsWith(cwd +
|
|
19898
|
+
const cwd = fs13.realpathSync(resolveWorkspaceRoot(workspaceRoot));
|
|
19899
|
+
const target = path20.resolve(cwd, changelogPath || "CHANGELOG.md");
|
|
19900
|
+
if (target !== cwd && !target.startsWith(cwd + path20.sep))
|
|
19294
19901
|
return { error: "changelog path must be inside workspace_root" };
|
|
19295
19902
|
let ancestor = target;
|
|
19296
|
-
while (!
|
|
19297
|
-
ancestor =
|
|
19298
|
-
const canonicalAncestor =
|
|
19299
|
-
if (canonicalAncestor !== cwd && !canonicalAncestor.startsWith(cwd +
|
|
19903
|
+
while (!fs13.existsSync(ancestor))
|
|
19904
|
+
ancestor = path20.dirname(ancestor);
|
|
19905
|
+
const canonicalAncestor = fs13.realpathSync(ancestor);
|
|
19906
|
+
if (canonicalAncestor !== cwd && !canonicalAncestor.startsWith(cwd + path20.sep))
|
|
19300
19907
|
return { error: "changelog path must be inside workspace_root" };
|
|
19301
|
-
return { cwd, target:
|
|
19908
|
+
return { cwd, target: fs13.existsSync(target) ? fs13.realpathSync(target) : target };
|
|
19302
19909
|
} catch {
|
|
19303
19910
|
return { error: "changelog path must be inside workspace_root" };
|
|
19304
19911
|
}
|
|
@@ -19453,98 +20060,6 @@ function redactText(value) {
|
|
|
19453
20060
|
`);
|
|
19454
20061
|
}
|
|
19455
20062
|
|
|
19456
|
-
// packages/workit-core/src/git/rev.ts
|
|
19457
|
-
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
19458
|
-
var MAX_BUFFER = 256 * 1024 * 1024;
|
|
19459
|
-
var GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
|
|
19460
|
-
var git = (cwd, args, options = {}) => {
|
|
19461
|
-
const result = spawnSync8("git", args, {
|
|
19462
|
-
cwd,
|
|
19463
|
-
encoding: "utf8",
|
|
19464
|
-
env: options.env ?? process.env,
|
|
19465
|
-
input: options.input,
|
|
19466
|
-
maxBuffer: MAX_BUFFER,
|
|
19467
|
-
timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
|
|
19468
|
-
killSignal: "SIGKILL",
|
|
19469
|
-
windowsHide: true
|
|
19470
|
-
});
|
|
19471
|
-
const error = result.error;
|
|
19472
|
-
return {
|
|
19473
|
-
ok: result.status === 0,
|
|
19474
|
-
stdout: result.stdout ?? "",
|
|
19475
|
-
stderr: result.stderr ?? "",
|
|
19476
|
-
timedOut: error?.code === "ETIMEDOUT"
|
|
19477
|
-
};
|
|
19478
|
-
};
|
|
19479
|
-
var line = (run) => {
|
|
19480
|
-
if (!run.ok)
|
|
19481
|
-
return null;
|
|
19482
|
-
const value = run.stdout.trim();
|
|
19483
|
-
return value ? value : null;
|
|
19484
|
-
};
|
|
19485
|
-
function networkGitInvocation(cwd, args, timeoutMs) {
|
|
19486
|
-
const seconds = Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
19487
|
-
const env = {
|
|
19488
|
-
...process.env,
|
|
19489
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
19490
|
-
GIT_ASKPASS: "",
|
|
19491
|
-
SSH_ASKPASS: "",
|
|
19492
|
-
SSH_ASKPASS_REQUIRE: "never",
|
|
19493
|
-
GCM_INTERACTIVE: "never"
|
|
19494
|
-
};
|
|
19495
|
-
const base = process.env.GIT_SSH_COMMAND || line(git(cwd, ["config", "--get", "core.sshCommand"])) || (process.env.GIT_SSH ? null : "ssh");
|
|
19496
|
-
if (base)
|
|
19497
|
-
env.GIT_SSH_COMMAND = `${base} -o BatchMode=yes -o ConnectTimeout=${Math.min(10, seconds)}`;
|
|
19498
|
-
return {
|
|
19499
|
-
args: ["-c", "http.lowSpeedLimit=1", "-c", `http.lowSpeedTime=${seconds}`, ...args],
|
|
19500
|
-
env
|
|
19501
|
-
};
|
|
19502
|
-
}
|
|
19503
|
-
var WATCHDOG = `
|
|
19504
|
-
const cp = require("node:child_process");
|
|
19505
|
-
const { ms, args } = JSON.parse(process.env.WORKIT_GIT_WATCHDOG);
|
|
19506
|
-
delete process.env.WORKIT_GIT_WATCHDOG;
|
|
19507
|
-
const win = process.platform === "win32";
|
|
19508
|
-
const child = cp.spawn("git", args, { stdio: "inherit", detached: !win, windowsHide: true });
|
|
19509
|
-
const killTree = () => {
|
|
19510
|
-
if (!child.pid) return;
|
|
19511
|
-
if (win) cp.spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
|
|
19512
|
-
else try { process.kill(-child.pid, "SIGKILL"); } catch {}
|
|
19513
|
-
};
|
|
19514
|
-
let timedOut = false;
|
|
19515
|
-
const timer = setTimeout(() => { timedOut = true; killTree(); }, ms);
|
|
19516
|
-
child.on("error", () => { clearTimeout(timer); process.exit(127); });
|
|
19517
|
-
child.on("exit", (code) => {
|
|
19518
|
-
clearTimeout(timer);
|
|
19519
|
-
if (!win) killTree();
|
|
19520
|
-
process.exit(timedOut ? 124 : code ?? 1);
|
|
19521
|
-
});
|
|
19522
|
-
`;
|
|
19523
|
-
var WATCHDOG_TIMEOUT_EXIT = 124;
|
|
19524
|
-
var gitNetwork = (cwd, args, timeoutMs) => {
|
|
19525
|
-
const invocation = networkGitInvocation(cwd, args, timeoutMs);
|
|
19526
|
-
const result = spawnSync8(process.execPath, ["-e", WATCHDOG], {
|
|
19527
|
-
cwd,
|
|
19528
|
-
encoding: "utf8",
|
|
19529
|
-
env: {
|
|
19530
|
-
...invocation.env,
|
|
19531
|
-
WORKIT_GIT_WATCHDOG: JSON.stringify({ ms: timeoutMs, args: invocation.args })
|
|
19532
|
-
},
|
|
19533
|
-
maxBuffer: MAX_BUFFER,
|
|
19534
|
-
timeout: timeoutMs + 1e4,
|
|
19535
|
-
killSignal: "SIGKILL",
|
|
19536
|
-
windowsHide: true
|
|
19537
|
-
});
|
|
19538
|
-
const error = result.error;
|
|
19539
|
-
return {
|
|
19540
|
-
ok: result.status === 0,
|
|
19541
|
-
stdout: result.stdout ?? "",
|
|
19542
|
-
stderr: result.stderr ?? "",
|
|
19543
|
-
timedOut: result.status === WATCHDOG_TIMEOUT_EXIT || error?.code === "ETIMEDOUT"
|
|
19544
|
-
};
|
|
19545
|
-
};
|
|
19546
|
-
var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
|
|
19547
|
-
|
|
19548
20063
|
// packages/workit-core/src/ledger.ts
|
|
19549
20064
|
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
19550
20065
|
var FAILING = new Set(["failed", "blocked"]);
|
|
@@ -19659,7 +20174,7 @@ var credentialFreeRemote = (value) => pushRemoteIdentity(value);
|
|
|
19659
20174
|
var actionRoot = (taskRoot, request) => {
|
|
19660
20175
|
const chosen = "cwd" in request.payload ? request.payload.cwd : undefined;
|
|
19661
20176
|
try {
|
|
19662
|
-
const directory =
|
|
20177
|
+
const directory = fs14.realpathSync(chosen ? path21.resolve(taskRoot, chosen) : taskRoot);
|
|
19663
20178
|
if (request.operation === "context.read")
|
|
19664
20179
|
return success(null, null, directory);
|
|
19665
20180
|
const gitAction = request.operation.startsWith("git.") || request.operation.startsWith("hosting.");
|
|
@@ -19670,7 +20185,7 @@ var actionRoot = (taskRoot, request) => {
|
|
|
19670
20185
|
return failure2("capability_unavailable", "action target is not a Git repository", {
|
|
19671
20186
|
outcome: "not_started"
|
|
19672
20187
|
});
|
|
19673
|
-
return success(null, null,
|
|
20188
|
+
return success(null, null, fs14.realpathSync(checkout));
|
|
19674
20189
|
} catch {
|
|
19675
20190
|
return failure2("invalid_input", "action target directory cannot be resolved", {
|
|
19676
20191
|
outcome: "not_started"
|
|
@@ -20002,7 +20517,7 @@ var hostingProvider = (root, remote) => {
|
|
|
20002
20517
|
};
|
|
20003
20518
|
var remoteBranchTip = (root, provider, host, repo, branch) => {
|
|
20004
20519
|
const endpoint = provider === "github" ? `repos/${repo}/git/ref/heads/${branch.split("/").map(encodeURIComponent).join("/")}` : `projects/${encodeURIComponent(repo)}/repository/branches/${encodeURIComponent(branch)}`;
|
|
20005
|
-
const response =
|
|
20520
|
+
const response = spawnSync10(provider === "github" ? "gh" : "glab", ["api", endpoint], {
|
|
20006
20521
|
cwd: root,
|
|
20007
20522
|
encoding: "utf8",
|
|
20008
20523
|
env: hostingCliEnv(provider, host)
|
|
@@ -20010,7 +20525,7 @@ var remoteBranchTip = (root, provider, host, repo, branch) => {
|
|
|
20010
20525
|
if (response.status !== 0) {
|
|
20011
20526
|
if (/\b404\b/.test(response.stderr ?? "")) {
|
|
20012
20527
|
const project = provider === "github" ? `repos/${repo}` : `projects/${encodeURIComponent(repo)}`;
|
|
20013
|
-
const visible =
|
|
20528
|
+
const visible = spawnSync10(provider === "github" ? "gh" : "glab", ["api", project], {
|
|
20014
20529
|
cwd: root,
|
|
20015
20530
|
encoding: "utf8",
|
|
20016
20531
|
env: hostingCliEnv(provider, host)
|
|
@@ -20066,7 +20581,7 @@ var mergedBranch = (root, branch) => {
|
|
|
20066
20581
|
outcome: "not_started"
|
|
20067
20582
|
});
|
|
20068
20583
|
const endpoint = provider === "github" ? `repos/${repo}/pulls?state=closed&head=${encodeURIComponent(`${repo.split("/")[0]}:${branch}`)}&per_page=100` : `projects/${encodeURIComponent(repo)}/merge_requests?state=merged&source_branch=${encodeURIComponent(branch)}&per_page=100`;
|
|
20069
|
-
const response =
|
|
20584
|
+
const response = spawnSync10(provider === "github" ? "gh" : "glab", ["api", endpoint], {
|
|
20070
20585
|
cwd: root,
|
|
20071
20586
|
encoding: "utf8",
|
|
20072
20587
|
env: hostingCliEnv(provider, auth.host)
|
|
@@ -20128,7 +20643,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
|
|
|
20128
20643
|
return unknownHostingEvidence(operation);
|
|
20129
20644
|
const endpoint = cfg.provider === "github" ? `repos/${repo}/pulls?state=all&head=${encodeURIComponent(`${repo.split("/")[0]}:${branch}`)}&base=${encodeURIComponent(target)}&per_page=100&page=1` : `projects/${encodeURIComponent(repo)}/merge_requests?state=all&source_branch=${encodeURIComponent(branch)}&target_branch=${encodeURIComponent(target)}&per_page=100&page=1`;
|
|
20130
20645
|
const cli = cfg.provider === "github" ? "gh" : "glab";
|
|
20131
|
-
const response =
|
|
20646
|
+
const response = spawnSync10(cli, ["api", endpoint], {
|
|
20132
20647
|
cwd: root,
|
|
20133
20648
|
encoding: "utf8",
|
|
20134
20649
|
env: hostingCliEnv(cfg.provider, auth.host)
|
|
@@ -20320,7 +20835,7 @@ var readLocalAction = async (root, resolved, actionRef) => {
|
|
|
20320
20835
|
const sdd = resolved.request.payload.sdd_dir;
|
|
20321
20836
|
if (resolved.request.payload.action === "reapply_stash" && typeof sdd === "string") {
|
|
20322
20837
|
try {
|
|
20323
|
-
const manifest = JSON.parse(
|
|
20838
|
+
const manifest = JSON.parse(fs14.readFileSync(path21.join(sdd, "manifest.json"), "utf8"));
|
|
20324
20839
|
const stashes = gitRaw(root, ["stash", "list", "--format=%H"]);
|
|
20325
20840
|
if (typeof target.stash_commit === "string" && manifest.stash_ref === undefined && stashes !== null && !stashes.split(`
|
|
20326
20841
|
`).includes(target.stash_commit))
|
|
@@ -20328,7 +20843,7 @@ var readLocalAction = async (root, resolved, actionRef) => {
|
|
|
20328
20843
|
} catch {}
|
|
20329
20844
|
} else if (typeof targetBranch === "string" && typeof sdd === "string" && gitValue(root, ["branch", "--show-current"]) === targetBranch && typeof target.target_head === "string" && gitValue(root, ["rev-parse", "HEAD"]) === target.target_head) {
|
|
20330
20845
|
try {
|
|
20331
|
-
const manifest = JSON.parse(
|
|
20846
|
+
const manifest = JSON.parse(fs14.readFileSync(path21.join(sdd, "manifest.json"), "utf8"));
|
|
20332
20847
|
if (manifest.branch === targetBranch)
|
|
20333
20848
|
observed = { branch: targetBranch };
|
|
20334
20849
|
} catch {}
|
|
@@ -20381,7 +20896,7 @@ var readDeletedBranch = async (root, resolved, actionRef) => {
|
|
|
20381
20896
|
};
|
|
20382
20897
|
var youTrackBase = () => {
|
|
20383
20898
|
try {
|
|
20384
|
-
const config = JSON.parse(
|
|
20899
|
+
const config = JSON.parse(fs14.readFileSync(youTrackConfigPath(), "utf8"));
|
|
20385
20900
|
const value = typeof config.baseUrl === "string" ? config.baseUrl.trim() : "";
|
|
20386
20901
|
if (!value || /[\s#?]/.test(value))
|
|
20387
20902
|
return null;
|
|
@@ -20449,7 +20964,7 @@ var resolveExternalActionAtRoot = (root, request, preservedDateMs) => {
|
|
|
20449
20964
|
let stashTarget;
|
|
20450
20965
|
if (action === "reapply_stash") {
|
|
20451
20966
|
try {
|
|
20452
|
-
const manifest = JSON.parse(
|
|
20967
|
+
const manifest = JSON.parse(fs14.readFileSync(path21.join(sdd_dir, "manifest.json"), "utf8"));
|
|
20453
20968
|
const stash_ref = typeof manifest.stash_ref === "string" ? manifest.stash_ref : "";
|
|
20454
20969
|
const stash_commit = stash_ref ? gitValue(root, ["rev-parse", stash_ref]) : null;
|
|
20455
20970
|
if (stash_ref && stash_commit)
|
|
@@ -20724,7 +21239,7 @@ var resolveExternalActionAtRoot = (root, request, preservedDateMs) => {
|
|
|
20724
21239
|
if (preview.error)
|
|
20725
21240
|
return failure2("invalid_input", preview.error);
|
|
20726
21241
|
const target = String(preview.path);
|
|
20727
|
-
const relative =
|
|
21242
|
+
const relative = path21.relative(fs14.realpathSync(root), target).replaceAll(path21.sep, "/") || "CHANGELOG.md";
|
|
20728
21243
|
const normalized = {
|
|
20729
21244
|
operation: request.operation,
|
|
20730
21245
|
payload: {
|
|
@@ -21566,9 +22081,9 @@ import { randomUUID as randomUUID3 } from "node:crypto";
|
|
|
21566
22081
|
// packages/workit-pi/src/worker-protocol.ts
|
|
21567
22082
|
var MAX_WORKER_LINE_BYTES = 64 * 1024;
|
|
21568
22083
|
var MAX_WORKER_STDERR_BYTES = 64 * 1024;
|
|
21569
|
-
var
|
|
22084
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null;
|
|
21570
22085
|
var parseEvent = (value) => {
|
|
21571
|
-
if (!
|
|
22086
|
+
if (!isRecord4(value) || typeof value.type !== "string")
|
|
21572
22087
|
return null;
|
|
21573
22088
|
if (value.type === "workit_worker_ready") {
|
|
21574
22089
|
if (typeof value.workerId !== "string" || typeof value.sessionId !== "string")
|