@brainervirus/workit-pi 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/worker.js +23 -6
- package/dist/workit.js +848 -264
- package/package.json +1 -1
- package/skills/workit-plan/SKILL.md +14 -2
package/dist/workit.js
CHANGED
|
@@ -3,8 +3,8 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
3
3
|
|
|
4
4
|
// packages/workit-core/src/core.ts
|
|
5
5
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
6
|
-
import { existsSync as
|
|
7
|
-
import
|
|
6
|
+
import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
|
|
7
|
+
import path7 from "node:path";
|
|
8
8
|
|
|
9
9
|
// packages/workit-core/src/core/task-contract.ts
|
|
10
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -7796,10 +7796,12 @@ var decisionSchema = object({
|
|
|
7796
7796
|
scope: scopeSchema,
|
|
7797
7797
|
presented: text,
|
|
7798
7798
|
approvedContent: text,
|
|
7799
|
-
|
|
7799
|
+
displayed: text.optional(),
|
|
7800
|
+
contentRefs: array(refSchema),
|
|
7801
|
+
statedChoice: object({ ref: text, text }).strict().optional()
|
|
7800
7802
|
}).strict(),
|
|
7801
7803
|
digest,
|
|
7802
|
-
response: _enum(["approved", "rejected"]),
|
|
7804
|
+
response: _enum(["approved", "rejected", "stated"]),
|
|
7803
7805
|
requirementIds: array(digest),
|
|
7804
7806
|
revoked: object({ at: utc, reason: text }).strict().nullable(),
|
|
7805
7807
|
consumption: object({
|
|
@@ -7808,6 +7810,27 @@ var decisionSchema = object({
|
|
|
7808
7810
|
actionRef: refSchema
|
|
7809
7811
|
}).strict().nullable()
|
|
7810
7812
|
}).strict();
|
|
7813
|
+
var BINDING_QUESTION_BUDGET = 300;
|
|
7814
|
+
var bindingQuestionHeader = /^Workit decision: (design|action|limitation|preference)$/;
|
|
7815
|
+
var workitBindingQuestionIssue = (questions) => {
|
|
7816
|
+
if (!Array.isArray(questions) || questions.length !== 1)
|
|
7817
|
+
return null;
|
|
7818
|
+
const question = questions[0];
|
|
7819
|
+
if (!question || typeof question !== "object")
|
|
7820
|
+
return null;
|
|
7821
|
+
if (typeof question.header !== "string" || !bindingQuestionHeader.test(question.header.trim()))
|
|
7822
|
+
return null;
|
|
7823
|
+
const over = (label, value) => `invalid_input: ${label} is ${value.length} characters; Workit binding questions must stay within ${BINDING_QUESTION_BUDGET}. present the item in the conversation first, then ask a short scoped question.`;
|
|
7824
|
+
if (typeof question.question === "string" && question.question.length > BINDING_QUESTION_BUDGET)
|
|
7825
|
+
return over("the question text", question.question);
|
|
7826
|
+
if (Array.isArray(question.options))
|
|
7827
|
+
for (const option of question.options) {
|
|
7828
|
+
const description = option.description;
|
|
7829
|
+
if (typeof description === "string" && description.length > BINDING_QUESTION_BUDGET)
|
|
7830
|
+
return over("an option description", description);
|
|
7831
|
+
}
|
|
7832
|
+
return null;
|
|
7833
|
+
};
|
|
7811
7834
|
var findingSchema = object({
|
|
7812
7835
|
claim: text,
|
|
7813
7836
|
consequence: text,
|
|
@@ -7874,6 +7897,10 @@ var actionProgressListSchema = array(actionProgressSchema).check((ctx) => {
|
|
|
7874
7897
|
path: ["decisionId"]
|
|
7875
7898
|
});
|
|
7876
7899
|
});
|
|
7900
|
+
var runtimeSchema = object({
|
|
7901
|
+
createdWith: text.nullable(),
|
|
7902
|
+
updatedWith: text
|
|
7903
|
+
}).strict();
|
|
7877
7904
|
var taskRecordSchema = object({
|
|
7878
7905
|
schemaVersion: literal(1),
|
|
7879
7906
|
id,
|
|
@@ -7887,6 +7914,8 @@ var taskRecordSchema = object({
|
|
|
7887
7914
|
status: _enum(["active", "paused", "closed"]),
|
|
7888
7915
|
closure: closureSchema.nullable(),
|
|
7889
7916
|
progress: progressSchema,
|
|
7917
|
+
pauseReason: text.nullable().optional(),
|
|
7918
|
+
runtime: runtimeSchema.optional(),
|
|
7890
7919
|
assessments: array(entrySchema(assessmentSchema)),
|
|
7891
7920
|
policy: policySchema.nullable(),
|
|
7892
7921
|
policyChanges: array(policyChangeSchema),
|
|
@@ -7902,6 +7931,7 @@ var workspaceRecordSchema = object({
|
|
|
7902
7931
|
id,
|
|
7903
7932
|
revision,
|
|
7904
7933
|
root: nonEmpty,
|
|
7934
|
+
runtime: runtimeSchema.optional(),
|
|
7905
7935
|
writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
7906
7936
|
}).strict();
|
|
7907
7937
|
var capabilitySchema = object({
|
|
@@ -7929,6 +7959,9 @@ var taskSummarySchema = object({
|
|
|
7929
7959
|
id,
|
|
7930
7960
|
revision,
|
|
7931
7961
|
workspaceRevision: revision,
|
|
7962
|
+
createdAt: utc,
|
|
7963
|
+
updatedAt: utc,
|
|
7964
|
+
runtime: runtimeSchema.nullable(),
|
|
7932
7965
|
objective: text,
|
|
7933
7966
|
status: _enum(["active", "paused", "closed"]),
|
|
7934
7967
|
closure: closureSchema.nullable(),
|
|
@@ -7999,7 +8032,7 @@ var taskOperations = {
|
|
|
7999
8032
|
expectedWorkspaceRevision: revision.optional(),
|
|
8000
8033
|
outcome: outcomeSchema,
|
|
8001
8034
|
summary: text,
|
|
8002
|
-
decisionIds: array(id)
|
|
8035
|
+
decisionIds: array(id).optional()
|
|
8003
8036
|
})
|
|
8004
8037
|
};
|
|
8005
8038
|
var policyOperations = {
|
|
@@ -8091,14 +8124,15 @@ var writerOperations = {
|
|
|
8091
8124
|
...taskId,
|
|
8092
8125
|
expectedRevision: revision.optional(),
|
|
8093
8126
|
expectedWorkspaceRevision: revision.optional(),
|
|
8094
|
-
workerId: nullableId.optional()
|
|
8127
|
+
workerId: nullableId.optional(),
|
|
8128
|
+
reason: text.optional()
|
|
8095
8129
|
}),
|
|
8096
8130
|
release: operation({
|
|
8097
8131
|
action: literal("release"),
|
|
8098
8132
|
...taskId,
|
|
8099
8133
|
expectedRevision: revision.optional(),
|
|
8100
8134
|
expectedWorkspaceRevision: revision.optional(),
|
|
8101
|
-
reason: text
|
|
8135
|
+
reason: text.optional()
|
|
8102
8136
|
})
|
|
8103
8137
|
};
|
|
8104
8138
|
var stateOperations = {
|
|
@@ -8305,7 +8339,26 @@ function requirementId(input) {
|
|
|
8305
8339
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
8306
8340
|
import * as fs4 from "node:fs";
|
|
8307
8341
|
import { hostname } from "node:os";
|
|
8308
|
-
import
|
|
8342
|
+
import path4 from "node:path";
|
|
8343
|
+
|
|
8344
|
+
// packages/workit-core/src/core/package-root.ts
|
|
8345
|
+
import { existsSync } from "node:fs";
|
|
8346
|
+
import path from "node:path";
|
|
8347
|
+
import { fileURLToPath } from "node:url";
|
|
8348
|
+
var packageRoot = () => {
|
|
8349
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
8350
|
+
while (true) {
|
|
8351
|
+
const parent = path.dirname(dir);
|
|
8352
|
+
if (existsSync(path.join(dir, "package.json")) || parent === dir)
|
|
8353
|
+
return dir;
|
|
8354
|
+
dir = parent;
|
|
8355
|
+
}
|
|
8356
|
+
};
|
|
8357
|
+
var assetRoot = () => {
|
|
8358
|
+
const root = packageRoot();
|
|
8359
|
+
const assets = path.join(root, "assets");
|
|
8360
|
+
return existsSync(assets) ? assets : root;
|
|
8361
|
+
};
|
|
8309
8362
|
|
|
8310
8363
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
8311
8364
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -8356,7 +8409,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
8356
8409
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
8357
8410
|
import { randomBytes } from "node:crypto";
|
|
8358
8411
|
import fsSync from "node:fs";
|
|
8359
|
-
import
|
|
8412
|
+
import path2 from "node:path";
|
|
8360
8413
|
|
|
8361
8414
|
// node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
8362
8415
|
import fs from "node:fs";
|
|
@@ -8446,13 +8499,13 @@ ${ownershipToken}
|
|
|
8446
8499
|
return { raw, ownershipToken };
|
|
8447
8500
|
}
|
|
8448
8501
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
8449
|
-
const resolved =
|
|
8450
|
-
const lexicalRelative =
|
|
8451
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
8452
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
8502
|
+
const resolved = path2.resolve(lockPath);
|
|
8503
|
+
const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
|
|
8504
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
|
|
8505
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
8453
8506
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
8454
8507
|
}
|
|
8455
|
-
return relative.split(
|
|
8508
|
+
return relative.split(path2.sep).join(path2.posix.sep);
|
|
8456
8509
|
}
|
|
8457
8510
|
function parseSidecarLockPayload(raw, parser) {
|
|
8458
8511
|
if (parser) {
|
|
@@ -8599,7 +8652,7 @@ function getFsSafeLockConfig() {
|
|
|
8599
8652
|
|
|
8600
8653
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
8601
8654
|
import fs3 from "node:fs";
|
|
8602
|
-
import
|
|
8655
|
+
import path3 from "node:path";
|
|
8603
8656
|
|
|
8604
8657
|
// node_modules/@openclaw/fs-safe/dist/timing.js
|
|
8605
8658
|
function sleepSync(ms) {
|
|
@@ -8690,26 +8743,26 @@ function canonicalLockParentSync(parent) {
|
|
|
8690
8743
|
return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
|
|
8691
8744
|
}
|
|
8692
8745
|
function normalizeTargetPath(targetPath) {
|
|
8693
|
-
const resolved =
|
|
8694
|
-
fs3.mkdirSync(
|
|
8746
|
+
const resolved = path3.resolve(targetPath);
|
|
8747
|
+
fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
|
|
8695
8748
|
try {
|
|
8696
|
-
return
|
|
8749
|
+
return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
|
|
8697
8750
|
} catch {
|
|
8698
8751
|
return resolved;
|
|
8699
8752
|
}
|
|
8700
8753
|
}
|
|
8701
8754
|
function boundedLockPath(lockPath, lockRoot) {
|
|
8702
|
-
const resolved =
|
|
8755
|
+
const resolved = path3.resolve(lockPath);
|
|
8703
8756
|
if (!lockRoot)
|
|
8704
8757
|
return resolved;
|
|
8705
8758
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
8706
|
-
const parent =
|
|
8759
|
+
const parent = path3.dirname(resolved);
|
|
8707
8760
|
const parentReal = canonicalLockParentSync(parent);
|
|
8708
|
-
const parentRelative =
|
|
8709
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
8761
|
+
const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
|
|
8762
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
|
|
8710
8763
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
8711
8764
|
}
|
|
8712
|
-
return
|
|
8765
|
+
return path3.join(parentReal, path3.basename(resolved));
|
|
8713
8766
|
}
|
|
8714
8767
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
8715
8768
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -8922,6 +8975,33 @@ var metadataLockSchema = object({
|
|
|
8922
8975
|
nonce: string2().min(1)
|
|
8923
8976
|
}).strict();
|
|
8924
8977
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
8978
|
+
var cachedRuntimeVersion = null;
|
|
8979
|
+
var runtimeVersion = () => {
|
|
8980
|
+
if (cachedRuntimeVersion === null) {
|
|
8981
|
+
try {
|
|
8982
|
+
const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
|
|
8983
|
+
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
8984
|
+
} catch {
|
|
8985
|
+
cachedRuntimeVersion = "0.0.0";
|
|
8986
|
+
}
|
|
8987
|
+
}
|
|
8988
|
+
return cachedRuntimeVersion;
|
|
8989
|
+
};
|
|
8990
|
+
var stampNew = (record) => ({
|
|
8991
|
+
createdWith: record.runtime?.createdWith ?? null,
|
|
8992
|
+
updatedWith: runtimeVersion()
|
|
8993
|
+
});
|
|
8994
|
+
var isNewerVersion = (candidate, current) => {
|
|
8995
|
+
const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
|
|
8996
|
+
const [next, now] = [parts(candidate), parts(current)];
|
|
8997
|
+
for (let index = 0;index < 3; index += 1) {
|
|
8998
|
+
const left = next[index] ?? 0;
|
|
8999
|
+
const right = now[index] ?? 0;
|
|
9000
|
+
if (left !== right)
|
|
9001
|
+
return left > right;
|
|
9002
|
+
}
|
|
9003
|
+
return false;
|
|
9004
|
+
};
|
|
8925
9005
|
var jsonBytes = (value) => `${canonicalJson(value)}
|
|
8926
9006
|
`;
|
|
8927
9007
|
var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
|
|
@@ -8948,12 +9028,12 @@ var sameMetadataLock = (left, right) => {
|
|
|
8948
9028
|
class TaskStore {
|
|
8949
9029
|
root;
|
|
8950
9030
|
constructor(root) {
|
|
8951
|
-
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) :
|
|
9031
|
+
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
|
|
8952
9032
|
}
|
|
8953
9033
|
readTask(taskId) {
|
|
8954
9034
|
if (!validId(taskId))
|
|
8955
9035
|
return failure("invalid_input", "task ID is invalid", { taskId });
|
|
8956
|
-
const result = this.readRecord(
|
|
9036
|
+
const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
|
|
8957
9037
|
if (!result.exists)
|
|
8958
9038
|
return failure("not_found", "task not found", { taskId });
|
|
8959
9039
|
if (result.result.ok && result.result.data.id !== taskId)
|
|
@@ -8987,7 +9067,7 @@ class TaskStore {
|
|
|
8987
9067
|
});
|
|
8988
9068
|
const tasks = [];
|
|
8989
9069
|
for (const name of names.sort()) {
|
|
8990
|
-
const item = this.readRecord(
|
|
9070
|
+
const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
|
|
8991
9071
|
if (!item.exists)
|
|
8992
9072
|
continue;
|
|
8993
9073
|
if (!item.result.ok)
|
|
@@ -9021,7 +9101,7 @@ class TaskStore {
|
|
|
9021
9101
|
if (!current.ok)
|
|
9022
9102
|
return current;
|
|
9023
9103
|
if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
|
|
9024
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
9104
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
9025
9105
|
expectedWorkspaceRevision: value.expectedWorkspaceRevision,
|
|
9026
9106
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
9027
9107
|
});
|
|
@@ -9033,11 +9113,17 @@ class TaskStore {
|
|
|
9033
9113
|
const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
|
|
9034
9114
|
if (current.data && !previousWorkspaceBytes)
|
|
9035
9115
|
return failure("storage_error", "workspace snapshot disappeared during creation");
|
|
9036
|
-
const workspace = current.data ? {
|
|
9116
|
+
const workspace = current.data ? {
|
|
9117
|
+
...current.data,
|
|
9118
|
+
revision: newRevision(),
|
|
9119
|
+
root: this.root,
|
|
9120
|
+
runtime: stampNew(current.data)
|
|
9121
|
+
} : {
|
|
9037
9122
|
schemaVersion: SCHEMA_VERSION,
|
|
9038
9123
|
id: newId(),
|
|
9039
9124
|
revision: newRevision(),
|
|
9040
9125
|
root: this.root,
|
|
9126
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
9041
9127
|
writer: null
|
|
9042
9128
|
};
|
|
9043
9129
|
const timestamp = value.now ?? now();
|
|
@@ -9059,6 +9145,7 @@ class TaskStore {
|
|
|
9059
9145
|
status: "active",
|
|
9060
9146
|
closure: null,
|
|
9061
9147
|
progress: { summary: "", nextAction: null, blockers: [] },
|
|
9148
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
9062
9149
|
assessments: [],
|
|
9063
9150
|
policy: null,
|
|
9064
9151
|
policyChanges: [],
|
|
@@ -9089,7 +9176,7 @@ class TaskStore {
|
|
|
9089
9176
|
if (!current.ok)
|
|
9090
9177
|
return current;
|
|
9091
9178
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
9092
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
9179
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
9093
9180
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
9094
9181
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
9095
9182
|
});
|
|
@@ -9097,11 +9184,17 @@ class TaskStore {
|
|
|
9097
9184
|
if (current.data && !previousWorkspaceBytes)
|
|
9098
9185
|
return failure("storage_error", "workspace snapshot disappeared during import");
|
|
9099
9186
|
const timestamp = input.now ?? now();
|
|
9100
|
-
const workspace = current.data ? {
|
|
9187
|
+
const workspace = current.data ? {
|
|
9188
|
+
...current.data,
|
|
9189
|
+
revision: newRevision(),
|
|
9190
|
+
root: this.root,
|
|
9191
|
+
runtime: stampNew(current.data)
|
|
9192
|
+
} : {
|
|
9101
9193
|
schemaVersion: SCHEMA_VERSION,
|
|
9102
9194
|
id: input.workspaceId ?? newId(),
|
|
9103
9195
|
revision: newRevision(),
|
|
9104
9196
|
root: this.root,
|
|
9197
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
9105
9198
|
writer: null
|
|
9106
9199
|
};
|
|
9107
9200
|
const task = {
|
|
@@ -9109,7 +9202,11 @@ class TaskStore {
|
|
|
9109
9202
|
workspaceId: workspace.id,
|
|
9110
9203
|
revision: newRevision(),
|
|
9111
9204
|
createdAt: timestamp,
|
|
9112
|
-
updatedAt: timestamp
|
|
9205
|
+
updatedAt: timestamp,
|
|
9206
|
+
runtime: {
|
|
9207
|
+
createdWith: input.task.runtime?.createdWith ?? null,
|
|
9208
|
+
updatedWith: runtimeVersion()
|
|
9209
|
+
}
|
|
9113
9210
|
};
|
|
9114
9211
|
const validTask = taskRecordSchema.safeParse(task);
|
|
9115
9212
|
if (!validTask.success)
|
|
@@ -9158,7 +9255,8 @@ class TaskStore {
|
|
|
9158
9255
|
workspaceId: current.data.workspaceId,
|
|
9159
9256
|
createdAt: current.data.createdAt,
|
|
9160
9257
|
revision: context.revision,
|
|
9161
|
-
updatedAt: context.now
|
|
9258
|
+
updatedAt: context.now,
|
|
9259
|
+
runtime: stampNew(current.data)
|
|
9162
9260
|
};
|
|
9163
9261
|
const valid = taskRecordSchema.safeParse(record);
|
|
9164
9262
|
if (!valid.success)
|
|
@@ -9192,7 +9290,8 @@ class TaskStore {
|
|
|
9192
9290
|
...changed.data,
|
|
9193
9291
|
id: current.data.id,
|
|
9194
9292
|
root: this.root,
|
|
9195
|
-
revision: context.revision
|
|
9293
|
+
revision: context.revision,
|
|
9294
|
+
runtime: stampNew(current.data)
|
|
9196
9295
|
};
|
|
9197
9296
|
const valid = workspaceRecordSchema.safeParse(record);
|
|
9198
9297
|
if (!valid.success)
|
|
@@ -9235,7 +9334,8 @@ class TaskStore {
|
|
|
9235
9334
|
...changedWorkspace.data,
|
|
9236
9335
|
id: workspace.data.id,
|
|
9237
9336
|
root: this.root,
|
|
9238
|
-
revision: workspaceContext.revision
|
|
9337
|
+
revision: workspaceContext.revision,
|
|
9338
|
+
runtime: stampNew(workspace.data)
|
|
9239
9339
|
});
|
|
9240
9340
|
if (!nextWorkspace.success)
|
|
9241
9341
|
return failure("invalid_input", "workspace mutation produced an invalid record");
|
|
@@ -9260,7 +9360,8 @@ class TaskStore {
|
|
|
9260
9360
|
workspaceId: task.data.workspaceId,
|
|
9261
9361
|
createdAt: task.data.createdAt,
|
|
9262
9362
|
revision: taskContext.revision,
|
|
9263
|
-
updatedAt: taskContext.now
|
|
9363
|
+
updatedAt: taskContext.now,
|
|
9364
|
+
runtime: stampNew(task.data)
|
|
9264
9365
|
});
|
|
9265
9366
|
if (!nextTask.success) {
|
|
9266
9367
|
this.markUncertain(nextWorkspace.data);
|
|
@@ -9287,7 +9388,7 @@ class TaskStore {
|
|
|
9287
9388
|
if (match)
|
|
9288
9389
|
candidates.push({
|
|
9289
9390
|
target: match[1],
|
|
9290
|
-
path:
|
|
9391
|
+
path: path4.join(this.recoveryDir, name),
|
|
9291
9392
|
digest: match[3]
|
|
9292
9393
|
});
|
|
9293
9394
|
}
|
|
@@ -9480,7 +9581,8 @@ class TaskStore {
|
|
|
9480
9581
|
const value = {
|
|
9481
9582
|
...workspace,
|
|
9482
9583
|
writer: { ...workspace.writer, state: "uncertain" },
|
|
9483
|
-
revision: newRevision()
|
|
9584
|
+
revision: newRevision(),
|
|
9585
|
+
runtime: stampNew(workspace)
|
|
9484
9586
|
};
|
|
9485
9587
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
9486
9588
|
}
|
|
@@ -9558,7 +9660,7 @@ class TaskStore {
|
|
|
9558
9660
|
}
|
|
9559
9661
|
fs4.renameSync(temporary, file);
|
|
9560
9662
|
temporary = undefined;
|
|
9561
|
-
this.fsyncDirectory(
|
|
9663
|
+
this.fsyncDirectory(path4.dirname(file));
|
|
9562
9664
|
return success(null, null, value);
|
|
9563
9665
|
} catch (error) {
|
|
9564
9666
|
return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
|
|
@@ -9572,9 +9674,9 @@ class TaskStore {
|
|
|
9572
9674
|
}
|
|
9573
9675
|
}
|
|
9574
9676
|
saveRecovery(file, bytes) {
|
|
9575
|
-
const target =
|
|
9576
|
-
const id = target === "task" ?
|
|
9577
|
-
const destination =
|
|
9677
|
+
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
9678
|
+
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
9679
|
+
const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
|
|
9578
9680
|
if (fs4.existsSync(destination)) {
|
|
9579
9681
|
if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
|
|
9580
9682
|
return;
|
|
@@ -9652,7 +9754,7 @@ class TaskStore {
|
|
|
9652
9754
|
for (const name of fs4.readdirSync(this.tasksDir)) {
|
|
9653
9755
|
if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
|
|
9654
9756
|
return null;
|
|
9655
|
-
const parsed = this.parseBytes(fs4.readFileSync(
|
|
9757
|
+
const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
|
|
9656
9758
|
if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
|
|
9657
9759
|
return null;
|
|
9658
9760
|
ids.add(parsed.data.workspaceId);
|
|
@@ -9694,7 +9796,12 @@ class TaskStore {
|
|
|
9694
9796
|
if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
9695
9797
|
return failure("unsupported_version", "unsupported snapshot schema version");
|
|
9696
9798
|
const parsed = schema.safeParse(value);
|
|
9697
|
-
|
|
9799
|
+
if (parsed.success)
|
|
9800
|
+
return success(null, null, parsed.data);
|
|
9801
|
+
const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
|
|
9802
|
+
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
9803
|
+
return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
9804
|
+
return failure("recovery_required", "snapshot does not satisfy its schema");
|
|
9698
9805
|
}
|
|
9699
9806
|
processStart(pid) {
|
|
9700
9807
|
try {
|
|
@@ -9704,43 +9811,98 @@ class TaskStore {
|
|
|
9704
9811
|
}
|
|
9705
9812
|
}
|
|
9706
9813
|
conflict(expected, actual) {
|
|
9707
|
-
return failure("revision_conflict", "snapshot revision does not match", {
|
|
9814
|
+
return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
9708
9815
|
expectedRevision: expected,
|
|
9709
9816
|
actualRevision: actual
|
|
9710
9817
|
});
|
|
9711
9818
|
}
|
|
9712
9819
|
taskPath(taskId) {
|
|
9713
|
-
return
|
|
9820
|
+
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
9714
9821
|
}
|
|
9715
9822
|
get workitDir() {
|
|
9716
|
-
return
|
|
9823
|
+
return path4.join(this.root, ".workit");
|
|
9717
9824
|
}
|
|
9718
9825
|
get tasksDir() {
|
|
9719
|
-
return
|
|
9826
|
+
return path4.join(this.workitDir, "tasks");
|
|
9720
9827
|
}
|
|
9721
9828
|
get recoveryDir() {
|
|
9722
|
-
return
|
|
9829
|
+
return path4.join(this.workitDir, "recovery");
|
|
9723
9830
|
}
|
|
9724
9831
|
get workspacePath() {
|
|
9725
|
-
return
|
|
9832
|
+
return path4.join(this.workitDir, "workspace.json");
|
|
9726
9833
|
}
|
|
9727
9834
|
get lockPath() {
|
|
9728
|
-
return
|
|
9835
|
+
return path4.join(this.workitDir, "metadata.lock");
|
|
9729
9836
|
}
|
|
9730
9837
|
get gitignorePath() {
|
|
9731
|
-
return
|
|
9838
|
+
return path4.join(this.workitDir, ".gitignore");
|
|
9732
9839
|
}
|
|
9733
9840
|
}
|
|
9841
|
+
// packages/workit-core/src/core/workers.ts
|
|
9842
|
+
var same = (left, right) => {
|
|
9843
|
+
try {
|
|
9844
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
9845
|
+
} catch {
|
|
9846
|
+
return false;
|
|
9847
|
+
}
|
|
9848
|
+
};
|
|
9849
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
9850
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
9851
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
9852
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
9853
|
+
const code = char.charCodeAt(0);
|
|
9854
|
+
return code < 32 || code === 127;
|
|
9855
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
9856
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
9857
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
9858
|
+
function assertProductWriteAllowed(input) {
|
|
9859
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
9860
|
+
return failure("invalid_input", "invalid product write path");
|
|
9861
|
+
if (!input.store)
|
|
9862
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
9863
|
+
const task = input.store.readTask(input.task.id);
|
|
9864
|
+
if (!task.ok)
|
|
9865
|
+
return task;
|
|
9866
|
+
const workspace = input.store.readWorkspace();
|
|
9867
|
+
if (!workspace.ok)
|
|
9868
|
+
return workspace;
|
|
9869
|
+
if (!workspace.data)
|
|
9870
|
+
return failure("not_found", "workspace not found");
|
|
9871
|
+
const workspaceRecord = workspace.data;
|
|
9872
|
+
if (task.data.status !== "active")
|
|
9873
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
9874
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
9875
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
9876
|
+
const writer = workspaceRecord.writer;
|
|
9877
|
+
if (!writer)
|
|
9878
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
9879
|
+
if (writer.state === "uncertain")
|
|
9880
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
9881
|
+
owner: writer.owner
|
|
9882
|
+
});
|
|
9883
|
+
const caller = callerValue(input.caller);
|
|
9884
|
+
const workerId = workerIdOf(input.caller);
|
|
9885
|
+
const owner = writer.owner;
|
|
9886
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
9887
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
9888
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
9889
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
9890
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
9891
|
+
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))))
|
|
9892
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
9893
|
+
return success(null, workspaceRecord.revision, owner);
|
|
9894
|
+
}
|
|
9895
|
+
|
|
9734
9896
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
9735
9897
|
import { createHash as createHash4 } from "node:crypto";
|
|
9736
9898
|
import * as fs6 from "node:fs";
|
|
9737
9899
|
import { spawnSync } from "node:child_process";
|
|
9738
|
-
import
|
|
9900
|
+
import path6 from "node:path";
|
|
9739
9901
|
|
|
9740
9902
|
// packages/workit-core/src/core/authority.ts
|
|
9741
9903
|
import { createHash as createHash3 } from "node:crypto";
|
|
9742
9904
|
import * as fs5 from "node:fs";
|
|
9743
|
-
import
|
|
9905
|
+
import path5 from "node:path";
|
|
9744
9906
|
var verifiedAuthorities = new WeakMap;
|
|
9745
9907
|
var trustedAuthority = (kind, provenance, expected, binding, caller) => {
|
|
9746
9908
|
const token = {};
|
|
@@ -9867,20 +10029,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
9867
10029
|
continue;
|
|
9868
10030
|
if (!reference.digest)
|
|
9869
10031
|
return failure("invalid_input", "document references require a byte digest");
|
|
9870
|
-
const target =
|
|
9871
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
9872
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
10032
|
+
const target = path5.resolve(root, reference.path);
|
|
10033
|
+
if (target !== root && !target.startsWith(`${root}${path5.sep}`))
|
|
10034
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
10035
|
+
fields: [
|
|
10036
|
+
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
10037
|
+
]
|
|
10038
|
+
});
|
|
9873
10039
|
try {
|
|
9874
|
-
const relative =
|
|
10040
|
+
const relative = path5.relative(root, target);
|
|
9875
10041
|
let current = root;
|
|
9876
|
-
for (const segment of relative.split(
|
|
10042
|
+
for (const segment of relative.split(path5.sep)) {
|
|
9877
10043
|
if (!segment)
|
|
9878
10044
|
continue;
|
|
9879
|
-
current =
|
|
10045
|
+
current = path5.join(current, segment);
|
|
9880
10046
|
const stat = fs5.lstatSync(current);
|
|
9881
10047
|
const real = fs5.realpathSync(current);
|
|
9882
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
9883
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
10048
|
+
if (real !== root && !real.startsWith(`${root}${path5.sep}`))
|
|
10049
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
10050
|
+
fields: [
|
|
10051
|
+
{
|
|
10052
|
+
path: "contentRefs.path",
|
|
10053
|
+
reason: `${reference.path} resolves outside the checkout`
|
|
10054
|
+
}
|
|
10055
|
+
]
|
|
10056
|
+
});
|
|
9884
10057
|
if (stat.isSymbolicLink())
|
|
9885
10058
|
return failure("invalid_input", "document reference uses a symlink");
|
|
9886
10059
|
if (current === target && !stat.isFile())
|
|
@@ -10227,14 +10400,14 @@ var verifyDecisionContent = verifyContentRefs;
|
|
|
10227
10400
|
|
|
10228
10401
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
10229
10402
|
var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
|
|
10230
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
10231
|
-
var relative = (root, candidate) =>
|
|
10403
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
|
|
10404
|
+
var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
|
|
10232
10405
|
var canonicalPath = (value) => {
|
|
10233
10406
|
if (typeof value !== "string" || !value)
|
|
10234
10407
|
return null;
|
|
10235
10408
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
10236
10409
|
return null;
|
|
10237
|
-
const normalized =
|
|
10410
|
+
const normalized = path6.posix.normalize(value);
|
|
10238
10411
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
10239
10412
|
return null;
|
|
10240
10413
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -10282,14 +10455,14 @@ var gitPaths = (root) => {
|
|
|
10282
10455
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
10283
10456
|
};
|
|
10284
10457
|
}
|
|
10285
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
10458
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
|
|
10286
10459
|
const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
10287
10460
|
if (stagedDeleted.status !== 0) {
|
|
10288
10461
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
10289
10462
|
if (!/not a git repository/i.test(stderr))
|
|
10290
10463
|
return { paths, uncertain: true, git: true };
|
|
10291
10464
|
} else if (stagedDeleted.stdout) {
|
|
10292
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
10465
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
|
|
10293
10466
|
}
|
|
10294
10467
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
10295
10468
|
};
|
|
@@ -10304,7 +10477,7 @@ var walk = (root, directory, output) => {
|
|
|
10304
10477
|
for (const name of names) {
|
|
10305
10478
|
if (name === ".git" || name === ".workit")
|
|
10306
10479
|
continue;
|
|
10307
|
-
const target =
|
|
10480
|
+
const target = path6.join(directory, name);
|
|
10308
10481
|
let stat;
|
|
10309
10482
|
try {
|
|
10310
10483
|
stat = fs6.lstatSync(target);
|
|
@@ -10322,17 +10495,17 @@ var walk = (root, directory, output) => {
|
|
|
10322
10495
|
var scopeRoots = (root, scope) => {
|
|
10323
10496
|
const roots = [];
|
|
10324
10497
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
10325
|
-
const target =
|
|
10498
|
+
const target = path6.resolve(root, item);
|
|
10326
10499
|
if (!inside(root, target))
|
|
10327
|
-
return failure("invalid_input",
|
|
10500
|
+
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` }] });
|
|
10328
10501
|
let ancestor = target;
|
|
10329
10502
|
while (!fs6.existsSync(ancestor) && ancestor !== root)
|
|
10330
|
-
ancestor =
|
|
10503
|
+
ancestor = path6.dirname(ancestor);
|
|
10331
10504
|
try {
|
|
10332
10505
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
10333
|
-
return failure("invalid_input",
|
|
10506
|
+
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` }] });
|
|
10334
10507
|
} catch {
|
|
10335
|
-
return failure("invalid_input",
|
|
10508
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
10336
10509
|
}
|
|
10337
10510
|
roots.push(target);
|
|
10338
10511
|
}
|
|
@@ -10347,7 +10520,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
10347
10520
|
}
|
|
10348
10521
|
const normalizedScope = canonicalScope(scope);
|
|
10349
10522
|
if (!normalizedScope)
|
|
10350
|
-
return failure("invalid_input", "candidate scope is invalid");
|
|
10523
|
+
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" }] });
|
|
10351
10524
|
const roots = scopeRoots(checkout, normalizedScope);
|
|
10352
10525
|
if (!roots.ok)
|
|
10353
10526
|
return roots;
|
|
@@ -10379,7 +10552,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
10379
10552
|
for (const item of [...names].sort()) {
|
|
10380
10553
|
if (!scopeMatches(item, normalizedScope))
|
|
10381
10554
|
continue;
|
|
10382
|
-
const target =
|
|
10555
|
+
const target = path6.join(checkout, item);
|
|
10383
10556
|
try {
|
|
10384
10557
|
const stat = fs6.lstatSync(target);
|
|
10385
10558
|
if (stat.isSymbolicLink()) {
|
|
@@ -10501,7 +10674,7 @@ function evaluateEvidence(task, candidate) {
|
|
|
10501
10674
|
}
|
|
10502
10675
|
var scopeCovers2 = scopeCovers;
|
|
10503
10676
|
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")));
|
|
10504
|
-
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && data.response === "approved" && 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 }));
|
|
10677
|
+
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 }));
|
|
10505
10678
|
var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
|
|
10506
10679
|
if (kind !== "passed")
|
|
10507
10680
|
return false;
|
|
@@ -10536,7 +10709,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10536
10709
|
return true;
|
|
10537
10710
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
10538
10711
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
10539
|
-
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
|
|
10712
|
+
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
|
|
10540
10713
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
10541
10714
|
});
|
|
10542
10715
|
if (passed.length) {
|
|
@@ -10577,7 +10750,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10577
10750
|
status: "satisfied",
|
|
10578
10751
|
evidenceIds: [],
|
|
10579
10752
|
decisionIds: decisions.map((decision) => decision.id),
|
|
10580
|
-
reason: "an applicable approved decision satisfies the requirement"
|
|
10753
|
+
reason: "an applicable approved or stated decision satisfies the requirement"
|
|
10581
10754
|
};
|
|
10582
10755
|
const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
|
|
10583
10756
|
if (limitations.length)
|
|
@@ -10632,7 +10805,17 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10632
10805
|
const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
10633
10806
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
10634
10807
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
10635
|
-
requirementIds: blocking.map((item) => item.requirementId)
|
|
10808
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
10809
|
+
requirements: blocking.map((item) => {
|
|
10810
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
10811
|
+
return {
|
|
10812
|
+
requirementId: item.requirementId,
|
|
10813
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
10814
|
+
reason: item.reason,
|
|
10815
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
10816
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
10817
|
+
};
|
|
10818
|
+
})
|
|
10636
10819
|
});
|
|
10637
10820
|
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
10638
10821
|
if (requestedOutcome === "verified" && accepted.length)
|
|
@@ -10663,7 +10846,7 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10663
10846
|
var METHODS = {
|
|
10664
10847
|
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
10665
10848
|
"workit-behavioral-tdd": { dimensions: ["testing"] },
|
|
10666
|
-
"workit-review": { dimensions: ["review"] },
|
|
10849
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
|
|
10667
10850
|
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
10668
10851
|
"workit-implement": { dimensions: ["delegation"] },
|
|
10669
10852
|
"workit-debug": { ruleIds: ["root-cause-investigation"] },
|
|
@@ -10725,7 +10908,18 @@ decision.record {binding with taskId/workspaceId from inspect plus presented and
|
|
|
10725
10908
|
approvedContent} only through a native approval question asked receipt-shaped:
|
|
10726
10909
|
header Workit decision: <purpose>, the same label repeated in the question
|
|
10727
10910
|
text (some host UIs do not render headers), exactly two options approved and
|
|
10728
|
-
rejected; ask once, never re-ask to mint a receipt.
|
|
10911
|
+
rejected; ask once, never re-ask to mint a receipt. Before asking, present the
|
|
10912
|
+
item in the conversation (durable artifacts: complete digest plus exact path;
|
|
10913
|
+
inline plans: the content) and keep the question to one short scoped sentence;
|
|
10914
|
+
never ask to approve something unseen. An answer with custom text is steering,
|
|
10915
|
+
not approval: adjust, re-present, then ask a new scoped question. After the user
|
|
10916
|
+
approves a spec or plan, execute the remaining tasks continuously with one
|
|
10917
|
+
atomic commit per task and do not ask whether to continue; stop only for a new
|
|
10918
|
+
product decision, a failed safety or verification gate, a conflicting edit, or
|
|
10919
|
+
missing authority. Record the plan's commit list once through the plan-scoped
|
|
10920
|
+
action approval (git.commit with plan_steps and plan_branch) so each listed
|
|
10921
|
+
commit executes without a new question; an unlisted message needs a fresh
|
|
10922
|
+
approval. task.close {outcome,
|
|
10729
10923
|
summary, decisionIds}. A completed native subagent run stops its bound worker
|
|
10730
10924
|
by itself; if a worker strands in cancelling with its run verifiably over,
|
|
10731
10925
|
repeat worker.cancel {reason} to confirm the stop. Check and review evidence
|
|
@@ -10757,7 +10951,7 @@ function reconcileResume(view, observations = []) {
|
|
|
10757
10951
|
return candidate;
|
|
10758
10952
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
10759
10953
|
const blockers = [...view.task.progress.blockers];
|
|
10760
|
-
if (view.task.workers.some((entry) =>
|
|
10954
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
10761
10955
|
blockers.push({
|
|
10762
10956
|
reason: "worker state requires reconciliation",
|
|
10763
10957
|
dependentAction: "resume",
|
|
@@ -10889,7 +11083,25 @@ function shellRouteIntent(command) {
|
|
|
10889
11083
|
}
|
|
10890
11084
|
return null;
|
|
10891
11085
|
}
|
|
11086
|
+
function hasLiveWorkitTask(dir) {
|
|
11087
|
+
try {
|
|
11088
|
+
if (typeof dir !== "string" || dir === "")
|
|
11089
|
+
return false;
|
|
11090
|
+
const listed = new TaskStore(dir).listTasks();
|
|
11091
|
+
if (!listed.ok)
|
|
11092
|
+
return false;
|
|
11093
|
+
return listed.data.some((task) => task.status === "active" || task.status === "paused");
|
|
11094
|
+
} catch {
|
|
11095
|
+
return false;
|
|
11096
|
+
}
|
|
11097
|
+
}
|
|
11098
|
+
function shouldDenyShellRoute(dir, command) {
|
|
11099
|
+
if (!hasLiveWorkitTask(dir))
|
|
11100
|
+
return null;
|
|
11101
|
+
return shellRouteIntent(command);
|
|
11102
|
+
}
|
|
10892
11103
|
// packages/workit-core/src/core/external-action.ts
|
|
11104
|
+
import { execFileSync } from "node:child_process";
|
|
10893
11105
|
var externalActionSchema = discriminatedUnion("operation", [
|
|
10894
11106
|
object({
|
|
10895
11107
|
operation: literal("git.branch_setup"),
|
|
@@ -10902,7 +11114,11 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
10902
11114
|
}).strict(),
|
|
10903
11115
|
object({
|
|
10904
11116
|
operation: literal("git.commit"),
|
|
10905
|
-
payload: object({
|
|
11117
|
+
payload: object({
|
|
11118
|
+
message: string2().min(1).optional(),
|
|
11119
|
+
plan_steps: array(string2().min(1)).optional(),
|
|
11120
|
+
plan_branch: string2().min(1).optional()
|
|
11121
|
+
}).strict()
|
|
10906
11122
|
}).strict(),
|
|
10907
11123
|
object({
|
|
10908
11124
|
operation: literal("git.push"),
|
|
@@ -10991,7 +11207,7 @@ var externalActionDescriptor = (operation, payload) => canonicalJson({
|
|
|
10991
11207
|
payload,
|
|
10992
11208
|
...payload && typeof payload === "object" && !Array.isArray(payload) && "resolved" in payload && payload.resolved && typeof payload.resolved === "object" && Array.isArray(payload.resolved.steps) ? { steps: payload.resolved.steps } : {}
|
|
10993
11209
|
});
|
|
10994
|
-
var externalActionHelp = "Fixed actions: git.branch_setup {action?,sdd_dir?,target_branch
|
|
11210
|
+
var externalActionHelp = "Fixed actions: git.branch_setup {action?,sdd_dir?,target_branch(required unless reapply_stash; the working branch to create or switch to),stash?}; git.commit {message}; git.push {branch?}; hosting.pull_request {title,body?,draft?,target_branch?,babysit?}; youtrack.update {issueId,markdown,minutes?}; youtrack.time {issueId,minutes,text?,dateMs?}; youtrack.meeting {issueId,minutes,text}; changelog.apply {entries?,path?,normalize_only?}; context.read {kind,range?,issueId?,issueUrl?,issueRef?,mode?,specPath?,planPath?}. context.read is read-only and needs no approval; all other operations require native approval (CLI uses a TTY; caller-unattested MCP cannot mutate).";
|
|
10995
11211
|
var externalActionRef = (host, actor, descriptor) => ({
|
|
10996
11212
|
kind: "host",
|
|
10997
11213
|
host,
|
|
@@ -11042,14 +11258,185 @@ var priorExternalAction = (store, host, actor, operation, payload) => {
|
|
|
11042
11258
|
return failure("permission_denied", candidates.length === 0 ? "no prior external action matches this request" : "multiple prior external actions match this request");
|
|
11043
11259
|
return success(workspace.data.revision, null, { ...candidates[0], workspace: workspace.data });
|
|
11044
11260
|
};
|
|
11261
|
+
var planCommitDescriptor = (content) => {
|
|
11262
|
+
try {
|
|
11263
|
+
const value = JSON.parse(content);
|
|
11264
|
+
if (value.operation !== "git.commit" || !value.payload)
|
|
11265
|
+
return null;
|
|
11266
|
+
const steps = value.payload.plan_steps;
|
|
11267
|
+
const branch = value.payload.plan_branch;
|
|
11268
|
+
if (!Array.isArray(steps) || steps.some((step) => typeof step !== "string" || !step) || typeof branch !== "string" || !branch)
|
|
11269
|
+
return null;
|
|
11270
|
+
return { steps, branch };
|
|
11271
|
+
} catch {
|
|
11272
|
+
return null;
|
|
11273
|
+
}
|
|
11274
|
+
};
|
|
11275
|
+
var approvedPlanCommit = (store, host, actor, message) => {
|
|
11276
|
+
if (typeof message !== "string" || !message)
|
|
11277
|
+
return failure("invalid_input", "commit message is required");
|
|
11278
|
+
const listed = store.listTasks();
|
|
11279
|
+
const workspace = store.readWorkspace();
|
|
11280
|
+
if (!listed.ok || !workspace.ok)
|
|
11281
|
+
return failure("storage_error", "external action state is unavailable");
|
|
11282
|
+
if (!workspace.data)
|
|
11283
|
+
return failure("not_found", "workspace not found");
|
|
11284
|
+
const matches = listed.data.flatMap((task) => {
|
|
11285
|
+
if (task.status !== "active" || task.workspaceId !== workspace.data.id)
|
|
11286
|
+
return [];
|
|
11287
|
+
if (task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
|
|
11288
|
+
return [];
|
|
11289
|
+
return task.decisions.flatMap((entry) => {
|
|
11290
|
+
const decision = entry.data;
|
|
11291
|
+
if (decision.purpose !== "action" || decision.response !== "approved" || decision.revoked !== null)
|
|
11292
|
+
return [];
|
|
11293
|
+
if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0)
|
|
11294
|
+
return [];
|
|
11295
|
+
const plan = planCommitDescriptor(decision.binding.approvedContent);
|
|
11296
|
+
if (!plan)
|
|
11297
|
+
return [];
|
|
11298
|
+
const completed = task.actionProgress?.find((progress) => progress.decisionId === entry.id)?.completedSteps ?? [];
|
|
11299
|
+
if (plan.steps[completed.length] !== message)
|
|
11300
|
+
return [];
|
|
11301
|
+
return [{ task, entry, steps: plan.steps, branch: plan.branch, completed: [...completed] }];
|
|
11302
|
+
});
|
|
11303
|
+
});
|
|
11304
|
+
if (matches.length !== 1)
|
|
11305
|
+
return failure("permission_denied", matches.length === 0 ? "no plan commit authorization matches this message" : "multiple plan authorizations match this message");
|
|
11306
|
+
return success(workspace.data.revision, null, matches[0]);
|
|
11307
|
+
};
|
|
11308
|
+
var commitMessageFromDescriptor = (operation) => {
|
|
11309
|
+
try {
|
|
11310
|
+
const descriptor = JSON.parse(operation);
|
|
11311
|
+
return descriptor.operation === "git.commit" && typeof descriptor.payload?.message === "string" ? descriptor.payload.message : undefined;
|
|
11312
|
+
} catch {
|
|
11313
|
+
return;
|
|
11314
|
+
}
|
|
11315
|
+
};
|
|
11316
|
+
var planCommitBinding = (store, host, actor, operation) => {
|
|
11317
|
+
const message = commitMessageFromDescriptor(operation);
|
|
11318
|
+
if (!message)
|
|
11319
|
+
return null;
|
|
11320
|
+
const plan = approvedPlanCommit(store, host, actor, message);
|
|
11321
|
+
if (!plan.ok)
|
|
11322
|
+
return null;
|
|
11323
|
+
let branch;
|
|
11324
|
+
try {
|
|
11325
|
+
branch = execFileSync("git", ["branch", "--show-current"], {
|
|
11326
|
+
cwd: store.root,
|
|
11327
|
+
encoding: "utf8",
|
|
11328
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
11329
|
+
}).trim();
|
|
11330
|
+
} catch {
|
|
11331
|
+
return null;
|
|
11332
|
+
}
|
|
11333
|
+
if (!branch || branch !== plan.data.branch)
|
|
11334
|
+
return null;
|
|
11335
|
+
const workspace = store.readWorkspace();
|
|
11336
|
+
if (!workspace.ok || !workspace.data)
|
|
11337
|
+
return null;
|
|
11338
|
+
return {
|
|
11339
|
+
taskId: plan.data.task.id,
|
|
11340
|
+
decisionId: plan.data.entry.id,
|
|
11341
|
+
binding: plan.data.entry.data.binding,
|
|
11342
|
+
expectedRevision: plan.data.task.revision,
|
|
11343
|
+
expectedWorkspaceRevision: workspace.data.revision,
|
|
11344
|
+
step: message
|
|
11345
|
+
};
|
|
11346
|
+
};
|
|
11045
11347
|
var approvedExternalAction = (store, host, actor, operation) => {
|
|
11046
11348
|
const selected = externalActionState(store, host, actor, operation);
|
|
11047
|
-
if (
|
|
11349
|
+
if (selected.ok) {
|
|
11350
|
+
if (selected.data.entry.data.consumption !== null)
|
|
11351
|
+
return failure(selected.data.entry.data.consumption.state === "uncertain" ? "external_outcome_unknown" : "permission_denied", selected.data.entry.data.consumption.state === "uncertain" ? "previous external action outcome is unknown" : "external action was already settled");
|
|
11048
11352
|
return selected;
|
|
11049
|
-
|
|
11050
|
-
|
|
11353
|
+
}
|
|
11354
|
+
const intent = approvedBranchSetupIntent(store, host, actor, operation);
|
|
11355
|
+
if (intent.ok) {
|
|
11356
|
+
if (intent.data.entry.data.consumption !== null)
|
|
11357
|
+
return failure(intent.data.entry.data.consumption.state === "uncertain" ? "external_outcome_unknown" : "permission_denied", intent.data.entry.data.consumption.state === "uncertain" ? "previous external action outcome is unknown" : "external action was already settled");
|
|
11358
|
+
return intent;
|
|
11359
|
+
}
|
|
11051
11360
|
return selected;
|
|
11052
11361
|
};
|
|
11362
|
+
var branchSetupIntent = (descriptor) => {
|
|
11363
|
+
try {
|
|
11364
|
+
const value = JSON.parse(descriptor);
|
|
11365
|
+
if (value.operation !== "git.branch_setup" || !value.payload)
|
|
11366
|
+
return null;
|
|
11367
|
+
const { target_branch, resolved } = value.payload;
|
|
11368
|
+
if (typeof target_branch !== "string" || !target_branch || !resolved)
|
|
11369
|
+
return null;
|
|
11370
|
+
if (typeof resolved.base_branch !== "string" || typeof resolved.target_exists !== "boolean")
|
|
11371
|
+
return null;
|
|
11372
|
+
return {
|
|
11373
|
+
target: target_branch,
|
|
11374
|
+
base: resolved.base_branch,
|
|
11375
|
+
remoteBase: typeof resolved.remote_base === "string" ? resolved.remote_base : null,
|
|
11376
|
+
targetExists: resolved.target_exists
|
|
11377
|
+
};
|
|
11378
|
+
} catch {
|
|
11379
|
+
return null;
|
|
11380
|
+
}
|
|
11381
|
+
};
|
|
11382
|
+
var approvedBranchSetupIntent = (store, host, actor, operation) => {
|
|
11383
|
+
const current = branchSetupIntent(operation);
|
|
11384
|
+
if (!current)
|
|
11385
|
+
return failure("permission_denied", "no approved action is bound to this session");
|
|
11386
|
+
const listed = store.listTasks();
|
|
11387
|
+
const workspace = store.readWorkspace();
|
|
11388
|
+
if (!listed.ok || !workspace.ok)
|
|
11389
|
+
return failure("storage_error", "external action state is unavailable");
|
|
11390
|
+
if (!workspace.data)
|
|
11391
|
+
return failure("not_found", "workspace not found");
|
|
11392
|
+
const candidates = listed.data.flatMap((task) => {
|
|
11393
|
+
if (task.status !== "active" || task.workspaceId !== workspace.data.id || task.intent.provenance.session?.kind !== "host" || task.intent.provenance.session.host !== host || task.intent.provenance.session.handle !== actor)
|
|
11394
|
+
return [];
|
|
11395
|
+
return task.decisions.filter((entry) => entry.data.purpose === "action" && entry.data.response === "approved" && entry.data.revoked === null).map((entry) => ({ task, entry }));
|
|
11396
|
+
});
|
|
11397
|
+
const matches = candidates.filter(({ entry }) => {
|
|
11398
|
+
const intent = branchSetupIntent(entry.data.binding.approvedContent);
|
|
11399
|
+
return intent !== null && intent.target === current.target && intent.base === current.base && intent.remoteBase === current.remoteBase && intent.targetExists === current.targetExists;
|
|
11400
|
+
});
|
|
11401
|
+
if (matches.length !== 1)
|
|
11402
|
+
return failure("permission_denied", matches.length === 0 ? "no approved action is bound to this session" : "multiple approved actions match this session");
|
|
11403
|
+
return success(workspace.data.revision, null, { ...matches[0], workspace: workspace.data });
|
|
11404
|
+
};
|
|
11405
|
+
var priorResolvedDrift = (store, host, actor, operation, payload, currentResolved) => {
|
|
11406
|
+
const prior = priorExternalAction(store, host, actor, operation, payload);
|
|
11407
|
+
if (!prior.ok)
|
|
11408
|
+
return success(null, null, null);
|
|
11409
|
+
let baseline;
|
|
11410
|
+
try {
|
|
11411
|
+
const parsed = JSON.parse(prior.data.entry.data.binding.approvedContent);
|
|
11412
|
+
baseline = parsed.payload?.resolved;
|
|
11413
|
+
} catch {
|
|
11414
|
+
return success(null, null, null);
|
|
11415
|
+
}
|
|
11416
|
+
if (baseline === undefined || baseline === null)
|
|
11417
|
+
return success(null, null, null);
|
|
11418
|
+
if (operation === "git.branch_setup") {
|
|
11419
|
+
const before = branchSetupIntent(prior.data.entry.data.binding.approvedContent);
|
|
11420
|
+
let after = null;
|
|
11421
|
+
try {
|
|
11422
|
+
after = branchSetupIntent(JSON.stringify({
|
|
11423
|
+
operation,
|
|
11424
|
+
payload: { ...payload, resolved: currentResolved }
|
|
11425
|
+
}));
|
|
11426
|
+
} catch {
|
|
11427
|
+
after = null;
|
|
11428
|
+
}
|
|
11429
|
+
if (before && after) {
|
|
11430
|
+
const moved = before.target !== after.target ? "target_branch" : before.base !== after.base ? "base_branch" : before.remoteBase !== after.remoteBase ? "remote_base" : before.targetExists !== after.targetExists ? "target_exists" : null;
|
|
11431
|
+
if (moved === null)
|
|
11432
|
+
return success(null, null, null);
|
|
11433
|
+
return failure("invalid_input", `repository state changed since approval (${moved} moved); show the current state and approve the action again`, { outcome: "not_started", operation });
|
|
11434
|
+
}
|
|
11435
|
+
}
|
|
11436
|
+
if (canonicalJson(baseline) === canonicalJson(currentResolved))
|
|
11437
|
+
return success(null, null, null);
|
|
11438
|
+
return failure("invalid_input", "repository state changed since approval; show the current state and approve the action again", { outcome: "not_started", operation });
|
|
11439
|
+
};
|
|
11053
11440
|
var createAuthorizedExternalActionRunner = (core, bind) => async (operation, effect) => {
|
|
11054
11441
|
let steps = [];
|
|
11055
11442
|
try {
|
|
@@ -11327,7 +11714,7 @@ function resolveRequirements(input) {
|
|
|
11327
11714
|
].join(", ")}`,
|
|
11328
11715
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
11329
11716
|
before: "dependent_action",
|
|
11330
|
-
dependentAction:
|
|
11717
|
+
dependentAction: null,
|
|
11331
11718
|
acceptanceAllowed: false
|
|
11332
11719
|
}));
|
|
11333
11720
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -11338,7 +11725,7 @@ function resolveRequirements(input) {
|
|
|
11338
11725
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
11339
11726
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
11340
11727
|
before: "dependent_action",
|
|
11341
|
-
dependentAction:
|
|
11728
|
+
dependentAction: null,
|
|
11342
11729
|
acceptanceAllowed: false
|
|
11343
11730
|
}));
|
|
11344
11731
|
if (signals.behaviorChange.value === true)
|
|
@@ -11496,59 +11883,6 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
11496
11883
|
return parsed.data;
|
|
11497
11884
|
}
|
|
11498
11885
|
|
|
11499
|
-
// packages/workit-core/src/core/workers.ts
|
|
11500
|
-
var same = (left, right) => {
|
|
11501
|
-
try {
|
|
11502
|
-
return canonicalJson(left) === canonicalJson(right);
|
|
11503
|
-
} catch {
|
|
11504
|
-
return false;
|
|
11505
|
-
}
|
|
11506
|
-
};
|
|
11507
|
-
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
11508
|
-
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
11509
|
-
const code = char.charCodeAt(0);
|
|
11510
|
-
return code < 32 || code === 127;
|
|
11511
|
-
}) && !value.split(/[\\/]/).includes("..");
|
|
11512
|
-
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
11513
|
-
var workerIdOf = (value) => value.workerId ?? null;
|
|
11514
|
-
function assertProductWriteAllowed(input) {
|
|
11515
|
-
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
11516
|
-
return failure("invalid_input", "invalid product write path");
|
|
11517
|
-
if (!input.store)
|
|
11518
|
-
return failure("permission_denied", "write authorization requires core state");
|
|
11519
|
-
const task = input.store.readTask(input.task.id);
|
|
11520
|
-
if (!task.ok)
|
|
11521
|
-
return task;
|
|
11522
|
-
const workspace = input.store.readWorkspace();
|
|
11523
|
-
if (!workspace.ok)
|
|
11524
|
-
return workspace;
|
|
11525
|
-
if (!workspace.data)
|
|
11526
|
-
return failure("not_found", "workspace not found");
|
|
11527
|
-
const workspaceRecord = workspace.data;
|
|
11528
|
-
if (task.data.status !== "active")
|
|
11529
|
-
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
11530
|
-
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
11531
|
-
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
11532
|
-
const writer = workspaceRecord.writer;
|
|
11533
|
-
if (!writer)
|
|
11534
|
-
return failure("permission_denied", "checkout has no writer owner");
|
|
11535
|
-
if (writer.state === "uncertain")
|
|
11536
|
-
return failure("recovery_required", "checkout writer requires recovery", {
|
|
11537
|
-
owner: writer.owner
|
|
11538
|
-
});
|
|
11539
|
-
const caller = callerValue(input.caller);
|
|
11540
|
-
const workerId = workerIdOf(input.caller);
|
|
11541
|
-
const owner = writer.owner;
|
|
11542
|
-
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
11543
|
-
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
11544
|
-
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
11545
|
-
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
11546
|
-
return failure("permission_denied", "only implementers can own product writes");
|
|
11547
|
-
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))))
|
|
11548
|
-
return failure("recovery_required", "worker session is not an active writer");
|
|
11549
|
-
return success(null, workspaceRecord.revision, owner);
|
|
11550
|
-
}
|
|
11551
|
-
|
|
11552
11886
|
// packages/workit-core/src/core/task-engine.ts
|
|
11553
11887
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
11554
11888
|
kind,
|
|
@@ -11679,15 +12013,20 @@ var applyWorkerLifecycle = (input) => {
|
|
|
11679
12013
|
return failure("invalid_transition", "worker has not started");
|
|
11680
12014
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
11681
12015
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
12016
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
11682
12017
|
const nextEntry = {
|
|
11683
12018
|
...entry,
|
|
11684
12019
|
recordedAt: input.now ?? entry.recordedAt,
|
|
11685
12020
|
provenance: authority.provenance,
|
|
11686
|
-
data: {
|
|
12021
|
+
data: {
|
|
12022
|
+
...entry.data,
|
|
12023
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
12024
|
+
session: input.session
|
|
12025
|
+
}
|
|
11687
12026
|
};
|
|
11688
12027
|
const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
11689
12028
|
const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
11690
|
-
if (entry.data.state ===
|
|
12029
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
11691
12030
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
11692
12031
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
11693
12032
|
taskId: task.data.id,
|
|
@@ -12057,7 +12396,7 @@ class WorkitCore {
|
|
|
12057
12396
|
activeWorkerBlocker(task, workspace) {
|
|
12058
12397
|
if (workspace.writer)
|
|
12059
12398
|
return failure("recovery_required", "writer ownership must be released first");
|
|
12060
|
-
if (task.workers.some((entry) =>
|
|
12399
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
12061
12400
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
12062
12401
|
return null;
|
|
12063
12402
|
}
|
|
@@ -12294,11 +12633,6 @@ class WorkitCore {
|
|
|
12294
12633
|
if (helper && !helper.ok)
|
|
12295
12634
|
return helper;
|
|
12296
12635
|
const evidence = input.evidence;
|
|
12297
|
-
if (helper?.ok) {
|
|
12298
|
-
const assignment = helper.data.data.assignment;
|
|
12299
|
-
if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || assignment.candidateId !== null && evidence.candidateId !== assignment.candidateId && evidence.beforeCandidateId !== assignment.candidateId || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
|
|
12300
|
-
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
12301
|
-
}
|
|
12302
12636
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
12303
12637
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
12304
12638
|
if (evidence.kind === "review") {
|
|
@@ -12315,6 +12649,11 @@ class WorkitCore {
|
|
|
12315
12649
|
evidence.beforeCandidateId ??= null;
|
|
12316
12650
|
evidence.candidateId ??= null;
|
|
12317
12651
|
}
|
|
12652
|
+
if (helper?.ok) {
|
|
12653
|
+
const assignment = helper.data.data.assignment;
|
|
12654
|
+
if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || assignment.candidateId !== null && evidence.candidateId !== assignment.candidateId && evidence.beforeCandidateId !== assignment.candidateId || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
|
|
12655
|
+
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
12656
|
+
}
|
|
12318
12657
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
12319
12658
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
12320
12659
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -12368,7 +12707,12 @@ class WorkitCore {
|
|
|
12368
12707
|
const input = parsed.data;
|
|
12369
12708
|
if (nativeRequired && input.action !== "record")
|
|
12370
12709
|
return failure("permission_denied", "native observation is only valid for recording decisions");
|
|
12371
|
-
|
|
12710
|
+
const stated = input.action === "record" && input.response === "stated";
|
|
12711
|
+
if (stated && input.purpose === "action")
|
|
12712
|
+
return failure("permission_denied", "stated choices cannot authorize mutating actions");
|
|
12713
|
+
if (stated && !input.binding?.statedChoice)
|
|
12714
|
+
return failure("invalid_input", "stated choices require binding.statedChoice");
|
|
12715
|
+
if (nativeRequired && nativeObservation === undefined && !stated)
|
|
12372
12716
|
return failure("permission_denied", "native decision observation is required");
|
|
12373
12717
|
const task = this.store.readTask(input.taskId);
|
|
12374
12718
|
if (!task.ok)
|
|
@@ -12401,7 +12745,7 @@ class WorkitCore {
|
|
|
12401
12745
|
consumption: null
|
|
12402
12746
|
};
|
|
12403
12747
|
const data = { ...base, digest: decisionDigest(base) };
|
|
12404
|
-
const native = nativeRequired ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
12748
|
+
const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
12405
12749
|
observation: nativeObservation,
|
|
12406
12750
|
expected: {
|
|
12407
12751
|
taskId: task.data.id,
|
|
@@ -13210,6 +13554,9 @@ class WorkitCore {
|
|
|
13210
13554
|
id: task.id,
|
|
13211
13555
|
revision: task.revision,
|
|
13212
13556
|
workspaceRevision: workspace.data.revision,
|
|
13557
|
+
createdAt: task.createdAt,
|
|
13558
|
+
updatedAt: task.updatedAt,
|
|
13559
|
+
runtime: task.runtime ?? null,
|
|
13213
13560
|
objective: task.intent.data.objective,
|
|
13214
13561
|
status: task.status,
|
|
13215
13562
|
closure: task.closure,
|
|
@@ -13295,7 +13642,7 @@ class WorkitCore {
|
|
|
13295
13642
|
const base = reconcileResume(effectiveView);
|
|
13296
13643
|
if (!base.ok)
|
|
13297
13644
|
return base;
|
|
13298
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
13645
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
13299
13646
|
const blockers = [...base.data.blockers];
|
|
13300
13647
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
13301
13648
|
blockers.push({
|
|
@@ -13362,7 +13709,8 @@ class WorkitCore {
|
|
|
13362
13709
|
...current,
|
|
13363
13710
|
status,
|
|
13364
13711
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
13365
|
-
progress:
|
|
13712
|
+
progress: current.progress,
|
|
13713
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
13366
13714
|
})
|
|
13367
13715
|
});
|
|
13368
13716
|
if (!changed.ok)
|
|
@@ -13398,7 +13746,7 @@ class WorkitCore {
|
|
|
13398
13746
|
return workspace;
|
|
13399
13747
|
if (!workspace.data)
|
|
13400
13748
|
return failure("not_found", "workspace not found");
|
|
13401
|
-
if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state
|
|
13749
|
+
if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
13402
13750
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
13403
13751
|
this.fillRevisions(input, task.data, workspace.data);
|
|
13404
13752
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -13516,15 +13864,15 @@ function resolveGitRevision(root, value) {
|
|
|
13516
13864
|
}
|
|
13517
13865
|
function resolveInside(root, candidate) {
|
|
13518
13866
|
const base = realpathSync5(root);
|
|
13519
|
-
const target =
|
|
13520
|
-
if (target !== base && !target.startsWith(base +
|
|
13867
|
+
const target = path7.resolve(base, candidate);
|
|
13868
|
+
if (target !== base && !target.startsWith(base + path7.sep)) {
|
|
13521
13869
|
throw new Error("path must stay inside repository root");
|
|
13522
13870
|
}
|
|
13523
13871
|
let ancestor = target;
|
|
13524
|
-
while (!
|
|
13525
|
-
ancestor =
|
|
13872
|
+
while (!existsSync4(ancestor))
|
|
13873
|
+
ancestor = path7.dirname(ancestor);
|
|
13526
13874
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
13527
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
13875
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path7.sep)) {
|
|
13528
13876
|
throw new Error("path must stay inside repository root");
|
|
13529
13877
|
}
|
|
13530
13878
|
return target;
|
|
@@ -13567,6 +13915,13 @@ var piCapabilities = (ctx) => [
|
|
|
13567
13915
|
assurance: "agent_guided",
|
|
13568
13916
|
reason: "Pi extensions do not sandbox arbitrary shell commands.",
|
|
13569
13917
|
refs: [hostRef("tool_call")]
|
|
13918
|
+
},
|
|
13919
|
+
{
|
|
13920
|
+
name: "fresh-context-review",
|
|
13921
|
+
surface: "supervised_worker",
|
|
13922
|
+
assurance: "agent_guided",
|
|
13923
|
+
reason: "independent review runs as a supervised stock-Pi process; evidence evaluation enforces reviewer exclusivity",
|
|
13924
|
+
refs: [hostRef("worker")]
|
|
13570
13925
|
}
|
|
13571
13926
|
];
|
|
13572
13927
|
var piContext = (ctx) => ({
|
|
@@ -13634,15 +13989,15 @@ var WORKIT_SKILL_ALIASES = {
|
|
|
13634
13989
|
if (false) {}
|
|
13635
13990
|
|
|
13636
13991
|
// packages/workit-core/src/core/branch.ts
|
|
13637
|
-
import { existsSync as
|
|
13638
|
-
import { execFileSync as
|
|
13639
|
-
import
|
|
13992
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
|
|
13993
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
13994
|
+
import path11 from "node:path";
|
|
13640
13995
|
|
|
13641
13996
|
// packages/workit-core/src/core/git.ts
|
|
13642
|
-
import { execFileSync } from "node:child_process";
|
|
13997
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
13643
13998
|
var run2 = (cwd, args) => {
|
|
13644
13999
|
try {
|
|
13645
|
-
const stdout =
|
|
14000
|
+
const stdout = execFileSync2("git", args, {
|
|
13646
14001
|
cwd,
|
|
13647
14002
|
encoding: "utf8",
|
|
13648
14003
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -13710,14 +14065,14 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
13710
14065
|
import {
|
|
13711
14066
|
copyFileSync,
|
|
13712
14067
|
cpSync,
|
|
13713
|
-
existsSync as
|
|
14068
|
+
existsSync as existsSync5,
|
|
13714
14069
|
mkdirSync as mkdirSync2,
|
|
13715
14070
|
readFileSync as readFileSync4,
|
|
13716
14071
|
readdirSync as readdirSync3,
|
|
13717
14072
|
writeFileSync as writeFileSync2
|
|
13718
14073
|
} from "node:fs";
|
|
13719
14074
|
import os from "node:os";
|
|
13720
|
-
import
|
|
14075
|
+
import path8 from "node:path";
|
|
13721
14076
|
|
|
13722
14077
|
// packages/workit-core/src/core/boundary.ts
|
|
13723
14078
|
var EVENT = {
|
|
@@ -13761,7 +14116,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
13761
14116
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
13762
14117
|
};
|
|
13763
14118
|
};
|
|
13764
|
-
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
14119
|
+
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path8.join(process.env.XDG_CONFIG_HOME || path8.join(os.homedir(), ".config"), "workit");
|
|
13765
14120
|
var migratedDir = null;
|
|
13766
14121
|
var migrationFailed = false;
|
|
13767
14122
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -13771,12 +14126,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
13771
14126
|
migratedDir = dir;
|
|
13772
14127
|
return dir;
|
|
13773
14128
|
}
|
|
13774
|
-
const legacy =
|
|
13775
|
-
if (!
|
|
14129
|
+
const legacy = path8.join(process.env.XDG_CONFIG_HOME || path8.join(os.homedir(), ".config"), "workflow-toolkit");
|
|
14130
|
+
if (!existsSync5(legacy)) {
|
|
13776
14131
|
migratedDir = dir;
|
|
13777
14132
|
return dir;
|
|
13778
14133
|
}
|
|
13779
|
-
if (!migrationFailed &&
|
|
14134
|
+
if (!migrationFailed && existsSync5(dir)) {
|
|
13780
14135
|
migratedDir = dir;
|
|
13781
14136
|
return dir;
|
|
13782
14137
|
}
|
|
@@ -13784,9 +14139,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
13784
14139
|
mkdirSync2(dir, { recursive: true });
|
|
13785
14140
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
13786
14141
|
for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
|
|
13787
|
-
const src =
|
|
13788
|
-
const dest =
|
|
13789
|
-
if (
|
|
14142
|
+
const src = path8.join(legacy, entry.name);
|
|
14143
|
+
const dest = path8.join(dir, entry.name);
|
|
14144
|
+
if (existsSync5(dest))
|
|
13790
14145
|
continue;
|
|
13791
14146
|
try {
|
|
13792
14147
|
if (entry.isDirectory())
|
|
@@ -13859,7 +14214,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
13859
14214
|
};
|
|
13860
14215
|
};
|
|
13861
14216
|
var readConfigTyped = (dir) => {
|
|
13862
|
-
const file =
|
|
14217
|
+
const file = path8.join(dir ?? configDir(), "config.json");
|
|
13863
14218
|
return parseConfigResult(readSafe(file), file);
|
|
13864
14219
|
};
|
|
13865
14220
|
var readConfig = () => {
|
|
@@ -13905,9 +14260,9 @@ var resolveCommitPolicy = (config, workspace) => {
|
|
|
13905
14260
|
|
|
13906
14261
|
// packages/workit-core/src/core/workspaces.ts
|
|
13907
14262
|
import { readFileSync as readFileSync5, realpathSync as realpathSync6 } from "node:fs";
|
|
13908
|
-
import
|
|
14263
|
+
import path9 from "node:path";
|
|
13909
14264
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
13910
|
-
const file =
|
|
14265
|
+
const file = path9.join(dir, "workspaces.json");
|
|
13911
14266
|
let raw;
|
|
13912
14267
|
try {
|
|
13913
14268
|
raw = readFileSync5(file, "utf8");
|
|
@@ -14005,10 +14360,10 @@ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
|
|
|
14005
14360
|
|
|
14006
14361
|
// packages/workit-core/src/core/vcs-config.ts
|
|
14007
14362
|
import fs7 from "node:fs";
|
|
14008
|
-
import
|
|
14363
|
+
import path10 from "node:path";
|
|
14009
14364
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
14010
14365
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
14011
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ??
|
|
14366
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
|
|
14012
14367
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
14013
14368
|
var remoteProvider = (cwd) => {
|
|
14014
14369
|
const r = spawnSync3("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
@@ -14091,8 +14446,8 @@ function vcsConfig(mode, cwd) {
|
|
|
14091
14446
|
}
|
|
14092
14447
|
const prov = cfg[provider] ?? {};
|
|
14093
14448
|
const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
|
|
14094
|
-
const tokenFile = String(wsTokenFile ?? prov.tokenFile ??
|
|
14095
|
-
const tokenPath =
|
|
14449
|
+
const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path10.join(configDir(), `${provider}.token`));
|
|
14450
|
+
const tokenPath = path10.resolve(tokenFile);
|
|
14096
14451
|
let tokenOk = false;
|
|
14097
14452
|
if (fs7.existsSync(tokenPath)) {
|
|
14098
14453
|
const token = fs7.readFileSync(tokenPath, "utf8").trim();
|
|
@@ -14101,7 +14456,7 @@ function vcsConfig(mode, cwd) {
|
|
|
14101
14456
|
}
|
|
14102
14457
|
const out = {
|
|
14103
14458
|
ok: true,
|
|
14104
|
-
configPath:
|
|
14459
|
+
configPath: path10.resolve(cfgPath),
|
|
14105
14460
|
provider,
|
|
14106
14461
|
defaultTargetBranch: defaultTarget,
|
|
14107
14462
|
pr: cfg.pr ?? {},
|
|
@@ -14202,7 +14557,7 @@ var baseBranch = (cwd) => {
|
|
|
14202
14557
|
return { base: String(resolved.defaultTargetBranch ?? "develop") };
|
|
14203
14558
|
};
|
|
14204
14559
|
var originBaseReady = (cwd, base) => {
|
|
14205
|
-
const run = (args) =>
|
|
14560
|
+
const run = (args) => execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
14206
14561
|
try {
|
|
14207
14562
|
try {
|
|
14208
14563
|
run(["fetch", "origin", base, "--prune"]);
|
|
@@ -14210,7 +14565,7 @@ var originBaseReady = (cwd, base) => {
|
|
|
14210
14565
|
run(["fetch", "origin", "--prune"]);
|
|
14211
14566
|
}
|
|
14212
14567
|
try {
|
|
14213
|
-
|
|
14568
|
+
execFileSync3("git", ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${base}`], {
|
|
14214
14569
|
cwd,
|
|
14215
14570
|
stdio: "pipe"
|
|
14216
14571
|
});
|
|
@@ -14229,11 +14584,11 @@ var originBaseReady = (cwd, base) => {
|
|
|
14229
14584
|
}
|
|
14230
14585
|
};
|
|
14231
14586
|
var fastForwardBase = (cwd, base) => {
|
|
14232
|
-
const run = (args) =>
|
|
14587
|
+
const run = (args) => execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
14233
14588
|
try {
|
|
14234
14589
|
let hasLocalBase = true;
|
|
14235
14590
|
try {
|
|
14236
|
-
|
|
14591
|
+
execFileSync3("git", ["show-ref", "--verify", "--quiet", `refs/heads/${base}`], {
|
|
14237
14592
|
cwd,
|
|
14238
14593
|
stdio: "pipe"
|
|
14239
14594
|
});
|
|
@@ -14273,19 +14628,18 @@ var branchSetup = ({
|
|
|
14273
14628
|
workspace_root,
|
|
14274
14629
|
log
|
|
14275
14630
|
}) => {
|
|
14276
|
-
const cwd =
|
|
14277
|
-
const exec = (args) =>
|
|
14631
|
+
const cwd = path11.resolve(workspace_root);
|
|
14632
|
+
const exec = (args) => execFileSync3("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
14278
14633
|
const current = gitContext(cwd).branch;
|
|
14279
14634
|
if (!current || current === "unknown")
|
|
14280
|
-
return { error: "not in a git repository" };
|
|
14635
|
+
return { error: "not in a git repository", phase: "preflight" };
|
|
14281
14636
|
const sdd = sdd_dir ?? "docs";
|
|
14282
|
-
const manifestPath =
|
|
14637
|
+
const manifestPath = path11.isAbsolute(sdd) ? path11.join(sdd, "manifest.json") : path11.join(cwd, sdd, "manifest.json");
|
|
14283
14638
|
const ensureManifestDir = () => {
|
|
14284
|
-
const dir =
|
|
14285
|
-
if (!
|
|
14639
|
+
const dir = path11.dirname(manifestPath);
|
|
14640
|
+
if (!existsSync6(dir))
|
|
14286
14641
|
mkdirSync3(dir, { recursive: true, mode: 493 });
|
|
14287
14642
|
};
|
|
14288
|
-
ensureManifestDir();
|
|
14289
14643
|
const readManifest = () => {
|
|
14290
14644
|
try {
|
|
14291
14645
|
return JSON.parse(readFileSync6(manifestPath, "utf8"));
|
|
@@ -14303,13 +14657,16 @@ var branchSetup = ({
|
|
|
14303
14657
|
const manifest = readManifest();
|
|
14304
14658
|
const ref = manifest.stash_ref;
|
|
14305
14659
|
if (!ref)
|
|
14306
|
-
return { error: "no stash_ref in manifest" };
|
|
14660
|
+
return { error: "no stash_ref in manifest", phase: "preflight" };
|
|
14307
14661
|
journal(`pre-pop: ${String(ref)}`);
|
|
14308
14662
|
try {
|
|
14309
14663
|
exec(["stash", "pop", String(ref)]);
|
|
14310
14664
|
} catch (error) {
|
|
14311
14665
|
journal("pop: failed");
|
|
14312
|
-
return {
|
|
14666
|
+
return {
|
|
14667
|
+
error: error instanceof Error ? error.message : "stash pop failed",
|
|
14668
|
+
phase: "post_mutation"
|
|
14669
|
+
};
|
|
14313
14670
|
}
|
|
14314
14671
|
journal("pop: ok");
|
|
14315
14672
|
delete manifest.stash_ref;
|
|
@@ -14319,11 +14676,17 @@ var branchSetup = ({
|
|
|
14319
14676
|
}
|
|
14320
14677
|
const target = target_branch ?? "";
|
|
14321
14678
|
if (!target)
|
|
14322
|
-
return {
|
|
14679
|
+
return {
|
|
14680
|
+
error: "target_branch (the working branch to create or switch to) is required for setup",
|
|
14681
|
+
phase: "preflight"
|
|
14682
|
+
};
|
|
14323
14683
|
if (isProtected(cwd, target))
|
|
14324
|
-
return { error: `protected branch ${target}
|
|
14684
|
+
return { error: `protected branch ${target}`, phase: "preflight" };
|
|
14325
14685
|
if (!allowedBranch(cwd, target))
|
|
14326
|
-
return {
|
|
14686
|
+
return {
|
|
14687
|
+
error: `target branch ${target} is not allowed by the branch policy`,
|
|
14688
|
+
phase: "preflight"
|
|
14689
|
+
};
|
|
14327
14690
|
let base;
|
|
14328
14691
|
let targetExists = true;
|
|
14329
14692
|
try {
|
|
@@ -14334,12 +14697,12 @@ var branchSetup = ({
|
|
|
14334
14697
|
if (!targetExists) {
|
|
14335
14698
|
const baseResolved = baseBranch(cwd);
|
|
14336
14699
|
if ("error" in baseResolved)
|
|
14337
|
-
return { error: baseResolved.error };
|
|
14700
|
+
return { error: baseResolved.error, phase: "preflight" };
|
|
14338
14701
|
base = baseResolved.base;
|
|
14339
14702
|
}
|
|
14340
14703
|
journal(`entry: current=${current} target=${target} base=${base ?? "-"}`);
|
|
14341
14704
|
let stash_ref;
|
|
14342
|
-
const failAfterStash = (message) => {
|
|
14705
|
+
const failAfterStash = (message, phase = "post_mutation") => {
|
|
14343
14706
|
let suffix = "";
|
|
14344
14707
|
if (stash_ref) {
|
|
14345
14708
|
journal(`pre-pop: ${stash_ref}`);
|
|
@@ -14352,27 +14715,31 @@ var branchSetup = ({
|
|
|
14352
14715
|
suffix = " (changes preserved in stash)";
|
|
14353
14716
|
}
|
|
14354
14717
|
}
|
|
14355
|
-
return { error: `${message}${suffix}
|
|
14718
|
+
return { error: `${message}${suffix}`, phase: stash_ref ? "post_mutation" : phase };
|
|
14356
14719
|
};
|
|
14357
14720
|
if (current !== target) {
|
|
14358
14721
|
const dirty = Boolean(gitContext(cwd).status_short.trim());
|
|
14359
14722
|
if (dirty && stash !== "yes") {
|
|
14360
14723
|
return {
|
|
14361
|
-
error: "dirty working tree — ask with native question, then call workit_branch_setup with stash=yes"
|
|
14724
|
+
error: "dirty working tree — ask with native question, then call workit_branch_setup with stash=yes",
|
|
14725
|
+
phase: "preflight"
|
|
14362
14726
|
};
|
|
14363
14727
|
}
|
|
14364
14728
|
let validatedBase;
|
|
14365
14729
|
if (!targetExists && base !== undefined) {
|
|
14366
14730
|
const ready = originBaseReady(cwd, base);
|
|
14367
14731
|
if (!ready.ok)
|
|
14368
|
-
return { error: ready.error ?? "ensure-base-branch failed" };
|
|
14732
|
+
return { error: ready.error ?? "ensure-base-branch failed", phase: "preflight" };
|
|
14369
14733
|
validatedBase = base;
|
|
14370
14734
|
}
|
|
14371
14735
|
if (dirty) {
|
|
14372
14736
|
try {
|
|
14373
14737
|
exec(["stash", "push", "-u", "-m", `workit: pre-checkout ${target}`]);
|
|
14374
14738
|
} catch (error) {
|
|
14375
|
-
return {
|
|
14739
|
+
return {
|
|
14740
|
+
error: error instanceof Error ? error.message : "stash push failed",
|
|
14741
|
+
phase: "post_mutation"
|
|
14742
|
+
};
|
|
14376
14743
|
}
|
|
14377
14744
|
stash_ref = "stash@{0}";
|
|
14378
14745
|
journal(`stash push: ${stash_ref}`);
|
|
@@ -14383,14 +14750,14 @@ var branchSetup = ({
|
|
|
14383
14750
|
} catch (error) {
|
|
14384
14751
|
const message = error instanceof Error ? error.message : "checkout failed";
|
|
14385
14752
|
if (/worktree/i.test(message)) {
|
|
14386
|
-
return failAfterStash(`branch ${target} is locked by an existing git worktree — remove it first (we do not use worktrees)
|
|
14753
|
+
return failAfterStash(`branch ${target} is locked by an existing git worktree — remove it first (we do not use worktrees)`, "preflight");
|
|
14387
14754
|
}
|
|
14388
14755
|
try {
|
|
14389
14756
|
let effectiveBase = base;
|
|
14390
14757
|
if (effectiveBase === undefined) {
|
|
14391
14758
|
const lateResolved = baseBranch(cwd);
|
|
14392
14759
|
if ("error" in lateResolved)
|
|
14393
|
-
return failAfterStash(lateResolved.error);
|
|
14760
|
+
return failAfterStash(lateResolved.error, "preflight");
|
|
14394
14761
|
effectiveBase = lateResolved.base;
|
|
14395
14762
|
}
|
|
14396
14763
|
const baseResult = validatedBase !== undefined ? fastForwardBase(cwd, effectiveBase) : ensureBaseBranch(cwd, effectiveBase);
|
|
@@ -14437,7 +14804,7 @@ var branchSetup = ({
|
|
|
14437
14804
|
|
|
14438
14805
|
// packages/workit-core/src/core/pr-create.ts
|
|
14439
14806
|
import fs8 from "node:fs";
|
|
14440
|
-
import
|
|
14807
|
+
import path12 from "node:path";
|
|
14441
14808
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
14442
14809
|
function parseGhRepo(remote) {
|
|
14443
14810
|
remote = (remote || "").trim().replace(/\/+$/, "");
|
|
@@ -14502,11 +14869,11 @@ ${line}` : line;
|
|
|
14502
14869
|
}
|
|
14503
14870
|
function whichOnPath(tool) {
|
|
14504
14871
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
14505
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
14872
|
+
for (const dir of (process.env.PATH ?? "").split(path12.delimiter)) {
|
|
14506
14873
|
if (!dir)
|
|
14507
14874
|
continue;
|
|
14508
14875
|
for (const name of names) {
|
|
14509
|
-
const candidate =
|
|
14876
|
+
const candidate = path12.join(dir, name);
|
|
14510
14877
|
try {
|
|
14511
14878
|
fs8.accessSync(candidate, fs8.constants.X_OK);
|
|
14512
14879
|
return candidate;
|
|
@@ -14609,7 +14976,7 @@ function prCreate(env, cwd) {
|
|
|
14609
14976
|
const draft = String(env.WF_PR_DRAFT ?? "false").toLowerCase() === "true";
|
|
14610
14977
|
let baseUrl = cfg.youtrack_base_url;
|
|
14611
14978
|
if (!baseUrl) {
|
|
14612
|
-
const ytCfg = process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
14979
|
+
const ytCfg = process.env.WORKFLOW_YOUTRACK_CONFIG ?? path12.join(path12.dirname(String(cfg.configPath)), "youtrack.json");
|
|
14613
14980
|
try {
|
|
14614
14981
|
const yt = JSON.parse(fs8.readFileSync(ytCfg, "utf8"));
|
|
14615
14982
|
if (yt && typeof yt === "object")
|
|
@@ -14716,8 +15083,8 @@ function prCreate(env, cwd) {
|
|
|
14716
15083
|
|
|
14717
15084
|
// packages/workit-core/src/core/repo-context.ts
|
|
14718
15085
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
14719
|
-
import { existsSync as
|
|
14720
|
-
import
|
|
15086
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
|
|
15087
|
+
import path13 from "node:path";
|
|
14721
15088
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
14722
15089
|
try {
|
|
14723
15090
|
gitRevisionParts(value);
|
|
@@ -14851,7 +15218,7 @@ var findPrTemplate = (cwd) => {
|
|
|
14851
15218
|
]) {
|
|
14852
15219
|
const parts = rel.split("/");
|
|
14853
15220
|
const base = parts[parts.length - 1];
|
|
14854
|
-
const dir = parts.length > 1 ?
|
|
15221
|
+
const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
14855
15222
|
const actual = probeCaseInsensitive(dir, base);
|
|
14856
15223
|
if (actual)
|
|
14857
15224
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -14882,8 +15249,8 @@ var diffStatForRange = (cwd, range) => {
|
|
|
14882
15249
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
14883
15250
|
};
|
|
14884
15251
|
var packageScripts = (cwd, keys) => {
|
|
14885
|
-
const pkgPath =
|
|
14886
|
-
if (!
|
|
15252
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
15253
|
+
if (!existsSync7(pkgPath))
|
|
14887
15254
|
return [];
|
|
14888
15255
|
try {
|
|
14889
15256
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
@@ -14908,9 +15275,9 @@ var documentationFiles = (cwd) => {
|
|
|
14908
15275
|
if (entry.isDirectory()) {
|
|
14909
15276
|
if (excluded.has(entry.name))
|
|
14910
15277
|
continue;
|
|
14911
|
-
walk(
|
|
15278
|
+
walk(path13.join(dir, entry.name), depth + 1);
|
|
14912
15279
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
14913
|
-
matches.push("./" +
|
|
15280
|
+
matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
14914
15281
|
}
|
|
14915
15282
|
}
|
|
14916
15283
|
};
|
|
@@ -14918,7 +15285,7 @@ var documentationFiles = (cwd) => {
|
|
|
14918
15285
|
return matches.sort().slice(0, 200);
|
|
14919
15286
|
};
|
|
14920
15287
|
var readTrimmed = (file, maxLines) => {
|
|
14921
|
-
if (!
|
|
15288
|
+
if (!existsSync7(file))
|
|
14922
15289
|
return "";
|
|
14923
15290
|
const lines = readFileSync7(file, "utf8").split(`
|
|
14924
15291
|
`);
|
|
@@ -14926,7 +15293,7 @@ var readTrimmed = (file, maxLines) => {
|
|
|
14926
15293
|
`);
|
|
14927
15294
|
};
|
|
14928
15295
|
function prReadyContext(root, range) {
|
|
14929
|
-
const cwd =
|
|
15296
|
+
const cwd = path13.resolve(repoRoot2(root));
|
|
14930
15297
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
14931
15298
|
return invalidRange(cwd);
|
|
14932
15299
|
let stdout = "";
|
|
@@ -14984,7 +15351,7 @@ ${status.stderr}` : "");
|
|
|
14984
15351
|
stdout += `template_path: ${template}
|
|
14985
15352
|
|
|
14986
15353
|
`;
|
|
14987
|
-
stdout += readTrimmed(
|
|
15354
|
+
stdout += readTrimmed(path13.join(cwd, template), 220);
|
|
14988
15355
|
} else {
|
|
14989
15356
|
stdout += `template_path: none
|
|
14990
15357
|
|
|
@@ -14994,7 +15361,7 @@ ${status.stderr}` : "");
|
|
|
14994
15361
|
stdout += `
|
|
14995
15362
|
`;
|
|
14996
15363
|
stdout += printSection("Recent Validation Signals");
|
|
14997
|
-
if (
|
|
15364
|
+
if (existsSync7(path13.join(cwd, "package.json"))) {
|
|
14998
15365
|
stdout += `package.json detected. Common scripts:
|
|
14999
15366
|
`;
|
|
15000
15367
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -15003,7 +15370,7 @@ ${status.stderr}` : "");
|
|
|
15003
15370
|
`) + `
|
|
15004
15371
|
`;
|
|
15005
15372
|
}
|
|
15006
|
-
if (
|
|
15373
|
+
if (existsSync7(path13.join(cwd, "Cargo.toml")) || existsSync7(path13.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
15007
15374
|
stdout += `Rust project detected.
|
|
15008
15375
|
`;
|
|
15009
15376
|
}
|
|
@@ -15036,7 +15403,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
15036
15403
|
- Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
|
|
15037
15404
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
15038
15405
|
function changelogContext(root, range) {
|
|
15039
|
-
const cwd =
|
|
15406
|
+
const cwd = path13.resolve(repoRoot2(root));
|
|
15040
15407
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
15041
15408
|
return invalidRange(cwd);
|
|
15042
15409
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -15054,8 +15421,8 @@ function changelogContext(root, range) {
|
|
|
15054
15421
|
stdout += CHANGELOG_RULES + `
|
|
15055
15422
|
`;
|
|
15056
15423
|
stdout += printSection("Existing CHANGELOG.md");
|
|
15057
|
-
const changelogPath =
|
|
15058
|
-
if (
|
|
15424
|
+
const changelogPath = path13.join(cwd, "CHANGELOG.md");
|
|
15425
|
+
if (existsSync7(changelogPath)) {
|
|
15059
15426
|
stdout += readTrimmed(changelogPath, 260) + `
|
|
15060
15427
|
`;
|
|
15061
15428
|
} else {
|
|
@@ -15074,7 +15441,7 @@ function changelogContext(root, range) {
|
|
|
15074
15441
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
15075
15442
|
}
|
|
15076
15443
|
function docsRefreshContext(root, range) {
|
|
15077
|
-
const cwd =
|
|
15444
|
+
const cwd = path13.resolve(repoRoot2(root));
|
|
15078
15445
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
15079
15446
|
return invalidRange(cwd);
|
|
15080
15447
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -15096,8 +15463,8 @@ function docsRefreshContext(root, range) {
|
|
|
15096
15463
|
`) + `
|
|
15097
15464
|
`;
|
|
15098
15465
|
stdout += printSection("README Preview");
|
|
15099
|
-
const readme =
|
|
15100
|
-
if (
|
|
15466
|
+
const readme = path13.join(cwd, "README.md");
|
|
15467
|
+
if (existsSync7(readme)) {
|
|
15101
15468
|
stdout += readTrimmed(readme, 220) + `
|
|
15102
15469
|
`;
|
|
15103
15470
|
} else {
|
|
@@ -15105,8 +15472,8 @@ function docsRefreshContext(root, range) {
|
|
|
15105
15472
|
`;
|
|
15106
15473
|
}
|
|
15107
15474
|
stdout += printSection("Package Scripts");
|
|
15108
|
-
const pkgPath =
|
|
15109
|
-
if (
|
|
15475
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
15476
|
+
if (existsSync7(pkgPath)) {
|
|
15110
15477
|
try {
|
|
15111
15478
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
15112
15479
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
@@ -15119,7 +15486,7 @@ function docsRefreshContext(root, range) {
|
|
|
15119
15486
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
15120
15487
|
}
|
|
15121
15488
|
function releaseNotesContext(root, rangeOrTag) {
|
|
15122
|
-
const cwd =
|
|
15489
|
+
const cwd = path13.resolve(repoRoot2(root));
|
|
15123
15490
|
if (!rangeOrTag) {
|
|
15124
15491
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
15125
15492
|
`, exitCode: 1, cwd };
|
|
@@ -15157,7 +15524,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
15157
15524
|
`;
|
|
15158
15525
|
stdout += printSection("Existing Release Files");
|
|
15159
15526
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
15160
|
-
if (
|
|
15527
|
+
if (existsSync7(path13.join(cwd, rel)))
|
|
15161
15528
|
stdout += rel + `
|
|
15162
15529
|
`;
|
|
15163
15530
|
}
|
|
@@ -15348,25 +15715,6 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
15348
15715
|
import fs10 from "node:fs";
|
|
15349
15716
|
import path14 from "node:path";
|
|
15350
15717
|
|
|
15351
|
-
// packages/workit-core/src/core/package-root.ts
|
|
15352
|
-
import { existsSync as existsSync7 } from "node:fs";
|
|
15353
|
-
import path13 from "node:path";
|
|
15354
|
-
import { fileURLToPath } from "node:url";
|
|
15355
|
-
var packageRoot = () => {
|
|
15356
|
-
let dir = path13.dirname(fileURLToPath(import.meta.url));
|
|
15357
|
-
while (true) {
|
|
15358
|
-
const parent = path13.dirname(dir);
|
|
15359
|
-
if (existsSync7(path13.join(dir, "package.json")) || parent === dir)
|
|
15360
|
-
return dir;
|
|
15361
|
-
dir = parent;
|
|
15362
|
-
}
|
|
15363
|
-
};
|
|
15364
|
-
var assetRoot = () => {
|
|
15365
|
-
const root = packageRoot();
|
|
15366
|
-
const assets = path13.join(root, "assets");
|
|
15367
|
-
return existsSync7(assets) ? assets : root;
|
|
15368
|
-
};
|
|
15369
|
-
|
|
15370
15718
|
// packages/workit-core/src/core/templates.ts
|
|
15371
15719
|
var repoRoot3 = assetRoot();
|
|
15372
15720
|
|
|
@@ -16572,6 +16920,101 @@ var safePath = (root, value) => {
|
|
|
16572
16920
|
return null;
|
|
16573
16921
|
}
|
|
16574
16922
|
};
|
|
16923
|
+
var branchCreationBaseline = (root, target, base) => {
|
|
16924
|
+
const exists = gitValue(root, ["rev-parse", "--verify", "--quiet", `refs/heads/${target}`]) !== null;
|
|
16925
|
+
if (exists)
|
|
16926
|
+
return { base_branch: base, target_exists: true, remote_base: null };
|
|
16927
|
+
const remote = run3(root, ["ls-remote", "origin", `refs/heads/${base}`]);
|
|
16928
|
+
const sha = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : "";
|
|
16929
|
+
return {
|
|
16930
|
+
base_branch: base,
|
|
16931
|
+
target_exists: false,
|
|
16932
|
+
remote_base: /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : null
|
|
16933
|
+
};
|
|
16934
|
+
};
|
|
16935
|
+
var shortSha = (value) => typeof value === "string" && value ? value.slice(0, 8) : "unknown";
|
|
16936
|
+
var isProtectedBranch2 = (root, branch) => resolveBranchPolicyFor(root).protected.has(branch.toLowerCase());
|
|
16937
|
+
var actionProposalQuestion = (request, descriptorPayload) => {
|
|
16938
|
+
const payload = descriptorPayload ?? {};
|
|
16939
|
+
const resolved = payload.resolved ?? {};
|
|
16940
|
+
const payloadTarget = String(payload.target_branch ?? "");
|
|
16941
|
+
if (request.operation === "git.commit" && Array.isArray(payload.plan_steps)) {
|
|
16942
|
+
const steps = payload.plan_steps;
|
|
16943
|
+
return {
|
|
16944
|
+
presented: `Workit decision: action — Approve committing the plan's ${steps.length} listed tasks on \`${String(payload.plan_branch ?? "")}\`?`,
|
|
16945
|
+
approvedText: `Approve the ${steps.length} listed plan commits.`
|
|
16946
|
+
};
|
|
16947
|
+
}
|
|
16948
|
+
switch (request.operation) {
|
|
16949
|
+
case "git.branch_setup": {
|
|
16950
|
+
if (request.payload.action === "reapply_stash")
|
|
16951
|
+
return {
|
|
16952
|
+
presented: "Workit decision: action — Reapply the pre-checkout stash recorded in the branch manifest?",
|
|
16953
|
+
approvedText: "Reapply the recorded stash."
|
|
16954
|
+
};
|
|
16955
|
+
const target = String(payload.target_branch ?? "");
|
|
16956
|
+
const stashes = payload.stash === "yes" && resolved.dirty === true;
|
|
16957
|
+
if (resolved.target_exists === true)
|
|
16958
|
+
return stashes ? {
|
|
16959
|
+
presented: `Workit decision: action — Stash the dirty working tree and switch to branch \`${target}\`?`,
|
|
16960
|
+
approvedText: `Stash and switch to \`${target}\`.`
|
|
16961
|
+
} : {
|
|
16962
|
+
presented: `Workit decision: action — Switch to branch \`${target}\`?`,
|
|
16963
|
+
approvedText: `Switch to \`${target}\`.`
|
|
16964
|
+
};
|
|
16965
|
+
const base = String(resolved.base_branch ?? "the base branch");
|
|
16966
|
+
return stashes ? {
|
|
16967
|
+
presented: `Workit decision: action — Stash the dirty working tree and create branch \`${target}\` from \`${base}\` (currently ${shortSha(resolved.remote_base)})?`,
|
|
16968
|
+
approvedText: `Stash and create \`${target}\` from \`${base}\` at ${shortSha(resolved.remote_base)}.`
|
|
16969
|
+
} : {
|
|
16970
|
+
presented: `Workit decision: action — Create branch \`${target}\` from \`${base}\` (currently ${shortSha(resolved.remote_base)})?`,
|
|
16971
|
+
approvedText: `Create \`${target}\` from \`${base}\` at ${shortSha(resolved.remote_base)}.`
|
|
16972
|
+
};
|
|
16973
|
+
}
|
|
16974
|
+
case "git.commit": {
|
|
16975
|
+
const paths = Array.isArray(resolved.paths) ? resolved.paths : [];
|
|
16976
|
+
return {
|
|
16977
|
+
presented: `Workit decision: action — Commit ${paths.length} staged file(s) as "${String(request.payload.message)}"?`,
|
|
16978
|
+
approvedText: `Commit as "${String(request.payload.message)}".`
|
|
16979
|
+
};
|
|
16980
|
+
}
|
|
16981
|
+
case "git.push":
|
|
16982
|
+
return {
|
|
16983
|
+
presented: `Workit decision: action — Push branch \`${String(payload.branch ?? "")}\` to origin?`,
|
|
16984
|
+
approvedText: `Push \`${String(payload.branch ?? "")}\` to origin.`
|
|
16985
|
+
};
|
|
16986
|
+
case "hosting.pull_request":
|
|
16987
|
+
return {
|
|
16988
|
+
presented: `Workit decision: action — Open a PR from \`${String(resolved.source_branch ?? "")}\` to \`${payloadTarget}\` titled "${String(request.payload.title)}"?`,
|
|
16989
|
+
approvedText: `Open the PR: ${String(resolved.source_branch ?? "")} → ${payloadTarget}, "${String(request.payload.title)}".`
|
|
16990
|
+
};
|
|
16991
|
+
case "changelog.apply":
|
|
16992
|
+
return {
|
|
16993
|
+
presented: `Workit decision: action — Apply changelog changes at \`${String(payload.path ?? "CHANGELOG.md")}\`?`,
|
|
16994
|
+
approvedText: `Apply changelog changes at ${String(payload.path ?? "CHANGELOG.md")}.`
|
|
16995
|
+
};
|
|
16996
|
+
case "youtrack.update":
|
|
16997
|
+
return {
|
|
16998
|
+
presented: `Workit decision: action — Update YouTrack issue ${String(request.payload.issueId)}${request.payload.minutes ? ` and log ${String(request.payload.minutes)}m` : ""}?`,
|
|
16999
|
+
approvedText: `Update YouTrack issue ${String(request.payload.issueId)}.`
|
|
17000
|
+
};
|
|
17001
|
+
case "youtrack.time":
|
|
17002
|
+
return {
|
|
17003
|
+
presented: `Workit decision: action — Log ${String(request.payload.minutes)}m to YouTrack issue ${String(request.payload.issueId)}?`,
|
|
17004
|
+
approvedText: `Log time to YouTrack issue ${String(request.payload.issueId)}.`
|
|
17005
|
+
};
|
|
17006
|
+
case "youtrack.meeting":
|
|
17007
|
+
return {
|
|
17008
|
+
presented: `Workit decision: action — Log meeting time to YouTrack issue ${String(request.payload.issueId)}?`,
|
|
17009
|
+
approvedText: `Log meeting time to YouTrack issue ${String(request.payload.issueId)}.`
|
|
17010
|
+
};
|
|
17011
|
+
default:
|
|
17012
|
+
return {
|
|
17013
|
+
presented: `Workit decision: action — Approve ${request.operation}?`,
|
|
17014
|
+
approvedText: `Approve ${request.operation}.`
|
|
17015
|
+
};
|
|
17016
|
+
}
|
|
17017
|
+
};
|
|
16575
17018
|
var approvedResolvedExternalAction = (content) => {
|
|
16576
17019
|
try {
|
|
16577
17020
|
const value = JSON.parse(content);
|
|
@@ -16835,24 +17278,35 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
16835
17278
|
const sdd_dir = safePath(root, request.payload.sdd_dir ?? "docs");
|
|
16836
17279
|
if (!sdd_dir)
|
|
16837
17280
|
return failure("invalid_input", "sdd_dir must stay inside the workspace");
|
|
17281
|
+
const action = request.payload.action ?? "setup";
|
|
16838
17282
|
const policy = resolveBranchPolicyFor(root);
|
|
16839
|
-
const target_branch = request.payload.target_branch
|
|
17283
|
+
const target_branch = request.payload.target_branch;
|
|
17284
|
+
if (action !== "reapply_stash" && !target_branch)
|
|
17285
|
+
return failure("invalid_input", "target_branch (the working branch to create or switch to) is required for setup", {
|
|
17286
|
+
outcome: "not_started",
|
|
17287
|
+
fields: [{ path: "target_branch", reason: "required for action=setup" }]
|
|
17288
|
+
});
|
|
16840
17289
|
const head = gitValue(root, ["rev-parse", "HEAD"]);
|
|
16841
17290
|
if (!head)
|
|
16842
17291
|
return failure("storage_error", "current Git commit could not be resolved");
|
|
17292
|
+
const dirty = Boolean((gitRaw(root, ["status", "--short"]) ?? "").trim());
|
|
17293
|
+
const creation = action === "reapply_stash" || !target_branch ? { base_branch: policy.defaultTargetBranch } : branchCreationBaseline(root, target_branch, policy.defaultTargetBranch);
|
|
16843
17294
|
const normalized = {
|
|
16844
17295
|
operation: request.operation,
|
|
16845
17296
|
payload: {
|
|
16846
17297
|
...request.payload,
|
|
16847
|
-
action
|
|
17298
|
+
action,
|
|
16848
17299
|
sdd_dir,
|
|
16849
17300
|
stash: request.payload.stash ?? "no",
|
|
16850
|
-
target_branch
|
|
17301
|
+
...target_branch ? { target_branch } : {}
|
|
16851
17302
|
}
|
|
16852
17303
|
};
|
|
16853
17304
|
return success(null, null, {
|
|
16854
17305
|
request: normalized,
|
|
16855
|
-
descriptorPayload: {
|
|
17306
|
+
descriptorPayload: {
|
|
17307
|
+
...normalized.payload,
|
|
17308
|
+
resolved: { head, dirty, ...creation }
|
|
17309
|
+
}
|
|
16856
17310
|
});
|
|
16857
17311
|
}
|
|
16858
17312
|
case "git.push": {
|
|
@@ -16866,7 +17320,37 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
16866
17320
|
}) : failure("invalid_input", "git push requires a resolvable current branch");
|
|
16867
17321
|
}
|
|
16868
17322
|
case "git.commit": {
|
|
17323
|
+
const planSteps = request.payload.plan_steps;
|
|
17324
|
+
const planBranch = request.payload.plan_branch;
|
|
16869
17325
|
const head = gitValue(root, ["rev-parse", "HEAD"]);
|
|
17326
|
+
if (!head)
|
|
17327
|
+
return failure("storage_error", "Git state could not be resolved");
|
|
17328
|
+
const branch = gitValue(root, ["branch", "--show-current"]);
|
|
17329
|
+
if (planSteps !== undefined || planBranch !== undefined) {
|
|
17330
|
+
if (!Array.isArray(planSteps) || planSteps.some((step) => typeof step !== "string" || !step.trim()) || new Set(planSteps).size !== planSteps.length || planSteps.length === 0 || planSteps.length > 32)
|
|
17331
|
+
return failure("invalid_input", "plan_steps must be 1-32 unique non-empty commit messages", {
|
|
17332
|
+
outcome: "not_started",
|
|
17333
|
+
fields: [{ path: "plan_steps", reason: "unique strings" }]
|
|
17334
|
+
});
|
|
17335
|
+
if (typeof planBranch !== "string" || !planBranch.trim() || planBranch !== branch)
|
|
17336
|
+
return failure("invalid_input", "plan_branch must name the current working branch", {
|
|
17337
|
+
outcome: "not_started",
|
|
17338
|
+
fields: [{ path: "plan_branch", reason: "current branch" }]
|
|
17339
|
+
});
|
|
17340
|
+
if (isProtectedBranch2(root, planBranch))
|
|
17341
|
+
return failure("invalid_input", "plan_branch must not be a protected branch", {
|
|
17342
|
+
outcome: "not_started",
|
|
17343
|
+
fields: [{ path: "plan_branch", reason: "protected branch" }]
|
|
17344
|
+
});
|
|
17345
|
+
return success(null, null, {
|
|
17346
|
+
request,
|
|
17347
|
+
descriptorPayload: {
|
|
17348
|
+
plan_steps: planSteps,
|
|
17349
|
+
plan_branch: planBranch,
|
|
17350
|
+
resolved: { head, branch, steps: planSteps }
|
|
17351
|
+
}
|
|
17352
|
+
});
|
|
17353
|
+
}
|
|
16870
17354
|
const staged = gitRaw(root, [
|
|
16871
17355
|
"diff",
|
|
16872
17356
|
"--cached",
|
|
@@ -16877,7 +17361,13 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
16877
17361
|
"HEAD"
|
|
16878
17362
|
]);
|
|
16879
17363
|
const paths = stagedPaths(root);
|
|
16880
|
-
|
|
17364
|
+
const message = request.payload.message;
|
|
17365
|
+
if (typeof message !== "string" || !message)
|
|
17366
|
+
return failure("invalid_input", "git.commit requires a commit message", {
|
|
17367
|
+
outcome: "not_started",
|
|
17368
|
+
fields: [{ path: "message", reason: "required unless plan_steps is provided" }]
|
|
17369
|
+
});
|
|
17370
|
+
if (staged === null || paths === null)
|
|
16881
17371
|
return failure("storage_error", "Git state could not be resolved");
|
|
16882
17372
|
let policy;
|
|
16883
17373
|
try {
|
|
@@ -16892,13 +17382,13 @@ var resolveExternalActionRequest = (root, request, preservedDateMs) => {
|
|
|
16892
17382
|
`).filter(Boolean));
|
|
16893
17383
|
flavor = detected.flavor ?? "conventional";
|
|
16894
17384
|
}
|
|
16895
|
-
if (!matchCommitFlavor(
|
|
17385
|
+
if (!matchCommitFlavor(message, flavor, policy.pattern))
|
|
16896
17386
|
return failure("invalid_input", `Commit message does not match the ${flavor} flavor (commitPolicy.preset)`);
|
|
16897
17387
|
return success(null, null, {
|
|
16898
17388
|
request,
|
|
16899
17389
|
descriptorPayload: {
|
|
16900
17390
|
...request.payload,
|
|
16901
|
-
resolved: { head, staged: sha256(staged), paths }
|
|
17391
|
+
resolved: { head, branch, staged: sha256(staged), paths }
|
|
16902
17392
|
}
|
|
16903
17393
|
});
|
|
16904
17394
|
}
|
|
@@ -17068,10 +17558,24 @@ var executeConcreteExternalAction = async (request, root, marker, dateMs, step,
|
|
|
17068
17558
|
sdd_dir: request.payload.sdd_dir ?? "docs",
|
|
17069
17559
|
workspace_root: root
|
|
17070
17560
|
});
|
|
17071
|
-
|
|
17561
|
+
if ("error" in result) {
|
|
17562
|
+
if (result.phase === "preflight")
|
|
17563
|
+
return failure("invalid_input", result.error, {
|
|
17564
|
+
outcome: "not_started",
|
|
17565
|
+
operation: request.operation
|
|
17566
|
+
});
|
|
17567
|
+
return unknown2(request.operation);
|
|
17568
|
+
}
|
|
17569
|
+
return success(null, null, result);
|
|
17072
17570
|
}
|
|
17073
17571
|
case "git.commit": {
|
|
17074
|
-
const
|
|
17572
|
+
const message = request.payload.message;
|
|
17573
|
+
if (typeof message !== "string" || !message)
|
|
17574
|
+
return failure("invalid_input", "git.commit requires a commit message", {
|
|
17575
|
+
outcome: "not_started",
|
|
17576
|
+
fields: [{ path: "message", reason: "required" }]
|
|
17577
|
+
});
|
|
17578
|
+
const result = run3(root, ["commit", "-m", message]);
|
|
17075
17579
|
return result.exitCode === 0 ? success(null, null, { stdout: result.stdout.trim() }) : unknown2(request.operation);
|
|
17076
17580
|
}
|
|
17077
17581
|
case "git.push": {
|
|
@@ -17244,8 +17748,34 @@ var nativeAuthority = (actor, reconciliationTokens = new WeakSet) => ({
|
|
|
17244
17748
|
var nativeExternalActionRunner = (root, actor, core, step) => createAuthorizedExternalActionRunner(core, (operation) => {
|
|
17245
17749
|
const store = new TaskStore(root);
|
|
17246
17750
|
const selected = approvedExternalAction(store, "pi", actor, operation);
|
|
17247
|
-
if (!selected.ok)
|
|
17751
|
+
if (!selected.ok) {
|
|
17752
|
+
const plan = planCommitBinding(store, "pi", actor, operation);
|
|
17753
|
+
if (plan) {
|
|
17754
|
+
const actionRef = externalActionRef("pi", actor, operation);
|
|
17755
|
+
return {
|
|
17756
|
+
taskId: plan.taskId,
|
|
17757
|
+
decisionId: plan.decisionId,
|
|
17758
|
+
actionRef,
|
|
17759
|
+
expectedRevision: plan.expectedRevision,
|
|
17760
|
+
expectedWorkspaceRevision: plan.expectedWorkspaceRevision,
|
|
17761
|
+
binding: plan.binding,
|
|
17762
|
+
step: plan.step,
|
|
17763
|
+
refresh: () => {
|
|
17764
|
+
const task = store.readTask(plan.taskId);
|
|
17765
|
+
const freshWorkspace = store.readWorkspace();
|
|
17766
|
+
if (!task.ok || !freshWorkspace.ok || !freshWorkspace.data)
|
|
17767
|
+
throw new Error("external action state changed");
|
|
17768
|
+
return {
|
|
17769
|
+
expectedRevision: task.data.revision,
|
|
17770
|
+
expectedWorkspaceRevision: freshWorkspace.data.revision
|
|
17771
|
+
};
|
|
17772
|
+
},
|
|
17773
|
+
reserveObservation: nativeExternalActionObservation(actor, actionRef, "reserve"),
|
|
17774
|
+
settleObservation: (outcome, revisions) => nativeExternalActionObservation(actor, actionRef, outcome, actionRef.kind === "host" ? actionRef.handle : "external-action", revisions)
|
|
17775
|
+
};
|
|
17776
|
+
}
|
|
17248
17777
|
return selected;
|
|
17778
|
+
}
|
|
17249
17779
|
const actionRef = externalActionRef("pi", actor, operation);
|
|
17250
17780
|
return {
|
|
17251
17781
|
taskId: selected.data.task.id,
|
|
@@ -17311,6 +17841,21 @@ var executeFamily = async (family, toolCallId, input, ctx) => {
|
|
|
17311
17841
|
operation: "decision.record"
|
|
17312
17842
|
}));
|
|
17313
17843
|
const decision = parsed.data;
|
|
17844
|
+
const budgetIssue = workitBindingQuestionIssue([
|
|
17845
|
+
{
|
|
17846
|
+
header: `Workit decision: ${parsed.data.purpose}`,
|
|
17847
|
+
question: decision.binding.presented,
|
|
17848
|
+
options: [
|
|
17849
|
+
{
|
|
17850
|
+
label: "approved",
|
|
17851
|
+
description: decision.binding.displayed ?? decision.binding.approvedContent
|
|
17852
|
+
},
|
|
17853
|
+
{ label: "rejected", description: "Reject this decision" }
|
|
17854
|
+
]
|
|
17855
|
+
}
|
|
17856
|
+
]);
|
|
17857
|
+
if (budgetIssue)
|
|
17858
|
+
return output(failure("invalid_input", budgetIssue));
|
|
17314
17859
|
const approved = await ctx.ui.confirm("Workit decision", decision.binding.presented);
|
|
17315
17860
|
if (approved !== (decision.response === "approved"))
|
|
17316
17861
|
return output(failure("permission_denied", "Pi decision answer did not match the requested response"));
|
|
@@ -17363,9 +17908,17 @@ var registerWorkitTools = (pi, options = {}) => {
|
|
|
17363
17908
|
if (!parsed.ok)
|
|
17364
17909
|
return output(parsed);
|
|
17365
17910
|
const context = piContext(ctx);
|
|
17366
|
-
|
|
17911
|
+
let resolved = resolveExternalActionRequest(ctx.cwd, parsed.data);
|
|
17367
17912
|
if (!resolved.ok)
|
|
17368
17913
|
return output(resolved);
|
|
17914
|
+
if (resolved.data.request.operation === "git.branch_setup" && resolved.data.descriptorPayload.resolved?.dirty === true && resolved.data.descriptorPayload.stash !== "yes") {
|
|
17915
|
+
const upgraded = resolveExternalActionRequest(ctx.cwd, {
|
|
17916
|
+
...parsed.data,
|
|
17917
|
+
payload: { ...parsed.data.payload, stash: "yes" }
|
|
17918
|
+
});
|
|
17919
|
+
if (upgraded.ok)
|
|
17920
|
+
resolved = upgraded;
|
|
17921
|
+
}
|
|
17369
17922
|
if (resolved.data.request.operation === "context.read")
|
|
17370
17923
|
return output(await executeResolvedExternalAction(resolved.data, ctx.cwd));
|
|
17371
17924
|
if (!ctx.isProjectTrusted())
|
|
@@ -17383,6 +17936,22 @@ var registerWorkitTools = (pi, options = {}) => {
|
|
|
17383
17936
|
...context,
|
|
17384
17937
|
nativeAuthority: nativeAuthority(actor, reconciliationTokens)
|
|
17385
17938
|
});
|
|
17939
|
+
const localOperation = [
|
|
17940
|
+
"git.branch_setup",
|
|
17941
|
+
"git.commit",
|
|
17942
|
+
"git.push",
|
|
17943
|
+
"hosting.pull_request",
|
|
17944
|
+
"changelog.apply"
|
|
17945
|
+
].includes(resolved.data.request.operation);
|
|
17946
|
+
if (localOperation) {
|
|
17947
|
+
const writer = assertLocalExternalActionWriter(ctx.cwd, { host: "pi", actor });
|
|
17948
|
+
if (!writer.ok)
|
|
17949
|
+
return output(failure("needs_input", "writer ownership is required before this action", {
|
|
17950
|
+
outcome: "not_started",
|
|
17951
|
+
operation: resolved.data.request.operation,
|
|
17952
|
+
guidance: "Acquire checkout writer ownership first (writer.acquire) and retry the action."
|
|
17953
|
+
}));
|
|
17954
|
+
}
|
|
17386
17955
|
const descriptor = externalActionDescriptor(resolved.data.request.operation, resolved.data.descriptorPayload);
|
|
17387
17956
|
const priorRequest = priorExternalAction(store, "pi", actor, resolved.data.request.operation, resolved.data.request.payload);
|
|
17388
17957
|
if (priorRequest.ok && priorRequest.data.entry.data.consumption?.state === "uncertain") {
|
|
@@ -17421,8 +17990,18 @@ var registerWorkitTools = (pi, options = {}) => {
|
|
|
17421
17990
|
}
|
|
17422
17991
|
if (priorRequest.ok && priorRequest.data.entry.data.consumption !== null)
|
|
17423
17992
|
return output(failure("permission_denied", "external action was already settled"));
|
|
17993
|
+
const drift = resolved.data.request.operation === "git.branch_setup" ? priorResolvedDrift(store, "pi", actor, resolved.data.request.operation, resolved.data.request.payload, resolved.data.descriptorPayload.resolved) : success(null, null, null);
|
|
17994
|
+
if (!drift.ok)
|
|
17995
|
+
return output(drift);
|
|
17424
17996
|
const existing = approvedExternalAction(store, "pi", actor, descriptor);
|
|
17425
17997
|
if (!existing.ok) {
|
|
17998
|
+
const commitMessage = resolved.data.request.operation === "git.commit" ? resolved.data.request.payload.message : undefined;
|
|
17999
|
+
const currentBranch = resolved.data.descriptorPayload.resolved?.branch;
|
|
18000
|
+
const plan = typeof commitMessage === "string" && commitMessage ? approvedPlanCommit(store, "pi", actor, commitMessage) : null;
|
|
18001
|
+
if (plan?.ok && plan.data.branch === currentBranch) {
|
|
18002
|
+
const result = await nativeExternalActionRunner(ctx.cwd, actor, core)(descriptor, (step) => executeResolvedExternalAction(resolved.data, ctx.cwd, step, { host: "pi", actor }));
|
|
18003
|
+
return output(result);
|
|
18004
|
+
}
|
|
17426
18005
|
const prior = externalActionState(store, "pi", actor, descriptor);
|
|
17427
18006
|
if (prior.ok && prior.data.entry.data.consumption !== null)
|
|
17428
18007
|
return output(failure("permission_denied", "external action was already settled"));
|
|
@@ -17434,7 +18013,8 @@ var registerWorkitTools = (pi, options = {}) => {
|
|
|
17434
18013
|
if (candidates.length !== 1)
|
|
17435
18014
|
return output(failure("permission_denied", "external action requires exactly one active Pi task"));
|
|
17436
18015
|
const task = candidates[0];
|
|
17437
|
-
const
|
|
18016
|
+
const question = actionProposalQuestion(resolved.data.request, resolved.data.descriptorPayload);
|
|
18017
|
+
const approved = await ctx.ui.confirm(question.presented, question.approvedText);
|
|
17438
18018
|
if (!approved)
|
|
17439
18019
|
return output(failure("permission_denied", "external action was not approved"));
|
|
17440
18020
|
const currentWorkspace = store.readWorkspace();
|
|
@@ -17450,8 +18030,9 @@ var registerWorkitTools = (pi, options = {}) => {
|
|
|
17450
18030
|
taskId: task.id,
|
|
17451
18031
|
workspaceId: currentWorkspace.data.id,
|
|
17452
18032
|
scope: task.intent.data.scope,
|
|
17453
|
-
presented:
|
|
18033
|
+
presented: question.presented,
|
|
17454
18034
|
approvedContent: descriptor,
|
|
18035
|
+
displayed: question.approvedText,
|
|
17455
18036
|
contentRefs: []
|
|
17456
18037
|
},
|
|
17457
18038
|
response: "approved",
|
|
@@ -17460,6 +18041,9 @@ var registerWorkitTools = (pi, options = {}) => {
|
|
|
17460
18041
|
if (!decision.ok)
|
|
17461
18042
|
return output(decision);
|
|
17462
18043
|
}
|
|
18044
|
+
const planSteps = resolved.data.descriptorPayload.plan_steps;
|
|
18045
|
+
if (resolved.data.request.operation === "git.commit" && Array.isArray(planSteps))
|
|
18046
|
+
return output(success(null, null, { plan_commits: planSteps.length }));
|
|
17463
18047
|
const result = await nativeExternalActionRunner(ctx.cwd, actor, core)(descriptor, (step) => executeResolvedExternalAction(resolved.data, ctx.cwd, step, { host: "pi", actor }));
|
|
17464
18048
|
return output(result);
|
|
17465
18049
|
}
|
|
@@ -17468,7 +18052,7 @@ var registerWorkitTools = (pi, options = {}) => {
|
|
|
17468
18052
|
var enforceNativeWriter = (event, ctx) => {
|
|
17469
18053
|
if (event.toolName === "bash") {
|
|
17470
18054
|
const command = event.input?.command;
|
|
17471
|
-
const route = typeof command === "string" ?
|
|
18055
|
+
const route = typeof command === "string" ? shouldDenyShellRoute(ctx.cwd, command) : null;
|
|
17472
18056
|
if (route)
|
|
17473
18057
|
return {
|
|
17474
18058
|
block: true,
|