@brainervirus/workit-mcp 2.1.1 → 2.1.3
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/index.js +1302 -1072
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -21582,6 +21582,45 @@ var metadataLockSchema = object4({
|
|
|
21582
21582
|
nonce: string5().min(1),
|
|
21583
21583
|
externalAction: literal3(true).optional()
|
|
21584
21584
|
}).strict();
|
|
21585
|
+
var INDEX_VERSION = 1;
|
|
21586
|
+
var hostSession = (value) => isObject3(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
|
|
21587
|
+
var indexEntry = (task, file) => {
|
|
21588
|
+
const sessions = [];
|
|
21589
|
+
const intent = hostSession(task.intent.provenance.session);
|
|
21590
|
+
if (intent)
|
|
21591
|
+
sessions.push({ ...intent, workerId: null });
|
|
21592
|
+
for (const worker of task.workers) {
|
|
21593
|
+
const session = hostSession(worker.data.session);
|
|
21594
|
+
if (session)
|
|
21595
|
+
sessions.push({ ...session, workerId: worker.id });
|
|
21596
|
+
}
|
|
21597
|
+
return {
|
|
21598
|
+
id: task.id,
|
|
21599
|
+
revision: task.revision,
|
|
21600
|
+
status: task.status,
|
|
21601
|
+
createdAt: task.createdAt,
|
|
21602
|
+
updatedAt: task.updatedAt,
|
|
21603
|
+
objective: task.intent.data.objective,
|
|
21604
|
+
source: { host: task.intent.provenance.host, kind: task.intent.provenance.kind },
|
|
21605
|
+
progress: { summary: task.progress.summary, nextAction: task.progress.nextAction },
|
|
21606
|
+
sessions,
|
|
21607
|
+
file
|
|
21608
|
+
};
|
|
21609
|
+
};
|
|
21610
|
+
var fileSignature = (file) => {
|
|
21611
|
+
try {
|
|
21612
|
+
const stat = fs4.statSync(file, { bigint: true });
|
|
21613
|
+
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
21614
|
+
} catch {
|
|
21615
|
+
return null;
|
|
21616
|
+
}
|
|
21617
|
+
};
|
|
21618
|
+
var RACY_WINDOW_NS = 2000000000n;
|
|
21619
|
+
var racySignature = (signature) => {
|
|
21620
|
+
const [mtime = 0n, ctime = 0n] = signature.split(":").slice(3, 5).map(BigInt);
|
|
21621
|
+
const changed = mtime > ctime ? mtime : ctime;
|
|
21622
|
+
return BigInt(Date.now()) * 1000000n - changed < RACY_WINDOW_NS;
|
|
21623
|
+
};
|
|
21585
21624
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
21586
21625
|
var cachedRuntimeVersion = null;
|
|
21587
21626
|
var runtimeVersion = () => {
|
|
@@ -21710,6 +21749,57 @@ class TaskStore {
|
|
|
21710
21749
|
}
|
|
21711
21750
|
return success(null, null, tasks);
|
|
21712
21751
|
}
|
|
21752
|
+
listTaskIndex() {
|
|
21753
|
+
if (!fs4.existsSync(this.tasksDir))
|
|
21754
|
+
return success(null, null, []);
|
|
21755
|
+
let names;
|
|
21756
|
+
try {
|
|
21757
|
+
names = fs4.readdirSync(this.tasksDir).filter((name) => name.endsWith(".json"));
|
|
21758
|
+
} catch (error) {
|
|
21759
|
+
return failure("storage_error", `unable to list tasks: ${String(error)}`, {
|
|
21760
|
+
path: this.tasksDir
|
|
21761
|
+
});
|
|
21762
|
+
}
|
|
21763
|
+
const workspace = this.readWorkspace();
|
|
21764
|
+
if (!workspace.ok)
|
|
21765
|
+
return workspace;
|
|
21766
|
+
if (!workspace.data)
|
|
21767
|
+
return failure("recovery_required", "task workspace binding is invalid", {
|
|
21768
|
+
path: this.workspacePath
|
|
21769
|
+
});
|
|
21770
|
+
const stored = this.readIndex(workspace.data.id);
|
|
21771
|
+
const entries = [];
|
|
21772
|
+
let changed = Object.keys(stored).length !== names.length;
|
|
21773
|
+
for (const name of names.sort()) {
|
|
21774
|
+
const file = path4.join(this.tasksDir, name);
|
|
21775
|
+
const signature = fileSignature(file);
|
|
21776
|
+
if (signature === null) {
|
|
21777
|
+
changed = true;
|
|
21778
|
+
continue;
|
|
21779
|
+
}
|
|
21780
|
+
const cached = stored[name.slice(0, -5)];
|
|
21781
|
+
if (cached && cached.file === signature && !racySignature(signature)) {
|
|
21782
|
+
entries.push(cached);
|
|
21783
|
+
continue;
|
|
21784
|
+
}
|
|
21785
|
+
const item = this.readRecord(file, taskRecordSchema);
|
|
21786
|
+
if (!item.exists)
|
|
21787
|
+
continue;
|
|
21788
|
+
if (!item.result.ok)
|
|
21789
|
+
return item.result;
|
|
21790
|
+
if (!validId(name.slice(0, -5)) || item.result.data.id !== name.slice(0, -5))
|
|
21791
|
+
return failure("recovery_required", "task filename and record ID differ", { path: name });
|
|
21792
|
+
if (item.result.data.workspaceId !== workspace.data.id)
|
|
21793
|
+
return failure("recovery_required", "task workspace binding is invalid", { path: name });
|
|
21794
|
+
const entry = indexEntry(item.result.data, signature);
|
|
21795
|
+
if (!cached || canonicalJson(cached) !== canonicalJson(entry))
|
|
21796
|
+
changed = true;
|
|
21797
|
+
entries.push(entry);
|
|
21798
|
+
}
|
|
21799
|
+
if (changed)
|
|
21800
|
+
this.writeIndex(workspace.data.id, entries);
|
|
21801
|
+
return success(null, null, entries);
|
|
21802
|
+
}
|
|
21713
21803
|
readWorkspace() {
|
|
21714
21804
|
const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
|
|
21715
21805
|
if (!item.exists)
|
|
@@ -22389,6 +22479,8 @@ class TaskStore {
|
|
|
22389
22479
|
fs4.renameSync(temporary, file);
|
|
22390
22480
|
temporary = undefined;
|
|
22391
22481
|
this.fsyncDirectory(path4.dirname(file));
|
|
22482
|
+
if (path4.dirname(file) === this.tasksDir)
|
|
22483
|
+
this.indexTaskWrite(file, value);
|
|
22392
22484
|
return success(null, null, value);
|
|
22393
22485
|
} catch (error) {
|
|
22394
22486
|
return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
|
|
@@ -22401,6 +22493,45 @@ class TaskStore {
|
|
|
22401
22493
|
} catch {}
|
|
22402
22494
|
}
|
|
22403
22495
|
}
|
|
22496
|
+
readIndex(workspaceId) {
|
|
22497
|
+
try {
|
|
22498
|
+
const value = JSON.parse(fs4.readFileSync(this.indexPath, "utf8"));
|
|
22499
|
+
if (!isObject3(value) || value.version !== INDEX_VERSION || value.workspaceId !== workspaceId || !isObject3(value.tasks))
|
|
22500
|
+
return {};
|
|
22501
|
+
const tasks = {};
|
|
22502
|
+
for (const [id, entry] of Object.entries(value.tasks))
|
|
22503
|
+
if (isObject3(entry) && entry.id === id && typeof entry.file === "string" && typeof entry.status === "string" && typeof entry.updatedAt === "string" && Array.isArray(entry.sessions) && isObject3(entry.progress) && isObject3(entry.source))
|
|
22504
|
+
tasks[id] = entry;
|
|
22505
|
+
return tasks;
|
|
22506
|
+
} catch {
|
|
22507
|
+
return {};
|
|
22508
|
+
}
|
|
22509
|
+
}
|
|
22510
|
+
writeIndex(workspaceId, entries) {
|
|
22511
|
+
const temporary = `${this.indexPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
22512
|
+
try {
|
|
22513
|
+
fs4.writeFileSync(temporary, JSON.stringify({
|
|
22514
|
+
version: INDEX_VERSION,
|
|
22515
|
+
workspaceId,
|
|
22516
|
+
tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
|
|
22517
|
+
}), { mode: 384, flag: "wx" });
|
|
22518
|
+
fs4.renameSync(temporary, this.indexPath);
|
|
22519
|
+
} catch {
|
|
22520
|
+
try {
|
|
22521
|
+
fs4.unlinkSync(temporary);
|
|
22522
|
+
} catch {}
|
|
22523
|
+
}
|
|
22524
|
+
}
|
|
22525
|
+
indexTaskWrite(file, task) {
|
|
22526
|
+
try {
|
|
22527
|
+
const signature = fileSignature(file);
|
|
22528
|
+
if (signature === null)
|
|
22529
|
+
return;
|
|
22530
|
+
const tasks = this.readIndex(task.workspaceId);
|
|
22531
|
+
tasks[task.id] = indexEntry(task, signature);
|
|
22532
|
+
this.writeIndex(task.workspaceId, Object.values(tasks));
|
|
22533
|
+
} catch {}
|
|
22534
|
+
}
|
|
22404
22535
|
saveRecovery(file, bytes) {
|
|
22405
22536
|
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
22406
22537
|
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
@@ -22559,6 +22690,9 @@ class TaskStore {
|
|
|
22559
22690
|
get workspacePath() {
|
|
22560
22691
|
return path4.join(this.workitDir, "workspace.json");
|
|
22561
22692
|
}
|
|
22693
|
+
get indexPath() {
|
|
22694
|
+
return path4.join(this.workitDir, "index.json");
|
|
22695
|
+
}
|
|
22562
22696
|
get lockPath() {
|
|
22563
22697
|
return path4.join(this.workitDir, "metadata.lock");
|
|
22564
22698
|
}
|
|
@@ -22566,71 +22700,8 @@ class TaskStore {
|
|
|
22566
22700
|
return path4.join(this.workitDir, ".gitignore");
|
|
22567
22701
|
}
|
|
22568
22702
|
}
|
|
22569
|
-
// packages/workit-core/src/core/
|
|
22570
|
-
|
|
22571
|
-
try {
|
|
22572
|
-
return canonicalJson(left) === canonicalJson(right);
|
|
22573
|
-
} catch {
|
|
22574
|
-
return false;
|
|
22575
|
-
}
|
|
22576
|
-
};
|
|
22577
|
-
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
22578
|
-
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
22579
|
-
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
22580
|
-
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
22581
|
-
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
22582
|
-
const code = char.charCodeAt(0);
|
|
22583
|
-
return code < 32 || code === 127;
|
|
22584
|
-
}) && !value.split(/[\\/]/).includes("..");
|
|
22585
|
-
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
22586
|
-
var workerIdOf = (value) => value.workerId ?? null;
|
|
22587
|
-
var currentWriterOwnsTask = (task, workspace, caller) => {
|
|
22588
|
-
const owner = workspace.writer?.owner;
|
|
22589
|
-
return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
|
|
22590
|
-
};
|
|
22591
|
-
function assertProductWriteAllowed(input) {
|
|
22592
|
-
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
22593
|
-
return failure("invalid_input", "invalid product write path");
|
|
22594
|
-
if (!input.store)
|
|
22595
|
-
return failure("permission_denied", "write authorization requires core state");
|
|
22596
|
-
const task = input.store.readTask(input.task.id);
|
|
22597
|
-
if (!task.ok)
|
|
22598
|
-
return task;
|
|
22599
|
-
const workspace = input.store.readWorkspace();
|
|
22600
|
-
if (!workspace.ok)
|
|
22601
|
-
return workspace;
|
|
22602
|
-
if (!workspace.data)
|
|
22603
|
-
return failure("not_found", "workspace not found");
|
|
22604
|
-
const workspaceRecord = workspace.data;
|
|
22605
|
-
if (task.data.status !== "active")
|
|
22606
|
-
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
22607
|
-
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
22608
|
-
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
22609
|
-
const writer = workspaceRecord.writer;
|
|
22610
|
-
if (!writer)
|
|
22611
|
-
return failure("permission_denied", "checkout has no writer owner");
|
|
22612
|
-
if (writer.state === "uncertain")
|
|
22613
|
-
return failure("recovery_required", "checkout writer requires recovery", {
|
|
22614
|
-
owner: writer.owner
|
|
22615
|
-
});
|
|
22616
|
-
const caller = callerValue(input.caller);
|
|
22617
|
-
const workerId = workerIdOf(input.caller);
|
|
22618
|
-
const owner = writer.owner;
|
|
22619
|
-
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
22620
|
-
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
22621
|
-
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
22622
|
-
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
22623
|
-
return failure("permission_denied", "only implementers can own product writes");
|
|
22624
|
-
if (workerId !== null && (!worker?.data.session || worker.data.state === "cancelling" || worker.data.state === "unknown" || worker.data.state === "stopped" || !same(worker.data.session, callerSession(input.caller))))
|
|
22625
|
-
return failure("recovery_required", "worker session is not an active writer");
|
|
22626
|
-
return success(null, workspaceRecord.revision, owner);
|
|
22627
|
-
}
|
|
22628
|
-
|
|
22629
|
-
// packages/workit-core/src/core/task-evaluation.ts
|
|
22630
|
-
import { createHash as createHash6 } from "node:crypto";
|
|
22631
|
-
import * as fs7 from "node:fs";
|
|
22632
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
22633
|
-
import path10 from "node:path";
|
|
22703
|
+
// packages/workit-core/src/core/task-engine.ts
|
|
22704
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
22634
22705
|
|
|
22635
22706
|
// packages/workit-core/src/core/authority.ts
|
|
22636
22707
|
import { createHash as createHash5 } from "node:crypto";
|
|
@@ -22764,7 +22835,6 @@ var LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
|
|
|
22764
22835
|
var DEFAULTS = {
|
|
22765
22836
|
locale: "en",
|
|
22766
22837
|
localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
|
|
22767
|
-
timezone: "America/Santiago",
|
|
22768
22838
|
branchPolicy: {
|
|
22769
22839
|
preset: "gitflow",
|
|
22770
22840
|
allowed: [...PRESETS.gitflow.allowed],
|
|
@@ -22802,7 +22872,6 @@ var parseConfigResult = (raw, file) => {
|
|
|
22802
22872
|
config: {
|
|
22803
22873
|
locale,
|
|
22804
22874
|
localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
|
|
22805
|
-
timezone: input.timezone ?? DEFAULTS.timezone,
|
|
22806
22875
|
branchPolicy: mergePreset(preset, {
|
|
22807
22876
|
allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
|
|
22808
22877
|
protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
|
|
@@ -23637,6 +23706,66 @@ var verifyPushIdentity = (root, provider, account) => {
|
|
|
23637
23706
|
return { ok: true };
|
|
23638
23707
|
};
|
|
23639
23708
|
|
|
23709
|
+
// packages/workit-core/src/core/workers.ts
|
|
23710
|
+
var same = (left, right) => {
|
|
23711
|
+
try {
|
|
23712
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
23713
|
+
} catch {
|
|
23714
|
+
return false;
|
|
23715
|
+
}
|
|
23716
|
+
};
|
|
23717
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
23718
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
23719
|
+
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
23720
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
23721
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
23722
|
+
const code = char.charCodeAt(0);
|
|
23723
|
+
return code < 32 || code === 127;
|
|
23724
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
23725
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
23726
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
23727
|
+
var currentWriterOwnsTask = (task, workspace, caller) => {
|
|
23728
|
+
const owner = workspace.writer?.owner;
|
|
23729
|
+
return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
|
|
23730
|
+
};
|
|
23731
|
+
function assertProductWriteAllowed(input) {
|
|
23732
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
23733
|
+
return failure("invalid_input", "invalid product write path");
|
|
23734
|
+
if (!input.store)
|
|
23735
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
23736
|
+
const task = input.store.readTask(input.task.id);
|
|
23737
|
+
if (!task.ok)
|
|
23738
|
+
return task;
|
|
23739
|
+
const workspace = input.store.readWorkspace();
|
|
23740
|
+
if (!workspace.ok)
|
|
23741
|
+
return workspace;
|
|
23742
|
+
if (!workspace.data)
|
|
23743
|
+
return failure("not_found", "workspace not found");
|
|
23744
|
+
const workspaceRecord = workspace.data;
|
|
23745
|
+
if (task.data.status !== "active")
|
|
23746
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
23747
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
23748
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
23749
|
+
const writer = workspaceRecord.writer;
|
|
23750
|
+
if (!writer)
|
|
23751
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
23752
|
+
if (writer.state === "uncertain")
|
|
23753
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
23754
|
+
owner: writer.owner
|
|
23755
|
+
});
|
|
23756
|
+
const caller = callerValue(input.caller);
|
|
23757
|
+
const workerId = workerIdOf(input.caller);
|
|
23758
|
+
const owner = writer.owner;
|
|
23759
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
23760
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
23761
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
23762
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
23763
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
23764
|
+
if (workerId !== null && (!worker?.data.session || worker.data.state === "cancelling" || worker.data.state === "unknown" || worker.data.state === "stopped" || !same(worker.data.session, callerSession(input.caller))))
|
|
23765
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
23766
|
+
return success(null, workspaceRecord.revision, owner);
|
|
23767
|
+
}
|
|
23768
|
+
|
|
23640
23769
|
// packages/workit-core/src/core/auto-approval.ts
|
|
23641
23770
|
var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
|
|
23642
23771
|
var OPERATION_CLASS = {
|
|
@@ -24327,41 +24456,18 @@ function reconcileAction(input) {
|
|
|
24327
24456
|
var bindingCovers = scopeCovers;
|
|
24328
24457
|
var verifyDecisionContent = verifyContentRefs;
|
|
24329
24458
|
|
|
24330
|
-
// packages/workit-core/src/core/
|
|
24331
|
-
var
|
|
24332
|
-
|
|
24333
|
-
|
|
24334
|
-
|
|
24335
|
-
|
|
24336
|
-
|
|
24337
|
-
|
|
24338
|
-
|
|
24339
|
-
|
|
24340
|
-
|
|
24341
|
-
|
|
24342
|
-
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
24343
|
-
};
|
|
24344
|
-
var canonicalScope = (scope) => {
|
|
24345
|
-
const paths = scope.paths.length ? scope.paths : ["."];
|
|
24346
|
-
const normalizedPaths = paths.map(canonicalPath);
|
|
24347
|
-
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
24348
|
-
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
24349
|
-
return null;
|
|
24350
|
-
return {
|
|
24351
|
-
description: scope.description,
|
|
24352
|
-
paths: [...new Set(normalizedPaths)],
|
|
24353
|
-
exclusions: [...new Set(normalizedExclusions)]
|
|
24354
|
-
};
|
|
24355
|
-
};
|
|
24356
|
-
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
24357
|
-
var scopeMatches = (value, scope) => {
|
|
24358
|
-
const normalized = canonicalScope(scope);
|
|
24359
|
-
if (!normalized)
|
|
24360
|
-
return false;
|
|
24361
|
-
const paths = normalized.paths;
|
|
24362
|
-
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
24363
|
-
};
|
|
24364
|
-
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
24459
|
+
// packages/workit-core/src/core/policy-resolver.ts
|
|
24460
|
+
var resolverInputSchema = object4({
|
|
24461
|
+
intent: intentSchema,
|
|
24462
|
+
assessment: assessmentSchema,
|
|
24463
|
+
constraints: array3(constraintSchema),
|
|
24464
|
+
prior: object4({
|
|
24465
|
+
decisions: array3(decisionSchema),
|
|
24466
|
+
findings: array3(findingSchema),
|
|
24467
|
+
requirements: array3(requirementSchema)
|
|
24468
|
+
}).strict(),
|
|
24469
|
+
preferences: object4({ fast: boolean5().optional(), thorough: boolean5().optional() }).strict().optional()
|
|
24470
|
+
}).strict();
|
|
24365
24471
|
var compareCodeUnits2 = (left, right) => {
|
|
24366
24472
|
const length = Math.min(left.length, right.length);
|
|
24367
24473
|
for (let index = 0;index < length; index += 1) {
|
|
@@ -24371,927 +24477,970 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
24371
24477
|
}
|
|
24372
24478
|
return left.length - right.length;
|
|
24373
24479
|
};
|
|
24374
|
-
var
|
|
24375
|
-
|
|
24376
|
-
|
|
24377
|
-
|
|
24378
|
-
|
|
24379
|
-
|
|
24380
|
-
|
|
24381
|
-
|
|
24382
|
-
|
|
24383
|
-
|
|
24384
|
-
|
|
24385
|
-
};
|
|
24386
|
-
}
|
|
24387
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
|
|
24388
|
-
const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
24389
|
-
if (stagedDeleted.status !== 0) {
|
|
24390
|
-
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
24391
|
-
if (!/not a git repository/i.test(stderr))
|
|
24392
|
-
return { paths, uncertain: true, git: true };
|
|
24393
|
-
} else if (stagedDeleted.stdout) {
|
|
24394
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
|
|
24395
|
-
}
|
|
24396
|
-
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
24397
|
-
};
|
|
24398
|
-
var walk = (root, directory, output) => {
|
|
24399
|
-
let names;
|
|
24400
|
-
try {
|
|
24401
|
-
names = fs7.readdirSync(directory);
|
|
24402
|
-
} catch {
|
|
24403
|
-
return true;
|
|
24404
|
-
}
|
|
24405
|
-
let uncertain = false;
|
|
24406
|
-
for (const name of names) {
|
|
24407
|
-
if (name === ".git" || name === ".workit")
|
|
24408
|
-
continue;
|
|
24409
|
-
const target = path10.join(directory, name);
|
|
24410
|
-
let stat;
|
|
24411
|
-
try {
|
|
24412
|
-
stat = fs7.lstatSync(target);
|
|
24413
|
-
} catch {
|
|
24414
|
-
uncertain = true;
|
|
24415
|
-
continue;
|
|
24416
|
-
}
|
|
24417
|
-
const item = relative(root, target);
|
|
24418
|
-
output.add(item);
|
|
24419
|
-
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
24420
|
-
uncertain = walk(root, target, output) || uncertain;
|
|
24421
|
-
}
|
|
24422
|
-
return uncertain;
|
|
24423
|
-
};
|
|
24424
|
-
var scopeRoots = (root, scope) => {
|
|
24425
|
-
const roots = [];
|
|
24426
|
-
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
24427
|
-
const target = path10.resolve(root, item);
|
|
24428
|
-
if (!inside(root, target))
|
|
24429
|
-
return failure("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` }] });
|
|
24430
|
-
let ancestor = target;
|
|
24431
|
-
while (!fs7.existsSync(ancestor) && ancestor !== root)
|
|
24432
|
-
ancestor = path10.dirname(ancestor);
|
|
24433
|
-
try {
|
|
24434
|
-
if (!inside(root, fs7.realpathSync(ancestor)))
|
|
24435
|
-
return failure("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` }] });
|
|
24436
|
-
} catch {
|
|
24437
|
-
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
24438
|
-
}
|
|
24439
|
-
roots.push(target);
|
|
24440
|
-
}
|
|
24441
|
-
return success(null, null, roots);
|
|
24442
|
-
};
|
|
24443
|
-
function captureCandidate(root, scope, environment = []) {
|
|
24444
|
-
let checkout;
|
|
24445
|
-
try {
|
|
24446
|
-
checkout = fs7.realpathSync(root);
|
|
24447
|
-
} catch {
|
|
24448
|
-
return failure("invalid_input", "candidate root does not exist");
|
|
24449
|
-
}
|
|
24450
|
-
const normalizedScope = canonicalScope(scope);
|
|
24451
|
-
if (!normalizedScope)
|
|
24452
|
-
return failure("invalid_input", "candidate scope is invalid: paths must be checkout-relative — to work in another repository, keep this task here and coordinate a separate linked task in that checkout", { fields: [{ path: "scope.paths", reason: "must be checkout-relative" }] });
|
|
24453
|
-
const roots = scopeRoots(checkout, normalizedScope);
|
|
24454
|
-
if (!roots.ok)
|
|
24455
|
-
return roots;
|
|
24456
|
-
const git = gitPaths(checkout);
|
|
24457
|
-
const names = new Set;
|
|
24458
|
-
let uncertain = git.uncertain;
|
|
24459
|
-
for (const target of roots.data) {
|
|
24460
|
-
const item = relative(checkout, target);
|
|
24461
|
-
if (item === ".git" || item === ".workit")
|
|
24462
|
-
continue;
|
|
24463
|
-
names.add(item);
|
|
24464
|
-
if (!fs7.existsSync(target))
|
|
24465
|
-
continue;
|
|
24466
|
-
let stat;
|
|
24467
|
-
try {
|
|
24468
|
-
stat = fs7.lstatSync(target);
|
|
24469
|
-
} catch {
|
|
24470
|
-
uncertain = true;
|
|
24471
|
-
continue;
|
|
24472
|
-
}
|
|
24473
|
-
if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
|
|
24474
|
-
uncertain = walk(checkout, target, names) || uncertain;
|
|
24475
|
-
}
|
|
24476
|
-
for (const item of git.paths)
|
|
24477
|
-
if (scopeMatches(item, normalizedScope))
|
|
24478
|
-
names.add(item);
|
|
24479
|
-
const files = [];
|
|
24480
|
-
let completeness = uncertain ? "uncertain" : "known";
|
|
24481
|
-
for (const item of [...names].sort()) {
|
|
24482
|
-
if (!scopeMatches(item, normalizedScope))
|
|
24483
|
-
continue;
|
|
24484
|
-
const target = path10.join(checkout, item);
|
|
24485
|
-
try {
|
|
24486
|
-
const stat = fs7.lstatSync(target);
|
|
24487
|
-
if (stat.isSymbolicLink()) {
|
|
24488
|
-
const link = fs7.readlinkSync(target);
|
|
24489
|
-
files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
|
|
24490
|
-
} else if (stat.isFile()) {
|
|
24491
|
-
files.push({
|
|
24492
|
-
path: item,
|
|
24493
|
-
kind: "file",
|
|
24494
|
-
digest: digestBytes3(fs7.readFileSync(target)),
|
|
24495
|
-
executable: (stat.mode & 73) !== 0
|
|
24496
|
-
});
|
|
24497
|
-
} else if (!stat.isDirectory()) {
|
|
24498
|
-
completeness = "uncertain";
|
|
24499
|
-
} else if (git.git && git.paths.includes(item)) {
|
|
24500
|
-
completeness = "uncertain";
|
|
24501
|
-
}
|
|
24502
|
-
} catch (error) {
|
|
24503
|
-
if (error?.code === "ENOENT") {
|
|
24504
|
-
files.push({ path: item, kind: "absent", digest: null, executable: null });
|
|
24505
|
-
} else
|
|
24506
|
-
completeness = "uncertain";
|
|
24507
|
-
}
|
|
24508
|
-
}
|
|
24509
|
-
const supplied = Array.isArray(environment) ? environment.map((value) => typeof value === "string" ? { name: value, value: process.env[value] ?? null } : { name: value.name, value: value.value ?? null }) : Object.keys(environment).map((name) => ({
|
|
24480
|
+
var stableUnique = (values) => [...new Set(values)].sort(compareCodeUnits2);
|
|
24481
|
+
var stableList = (values) => [...values].sort((left, right) => compareCodeUnits2(canonicalJson(left), canonicalJson(right)));
|
|
24482
|
+
var normalizedScope = (scope) => ({
|
|
24483
|
+
description: scope.description,
|
|
24484
|
+
paths: stableUnique(scope.paths),
|
|
24485
|
+
exclusions: stableUnique(scope.exclusions)
|
|
24486
|
+
});
|
|
24487
|
+
var normalizedRefList = (values) => stableList(values);
|
|
24488
|
+
var normalizeAssessment = (assessment) => ({
|
|
24489
|
+
facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
|
|
24490
|
+
signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
|
|
24510
24491
|
name,
|
|
24511
|
-
|
|
24512
|
-
|
|
24513
|
-
|
|
24514
|
-
|
|
24515
|
-
|
|
24516
|
-
|
|
24517
|
-
|
|
24518
|
-
|
|
24519
|
-
|
|
24520
|
-
|
|
24521
|
-
|
|
24522
|
-
|
|
24523
|
-
|
|
24524
|
-
|
|
24525
|
-
|
|
24526
|
-
|
|
24527
|
-
|
|
24528
|
-
|
|
24529
|
-
|
|
24492
|
+
{ ...signal, refs: normalizedRefList(signal.refs) }
|
|
24493
|
+
])),
|
|
24494
|
+
consequences: stableList(assessment.consequences.map((consequence) => ({
|
|
24495
|
+
...consequence,
|
|
24496
|
+
fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
|
|
24497
|
+
}))),
|
|
24498
|
+
verification: stableList(assessment.verification.map((verification) => ({
|
|
24499
|
+
...verification,
|
|
24500
|
+
scope: normalizedScope(verification.scope),
|
|
24501
|
+
availableChecks: normalizedRefList(verification.availableChecks),
|
|
24502
|
+
gaps: stableUnique(verification.gaps)
|
|
24503
|
+
})))
|
|
24504
|
+
});
|
|
24505
|
+
var normalizeConstraint = (constraint) => ({
|
|
24506
|
+
...constraint,
|
|
24507
|
+
source: constraint.source,
|
|
24508
|
+
requires: stableList(constraint.requires.map((requirement) => ({
|
|
24509
|
+
...requirement,
|
|
24510
|
+
scope: normalizedScope(requirement.scope),
|
|
24511
|
+
refs: normalizedRefList(requirement.refs)
|
|
24512
|
+
})))
|
|
24513
|
+
});
|
|
24514
|
+
var normalizeDecision = (decision) => ({
|
|
24515
|
+
...decision,
|
|
24516
|
+
binding: {
|
|
24517
|
+
...decision.binding,
|
|
24518
|
+
scope: normalizedScope(decision.binding.scope),
|
|
24519
|
+
contentRefs: normalizedRefList(decision.binding.contentRefs)
|
|
24520
|
+
},
|
|
24521
|
+
requirementIds: stableUnique(decision.requirementIds)
|
|
24522
|
+
});
|
|
24523
|
+
var normalizeIntent = (intent) => ({
|
|
24524
|
+
...intent,
|
|
24525
|
+
scope: normalizedScope(intent.scope),
|
|
24526
|
+
authorityRefs: normalizedRefList(intent.authorityRefs)
|
|
24527
|
+
});
|
|
24528
|
+
var normalizePrior = (prior) => ({
|
|
24529
|
+
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
24530
|
+
findings: stableList(prior.findings.map((finding) => ({
|
|
24531
|
+
...finding,
|
|
24532
|
+
scope: normalizedScope(finding.scope),
|
|
24533
|
+
refs: normalizedRefList(finding.refs),
|
|
24534
|
+
resolution: finding.resolution ? {
|
|
24535
|
+
...finding.resolution,
|
|
24536
|
+
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
24537
|
+
decisionIds: stableUnique(finding.resolution.decisionIds)
|
|
24538
|
+
} : null
|
|
24539
|
+
}))),
|
|
24540
|
+
requirements: stableList(prior.requirements.map((requirement) => ({
|
|
24541
|
+
...requirement,
|
|
24542
|
+
scope: normalizedScope(requirement.scope)
|
|
24543
|
+
})))
|
|
24544
|
+
});
|
|
24545
|
+
var invalidFields = (error) => ({
|
|
24546
|
+
fields: error.issues.map((issue) => ({
|
|
24547
|
+
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
24548
|
+
reason: issue.message
|
|
24549
|
+
}))
|
|
24550
|
+
});
|
|
24551
|
+
function normalizeResolverInput(input) {
|
|
24552
|
+
const parsed = resolverInputSchema.safeParse(input);
|
|
24553
|
+
if (!parsed.success)
|
|
24554
|
+
return failure("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
24555
|
+
const value = parsed.data;
|
|
24556
|
+
const signals = value.assessment.signals;
|
|
24557
|
+
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
24558
|
+
return failure("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
24559
|
+
fields: [
|
|
24560
|
+
{
|
|
24561
|
+
path: "assessment.signals",
|
|
24562
|
+
reason: "contradictory behaviorChange and mechanicalLowRisk"
|
|
24563
|
+
}
|
|
24564
|
+
]
|
|
24565
|
+
});
|
|
24566
|
+
const ids = value.constraints.map((constraint) => constraint.id);
|
|
24567
|
+
if (new Set(ids).size !== ids.length)
|
|
24568
|
+
return failure("invalid_input", "constraint IDs must be unique", {
|
|
24569
|
+
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
24570
|
+
});
|
|
24571
|
+
if (value.preferences?.fast && value.preferences.thorough)
|
|
24572
|
+
return failure("invalid_input", "fast and thorough preferences contradict", {
|
|
24573
|
+
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
|
|
24574
|
+
});
|
|
24575
|
+
const normalized = {
|
|
24576
|
+
intent: normalizeIntent(value.intent),
|
|
24577
|
+
assessment: normalizeAssessment(value.assessment),
|
|
24578
|
+
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
24579
|
+
prior: normalizePrior(value.prior),
|
|
24580
|
+
...value.preferences ? { preferences: { ...value.preferences } } : {},
|
|
24581
|
+
policyVersion: POLICY_VERSION
|
|
24530
24582
|
};
|
|
24531
|
-
|
|
24532
|
-
const parsed = candidateSchema.safeParse(candidate);
|
|
24533
|
-
return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
|
|
24583
|
+
return success(null, null, normalized);
|
|
24534
24584
|
}
|
|
24535
|
-
var
|
|
24536
|
-
|
|
24537
|
-
|
|
24538
|
-
|
|
24539
|
-
|
|
24540
|
-
|
|
24541
|
-
|
|
24542
|
-
|
|
24543
|
-
|
|
24544
|
-
|
|
24545
|
-
|
|
24546
|
-
|
|
24547
|
-
|
|
24548
|
-
|
|
24549
|
-
|
|
24550
|
-
|
|
24551
|
-
|
|
24552
|
-
|
|
24553
|
-
|
|
24554
|
-
|
|
24555
|
-
|
|
24556
|
-
|
|
24557
|
-
|
|
24558
|
-
|
|
24559
|
-
|
|
24560
|
-
|
|
24561
|
-
|
|
24562
|
-
|
|
24563
|
-
|
|
24564
|
-
|
|
24565
|
-
|
|
24566
|
-
|
|
24567
|
-
|
|
24568
|
-
|
|
24569
|
-
|
|
24570
|
-
|
|
24571
|
-
|
|
24572
|
-
|
|
24573
|
-
|
|
24574
|
-
|
|
24575
|
-
|
|
24576
|
-
|
|
24577
|
-
|
|
24578
|
-
|
|
24579
|
-
|
|
24585
|
+
var requirement = (spec) => ({
|
|
24586
|
+
...spec,
|
|
24587
|
+
id: requirementId({
|
|
24588
|
+
ruleId: spec.ruleId,
|
|
24589
|
+
scope: spec.scope,
|
|
24590
|
+
satisfaction: spec.satisfaction,
|
|
24591
|
+
before: spec.before,
|
|
24592
|
+
dependentAction: spec.dependentAction
|
|
24593
|
+
})
|
|
24594
|
+
});
|
|
24595
|
+
var signalReason = (input, name) => input.assessment.signals[name].reason;
|
|
24596
|
+
function resolveRequirements(input) {
|
|
24597
|
+
const { signals } = input.assessment;
|
|
24598
|
+
const scope = input.intent.scope;
|
|
24599
|
+
const requirements = [];
|
|
24600
|
+
const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
|
|
24601
|
+
const hasUnknownFact = [
|
|
24602
|
+
...input.assessment.facts,
|
|
24603
|
+
...input.assessment.consequences.map(({ fact }) => fact)
|
|
24604
|
+
].some((fact) => fact.basis === "unknown");
|
|
24605
|
+
if (hasUnknownSignal || hasUnknownFact)
|
|
24606
|
+
requirements.push(requirement({
|
|
24607
|
+
ruleId: "consequential-unknown",
|
|
24608
|
+
dimension: "investigation",
|
|
24609
|
+
scope,
|
|
24610
|
+
reason: `Resolve consequential uncertainty before the dependent action: ${[
|
|
24611
|
+
...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
|
|
24612
|
+
...hasUnknownFact ? ["unknown fact"] : []
|
|
24613
|
+
].join(", ")}`,
|
|
24614
|
+
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
24615
|
+
before: "dependent_action",
|
|
24616
|
+
dependentAction: null,
|
|
24617
|
+
acceptanceAllowed: false
|
|
24618
|
+
}));
|
|
24619
|
+
if (signals.productChoiceOpen.value === true)
|
|
24620
|
+
requirements.push(requirement({
|
|
24621
|
+
ruleId: "product-decision",
|
|
24622
|
+
dimension: "decisions",
|
|
24623
|
+
scope,
|
|
24624
|
+
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
24625
|
+
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
24626
|
+
before: "dependent_action",
|
|
24627
|
+
dependentAction: null,
|
|
24628
|
+
acceptanceAllowed: false
|
|
24629
|
+
}));
|
|
24630
|
+
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
24631
|
+
if (signals.behaviorChange.value === true)
|
|
24632
|
+
requirements.push(requirement({
|
|
24633
|
+
ruleId: "behavioral-verification",
|
|
24634
|
+
dimension: "testing",
|
|
24635
|
+
scope,
|
|
24636
|
+
reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
|
|
24637
|
+
satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
|
|
24638
|
+
before: "close",
|
|
24639
|
+
dependentAction: null,
|
|
24640
|
+
acceptanceAllowed: false
|
|
24641
|
+
}), requirement({
|
|
24642
|
+
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
24643
|
+
dimension: "review",
|
|
24644
|
+
scope,
|
|
24645
|
+
reason: independentBehaviorReview ? "Consequential behavior or the thorough preference requires fresh-context review." : "Bounded behavior changes use self-review unless consequential risks or trusted rules require more.",
|
|
24646
|
+
satisfaction: independentBehaviorReview ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
24647
|
+
before: "close",
|
|
24648
|
+
dependentAction: null,
|
|
24649
|
+
acceptanceAllowed: !independentBehaviorReview
|
|
24650
|
+
}));
|
|
24651
|
+
else if (signals.mechanicalLowRisk.value === true)
|
|
24652
|
+
requirements.push(requirement({
|
|
24653
|
+
ruleId: "mechanical-existing-checks",
|
|
24654
|
+
dimension: "verification",
|
|
24655
|
+
scope,
|
|
24656
|
+
reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
|
|
24657
|
+
satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
|
|
24658
|
+
before: "close",
|
|
24659
|
+
dependentAction: null,
|
|
24660
|
+
acceptanceAllowed: true
|
|
24661
|
+
}), requirement({
|
|
24662
|
+
ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
|
|
24663
|
+
dimension: "review",
|
|
24664
|
+
scope,
|
|
24665
|
+
reason: input.preferences?.thorough ? "The thorough preference upgrades mechanical self-review to fresh-context review." : "Mechanical low-risk work uses self-review rather than mandatory fresh-context review.",
|
|
24666
|
+
satisfaction: input.preferences?.thorough ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
24667
|
+
before: "close",
|
|
24668
|
+
dependentAction: null,
|
|
24669
|
+
acceptanceAllowed: !input.preferences?.thorough
|
|
24670
|
+
}));
|
|
24671
|
+
if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
|
|
24672
|
+
requirements.push(requirement({
|
|
24673
|
+
ruleId: "pre-pr-cleanup",
|
|
24674
|
+
dimension: "verification",
|
|
24675
|
+
scope,
|
|
24676
|
+
reason: "Implementation output must be deslopped before it is delivered.",
|
|
24677
|
+
satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
|
|
24678
|
+
before: "dependent_action",
|
|
24679
|
+
dependentAction: "hosting.pull_request",
|
|
24680
|
+
acceptanceAllowed: true
|
|
24681
|
+
}));
|
|
24682
|
+
if (signals.durableAgreementNeeded.value === true)
|
|
24683
|
+
requirements.push(requirement({
|
|
24684
|
+
ruleId: "durable-spec",
|
|
24685
|
+
dimension: "artifacts",
|
|
24686
|
+
scope,
|
|
24687
|
+
reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
|
|
24688
|
+
satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
|
|
24689
|
+
before: "write",
|
|
24690
|
+
dependentAction: null,
|
|
24691
|
+
acceptanceAllowed: true
|
|
24692
|
+
}));
|
|
24693
|
+
if (signals.coordinationPlanNeeded.value === true)
|
|
24694
|
+
requirements.push(requirement({
|
|
24695
|
+
ruleId: "coordination-plan",
|
|
24696
|
+
dimension: "continuity",
|
|
24697
|
+
scope,
|
|
24698
|
+
reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
|
|
24699
|
+
satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
|
|
24700
|
+
before: "dependent_action",
|
|
24701
|
+
dependentAction: null,
|
|
24702
|
+
acceptanceAllowed: true
|
|
24703
|
+
}));
|
|
24704
|
+
if (signals.helperUseful.value === true && !input.preferences?.fast)
|
|
24705
|
+
requirements.push(requirement({
|
|
24706
|
+
ruleId: "helper-usefulness",
|
|
24707
|
+
dimension: "delegation",
|
|
24708
|
+
scope,
|
|
24709
|
+
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
24710
|
+
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
24711
|
+
before: "dependent_action",
|
|
24712
|
+
dependentAction: null,
|
|
24713
|
+
acceptanceAllowed: true
|
|
24714
|
+
}));
|
|
24715
|
+
for (const constraint of input.constraints)
|
|
24716
|
+
for (const obligation of constraint.requires) {
|
|
24717
|
+
const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
|
|
24718
|
+
requirements.push(requirement({
|
|
24719
|
+
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
24720
|
+
kind: obligation.kind,
|
|
24721
|
+
scope: normalizedScope(obligation.scope),
|
|
24722
|
+
refs: normalizedRefList(obligation.refs),
|
|
24723
|
+
method: obligation.method,
|
|
24724
|
+
before: obligation.before,
|
|
24725
|
+
dependentAction: obligation.dependentAction
|
|
24726
|
+
})}`,
|
|
24727
|
+
dimension,
|
|
24728
|
+
scope: obligation.scope,
|
|
24729
|
+
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
24730
|
+
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
24731
|
+
before: obligation.before,
|
|
24732
|
+
dependentAction: obligation.dependentAction,
|
|
24733
|
+
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
24734
|
+
}));
|
|
24580
24735
|
}
|
|
24581
|
-
|
|
24582
|
-
|
|
24583
|
-
|
|
24584
|
-
|
|
24585
|
-
|
|
24586
|
-
|
|
24587
|
-
const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
|
|
24588
|
-
if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
|
|
24589
|
-
return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
|
|
24590
|
-
if (observed.id === current.id)
|
|
24591
|
-
return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
|
|
24592
|
-
const scopes = evidenceScopes(task, evidence.requirementIds);
|
|
24593
|
-
const changed = changedPaths(observed, current);
|
|
24594
|
-
const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
|
|
24595
|
-
if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
|
|
24596
|
-
return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
|
|
24597
|
-
return {
|
|
24598
|
-
evidenceId: entry.id,
|
|
24599
|
-
status: evidence.result,
|
|
24600
|
-
reason: "candidate changed outside evidence scope"
|
|
24601
|
-
};
|
|
24736
|
+
const seen = new Set;
|
|
24737
|
+
return requirements.filter((item) => {
|
|
24738
|
+
if (seen.has(item.id))
|
|
24739
|
+
return false;
|
|
24740
|
+
seen.add(item.id);
|
|
24741
|
+
return true;
|
|
24602
24742
|
});
|
|
24603
24743
|
}
|
|
24604
|
-
|
|
24605
|
-
|
|
24606
|
-
|
|
24607
|
-
|
|
24608
|
-
|
|
24609
|
-
|
|
24610
|
-
|
|
24611
|
-
|
|
24612
|
-
|
|
24613
|
-
const fallback = bound ? pin ?? currentCandidateId : null;
|
|
24614
|
-
return {
|
|
24615
|
-
beforeCandidateId: evidence.beforeCandidateId ?? fallback,
|
|
24616
|
-
candidateId: evidence.candidateId ?? fallback
|
|
24744
|
+
function resolvePolicy(input) {
|
|
24745
|
+
const normalized = normalizeResolverInput(input);
|
|
24746
|
+
if (!normalized.ok)
|
|
24747
|
+
return normalized;
|
|
24748
|
+
const requirements = resolveRequirements(normalized.data);
|
|
24749
|
+
const policy = {
|
|
24750
|
+
policyVersion: POLICY_VERSION,
|
|
24751
|
+
inputDigest: sha256(canonicalJson(normalized.data)),
|
|
24752
|
+
requirements
|
|
24617
24753
|
};
|
|
24618
|
-
|
|
24619
|
-
|
|
24620
|
-
|
|
24621
|
-
|
|
24622
|
-
|
|
24623
|
-
|
|
24624
|
-
|
|
24625
|
-
|
|
24626
|
-
|
|
24627
|
-
|
|
24628
|
-
|
|
24629
|
-
|
|
24630
|
-
|
|
24631
|
-
|
|
24632
|
-
|
|
24633
|
-
|
|
24634
|
-
|
|
24635
|
-
|
|
24636
|
-
|
|
24637
|
-
|
|
24638
|
-
|
|
24639
|
-
|
|
24640
|
-
|
|
24641
|
-
|
|
24642
|
-
|
|
24643
|
-
|
|
24644
|
-
|
|
24645
|
-
|
|
24646
|
-
|
|
24647
|
-
|
|
24648
|
-
|
|
24649
|
-
|
|
24650
|
-
|
|
24651
|
-
|
|
24652
|
-
|
|
24653
|
-
|
|
24654
|
-
|
|
24655
|
-
|
|
24656
|
-
|
|
24657
|
-
|
|
24658
|
-
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
24662
|
-
|
|
24663
|
-
|
|
24664
|
-
|
|
24665
|
-
|
|
24666
|
-
|
|
24667
|
-
|
|
24668
|
-
|
|
24669
|
-
|
|
24670
|
-
|
|
24671
|
-
|
|
24672
|
-
|
|
24673
|
-
|
|
24674
|
-
|
|
24675
|
-
|
|
24676
|
-
|
|
24677
|
-
|
|
24678
|
-
|
|
24679
|
-
|
|
24680
|
-
|
|
24681
|
-
|
|
24682
|
-
|
|
24683
|
-
|
|
24684
|
-
|
|
24685
|
-
|
|
24686
|
-
|
|
24687
|
-
|
|
24688
|
-
|
|
24689
|
-
|
|
24690
|
-
|
|
24691
|
-
|
|
24692
|
-
|
|
24693
|
-
|
|
24694
|
-
|
|
24695
|
-
|
|
24696
|
-
|
|
24697
|
-
|
|
24698
|
-
|
|
24699
|
-
|
|
24700
|
-
|
|
24701
|
-
|
|
24702
|
-
|
|
24703
|
-
|
|
24704
|
-
|
|
24705
|
-
}
|
|
24706
|
-
const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
|
|
24707
|
-
if (decisions.length)
|
|
24708
|
-
return {
|
|
24709
|
-
requirementId: requirement.id,
|
|
24710
|
-
status: "satisfied",
|
|
24711
|
-
evidenceIds: [],
|
|
24712
|
-
decisionIds: decisions.map((decision) => decision.id),
|
|
24713
|
-
reason: "an applicable approved or stated decision satisfies the requirement"
|
|
24714
|
-
};
|
|
24715
|
-
const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
|
|
24716
|
-
if (limitations.length)
|
|
24717
|
-
return {
|
|
24718
|
-
requirementId: requirement.id,
|
|
24719
|
-
status: "accepted_limitation",
|
|
24720
|
-
evidenceIds: [],
|
|
24721
|
-
decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
|
|
24722
|
-
reason: "an applicable approved limitation permits the missing evidence"
|
|
24723
|
-
};
|
|
24724
|
-
const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
|
|
24725
|
-
if (!unavailable && related.length > 0 && !passed.length)
|
|
24726
|
-
return {
|
|
24727
|
-
requirementId: requirement.id,
|
|
24728
|
-
status: "unsatisfied",
|
|
24729
|
-
evidenceIds: related.map(({ entry }) => entry.id),
|
|
24730
|
-
decisionIds: [],
|
|
24731
|
-
reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
|
|
24732
|
-
};
|
|
24754
|
+
const parsed = policySchema.safeParse(policy);
|
|
24755
|
+
if (!parsed.success)
|
|
24756
|
+
return failure("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
|
|
24757
|
+
return success(null, null, parsed.data);
|
|
24758
|
+
}
|
|
24759
|
+
function diffPolicy(previous, next, reason, now) {
|
|
24760
|
+
if (!policySchema.safeParse(previous).success && previous !== null)
|
|
24761
|
+
throw new TypeError("previous policy is invalid");
|
|
24762
|
+
if (!policySchema.safeParse(next).success)
|
|
24763
|
+
throw new TypeError("next policy is invalid");
|
|
24764
|
+
const oldIds = new Set(previous?.requirements.map((requirement) => requirement.id) ?? []);
|
|
24765
|
+
const newIds = new Set(next.requirements.map((requirement) => requirement.id));
|
|
24766
|
+
const added = [...newIds].filter((id) => !oldIds.has(id)).sort(compareCodeUnits2);
|
|
24767
|
+
const retired = [...oldIds].filter((id) => !newIds.has(id)).sort(compareCodeUnits2);
|
|
24768
|
+
if (added.length === 0 && retired.length === 0)
|
|
24769
|
+
return null;
|
|
24770
|
+
const normalizedReason = reason.trim();
|
|
24771
|
+
const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
|
|
24772
|
+
const change = {
|
|
24773
|
+
recordedAt: now,
|
|
24774
|
+
fromInputDigest: previous?.inputDigest ?? null,
|
|
24775
|
+
toInputDigest: next.inputDigest,
|
|
24776
|
+
added,
|
|
24777
|
+
retired,
|
|
24778
|
+
reason: changeReason
|
|
24779
|
+
};
|
|
24780
|
+
const parsed = policyChangeSchema.safeParse(change);
|
|
24781
|
+
if (!parsed.success)
|
|
24782
|
+
throw new TypeError("policy change is invalid");
|
|
24783
|
+
return parsed.data;
|
|
24784
|
+
}
|
|
24785
|
+
|
|
24786
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
24787
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
24788
|
+
import * as fs7 from "node:fs";
|
|
24789
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
24790
|
+
import path10 from "node:path";
|
|
24791
|
+
var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
24792
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
|
|
24793
|
+
var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
|
|
24794
|
+
var canonicalPath = (value) => {
|
|
24795
|
+
if (typeof value !== "string" || !value)
|
|
24796
|
+
return null;
|
|
24797
|
+
if (value.includes("\\") || value.split("/").includes(".."))
|
|
24798
|
+
return null;
|
|
24799
|
+
const normalized = path10.posix.normalize(value);
|
|
24800
|
+
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
24801
|
+
return null;
|
|
24802
|
+
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
24803
|
+
};
|
|
24804
|
+
var canonicalScope = (scope) => {
|
|
24805
|
+
const paths = scope.paths.length ? scope.paths : ["."];
|
|
24806
|
+
const normalizedPaths = paths.map(canonicalPath);
|
|
24807
|
+
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
24808
|
+
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
24809
|
+
return null;
|
|
24810
|
+
return {
|
|
24811
|
+
description: scope.description,
|
|
24812
|
+
paths: [...new Set(normalizedPaths)],
|
|
24813
|
+
exclusions: [...new Set(normalizedExclusions)]
|
|
24814
|
+
};
|
|
24815
|
+
};
|
|
24816
|
+
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
24817
|
+
var scopeMatches = (value, scope) => {
|
|
24818
|
+
const normalized = canonicalScope(scope);
|
|
24819
|
+
if (!normalized)
|
|
24820
|
+
return false;
|
|
24821
|
+
const paths = normalized.paths;
|
|
24822
|
+
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
24823
|
+
};
|
|
24824
|
+
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
24825
|
+
var compareCodeUnits3 = (left, right) => {
|
|
24826
|
+
const length = Math.min(left.length, right.length);
|
|
24827
|
+
for (let index = 0;index < length; index += 1) {
|
|
24828
|
+
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
24829
|
+
if (difference !== 0)
|
|
24830
|
+
return difference;
|
|
24831
|
+
}
|
|
24832
|
+
return left.length - right.length;
|
|
24833
|
+
};
|
|
24834
|
+
var gitPaths = (root) => {
|
|
24835
|
+
const result = spawnSync2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
24836
|
+
cwd: root,
|
|
24837
|
+
encoding: "buffer"
|
|
24838
|
+
});
|
|
24839
|
+
if (result.status !== 0) {
|
|
24840
|
+
const stderr = result.stderr?.toString("utf8") ?? "";
|
|
24733
24841
|
return {
|
|
24734
|
-
|
|
24735
|
-
|
|
24736
|
-
|
|
24737
|
-
decisionIds: [],
|
|
24738
|
-
reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
|
|
24842
|
+
paths: [],
|
|
24843
|
+
uncertain: !/not a git repository/i.test(stderr),
|
|
24844
|
+
git: /not a git repository/i.test(stderr) ? false : true
|
|
24739
24845
|
};
|
|
24740
|
-
}
|
|
24741
|
-
|
|
24742
|
-
|
|
24743
|
-
if (
|
|
24744
|
-
|
|
24745
|
-
|
|
24746
|
-
|
|
24747
|
-
|
|
24748
|
-
|
|
24749
|
-
|
|
24750
|
-
|
|
24751
|
-
|
|
24752
|
-
|
|
24753
|
-
|
|
24754
|
-
|
|
24755
|
-
|
|
24756
|
-
|
|
24757
|
-
|
|
24758
|
-
|
|
24759
|
-
|
|
24760
|
-
|
|
24761
|
-
|
|
24762
|
-
|
|
24763
|
-
|
|
24764
|
-
|
|
24765
|
-
|
|
24766
|
-
|
|
24767
|
-
|
|
24768
|
-
|
|
24769
|
-
|
|
24770
|
-
const decisionEntry = view.task.decisions.find((item) => item.id === id);
|
|
24771
|
-
const decision = decisionEntry?.data;
|
|
24772
|
-
return Boolean(decision && decisionEntry?.provenance.kind !== "imported" && decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.digest === decisionDigest(decision) && verifyDecisionContentAtRoot(view.workspace.root, decision.binding).ok && decision.binding.taskId === view.task.id && decision.binding.workspaceId === view.workspace.id && scopeCovers2(decision.binding.scope, entry.data.scope) && decision.requirementIds.some((requirementId) => view.task.policy?.requirements.some((requirement) => requirement.id === requirementId && requirement.acceptanceAllowed)));
|
|
24773
|
-
});
|
|
24774
|
-
if (!valid)
|
|
24775
|
-
return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
|
|
24776
|
-
}
|
|
24846
|
+
}
|
|
24847
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
|
|
24848
|
+
const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
24849
|
+
if (stagedDeleted.status !== 0) {
|
|
24850
|
+
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
24851
|
+
if (!/not a git repository/i.test(stderr))
|
|
24852
|
+
return { paths, uncertain: true, git: true };
|
|
24853
|
+
} else if (stagedDeleted.stdout) {
|
|
24854
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
|
|
24855
|
+
}
|
|
24856
|
+
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
24857
|
+
};
|
|
24858
|
+
var walk = (root, directory, output) => {
|
|
24859
|
+
let names;
|
|
24860
|
+
try {
|
|
24861
|
+
names = fs7.readdirSync(directory);
|
|
24862
|
+
} catch {
|
|
24863
|
+
return true;
|
|
24864
|
+
}
|
|
24865
|
+
let uncertain = false;
|
|
24866
|
+
for (const name of names) {
|
|
24867
|
+
if (name === ".git" || name === ".workit")
|
|
24868
|
+
continue;
|
|
24869
|
+
const target = path10.join(directory, name);
|
|
24870
|
+
let stat;
|
|
24871
|
+
try {
|
|
24872
|
+
stat = fs7.lstatSync(target);
|
|
24873
|
+
} catch {
|
|
24874
|
+
uncertain = true;
|
|
24875
|
+
continue;
|
|
24777
24876
|
}
|
|
24877
|
+
const item = relative(root, target);
|
|
24878
|
+
output.add(item);
|
|
24879
|
+
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
24880
|
+
uncertain = walk(root, target, output) || uncertain;
|
|
24778
24881
|
}
|
|
24779
|
-
|
|
24780
|
-
if (item.status !== "unsatisfied" && item.status !== "unavailable")
|
|
24781
|
-
return false;
|
|
24782
|
-
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
24783
|
-
return !requirement || requirement.before === "close";
|
|
24784
|
-
});
|
|
24785
|
-
if (requestedOutcome !== "stopped" && blocking.length)
|
|
24786
|
-
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
24787
|
-
requirementIds: blocking.map((item) => item.requirementId),
|
|
24788
|
-
requirements: blocking.map((item) => {
|
|
24789
|
-
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
24790
|
-
return {
|
|
24791
|
-
requirementId: item.requirementId,
|
|
24792
|
-
ruleId: requirement?.ruleId ?? "unknown",
|
|
24793
|
-
reason: item.reason,
|
|
24794
|
-
satisfaction: requirement?.satisfaction ?? "",
|
|
24795
|
-
dependentAction: requirement?.dependentAction ?? null
|
|
24796
|
-
};
|
|
24797
|
-
})
|
|
24798
|
-
});
|
|
24799
|
-
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
24800
|
-
if (requestedOutcome === "verified" && accepted.length)
|
|
24801
|
-
return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
|
|
24802
|
-
requirementIds: accepted.map((item) => item.requirementId)
|
|
24803
|
-
});
|
|
24804
|
-
const evidenceIds = [
|
|
24805
|
-
...new Set([
|
|
24806
|
-
...evaluations.flatMap((item) => item.evidenceIds),
|
|
24807
|
-
...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
|
|
24808
|
-
])
|
|
24809
|
-
];
|
|
24810
|
-
const decisionIds = [
|
|
24811
|
-
...new Set([
|
|
24812
|
-
...evaluations.flatMap((item) => item.decisionIds),
|
|
24813
|
-
...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
|
|
24814
|
-
])
|
|
24815
|
-
];
|
|
24816
|
-
return success(null, null, {
|
|
24817
|
-
outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
|
|
24818
|
-
evidenceIds,
|
|
24819
|
-
decisionIds,
|
|
24820
|
-
requirementIds: evaluations.map((item) => item.requirementId)
|
|
24821
|
-
});
|
|
24822
|
-
}
|
|
24823
|
-
|
|
24824
|
-
// packages/workit-core/src/core/task-context.ts
|
|
24825
|
-
function reconcileResume(view, observations = []) {
|
|
24826
|
-
if (observations.length > 0)
|
|
24827
|
-
return failure("permission_denied", "worker observations require native host verification");
|
|
24828
|
-
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
24829
|
-
if (!candidate.ok)
|
|
24830
|
-
return candidate;
|
|
24831
|
-
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
24832
|
-
const blockers = [...view.task.progress.blockers];
|
|
24833
|
-
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
24834
|
-
blockers.push({
|
|
24835
|
-
reason: "worker state requires reconciliation",
|
|
24836
|
-
dependentAction: "resume",
|
|
24837
|
-
refs: []
|
|
24838
|
-
});
|
|
24839
|
-
return success(null, null, {
|
|
24840
|
-
candidate: candidate.data,
|
|
24841
|
-
staleEvidenceIds,
|
|
24842
|
-
workerUpdates: [],
|
|
24843
|
-
blockers,
|
|
24844
|
-
reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
|
|
24845
|
-
});
|
|
24846
|
-
}
|
|
24847
|
-
var exportDigest = (bundle) => sha256({
|
|
24848
|
-
schemaVersion: bundle.schemaVersion,
|
|
24849
|
-
exportedAt: bundle.exportedAt,
|
|
24850
|
-
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
24851
|
-
task: bundle.task
|
|
24852
|
-
});
|
|
24853
|
-
// packages/workit-core/src/core/external-action.ts
|
|
24854
|
-
var chainStepSchema = union3([
|
|
24855
|
-
string5().min(1),
|
|
24856
|
-
object4({ branch: string5().min(1) }).strict(),
|
|
24857
|
-
object4({ pr: literal3(true) }).strict()
|
|
24858
|
-
]);
|
|
24859
|
-
var externalActionSchema = discriminatedUnion2("operation", [
|
|
24860
|
-
object4({
|
|
24861
|
-
operation: literal3("git.branch_setup"),
|
|
24862
|
-
payload: object4({
|
|
24863
|
-
action: _enum2(["setup", "reapply_stash"]).optional(),
|
|
24864
|
-
sdd_dir: string5().optional(),
|
|
24865
|
-
target_branch: string5().optional(),
|
|
24866
|
-
stash: _enum2(["yes", "no"]).optional(),
|
|
24867
|
-
cwd: string5().min(1).optional()
|
|
24868
|
-
}).strict()
|
|
24869
|
-
}).strict(),
|
|
24870
|
-
object4({
|
|
24871
|
-
operation: literal3("git.commit"),
|
|
24872
|
-
payload: object4({
|
|
24873
|
-
message: string5().min(1).optional(),
|
|
24874
|
-
plan_steps: array3(chainStepSchema).min(1).max(32).optional(),
|
|
24875
|
-
plan_branch: string5().min(1).optional(),
|
|
24876
|
-
cwd: string5().min(1).optional()
|
|
24877
|
-
}).strict()
|
|
24878
|
-
}).strict(),
|
|
24879
|
-
object4({
|
|
24880
|
-
operation: literal3("git.push"),
|
|
24881
|
-
payload: object4({ branch: string5().min(1).optional(), cwd: string5().min(1).optional() }).strict()
|
|
24882
|
-
}).strict(),
|
|
24883
|
-
object4({
|
|
24884
|
-
operation: literal3("hosting.pull_request"),
|
|
24885
|
-
payload: object4({
|
|
24886
|
-
title: string5().min(1),
|
|
24887
|
-
body: string5().optional(),
|
|
24888
|
-
draft: boolean5().optional(),
|
|
24889
|
-
target_branch: string5().optional(),
|
|
24890
|
-
babysit: boolean5().optional(),
|
|
24891
|
-
cwd: string5().min(1).optional()
|
|
24892
|
-
}).strict()
|
|
24893
|
-
}).strict(),
|
|
24894
|
-
object4({
|
|
24895
|
-
operation: literal3("hosting.merge"),
|
|
24896
|
-
payload: object4({
|
|
24897
|
-
target_branch: string5().min(1).optional(),
|
|
24898
|
-
source_branch: string5().min(1).optional(),
|
|
24899
|
-
cwd: string5().min(1).optional()
|
|
24900
|
-
}).strict()
|
|
24901
|
-
}).strict(),
|
|
24902
|
-
object4({
|
|
24903
|
-
operation: literal3("hosting.delete_branch"),
|
|
24904
|
-
payload: object4({ branch: string5().min(1), cwd: string5().min(1).optional() }).strict()
|
|
24905
|
-
}).strict(),
|
|
24906
|
-
object4({
|
|
24907
|
-
operation: literal3("youtrack.update"),
|
|
24908
|
-
payload: object4({
|
|
24909
|
-
issueId: string5().min(1),
|
|
24910
|
-
markdown: string5().min(1),
|
|
24911
|
-
minutes: number5().positive().optional()
|
|
24912
|
-
}).strict()
|
|
24913
|
-
}).strict(),
|
|
24914
|
-
object4({
|
|
24915
|
-
operation: literal3("youtrack.time"),
|
|
24916
|
-
payload: object4({
|
|
24917
|
-
issueId: string5().min(1),
|
|
24918
|
-
minutes: number5().positive(),
|
|
24919
|
-
text: string5().optional(),
|
|
24920
|
-
dateMs: number5().finite().optional()
|
|
24921
|
-
}).strict()
|
|
24922
|
-
}).strict(),
|
|
24923
|
-
object4({
|
|
24924
|
-
operation: literal3("youtrack.meeting"),
|
|
24925
|
-
payload: object4({
|
|
24926
|
-
issueId: string5().min(1),
|
|
24927
|
-
minutes: number5().positive(),
|
|
24928
|
-
text: string5().min(1)
|
|
24929
|
-
}).strict()
|
|
24930
|
-
}).strict(),
|
|
24931
|
-
object4({
|
|
24932
|
-
operation: literal3("changelog.apply"),
|
|
24933
|
-
payload: object4({
|
|
24934
|
-
entries: union3([
|
|
24935
|
-
record3(string5(), array3(string5())),
|
|
24936
|
-
array3(object4({ category: string5().min(1), text: string5().min(1) }).strict())
|
|
24937
|
-
]).optional(),
|
|
24938
|
-
path: string5().optional(),
|
|
24939
|
-
normalize_only: boolean5().optional()
|
|
24940
|
-
}).strict()
|
|
24941
|
-
}).strict(),
|
|
24942
|
-
object4({
|
|
24943
|
-
operation: literal3("context.read"),
|
|
24944
|
-
payload: object4({
|
|
24945
|
-
kind: _enum2([
|
|
24946
|
-
"git",
|
|
24947
|
-
"pr",
|
|
24948
|
-
"youtrack",
|
|
24949
|
-
"github_issue",
|
|
24950
|
-
"gitlab_issue",
|
|
24951
|
-
"changelog",
|
|
24952
|
-
"release",
|
|
24953
|
-
"affected"
|
|
24954
|
-
]),
|
|
24955
|
-
range: string5().optional(),
|
|
24956
|
-
issueId: string5().optional(),
|
|
24957
|
-
issueUrl: string5().optional(),
|
|
24958
|
-
issueRef: string5().optional(),
|
|
24959
|
-
mode: string5().optional(),
|
|
24960
|
-
specPath: string5().optional(),
|
|
24961
|
-
planPath: string5().optional(),
|
|
24962
|
-
cwd: string5().min(1).optional()
|
|
24963
|
-
}).strict()
|
|
24964
|
-
}).strict()
|
|
24965
|
-
]);
|
|
24966
|
-
// packages/workit-core/src/core/task-engine.ts
|
|
24967
|
-
import { realpathSync as realpathSync6 } from "node:fs";
|
|
24968
|
-
|
|
24969
|
-
// packages/workit-core/src/core/policy-resolver.ts
|
|
24970
|
-
var resolverInputSchema = object4({
|
|
24971
|
-
intent: intentSchema,
|
|
24972
|
-
assessment: assessmentSchema,
|
|
24973
|
-
constraints: array3(constraintSchema),
|
|
24974
|
-
prior: object4({
|
|
24975
|
-
decisions: array3(decisionSchema),
|
|
24976
|
-
findings: array3(findingSchema),
|
|
24977
|
-
requirements: array3(requirementSchema)
|
|
24978
|
-
}).strict(),
|
|
24979
|
-
preferences: object4({ fast: boolean5().optional(), thorough: boolean5().optional() }).strict().optional()
|
|
24980
|
-
}).strict();
|
|
24981
|
-
var compareCodeUnits3 = (left, right) => {
|
|
24982
|
-
const length = Math.min(left.length, right.length);
|
|
24983
|
-
for (let index = 0;index < length; index += 1) {
|
|
24984
|
-
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
24985
|
-
if (difference !== 0)
|
|
24986
|
-
return difference;
|
|
24987
|
-
}
|
|
24988
|
-
return left.length - right.length;
|
|
24882
|
+
return uncertain;
|
|
24989
24883
|
};
|
|
24990
|
-
var
|
|
24991
|
-
|
|
24992
|
-
|
|
24993
|
-
|
|
24994
|
-
|
|
24995
|
-
|
|
24996
|
-
|
|
24997
|
-
|
|
24998
|
-
|
|
24999
|
-
|
|
25000
|
-
|
|
25001
|
-
|
|
25002
|
-
|
|
25003
|
-
|
|
25004
|
-
|
|
25005
|
-
|
|
25006
|
-
|
|
25007
|
-
|
|
25008
|
-
|
|
25009
|
-
|
|
25010
|
-
|
|
25011
|
-
|
|
25012
|
-
|
|
25013
|
-
}
|
|
25014
|
-
|
|
25015
|
-
|
|
25016
|
-
|
|
25017
|
-
|
|
25018
|
-
|
|
25019
|
-
|
|
25020
|
-
|
|
25021
|
-
|
|
25022
|
-
|
|
25023
|
-
|
|
25024
|
-
|
|
25025
|
-
|
|
25026
|
-
|
|
25027
|
-
|
|
25028
|
-
|
|
25029
|
-
|
|
25030
|
-
|
|
25031
|
-
|
|
25032
|
-
|
|
25033
|
-
|
|
25034
|
-
|
|
25035
|
-
|
|
25036
|
-
|
|
25037
|
-
|
|
25038
|
-
|
|
25039
|
-
|
|
25040
|
-
|
|
25041
|
-
|
|
25042
|
-
|
|
25043
|
-
|
|
25044
|
-
|
|
25045
|
-
|
|
25046
|
-
|
|
25047
|
-
|
|
25048
|
-
|
|
25049
|
-
|
|
25050
|
-
|
|
25051
|
-
|
|
25052
|
-
|
|
25053
|
-
|
|
25054
|
-
|
|
25055
|
-
|
|
25056
|
-
|
|
25057
|
-
|
|
25058
|
-
|
|
25059
|
-
|
|
25060
|
-
|
|
25061
|
-
|
|
25062
|
-
|
|
25063
|
-
|
|
25064
|
-
|
|
25065
|
-
|
|
25066
|
-
|
|
25067
|
-
|
|
25068
|
-
|
|
25069
|
-
|
|
25070
|
-
{
|
|
25071
|
-
|
|
25072
|
-
|
|
25073
|
-
|
|
25074
|
-
|
|
25075
|
-
|
|
25076
|
-
|
|
25077
|
-
|
|
25078
|
-
|
|
25079
|
-
|
|
25080
|
-
|
|
25081
|
-
|
|
25082
|
-
|
|
25083
|
-
|
|
25084
|
-
|
|
25085
|
-
const
|
|
25086
|
-
|
|
25087
|
-
|
|
25088
|
-
|
|
25089
|
-
|
|
25090
|
-
|
|
25091
|
-
|
|
24884
|
+
var scopeRoots = (root, scope) => {
|
|
24885
|
+
const roots = [];
|
|
24886
|
+
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
24887
|
+
const target = path10.resolve(root, item);
|
|
24888
|
+
if (!inside(root, target))
|
|
24889
|
+
return failure("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` }] });
|
|
24890
|
+
let ancestor = target;
|
|
24891
|
+
while (!fs7.existsSync(ancestor) && ancestor !== root)
|
|
24892
|
+
ancestor = path10.dirname(ancestor);
|
|
24893
|
+
try {
|
|
24894
|
+
if (!inside(root, fs7.realpathSync(ancestor)))
|
|
24895
|
+
return failure("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` }] });
|
|
24896
|
+
} catch {
|
|
24897
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
24898
|
+
}
|
|
24899
|
+
roots.push(target);
|
|
24900
|
+
}
|
|
24901
|
+
return success(null, null, roots);
|
|
24902
|
+
};
|
|
24903
|
+
function captureCandidate(root, scope, environment = []) {
|
|
24904
|
+
let checkout;
|
|
24905
|
+
try {
|
|
24906
|
+
checkout = fs7.realpathSync(root);
|
|
24907
|
+
} catch {
|
|
24908
|
+
return failure("invalid_input", "candidate root does not exist");
|
|
24909
|
+
}
|
|
24910
|
+
const normalizedScope = canonicalScope(scope);
|
|
24911
|
+
if (!normalizedScope)
|
|
24912
|
+
return failure("invalid_input", "candidate scope is invalid: paths must be checkout-relative — to work in another repository, keep this task here and coordinate a separate linked task in that checkout", { fields: [{ path: "scope.paths", reason: "must be checkout-relative" }] });
|
|
24913
|
+
const roots = scopeRoots(checkout, normalizedScope);
|
|
24914
|
+
if (!roots.ok)
|
|
24915
|
+
return roots;
|
|
24916
|
+
const git = gitPaths(checkout);
|
|
24917
|
+
const names = new Set;
|
|
24918
|
+
let uncertain = git.uncertain;
|
|
24919
|
+
for (const target of roots.data) {
|
|
24920
|
+
const item = relative(checkout, target);
|
|
24921
|
+
if (item === ".git" || item === ".workit")
|
|
24922
|
+
continue;
|
|
24923
|
+
names.add(item);
|
|
24924
|
+
if (!fs7.existsSync(target))
|
|
24925
|
+
continue;
|
|
24926
|
+
let stat;
|
|
24927
|
+
try {
|
|
24928
|
+
stat = fs7.lstatSync(target);
|
|
24929
|
+
} catch {
|
|
24930
|
+
uncertain = true;
|
|
24931
|
+
continue;
|
|
24932
|
+
}
|
|
24933
|
+
if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
|
|
24934
|
+
uncertain = walk(checkout, target, names) || uncertain;
|
|
24935
|
+
}
|
|
24936
|
+
for (const item of git.paths)
|
|
24937
|
+
if (scopeMatches(item, normalizedScope))
|
|
24938
|
+
names.add(item);
|
|
24939
|
+
const files = [];
|
|
24940
|
+
let completeness = uncertain ? "uncertain" : "known";
|
|
24941
|
+
for (const item of [...names].sort()) {
|
|
24942
|
+
if (!scopeMatches(item, normalizedScope))
|
|
24943
|
+
continue;
|
|
24944
|
+
const target = path10.join(checkout, item);
|
|
24945
|
+
try {
|
|
24946
|
+
const stat = fs7.lstatSync(target);
|
|
24947
|
+
if (stat.isSymbolicLink()) {
|
|
24948
|
+
const link = fs7.readlinkSync(target);
|
|
24949
|
+
files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
|
|
24950
|
+
} else if (stat.isFile()) {
|
|
24951
|
+
files.push({
|
|
24952
|
+
path: item,
|
|
24953
|
+
kind: "file",
|
|
24954
|
+
digest: digestBytes3(fs7.readFileSync(target)),
|
|
24955
|
+
executable: (stat.mode & 73) !== 0
|
|
24956
|
+
});
|
|
24957
|
+
} else if (!stat.isDirectory()) {
|
|
24958
|
+
completeness = "uncertain";
|
|
24959
|
+
} else if (git.git && git.paths.includes(item)) {
|
|
24960
|
+
completeness = "uncertain";
|
|
24961
|
+
}
|
|
24962
|
+
} catch (error) {
|
|
24963
|
+
if (error?.code === "ENOENT") {
|
|
24964
|
+
files.push({ path: item, kind: "absent", digest: null, executable: null });
|
|
24965
|
+
} else
|
|
24966
|
+
completeness = "uncertain";
|
|
24967
|
+
}
|
|
24968
|
+
}
|
|
24969
|
+
const supplied = Array.isArray(environment) ? environment.map((value) => typeof value === "string" ? { name: value, value: process.env[value] ?? null } : { name: value.name, value: value.value ?? null }) : Object.keys(environment).map((name) => ({
|
|
24970
|
+
name,
|
|
24971
|
+
value: environment[name] ?? null
|
|
24972
|
+
}));
|
|
24973
|
+
const namesByEnvironment = supplied.map(({ name }) => name);
|
|
24974
|
+
if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
|
|
24975
|
+
return failure("invalid_input", "candidate environment names must be unique and non-empty");
|
|
24976
|
+
const values = supplied.sort((left, right) => compareCodeUnits3(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
24977
|
+
if (values.some(({ value }) => value === null))
|
|
24978
|
+
completeness = "uncertain";
|
|
24979
|
+
const headResult = spawnSync2("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
24980
|
+
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
24981
|
+
completeness = "uncertain";
|
|
24982
|
+
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
24983
|
+
const partial = {
|
|
24984
|
+
id: "0".repeat(64),
|
|
24985
|
+
scope: normalizedScope,
|
|
24986
|
+
completeness,
|
|
24987
|
+
files,
|
|
24988
|
+
environment: values,
|
|
24989
|
+
head
|
|
25092
24990
|
};
|
|
25093
|
-
|
|
24991
|
+
const candidate = { ...partial, id: candidateDigest(partial) };
|
|
24992
|
+
const parsed = candidateSchema.safeParse(candidate);
|
|
24993
|
+
return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
|
|
25094
24994
|
}
|
|
25095
|
-
var
|
|
25096
|
-
|
|
25097
|
-
|
|
25098
|
-
|
|
25099
|
-
|
|
25100
|
-
|
|
25101
|
-
|
|
25102
|
-
|
|
25103
|
-
|
|
25104
|
-
|
|
25105
|
-
|
|
25106
|
-
|
|
25107
|
-
|
|
25108
|
-
|
|
25109
|
-
|
|
25110
|
-
|
|
25111
|
-
|
|
25112
|
-
|
|
25113
|
-
|
|
25114
|
-
|
|
25115
|
-
|
|
25116
|
-
|
|
25117
|
-
|
|
25118
|
-
|
|
25119
|
-
|
|
25120
|
-
|
|
25121
|
-
|
|
25122
|
-
|
|
25123
|
-
|
|
25124
|
-
|
|
25125
|
-
|
|
25126
|
-
|
|
25127
|
-
|
|
25128
|
-
|
|
25129
|
-
|
|
25130
|
-
|
|
25131
|
-
|
|
25132
|
-
|
|
25133
|
-
|
|
25134
|
-
|
|
25135
|
-
|
|
25136
|
-
|
|
25137
|
-
|
|
25138
|
-
|
|
25139
|
-
|
|
25140
|
-
|
|
25141
|
-
|
|
25142
|
-
|
|
25143
|
-
|
|
25144
|
-
|
|
25145
|
-
|
|
25146
|
-
|
|
25147
|
-
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
|
|
25151
|
-
|
|
25152
|
-
|
|
25153
|
-
|
|
25154
|
-
|
|
25155
|
-
|
|
25156
|
-
|
|
25157
|
-
|
|
25158
|
-
|
|
25159
|
-
|
|
25160
|
-
|
|
25161
|
-
|
|
25162
|
-
|
|
25163
|
-
|
|
25164
|
-
|
|
25165
|
-
|
|
25166
|
-
|
|
25167
|
-
|
|
25168
|
-
|
|
25169
|
-
|
|
25170
|
-
|
|
25171
|
-
|
|
25172
|
-
|
|
25173
|
-
|
|
25174
|
-
|
|
25175
|
-
|
|
25176
|
-
|
|
25177
|
-
|
|
25178
|
-
|
|
25179
|
-
|
|
25180
|
-
|
|
25181
|
-
|
|
25182
|
-
|
|
25183
|
-
|
|
25184
|
-
|
|
25185
|
-
|
|
25186
|
-
|
|
25187
|
-
|
|
25188
|
-
|
|
25189
|
-
|
|
25190
|
-
|
|
25191
|
-
|
|
25192
|
-
|
|
25193
|
-
|
|
25194
|
-
|
|
25195
|
-
|
|
25196
|
-
|
|
25197
|
-
|
|
25198
|
-
|
|
25199
|
-
|
|
25200
|
-
|
|
25201
|
-
|
|
25202
|
-
|
|
25203
|
-
|
|
25204
|
-
|
|
25205
|
-
|
|
25206
|
-
|
|
25207
|
-
|
|
25208
|
-
|
|
25209
|
-
|
|
25210
|
-
|
|
25211
|
-
|
|
25212
|
-
|
|
25213
|
-
|
|
25214
|
-
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
25220
|
-
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
25221
|
-
before: "dependent_action",
|
|
25222
|
-
dependentAction: null,
|
|
25223
|
-
acceptanceAllowed: true
|
|
24995
|
+
var fileMap = (candidate) => new Map(candidate.files.map((file) => [file.path, file]));
|
|
24996
|
+
var changedPaths = (before, after) => {
|
|
24997
|
+
const paths = new Set([...before.files, ...after.files].map((file) => file.path));
|
|
24998
|
+
const left = fileMap(before);
|
|
24999
|
+
const right = fileMap(after);
|
|
25000
|
+
const changed = [...paths].filter((item) => JSON.stringify(left.get(item) ?? null) !== JSON.stringify(right.get(item) ?? null));
|
|
25001
|
+
if (before.head !== after.head)
|
|
25002
|
+
changed.push(".");
|
|
25003
|
+
if (JSON.stringify(before.environment) !== JSON.stringify(after.environment))
|
|
25004
|
+
changed.push(".");
|
|
25005
|
+
return changed;
|
|
25006
|
+
};
|
|
25007
|
+
var sessionFromRef = (value) => {
|
|
25008
|
+
if (typeof value !== "object" || value === null || value.kind !== "host")
|
|
25009
|
+
return null;
|
|
25010
|
+
return typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
|
|
25011
|
+
};
|
|
25012
|
+
var sameSession = (left, right) => {
|
|
25013
|
+
const other = sessionFromRef(right);
|
|
25014
|
+
return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
|
|
25015
|
+
};
|
|
25016
|
+
var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
|
|
25017
|
+
function evaluateEvidence(task, candidate) {
|
|
25018
|
+
const current = candidate ?? task.candidates.at(-1) ?? null;
|
|
25019
|
+
return task.evidence.map((entry) => {
|
|
25020
|
+
const evidence = entry.data;
|
|
25021
|
+
if (evidence.result === "failed")
|
|
25022
|
+
return {
|
|
25023
|
+
evidenceId: entry.id,
|
|
25024
|
+
status: "failed",
|
|
25025
|
+
reason: "failed evidence remains historical"
|
|
25026
|
+
};
|
|
25027
|
+
if (evidence.kind === "check" || evidence.kind === "review") {
|
|
25028
|
+
if (!evidence.beforeCandidateId || !evidence.candidateId)
|
|
25029
|
+
return {
|
|
25030
|
+
evidenceId: entry.id,
|
|
25031
|
+
status: "stale",
|
|
25032
|
+
reason: "check or review evidence lacks start and end candidates"
|
|
25033
|
+
};
|
|
25034
|
+
if (evidence.beforeCandidateId !== evidence.candidateId)
|
|
25035
|
+
return {
|
|
25036
|
+
evidenceId: entry.id,
|
|
25037
|
+
status: "stale",
|
|
25038
|
+
reason: "candidate changed during the check or review"
|
|
25039
|
+
};
|
|
25040
|
+
}
|
|
25041
|
+
if (!evidence.candidateId || !current)
|
|
25042
|
+
return {
|
|
25043
|
+
evidenceId: entry.id,
|
|
25044
|
+
status: evidence.result,
|
|
25045
|
+
reason: "evidence has no candidate binding"
|
|
25046
|
+
};
|
|
25047
|
+
const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
|
|
25048
|
+
if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
|
|
25049
|
+
return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
|
|
25050
|
+
if (observed.id === current.id)
|
|
25051
|
+
return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
|
|
25052
|
+
const scopes = evidenceScopes(task, evidence.requirementIds);
|
|
25053
|
+
const changed = changedPaths(observed, current);
|
|
25054
|
+
const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
|
|
25055
|
+
if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
|
|
25056
|
+
return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
|
|
25057
|
+
return {
|
|
25058
|
+
evidenceId: entry.id,
|
|
25059
|
+
status: evidence.result,
|
|
25060
|
+
reason: "candidate changed outside evidence scope"
|
|
25061
|
+
};
|
|
25062
|
+
});
|
|
25063
|
+
}
|
|
25064
|
+
var scopeCovers2 = scopeCovers;
|
|
25065
|
+
var checkPin = (assignment, beforeCandidateId, candidateId) => {
|
|
25066
|
+
const pin = assignment.candidateId ?? null;
|
|
25067
|
+
if (pin === null)
|
|
25068
|
+
return true;
|
|
25069
|
+
return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
|
|
25070
|
+
};
|
|
25071
|
+
var resolveBinding = (evidence, pin, currentCandidateId) => {
|
|
25072
|
+
const bound = evidence.kind === "check" || evidence.kind === "review";
|
|
25073
|
+
const fallback = bound ? pin ?? currentCandidateId : null;
|
|
25074
|
+
return {
|
|
25075
|
+
beforeCandidateId: evidence.beforeCandidateId ?? fallback,
|
|
25076
|
+
candidateId: evidence.candidateId ?? fallback
|
|
25077
|
+
};
|
|
25078
|
+
};
|
|
25079
|
+
var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
|
|
25080
|
+
var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
|
|
25081
|
+
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
25082
|
+
var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
|
|
25083
|
+
if (kind !== "passed")
|
|
25084
|
+
return false;
|
|
25085
|
+
if (dimension === "testing" || dimension === "verification")
|
|
25086
|
+
return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
|
|
25087
|
+
if (dimension === "review")
|
|
25088
|
+
return evidenceKind === "review";
|
|
25089
|
+
if (dimension === "investigation" || dimension === "challenge")
|
|
25090
|
+
return evidenceKind === "investigation";
|
|
25091
|
+
if (dimension === "artifacts" || dimension === "continuity")
|
|
25092
|
+
return evidenceKind === "artifact";
|
|
25093
|
+
return false;
|
|
25094
|
+
};
|
|
25095
|
+
var expectedKinds = (dimension, ruleId) => {
|
|
25096
|
+
if (dimension === "testing")
|
|
25097
|
+
return "check";
|
|
25098
|
+
if (dimension === "verification")
|
|
25099
|
+
return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
|
|
25100
|
+
if (dimension === "review")
|
|
25101
|
+
return "review";
|
|
25102
|
+
if (dimension === "investigation" || dimension === "challenge")
|
|
25103
|
+
return "investigation";
|
|
25104
|
+
if (dimension === "artifacts" || dimension === "continuity")
|
|
25105
|
+
return "artifact";
|
|
25106
|
+
if (dimension === "decisions")
|
|
25107
|
+
return "decision";
|
|
25108
|
+
return "worker report";
|
|
25109
|
+
};
|
|
25110
|
+
function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
|
|
25111
|
+
const evidence = evaluateEvidence(task, candidate);
|
|
25112
|
+
if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
|
|
25113
|
+
return task.policy.requirements.map((requirement) => ({
|
|
25114
|
+
requirementId: requirement.id,
|
|
25115
|
+
status: "unsatisfied",
|
|
25116
|
+
evidenceIds: [],
|
|
25117
|
+
decisionIds: [],
|
|
25118
|
+
reason: "stored policy version is unsupported; reassessment is required"
|
|
25224
25119
|
}));
|
|
25225
|
-
|
|
25226
|
-
|
|
25227
|
-
|
|
25228
|
-
|
|
25229
|
-
|
|
25230
|
-
|
|
25231
|
-
|
|
25232
|
-
|
|
25233
|
-
|
|
25234
|
-
|
|
25235
|
-
|
|
25236
|
-
|
|
25237
|
-
|
|
25238
|
-
|
|
25239
|
-
|
|
25240
|
-
|
|
25241
|
-
|
|
25242
|
-
|
|
25243
|
-
|
|
25244
|
-
|
|
25120
|
+
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
25121
|
+
const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
|
|
25122
|
+
const passed = related.filter(({ entry, evaluation }) => {
|
|
25123
|
+
if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
|
|
25124
|
+
return false;
|
|
25125
|
+
if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
|
|
25126
|
+
return false;
|
|
25127
|
+
if (requirement.dimension !== "review")
|
|
25128
|
+
return true;
|
|
25129
|
+
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
25130
|
+
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
25131
|
+
const sameEvidenceSession = related.some(({ entry: other }) => other.id !== entry.id && other.data.kind === "review" && other.data.candidateId !== null && other.data.candidateId === entry.data.candidateId && sameSession(reviewSession, other.provenance.session));
|
|
25132
|
+
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
25133
|
+
});
|
|
25134
|
+
if (passed.length) {
|
|
25135
|
+
const currentId = candidate?.id ?? null;
|
|
25136
|
+
const withRed = requirement.dimension === "testing" ? passed.filter(({ entry }) => related.some((other) => other.entry.data.kind === "check" && other.evaluation.status === "failed" && other.entry.data.requirementIds.includes(requirement.id) && other.entry.recordedAt <= entry.recordedAt) || related.some((baseline) => baseline.entry.id !== entry.id && baseline.entry.data.kind === "check" && baseline.entry.data.result === "passed" && baseline.entry.data.candidateId !== null && currentId !== null && baseline.entry.data.candidateId !== currentId)) : passed;
|
|
25137
|
+
if (withRed.length)
|
|
25138
|
+
return {
|
|
25139
|
+
requirementId: requirement.id,
|
|
25140
|
+
status: "satisfied",
|
|
25141
|
+
evidenceIds: withRed.map(({ entry }) => entry.id),
|
|
25142
|
+
decisionIds: [],
|
|
25143
|
+
reason: "fresh applicable evidence passed"
|
|
25144
|
+
};
|
|
25145
|
+
if (requirement.dimension === "testing")
|
|
25146
|
+
return {
|
|
25147
|
+
requirementId: requirement.id,
|
|
25148
|
+
status: "unsatisfied",
|
|
25149
|
+
evidenceIds: [],
|
|
25150
|
+
decisionIds: [],
|
|
25151
|
+
reason: "testing requirement has GREEN evidence but no preceding RED"
|
|
25152
|
+
};
|
|
25245
25153
|
}
|
|
25246
|
-
|
|
25247
|
-
|
|
25248
|
-
|
|
25154
|
+
if (requirement.dimension === "delegation") {
|
|
25155
|
+
const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
|
|
25156
|
+
const reconciled = task.workers.some((worker) => worker.data.report?.outcome === "completed" && (worker.data.assignment.requirementIds.includes(requirement.id) || scopeCovers2(dot(requirement.scope), dot(worker.data.assignment.scope))));
|
|
25157
|
+
if (reconciled)
|
|
25158
|
+
return {
|
|
25159
|
+
requirementId: requirement.id,
|
|
25160
|
+
status: "satisfied",
|
|
25161
|
+
evidenceIds: [],
|
|
25162
|
+
decisionIds: [],
|
|
25163
|
+
reason: "bounded helper completed and its result is recorded"
|
|
25164
|
+
};
|
|
25165
|
+
}
|
|
25166
|
+
const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
|
|
25167
|
+
if (decisions.length)
|
|
25168
|
+
return {
|
|
25169
|
+
requirementId: requirement.id,
|
|
25170
|
+
status: "satisfied",
|
|
25171
|
+
evidenceIds: [],
|
|
25172
|
+
decisionIds: decisions.map((decision) => decision.id),
|
|
25173
|
+
reason: "an applicable approved or stated decision satisfies the requirement"
|
|
25174
|
+
};
|
|
25175
|
+
const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
|
|
25176
|
+
if (limitations.length)
|
|
25177
|
+
return {
|
|
25178
|
+
requirementId: requirement.id,
|
|
25179
|
+
status: "accepted_limitation",
|
|
25180
|
+
evidenceIds: [],
|
|
25181
|
+
decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
|
|
25182
|
+
reason: "an applicable approved limitation permits the missing evidence"
|
|
25183
|
+
};
|
|
25184
|
+
const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
|
|
25185
|
+
if (!unavailable && related.length > 0 && !passed.length)
|
|
25186
|
+
return {
|
|
25187
|
+
requirementId: requirement.id,
|
|
25188
|
+
status: "unsatisfied",
|
|
25189
|
+
evidenceIds: related.map(({ entry }) => entry.id),
|
|
25190
|
+
decisionIds: [],
|
|
25191
|
+
reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
|
|
25192
|
+
};
|
|
25193
|
+
return {
|
|
25194
|
+
requirementId: requirement.id,
|
|
25195
|
+
status: unavailable ? "unavailable" : "unsatisfied",
|
|
25196
|
+
evidenceIds: related.map(({ entry }) => entry.id),
|
|
25197
|
+
decisionIds: [],
|
|
25198
|
+
reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
|
|
25199
|
+
};
|
|
25200
|
+
});
|
|
25201
|
+
}
|
|
25202
|
+
function evaluateClosure(requestedOutcome, view) {
|
|
25203
|
+
if (requestedOutcome !== "stopped" && !view.task.policy)
|
|
25204
|
+
return failure("requirements_unsatisfied", "task has not been assessed");
|
|
25205
|
+
if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
|
|
25206
|
+
return failure("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
|
|
25207
|
+
const evaluations = view.requirements;
|
|
25208
|
+
const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
|
|
25209
|
+
if (requestedOutcome !== "stopped" && openFindings.length)
|
|
25210
|
+
return failure("requirements_unsatisfied", "open findings must be resolved before closure");
|
|
25211
|
+
if (requestedOutcome !== "stopped") {
|
|
25212
|
+
const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
|
|
25213
|
+
for (const entry of view.task.findings) {
|
|
25214
|
+
if (entry.data.disposition === "fixed") {
|
|
25215
|
+
const verified = entry.data.resolution?.evidenceIds.some((id) => {
|
|
25216
|
+
const evidence = view.task.evidence.find((item) => item.id === id);
|
|
25217
|
+
if (!evidence)
|
|
25218
|
+
return false;
|
|
25219
|
+
return findingVerificationPasses(entry.data.candidateId, {
|
|
25220
|
+
kind: evidence.data.kind,
|
|
25221
|
+
candidateId: evidence.data.candidateId,
|
|
25222
|
+
status: evidenceById.get(id) ?? "missing"
|
|
25223
|
+
});
|
|
25224
|
+
});
|
|
25225
|
+
if (!verified)
|
|
25226
|
+
return failure("requirements_unsatisfied", "fixed findings require current verification");
|
|
25227
|
+
}
|
|
25228
|
+
if (entry.data.disposition === "deferred") {
|
|
25229
|
+
const valid = entry.data.resolution?.decisionIds.some((id) => {
|
|
25230
|
+
const decisionEntry = view.task.decisions.find((item) => item.id === id);
|
|
25231
|
+
const decision = decisionEntry?.data;
|
|
25232
|
+
return Boolean(decision && decisionEntry?.provenance.kind !== "imported" && decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.digest === decisionDigest(decision) && verifyDecisionContentAtRoot(view.workspace.root, decision.binding).ok && decision.binding.taskId === view.task.id && decision.binding.workspaceId === view.workspace.id && scopeCovers2(decision.binding.scope, entry.data.scope) && decision.requirementIds.some((requirementId) => view.task.policy?.requirements.some((requirement) => requirement.id === requirementId && requirement.acceptanceAllowed)));
|
|
25233
|
+
});
|
|
25234
|
+
if (!valid)
|
|
25235
|
+
return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
|
|
25236
|
+
}
|
|
25237
|
+
}
|
|
25238
|
+
}
|
|
25239
|
+
const blocking = evaluations.filter((item) => {
|
|
25240
|
+
if (item.status !== "unsatisfied" && item.status !== "unavailable")
|
|
25249
25241
|
return false;
|
|
25250
|
-
|
|
25251
|
-
return
|
|
25242
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
25243
|
+
return !requirement || requirement.before === "close";
|
|
25244
|
+
});
|
|
25245
|
+
if (requestedOutcome !== "stopped" && blocking.length)
|
|
25246
|
+
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
25247
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
25248
|
+
requirements: blocking.map((item) => {
|
|
25249
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
25250
|
+
return {
|
|
25251
|
+
requirementId: item.requirementId,
|
|
25252
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
25253
|
+
reason: item.reason,
|
|
25254
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
25255
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
25256
|
+
};
|
|
25257
|
+
})
|
|
25258
|
+
});
|
|
25259
|
+
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
25260
|
+
if (requestedOutcome === "verified" && accepted.length)
|
|
25261
|
+
return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
|
|
25262
|
+
requirementIds: accepted.map((item) => item.requirementId)
|
|
25263
|
+
});
|
|
25264
|
+
const evidenceIds = [
|
|
25265
|
+
...new Set([
|
|
25266
|
+
...evaluations.flatMap((item) => item.evidenceIds),
|
|
25267
|
+
...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
|
|
25268
|
+
])
|
|
25269
|
+
];
|
|
25270
|
+
const decisionIds = [
|
|
25271
|
+
...new Set([
|
|
25272
|
+
...evaluations.flatMap((item) => item.decisionIds),
|
|
25273
|
+
...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
|
|
25274
|
+
])
|
|
25275
|
+
];
|
|
25276
|
+
return success(null, null, {
|
|
25277
|
+
outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
|
|
25278
|
+
evidenceIds,
|
|
25279
|
+
decisionIds,
|
|
25280
|
+
requirementIds: evaluations.map((item) => item.requirementId)
|
|
25252
25281
|
});
|
|
25253
25282
|
}
|
|
25254
|
-
|
|
25255
|
-
|
|
25256
|
-
|
|
25257
|
-
|
|
25258
|
-
|
|
25259
|
-
|
|
25260
|
-
|
|
25261
|
-
|
|
25262
|
-
|
|
25263
|
-
}
|
|
25264
|
-
|
|
25265
|
-
|
|
25266
|
-
|
|
25267
|
-
|
|
25283
|
+
|
|
25284
|
+
// packages/workit-core/src/core/methods.ts
|
|
25285
|
+
var METHODS = {
|
|
25286
|
+
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
25287
|
+
"workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
|
|
25288
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
|
|
25289
|
+
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
25290
|
+
"workit-implement": { dimensions: ["delegation"] },
|
|
25291
|
+
"workit-debug": {},
|
|
25292
|
+
"workit-handoff": {},
|
|
25293
|
+
"workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
|
|
25294
|
+
};
|
|
25295
|
+
var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
|
|
25296
|
+
var assuranceFor = (id, definition, capabilities) => {
|
|
25297
|
+
const keys = new Set([
|
|
25298
|
+
id,
|
|
25299
|
+
...definition.dimensions ?? [],
|
|
25300
|
+
...definition.ruleIds ?? []
|
|
25301
|
+
]);
|
|
25302
|
+
const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
|
|
25303
|
+
if (id === "workit-review" && matches.length === 0)
|
|
25304
|
+
return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
|
|
25305
|
+
if (matches.some((capability) => capability.assurance === "unavailable"))
|
|
25306
|
+
return {
|
|
25307
|
+
assurance: "unavailable",
|
|
25308
|
+
reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
|
|
25309
|
+
};
|
|
25310
|
+
if (matches.some((capability) => capability.assurance === "enforced"))
|
|
25311
|
+
return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
|
|
25312
|
+
return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
|
|
25313
|
+
};
|
|
25314
|
+
function selectMethods(policy, capabilities) {
|
|
25315
|
+
return Object.entries(METHODS).flatMap(([id, definition]) => {
|
|
25316
|
+
const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
|
|
25317
|
+
if (requirements.length === 0)
|
|
25318
|
+
return [];
|
|
25319
|
+
const capability = assuranceFor(id, definition, capabilities);
|
|
25320
|
+
const reasons = [
|
|
25321
|
+
...new Set(requirements.map((requirement) => requirement.reason)),
|
|
25322
|
+
...capability.reasons
|
|
25323
|
+
];
|
|
25324
|
+
if (capability.assurance === "unavailable")
|
|
25325
|
+
reasons.unshift("required capability is unavailable");
|
|
25326
|
+
return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
|
|
25327
|
+
});
|
|
25268
25328
|
}
|
|
25269
|
-
|
|
25270
|
-
|
|
25271
|
-
|
|
25272
|
-
if (
|
|
25273
|
-
|
|
25274
|
-
const
|
|
25275
|
-
|
|
25276
|
-
|
|
25277
|
-
const
|
|
25278
|
-
|
|
25279
|
-
|
|
25280
|
-
|
|
25281
|
-
|
|
25282
|
-
|
|
25283
|
-
|
|
25284
|
-
|
|
25285
|
-
|
|
25286
|
-
|
|
25287
|
-
|
|
25288
|
-
|
|
25289
|
-
|
|
25290
|
-
|
|
25291
|
-
|
|
25292
|
-
throw new TypeError("policy change is invalid");
|
|
25293
|
-
return parsed.data;
|
|
25329
|
+
|
|
25330
|
+
// packages/workit-core/src/core/task-context.ts
|
|
25331
|
+
function reconcileResume(view, observations = []) {
|
|
25332
|
+
if (observations.length > 0)
|
|
25333
|
+
return failure("permission_denied", "worker observations require native host verification");
|
|
25334
|
+
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
25335
|
+
if (!candidate.ok)
|
|
25336
|
+
return candidate;
|
|
25337
|
+
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
25338
|
+
const blockers = [...view.task.progress.blockers];
|
|
25339
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
25340
|
+
blockers.push({
|
|
25341
|
+
reason: "worker state requires reconciliation",
|
|
25342
|
+
dependentAction: "resume",
|
|
25343
|
+
refs: []
|
|
25344
|
+
});
|
|
25345
|
+
return success(null, null, {
|
|
25346
|
+
candidate: candidate.data,
|
|
25347
|
+
staleEvidenceIds,
|
|
25348
|
+
workerUpdates: [],
|
|
25349
|
+
blockers,
|
|
25350
|
+
reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
|
|
25351
|
+
});
|
|
25294
25352
|
}
|
|
25353
|
+
var COMPACT_MAX_BYTES = 4096;
|
|
25354
|
+
var COMPACT_MAX_ITEMS = 8;
|
|
25355
|
+
var COMPACT_TEXT_BYTES = 128;
|
|
25356
|
+
var sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
|
|
25357
|
+
var sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
|
|
25358
|
+
var privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
|
|
25359
|
+
var compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
|
|
25360
|
+
const separator = match.search(/[:=]/u);
|
|
25361
|
+
if (separator >= 0)
|
|
25362
|
+
return `${match.slice(0, separator)}=<redacted>`;
|
|
25363
|
+
const space = match.search(/\s/u);
|
|
25364
|
+
return `${match.slice(0, space)} <redacted>`;
|
|
25365
|
+
}).slice(0, limit);
|
|
25366
|
+
var compactBytes = (value) => new TextEncoder().encode(value).byteLength;
|
|
25367
|
+
var compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
|
|
25368
|
+
var compactDecisionChoice = (entry) => {
|
|
25369
|
+
const stated = entry.data.binding.statedChoice?.text;
|
|
25370
|
+
const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
|
|
25371
|
+
const source = stated?.trim() || approved.trim() || null;
|
|
25372
|
+
return compactText(source, COMPACT_TEXT_BYTES);
|
|
25373
|
+
};
|
|
25374
|
+
var compactReference = (ref) => {
|
|
25375
|
+
if (ref.kind === "file")
|
|
25376
|
+
return {
|
|
25377
|
+
kind: "file",
|
|
25378
|
+
path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
|
|
25379
|
+
};
|
|
25380
|
+
if (ref.kind === "record")
|
|
25381
|
+
return {
|
|
25382
|
+
kind: "record",
|
|
25383
|
+
collection: ref.collection,
|
|
25384
|
+
id: ref.id
|
|
25385
|
+
};
|
|
25386
|
+
return { kind: ref.kind };
|
|
25387
|
+
};
|
|
25388
|
+
function compactTaskContext(view) {
|
|
25389
|
+
const decisions = view.task.decisions.slice().sort((left, right) => {
|
|
25390
|
+
const leftAt = compactTimestamp(left.recordedAt);
|
|
25391
|
+
const rightAt = compactTimestamp(right.recordedAt);
|
|
25392
|
+
if (leftAt !== rightAt)
|
|
25393
|
+
return leftAt > rightAt ? -1 : 1;
|
|
25394
|
+
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
25395
|
+
}).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
|
|
25396
|
+
id: entry.id,
|
|
25397
|
+
purpose: entry.data.purpose,
|
|
25398
|
+
status: entry.data.response,
|
|
25399
|
+
digest: entry.data.digest,
|
|
25400
|
+
choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
|
|
25401
|
+
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
|
|
25402
|
+
}));
|
|
25403
|
+
const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
25404
|
+
id: method.id,
|
|
25405
|
+
assurance: method.assurance,
|
|
25406
|
+
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
25407
|
+
})) : [];
|
|
25408
|
+
const gaps = Array.from(new Set([
|
|
25409
|
+
...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
|
|
25410
|
+
...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
25411
|
+
...view.task.progress.blockers.map((entry) => entry.reason)
|
|
25412
|
+
])).sort().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
|
|
25413
|
+
const context = {
|
|
25414
|
+
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
25415
|
+
status: view.task.status,
|
|
25416
|
+
decisions,
|
|
25417
|
+
methods,
|
|
25418
|
+
gaps,
|
|
25419
|
+
nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
|
|
25420
|
+
};
|
|
25421
|
+
let encoded = JSON.stringify(context);
|
|
25422
|
+
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
|
|
25423
|
+
if (context.gaps.length)
|
|
25424
|
+
context.gaps.pop();
|
|
25425
|
+
else if (context.decisions.length)
|
|
25426
|
+
context.decisions.pop();
|
|
25427
|
+
else
|
|
25428
|
+
context.methods.pop();
|
|
25429
|
+
encoded = JSON.stringify(context);
|
|
25430
|
+
}
|
|
25431
|
+
if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
|
|
25432
|
+
context.objective = compactText(context.objective, 64) ?? "";
|
|
25433
|
+
context.nextAction = compactText(context.nextAction, 64);
|
|
25434
|
+
encoded = JSON.stringify(context);
|
|
25435
|
+
}
|
|
25436
|
+
return encoded;
|
|
25437
|
+
}
|
|
25438
|
+
var exportDigest = (bundle) => sha256({
|
|
25439
|
+
schemaVersion: bundle.schemaVersion,
|
|
25440
|
+
exportedAt: bundle.exportedAt,
|
|
25441
|
+
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
25442
|
+
task: bundle.task
|
|
25443
|
+
});
|
|
25295
25444
|
|
|
25296
25445
|
// packages/workit-core/src/core/task-engine.ts
|
|
25297
25446
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
@@ -26979,14 +27128,29 @@ class WorkitCore {
|
|
|
26979
27128
|
writer: evaluationWorkspace.writer
|
|
26980
27129
|
});
|
|
26981
27130
|
}
|
|
26982
|
-
|
|
27131
|
+
compactContext(taskId) {
|
|
27132
|
+
const root = this.contextRootError();
|
|
27133
|
+
if (!root.ok)
|
|
27134
|
+
return root;
|
|
27135
|
+
const task = this.store.readTask(taskId);
|
|
27136
|
+
if (!task.ok)
|
|
27137
|
+
return task;
|
|
27138
|
+
const helper = this.helperTaskGuard(task.data, true);
|
|
27139
|
+
if (!helper.ok)
|
|
27140
|
+
return helper;
|
|
27141
|
+
const view = this.view(task.data, false);
|
|
27142
|
+
if (!view.ok)
|
|
27143
|
+
return view;
|
|
27144
|
+
return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
|
|
27145
|
+
}
|
|
27146
|
+
view(task, capture = true) {
|
|
26983
27147
|
const workspace = this.store.readWorkspace();
|
|
26984
27148
|
if (!workspace.ok)
|
|
26985
27149
|
return workspace;
|
|
26986
27150
|
if (!workspace.data)
|
|
26987
27151
|
return failure("not_found", "workspace not found");
|
|
26988
27152
|
const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
|
|
26989
|
-
const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
|
|
27153
|
+
const current = historical ? success(null, null, historical) : capture ? captureCandidate(this.store.root, task.intent.data.scope, environment()) : success(null, null, task.candidates.at(-1) ?? null);
|
|
26990
27154
|
if (!current.ok)
|
|
26991
27155
|
return current;
|
|
26992
27156
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
@@ -27258,6 +27422,122 @@ class WorkitCore {
|
|
|
27258
27422
|
return this.summary(changed.data.task);
|
|
27259
27423
|
}
|
|
27260
27424
|
}
|
|
27425
|
+
|
|
27426
|
+
// packages/workit-core/src/core/session-context.ts
|
|
27427
|
+
var cache = new Map;
|
|
27428
|
+
// packages/workit-core/src/core/external-action.ts
|
|
27429
|
+
var chainStepSchema = union3([
|
|
27430
|
+
string5().min(1),
|
|
27431
|
+
object4({ branch: string5().min(1) }).strict(),
|
|
27432
|
+
object4({ pr: literal3(true) }).strict()
|
|
27433
|
+
]);
|
|
27434
|
+
var externalActionSchema = discriminatedUnion2("operation", [
|
|
27435
|
+
object4({
|
|
27436
|
+
operation: literal3("git.branch_setup"),
|
|
27437
|
+
payload: object4({
|
|
27438
|
+
action: _enum2(["setup", "reapply_stash"]).optional(),
|
|
27439
|
+
sdd_dir: string5().optional(),
|
|
27440
|
+
target_branch: string5().optional(),
|
|
27441
|
+
stash: _enum2(["yes", "no"]).optional(),
|
|
27442
|
+
cwd: string5().min(1).optional()
|
|
27443
|
+
}).strict()
|
|
27444
|
+
}).strict(),
|
|
27445
|
+
object4({
|
|
27446
|
+
operation: literal3("git.commit"),
|
|
27447
|
+
payload: object4({
|
|
27448
|
+
message: string5().min(1).optional(),
|
|
27449
|
+
plan_steps: array3(chainStepSchema).min(1).max(32).optional(),
|
|
27450
|
+
plan_branch: string5().min(1).optional(),
|
|
27451
|
+
cwd: string5().min(1).optional()
|
|
27452
|
+
}).strict()
|
|
27453
|
+
}).strict(),
|
|
27454
|
+
object4({
|
|
27455
|
+
operation: literal3("git.push"),
|
|
27456
|
+
payload: object4({ branch: string5().min(1).optional(), cwd: string5().min(1).optional() }).strict()
|
|
27457
|
+
}).strict(),
|
|
27458
|
+
object4({
|
|
27459
|
+
operation: literal3("hosting.pull_request"),
|
|
27460
|
+
payload: object4({
|
|
27461
|
+
title: string5().min(1),
|
|
27462
|
+
body: string5().optional(),
|
|
27463
|
+
draft: boolean5().optional(),
|
|
27464
|
+
target_branch: string5().optional(),
|
|
27465
|
+
babysit: boolean5().optional(),
|
|
27466
|
+
cwd: string5().min(1).optional()
|
|
27467
|
+
}).strict()
|
|
27468
|
+
}).strict(),
|
|
27469
|
+
object4({
|
|
27470
|
+
operation: literal3("hosting.merge"),
|
|
27471
|
+
payload: object4({
|
|
27472
|
+
target_branch: string5().min(1).optional(),
|
|
27473
|
+
source_branch: string5().min(1).optional(),
|
|
27474
|
+
cwd: string5().min(1).optional()
|
|
27475
|
+
}).strict()
|
|
27476
|
+
}).strict(),
|
|
27477
|
+
object4({
|
|
27478
|
+
operation: literal3("hosting.delete_branch"),
|
|
27479
|
+
payload: object4({ branch: string5().min(1), cwd: string5().min(1).optional() }).strict()
|
|
27480
|
+
}).strict(),
|
|
27481
|
+
object4({
|
|
27482
|
+
operation: literal3("youtrack.update"),
|
|
27483
|
+
payload: object4({
|
|
27484
|
+
issueId: string5().min(1),
|
|
27485
|
+
markdown: string5().min(1),
|
|
27486
|
+
minutes: number5().positive().optional()
|
|
27487
|
+
}).strict()
|
|
27488
|
+
}).strict(),
|
|
27489
|
+
object4({
|
|
27490
|
+
operation: literal3("youtrack.time"),
|
|
27491
|
+
payload: object4({
|
|
27492
|
+
issueId: string5().min(1),
|
|
27493
|
+
minutes: number5().positive(),
|
|
27494
|
+
text: string5().optional(),
|
|
27495
|
+
dateMs: number5().finite().optional()
|
|
27496
|
+
}).strict()
|
|
27497
|
+
}).strict(),
|
|
27498
|
+
object4({
|
|
27499
|
+
operation: literal3("youtrack.meeting"),
|
|
27500
|
+
payload: object4({
|
|
27501
|
+
issueId: string5().min(1),
|
|
27502
|
+
minutes: number5().positive(),
|
|
27503
|
+
text: string5().min(1)
|
|
27504
|
+
}).strict()
|
|
27505
|
+
}).strict(),
|
|
27506
|
+
object4({
|
|
27507
|
+
operation: literal3("changelog.apply"),
|
|
27508
|
+
payload: object4({
|
|
27509
|
+
entries: union3([
|
|
27510
|
+
record3(string5(), array3(string5())),
|
|
27511
|
+
array3(object4({ category: string5().min(1), text: string5().min(1) }).strict())
|
|
27512
|
+
]).optional(),
|
|
27513
|
+
path: string5().optional(),
|
|
27514
|
+
normalize_only: boolean5().optional()
|
|
27515
|
+
}).strict()
|
|
27516
|
+
}).strict(),
|
|
27517
|
+
object4({
|
|
27518
|
+
operation: literal3("context.read"),
|
|
27519
|
+
payload: object4({
|
|
27520
|
+
kind: _enum2([
|
|
27521
|
+
"git",
|
|
27522
|
+
"pr",
|
|
27523
|
+
"youtrack",
|
|
27524
|
+
"github_issue",
|
|
27525
|
+
"gitlab_issue",
|
|
27526
|
+
"changelog",
|
|
27527
|
+
"release",
|
|
27528
|
+
"affected"
|
|
27529
|
+
]),
|
|
27530
|
+
range: string5().optional(),
|
|
27531
|
+
issueId: string5().optional(),
|
|
27532
|
+
issueUrl: string5().optional(),
|
|
27533
|
+
issueRef: string5().optional(),
|
|
27534
|
+
mode: string5().optional(),
|
|
27535
|
+
specPath: string5().optional(),
|
|
27536
|
+
planPath: string5().optional(),
|
|
27537
|
+
cwd: string5().min(1).optional()
|
|
27538
|
+
}).strict()
|
|
27539
|
+
}).strict()
|
|
27540
|
+
]);
|
|
27261
27541
|
// packages/workit-core/src/core.ts
|
|
27262
27542
|
var revision2 = /^[A-Za-z0-9@][A-Za-z0-9@._/~^{}-]*$/;
|
|
27263
27543
|
function gitRevisionParts(value) {
|
|
@@ -28028,56 +28308,19 @@ var readYouTrackContextConfig = () => {
|
|
|
28028
28308
|
return { error: "YouTrack configuration is unavailable" };
|
|
28029
28309
|
return { data: readOnlyYouTrackConfig(parsed) };
|
|
28030
28310
|
};
|
|
28031
|
-
|
|
28032
|
-
|
|
28033
|
-
|
|
28034
|
-
|
|
28035
|
-
|
|
28036
|
-
|
|
28037
|
-
|
|
28038
|
-
|
|
28039
|
-
|
|
28040
|
-
|
|
28041
|
-
|
|
28042
|
-
return
|
|
28043
|
-
}
|
|
28044
|
-
function youTrackGreeting(configOverride) {
|
|
28045
|
-
const cfgPath = configOverride ?? youTrackConfigPath();
|
|
28046
|
-
let parsed;
|
|
28047
|
-
try {
|
|
28048
|
-
parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
|
|
28049
|
-
} catch {
|
|
28050
|
-
return {
|
|
28051
|
-
stdout: "",
|
|
28052
|
-
exitCode: 1,
|
|
28053
|
-
stderr: fs9.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
|
|
28054
|
-
};
|
|
28055
|
-
}
|
|
28056
|
-
if (!isConfigObject(parsed)) {
|
|
28057
|
-
return { stdout: "", exitCode: 1, stderr: `${cfgPath} is not a JSON object` };
|
|
28058
|
-
}
|
|
28059
|
-
const config = parsed;
|
|
28060
|
-
try {
|
|
28061
|
-
const tz = String(config.timezone ?? "America/Santiago");
|
|
28062
|
-
const now = new Date;
|
|
28063
|
-
const { y, m, d, hour, minute } = tzParts(now, tz);
|
|
28064
|
-
const cutoff = String(config.greetingCutoff ?? "12:00").split(":");
|
|
28065
|
-
const cutoffHour = Number(cutoff[0]);
|
|
28066
|
-
const cutoffMinute = Number(cutoff[1] ?? 0);
|
|
28067
|
-
const greetings = config.greetings ?? {};
|
|
28068
|
-
const isMorning = Number(hour) < cutoffHour || Number(hour) === cutoffHour && Number(minute) < cutoffMinute;
|
|
28069
|
-
const greeting = isMorning ? greetings.morning ?? "buenos días" : greetings.afternoon ?? "buenas tardes";
|
|
28070
|
-
const mention = String(config.defaultMention ?? "Alejandra.Flores");
|
|
28071
|
-
return { stdout: `@${mention} Hola, ${greeting}.
|
|
28072
|
-
`, exitCode: 0, stderr: "" };
|
|
28073
|
-
} catch (err) {
|
|
28074
|
-
return {
|
|
28075
|
-
stdout: "",
|
|
28076
|
-
exitCode: 1,
|
|
28077
|
-
stderr: err instanceof Error ? err.message : "greeting failed"
|
|
28078
|
-
};
|
|
28079
|
-
}
|
|
28080
|
-
}
|
|
28311
|
+
var processTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
28312
|
+
var effectiveWorkTimezone = (cfg) => {
|
|
28313
|
+
const configured = cfg?.timezone;
|
|
28314
|
+
return typeof configured === "string" && configured.trim() ? { timezone: configured.trim(), source: "youtrack.json" } : { timezone: processTimezone(), source: "process" };
|
|
28315
|
+
};
|
|
28316
|
+
var DEFAULT_MEETING_WORK_ITEM_TEXT = "Meetings";
|
|
28317
|
+
var meetingWorkItemText = (cfg, item) => {
|
|
28318
|
+
if (typeof item?.workItemText === "string" && item.workItemText)
|
|
28319
|
+
return item.workItemText;
|
|
28320
|
+
if (typeof cfg?.meetingWorkItemText === "string" && cfg.meetingWorkItemText)
|
|
28321
|
+
return cfg.meetingWorkItemText;
|
|
28322
|
+
return DEFAULT_MEETING_WORK_ITEM_TEXT;
|
|
28323
|
+
};
|
|
28081
28324
|
function youTrackParseDuration(text) {
|
|
28082
28325
|
const lower = String(text).toLowerCase().trim();
|
|
28083
28326
|
let total = 0;
|
|
@@ -28154,7 +28397,6 @@ function parseIssueRef(input) {
|
|
|
28154
28397
|
}
|
|
28155
28398
|
var contextScripts = {
|
|
28156
28399
|
config: readYouTrackContextConfig,
|
|
28157
|
-
greeting: () => youTrackGreeting(youTrackReadOnlyConfigPath()),
|
|
28158
28400
|
parseDuration: (text) => youTrackParseDuration(text),
|
|
28159
28401
|
api: () => ({ error: "YouTrack context is read-only" })
|
|
28160
28402
|
};
|
|
@@ -28183,7 +28425,7 @@ function meetingOptionsFromConfig(cfg) {
|
|
|
28183
28425
|
key,
|
|
28184
28426
|
issue: item.issue,
|
|
28185
28427
|
label: item.label ?? item.issue,
|
|
28186
|
-
workItemText: item
|
|
28428
|
+
workItemText: meetingWorkItemText(cfg, item),
|
|
28187
28429
|
url: item.url ?? (base && item.issue ? `${base}/issue/${item.issue}` : null)
|
|
28188
28430
|
}));
|
|
28189
28431
|
}
|
|
@@ -28193,7 +28435,7 @@ function meetingOptionsFromConfig(cfg) {
|
|
|
28193
28435
|
key: "general",
|
|
28194
28436
|
issue,
|
|
28195
28437
|
label: "General meetings",
|
|
28196
|
-
workItemText:
|
|
28438
|
+
workItemText: meetingWorkItemText(cfg),
|
|
28197
28439
|
url: base && issue ? `${base}/issue/${issue}` : null
|
|
28198
28440
|
}
|
|
28199
28441
|
];
|
|
@@ -28215,17 +28457,9 @@ var readOnlyYouTrackConfig = (cfg) => {
|
|
|
28215
28457
|
const baseUrl = publicUrl(cfg.baseUrl);
|
|
28216
28458
|
if (baseUrl)
|
|
28217
28459
|
safe.baseUrl = baseUrl;
|
|
28218
|
-
for (const key of ["timezone", "
|
|
28460
|
+
for (const key of ["timezone", "meetingIssue", "meetingWorkItemText"])
|
|
28219
28461
|
if (typeof cfg[key] === "string")
|
|
28220
28462
|
safe[key] = cfg[key];
|
|
28221
|
-
if (cfg.greetings && typeof cfg.greetings === "object" && !Array.isArray(cfg.greetings)) {
|
|
28222
|
-
const greetings = {};
|
|
28223
|
-
for (const key of ["morning", "afternoon"])
|
|
28224
|
-
if (typeof cfg.greetings[key] === "string")
|
|
28225
|
-
greetings[key] = cfg.greetings[key];
|
|
28226
|
-
if (Object.keys(greetings).length)
|
|
28227
|
-
safe.greetings = greetings;
|
|
28228
|
-
}
|
|
28229
28463
|
if (cfg.meetingIssues && typeof cfg.meetingIssues === "object" && !Array.isArray(cfg.meetingIssues)) {
|
|
28230
28464
|
safe.meetingIssues = Object.fromEntries(Object.entries(cfg.meetingIssues).flatMap(([key, value]) => {
|
|
28231
28465
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
@@ -28255,16 +28489,12 @@ function context({
|
|
|
28255
28489
|
const cfg = scripts.config();
|
|
28256
28490
|
if (cfg.error)
|
|
28257
28491
|
return { error: cfg.error };
|
|
28258
|
-
const greeting = scripts.greeting();
|
|
28259
|
-
if (greeting.exitCode !== 0) {
|
|
28260
|
-
return { error: (greeting.stderr || greeting.stdout || "greeting failed").trim() };
|
|
28261
|
-
}
|
|
28262
28492
|
const safeConfig = readOnlyYouTrackConfig(cfg.data);
|
|
28263
28493
|
const meetingOptions = meetingOptionsFromConfig(safeConfig);
|
|
28264
28494
|
if (mode === "meetings" && !issue_id && !issue_url && !issue_ref) {
|
|
28265
28495
|
return {
|
|
28266
28496
|
config: safeConfig,
|
|
28267
|
-
|
|
28497
|
+
workTimezone: effectiveWorkTimezone(safeConfig),
|
|
28268
28498
|
mode: "meetings",
|
|
28269
28499
|
requiresMeetingChoice: true,
|
|
28270
28500
|
meetingOptions,
|
|
@@ -28293,7 +28523,7 @@ function context({
|
|
|
28293
28523
|
const selectedMeeting = meetingOptions.find((m) => m.issue === issue);
|
|
28294
28524
|
return {
|
|
28295
28525
|
config: safeConfig,
|
|
28296
|
-
|
|
28526
|
+
workTimezone: effectiveWorkTimezone(safeConfig),
|
|
28297
28527
|
issueId: issue,
|
|
28298
28528
|
issueUrl,
|
|
28299
28529
|
mode: mode ?? (selectedMeeting ? "meetings" : "task"),
|