@brainervirus/workit-pi 6.0.1 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/worker.js +220 -96
- package/dist/workit.js +2593 -779
- package/package.json +1 -1
- package/skills/workit-deslop/SKILL.md +1 -1
- package/skills/workit-implement/SKILL.md +9 -4
- package/skills/workit-review/SKILL.md +2 -0
- package/skills/workit-shape/SKILL.md +1 -1
- package/skills/workit-ship/SKILL.md +1 -1
package/dist/workit.js
CHANGED
|
@@ -3,9 +3,9 @@ import { randomUUID as randomUUID5 } from "node:crypto";
|
|
|
3
3
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4
4
|
|
|
5
5
|
// packages/workit-core/src/core.ts
|
|
6
|
-
import { spawnSync as
|
|
7
|
-
import { existsSync as
|
|
8
|
-
import
|
|
6
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
7
|
+
import { existsSync as existsSync7, realpathSync as realpathSync5 } from "node:fs";
|
|
8
|
+
import path19 from "node:path";
|
|
9
9
|
|
|
10
10
|
// packages/workit-core/src/core/task-contract.ts
|
|
11
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -7429,6 +7429,14 @@ function object(shape, params) {
|
|
|
7429
7429
|
};
|
|
7430
7430
|
return new ZodObject(def);
|
|
7431
7431
|
}
|
|
7432
|
+
function looseObject(shape, params) {
|
|
7433
|
+
return new ZodObject({
|
|
7434
|
+
type: "object",
|
|
7435
|
+
shape,
|
|
7436
|
+
catchall: unknown(),
|
|
7437
|
+
...normalizeParams(params)
|
|
7438
|
+
});
|
|
7439
|
+
}
|
|
7432
7440
|
var ZodUnion = /* @__PURE__ */ $constructor("ZodUnion", (inst, def) => {
|
|
7433
7441
|
$ZodUnion.init(inst, def);
|
|
7434
7442
|
ZodType.init(inst, def);
|
|
@@ -7929,76 +7937,16 @@ var provenanceSchema = object({
|
|
|
7929
7937
|
workerId: nullableId
|
|
7930
7938
|
}).strict();
|
|
7931
7939
|
var entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict();
|
|
7932
|
-
var
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
code: "custom",
|
|
7940
|
-
input: ctx.value,
|
|
7941
|
-
message: "observed facts require supporting references",
|
|
7942
|
-
path: ["refs"]
|
|
7943
|
-
});
|
|
7944
|
-
});
|
|
7945
|
-
var signalSchema = object({
|
|
7946
|
-
value: union([boolean2(), literal("unknown")]),
|
|
7947
|
-
basis: _enum(["observed", "inferred", "unknown"]),
|
|
7948
|
-
reason: text,
|
|
7940
|
+
var RISK_TIERS = ["trivial", "normal", "high"];
|
|
7941
|
+
var judgmentSchema = object({
|
|
7942
|
+
riskTier: _enum(RISK_TIERS),
|
|
7943
|
+
behaviorChange: boolean2(),
|
|
7944
|
+
productChoiceOpen: boolean2(),
|
|
7945
|
+
needsPlan: boolean2(),
|
|
7946
|
+
note: text.nullable(),
|
|
7949
7947
|
refs: array(refSchema)
|
|
7950
|
-
}).strict().check((ctx) => {
|
|
7951
|
-
const signal = ctx.value;
|
|
7952
|
-
const valid = signal.value === "unknown" ? signal.basis === "unknown" : signal.basis === "observed" || signal.basis === "inferred";
|
|
7953
|
-
if (!valid)
|
|
7954
|
-
ctx.issues.push({
|
|
7955
|
-
code: "custom",
|
|
7956
|
-
input: signal,
|
|
7957
|
-
message: "signal value and basis disagree",
|
|
7958
|
-
path: []
|
|
7959
|
-
});
|
|
7960
|
-
if (signal.basis === "observed" && signal.refs.length === 0)
|
|
7961
|
-
ctx.issues.push({
|
|
7962
|
-
code: "custom",
|
|
7963
|
-
input: signal,
|
|
7964
|
-
message: "observed signals require supporting references",
|
|
7965
|
-
path: ["refs"]
|
|
7966
|
-
});
|
|
7967
|
-
});
|
|
7968
|
-
var signalSet = object({
|
|
7969
|
-
approachUnknown: signalSchema,
|
|
7970
|
-
productChoiceOpen: signalSchema,
|
|
7971
|
-
behaviorChange: signalSchema,
|
|
7972
|
-
mechanicalLowRisk: signalSchema,
|
|
7973
|
-
durableAgreementNeeded: signalSchema,
|
|
7974
|
-
coordinationPlanNeeded: signalSchema,
|
|
7975
|
-
helperUseful: signalSchema,
|
|
7976
|
-
testFirstPractical: signalSchema
|
|
7977
|
-
}).strict();
|
|
7978
|
-
var consequenceSchema = object({
|
|
7979
|
-
area: _enum([
|
|
7980
|
-
"behavior",
|
|
7981
|
-
"public_contract",
|
|
7982
|
-
"data",
|
|
7983
|
-
"security",
|
|
7984
|
-
"operations",
|
|
7985
|
-
"reversibility",
|
|
7986
|
-
"consumers"
|
|
7987
|
-
]),
|
|
7988
|
-
fact: factSchema
|
|
7989
|
-
}).strict();
|
|
7990
|
-
var verificationSchema = object({
|
|
7991
|
-
claim: text,
|
|
7992
|
-
scope: scopeSchema,
|
|
7993
|
-
availableChecks: array(refSchema),
|
|
7994
|
-
gaps: array(text)
|
|
7995
|
-
}).strict();
|
|
7996
|
-
var assessmentSchema = object({
|
|
7997
|
-
facts: array(factSchema),
|
|
7998
|
-
signals: signalSet,
|
|
7999
|
-
consequences: array(consequenceSchema),
|
|
8000
|
-
verification: array(verificationSchema)
|
|
8001
7948
|
}).strict();
|
|
7949
|
+
var legacyAssessmentSchema = looseObject({});
|
|
8002
7950
|
var constraintSchema = object({
|
|
8003
7951
|
id: nonEmpty,
|
|
8004
7952
|
kind: _enum(["host", "project", "user"]),
|
|
@@ -8235,7 +8183,8 @@ var taskRecordSchema = object({
|
|
|
8235
8183
|
progress: progressSchema,
|
|
8236
8184
|
pauseReason: text.nullable().optional(),
|
|
8237
8185
|
runtime: runtimeSchema.optional(),
|
|
8238
|
-
assessments: array(entrySchema(
|
|
8186
|
+
assessments: array(entrySchema(legacyAssessmentSchema)),
|
|
8187
|
+
judgments: array(entrySchema(judgmentSchema)).optional(),
|
|
8239
8188
|
policy: policySchema.nullable(),
|
|
8240
8189
|
policyChanges: array(policyChangeSchema),
|
|
8241
8190
|
candidates: array(candidateSchema),
|
|
@@ -8411,14 +8360,15 @@ var taskOperations = {
|
|
|
8411
8360
|
decisionIds: array(id).optional()
|
|
8412
8361
|
})
|
|
8413
8362
|
};
|
|
8363
|
+
var rawJudgment = record(string2(), unknown());
|
|
8414
8364
|
var policyOperations = {
|
|
8415
8365
|
assess: operation({
|
|
8416
8366
|
action: literal("assess"),
|
|
8417
8367
|
...taskId,
|
|
8418
8368
|
expectedRevision: revision.optional(),
|
|
8419
|
-
|
|
8369
|
+
judgment: rawJudgment
|
|
8420
8370
|
}),
|
|
8421
|
-
preview: operation({ action: literal("preview"), ...taskId,
|
|
8371
|
+
preview: operation({ action: literal("preview"), ...taskId, judgment: rawJudgment }),
|
|
8422
8372
|
explain: operation({ action: literal("explain"), ...taskId })
|
|
8423
8373
|
};
|
|
8424
8374
|
var evidenceOperations = {
|
|
@@ -8457,7 +8407,10 @@ var decisionOperations = {
|
|
|
8457
8407
|
...taskId,
|
|
8458
8408
|
expectedRevision: revision.optional(),
|
|
8459
8409
|
purpose: decisionSchema.shape.purpose,
|
|
8460
|
-
binding: decisionSchema.shape.binding
|
|
8410
|
+
binding: decisionSchema.shape.binding.extend({
|
|
8411
|
+
taskId: id.optional(),
|
|
8412
|
+
workspaceId: id.optional()
|
|
8413
|
+
}),
|
|
8461
8414
|
response: decisionSchema.shape.response,
|
|
8462
8415
|
requirementIds: array(digest)
|
|
8463
8416
|
}),
|
|
@@ -8511,6 +8464,15 @@ var operationSchemas = {
|
|
|
8511
8464
|
worker: discriminatedUnion("action", Object.values(workerOperations)),
|
|
8512
8465
|
state: discriminatedUnion("action", Object.values(stateOperations))
|
|
8513
8466
|
};
|
|
8467
|
+
var OPERATION_ACTIONS = {
|
|
8468
|
+
task: Object.keys(taskOperations),
|
|
8469
|
+
policy: Object.keys(policyOperations),
|
|
8470
|
+
evidence: Object.keys(evidenceOperations),
|
|
8471
|
+
finding: Object.keys(findingOperations),
|
|
8472
|
+
decision: Object.keys(decisionOperations),
|
|
8473
|
+
worker: Object.keys(workerOperations),
|
|
8474
|
+
state: Object.keys(stateOperations)
|
|
8475
|
+
};
|
|
8514
8476
|
var withImplicitTask = (options) => Object.values(options).map((option) => ("taskId" in option.shape) ? option.extend({ taskId: id.optional() }) : option);
|
|
8515
8477
|
var advertised = (options) => discriminatedUnion("action", withImplicitTask(options));
|
|
8516
8478
|
var advertisedOperationSchemas = {
|
|
@@ -8544,103 +8506,6 @@ function parseOperation(family, input) {
|
|
|
8544
8506
|
}));
|
|
8545
8507
|
return failure2("invalid_input", "operation input is invalid", { fields });
|
|
8546
8508
|
}
|
|
8547
|
-
function operationJsonSchema(family) {
|
|
8548
|
-
return toJSONSchema(advertisedOperationSchemas[family], { target: "draft-2020-12" });
|
|
8549
|
-
}
|
|
8550
|
-
var OPERATION_SCHEMA_DEPTH = 1;
|
|
8551
|
-
var schemaShapeDescription = (value, depth = 0) => {
|
|
8552
|
-
if (!value || typeof value !== "object")
|
|
8553
|
-
return "value";
|
|
8554
|
-
const schema = value;
|
|
8555
|
-
if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
|
|
8556
|
-
const options = Array.isArray(schema.oneOf) ? schema.oneOf : schema.anyOf;
|
|
8557
|
-
return `(${options.map((option) => schemaShapeDescription(option, depth)).join("|")})`;
|
|
8558
|
-
}
|
|
8559
|
-
if (schema.const !== undefined)
|
|
8560
|
-
return `=${JSON.stringify(schema.const)}`;
|
|
8561
|
-
if (Array.isArray(schema.enum))
|
|
8562
|
-
return `[${schema.enum.map(String).join("|")}]`;
|
|
8563
|
-
const type = Array.isArray(schema.type) ? schema.type.join("/") : schema.type;
|
|
8564
|
-
if (type === "array")
|
|
8565
|
-
return `arr<${schemaShapeDescription(schema.items, depth)}>`;
|
|
8566
|
-
if (type === "object" && schema.properties && typeof schema.properties === "object") {
|
|
8567
|
-
if (depth >= 2)
|
|
8568
|
-
return "obj";
|
|
8569
|
-
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
8570
|
-
const properties = schema.properties;
|
|
8571
|
-
const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child, depth + 1)}`);
|
|
8572
|
-
return `obj{${fields.join(",")}}`;
|
|
8573
|
-
}
|
|
8574
|
-
if (typeof type !== "string")
|
|
8575
|
-
return "?";
|
|
8576
|
-
return type.split("/").map((part) => ({ string: "str", boolean: "bool", number: "num", null: "null" })[part] ?? part).join("|");
|
|
8577
|
-
};
|
|
8578
|
-
var canonicalFieldsDescription = (value) => {
|
|
8579
|
-
if (Array.isArray(value))
|
|
8580
|
-
return `Canonical object fields: ${value.join(", ")}. Workit validates the complete nested value.`;
|
|
8581
|
-
const isZodType = (schema) => ("_zod" in schema) && typeof schema._zod === "object";
|
|
8582
|
-
const schema = isZodType(value) ? toJSONSchema(value, { target: "draft-2020-12" }) : value;
|
|
8583
|
-
if (!schema.properties || typeof schema.properties !== "object") {
|
|
8584
|
-
const variants = Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : null;
|
|
8585
|
-
return variants ? `Canonical shapes: ${variants.map((variant) => schemaShapeDescription(variant)).join(" | ")}. Full nested value is validated.` : "Canonical nested value; Workit validates the complete value.";
|
|
8586
|
-
}
|
|
8587
|
-
const properties = schema.properties;
|
|
8588
|
-
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
8589
|
-
const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child)}`);
|
|
8590
|
-
return `Fields (! required): ${fields.join(", ")}. Full nested value is validated.`;
|
|
8591
|
-
};
|
|
8592
|
-
function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, stringifiedObjects = false) {
|
|
8593
|
-
const tolerate = (projected) => {
|
|
8594
|
-
if (!stringifiedObjects)
|
|
8595
|
-
return projected;
|
|
8596
|
-
if (projected.properties && typeof projected.properties === "object")
|
|
8597
|
-
return { ...projected, type: ["object", "string"] };
|
|
8598
|
-
if (projected.items)
|
|
8599
|
-
return { ...projected, type: ["array", "string"] };
|
|
8600
|
-
if ("const" in projected && (typeof projected.const === "number" || typeof projected.const === "boolean"))
|
|
8601
|
-
return { anyOf: [projected, { const: String(projected.const) }] };
|
|
8602
|
-
if (projected.type === "object")
|
|
8603
|
-
return { ...projected, type: ["object", "string"] };
|
|
8604
|
-
if (projected.type === "array")
|
|
8605
|
-
return { ...projected, type: ["array", "string"] };
|
|
8606
|
-
return projected;
|
|
8607
|
-
};
|
|
8608
|
-
const collapse = (node, currentDepth) => {
|
|
8609
|
-
if (Array.isArray(node))
|
|
8610
|
-
return node.map((item) => collapse(item, currentDepth));
|
|
8611
|
-
if (typeof node !== "object" || node === null)
|
|
8612
|
-
return node;
|
|
8613
|
-
const record = node;
|
|
8614
|
-
if (record.properties && typeof record.properties === "object") {
|
|
8615
|
-
const properties = record.properties;
|
|
8616
|
-
const fields = Object.keys(properties);
|
|
8617
|
-
if (currentDepth >= maxDepth || fields.length === 0) {
|
|
8618
|
-
const description = stringifiedObjects ? `${canonicalFieldsDescription(record)} A JSON-encoded string is also accepted.` : canonicalFieldsDescription(record);
|
|
8619
|
-
return tolerate({ type: "object", description });
|
|
8620
|
-
}
|
|
8621
|
-
return tolerate({
|
|
8622
|
-
...record,
|
|
8623
|
-
properties: Object.fromEntries(Object.entries(record.properties).map(([key, child]) => [
|
|
8624
|
-
key,
|
|
8625
|
-
collapse(child, currentDepth + 1)
|
|
8626
|
-
]))
|
|
8627
|
-
});
|
|
8628
|
-
}
|
|
8629
|
-
if (record.items)
|
|
8630
|
-
return tolerate({ ...record, items: collapse(record.items, currentDepth) });
|
|
8631
|
-
const composed = ["anyOf", "oneOf", "allOf"].filter((key) => Array.isArray(record[key]));
|
|
8632
|
-
if (composed.length > 0) {
|
|
8633
|
-
const projected = { ...record };
|
|
8634
|
-
for (const key of composed)
|
|
8635
|
-
projected[key] = record[key].map((item) => collapse(item, currentDepth));
|
|
8636
|
-
return projected;
|
|
8637
|
-
}
|
|
8638
|
-
if ("const" in record)
|
|
8639
|
-
return tolerate({ ...record });
|
|
8640
|
-
return node;
|
|
8641
|
-
};
|
|
8642
|
-
return collapse(operationJsonSchema(family), 0);
|
|
8643
|
-
}
|
|
8644
8509
|
function canonical(value, seen) {
|
|
8645
8510
|
if (value === null || typeof value === "boolean" || typeof value === "string") {
|
|
8646
8511
|
if (typeof value === "string" && invalidUnicode(value))
|
|
@@ -8729,6 +8594,402 @@ function requirementId(input) {
|
|
|
8729
8594
|
}
|
|
8730
8595
|
});
|
|
8731
8596
|
}
|
|
8597
|
+
// packages/workit-core/src/core/operation-input.ts
|
|
8598
|
+
var str = (d, values) => ({
|
|
8599
|
+
type: "string",
|
|
8600
|
+
...values ? { enum: values } : {},
|
|
8601
|
+
...d ? { d } : {}
|
|
8602
|
+
});
|
|
8603
|
+
var list = (d) => ({ type: "array", ...d ? { d } : {} });
|
|
8604
|
+
var bool = (d) => ({ type: "boolean", ...d ? { d } : {} });
|
|
8605
|
+
var int2 = (d) => ({ type: "integer", ...d ? { d } : {} });
|
|
8606
|
+
var scopeFields = {
|
|
8607
|
+
paths: list("Scope paths (default: whole checkout)."),
|
|
8608
|
+
exclusions: list(),
|
|
8609
|
+
scope: str("Scope description.")
|
|
8610
|
+
};
|
|
8611
|
+
var refs = list("Paths or URLs.");
|
|
8612
|
+
var FLAT = {
|
|
8613
|
+
task: {
|
|
8614
|
+
fields: {
|
|
8615
|
+
objective: str(),
|
|
8616
|
+
...scopeFields,
|
|
8617
|
+
refs,
|
|
8618
|
+
reason: str(),
|
|
8619
|
+
summary: str(),
|
|
8620
|
+
nextAction: str(),
|
|
8621
|
+
blockers: list(),
|
|
8622
|
+
outcome: str(undefined, ["verified", "accepted_limitations", "stopped"]),
|
|
8623
|
+
decisionIds: list(),
|
|
8624
|
+
status: str(undefined, ["open", "closed", "all"]),
|
|
8625
|
+
query: str(),
|
|
8626
|
+
limit: int2(),
|
|
8627
|
+
view: str(undefined, ["summary", "full"])
|
|
8628
|
+
}
|
|
8629
|
+
},
|
|
8630
|
+
policy: {
|
|
8631
|
+
fields: {
|
|
8632
|
+
riskTier: str("low=trivial, medium=normal.", ["trivial", "normal", "high", "low", "medium"]),
|
|
8633
|
+
behaviorChange: bool("Observable behavior changes."),
|
|
8634
|
+
productChoiceOpen: bool("A product/preference choice is still open."),
|
|
8635
|
+
needsPlan: bool("Needs a written plan or spec first."),
|
|
8636
|
+
note: str(),
|
|
8637
|
+
refs: list("Plan/spec paths or URLs.")
|
|
8638
|
+
}
|
|
8639
|
+
},
|
|
8640
|
+
evidence: {
|
|
8641
|
+
fields: {
|
|
8642
|
+
kind: str(undefined, ["check", "review", "investigation", "artifact"]),
|
|
8643
|
+
claim: str(),
|
|
8644
|
+
result: str(undefined, ["passed", "failed", "missing", "skipped"]),
|
|
8645
|
+
summary: str(),
|
|
8646
|
+
exitCode: int2(),
|
|
8647
|
+
refs,
|
|
8648
|
+
requirementIds: list(),
|
|
8649
|
+
candidateId: str(),
|
|
8650
|
+
beforeCandidateId: str(),
|
|
8651
|
+
reviewSession: str("Reviewer session handle (review evidence).")
|
|
8652
|
+
}
|
|
8653
|
+
},
|
|
8654
|
+
finding: {
|
|
8655
|
+
fields: {
|
|
8656
|
+
claim: str(),
|
|
8657
|
+
consequence: str(),
|
|
8658
|
+
...scopeFields,
|
|
8659
|
+
candidateId: str(),
|
|
8660
|
+
refs,
|
|
8661
|
+
findingId: str(),
|
|
8662
|
+
disposition: str(undefined, ["open", "fixed", "dismissed", "deferred"]),
|
|
8663
|
+
reason: str(),
|
|
8664
|
+
evidenceIds: list(),
|
|
8665
|
+
decisionIds: list()
|
|
8666
|
+
}
|
|
8667
|
+
},
|
|
8668
|
+
decision: {
|
|
8669
|
+
fields: {
|
|
8670
|
+
purpose: str(undefined, ["design", "action", "limitation", "preference"]),
|
|
8671
|
+
response: str(undefined, ["approved", "rejected", "stated"]),
|
|
8672
|
+
presented: str("What the user was asked."),
|
|
8673
|
+
choice: str("The user's stated choice (response=stated)."),
|
|
8674
|
+
...scopeFields,
|
|
8675
|
+
refs,
|
|
8676
|
+
requirementIds: list(),
|
|
8677
|
+
decisionId: str(),
|
|
8678
|
+
reason: str()
|
|
8679
|
+
}
|
|
8680
|
+
},
|
|
8681
|
+
worker: {
|
|
8682
|
+
fields: {
|
|
8683
|
+
role: str(undefined, ["investigator", "reviewer", "implementer"]),
|
|
8684
|
+
objective: str(),
|
|
8685
|
+
...scopeFields,
|
|
8686
|
+
decisionIds: list(),
|
|
8687
|
+
requirementIds: list(),
|
|
8688
|
+
candidateId: str(),
|
|
8689
|
+
stoppingCondition: str(),
|
|
8690
|
+
workerId: str(),
|
|
8691
|
+
outcome: str(undefined, ["completed", "failed", "cancelled"]),
|
|
8692
|
+
summary: str(),
|
|
8693
|
+
evidenceIds: list(),
|
|
8694
|
+
findingIds: list(),
|
|
8695
|
+
reason: str()
|
|
8696
|
+
}
|
|
8697
|
+
},
|
|
8698
|
+
state: {
|
|
8699
|
+
fields: { bundle: { type: "object", d: "An export bundle (object or JSON string)." } }
|
|
8700
|
+
}
|
|
8701
|
+
};
|
|
8702
|
+
var COMMON = {
|
|
8703
|
+
taskId: str("Omit for this branch's task."),
|
|
8704
|
+
expectedRevision: str(),
|
|
8705
|
+
expectedWorkspaceRevision: str()
|
|
8706
|
+
};
|
|
8707
|
+
var DESCRIPTIONS = {
|
|
8708
|
+
task: "Workit task lifecycle for this branch's task (optional continuity).",
|
|
8709
|
+
policy: "Judge the work in four flat calls (riskTier, behaviorChange, productChoiceOpen, needsPlan); Workit derives the checks, verdicts, decisions and plan it needs.",
|
|
8710
|
+
evidence: "Record a note-level evidence entry; only `workit check` runs satisfy check gates.",
|
|
8711
|
+
finding: "Record or resolve a review finding.",
|
|
8712
|
+
decision: "Record or revoke a user decision (never an authorization).",
|
|
8713
|
+
worker: "Assign, report or cancel a bounded helper.",
|
|
8714
|
+
state: "Export or import a task handoff bundle."
|
|
8715
|
+
};
|
|
8716
|
+
var operationDescription = (family) => `${DESCRIPTIONS[family]} Flat fields; omit taskId and revisions.`;
|
|
8717
|
+
var READ_TASK_FIELDS = new Set(["status", "query", "limit", "view"]);
|
|
8718
|
+
var READ_ONLY_ACTIONS = new Set(["list", "inspect", "preview", "explain", "export"]);
|
|
8719
|
+
function flatOperationJsonSchema(family, options = {}) {
|
|
8720
|
+
const spec = FLAT[family];
|
|
8721
|
+
const actions = options.readOnly ? OPERATION_ACTIONS[family].filter((action) => READ_ONLY_ACTIONS.has(action)) : [...OPERATION_ACTIONS[family]];
|
|
8722
|
+
if (!actions.length)
|
|
8723
|
+
return null;
|
|
8724
|
+
const property = (field) => {
|
|
8725
|
+
const base = field.type === "array" ? { type: "array", items: { type: "string" } } : field.type === "object" ? { type: "object" } : { type: field.type };
|
|
8726
|
+
const tolerant = options.stringTolerant && field.type !== "string" ? { type: [field.type, "string"] } : {};
|
|
8727
|
+
return {
|
|
8728
|
+
...base,
|
|
8729
|
+
...tolerant,
|
|
8730
|
+
...field.enum ? { enum: field.enum } : {},
|
|
8731
|
+
...field.d ? { description: field.d } : {}
|
|
8732
|
+
};
|
|
8733
|
+
};
|
|
8734
|
+
const properties = { action: { type: "string", enum: actions } };
|
|
8735
|
+
const readField = (name) => name === "taskId" || (family === "task" ? READ_TASK_FIELDS.has(name) : family === "policy");
|
|
8736
|
+
for (const [name, field] of Object.entries({ ...COMMON, ...spec.fields }))
|
|
8737
|
+
if (options.readOnly ? readField(name) : !(family === "state" && name === "expectedRevision"))
|
|
8738
|
+
properties[name] = property(field);
|
|
8739
|
+
return {
|
|
8740
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
8741
|
+
type: "object",
|
|
8742
|
+
properties,
|
|
8743
|
+
required: ["action"]
|
|
8744
|
+
};
|
|
8745
|
+
}
|
|
8746
|
+
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8747
|
+
var decoded = (value) => {
|
|
8748
|
+
if (typeof value !== "string")
|
|
8749
|
+
return value;
|
|
8750
|
+
const trimmed = value.trim();
|
|
8751
|
+
if (!/^[[{]/.test(trimmed) && !/^(true|false|null|-?\d+)$/.test(trimmed))
|
|
8752
|
+
return value;
|
|
8753
|
+
try {
|
|
8754
|
+
return JSON.parse(trimmed);
|
|
8755
|
+
} catch {
|
|
8756
|
+
return value;
|
|
8757
|
+
}
|
|
8758
|
+
};
|
|
8759
|
+
var strings = (value) => {
|
|
8760
|
+
const plain = decoded(value);
|
|
8761
|
+
if (typeof plain === "string")
|
|
8762
|
+
return plain.trim() ? [plain] : [];
|
|
8763
|
+
return plain;
|
|
8764
|
+
};
|
|
8765
|
+
var refOf = (value) => {
|
|
8766
|
+
if (typeof value !== "string")
|
|
8767
|
+
return value;
|
|
8768
|
+
return /^https?:\/\//i.test(value) ? { kind: "external", url: value } : { kind: "file", path: value.replace(/^\.\//, ""), digest: null };
|
|
8769
|
+
};
|
|
8770
|
+
var refList = (value) => {
|
|
8771
|
+
const items = strings(value);
|
|
8772
|
+
return Array.isArray(items) ? items.map(refOf) : items;
|
|
8773
|
+
};
|
|
8774
|
+
var take = (flat, keys, map = decoded) => {
|
|
8775
|
+
const out = {};
|
|
8776
|
+
for (const key of keys)
|
|
8777
|
+
if (key in flat) {
|
|
8778
|
+
out[key] = map(flat[key]);
|
|
8779
|
+
delete flat[key];
|
|
8780
|
+
}
|
|
8781
|
+
return out;
|
|
8782
|
+
};
|
|
8783
|
+
var nest = (request, key, flat, defaults = {}) => {
|
|
8784
|
+
const nested = isObject2(decoded(request[key])) ? decoded(request[key]) : null;
|
|
8785
|
+
if (!nested && !Object.keys(flat).length)
|
|
8786
|
+
return;
|
|
8787
|
+
request[key] = { ...defaults, ...flat, ...nested };
|
|
8788
|
+
};
|
|
8789
|
+
var scopeOf = (flat, description) => {
|
|
8790
|
+
const scope = decoded(flat.scope);
|
|
8791
|
+
const has = "paths" in flat || "exclusions" in flat || typeof scope === "string";
|
|
8792
|
+
const out = isObject2(scope) ? scope : has ? {
|
|
8793
|
+
description: typeof scope === "string" ? scope : String(description ?? ""),
|
|
8794
|
+
paths: strings(flat.paths ?? ["."]),
|
|
8795
|
+
exclusions: strings(flat.exclusions ?? [])
|
|
8796
|
+
} : null;
|
|
8797
|
+
delete flat.scope;
|
|
8798
|
+
delete flat.paths;
|
|
8799
|
+
delete flat.exclusions;
|
|
8800
|
+
return out;
|
|
8801
|
+
};
|
|
8802
|
+
var defaultScope = (description) => ({
|
|
8803
|
+
description: typeof description === "string" ? description : "",
|
|
8804
|
+
paths: ["."],
|
|
8805
|
+
exclusions: []
|
|
8806
|
+
});
|
|
8807
|
+
var LISTS = [
|
|
8808
|
+
"decisionIds",
|
|
8809
|
+
"evidenceIds",
|
|
8810
|
+
"findingIds",
|
|
8811
|
+
"requirementIds",
|
|
8812
|
+
"refs",
|
|
8813
|
+
"blockers"
|
|
8814
|
+
];
|
|
8815
|
+
var ENVELOPE = new Set([
|
|
8816
|
+
"schemaVersion",
|
|
8817
|
+
"action",
|
|
8818
|
+
"taskId",
|
|
8819
|
+
"expectedRevision",
|
|
8820
|
+
"expectedWorkspaceRevision",
|
|
8821
|
+
"judgment"
|
|
8822
|
+
]);
|
|
8823
|
+
function normalizeOperationInput(family, input, host) {
|
|
8824
|
+
if (!isObject2(input))
|
|
8825
|
+
return input;
|
|
8826
|
+
const request = { ...input };
|
|
8827
|
+
if (!("schemaVersion" in request))
|
|
8828
|
+
request.schemaVersion = 1;
|
|
8829
|
+
else
|
|
8830
|
+
request.schemaVersion = decoded(request.schemaVersion);
|
|
8831
|
+
for (const key of ["limit", "exitCode"])
|
|
8832
|
+
if (key in request)
|
|
8833
|
+
request[key] = decoded(request[key]);
|
|
8834
|
+
const action = request.action;
|
|
8835
|
+
const flat = request;
|
|
8836
|
+
switch (family) {
|
|
8837
|
+
case "task": {
|
|
8838
|
+
if (action === "start" || action === "revise") {
|
|
8839
|
+
const intent = isObject2(decoded(request.intent)) ? decoded(request.intent) : {};
|
|
8840
|
+
const objective = flat.objective ?? intent.objective;
|
|
8841
|
+
const scope = scopeOf(flat, objective);
|
|
8842
|
+
const fields = take(flat, ["objective"]);
|
|
8843
|
+
const authorityRefs = "refs" in flat ? refList(flat.refs) : undefined;
|
|
8844
|
+
delete flat.refs;
|
|
8845
|
+
nest(request, "intent", {
|
|
8846
|
+
...fields,
|
|
8847
|
+
...scope ? { scope } : {},
|
|
8848
|
+
...authorityRefs ? { authorityRefs } : {}
|
|
8849
|
+
}, { scope: defaultScope(objective), authorityRefs: [] });
|
|
8850
|
+
} else if (action === "progress") {
|
|
8851
|
+
const fields = take(flat, ["summary", "nextAction"]);
|
|
8852
|
+
const blockers = "blockers" in flat ? strings(flat.blockers) : undefined;
|
|
8853
|
+
delete flat.blockers;
|
|
8854
|
+
nest(request, "progress", {
|
|
8855
|
+
...fields,
|
|
8856
|
+
...Array.isArray(blockers) ? {
|
|
8857
|
+
blockers: blockers.map((item) => typeof item === "string" ? { reason: item, dependentAction: "", refs: [] } : item)
|
|
8858
|
+
} : {}
|
|
8859
|
+
}, { nextAction: null, blockers: [] });
|
|
8860
|
+
} else if (action === "close" && "decisionIds" in flat)
|
|
8861
|
+
flat.decisionIds = strings(flat.decisionIds);
|
|
8862
|
+
break;
|
|
8863
|
+
}
|
|
8864
|
+
case "policy": {
|
|
8865
|
+
if (action === "assess" || action === "preview") {
|
|
8866
|
+
const judgment = isObject2(decoded(request.judgment)) ? { ...decoded(request.judgment) } : {};
|
|
8867
|
+
for (const key of Object.keys(flat))
|
|
8868
|
+
if (!ENVELOPE.has(key)) {
|
|
8869
|
+
if (!(key in judgment))
|
|
8870
|
+
judgment[key] = decoded(flat[key]);
|
|
8871
|
+
delete flat[key];
|
|
8872
|
+
}
|
|
8873
|
+
request.judgment = judgment;
|
|
8874
|
+
}
|
|
8875
|
+
break;
|
|
8876
|
+
}
|
|
8877
|
+
case "evidence": {
|
|
8878
|
+
if (action === "record") {
|
|
8879
|
+
const review = "reviewSession" in flat ? decoded(flat.reviewSession) : undefined;
|
|
8880
|
+
delete flat.reviewSession;
|
|
8881
|
+
const fields = take(flat, [
|
|
8882
|
+
"kind",
|
|
8883
|
+
"claim",
|
|
8884
|
+
"result",
|
|
8885
|
+
"summary",
|
|
8886
|
+
"exitCode",
|
|
8887
|
+
"candidateId",
|
|
8888
|
+
"beforeCandidateId",
|
|
8889
|
+
"reviewContext"
|
|
8890
|
+
]);
|
|
8891
|
+
if ("refs" in flat)
|
|
8892
|
+
fields.refs = refList(flat.refs);
|
|
8893
|
+
if ("requirementIds" in flat)
|
|
8894
|
+
fields.requirementIds = strings(flat.requirementIds);
|
|
8895
|
+
delete flat.refs;
|
|
8896
|
+
delete flat.requirementIds;
|
|
8897
|
+
if (typeof review === "string" && review)
|
|
8898
|
+
fields.reviewContext = { kind: "host", host, handle: review };
|
|
8899
|
+
nest(request, "evidence", fields, {
|
|
8900
|
+
summary: "",
|
|
8901
|
+
refs: [],
|
|
8902
|
+
requirementIds: [],
|
|
8903
|
+
exitCode: null,
|
|
8904
|
+
reviewContext: null
|
|
8905
|
+
});
|
|
8906
|
+
}
|
|
8907
|
+
break;
|
|
8908
|
+
}
|
|
8909
|
+
case "finding": {
|
|
8910
|
+
if (action === "record") {
|
|
8911
|
+
const scope = scopeOf(flat, flat.claim);
|
|
8912
|
+
if (scope)
|
|
8913
|
+
request.scope = scope;
|
|
8914
|
+
else if (!("scope" in request))
|
|
8915
|
+
request.scope = defaultScope(request.claim);
|
|
8916
|
+
request.refs = "refs" in flat ? refList(flat.refs) : [];
|
|
8917
|
+
}
|
|
8918
|
+
break;
|
|
8919
|
+
}
|
|
8920
|
+
case "decision": {
|
|
8921
|
+
if (action === "record") {
|
|
8922
|
+
const binding = isObject2(decoded(request.binding)) ? decoded(request.binding) : {};
|
|
8923
|
+
const presented = flat.presented ?? binding.presented;
|
|
8924
|
+
const scope = scopeOf(flat, presented);
|
|
8925
|
+
const fields = take(flat, ["presented"]);
|
|
8926
|
+
const choice = flat.choice;
|
|
8927
|
+
delete flat.choice;
|
|
8928
|
+
const contentRefs = "refs" in flat ? refList(flat.refs) : undefined;
|
|
8929
|
+
delete flat.refs;
|
|
8930
|
+
nest(request, "binding", {
|
|
8931
|
+
...fields,
|
|
8932
|
+
...scope ? { scope } : {},
|
|
8933
|
+
...contentRefs ? { contentRefs } : {},
|
|
8934
|
+
...typeof choice === "string" ? { statedChoice: { ref: "choice", text: choice } } : {}
|
|
8935
|
+
}, { scope: defaultScope(presented), contentRefs: [] });
|
|
8936
|
+
}
|
|
8937
|
+
break;
|
|
8938
|
+
}
|
|
8939
|
+
case "worker": {
|
|
8940
|
+
if (action === "assign") {
|
|
8941
|
+
const objective = flat.objective;
|
|
8942
|
+
const scope = scopeOf(flat, objective);
|
|
8943
|
+
const fields = take(flat, ["role", "objective", "candidateId", "stoppingCondition"]);
|
|
8944
|
+
for (const key of ["decisionIds", "requirementIds"])
|
|
8945
|
+
if (key in flat) {
|
|
8946
|
+
fields[key] = strings(flat[key]);
|
|
8947
|
+
delete flat[key];
|
|
8948
|
+
}
|
|
8949
|
+
nest(request, "assignment", { ...fields, ...scope ? { scope } : {} }, { scope: defaultScope(objective), decisionIds: [], requirementIds: [] });
|
|
8950
|
+
} else if (action === "report") {
|
|
8951
|
+
const fields = take(flat, ["outcome", "summary"]);
|
|
8952
|
+
for (const key of ["evidenceIds", "findingIds"])
|
|
8953
|
+
if (key in flat) {
|
|
8954
|
+
fields[key] = strings(flat[key]);
|
|
8955
|
+
delete flat[key];
|
|
8956
|
+
}
|
|
8957
|
+
nest(request, "report", fields, { evidenceIds: [], findingIds: [] });
|
|
8958
|
+
}
|
|
8959
|
+
break;
|
|
8960
|
+
}
|
|
8961
|
+
case "state": {
|
|
8962
|
+
if ("bundle" in request)
|
|
8963
|
+
request.bundle = decoded(request.bundle);
|
|
8964
|
+
break;
|
|
8965
|
+
}
|
|
8966
|
+
}
|
|
8967
|
+
const defaults = {
|
|
8968
|
+
finding: { resolve: ["evidenceIds", "decisionIds"] },
|
|
8969
|
+
decision: { record: ["requirementIds"] }
|
|
8970
|
+
};
|
|
8971
|
+
for (const key of defaults[family]?.[String(action)] ?? [])
|
|
8972
|
+
if (!(key in request))
|
|
8973
|
+
request[key] = [];
|
|
8974
|
+
for (const key of LISTS)
|
|
8975
|
+
if (key in request && typeof request[key] === "string")
|
|
8976
|
+
request[key] = strings(request[key]);
|
|
8977
|
+
return request;
|
|
8978
|
+
}
|
|
8979
|
+
function parseAdvertisedOperation(family, input, host) {
|
|
8980
|
+
const request = normalizeOperationInput(family, input, host);
|
|
8981
|
+
if (isObject2(request) && request.schemaVersion !== SCHEMA_VERSION)
|
|
8982
|
+
return failure2("unsupported_version", "unsupported schema version", { operation: family });
|
|
8983
|
+
const parsed = advertisedOperationSchemas[family].safeParse(request);
|
|
8984
|
+
if (parsed.success)
|
|
8985
|
+
return success(null, null, request);
|
|
8986
|
+
return failure2("invalid_input", "operation input is invalid", {
|
|
8987
|
+
fields: parsed.error.issues.map((issue) => ({
|
|
8988
|
+
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
8989
|
+
reason: issue.message
|
|
8990
|
+
}))
|
|
8991
|
+
});
|
|
8992
|
+
}
|
|
8732
8993
|
// packages/workit-core/src/core/task-store.ts
|
|
8733
8994
|
import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
|
|
8734
8995
|
import * as fs7 from "node:fs";
|
|
@@ -9783,7 +10044,7 @@ class LogDamage extends Error {
|
|
|
9783
10044
|
this.upgrade = upgrade;
|
|
9784
10045
|
}
|
|
9785
10046
|
}
|
|
9786
|
-
var
|
|
10047
|
+
var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9787
10048
|
var parseEvent = (line, file, offset) => {
|
|
9788
10049
|
let value;
|
|
9789
10050
|
try {
|
|
@@ -9791,11 +10052,11 @@ var parseEvent = (line, file, offset) => {
|
|
|
9791
10052
|
} catch {
|
|
9792
10053
|
throw new LogDamage(`${file}: unreadable event at byte ${offset}`);
|
|
9793
10054
|
}
|
|
9794
|
-
if (!
|
|
10055
|
+
if (!isObject3(value) || typeof value.v !== "number")
|
|
9795
10056
|
throw new LogDamage(`${file}: malformed event at byte ${offset}`);
|
|
9796
10057
|
if (value.v > EVENT_VERSION)
|
|
9797
10058
|
throw new LogDamage(`${file}: event format v${value.v} was written by a newer Workit; upgrade Workit to read this task`, true);
|
|
9798
|
-
if (typeof value.seq !== "number" || !Number.isInteger(value.seq) || typeof value.type !== "string" || typeof value.id !== "string" || typeof value.task !== "string" || !
|
|
10059
|
+
if (typeof value.seq !== "number" || !Number.isInteger(value.seq) || typeof value.type !== "string" || typeof value.id !== "string" || typeof value.task !== "string" || !isObject3(value.data))
|
|
9799
10060
|
throw new LogDamage(`${file}: malformed event at byte ${offset}`);
|
|
9800
10061
|
return value;
|
|
9801
10062
|
};
|
|
@@ -10066,13 +10327,13 @@ var KNOWN_TYPES = new Set([
|
|
|
10066
10327
|
"decision.updated",
|
|
10067
10328
|
"worker.updated"
|
|
10068
10329
|
]);
|
|
10069
|
-
var
|
|
10070
|
-
var isKey = (value) =>
|
|
10330
|
+
var isObject4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10331
|
+
var isKey = (value) => isObject4(value) && typeof value.key === "string" && (value.kind === "branch" || value.kind === "detached" || value.kind === "dir") && (value.branch === null || typeof value.branch === "string");
|
|
10071
10332
|
var BLOB_REF = "$blob";
|
|
10072
|
-
var blobRef = (value) =>
|
|
10333
|
+
var blobRef = (value) => isObject4(value) && Object.keys(value).length === 1 && typeof value[BLOB_REF] === "string" && /^[0-9a-f]{64}$/.test(value[BLOB_REF]) ? value[BLOB_REF] : null;
|
|
10073
10334
|
var blobDigest = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
10074
10335
|
var toBlob = (value, blobs) => {
|
|
10075
|
-
if (!
|
|
10336
|
+
if (!isObject4(value))
|
|
10076
10337
|
return value;
|
|
10077
10338
|
const digest = blobDigest(value);
|
|
10078
10339
|
blobs.write(digest, value);
|
|
@@ -10096,7 +10357,7 @@ var decode2 = (value, blobs) => {
|
|
|
10096
10357
|
return blobs.read(digest);
|
|
10097
10358
|
if (Array.isArray(value))
|
|
10098
10359
|
return value.map((item) => decode2(item, blobs));
|
|
10099
|
-
if (
|
|
10360
|
+
if (isObject4(value)) {
|
|
10100
10361
|
const out = {};
|
|
10101
10362
|
for (const [key, item] of Object.entries(value))
|
|
10102
10363
|
out[key] = decode2(item, blobs);
|
|
@@ -10115,7 +10376,7 @@ function reduce(state, event, blobs) {
|
|
|
10115
10376
|
}
|
|
10116
10377
|
let next = state;
|
|
10117
10378
|
if ("record" in data) {
|
|
10118
|
-
if (!
|
|
10379
|
+
if (!isObject4(data.record))
|
|
10119
10380
|
throw new LogDamage(`event ${event.seq} carries a malformed record`);
|
|
10120
10381
|
next = {
|
|
10121
10382
|
record: decode2(data.record, blobs),
|
|
@@ -10137,7 +10398,7 @@ function reduce(state, event, blobs) {
|
|
|
10137
10398
|
}
|
|
10138
10399
|
if ("key" in data)
|
|
10139
10400
|
next.key = isKey(data.key) ? data.key : null;
|
|
10140
|
-
if ("legacy" in data &&
|
|
10401
|
+
if ("legacy" in data && isObject4(data.legacy))
|
|
10141
10402
|
next.legacy = {
|
|
10142
10403
|
path: String(data.legacy.path ?? ""),
|
|
10143
10404
|
digest: String(data.legacy.digest ?? "")
|
|
@@ -10193,8 +10454,8 @@ var AUTO_COMPACT_BYTES = 2 * 1024 * 1024;
|
|
|
10193
10454
|
var TOMBSTONE = Symbol("tombstone");
|
|
10194
10455
|
var STALE_TEMPORARY_MS = 60 * 60000;
|
|
10195
10456
|
var INDEX_VERSION = 3;
|
|
10196
|
-
var
|
|
10197
|
-
var hostSession = (value) =>
|
|
10457
|
+
var isObject5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10458
|
+
var hostSession = (value) => isObject5(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
|
|
10198
10459
|
var indexEntry = (task, key, legacy, file) => {
|
|
10199
10460
|
const sessions = [];
|
|
10200
10461
|
const intent = hostSession(task.intent.provenance.session);
|
|
@@ -11007,7 +11268,7 @@ class TaskStore {
|
|
|
11007
11268
|
readSnapshot(taskId) {
|
|
11008
11269
|
try {
|
|
11009
11270
|
const value = JSON.parse(fs7.readFileSync(this.snapshotPath(taskId), "utf8"));
|
|
11010
|
-
if (!
|
|
11271
|
+
if (!isObject5(value) || value.v !== 1 || value.task !== taskId || typeof value.seq !== "number" || typeof value.lastId !== "string" || typeof value.lastStart !== "number" || typeof value.end !== "number" || !isObject5(value.record))
|
|
11011
11272
|
return null;
|
|
11012
11273
|
return value;
|
|
11013
11274
|
} catch {
|
|
@@ -11353,11 +11614,11 @@ class TaskStore {
|
|
|
11353
11614
|
readIndex() {
|
|
11354
11615
|
try {
|
|
11355
11616
|
const value = JSON.parse(fs7.readFileSync(this.paths().index, "utf8"));
|
|
11356
|
-
if (!
|
|
11617
|
+
if (!isObject5(value) || value.version !== INDEX_VERSION || !isObject5(value.tasks))
|
|
11357
11618
|
return {};
|
|
11358
11619
|
const tasks = {};
|
|
11359
11620
|
for (const [id, entry] of Object.entries(value.tasks))
|
|
11360
|
-
if (
|
|
11621
|
+
if (isObject5(entry) && entry.id === id && typeof entry.file === "string" && typeof entry.status === "string" && typeof entry.updatedAt === "string" && typeof entry.workspaceId === "string" && Array.isArray(entry.sessions) && isObject5(entry.progress) && isObject5(entry.source))
|
|
11361
11622
|
tasks[id] = entry;
|
|
11362
11623
|
return tasks;
|
|
11363
11624
|
} catch {
|
|
@@ -11401,12 +11662,12 @@ class TaskStore {
|
|
|
11401
11662
|
return this.parseValue(value, schema, label);
|
|
11402
11663
|
}
|
|
11403
11664
|
parseValue(value, schema, label) {
|
|
11404
|
-
if (
|
|
11665
|
+
if (isObject5(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
11405
11666
|
return failure2("unsupported_version", `unsupported ${label} schema version`);
|
|
11406
11667
|
const parsed = parseStoredRecord(schema, value);
|
|
11407
11668
|
if (parsed.success)
|
|
11408
11669
|
return success(null, null, parsed.data);
|
|
11409
|
-
const writerVersion =
|
|
11670
|
+
const writerVersion = isObject5(value) && isObject5(value.runtime) ? value.runtime.updatedWith : null;
|
|
11410
11671
|
if (parsed.critical.length > 0)
|
|
11411
11672
|
return failure2("recovery_required", `${label} record requires fields this Workit cannot read (${parsed.critical.join(", ")}); upgrade Workit before mutating this checkout`);
|
|
11412
11673
|
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
@@ -11608,7 +11869,7 @@ class TaskStore {
|
|
|
11608
11869
|
}
|
|
11609
11870
|
try {
|
|
11610
11871
|
const value = JSON.parse(bytes);
|
|
11611
|
-
if (
|
|
11872
|
+
if (isObject5(value) && isObject5(value.store) && value.store.format === "workit-store")
|
|
11612
11873
|
return TOMBSTONE;
|
|
11613
11874
|
} catch {}
|
|
11614
11875
|
return { bytes };
|
|
@@ -11653,7 +11914,7 @@ class TaskStore {
|
|
|
11653
11914
|
isStub(file) {
|
|
11654
11915
|
try {
|
|
11655
11916
|
const value = JSON.parse(fs7.readFileSync(file, "utf8"));
|
|
11656
|
-
return
|
|
11917
|
+
return isObject5(value) && isObject5(value.store) && value.store.format === "workit-store";
|
|
11657
11918
|
} catch {
|
|
11658
11919
|
return false;
|
|
11659
11920
|
}
|
|
@@ -11824,10 +12085,10 @@ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
|
11824
12085
|
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
11825
12086
|
|
|
11826
12087
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
11827
|
-
import { createHash as
|
|
11828
|
-
import * as
|
|
11829
|
-
import { spawnSync as
|
|
11830
|
-
import
|
|
12088
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
12089
|
+
import * as fs11 from "node:fs";
|
|
12090
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
12091
|
+
import path11 from "node:path";
|
|
11831
12092
|
|
|
11832
12093
|
// packages/workit-core/src/check-config.ts
|
|
11833
12094
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
@@ -12067,6 +12328,7 @@ var line = (run) => {
|
|
|
12067
12328
|
const value = run.stdout.trim();
|
|
12068
12329
|
return value ? value : null;
|
|
12069
12330
|
};
|
|
12331
|
+
var safeArg = (value) => value.length > 0 && !value.startsWith("-");
|
|
12070
12332
|
function headSha(cwd) {
|
|
12071
12333
|
return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
|
|
12072
12334
|
}
|
|
@@ -12158,124 +12420,733 @@ function worktreeSignal(cwd, options = {}) {
|
|
|
12158
12420
|
}
|
|
12159
12421
|
return `sig:${hash.digest("hex")}`;
|
|
12160
12422
|
}
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
var digestBytes = (value) => createHash6("sha256").update(value).digest("hex");
|
|
12164
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path9.sep}`);
|
|
12165
|
-
var relative = (root, candidate) => path9.relative(root, candidate).split(path9.sep).join("/") || ".";
|
|
12166
|
-
var canonicalPath = (value) => {
|
|
12167
|
-
if (typeof value !== "string" || !value)
|
|
12423
|
+
function mergeBase(cwd, a, b) {
|
|
12424
|
+
if (!safeArg(a) || !safeArg(b))
|
|
12168
12425
|
return null;
|
|
12169
|
-
|
|
12426
|
+
return line(git2(cwd, ["merge-base", a, b]));
|
|
12427
|
+
}
|
|
12428
|
+
function patchId(cwd, base, head) {
|
|
12429
|
+
if (!safeArg(base) || !safeArg(head))
|
|
12170
12430
|
return null;
|
|
12171
|
-
const
|
|
12172
|
-
|
|
12431
|
+
const diff = git2(cwd, [
|
|
12432
|
+
"diff",
|
|
12433
|
+
"-U3",
|
|
12434
|
+
"--diff-algorithm=myers",
|
|
12435
|
+
"--indent-heuristic",
|
|
12436
|
+
"--no-color",
|
|
12437
|
+
"--no-ext-diff",
|
|
12438
|
+
"--no-textconv",
|
|
12439
|
+
"--no-renames",
|
|
12440
|
+
"--full-index",
|
|
12441
|
+
"--src-prefix=a/",
|
|
12442
|
+
"--dst-prefix=b/",
|
|
12443
|
+
`${base}...${head}`,
|
|
12444
|
+
"--"
|
|
12445
|
+
]);
|
|
12446
|
+
if (!diff.ok || !diff.stdout.trim())
|
|
12173
12447
|
return null;
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
|
|
12178
|
-
|
|
12179
|
-
|
|
12180
|
-
|
|
12448
|
+
const id = git2(cwd, ["patch-id", "--stable"], { input: diff.stdout });
|
|
12449
|
+
return line(id)?.split(/\s+/u)[0] ?? null;
|
|
12450
|
+
}
|
|
12451
|
+
function remoteNames(cwd) {
|
|
12452
|
+
return (git2(cwd, ["remote"]).stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
|
|
12453
|
+
}
|
|
12454
|
+
function resolveRef(cwd, ref) {
|
|
12455
|
+
if (!safeArg(ref))
|
|
12181
12456
|
return null;
|
|
12457
|
+
return line(git2(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
12458
|
+
}
|
|
12459
|
+
var config2 = (cwd, key) => line(git2(cwd, ["config", "--get", key]));
|
|
12460
|
+
function currentBranch(cwd) {
|
|
12461
|
+
return line(git2(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
|
|
12462
|
+
}
|
|
12463
|
+
function pushRemoteName(cwd, branch) {
|
|
12464
|
+
const name = branch ?? currentBranch(cwd);
|
|
12465
|
+
const remotes = remoteNames(cwd);
|
|
12466
|
+
const candidates = [
|
|
12467
|
+
name ? config2(cwd, `branch.${name}.pushRemote`) : null,
|
|
12468
|
+
config2(cwd, "remote.pushDefault"),
|
|
12469
|
+
name ? config2(cwd, `branch.${name}.remote`) : null
|
|
12470
|
+
];
|
|
12471
|
+
for (const candidate of candidates)
|
|
12472
|
+
if (candidate && candidate !== "." && remotes.includes(candidate))
|
|
12473
|
+
return candidate;
|
|
12474
|
+
if (remotes.includes("origin"))
|
|
12475
|
+
return "origin";
|
|
12476
|
+
return remotes.length === 1 ? remotes[0] : null;
|
|
12477
|
+
}
|
|
12478
|
+
|
|
12479
|
+
// packages/workit-core/src/core/policy/derive.ts
|
|
12480
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
12481
|
+
import path10 from "node:path";
|
|
12482
|
+
|
|
12483
|
+
// packages/workit-core/src/ledger.ts
|
|
12484
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
12485
|
+
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
12486
|
+
import fs10 from "node:fs";
|
|
12487
|
+
import path9 from "node:path";
|
|
12488
|
+
var LEDGER_VERSION = 1;
|
|
12489
|
+
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
12490
|
+
var FAILING = new Set(["failed", "blocked"]);
|
|
12491
|
+
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
12492
|
+
var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
|
|
12493
|
+
var err = (code, error, unblock) => ({
|
|
12494
|
+
ok: false,
|
|
12495
|
+
code,
|
|
12496
|
+
error,
|
|
12497
|
+
...unblock ? { unblock } : {}
|
|
12498
|
+
});
|
|
12499
|
+
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
12500
|
+
const run = spawnSync5("git", args, {
|
|
12501
|
+
cwd,
|
|
12502
|
+
encoding: "utf8",
|
|
12503
|
+
timeout: timeoutMs,
|
|
12504
|
+
killSignal: "SIGKILL",
|
|
12505
|
+
windowsHide: true,
|
|
12506
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
12507
|
+
env: {
|
|
12508
|
+
...process.env,
|
|
12509
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
12510
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
12511
|
+
LC_ALL: "C",
|
|
12512
|
+
LANGUAGE: "C"
|
|
12513
|
+
}
|
|
12514
|
+
});
|
|
12182
12515
|
return {
|
|
12183
|
-
|
|
12184
|
-
|
|
12185
|
-
|
|
12516
|
+
ok: run.status === 0,
|
|
12517
|
+
stdout: run.stdout ?? "",
|
|
12518
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
12186
12519
|
};
|
|
12187
12520
|
};
|
|
12188
|
-
var
|
|
12189
|
-
|
|
12190
|
-
const
|
|
12191
|
-
|
|
12192
|
-
return false;
|
|
12193
|
-
const paths = normalized.paths;
|
|
12194
|
-
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
12195
|
-
};
|
|
12196
|
-
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
12197
|
-
var compareCodeUnits2 = (left, right) => {
|
|
12198
|
-
const length = Math.min(left.length, right.length);
|
|
12199
|
-
for (let index = 0;index < length; index += 1) {
|
|
12200
|
-
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
12201
|
-
if (difference !== 0)
|
|
12202
|
-
return difference;
|
|
12203
|
-
}
|
|
12204
|
-
return left.length - right.length;
|
|
12521
|
+
var git3 = (cwd, args) => {
|
|
12522
|
+
const run = gitRun2(cwd, args);
|
|
12523
|
+
const value = run.ok ? run.stdout.trim() : "";
|
|
12524
|
+
return value ? value : null;
|
|
12205
12525
|
};
|
|
12206
|
-
var
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
|
|
12210
|
-
});
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
|
|
12219
|
-
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
12526
|
+
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
12527
|
+
function resolveCommit(cwd, ref) {
|
|
12528
|
+
if (!safeRef(ref))
|
|
12529
|
+
return null;
|
|
12530
|
+
return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
12531
|
+
}
|
|
12532
|
+
function branchHead(cwd, branch) {
|
|
12533
|
+
if (!safeRef(branch))
|
|
12534
|
+
return null;
|
|
12535
|
+
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
12536
|
+
}
|
|
12537
|
+
function defaultBase(cwd) {
|
|
12538
|
+
const originHead = git3(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
|
|
12539
|
+
if (originHead && resolveCommit(cwd, originHead))
|
|
12540
|
+
return originHead;
|
|
12541
|
+
for (const candidate of ["origin/main", "origin/master", "main", "master"])
|
|
12542
|
+
if (resolveCommit(cwd, candidate))
|
|
12543
|
+
return candidate;
|
|
12544
|
+
return null;
|
|
12545
|
+
}
|
|
12546
|
+
function diffHash(cwd, base, head) {
|
|
12547
|
+
if (!safeRef(base) || !safeRef(head))
|
|
12548
|
+
return null;
|
|
12549
|
+
const diff = gitRun2(cwd, [
|
|
12550
|
+
"diff",
|
|
12551
|
+
"-U3",
|
|
12552
|
+
"--diff-algorithm=myers",
|
|
12553
|
+
"--indent-heuristic",
|
|
12554
|
+
"--no-color",
|
|
12555
|
+
"--no-ext-diff",
|
|
12556
|
+
"--no-textconv",
|
|
12557
|
+
"--no-renames",
|
|
12558
|
+
"--full-index",
|
|
12559
|
+
"--binary",
|
|
12560
|
+
"--src-prefix=a/",
|
|
12561
|
+
"--dst-prefix=b/",
|
|
12562
|
+
`${base}...${head}`,
|
|
12563
|
+
"--"
|
|
12564
|
+
], GIT_TIMEOUTS.worktree);
|
|
12565
|
+
if (!diff.ok || !diff.stdout)
|
|
12566
|
+
return null;
|
|
12567
|
+
const hash = createHash6("sha256");
|
|
12568
|
+
for (const line of diff.stdout.split(`
|
|
12569
|
+
`)) {
|
|
12570
|
+
if (line.startsWith("index "))
|
|
12571
|
+
continue;
|
|
12572
|
+
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
12573
|
+
hash.update(`
|
|
12574
|
+
`);
|
|
12227
12575
|
}
|
|
12228
|
-
return {
|
|
12576
|
+
return `sha256:${hash.digest("hex")}`;
|
|
12577
|
+
}
|
|
12578
|
+
function storeRoot(cwd) {
|
|
12579
|
+
const location = resolveStore(path9.resolve(cwd));
|
|
12580
|
+
if (location instanceof Error)
|
|
12581
|
+
return err("unavailable", location.message.replace(/; fix the repository.*$/u, ""), "fix the repository (git status should work here) or run from a non-git directory");
|
|
12582
|
+
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
12583
|
+
}
|
|
12584
|
+
function ledgerPath(cwd) {
|
|
12585
|
+
const root = storeRoot(cwd);
|
|
12586
|
+
return root.ok ? { ok: true, value: path9.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
12587
|
+
}
|
|
12588
|
+
var NETWORK_FS_TYPES = new Set([
|
|
12589
|
+
26985,
|
|
12590
|
+
20859,
|
|
12591
|
+
4266872130,
|
|
12592
|
+
4283649346,
|
|
12593
|
+
1702057286,
|
|
12594
|
+
1397113167,
|
|
12595
|
+
12805120,
|
|
12596
|
+
16914839,
|
|
12597
|
+
1196443219,
|
|
12598
|
+
198183888
|
|
12599
|
+
]);
|
|
12600
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12601
|
+
var str2 = (value) => typeof value === "string" ? value : null;
|
|
12602
|
+
var normalizeActor = (value) => {
|
|
12603
|
+
const actor = isRecord2(value) ? value : {};
|
|
12604
|
+
return {
|
|
12605
|
+
host: str2(actor.host) ?? "unknown",
|
|
12606
|
+
session: str2(actor.session),
|
|
12607
|
+
agentId: str2(actor.agentId)
|
|
12608
|
+
};
|
|
12229
12609
|
};
|
|
12230
|
-
|
|
12231
|
-
|
|
12610
|
+
function supersedeAllowed(by, target) {
|
|
12611
|
+
if (by.type !== target.type)
|
|
12612
|
+
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
12613
|
+
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
12614
|
+
return "only the session that wrote a row can supersede it";
|
|
12615
|
+
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
12616
|
+
return "a self verdict cannot replace an independent verdict";
|
|
12617
|
+
return null;
|
|
12618
|
+
}
|
|
12619
|
+
function readLedger(cwd) {
|
|
12620
|
+
const file = ledgerPath(cwd);
|
|
12621
|
+
if (!file.ok)
|
|
12622
|
+
return file;
|
|
12623
|
+
let text = "";
|
|
12232
12624
|
try {
|
|
12233
|
-
|
|
12234
|
-
} catch {
|
|
12235
|
-
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
|
|
12625
|
+
text = fs10.readFileSync(file.value, "utf8");
|
|
12626
|
+
} catch (error) {
|
|
12627
|
+
if (error.code === "ENOENT")
|
|
12628
|
+
return { ok: true, value: { path: file.value, rows: [], skipped: 0 } };
|
|
12629
|
+
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
12630
|
+
}
|
|
12631
|
+
const rows = [];
|
|
12632
|
+
let skipped = 0;
|
|
12633
|
+
for (const raw of text.split(`
|
|
12634
|
+
`)) {
|
|
12635
|
+
const trimmed = raw.trim();
|
|
12636
|
+
if (!trimmed)
|
|
12240
12637
|
continue;
|
|
12241
|
-
|
|
12242
|
-
let stat;
|
|
12638
|
+
let parsed;
|
|
12243
12639
|
try {
|
|
12244
|
-
|
|
12640
|
+
parsed = JSON.parse(trimmed);
|
|
12245
12641
|
} catch {
|
|
12246
|
-
|
|
12642
|
+
skipped += 1;
|
|
12247
12643
|
continue;
|
|
12248
12644
|
}
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
uncertain = walk(root, target, output) || uncertain;
|
|
12253
|
-
}
|
|
12254
|
-
return uncertain;
|
|
12255
|
-
};
|
|
12256
|
-
var scopeRoots = (root, scope) => {
|
|
12257
|
-
const roots = [];
|
|
12258
|
-
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
12259
|
-
const target = path9.resolve(root, item);
|
|
12260
|
-
if (!inside(root, target))
|
|
12261
|
-
return failure2("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
12262
|
-
let ancestor = target;
|
|
12263
|
-
while (!fs10.existsSync(ancestor) && ancestor !== root)
|
|
12264
|
-
ancestor = path9.dirname(ancestor);
|
|
12265
|
-
try {
|
|
12266
|
-
if (!inside(root, fs10.realpathSync(ancestor)))
|
|
12267
|
-
return failure2("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
12268
|
-
} catch {
|
|
12269
|
-
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
12645
|
+
if (!isRecord2(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
|
|
12646
|
+
skipped += 1;
|
|
12647
|
+
continue;
|
|
12270
12648
|
}
|
|
12271
|
-
|
|
12649
|
+
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
12650
|
+
rows.push({
|
|
12651
|
+
...parsed,
|
|
12652
|
+
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
12653
|
+
id: parsed.id,
|
|
12654
|
+
at: parsed.at,
|
|
12655
|
+
type: parsed.type,
|
|
12656
|
+
actor: normalizeActor(parsed.actor),
|
|
12657
|
+
branch: str2(parsed.branch),
|
|
12658
|
+
head: str2(parsed.head),
|
|
12659
|
+
base: str2(parsed.base),
|
|
12660
|
+
baseSha: str2(parsed.baseSha),
|
|
12661
|
+
patchId: str2(parsed.patchId),
|
|
12662
|
+
diffHash: str2(parsed.diffHash),
|
|
12663
|
+
tree: str2(parsed.tree),
|
|
12664
|
+
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
12665
|
+
...pr === undefined ? {} : { pr },
|
|
12666
|
+
...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
12667
|
+
seq: rows.length + 1,
|
|
12668
|
+
superseded: false,
|
|
12669
|
+
supersedeIgnored: false
|
|
12670
|
+
});
|
|
12272
12671
|
}
|
|
12273
|
-
|
|
12274
|
-
|
|
12275
|
-
|
|
12276
|
-
|
|
12672
|
+
const byId = new Map;
|
|
12673
|
+
for (const row of rows) {
|
|
12674
|
+
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
12675
|
+
if (target && supersedeAllowed(row, target) === null)
|
|
12676
|
+
target.superseded = true;
|
|
12677
|
+
else if (row.supersedes)
|
|
12678
|
+
row.supersedeIgnored = true;
|
|
12679
|
+
if (!byId.has(row.id))
|
|
12680
|
+
byId.set(row.id, row);
|
|
12681
|
+
}
|
|
12682
|
+
return { ok: true, value: { path: file.value, rows, skipped } };
|
|
12683
|
+
}
|
|
12684
|
+
function verdictBasis(verdict, current) {
|
|
12685
|
+
if (verdict.head && verdict.head === current.head)
|
|
12686
|
+
return "fresh";
|
|
12687
|
+
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
12688
|
+
return "carried";
|
|
12689
|
+
return "stale";
|
|
12690
|
+
}
|
|
12691
|
+
var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded;
|
|
12692
|
+
function trailerSessions(cwd, base, head) {
|
|
12693
|
+
if (!base || !head || !safeRef(base) || !safeRef(head))
|
|
12694
|
+
return [];
|
|
12695
|
+
const out = git3(cwd, [
|
|
12696
|
+
"log",
|
|
12697
|
+
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
12698
|
+
`${base}..${head}`
|
|
12699
|
+
]);
|
|
12700
|
+
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
12701
|
+
}
|
|
12702
|
+
function authorSessions(cwd, rows, branch, range = { base: null, head: null }) {
|
|
12703
|
+
const sessions = new Set;
|
|
12704
|
+
for (const row of rows) {
|
|
12705
|
+
if (row.branch !== branch || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
12706
|
+
continue;
|
|
12707
|
+
const session = str2(row.session) ?? row.actor.session;
|
|
12708
|
+
if (session)
|
|
12709
|
+
sessions.add(session);
|
|
12710
|
+
}
|
|
12711
|
+
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
12712
|
+
sessions.add(session);
|
|
12713
|
+
return sessions;
|
|
12714
|
+
}
|
|
12715
|
+
var independenceReasons = (row, authors) => {
|
|
12716
|
+
const reasons = [];
|
|
12717
|
+
if (!row.actor.session)
|
|
12718
|
+
reasons.push("no_session");
|
|
12719
|
+
else if (authors.has(row.actor.session))
|
|
12720
|
+
reasons.push("author_session");
|
|
12721
|
+
if (row.self === true && !reasons.includes("no_session"))
|
|
12722
|
+
reasons.push("self");
|
|
12723
|
+
return reasons;
|
|
12724
|
+
};
|
|
12725
|
+
function effectiveOf(rows, authors) {
|
|
12726
|
+
let effective = null;
|
|
12727
|
+
for (const row of rows) {
|
|
12728
|
+
if (!effective) {
|
|
12729
|
+
effective = row;
|
|
12730
|
+
continue;
|
|
12731
|
+
}
|
|
12732
|
+
const rowIndependent = independenceReasons(row, authors).length === 0;
|
|
12733
|
+
const effIndependent = independenceReasons(effective, authors).length === 0;
|
|
12734
|
+
if (effIndependent && !rowIndependent)
|
|
12735
|
+
continue;
|
|
12736
|
+
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session)
|
|
12737
|
+
continue;
|
|
12738
|
+
effective = row;
|
|
12739
|
+
}
|
|
12740
|
+
return effective;
|
|
12741
|
+
}
|
|
12742
|
+
function restackCarries(rows, branch, from, to) {
|
|
12743
|
+
if (!from || !to)
|
|
12744
|
+
return false;
|
|
12745
|
+
const edges = new Map;
|
|
12746
|
+
for (const row of rows)
|
|
12747
|
+
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
12748
|
+
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
12749
|
+
const seen = new Set([from]);
|
|
12750
|
+
const queue = [from];
|
|
12751
|
+
while (queue.length) {
|
|
12752
|
+
const next = queue.shift();
|
|
12753
|
+
if (next === to)
|
|
12754
|
+
return true;
|
|
12755
|
+
for (const target of edges.get(next) ?? [])
|
|
12756
|
+
if (!seen.has(target)) {
|
|
12757
|
+
seen.add(target);
|
|
12758
|
+
queue.push(target);
|
|
12759
|
+
}
|
|
12760
|
+
}
|
|
12761
|
+
return false;
|
|
12762
|
+
}
|
|
12763
|
+
function checkVerdicts(cwd, branch, rows) {
|
|
12764
|
+
const head = branchHead(cwd, branch);
|
|
12765
|
+
const fallbackBase = defaultBase(cwd);
|
|
12766
|
+
const keyCache = new Map;
|
|
12767
|
+
const keyFor = (base) => {
|
|
12768
|
+
if (!base || !head)
|
|
12769
|
+
return { patchId: null, diffHash: null };
|
|
12770
|
+
let key = keyCache.get(base);
|
|
12771
|
+
if (!key) {
|
|
12772
|
+
key = { patchId: patchId(cwd, base, head), diffHash: diffHash(cwd, base, head) };
|
|
12773
|
+
keyCache.set(base, key);
|
|
12774
|
+
}
|
|
12775
|
+
return key;
|
|
12776
|
+
};
|
|
12777
|
+
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head });
|
|
12778
|
+
const byKind = new Map;
|
|
12779
|
+
for (const row of rows)
|
|
12780
|
+
if (row.branch === branch && isLiveVerdict(row)) {
|
|
12781
|
+
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
12782
|
+
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
12783
|
+
}
|
|
12784
|
+
const verdicts = [];
|
|
12785
|
+
for (const [kind, list] of byKind) {
|
|
12786
|
+
const row = effectiveOf(list, authors);
|
|
12787
|
+
if (!row)
|
|
12788
|
+
continue;
|
|
12789
|
+
let basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
12790
|
+
if (basis === "stale" && restackCarries(rows, branch, row.head, head))
|
|
12791
|
+
basis = "carried";
|
|
12792
|
+
const current = basis !== "stale";
|
|
12793
|
+
const independence = independenceReasons(row, authors);
|
|
12794
|
+
const reasons = [
|
|
12795
|
+
...current ? [] : ["stale"],
|
|
12796
|
+
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
12797
|
+
...independence
|
|
12798
|
+
];
|
|
12799
|
+
verdicts.push({
|
|
12800
|
+
kind,
|
|
12801
|
+
basis,
|
|
12802
|
+
current,
|
|
12803
|
+
independent: independence.length === 0,
|
|
12804
|
+
accepted: reasons.length === 0,
|
|
12805
|
+
reasons,
|
|
12806
|
+
verdict: row
|
|
12807
|
+
});
|
|
12808
|
+
}
|
|
12809
|
+
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
12810
|
+
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
12811
|
+
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
12812
|
+
const newestAccepted = verdicts.findLast((entry) => entry.accepted) ?? null;
|
|
12813
|
+
const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
12814
|
+
accepted: false,
|
|
12815
|
+
verdict: failing?.verdict ?? null,
|
|
12816
|
+
reasons: failing ? ["failing_verdict"] : newestCurrent?.reasons ?? ["no_verdict"]
|
|
12817
|
+
};
|
|
12818
|
+
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
12819
|
+
return {
|
|
12820
|
+
branch,
|
|
12821
|
+
head,
|
|
12822
|
+
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
12823
|
+
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
12824
|
+
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
12825
|
+
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
12826
|
+
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
12827
|
+
accepted,
|
|
12828
|
+
verdicts,
|
|
12829
|
+
authors: [...authors].toSorted()
|
|
12830
|
+
};
|
|
12831
|
+
}
|
|
12832
|
+
var OBSERVED_ONLY = new Set(["check", ...PR_ROW_TYPES]);
|
|
12833
|
+
|
|
12834
|
+
// packages/workit-core/src/core/policy/derive.ts
|
|
12835
|
+
var RULES = {
|
|
12836
|
+
test: "check:test",
|
|
12837
|
+
self: "verdict:self",
|
|
12838
|
+
nonAuthor: "verdict:non-author",
|
|
12839
|
+
verified: "verdict:verified",
|
|
12840
|
+
product: "decision:product",
|
|
12841
|
+
plan: "plan"
|
|
12842
|
+
};
|
|
12843
|
+
var derived2 = (spec) => ({
|
|
12844
|
+
...spec,
|
|
12845
|
+
id: requirementId({
|
|
12846
|
+
ruleId: spec.ruleId,
|
|
12847
|
+
scope: spec.scope,
|
|
12848
|
+
satisfaction: spec.satisfaction,
|
|
12849
|
+
before: spec.before,
|
|
12850
|
+
dependentAction: spec.dependentAction
|
|
12851
|
+
})
|
|
12852
|
+
});
|
|
12853
|
+
var sorted = (values) => [...new Set(values)].toSorted();
|
|
12854
|
+
var normalizedScope = (scope) => ({
|
|
12855
|
+
description: scope.description,
|
|
12856
|
+
paths: sorted(scope.paths),
|
|
12857
|
+
exclusions: sorted(scope.exclusions)
|
|
12858
|
+
});
|
|
12859
|
+
function deriveRequirements(judgment, scope, constraints, verification = "self") {
|
|
12860
|
+
const out = [];
|
|
12861
|
+
const base = { scope, dependentAction: null };
|
|
12862
|
+
if (judgment.behaviorChange)
|
|
12863
|
+
out.push(derived2({
|
|
12864
|
+
...base,
|
|
12865
|
+
ruleId: RULES.test,
|
|
12866
|
+
dimension: "testing",
|
|
12867
|
+
reason: "Behavior changes: the configured test check must pass on the final tree.",
|
|
12868
|
+
satisfaction: "Run `workit check test` on the final tree.",
|
|
12869
|
+
before: "close",
|
|
12870
|
+
acceptanceAllowed: false
|
|
12871
|
+
}));
|
|
12872
|
+
if (judgment.behaviorChange && judgment.riskTier === "normal")
|
|
12873
|
+
out.push(verification === "independent" ? derived2({
|
|
12874
|
+
...base,
|
|
12875
|
+
ruleId: RULES.nonAuthor,
|
|
12876
|
+
dimension: "review",
|
|
12877
|
+
reason: "This workspace requires independent verification: a session that did not author the change records the verdict.",
|
|
12878
|
+
satisfaction: "An independent session records a passing verdict: `workit ledger verdict verified --how <what it exercised>`.",
|
|
12879
|
+
before: "close",
|
|
12880
|
+
acceptanceAllowed: false
|
|
12881
|
+
}) : derived2({
|
|
12882
|
+
...base,
|
|
12883
|
+
ruleId: RULES.self,
|
|
12884
|
+
dimension: "review",
|
|
12885
|
+
reason: "A behavior change needs a recorded verdict; the author's own is allowed and is shown as self-reviewed, never verified.",
|
|
12886
|
+
satisfaction: "After an observed passing `workit check test`, record `workit ledger verdict tests-verified --self --how <what you ran>` (or have a verifier record one).",
|
|
12887
|
+
before: "close",
|
|
12888
|
+
acceptanceAllowed: false
|
|
12889
|
+
}));
|
|
12890
|
+
if (judgment.riskTier === "high")
|
|
12891
|
+
out.push(derived2({
|
|
12892
|
+
...base,
|
|
12893
|
+
ruleId: RULES.verified,
|
|
12894
|
+
dimension: "review",
|
|
12895
|
+
reason: "High risk: an independent verifier must exercise the live surface.",
|
|
12896
|
+
satisfaction: "An independent verifier runs the real surface and records `workit ledger verdict verified --kind live --how <what it exercised>`.",
|
|
12897
|
+
before: "close",
|
|
12898
|
+
acceptanceAllowed: false
|
|
12899
|
+
}));
|
|
12900
|
+
if (judgment.productChoiceOpen)
|
|
12901
|
+
out.push(derived2({
|
|
12902
|
+
...base,
|
|
12903
|
+
ruleId: RULES.product,
|
|
12904
|
+
dimension: "decisions",
|
|
12905
|
+
reason: "A product or preference choice is open; the user decides it before code is written.",
|
|
12906
|
+
satisfaction: 'Ask the user (with your recommendation), then record the answer: `workit ledger decision "<choice>" --why "<reason>"`.',
|
|
12907
|
+
before: "write",
|
|
12908
|
+
acceptanceAllowed: false
|
|
12909
|
+
}));
|
|
12910
|
+
if (judgment.needsPlan || judgment.riskTier === "high")
|
|
12911
|
+
out.push(derived2({
|
|
12912
|
+
...base,
|
|
12913
|
+
ruleId: RULES.plan,
|
|
12914
|
+
dimension: "artifacts",
|
|
12915
|
+
reason: judgment.needsPlan ? "The work needs a written plan or spec before implementation." : "High-risk work needs a written plan before implementation.",
|
|
12916
|
+
satisfaction: "Write the plan or spec (e.g. docs/plans/<slug>.md; docs and Markdown writes stay allowed), then record it: `workit policy assess --ref <path>`.",
|
|
12917
|
+
before: "write",
|
|
12918
|
+
acceptanceAllowed: true
|
|
12919
|
+
}));
|
|
12920
|
+
for (const constraint of constraints)
|
|
12921
|
+
for (const obligation of constraint.requires)
|
|
12922
|
+
out.push(derived2({
|
|
12923
|
+
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
12924
|
+
kind: obligation.kind,
|
|
12925
|
+
scope: normalizedScope(obligation.scope),
|
|
12926
|
+
refs: obligation.refs.map((ref) => JSON.stringify(ref)).toSorted(),
|
|
12927
|
+
method: obligation.method,
|
|
12928
|
+
before: obligation.before,
|
|
12929
|
+
dependentAction: obligation.dependentAction
|
|
12930
|
+
})}`,
|
|
12931
|
+
dimension: obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge"),
|
|
12932
|
+
scope: obligation.scope,
|
|
12933
|
+
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
12934
|
+
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
12935
|
+
before: obligation.before,
|
|
12936
|
+
dependentAction: obligation.dependentAction,
|
|
12937
|
+
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
12938
|
+
}));
|
|
12939
|
+
const seen = new Set;
|
|
12940
|
+
return out.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true));
|
|
12941
|
+
}
|
|
12942
|
+
var latestJudgment = (task) => task.judgments?.at(-1)?.data ?? null;
|
|
12943
|
+
var ledgerView = (root) => {
|
|
12944
|
+
let branch;
|
|
12945
|
+
let rows;
|
|
12946
|
+
return {
|
|
12947
|
+
branch: () => branch === undefined ? branch = currentBranch(root) : branch,
|
|
12948
|
+
rows: () => {
|
|
12949
|
+
if (rows === undefined) {
|
|
12950
|
+
const read = readLedger(root);
|
|
12951
|
+
rows = read.ok ? read.value.rows : [];
|
|
12952
|
+
}
|
|
12953
|
+
return rows;
|
|
12954
|
+
}
|
|
12955
|
+
};
|
|
12956
|
+
};
|
|
12957
|
+
var productDecision = (task, ledger) => {
|
|
12958
|
+
const decisions = task.decisions.filter((entry) => entry.data.revoked === null && (entry.data.response === "approved" || entry.data.response === "stated") && entry.data.purpose !== "limitation");
|
|
12959
|
+
if (decisions.length)
|
|
12960
|
+
return {
|
|
12961
|
+
met: true,
|
|
12962
|
+
reason: "the user's decision is recorded on the task",
|
|
12963
|
+
decisionIds: decisions.map((entry) => entry.id)
|
|
12964
|
+
};
|
|
12965
|
+
const since = Date.parse(task.createdAt);
|
|
12966
|
+
const branch = ledger.branch();
|
|
12967
|
+
const row = ledger.rows().findLast((item) => item.type === "decision" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
|
|
12968
|
+
return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
|
|
12969
|
+
met: false,
|
|
12970
|
+
reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or re-judge productChoiceOpen=false if it is not open)',
|
|
12971
|
+
decisionIds: []
|
|
12972
|
+
};
|
|
12973
|
+
};
|
|
12974
|
+
var planRecorded = (task, root) => {
|
|
12975
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync3(path10.resolve(root, ref.path)));
|
|
12976
|
+
if (cited)
|
|
12977
|
+
return {
|
|
12978
|
+
met: true,
|
|
12979
|
+
reason: `plan recorded: ${cited.kind === "file" ? cited.path : cited.kind === "external" ? cited.url : ""}`,
|
|
12980
|
+
decisionIds: []
|
|
12981
|
+
};
|
|
12982
|
+
const artifact = task.evidence.some((entry) => entry.data.kind === "artifact" && entry.data.result === "passed");
|
|
12983
|
+
return artifact ? { met: true, reason: "a passing artifact records the plan", decisionIds: [] } : {
|
|
12984
|
+
met: false,
|
|
12985
|
+
reason: "no plan recorded: write it (docs/ and Markdown writes stay allowed), then `workit policy assess --ref <path>`",
|
|
12986
|
+
decisionIds: []
|
|
12987
|
+
};
|
|
12988
|
+
};
|
|
12989
|
+
var verdict = (requirement, root, ledger) => {
|
|
12990
|
+
const branch = ledger.branch();
|
|
12991
|
+
if (!branch)
|
|
12992
|
+
return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
|
|
12993
|
+
const check = checkVerdicts(root, branch, ledger.rows());
|
|
12994
|
+
const failing = check.accepted.reasons.includes("failing_verdict");
|
|
12995
|
+
const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
|
|
12996
|
+
const met = !failing && (requirement.ruleId === RULES.verified ? check.verdicts.find((entry) => entry.accepted && entry.kind === "live" && String(entry.verdict.result) === "verified") : requirement.ruleId === RULES.nonAuthor ? check.verdicts.find((entry) => entry.accepted && strong(entry)) : check.verdicts.find((entry) => entry.current && strong(entry)));
|
|
12997
|
+
if (met)
|
|
12998
|
+
return {
|
|
12999
|
+
met: true,
|
|
13000
|
+
reason: `${met.accepted ? "accepted independent" : "self-reviewed (author's own)"} ${String(met.verdict.result)} verdict ${met.verdict.id}`,
|
|
13001
|
+
decisionIds: []
|
|
13002
|
+
};
|
|
13003
|
+
const why = failing ? "a current independent verdict fails" : check.verdicts.some((entry) => entry.current && entry.verdict.result === "type-check-only") ? "type-check-only does not prove a behavior change" : (check.accepted.reasons.join(", ") || "no_verdict").replace(/_/g, " ");
|
|
13004
|
+
const unblock = requirement.ruleId === RULES.self ? "record your own verdict after an observed `workit check test`: `workit ledger verdict tests-verified --self --how <what you ran>` (shown as self-reviewed), or have a verifier record one" : `an independent session records \`workit ledger verdict verified${requirement.ruleId === RULES.verified ? " --kind live" : ""} --how <what it exercised>\``;
|
|
13005
|
+
return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
|
|
13006
|
+
};
|
|
13007
|
+
function derivedRuleStatus(task, requirement, root, ledger) {
|
|
13008
|
+
switch (requirement.ruleId) {
|
|
13009
|
+
case RULES.product:
|
|
13010
|
+
return productDecision(task, ledger);
|
|
13011
|
+
case RULES.plan:
|
|
13012
|
+
return planRecorded(task, root);
|
|
13013
|
+
case RULES.self:
|
|
13014
|
+
case RULES.nonAuthor:
|
|
13015
|
+
case RULES.verified:
|
|
13016
|
+
return verdict(requirement, root, ledger);
|
|
13017
|
+
default:
|
|
13018
|
+
return null;
|
|
13019
|
+
}
|
|
13020
|
+
}
|
|
13021
|
+
function writeBlockers(task, root) {
|
|
13022
|
+
if (task.status === "closed" || !task.policy)
|
|
13023
|
+
return [];
|
|
13024
|
+
const ledger = ledgerView(root);
|
|
13025
|
+
return task.policy.requirements.flatMap((requirement) => {
|
|
13026
|
+
if (requirement.before !== "write")
|
|
13027
|
+
return [];
|
|
13028
|
+
const status = derivedRuleStatus(task, requirement, root, ledger);
|
|
13029
|
+
return status && !status.met ? [{ ruleId: requirement.ruleId, reason: status.reason }] : [];
|
|
13030
|
+
});
|
|
13031
|
+
}
|
|
13032
|
+
|
|
13033
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
13034
|
+
var digestBytes = (value) => createHash7("sha256").update(value).digest("hex");
|
|
13035
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path11.sep}`);
|
|
13036
|
+
var relative = (root, candidate) => path11.relative(root, candidate).split(path11.sep).join("/") || ".";
|
|
13037
|
+
var canonicalPath = (value) => {
|
|
13038
|
+
if (typeof value !== "string" || !value)
|
|
13039
|
+
return null;
|
|
13040
|
+
if (value.includes("\\") || value.split("/").includes(".."))
|
|
13041
|
+
return null;
|
|
13042
|
+
const normalized = path11.posix.normalize(value);
|
|
13043
|
+
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
13044
|
+
return null;
|
|
13045
|
+
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
13046
|
+
};
|
|
13047
|
+
var canonicalScope = (scope) => {
|
|
13048
|
+
const paths = scope.paths.length ? scope.paths : ["."];
|
|
13049
|
+
const normalizedPaths = paths.map(canonicalPath);
|
|
13050
|
+
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
13051
|
+
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
13052
|
+
return null;
|
|
13053
|
+
return {
|
|
13054
|
+
description: scope.description,
|
|
13055
|
+
paths: [...new Set(normalizedPaths)],
|
|
13056
|
+
exclusions: [...new Set(normalizedExclusions)]
|
|
13057
|
+
};
|
|
13058
|
+
};
|
|
13059
|
+
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
13060
|
+
var scopeMatches = (value, scope) => {
|
|
13061
|
+
const normalized = canonicalScope(scope);
|
|
13062
|
+
if (!normalized)
|
|
13063
|
+
return false;
|
|
13064
|
+
const paths = normalized.paths;
|
|
13065
|
+
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
13066
|
+
};
|
|
13067
|
+
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
13068
|
+
var compareCodeUnits2 = (left, right) => {
|
|
13069
|
+
const length = Math.min(left.length, right.length);
|
|
13070
|
+
for (let index = 0;index < length; index += 1) {
|
|
13071
|
+
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
13072
|
+
if (difference !== 0)
|
|
13073
|
+
return difference;
|
|
13074
|
+
}
|
|
13075
|
+
return left.length - right.length;
|
|
13076
|
+
};
|
|
13077
|
+
var gitPaths = (root) => {
|
|
13078
|
+
const result = spawnSync6("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
13079
|
+
cwd: root,
|
|
13080
|
+
encoding: "buffer"
|
|
13081
|
+
});
|
|
13082
|
+
if (result.status !== 0) {
|
|
13083
|
+
const stderr = result.stderr?.toString("utf8") ?? "";
|
|
13084
|
+
return {
|
|
13085
|
+
paths: [],
|
|
13086
|
+
uncertain: !/not a git repository/i.test(stderr),
|
|
13087
|
+
git: !/not a git repository/i.test(stderr)
|
|
13088
|
+
};
|
|
13089
|
+
}
|
|
13090
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")) : [];
|
|
13091
|
+
const stagedDeleted = spawnSync6("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
13092
|
+
if (stagedDeleted.status !== 0) {
|
|
13093
|
+
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
13094
|
+
if (!/not a git repository/i.test(stderr))
|
|
13095
|
+
return { paths, uncertain: true, git: true };
|
|
13096
|
+
} else if (stagedDeleted.stdout) {
|
|
13097
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")));
|
|
13098
|
+
}
|
|
13099
|
+
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
13100
|
+
};
|
|
13101
|
+
var walk = (root, directory, output) => {
|
|
13102
|
+
let names;
|
|
13103
|
+
try {
|
|
13104
|
+
names = fs11.readdirSync(directory);
|
|
13105
|
+
} catch {
|
|
13106
|
+
return true;
|
|
13107
|
+
}
|
|
13108
|
+
let uncertain = false;
|
|
13109
|
+
for (const name of names) {
|
|
13110
|
+
if (name === ".git" || name === ".workit")
|
|
13111
|
+
continue;
|
|
13112
|
+
const target = path11.join(directory, name);
|
|
13113
|
+
let stat;
|
|
13114
|
+
try {
|
|
13115
|
+
stat = fs11.lstatSync(target);
|
|
13116
|
+
} catch {
|
|
13117
|
+
uncertain = true;
|
|
13118
|
+
continue;
|
|
13119
|
+
}
|
|
13120
|
+
const item = relative(root, target);
|
|
13121
|
+
output.add(item);
|
|
13122
|
+
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
13123
|
+
uncertain = walk(root, target, output) || uncertain;
|
|
13124
|
+
}
|
|
13125
|
+
return uncertain;
|
|
13126
|
+
};
|
|
13127
|
+
var scopeRoots = (root, scope) => {
|
|
13128
|
+
const roots = [];
|
|
13129
|
+
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
13130
|
+
const target = path11.resolve(root, item);
|
|
13131
|
+
if (!inside(root, target))
|
|
13132
|
+
return failure2("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
13133
|
+
let ancestor = target;
|
|
13134
|
+
while (!fs11.existsSync(ancestor) && ancestor !== root)
|
|
13135
|
+
ancestor = path11.dirname(ancestor);
|
|
13136
|
+
try {
|
|
13137
|
+
if (!inside(root, fs11.realpathSync(ancestor)))
|
|
13138
|
+
return failure2("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
13139
|
+
} catch {
|
|
13140
|
+
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
13141
|
+
}
|
|
13142
|
+
roots.push(target);
|
|
13143
|
+
}
|
|
13144
|
+
return success(null, null, roots);
|
|
13145
|
+
};
|
|
13146
|
+
function captureCandidate(root, scope, environment = []) {
|
|
13147
|
+
let checkout;
|
|
12277
13148
|
try {
|
|
12278
|
-
checkout =
|
|
13149
|
+
checkout = fs11.realpathSync(root);
|
|
12279
13150
|
} catch {
|
|
12280
13151
|
return failure2("invalid_input", "candidate root does not exist");
|
|
12281
13152
|
}
|
|
@@ -12293,11 +13164,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12293
13164
|
if (item === ".git" || item === ".workit")
|
|
12294
13165
|
continue;
|
|
12295
13166
|
names.add(item);
|
|
12296
|
-
if (!
|
|
13167
|
+
if (!fs11.existsSync(target))
|
|
12297
13168
|
continue;
|
|
12298
13169
|
let stat;
|
|
12299
13170
|
try {
|
|
12300
|
-
stat =
|
|
13171
|
+
stat = fs11.lstatSync(target);
|
|
12301
13172
|
} catch {
|
|
12302
13173
|
uncertain = true;
|
|
12303
13174
|
continue;
|
|
@@ -12313,17 +13184,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12313
13184
|
for (const item of [...names].toSorted()) {
|
|
12314
13185
|
if (!scopeMatches(item, normalizedScope))
|
|
12315
13186
|
continue;
|
|
12316
|
-
const target =
|
|
13187
|
+
const target = path11.join(checkout, item);
|
|
12317
13188
|
try {
|
|
12318
|
-
const stat =
|
|
13189
|
+
const stat = fs11.lstatSync(target);
|
|
12319
13190
|
if (stat.isSymbolicLink()) {
|
|
12320
|
-
const link =
|
|
13191
|
+
const link = fs11.readlinkSync(target);
|
|
12321
13192
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
12322
13193
|
} else if (stat.isFile()) {
|
|
12323
13194
|
files.push({
|
|
12324
13195
|
path: item,
|
|
12325
13196
|
kind: "file",
|
|
12326
|
-
digest: digestBytes(
|
|
13197
|
+
digest: digestBytes(fs11.readFileSync(target)),
|
|
12327
13198
|
executable: (stat.mode & 73) !== 0
|
|
12328
13199
|
});
|
|
12329
13200
|
} else if (!stat.isDirectory()) {
|
|
@@ -12348,7 +13219,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12348
13219
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
12349
13220
|
if (values.some(({ value }) => value === null))
|
|
12350
13221
|
completeness = "uncertain";
|
|
12351
|
-
const headResult =
|
|
13222
|
+
const headResult = spawnSync6("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
12352
13223
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
12353
13224
|
completeness = "uncertain";
|
|
12354
13225
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -12488,7 +13359,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
12488
13359
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
12489
13360
|
let root;
|
|
12490
13361
|
try {
|
|
12491
|
-
root =
|
|
13362
|
+
root = fs11.realpathSync(checkoutRoot);
|
|
12492
13363
|
} catch {
|
|
12493
13364
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
12494
13365
|
}
|
|
@@ -12497,23 +13368,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
12497
13368
|
continue;
|
|
12498
13369
|
if (!reference.digest)
|
|
12499
13370
|
return failure2("invalid_input", "document references require a byte digest");
|
|
12500
|
-
const target =
|
|
12501
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
13371
|
+
const target = path11.resolve(root, reference.path);
|
|
13372
|
+
if (target !== root && !target.startsWith(`${root}${path11.sep}`))
|
|
12502
13373
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
12503
13374
|
fields: [
|
|
12504
13375
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
12505
13376
|
]
|
|
12506
13377
|
});
|
|
12507
13378
|
try {
|
|
12508
|
-
const below =
|
|
13379
|
+
const below = path11.relative(root, target);
|
|
12509
13380
|
let current = root;
|
|
12510
|
-
for (const segment of below.split(
|
|
13381
|
+
for (const segment of below.split(path11.sep)) {
|
|
12511
13382
|
if (!segment)
|
|
12512
13383
|
continue;
|
|
12513
|
-
current =
|
|
12514
|
-
const stat =
|
|
12515
|
-
const real =
|
|
12516
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
13384
|
+
current = path11.join(current, segment);
|
|
13385
|
+
const stat = fs11.lstatSync(current);
|
|
13386
|
+
const real = fs11.realpathSync(current);
|
|
13387
|
+
if (real !== root && !real.startsWith(`${root}${path11.sep}`))
|
|
12517
13388
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
12518
13389
|
fields: [
|
|
12519
13390
|
{
|
|
@@ -12529,7 +13400,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
12529
13400
|
if (current !== target && !stat.isDirectory())
|
|
12530
13401
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
12531
13402
|
}
|
|
12532
|
-
if (digestBytes(
|
|
13403
|
+
if (digestBytes(fs11.readFileSync(target)) !== reference.digest)
|
|
12533
13404
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
12534
13405
|
} catch {
|
|
12535
13406
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -12614,7 +13485,35 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
12614
13485
|
decisionIds: [],
|
|
12615
13486
|
reason: "stored policy version is unsupported; reassessment is required"
|
|
12616
13487
|
}));
|
|
13488
|
+
const ledger = ledgerView(checkoutRoot);
|
|
12617
13489
|
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
13490
|
+
const derived = derivedRuleStatus(task, requirement, checkoutRoot, ledger);
|
|
13491
|
+
if (derived) {
|
|
13492
|
+
if (derived.met)
|
|
13493
|
+
return {
|
|
13494
|
+
requirementId: requirement.id,
|
|
13495
|
+
status: "satisfied",
|
|
13496
|
+
evidenceIds: [],
|
|
13497
|
+
decisionIds: derived.decisionIds,
|
|
13498
|
+
reason: derived.reason
|
|
13499
|
+
};
|
|
13500
|
+
const waived = requirement.acceptanceAllowed ? applicableDecision(task, workspace, requirement, checkoutRoot) : [];
|
|
13501
|
+
if (waived.length)
|
|
13502
|
+
return {
|
|
13503
|
+
requirementId: requirement.id,
|
|
13504
|
+
status: "accepted_limitation",
|
|
13505
|
+
evidenceIds: [],
|
|
13506
|
+
decisionIds: waived.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
|
|
13507
|
+
reason: "an applicable approved limitation permits the missing evidence"
|
|
13508
|
+
};
|
|
13509
|
+
return {
|
|
13510
|
+
requirementId: requirement.id,
|
|
13511
|
+
status: "unsatisfied",
|
|
13512
|
+
evidenceIds: [],
|
|
13513
|
+
decisionIds: [],
|
|
13514
|
+
reason: derived.reason
|
|
13515
|
+
};
|
|
13516
|
+
}
|
|
12618
13517
|
const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
|
|
12619
13518
|
if (gate?.satisfied.length)
|
|
12620
13519
|
return {
|
|
@@ -12869,10 +13768,10 @@ Autonomy contract:
|
|
|
12869
13768
|
inferred, or guess. Prose is a note: only a configured check the CLI ran
|
|
12870
13769
|
(\`workit check <name>\`) satisfies a gate; an ad-hoc
|
|
12871
13770
|
\`workit check -- <cmd>\` never does.
|
|
12872
|
-
- Author is not verifier:
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
13771
|
+
- Author is not verifier: your own verdict is \`--self\` (after an observed
|
|
13772
|
+
\`workit check test\`), shown as self-reviewed, never verified. High risk,
|
|
13773
|
+
\`verification: independent\` and merge need a verifier with its own
|
|
13774
|
+
session (\`WORKIT_SESSION_ID=<lead>-v<n>\`; Claude Code subagents get one).
|
|
12876
13775
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
12877
13776
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
12878
13777
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
@@ -12885,7 +13784,9 @@ PATH, \`npx -y @brainervirus/workit-cli\`). \`busy\` is retryable
|
|
|
12885
13784
|
The task families (task, policy, evidence, finding, decision, worker, state)
|
|
12886
13785
|
are optional continuity for tracked work; each branch has one implicit task
|
|
12887
13786
|
that the first recording creates, and a solo edit needs no task. A decision
|
|
12888
|
-
records a user choice; it never authorizes an effect.
|
|
13787
|
+
records a user choice; it never authorizes an effect. Judge work once with
|
|
13788
|
+
\`workit policy assess --judge risk= behavior= product-choice= plan=\`; an
|
|
13789
|
+
open choice or missing plan blocks edits until recorded.
|
|
12889
13790
|
Omit revisions; a revision_conflict means one you passed is stale, so re-read.
|
|
12890
13791
|
Across repositories, bind each item to its checkout, branch and endpoint,
|
|
12891
13792
|
resolve competing targets before a mutation, and reconcile an uncertain
|
|
@@ -13023,7 +13924,7 @@ var exportDigest = (bundle) => sha256({
|
|
|
13023
13924
|
task: bundle.task
|
|
13024
13925
|
});
|
|
13025
13926
|
// packages/workit-core/src/core/branch.ts
|
|
13026
|
-
import { createHash as
|
|
13927
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
13027
13928
|
|
|
13028
13929
|
// packages/workit-core/src/core/git.ts
|
|
13029
13930
|
import { execFileSync } from "node:child_process";
|
|
@@ -13097,14 +13998,14 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
13097
13998
|
import {
|
|
13098
13999
|
copyFileSync as copyFileSync2,
|
|
13099
14000
|
cpSync as cpSync2,
|
|
13100
|
-
existsSync as
|
|
14001
|
+
existsSync as existsSync5,
|
|
13101
14002
|
mkdirSync as mkdirSync2,
|
|
13102
14003
|
readFileSync as readFileSync3,
|
|
13103
14004
|
readdirSync as readdirSync3,
|
|
13104
14005
|
writeFileSync as writeFileSync2
|
|
13105
14006
|
} from "node:fs";
|
|
13106
14007
|
import os2 from "node:os";
|
|
13107
|
-
import
|
|
14008
|
+
import path12 from "node:path";
|
|
13108
14009
|
|
|
13109
14010
|
// packages/workit-core/src/core/boundary.ts
|
|
13110
14011
|
var EVENT = {
|
|
@@ -13148,7 +14049,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
13148
14049
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
13149
14050
|
};
|
|
13150
14051
|
};
|
|
13151
|
-
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
14052
|
+
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path12.join(process.env.XDG_CONFIG_HOME || path12.join(os2.homedir(), ".config"), "workit");
|
|
13152
14053
|
var migratedDir = null;
|
|
13153
14054
|
var migrationFailed = false;
|
|
13154
14055
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -13158,12 +14059,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
13158
14059
|
migratedDir = dir;
|
|
13159
14060
|
return dir;
|
|
13160
14061
|
}
|
|
13161
|
-
const legacy =
|
|
13162
|
-
if (!
|
|
14062
|
+
const legacy = path12.join(process.env.XDG_CONFIG_HOME || path12.join(os2.homedir(), ".config"), "workflow-toolkit");
|
|
14063
|
+
if (!existsSync5(legacy)) {
|
|
13163
14064
|
migratedDir = dir;
|
|
13164
14065
|
return dir;
|
|
13165
14066
|
}
|
|
13166
|
-
if (!migrationFailed &&
|
|
14067
|
+
if (!migrationFailed && existsSync5(dir)) {
|
|
13167
14068
|
migratedDir = dir;
|
|
13168
14069
|
return dir;
|
|
13169
14070
|
}
|
|
@@ -13171,9 +14072,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
13171
14072
|
mkdirSync2(dir, { recursive: true });
|
|
13172
14073
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
13173
14074
|
for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
|
|
13174
|
-
const src =
|
|
13175
|
-
const dest =
|
|
13176
|
-
if (
|
|
14075
|
+
const src = path12.join(legacy, entry.name);
|
|
14076
|
+
const dest = path12.join(dir, entry.name);
|
|
14077
|
+
if (existsSync5(dest))
|
|
13177
14078
|
continue;
|
|
13178
14079
|
try {
|
|
13179
14080
|
if (entry.isDirectory())
|
|
@@ -13244,7 +14145,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
13244
14145
|
};
|
|
13245
14146
|
};
|
|
13246
14147
|
var readConfigTyped = (dir) => {
|
|
13247
|
-
const file =
|
|
14148
|
+
const file = path12.join(dir ?? configDir(), "config.json");
|
|
13248
14149
|
return parseConfigResult(readSafe(file), file);
|
|
13249
14150
|
};
|
|
13250
14151
|
var readConfig = () => {
|
|
@@ -13253,6 +14154,8 @@ var readConfig = () => {
|
|
|
13253
14154
|
throw new Error(result.error);
|
|
13254
14155
|
return result.config ?? DEFAULTS;
|
|
13255
14156
|
};
|
|
14157
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path12.join(configDir(), "vcs.json");
|
|
14158
|
+
var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
|
|
13256
14159
|
var resolveBranchPolicy = (config, workspace) => {
|
|
13257
14160
|
const wp = workspace?.branchPolicy ?? {};
|
|
13258
14161
|
const preset = Object.hasOwn(PRESETS, wp.preset) ? wp.preset : config.branchPolicy?.preset ?? "gitflow";
|
|
@@ -13260,7 +14163,7 @@ var resolveBranchPolicy = (config, workspace) => {
|
|
|
13260
14163
|
allowed: wp.allowed,
|
|
13261
14164
|
protectedNames: wp.protected
|
|
13262
14165
|
}, config);
|
|
13263
|
-
const allowed = merged.allowed.map(
|
|
14166
|
+
const allowed = merged.allowed.map(branchGlobPattern);
|
|
13264
14167
|
return {
|
|
13265
14168
|
preset,
|
|
13266
14169
|
allowed,
|
|
@@ -13289,8 +14192,9 @@ var resolveCommitPolicy = (config, workspace) => {
|
|
|
13289
14192
|
};
|
|
13290
14193
|
|
|
13291
14194
|
// packages/workit-core/src/core/workspaces.ts
|
|
14195
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
13292
14196
|
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
13293
|
-
import
|
|
14197
|
+
import path13 from "node:path";
|
|
13294
14198
|
var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
|
|
13295
14199
|
var branchPreset = custom((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
|
|
13296
14200
|
var commitPreset = custom((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
|
|
@@ -13324,26 +14228,31 @@ var versionSourceSchema = discriminatedUnion("kind", [
|
|
|
13324
14228
|
object({ kind: literal("manual") }).strict()
|
|
13325
14229
|
]);
|
|
13326
14230
|
var releaseTrackSchema = object({
|
|
13327
|
-
strategy: branchPreset,
|
|
14231
|
+
strategy: branchPreset.optional(),
|
|
13328
14232
|
productionBranch: nonBlank,
|
|
13329
14233
|
integrationBranch: nonBlank,
|
|
13330
|
-
naming: object({
|
|
13331
|
-
|
|
13332
|
-
|
|
13333
|
-
|
|
13334
|
-
|
|
13335
|
-
|
|
13336
|
-
|
|
13337
|
-
|
|
14234
|
+
naming: object({
|
|
14235
|
+
feature: nonBlank.optional(),
|
|
14236
|
+
release: nonBlank.optional(),
|
|
14237
|
+
hotfix: nonBlank.optional()
|
|
14238
|
+
}).passthrough().optional(),
|
|
14239
|
+
baseBranch: nonBlank.optional(),
|
|
14240
|
+
mergeBackBranches: array(nonBlank).optional(),
|
|
14241
|
+
pullRequestTarget: nonBlank.optional(),
|
|
14242
|
+
tagNamespace: string2().optional(),
|
|
14243
|
+
versionSource: versionSourceSchema.optional(),
|
|
14244
|
+
requiredChecks: array(nonBlank).optional(),
|
|
14245
|
+
critical: array(nonBlank).optional()
|
|
14246
|
+
}).passthrough().superRefine((track, context) => {
|
|
13338
14247
|
for (const [field, values] of [
|
|
13339
|
-
["mergeBackBranches", track.mergeBackBranches],
|
|
13340
|
-
["requiredChecks", track.requiredChecks]
|
|
14248
|
+
["mergeBackBranches", track.mergeBackBranches ?? []],
|
|
14249
|
+
["requiredChecks", track.requiredChecks ?? []]
|
|
13341
14250
|
]) {
|
|
13342
14251
|
if (new Set(values).size !== values.length) {
|
|
13343
14252
|
context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
|
|
13344
14253
|
}
|
|
13345
14254
|
}
|
|
13346
|
-
if (track.versionSource
|
|
14255
|
+
if (track.versionSource?.kind === "package-json" && (path13.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
|
|
13347
14256
|
context.addIssue({
|
|
13348
14257
|
code: "custom",
|
|
13349
14258
|
path: ["versionSource", "path"],
|
|
@@ -13362,6 +14271,7 @@ var workspaceConfigSchema = object({
|
|
|
13362
14271
|
}).passthrough().optional(),
|
|
13363
14272
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
13364
14273
|
defaultEndpoint: _enum(["commit", "pr"]).optional(),
|
|
14274
|
+
verification: _enum(["self", "independent"]).optional(),
|
|
13365
14275
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
13366
14276
|
kind,
|
|
13367
14277
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -13402,6 +14312,72 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
|
|
|
13402
14312
|
names.add(workspace.name);
|
|
13403
14313
|
}
|
|
13404
14314
|
});
|
|
14315
|
+
var workspacesRevision = (contents) => createHash8("sha256").update(contents ?? "<missing>").digest("hex");
|
|
14316
|
+
var validateWorkspacesDocument = (parsed, file) => {
|
|
14317
|
+
if (!isConfigObject(parsed)) {
|
|
14318
|
+
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
14319
|
+
}
|
|
14320
|
+
const validated = workspacesFileSchema.safeParse(parsed, { reportInput: true });
|
|
14321
|
+
if (!validated.success) {
|
|
14322
|
+
const detail = validated.error.issues.map((issue) => {
|
|
14323
|
+
const field = String(issue.path[issue.path.length - 1] ?? "");
|
|
14324
|
+
const attempted = ["provider", "preset", "strategy"].includes(field) && typeof issue.input === "string" ? ` (${JSON.stringify(issue.input)})` : "";
|
|
14325
|
+
return `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}${attempted}`;
|
|
14326
|
+
}).join("; ");
|
|
14327
|
+
return {
|
|
14328
|
+
status: "invalid",
|
|
14329
|
+
path: file,
|
|
14330
|
+
entries: [],
|
|
14331
|
+
error: `${file} has invalid workspace configuration: ${detail}`
|
|
14332
|
+
};
|
|
14333
|
+
}
|
|
14334
|
+
for (const [index, workspace] of (validated.data.workspaces ?? []).entries()) {
|
|
14335
|
+
const glob = validateWorkspaceGlob(workspace.glob);
|
|
14336
|
+
if (!glob.ok) {
|
|
14337
|
+
return {
|
|
14338
|
+
status: "invalid",
|
|
14339
|
+
path: file,
|
|
14340
|
+
entries: [],
|
|
14341
|
+
error: `${file} workspaces.${index}.glob: ${glob.error}`
|
|
14342
|
+
};
|
|
14343
|
+
}
|
|
14344
|
+
}
|
|
14345
|
+
return {
|
|
14346
|
+
status: "valid",
|
|
14347
|
+
path: file,
|
|
14348
|
+
entries: validated.data.workspaces ?? [],
|
|
14349
|
+
document: parsed
|
|
14350
|
+
};
|
|
14351
|
+
};
|
|
14352
|
+
var readWorkspacesResult = (dir = configDir()) => {
|
|
14353
|
+
const file = path13.join(dir, "workspaces.json");
|
|
14354
|
+
let raw;
|
|
14355
|
+
try {
|
|
14356
|
+
raw = readFileSync4(file, "utf8");
|
|
14357
|
+
} catch (error) {
|
|
14358
|
+
if (error.code === "ENOENT")
|
|
14359
|
+
return { status: "missing", path: file, entries: [], revision: workspacesRevision(null) };
|
|
14360
|
+
return {
|
|
14361
|
+
status: "malformed",
|
|
14362
|
+
path: file,
|
|
14363
|
+
entries: [],
|
|
14364
|
+
error: `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
|
|
14365
|
+
};
|
|
14366
|
+
}
|
|
14367
|
+
let parsed;
|
|
14368
|
+
try {
|
|
14369
|
+
parsed = JSON.parse(raw);
|
|
14370
|
+
} catch {
|
|
14371
|
+
return {
|
|
14372
|
+
status: "malformed",
|
|
14373
|
+
path: file,
|
|
14374
|
+
entries: [],
|
|
14375
|
+
revision: workspacesRevision(raw),
|
|
14376
|
+
error: `${file} is not valid JSON`
|
|
14377
|
+
};
|
|
14378
|
+
}
|
|
14379
|
+
return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
|
|
14380
|
+
};
|
|
13405
14381
|
var globToRegExp = (glob) => {
|
|
13406
14382
|
let out = "";
|
|
13407
14383
|
for (let i = 0;i < glob.length; i++) {
|
|
@@ -13499,6 +14475,30 @@ var selectWorkspaceMatch = (matches, cwd, workspaceName) => {
|
|
|
13499
14475
|
}
|
|
13500
14476
|
return mostSpecific[0];
|
|
13501
14477
|
};
|
|
14478
|
+
var resolveWorkspaceFromEntries = (cwd, entries, workspaceName) => {
|
|
14479
|
+
const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
|
|
14480
|
+
const matches = [];
|
|
14481
|
+
for (const ws of entries) {
|
|
14482
|
+
const glob = ws.glob.replaceAll("\\", "/");
|
|
14483
|
+
const canonical = canonicalGlob(glob);
|
|
14484
|
+
let matched = false;
|
|
14485
|
+
for (const target of targets) {
|
|
14486
|
+
if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
|
|
14487
|
+
matched = true;
|
|
14488
|
+
break;
|
|
14489
|
+
}
|
|
14490
|
+
}
|
|
14491
|
+
if (matched)
|
|
14492
|
+
matches.push(ws);
|
|
14493
|
+
}
|
|
14494
|
+
return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
|
|
14495
|
+
};
|
|
14496
|
+
var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
|
|
14497
|
+
const result = readWorkspacesResult(dir);
|
|
14498
|
+
if (result.status === "malformed" || result.status === "invalid")
|
|
14499
|
+
throw new Error(result.error);
|
|
14500
|
+
return resolveWorkspaceFromEntries(cwd, result.entries, workspaceName);
|
|
14501
|
+
};
|
|
13502
14502
|
var runtimeWorkspacePolicySchema = (kind) => {
|
|
13503
14503
|
const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
|
|
13504
14504
|
const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object({
|
|
@@ -13520,8 +14520,8 @@ var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd,
|
|
|
13520
14520
|
var runtimeWorkspaceIndexSchema = object({
|
|
13521
14521
|
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
13522
14522
|
}).passthrough();
|
|
13523
|
-
var
|
|
13524
|
-
const file =
|
|
14523
|
+
var matchRuntimeWorkspace = (cwd) => {
|
|
14524
|
+
const file = path13.join(configDir(), "workspaces.json");
|
|
13525
14525
|
let raw;
|
|
13526
14526
|
try {
|
|
13527
14527
|
raw = readFileSync4(file, "utf8");
|
|
@@ -13555,8 +14555,17 @@ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
|
13555
14555
|
});
|
|
13556
14556
|
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
13557
14557
|
const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
|
|
13558
|
-
|
|
14558
|
+
return selected ? { file, selected } : null;
|
|
14559
|
+
};
|
|
14560
|
+
var resolveRuntimeReleaseTracks = (cwd) => {
|
|
14561
|
+
const matched = matchRuntimeWorkspace(cwd);
|
|
14562
|
+
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
14563
|
+
};
|
|
14564
|
+
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
14565
|
+
const matched = matchRuntimeWorkspace(cwd);
|
|
14566
|
+
if (!matched)
|
|
13559
14567
|
return null;
|
|
14568
|
+
const { file, selected } = matched;
|
|
13560
14569
|
const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
|
|
13561
14570
|
if (!policy.success) {
|
|
13562
14571
|
const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
|
|
@@ -13581,16 +14590,424 @@ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
|
|
|
13581
14590
|
policy: profilePolicy ?? workspacePolicy,
|
|
13582
14591
|
source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
|
|
13583
14592
|
};
|
|
13584
|
-
};
|
|
14593
|
+
};
|
|
14594
|
+
function workspaceDefaultTarget(cwd, globalDefault) {
|
|
14595
|
+
const ws = resolveRuntimeWorkspaceVcs(cwd);
|
|
14596
|
+
const wp = ws?.branchPolicy ?? {};
|
|
14597
|
+
const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
|
|
14598
|
+
const selected = resolveRuntimeWorkspacePolicy(cwd, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
14599
|
+
const policyDefault = resolveBranchPolicy(readConfig(), selected.policy ? { branchPolicy: selected.policy } : null).defaultTargetBranch;
|
|
14600
|
+
return String(ws?.vcs?.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : globalDefault ?? policyDefault) ?? "develop");
|
|
14601
|
+
}
|
|
14602
|
+
|
|
14603
|
+
// packages/workit-core/src/core/vcs-config.ts
|
|
14604
|
+
import fs12 from "node:fs";
|
|
14605
|
+
import path14 from "node:path";
|
|
14606
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
14607
|
+
|
|
14608
|
+
// packages/workit-core/src/core/release-tracks.ts
|
|
14609
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
14610
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
14611
|
+
class ReleaseTrackError extends Error {
|
|
14612
|
+
}
|
|
14613
|
+
var KNOWN_FIELDS = new Set([
|
|
14614
|
+
"strategy",
|
|
14615
|
+
"productionBranch",
|
|
14616
|
+
"integrationBranch",
|
|
14617
|
+
"naming",
|
|
14618
|
+
"baseBranch",
|
|
14619
|
+
"mergeBackBranches",
|
|
14620
|
+
"pullRequestTarget",
|
|
14621
|
+
"tagNamespace",
|
|
14622
|
+
"versionSource",
|
|
14623
|
+
"requiredChecks",
|
|
14624
|
+
"critical"
|
|
14625
|
+
]);
|
|
14626
|
+
var BRANCH_FIELDS = ["productionBranch", "integrationBranch", "baseBranch", "pullRequestTarget"];
|
|
14627
|
+
var PLACEHOLDER = /\{[^}]*\}/gu;
|
|
14628
|
+
var HAS_PLACEHOLDER = /\{[^}]*\}/u;
|
|
14629
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14630
|
+
var branchName = (value) => typeof value === "string" && value.trim() && !value.trim().startsWith("-") ? value.trim() : null;
|
|
14631
|
+
var namedBranches = (entry) => [
|
|
14632
|
+
...BRANCH_FIELDS.map((field) => branchName(entry[field])),
|
|
14633
|
+
...Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : []
|
|
14634
|
+
].filter((name) => name !== null);
|
|
14635
|
+
var literalOf = (template) => template.replace(PLACEHOLDER, "").replace(/[*/]/gu, "").trim();
|
|
14636
|
+
function readReleaseTracks(raw) {
|
|
14637
|
+
const out = { tracks: [], issues: [], error: null, protectedBranches: [] };
|
|
14638
|
+
if (raw === undefined || raw === null)
|
|
14639
|
+
return out;
|
|
14640
|
+
if (!isRecord3(raw)) {
|
|
14641
|
+
out.issues.push("releaseTracks is not an object; ignored");
|
|
14642
|
+
return out;
|
|
14643
|
+
}
|
|
14644
|
+
const fatal = [];
|
|
14645
|
+
const protectedNames = new Set;
|
|
14646
|
+
for (const [name, entry] of Object.entries(raw).toSorted(([a], [b]) => a < b ? -1 : 1)) {
|
|
14647
|
+
const label = `release track ${JSON.stringify(name)}`;
|
|
14648
|
+
if (!isRecord3(entry)) {
|
|
14649
|
+
out.issues.push(`${label} is not an object; ignored`);
|
|
14650
|
+
continue;
|
|
14651
|
+
}
|
|
14652
|
+
for (const branch of namedBranches(entry))
|
|
14653
|
+
protectedNames.add(branch);
|
|
14654
|
+
if (!name.trim()) {
|
|
14655
|
+
out.issues.push("a release track with a blank name was ignored");
|
|
14656
|
+
continue;
|
|
14657
|
+
}
|
|
14658
|
+
let critical = [];
|
|
14659
|
+
if (Array.isArray(entry.critical))
|
|
14660
|
+
critical = entry.critical.filter((item) => typeof item === "string");
|
|
14661
|
+
else if (entry.critical !== undefined)
|
|
14662
|
+
out.issues.push(`${label} critical: must be a list of field names; ignored`);
|
|
14663
|
+
const isCritical = (field) => critical.some((item) => field === item || field.startsWith(`${item}.`));
|
|
14664
|
+
const problem = (field, message) => {
|
|
14665
|
+
if (isCritical(field))
|
|
14666
|
+
fatal.push(`${label} ${field}: ${message}`);
|
|
14667
|
+
else
|
|
14668
|
+
out.issues.push(`${label} ${field}: ${message}`);
|
|
14669
|
+
};
|
|
14670
|
+
for (const key of Object.keys(entry))
|
|
14671
|
+
if (!KNOWN_FIELDS.has(key))
|
|
14672
|
+
problem(key, "unknown field, ignored");
|
|
14673
|
+
const productionBranch = branchName(entry.productionBranch);
|
|
14674
|
+
const integrationBranch = branchName(entry.integrationBranch);
|
|
14675
|
+
if (!productionBranch)
|
|
14676
|
+
problem("productionBranch", "missing or not a branch name");
|
|
14677
|
+
if (!integrationBranch)
|
|
14678
|
+
problem("integrationBranch", "missing or not a branch name");
|
|
14679
|
+
const optionalBranch = (field) => {
|
|
14680
|
+
if (entry[field] === undefined)
|
|
14681
|
+
return null;
|
|
14682
|
+
const value = branchName(entry[field]);
|
|
14683
|
+
if (!value)
|
|
14684
|
+
problem(field, "not a branch name; using the integration branch");
|
|
14685
|
+
return value;
|
|
14686
|
+
};
|
|
14687
|
+
const baseBranch = optionalBranch("baseBranch");
|
|
14688
|
+
const pullRequestTarget = optionalBranch("pullRequestTarget");
|
|
14689
|
+
let mergeBackBranches = [];
|
|
14690
|
+
if (entry.mergeBackBranches !== undefined) {
|
|
14691
|
+
const list = Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : [null];
|
|
14692
|
+
if (list.some((item) => item === null))
|
|
14693
|
+
problem("mergeBackBranches", "must be a list of branch names; ignored");
|
|
14694
|
+
else
|
|
14695
|
+
mergeBackBranches = [...new Set(list)];
|
|
14696
|
+
}
|
|
14697
|
+
const naming = {};
|
|
14698
|
+
if (entry.naming !== undefined) {
|
|
14699
|
+
if (!isRecord3(entry.naming))
|
|
14700
|
+
problem("naming", "not an object; ignored");
|
|
14701
|
+
else
|
|
14702
|
+
for (const kind of ["feature", "release", "hotfix"]) {
|
|
14703
|
+
const template = entry.naming[kind];
|
|
14704
|
+
if (template === undefined)
|
|
14705
|
+
continue;
|
|
14706
|
+
if (typeof template !== "string" || !template.trim())
|
|
14707
|
+
problem(`naming.${kind}`, "not a template string; ignored");
|
|
14708
|
+
else if (!literalOf(template))
|
|
14709
|
+
problem(`naming.${kind}`, "needs literal text besides placeholders; ignored");
|
|
14710
|
+
else
|
|
14711
|
+
naming[kind] = template.trim();
|
|
14712
|
+
}
|
|
14713
|
+
}
|
|
14714
|
+
if (!productionBranch || !integrationBranch) {
|
|
14715
|
+
out.issues.push(`${label} was skipped: it needs a productionBranch and integrationBranch`);
|
|
14716
|
+
continue;
|
|
14717
|
+
}
|
|
14718
|
+
out.tracks.push({
|
|
14719
|
+
name,
|
|
14720
|
+
productionBranch,
|
|
14721
|
+
integrationBranch,
|
|
14722
|
+
baseBranch: baseBranch ?? integrationBranch,
|
|
14723
|
+
pullRequestTarget: pullRequestTarget ?? integrationBranch,
|
|
14724
|
+
mergeBackBranches,
|
|
14725
|
+
naming
|
|
14726
|
+
});
|
|
14727
|
+
}
|
|
14728
|
+
out.protectedBranches = [...protectedNames];
|
|
14729
|
+
if (fatal.length)
|
|
14730
|
+
out.error = `workspace releaseTracks cannot be read by this Workit (${fatal.join("; ")}); upgrade Workit or fix the track`;
|
|
14731
|
+
return out;
|
|
14732
|
+
}
|
|
14733
|
+
var trackBranches = (tracks) => [
|
|
14734
|
+
...new Set(tracks.flatMap((track) => [
|
|
14735
|
+
track.productionBranch,
|
|
14736
|
+
track.integrationBranch,
|
|
14737
|
+
track.baseBranch,
|
|
14738
|
+
track.pullRequestTarget,
|
|
14739
|
+
...track.mergeBackBranches
|
|
14740
|
+
]))
|
|
14741
|
+
];
|
|
14742
|
+
var namingGlob = (template) => HAS_PLACEHOLDER.test(template) ? template.replace(PLACEHOLDER, "*") : `${template.replace(/\/?\*?$/u, "")}/*`;
|
|
14743
|
+
var IMPLICIT = "";
|
|
14744
|
+
var ownedBy = (tracks, branch) => tracks.filter((track) => [track.integrationBranch, track.productionBranch, track.baseBranch].includes(branch));
|
|
14745
|
+
var shortRef = (ref, remotes) => {
|
|
14746
|
+
const bare = ref.replace(/^refs\/heads\//u, "").replace(/^refs\/remotes\/[^/]+\//u, "");
|
|
14747
|
+
const remote = remotes.find((name) => bare.startsWith(`${name}/`));
|
|
14748
|
+
return remote ? bare.slice(remote.length + 1) : bare;
|
|
14749
|
+
};
|
|
14750
|
+
var MAX_RECORDED_HOPS = 10;
|
|
14751
|
+
function resolveReleaseTrack(input) {
|
|
14752
|
+
const tracks = [...input.tracks];
|
|
14753
|
+
const issues = [...input.issues ?? []];
|
|
14754
|
+
const requested = input.requested?.trim();
|
|
14755
|
+
if (tracks.length === 0) {
|
|
14756
|
+
if (requested)
|
|
14757
|
+
return {
|
|
14758
|
+
status: "invalid",
|
|
14759
|
+
error: `no release tracks configured for this workspace; --track ${requested} cannot apply`,
|
|
14760
|
+
issues
|
|
14761
|
+
};
|
|
14762
|
+
return { status: "none", issues };
|
|
14763
|
+
}
|
|
14764
|
+
const names = tracks.map((track) => track.name).join(", ");
|
|
14765
|
+
let envProblem = null;
|
|
14766
|
+
const done = (track, source, detail, warnings = [], ambiguous = null) => ({
|
|
14767
|
+
status: "resolved",
|
|
14768
|
+
track: track && track.name !== IMPLICIT ? track : null,
|
|
14769
|
+
source,
|
|
14770
|
+
detail,
|
|
14771
|
+
warnings: [...issues, ...envProblem ? [envProblem] : [], ...warnings],
|
|
14772
|
+
tracks,
|
|
14773
|
+
blocking: envProblem ?? ambiguous
|
|
14774
|
+
});
|
|
14775
|
+
if (requested) {
|
|
14776
|
+
const named = tracks.find((track) => track.name === requested);
|
|
14777
|
+
if (!named)
|
|
14778
|
+
return {
|
|
14779
|
+
status: "invalid",
|
|
14780
|
+
error: `no release track ${JSON.stringify(requested)}; configured: ${names}`,
|
|
14781
|
+
issues
|
|
14782
|
+
};
|
|
14783
|
+
return done(named, "flag", `track ${named.name} was requested explicitly`);
|
|
14784
|
+
}
|
|
14785
|
+
const owned = (branch) => branch !== null && tracks.some((track) => [
|
|
14786
|
+
track.integrationBranch,
|
|
14787
|
+
track.productionBranch,
|
|
14788
|
+
track.baseBranch,
|
|
14789
|
+
track.pullRequestTarget
|
|
14790
|
+
].includes(branch));
|
|
14791
|
+
const implicit = input.defaultBranch && !owned(input.defaultBranch) ? {
|
|
14792
|
+
name: IMPLICIT,
|
|
14793
|
+
productionBranch: input.defaultBranch,
|
|
14794
|
+
integrationBranch: input.defaultBranch,
|
|
14795
|
+
baseBranch: input.defaultBranch,
|
|
14796
|
+
pullRequestTarget: input.defaultBranch,
|
|
14797
|
+
mergeBackBranches: [],
|
|
14798
|
+
naming: {}
|
|
14799
|
+
} : null;
|
|
14800
|
+
const candidates = implicit ? [...tracks, implicit] : tracks;
|
|
14801
|
+
const label = (track) => track.name === IMPLICIT ? `the workspace default line (${track.integrationBranch})` : `track ${track.name}`;
|
|
14802
|
+
const fallback = (why) => {
|
|
14803
|
+
const byDefault = input.defaultBranch ? tracks.filter((track) => track.integrationBranch === input.defaultBranch || track.pullRequestTarget === input.defaultBranch) : [];
|
|
14804
|
+
const track = byDefault.length === 1 ? byDefault[0] : null;
|
|
14805
|
+
const target = track ? `track ${track.name}` : `default target ${input.defaultBranch ?? "(none)"}`;
|
|
14806
|
+
const ambiguous = candidates.length >= 2 ? `can't tell which release line ${input.branch ?? "HEAD"} is on (${why}); pass --track <name> (${names}) or --base <branch>` : null;
|
|
14807
|
+
return done(track, "default", `${why}; using the workspace ${target}`, [
|
|
14808
|
+
`release track not determined: ${why}; using the workspace ${target} (pass --track <name> to choose)`
|
|
14809
|
+
], ambiguous);
|
|
14810
|
+
};
|
|
14811
|
+
const branch = input.branch;
|
|
14812
|
+
if (branch) {
|
|
14813
|
+
const owners = ownedBy(candidates, branch);
|
|
14814
|
+
if (owners.length === 1)
|
|
14815
|
+
return done(owners[0], "branch", `${branch} is a branch of ${label(owners[0])}`);
|
|
14816
|
+
if (owners.length > 1)
|
|
14817
|
+
return fallback(`${branch} belongs to more than one track (${owners.map((track) => track.name).join(", ")})`);
|
|
14818
|
+
}
|
|
14819
|
+
const env = input.envRequested?.trim();
|
|
14820
|
+
if (env) {
|
|
14821
|
+
const named = tracks.find((track) => track.name === env);
|
|
14822
|
+
if (named)
|
|
14823
|
+
return done(named, "env", `WORKFLOW_RELEASE_TRACK selects track ${named.name}`);
|
|
14824
|
+
envProblem = `WORKFLOW_RELEASE_TRACK=${env} names no release track of this workspace (${names}); ignored`;
|
|
14825
|
+
}
|
|
14826
|
+
if (!branch)
|
|
14827
|
+
return fallback("HEAD is detached");
|
|
14828
|
+
if (tracks.length === 1 && !implicit)
|
|
14829
|
+
return done(tracks[0], "only-track", `${tracks[0].name} is the only track`);
|
|
14830
|
+
let remotes = null;
|
|
14831
|
+
const short = (ref) => shortRef(ref, remotes ??= input.probe.remotes());
|
|
14832
|
+
const ownerOf = (ref) => {
|
|
14833
|
+
const owner = ownedBy(candidates, short(ref));
|
|
14834
|
+
return owner.length === 1 ? owner[0] : null;
|
|
14835
|
+
};
|
|
14836
|
+
const followRecorded = (start) => {
|
|
14837
|
+
let cursor = start;
|
|
14838
|
+
const seen = new Set([start]);
|
|
14839
|
+
for (let hop = 0;hop < MAX_RECORDED_HOPS; hop += 1) {
|
|
14840
|
+
const recorded = input.probe.recordedBase(cursor);
|
|
14841
|
+
if (!recorded)
|
|
14842
|
+
return null;
|
|
14843
|
+
const base = short(recorded);
|
|
14844
|
+
const owner = ownerOf(base);
|
|
14845
|
+
if (owner)
|
|
14846
|
+
return owner;
|
|
14847
|
+
if (ownedBy(candidates, base).length > 1 || seen.has(base))
|
|
14848
|
+
return null;
|
|
14849
|
+
seen.add(base);
|
|
14850
|
+
cursor = base;
|
|
14851
|
+
}
|
|
14852
|
+
return null;
|
|
14853
|
+
};
|
|
14854
|
+
const recordedOwner = followRecorded(branch);
|
|
14855
|
+
if (recordedOwner)
|
|
14856
|
+
return done(recordedOwner, "recorded-base", `${branch} was created on ${label(recordedOwner)}'s line (recorded base)`);
|
|
14857
|
+
for (const source of ["reflog", "checkout"]) {
|
|
14858
|
+
const ref = source === "reflog" ? input.probe.createdFrom(branch) : input.probe.checkedOutFrom(branch);
|
|
14859
|
+
if (!ref)
|
|
14860
|
+
continue;
|
|
14861
|
+
const owner = ownerOf(ref) ?? followRecorded(short(ref));
|
|
14862
|
+
if (owner)
|
|
14863
|
+
return done(owner, source, `${branch} was created from ${short(ref)} (${source === "reflog" ? "branch reflog" : "HEAD reflog"})`);
|
|
14864
|
+
}
|
|
14865
|
+
const forks = [];
|
|
14866
|
+
for (const track of candidates)
|
|
14867
|
+
for (const ref of new Set([track.integrationBranch, track.baseBranch])) {
|
|
14868
|
+
const sha = input.probe.mergeBase(branch, ref);
|
|
14869
|
+
if (sha)
|
|
14870
|
+
forks.push({ track, ref, sha });
|
|
14871
|
+
}
|
|
14872
|
+
if (forks.length === 0)
|
|
14873
|
+
return fallback(`no track branch could be compared with ${branch} (fetch the track branches)`);
|
|
14874
|
+
const ancestry = new Map;
|
|
14875
|
+
const descends = (older, newer) => {
|
|
14876
|
+
if (older === newer)
|
|
14877
|
+
return false;
|
|
14878
|
+
const key = `${older}>${newer}`;
|
|
14879
|
+
if (!ancestry.has(key))
|
|
14880
|
+
ancestry.set(key, input.probe.isAncestor(older, newer));
|
|
14881
|
+
return ancestry.get(key);
|
|
14882
|
+
};
|
|
14883
|
+
const winners = forks.filter((fork) => forks.every((other) => other.track === fork.track || descends(other.sha, fork.sha)));
|
|
14884
|
+
const winningTracks = [...new Set(winners.map((fork) => fork.track))];
|
|
14885
|
+
if (winningTracks.length === 1 && forks.some((fork) => fork.track !== winningTracks[0])) {
|
|
14886
|
+
const win = winners[0];
|
|
14887
|
+
return done(win.track, "ancestry", `${branch} forked from ${win.ref} at ${win.sha.slice(0, 12)}, after every other line's fork point`);
|
|
14888
|
+
}
|
|
14889
|
+
if (winningTracks.length === 1)
|
|
14890
|
+
return fallback(`only ${label(winningTracks[0])} could be compared with ${branch}`);
|
|
14891
|
+
const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
|
|
14892
|
+
return fallback(`${branch} has the same or unrelated fork points with ${[...new Set(newest.map((fork) => `${fork.ref} (${fork.track.name === IMPLICIT ? "workspace default" : fork.track.name})`))].join(" and ")}`);
|
|
14893
|
+
}
|
|
14894
|
+
var git4 = (cwd, args) => {
|
|
14895
|
+
const run = spawnSync7("git", args, {
|
|
14896
|
+
cwd,
|
|
14897
|
+
encoding: "utf8",
|
|
14898
|
+
timeout: 20000,
|
|
14899
|
+
windowsHide: true,
|
|
14900
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
14901
|
+
});
|
|
14902
|
+
return run.status === 0 ? (run.stdout ?? "").trim() : null;
|
|
14903
|
+
};
|
|
14904
|
+
var recordedBaseKey = (branch) => `branch.${branch}.workitBase`;
|
|
14905
|
+
var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
14906
|
+
var reflog = (cwd, ref) => (git4(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
|
|
14907
|
+
`).map((line) => {
|
|
14908
|
+
const [sha = "", selector = "", ...subject] = line.split("\t");
|
|
14909
|
+
return {
|
|
14910
|
+
sha,
|
|
14911
|
+
time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
|
|
14912
|
+
subject: subject.join("\t")
|
|
14913
|
+
};
|
|
14914
|
+
}).filter((entry) => entry.sha && Number.isFinite(entry.time));
|
|
14915
|
+
function gitTrackProbe(cwd) {
|
|
14916
|
+
let remote;
|
|
14917
|
+
let remotes;
|
|
14918
|
+
const tip = (branch) => {
|
|
14919
|
+
remote ??= pushRemoteName(cwd) ?? "origin";
|
|
14920
|
+
return resolveRef(cwd, `refs/remotes/${remote}/${branch}`) ?? resolveRef(cwd, `refs/heads/${branch}`);
|
|
14921
|
+
};
|
|
14922
|
+
return {
|
|
14923
|
+
remotes: () => remotes ??= remoteNames(cwd),
|
|
14924
|
+
recordedBase: (branch) => git4(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
|
|
14925
|
+
createdFrom: (branch) => {
|
|
14926
|
+
const log = git4(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
|
|
14927
|
+
const oldest = log?.split(`
|
|
14928
|
+
`).findLast(Boolean) ?? "";
|
|
14929
|
+
const match = /^branch: Created from (\S+)$/u.exec(oldest);
|
|
14930
|
+
return match && match[1] !== "HEAD" ? match[1] : null;
|
|
14931
|
+
},
|
|
14932
|
+
checkedOutFrom: (branch) => {
|
|
14933
|
+
const own = reflog(cwd, `refs/heads/${branch}`);
|
|
14934
|
+
const created = own.at(-1);
|
|
14935
|
+
if (!created || created.subject !== "branch: Created from HEAD")
|
|
14936
|
+
return null;
|
|
14937
|
+
const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
|
|
14938
|
+
const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
|
|
14939
|
+
return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
|
|
14940
|
+
},
|
|
14941
|
+
mergeBase: (branch, trackBranch) => {
|
|
14942
|
+
const base = tip(trackBranch);
|
|
14943
|
+
return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
|
|
14944
|
+
},
|
|
14945
|
+
isAncestor: (ancestor, descendant) => git4(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
|
|
14946
|
+
};
|
|
14947
|
+
}
|
|
14948
|
+
function workspaceReleaseTracks(cwd) {
|
|
14949
|
+
const raw = resolveRuntimeReleaseTracks(cwd);
|
|
14950
|
+
return { workspace: raw?.workspace ?? null, ...readReleaseTracks(raw?.releaseTracks) };
|
|
14951
|
+
}
|
|
14952
|
+
var memo3 = new Map;
|
|
14953
|
+
var MEMO_LIMIT = 64;
|
|
14954
|
+
function resolveTrackFor(cwd, options) {
|
|
14955
|
+
const read = workspaceReleaseTracks(cwd);
|
|
14956
|
+
if (read.error)
|
|
14957
|
+
return { status: "invalid", error: read.error, issues: read.issues };
|
|
14958
|
+
if (read.tracks.length === 0 && !options.track?.trim())
|
|
14959
|
+
return { status: "none", issues: read.issues };
|
|
14960
|
+
const branch = options.branch === undefined ? currentBranch(cwd) : options.branch;
|
|
14961
|
+
const envRequested = process.env.WORKFLOW_RELEASE_TRACK?.trim() || null;
|
|
14962
|
+
const input = {
|
|
14963
|
+
tracks: read.tracks,
|
|
14964
|
+
requested: options.track ?? null,
|
|
14965
|
+
envRequested,
|
|
14966
|
+
branch,
|
|
14967
|
+
defaultBranch: options.defaultBranch,
|
|
14968
|
+
issues: read.issues
|
|
14969
|
+
};
|
|
14970
|
+
let key = null;
|
|
14971
|
+
if (!options.probe) {
|
|
14972
|
+
const refs = git4(cwd, [
|
|
14973
|
+
"for-each-ref",
|
|
14974
|
+
"--format=%(refname) %(objectname)",
|
|
14975
|
+
"refs/heads",
|
|
14976
|
+
"refs/remotes"
|
|
14977
|
+
]) ?? "";
|
|
14978
|
+
const head = git4(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
|
|
14979
|
+
const own = branch ? [
|
|
14980
|
+
git4(cwd, [
|
|
14981
|
+
"log",
|
|
14982
|
+
"-g",
|
|
14983
|
+
"-n",
|
|
14984
|
+
"1",
|
|
14985
|
+
"--date=unix",
|
|
14986
|
+
"--format=%H %gd %gs",
|
|
14987
|
+
`refs/heads/${branch}`,
|
|
14988
|
+
"--"
|
|
14989
|
+
]) ?? "",
|
|
14990
|
+
git4(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
|
|
14991
|
+
] : [];
|
|
14992
|
+
key = createHash9("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
|
|
14993
|
+
const hit = memo3.get(key);
|
|
14994
|
+
if (hit)
|
|
14995
|
+
return hit;
|
|
14996
|
+
}
|
|
14997
|
+
const resolution = resolveReleaseTrack({ ...input, probe: options.probe ?? gitTrackProbe(cwd) });
|
|
14998
|
+
if (key) {
|
|
14999
|
+
if (memo3.size >= MEMO_LIMIT)
|
|
15000
|
+
memo3.delete(memo3.keys().next().value);
|
|
15001
|
+
memo3.set(key, resolution);
|
|
15002
|
+
}
|
|
15003
|
+
return resolution;
|
|
15004
|
+
}
|
|
13585
15005
|
|
|
13586
15006
|
// packages/workit-core/src/core/vcs-config.ts
|
|
13587
|
-
|
|
13588
|
-
import path12 from "node:path";
|
|
13589
|
-
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
13590
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path12.join(configDir(), "vcs.json");
|
|
15007
|
+
var workspacesPath = () => path14.join(configDir(), "workspaces.json");
|
|
13591
15008
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
13592
15009
|
var remoteProvider = (cwd) => {
|
|
13593
|
-
const r =
|
|
15010
|
+
const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
13594
15011
|
if (r.status !== 0)
|
|
13595
15012
|
return null;
|
|
13596
15013
|
const url = (r.stdout ?? "").trim();
|
|
@@ -13627,7 +15044,7 @@ var remoteEndpoint = (remote) => {
|
|
|
13627
15044
|
var configuredSshEndpoint = (host) => {
|
|
13628
15045
|
if (host.startsWith("-"))
|
|
13629
15046
|
return null;
|
|
13630
|
-
const resolved =
|
|
15047
|
+
const resolved = spawnSync8("ssh", ["-G", host], {
|
|
13631
15048
|
encoding: "utf8",
|
|
13632
15049
|
timeout: 5000,
|
|
13633
15050
|
maxBuffer: 1024 * 1024,
|
|
@@ -13677,7 +15094,7 @@ var readVcsConfig = () => {
|
|
|
13677
15094
|
const file = vcsConfigPath();
|
|
13678
15095
|
let raw;
|
|
13679
15096
|
try {
|
|
13680
|
-
raw =
|
|
15097
|
+
raw = fs12.readFileSync(file, "utf8");
|
|
13681
15098
|
} catch {
|
|
13682
15099
|
return { status: "missing", path: file, config: {} };
|
|
13683
15100
|
}
|
|
@@ -13692,7 +15109,16 @@ var readVcsConfig = () => {
|
|
|
13692
15109
|
}
|
|
13693
15110
|
return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
|
|
13694
15111
|
};
|
|
13695
|
-
|
|
15112
|
+
var trackSummary = (resolution) => resolution.status === "resolved" ? {
|
|
15113
|
+
name: resolution.track?.name ?? null,
|
|
15114
|
+
source: resolution.source,
|
|
15115
|
+
detail: resolution.detail,
|
|
15116
|
+
warnings: resolution.warnings,
|
|
15117
|
+
track: resolution.track,
|
|
15118
|
+
tracks: resolution.tracks,
|
|
15119
|
+
blocking: resolution.blocking
|
|
15120
|
+
} : null;
|
|
15121
|
+
function vcsConfig(mode, cwd, options = {}) {
|
|
13696
15122
|
const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
|
|
13697
15123
|
const wsVcs = ws?.vcs ?? {};
|
|
13698
15124
|
const wsYt = ws?.youtrack ?? {};
|
|
@@ -13702,10 +15128,7 @@ function vcsConfig(mode, cwd) {
|
|
|
13702
15128
|
const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
|
|
13703
15129
|
const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
|
|
13704
15130
|
const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
|
|
13705
|
-
const
|
|
13706
|
-
const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
|
|
13707
|
-
const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
|
|
13708
|
-
const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
|
|
15131
|
+
const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
|
|
13709
15132
|
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
13710
15133
|
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
13711
15134
|
let issuesProvider = null;
|
|
@@ -13717,11 +15140,24 @@ function vcsConfig(mode, cwd) {
|
|
|
13717
15140
|
if (mode === "resolve") {
|
|
13718
15141
|
if (cfgStatus === "malformed")
|
|
13719
15142
|
return { ok: false, error: cfgError, configPath: cfgPath };
|
|
15143
|
+
const trackResolution = resolveTrackFor(root, {
|
|
15144
|
+
track: options.track,
|
|
15145
|
+
...options.branch !== undefined ? { branch: options.branch } : {},
|
|
15146
|
+
defaultBranch: workspaceDefault
|
|
15147
|
+
});
|
|
15148
|
+
if (trackResolution.status === "invalid")
|
|
15149
|
+
return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
|
|
15150
|
+
const releaseTrack = trackSummary(trackResolution);
|
|
15151
|
+
const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
|
|
15152
|
+
const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
|
|
13720
15153
|
return {
|
|
13721
15154
|
ok: true,
|
|
13722
15155
|
workspace_name: ws?.name ?? null,
|
|
13723
15156
|
provider,
|
|
13724
15157
|
defaultTargetBranch: defaultTarget,
|
|
15158
|
+
workspaceDefaultTargetBranch: workspaceDefault,
|
|
15159
|
+
baseBranch,
|
|
15160
|
+
releaseTrack,
|
|
13725
15161
|
link_issues: linkIssues,
|
|
13726
15162
|
youtrack_base_url: youtrackBaseUrl,
|
|
13727
15163
|
issues_provider: issuesProvider,
|
|
@@ -13743,9 +15179,9 @@ function vcsConfig(mode, cwd) {
|
|
|
13743
15179
|
const prov = cfg[provider] ?? {};
|
|
13744
15180
|
const out = {
|
|
13745
15181
|
ok: true,
|
|
13746
|
-
configPath:
|
|
15182
|
+
configPath: path14.resolve(cfgPath),
|
|
13747
15183
|
provider,
|
|
13748
|
-
defaultTargetBranch:
|
|
15184
|
+
defaultTargetBranch: workspaceDefault,
|
|
13749
15185
|
pr: cfg.pr ?? {},
|
|
13750
15186
|
workspace_name: ws?.name ?? null,
|
|
13751
15187
|
link_issues: linkIssues,
|
|
@@ -13781,7 +15217,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
13781
15217
|
`).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
|
|
13782
15218
|
descriptionPreview: desc.slice(0, 600)
|
|
13783
15219
|
});
|
|
13784
|
-
const remoteResult =
|
|
15220
|
+
const remoteResult = spawnSync8("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
13785
15221
|
cwd,
|
|
13786
15222
|
encoding: "utf8"
|
|
13787
15223
|
});
|
|
@@ -13797,7 +15233,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
13797
15233
|
if (!hostingApiHostMatches(remote, host))
|
|
13798
15234
|
return { ok: false, error: "PR style target does not match the provider host" };
|
|
13799
15235
|
if (provider === "gitlab") {
|
|
13800
|
-
const run = (args) =>
|
|
15236
|
+
const run = (args) => spawnSync8("glab", ["api", ...args], {
|
|
13801
15237
|
cwd,
|
|
13802
15238
|
encoding: "utf8",
|
|
13803
15239
|
env
|
|
@@ -13817,7 +15253,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
13817
15253
|
});
|
|
13818
15254
|
}
|
|
13819
15255
|
} else if (provider === "github") {
|
|
13820
|
-
const r =
|
|
15256
|
+
const r = spawnSync8("gh", [
|
|
13821
15257
|
"pr",
|
|
13822
15258
|
"list",
|
|
13823
15259
|
"--repo",
|
|
@@ -13850,11 +15286,35 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
13850
15286
|
}
|
|
13851
15287
|
|
|
13852
15288
|
// packages/workit-core/src/core/branch.ts
|
|
15289
|
+
var withReleaseTracks = (workspaceRoot, policy) => {
|
|
15290
|
+
const read = workspaceReleaseTracks(workspaceRoot);
|
|
15291
|
+
if (read.error)
|
|
15292
|
+
throw new ReleaseTrackError(read.error);
|
|
15293
|
+
if (read.tracks.length === 0 && read.protectedBranches.length === 0)
|
|
15294
|
+
return policy;
|
|
15295
|
+
const known = new Set(policy.allowed.map((pattern) => pattern.source));
|
|
15296
|
+
const allowed = [...policy.allowed];
|
|
15297
|
+
for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
|
|
15298
|
+
const pattern = branchGlobPattern(glob);
|
|
15299
|
+
if (!known.has(pattern.source)) {
|
|
15300
|
+
known.add(pattern.source);
|
|
15301
|
+
allowed.push(pattern);
|
|
15302
|
+
}
|
|
15303
|
+
}
|
|
15304
|
+
return {
|
|
15305
|
+
...policy,
|
|
15306
|
+
allowed,
|
|
15307
|
+
protected: new Set([
|
|
15308
|
+
...policy.protected,
|
|
15309
|
+
...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
|
|
15310
|
+
])
|
|
15311
|
+
};
|
|
15312
|
+
};
|
|
13853
15313
|
function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
13854
15314
|
const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
13855
15315
|
const config = readConfig();
|
|
13856
15316
|
return kind === "branch" ? {
|
|
13857
|
-
branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
|
|
15317
|
+
branchPolicy: withReleaseTracks(workspaceRoot, resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null)),
|
|
13858
15318
|
provenance: { branchPolicy: selected.source }
|
|
13859
15319
|
} : {
|
|
13860
15320
|
commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
|
|
@@ -13893,7 +15353,7 @@ var branchPolicySnapshotFor = (workspaceRoot) => {
|
|
|
13893
15353
|
const source = resolved.provenance.branchPolicy;
|
|
13894
15354
|
const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
|
|
13895
15355
|
const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
|
|
13896
|
-
const fingerprint =
|
|
15356
|
+
const fingerprint = createHash10("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
|
|
13897
15357
|
return {
|
|
13898
15358
|
branchPolicy,
|
|
13899
15359
|
namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
|
|
@@ -13917,7 +15377,6 @@ var validateBranchNameFor = (workspaceRoot, name) => {
|
|
|
13917
15377
|
};
|
|
13918
15378
|
}
|
|
13919
15379
|
};
|
|
13920
|
-
var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
|
|
13921
15380
|
|
|
13922
15381
|
// packages/workit-core/src/core/route-intent.ts
|
|
13923
15382
|
var shellBranchTarget = (command) => {
|
|
@@ -13938,305 +15397,34 @@ var shellBranchPolicyViolation = (root, command) => {
|
|
|
13938
15397
|
import { realpathSync as realpathSync4 } from "node:fs";
|
|
13939
15398
|
|
|
13940
15399
|
// packages/workit-core/src/core/policy-resolver.ts
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
assessment: assessmentSchema,
|
|
13944
|
-
constraints: array(constraintSchema),
|
|
13945
|
-
prior: object({
|
|
13946
|
-
decisions: array(decisionSchema),
|
|
13947
|
-
findings: array(findingSchema),
|
|
13948
|
-
requirements: array(requirementSchema)
|
|
13949
|
-
}).strict(),
|
|
13950
|
-
preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
|
|
13951
|
-
}).strict();
|
|
13952
|
-
var compareCodeUnits3 = (left, right) => {
|
|
13953
|
-
const length = Math.min(left.length, right.length);
|
|
13954
|
-
for (let index = 0;index < length; index += 1) {
|
|
13955
|
-
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
13956
|
-
if (difference !== 0)
|
|
13957
|
-
return difference;
|
|
13958
|
-
}
|
|
13959
|
-
return left.length - right.length;
|
|
13960
|
-
};
|
|
13961
|
-
var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits3);
|
|
13962
|
-
var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right)));
|
|
13963
|
-
var normalizedScope = (scope) => ({
|
|
13964
|
-
description: scope.description,
|
|
13965
|
-
paths: stableUnique(scope.paths),
|
|
13966
|
-
exclusions: stableUnique(scope.exclusions)
|
|
13967
|
-
});
|
|
13968
|
-
var normalizedRefList = (values) => stableList(values);
|
|
13969
|
-
var normalizeAssessment = (assessment) => ({
|
|
13970
|
-
facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
|
|
13971
|
-
signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
|
|
13972
|
-
name,
|
|
13973
|
-
{ ...signal, refs: normalizedRefList(signal.refs) }
|
|
13974
|
-
])),
|
|
13975
|
-
consequences: stableList(assessment.consequences.map((consequence) => ({
|
|
13976
|
-
...consequence,
|
|
13977
|
-
fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
|
|
13978
|
-
}))),
|
|
13979
|
-
verification: stableList(assessment.verification.map((verification) => ({
|
|
13980
|
-
...verification,
|
|
13981
|
-
scope: normalizedScope(verification.scope),
|
|
13982
|
-
availableChecks: normalizedRefList(verification.availableChecks),
|
|
13983
|
-
gaps: stableUnique(verification.gaps)
|
|
13984
|
-
})))
|
|
13985
|
-
});
|
|
13986
|
-
var normalizeConstraint = (constraint) => ({
|
|
13987
|
-
...constraint,
|
|
13988
|
-
source: constraint.source,
|
|
13989
|
-
requires: stableList(constraint.requires.map((requirement) => ({
|
|
13990
|
-
...requirement,
|
|
13991
|
-
scope: normalizedScope(requirement.scope),
|
|
13992
|
-
refs: normalizedRefList(requirement.refs)
|
|
13993
|
-
})))
|
|
13994
|
-
});
|
|
13995
|
-
var normalizeDecision = (decision) => ({
|
|
13996
|
-
...decision,
|
|
13997
|
-
binding: {
|
|
13998
|
-
...decision.binding,
|
|
13999
|
-
scope: normalizedScope(decision.binding.scope),
|
|
14000
|
-
contentRefs: normalizedRefList(decision.binding.contentRefs)
|
|
14001
|
-
},
|
|
14002
|
-
requirementIds: stableUnique(decision.requirementIds)
|
|
14003
|
-
});
|
|
14004
|
-
var normalizeIntent = (intent) => ({
|
|
14005
|
-
...intent,
|
|
14006
|
-
scope: normalizedScope(intent.scope),
|
|
14007
|
-
authorityRefs: normalizedRefList(intent.authorityRefs)
|
|
14008
|
-
});
|
|
14009
|
-
var normalizePrior = (prior) => ({
|
|
14010
|
-
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
14011
|
-
findings: stableList(prior.findings.map((finding) => ({
|
|
14012
|
-
...finding,
|
|
14013
|
-
scope: normalizedScope(finding.scope),
|
|
14014
|
-
refs: normalizedRefList(finding.refs),
|
|
14015
|
-
resolution: finding.resolution ? {
|
|
14016
|
-
...finding.resolution,
|
|
14017
|
-
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
14018
|
-
decisionIds: stableUnique(finding.resolution.decisionIds)
|
|
14019
|
-
} : null
|
|
14020
|
-
}))),
|
|
14021
|
-
requirements: stableList(prior.requirements.map((requirement) => ({
|
|
14022
|
-
...requirement,
|
|
14023
|
-
scope: normalizedScope(requirement.scope)
|
|
14024
|
-
})))
|
|
14025
|
-
});
|
|
14026
|
-
var invalidFields = (error) => ({
|
|
14027
|
-
fields: error.issues.map((issue) => ({
|
|
14028
|
-
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
14029
|
-
reason: issue.message
|
|
14030
|
-
}))
|
|
14031
|
-
});
|
|
14032
|
-
function normalizeResolverInput(input) {
|
|
14033
|
-
const parsed = resolverInputSchema.safeParse(input);
|
|
14034
|
-
if (!parsed.success)
|
|
14035
|
-
return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
14036
|
-
const value = parsed.data;
|
|
14037
|
-
const signals = value.assessment.signals;
|
|
14038
|
-
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
14039
|
-
return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
14040
|
-
fields: [
|
|
14041
|
-
{
|
|
14042
|
-
path: "assessment.signals",
|
|
14043
|
-
reason: "contradictory behaviorChange and mechanicalLowRisk"
|
|
14044
|
-
}
|
|
14045
|
-
]
|
|
14046
|
-
});
|
|
14047
|
-
const ids = value.constraints.map((constraint) => constraint.id);
|
|
15400
|
+
function resolvePolicy(input) {
|
|
15401
|
+
const ids = input.constraints.map((constraint) => constraint.id);
|
|
14048
15402
|
if (new Set(ids).size !== ids.length)
|
|
14049
15403
|
return failure2("invalid_input", "constraint IDs must be unique", {
|
|
14050
15404
|
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
14051
15405
|
});
|
|
14052
|
-
if (value.preferences?.fast && value.preferences.thorough)
|
|
14053
|
-
return failure2("invalid_input", "fast and thorough preferences contradict", {
|
|
14054
|
-
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
|
|
14055
|
-
});
|
|
14056
|
-
const normalized = {
|
|
14057
|
-
intent: normalizeIntent(value.intent),
|
|
14058
|
-
assessment: normalizeAssessment(value.assessment),
|
|
14059
|
-
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
14060
|
-
prior: normalizePrior(value.prior),
|
|
14061
|
-
...value.preferences ? { preferences: { ...value.preferences } } : {},
|
|
14062
|
-
policyVersion: POLICY_VERSION
|
|
14063
|
-
};
|
|
14064
|
-
return success(null, null, normalized);
|
|
14065
|
-
}
|
|
14066
|
-
var requirement = (spec) => ({
|
|
14067
|
-
...spec,
|
|
14068
|
-
id: requirementId({
|
|
14069
|
-
ruleId: spec.ruleId,
|
|
14070
|
-
scope: spec.scope,
|
|
14071
|
-
satisfaction: spec.satisfaction,
|
|
14072
|
-
before: spec.before,
|
|
14073
|
-
dependentAction: spec.dependentAction
|
|
14074
|
-
})
|
|
14075
|
-
});
|
|
14076
|
-
var signalReason = (input, name) => input.assessment.signals[name].reason;
|
|
14077
|
-
function resolveRequirements(input) {
|
|
14078
|
-
const { signals } = input.assessment;
|
|
14079
|
-
const scope = input.intent.scope;
|
|
14080
|
-
const requirements = [];
|
|
14081
|
-
const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
|
|
14082
|
-
const hasUnknownFact = [
|
|
14083
|
-
...input.assessment.facts,
|
|
14084
|
-
...input.assessment.consequences.map(({ fact }) => fact)
|
|
14085
|
-
].some((fact) => fact.basis === "unknown");
|
|
14086
|
-
if (hasUnknownSignal || hasUnknownFact)
|
|
14087
|
-
requirements.push(requirement({
|
|
14088
|
-
ruleId: "consequential-unknown",
|
|
14089
|
-
dimension: "investigation",
|
|
14090
|
-
scope,
|
|
14091
|
-
reason: `Resolve consequential uncertainty before the dependent action: ${[
|
|
14092
|
-
...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
|
|
14093
|
-
...hasUnknownFact ? ["unknown fact"] : []
|
|
14094
|
-
].join(", ")}`,
|
|
14095
|
-
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
14096
|
-
before: "dependent_action",
|
|
14097
|
-
dependentAction: null,
|
|
14098
|
-
acceptanceAllowed: false
|
|
14099
|
-
}));
|
|
14100
|
-
if (signals.productChoiceOpen.value === true)
|
|
14101
|
-
requirements.push(requirement({
|
|
14102
|
-
ruleId: "product-decision",
|
|
14103
|
-
dimension: "decisions",
|
|
14104
|
-
scope,
|
|
14105
|
-
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
14106
|
-
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
14107
|
-
before: "dependent_action",
|
|
14108
|
-
dependentAction: null,
|
|
14109
|
-
acceptanceAllowed: false
|
|
14110
|
-
}));
|
|
14111
|
-
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
14112
|
-
if (signals.behaviorChange.value === true)
|
|
14113
|
-
requirements.push(requirement({
|
|
14114
|
-
ruleId: "behavioral-verification",
|
|
14115
|
-
dimension: "testing",
|
|
14116
|
-
scope,
|
|
14117
|
-
reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
|
|
14118
|
-
satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
|
|
14119
|
-
before: "close",
|
|
14120
|
-
dependentAction: null,
|
|
14121
|
-
acceptanceAllowed: false
|
|
14122
|
-
}), requirement({
|
|
14123
|
-
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
14124
|
-
dimension: "review",
|
|
14125
|
-
scope,
|
|
14126
|
-
reason: independentBehaviorReview ? "Consequential behavior or the thorough preference requires fresh-context review." : "Bounded behavior changes use self-review unless consequential risks or trusted rules require more.",
|
|
14127
|
-
satisfaction: independentBehaviorReview ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
14128
|
-
before: "close",
|
|
14129
|
-
dependentAction: null,
|
|
14130
|
-
acceptanceAllowed: !independentBehaviorReview
|
|
14131
|
-
}));
|
|
14132
|
-
else if (signals.mechanicalLowRisk.value === true)
|
|
14133
|
-
requirements.push(requirement({
|
|
14134
|
-
ruleId: "mechanical-existing-checks",
|
|
14135
|
-
dimension: "verification",
|
|
14136
|
-
scope,
|
|
14137
|
-
reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
|
|
14138
|
-
satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
|
|
14139
|
-
before: "close",
|
|
14140
|
-
dependentAction: null,
|
|
14141
|
-
acceptanceAllowed: true
|
|
14142
|
-
}), requirement({
|
|
14143
|
-
ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
|
|
14144
|
-
dimension: "review",
|
|
14145
|
-
scope,
|
|
14146
|
-
reason: input.preferences?.thorough ? "The thorough preference upgrades mechanical self-review to fresh-context review." : "Mechanical low-risk work uses self-review rather than mandatory fresh-context review.",
|
|
14147
|
-
satisfaction: input.preferences?.thorough ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
14148
|
-
before: "close",
|
|
14149
|
-
dependentAction: null,
|
|
14150
|
-
acceptanceAllowed: !input.preferences?.thorough
|
|
14151
|
-
}));
|
|
14152
|
-
if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
|
|
14153
|
-
requirements.push(requirement({
|
|
14154
|
-
ruleId: "pre-pr-cleanup",
|
|
14155
|
-
dimension: "verification",
|
|
14156
|
-
scope,
|
|
14157
|
-
reason: "Implementation output must be deslopped before it is delivered.",
|
|
14158
|
-
satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
|
|
14159
|
-
before: "dependent_action",
|
|
14160
|
-
dependentAction: "hosting.pull_request",
|
|
14161
|
-
acceptanceAllowed: true
|
|
14162
|
-
}));
|
|
14163
|
-
if (signals.durableAgreementNeeded.value === true)
|
|
14164
|
-
requirements.push(requirement({
|
|
14165
|
-
ruleId: "durable-spec",
|
|
14166
|
-
dimension: "artifacts",
|
|
14167
|
-
scope,
|
|
14168
|
-
reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
|
|
14169
|
-
satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
|
|
14170
|
-
before: "write",
|
|
14171
|
-
dependentAction: null,
|
|
14172
|
-
acceptanceAllowed: true
|
|
14173
|
-
}));
|
|
14174
|
-
if (signals.coordinationPlanNeeded.value === true)
|
|
14175
|
-
requirements.push(requirement({
|
|
14176
|
-
ruleId: "coordination-plan",
|
|
14177
|
-
dimension: "continuity",
|
|
14178
|
-
scope,
|
|
14179
|
-
reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
|
|
14180
|
-
satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
|
|
14181
|
-
before: "dependent_action",
|
|
14182
|
-
dependentAction: null,
|
|
14183
|
-
acceptanceAllowed: true
|
|
14184
|
-
}));
|
|
14185
|
-
if (signals.helperUseful.value === true && !input.preferences?.fast)
|
|
14186
|
-
requirements.push(requirement({
|
|
14187
|
-
ruleId: "helper-usefulness",
|
|
14188
|
-
dimension: "delegation",
|
|
14189
|
-
scope,
|
|
14190
|
-
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
14191
|
-
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
14192
|
-
before: "dependent_action",
|
|
14193
|
-
dependentAction: null,
|
|
14194
|
-
acceptanceAllowed: true
|
|
14195
|
-
}));
|
|
14196
|
-
for (const constraint of input.constraints)
|
|
14197
|
-
for (const obligation of constraint.requires) {
|
|
14198
|
-
const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
|
|
14199
|
-
requirements.push(requirement({
|
|
14200
|
-
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
14201
|
-
kind: obligation.kind,
|
|
14202
|
-
scope: normalizedScope(obligation.scope),
|
|
14203
|
-
refs: normalizedRefList(obligation.refs),
|
|
14204
|
-
method: obligation.method,
|
|
14205
|
-
before: obligation.before,
|
|
14206
|
-
dependentAction: obligation.dependentAction
|
|
14207
|
-
})}`,
|
|
14208
|
-
dimension,
|
|
14209
|
-
scope: obligation.scope,
|
|
14210
|
-
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
14211
|
-
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
14212
|
-
before: obligation.before,
|
|
14213
|
-
dependentAction: obligation.dependentAction,
|
|
14214
|
-
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
14215
|
-
}));
|
|
14216
|
-
}
|
|
14217
|
-
const seen = new Set;
|
|
14218
|
-
return requirements.filter((item) => {
|
|
14219
|
-
if (seen.has(item.id))
|
|
14220
|
-
return false;
|
|
14221
|
-
seen.add(item.id);
|
|
14222
|
-
return true;
|
|
14223
|
-
});
|
|
14224
|
-
}
|
|
14225
|
-
function resolvePolicy(input) {
|
|
14226
|
-
const normalized = normalizeResolverInput(input);
|
|
14227
|
-
if (!normalized.ok)
|
|
14228
|
-
return normalized;
|
|
14229
|
-
const requirements = resolveRequirements(normalized.data);
|
|
14230
15406
|
const policy = {
|
|
14231
15407
|
policyVersion: POLICY_VERSION,
|
|
14232
|
-
inputDigest: sha256(canonicalJson(
|
|
14233
|
-
|
|
15408
|
+
inputDigest: sha256(canonicalJson({
|
|
15409
|
+
judgment: input.judgment,
|
|
15410
|
+
scope: input.intent.scope,
|
|
15411
|
+
constraints: input.constraints,
|
|
15412
|
+
verification: input.verification ?? "self",
|
|
15413
|
+
policyVersion: POLICY_VERSION
|
|
15414
|
+
})),
|
|
15415
|
+
requirements: deriveRequirements(input.judgment, input.intent.scope, input.constraints, input.verification)
|
|
14234
15416
|
};
|
|
14235
15417
|
const parsed = policySchema.safeParse(policy);
|
|
14236
15418
|
if (!parsed.success)
|
|
14237
|
-
return failure2("invalid_input", "resolved policy is invalid",
|
|
15419
|
+
return failure2("invalid_input", "resolved policy is invalid", {
|
|
15420
|
+
fields: parsed.error.issues.map((issue) => ({
|
|
15421
|
+
path: issue.path.join("."),
|
|
15422
|
+
reason: issue.message
|
|
15423
|
+
}))
|
|
15424
|
+
});
|
|
14238
15425
|
return success(null, null, parsed.data);
|
|
14239
15426
|
}
|
|
15427
|
+
var compareCodeUnits3 = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
14240
15428
|
function diffPolicy(previous, next, reason, now) {
|
|
14241
15429
|
if (!policySchema.safeParse(previous).success && previous !== null)
|
|
14242
15430
|
throw new TypeError("previous policy is invalid");
|
|
@@ -14264,7 +15452,222 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
14264
15452
|
return parsed.data;
|
|
14265
15453
|
}
|
|
14266
15454
|
|
|
15455
|
+
// packages/workit-core/src/core/policy/judgment.ts
|
|
15456
|
+
var isObject6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15457
|
+
var squash = (key) => key.toLowerCase().replace(/[_-]/g, "");
|
|
15458
|
+
var ALIASES = {
|
|
15459
|
+
riskTier: ["risktier", "risk", "risklevel", "tier"],
|
|
15460
|
+
behaviorChange: ["behaviorchange", "behavior", "behaviour", "behaviourchange"],
|
|
15461
|
+
productChoiceOpen: ["productchoiceopen", "openproductchoice", "productchoice", "openchoice"],
|
|
15462
|
+
needsPlan: ["needsplan", "plan", "needsspec", "spec"],
|
|
15463
|
+
note: ["note", "notes", "reason", "why"],
|
|
15464
|
+
refs: ["refs", "ref", "references"]
|
|
15465
|
+
};
|
|
15466
|
+
var LEGACY_KEYS = new Set(["assessment", "facts", "signals", "consequences", "verification"]);
|
|
15467
|
+
var RISK = {
|
|
15468
|
+
trivial: "trivial",
|
|
15469
|
+
low: "trivial",
|
|
15470
|
+
minor: "trivial",
|
|
15471
|
+
mechanical: "trivial",
|
|
15472
|
+
none: "trivial",
|
|
15473
|
+
normal: "normal",
|
|
15474
|
+
medium: "normal",
|
|
15475
|
+
med: "normal",
|
|
15476
|
+
moderate: "normal",
|
|
15477
|
+
high: "high",
|
|
15478
|
+
critical: "high",
|
|
15479
|
+
major: "high"
|
|
15480
|
+
};
|
|
15481
|
+
var YES = new Set(["true", "yes", "y", "1", "on"]);
|
|
15482
|
+
var NO = new Set(["false", "no", "n", "0", "off", "none"]);
|
|
15483
|
+
var flag = (value, unknownAs) => {
|
|
15484
|
+
if (typeof value === "boolean")
|
|
15485
|
+
return { ok: true, value };
|
|
15486
|
+
if (typeof value === "number" && (value === 0 || value === 1))
|
|
15487
|
+
return { ok: true, value: value === 1 };
|
|
15488
|
+
if (typeof value !== "string")
|
|
15489
|
+
return { ok: false };
|
|
15490
|
+
const text = value.trim().toLowerCase();
|
|
15491
|
+
if (YES.has(text))
|
|
15492
|
+
return { ok: true, value: true };
|
|
15493
|
+
if (NO.has(text))
|
|
15494
|
+
return { ok: true, value: false };
|
|
15495
|
+
if (text === "unknown" || text === "maybe")
|
|
15496
|
+
return { ok: true, value: unknownAs };
|
|
15497
|
+
return { ok: false };
|
|
15498
|
+
};
|
|
15499
|
+
var fromLegacy = (raw, previous) => {
|
|
15500
|
+
const source = isObject6(raw.assessment) ? raw.assessment : raw;
|
|
15501
|
+
const signals = isObject6(source.signals) ? source.signals : {};
|
|
15502
|
+
const value = (name) => isObject6(signals[name]) ? signals[name].value : undefined;
|
|
15503
|
+
const known = (name, fallback) => typeof value(name) === "boolean" ? value(name) : fallback;
|
|
15504
|
+
const behaviorChange = known("behaviorChange", previous?.behaviorChange ?? false);
|
|
15505
|
+
const consequences = Array.isArray(source.consequences) ? source.consequences : [];
|
|
15506
|
+
const freshReview = value("behaviorChange") === true && consequences.some((item) => isObject6(item) && ["security", "data", "public_contract", "operations"].includes(String(item.area)));
|
|
15507
|
+
return {
|
|
15508
|
+
riskTier: freshReview ? "high" : value("mechanicalLowRisk") === true && !behaviorChange ? "trivial" : "normal",
|
|
15509
|
+
behaviorChange,
|
|
15510
|
+
productChoiceOpen: known("productChoiceOpen", previous?.productChoiceOpen ?? false),
|
|
15511
|
+
needsPlan: known("durableAgreementNeeded", previous?.needsPlan ?? false) || value("coordinationPlanNeeded") === true
|
|
15512
|
+
};
|
|
15513
|
+
};
|
|
15514
|
+
var DEFAULT = {
|
|
15515
|
+
riskTier: "normal",
|
|
15516
|
+
behaviorChange: false,
|
|
15517
|
+
productChoiceOpen: false,
|
|
15518
|
+
needsPlan: false,
|
|
15519
|
+
note: null,
|
|
15520
|
+
refs: []
|
|
15521
|
+
};
|
|
15522
|
+
function normalizeJudgment(raw, previous) {
|
|
15523
|
+
const input = isObject6(raw) ? raw : {};
|
|
15524
|
+
const legacy = Object.keys(input).some((key) => LEGACY_KEYS.has(key));
|
|
15525
|
+
const base = { ...DEFAULT, ...previous, refs: [...previous?.refs ?? []] };
|
|
15526
|
+
const judgment = legacy ? { ...base, ...fromLegacy(input, previous) } : { ...base };
|
|
15527
|
+
const ignored = [];
|
|
15528
|
+
const fields = [];
|
|
15529
|
+
for (const [key, value] of Object.entries(input)) {
|
|
15530
|
+
if (LEGACY_KEYS.has(key))
|
|
15531
|
+
continue;
|
|
15532
|
+
const name = Object.keys(ALIASES).find((field) => ALIASES[field].includes(squash(key)));
|
|
15533
|
+
if (!name) {
|
|
15534
|
+
ignored.push(key);
|
|
15535
|
+
continue;
|
|
15536
|
+
}
|
|
15537
|
+
if (name === "riskTier") {
|
|
15538
|
+
const tier = typeof value === "string" ? RISK[value.trim().toLowerCase()] : undefined;
|
|
15539
|
+
if (tier)
|
|
15540
|
+
judgment.riskTier = tier;
|
|
15541
|
+
else
|
|
15542
|
+
fields.push({ path: key, reason: "expected trivial|normal|high (or low|medium)" });
|
|
15543
|
+
} else if (name === "note") {
|
|
15544
|
+
judgment.note = typeof value === "string" && value.trim() ? value.trim() : null;
|
|
15545
|
+
} else if (name === "refs") {
|
|
15546
|
+
const items = Array.isArray(value) ? value : [value];
|
|
15547
|
+
judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
|
|
15548
|
+
} else {
|
|
15549
|
+
if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
|
|
15550
|
+
judgment.needsPlan = true;
|
|
15551
|
+
judgment.refs.push(refOf(value.trim()));
|
|
15552
|
+
continue;
|
|
15553
|
+
}
|
|
15554
|
+
const parsed = flag(value, name === "behaviorChange");
|
|
15555
|
+
if (parsed.ok)
|
|
15556
|
+
judgment[name] = parsed.value;
|
|
15557
|
+
else
|
|
15558
|
+
fields.push({ path: key, reason: "expected true|false (yes|no accepted)" });
|
|
15559
|
+
}
|
|
15560
|
+
}
|
|
15561
|
+
if (fields.length)
|
|
15562
|
+
return failure2("invalid_input", "judgment values are invalid", { fields });
|
|
15563
|
+
const seen = new Set;
|
|
15564
|
+
judgment.refs = judgment.refs.filter((ref) => {
|
|
15565
|
+
const key = JSON.stringify(ref);
|
|
15566
|
+
return seen.has(key) ? false : (seen.add(key), true);
|
|
15567
|
+
});
|
|
15568
|
+
const parsed = judgmentSchema.safeParse(judgment);
|
|
15569
|
+
if (!parsed.success)
|
|
15570
|
+
return failure2("invalid_input", "judgment is invalid", {
|
|
15571
|
+
fields: parsed.error.issues.map((issue) => ({
|
|
15572
|
+
path: issue.path.join("."),
|
|
15573
|
+
reason: issue.message
|
|
15574
|
+
}))
|
|
15575
|
+
});
|
|
15576
|
+
return success(null, null, { judgment: parsed.data, ignored, legacy });
|
|
15577
|
+
}
|
|
15578
|
+
|
|
15579
|
+
// packages/workit-core/src/autonomy.ts
|
|
15580
|
+
import os3 from "node:os";
|
|
15581
|
+
import path15 from "node:path";
|
|
15582
|
+
var accountHome = () => {
|
|
15583
|
+
try {
|
|
15584
|
+
return os3.userInfo().homedir;
|
|
15585
|
+
} catch {
|
|
15586
|
+
return os3.homedir();
|
|
15587
|
+
}
|
|
15588
|
+
};
|
|
15589
|
+
var grantsHome = { resolve: accountHome };
|
|
15590
|
+
var grantsDir = () => path15.join(grantsHome.resolve(), ".config", "workit");
|
|
15591
|
+
var grantsOverride = () => {
|
|
15592
|
+
const configured = path15.resolve(resolveConfigDir());
|
|
15593
|
+
return configured === path15.resolve(grantsDir()) ? null : `the config directory is redirected to ${configured} (WORKFLOW_TOOLKIT_CONFIG, WORKFLOW_TOOLKIT_CONFIG_DIR or XDG_CONFIG_HOME); grants are read only from ${path15.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
|
|
15594
|
+
};
|
|
15595
|
+
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
15596
|
+
var DEFAULT_GRANTS = Object.freeze({
|
|
15597
|
+
push: true,
|
|
15598
|
+
pr: true,
|
|
15599
|
+
merge: false,
|
|
15600
|
+
release: false,
|
|
15601
|
+
rerun: true
|
|
15602
|
+
});
|
|
15603
|
+
var DEFAULT_ENDPOINT = "commit";
|
|
15604
|
+
var DEFAULT_VERIFICATION = "self";
|
|
15605
|
+
var verificationOf = (entry) => entry?.verification === "independent" ? "independent" : "self";
|
|
15606
|
+
var endpointOf = (entry) => entry?.defaultEndpoint === "pr" ? "pr" : "commit";
|
|
15607
|
+
var isGrantValue = (value) => value === true || value === false || value === "verified";
|
|
15608
|
+
var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
|
|
15609
|
+
var fromAutoApprove = (value) => {
|
|
15610
|
+
if (value === true)
|
|
15611
|
+
return { push: true, pr: true, merge: "verified" };
|
|
15612
|
+
if (!Array.isArray(value))
|
|
15613
|
+
return null;
|
|
15614
|
+
const grants = {};
|
|
15615
|
+
for (const item of value)
|
|
15616
|
+
if (item === "push" || item === "pr")
|
|
15617
|
+
grants[item] = true;
|
|
15618
|
+
else if (item === "merge")
|
|
15619
|
+
grants.merge = "verified";
|
|
15620
|
+
return Object.keys(grants).length > 0 ? grants : null;
|
|
15621
|
+
};
|
|
15622
|
+
function configuredGrants(entry) {
|
|
15623
|
+
const record = entry ?? {};
|
|
15624
|
+
const raw = record.autonomy;
|
|
15625
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
15626
|
+
const grants = {};
|
|
15627
|
+
for (const kind of GRANT_KINDS) {
|
|
15628
|
+
const value = raw[kind];
|
|
15629
|
+
if (isGrantValue(value))
|
|
15630
|
+
grants[kind] = normalized(kind, value);
|
|
15631
|
+
}
|
|
15632
|
+
return { grants, source: "autonomy" };
|
|
15633
|
+
}
|
|
15634
|
+
const legacy = fromAutoApprove(record.autoApprove);
|
|
15635
|
+
return legacy ? { grants: legacy, source: "autoApprove" } : { grants: {}, source: "default" };
|
|
15636
|
+
}
|
|
15637
|
+
function resolveAutonomy(cwd) {
|
|
15638
|
+
const override = grantsOverride();
|
|
15639
|
+
if (override)
|
|
15640
|
+
return {
|
|
15641
|
+
workspace: null,
|
|
15642
|
+
grants: { ...DEFAULT_GRANTS },
|
|
15643
|
+
configured: [],
|
|
15644
|
+
source: "default",
|
|
15645
|
+
accountConfigured: false,
|
|
15646
|
+
defaultEndpoint: DEFAULT_ENDPOINT,
|
|
15647
|
+
verification: DEFAULT_VERIFICATION,
|
|
15648
|
+
note: override
|
|
15649
|
+
};
|
|
15650
|
+
const workspace = resolveWorkspaceFrom(cwd, grantsDir(), process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined);
|
|
15651
|
+
const { grants, source } = configuredGrants(workspace);
|
|
15652
|
+
return {
|
|
15653
|
+
workspace: workspace?.name ?? null,
|
|
15654
|
+
grants: { ...DEFAULT_GRANTS, ...grants },
|
|
15655
|
+
configured: GRANT_KINDS.filter((kind) => grants[kind] !== undefined),
|
|
15656
|
+
source,
|
|
15657
|
+
accountConfigured: Boolean(workspace?.vcs?.account),
|
|
15658
|
+
defaultEndpoint: endpointOf(workspace),
|
|
15659
|
+
verification: verificationOf(workspace)
|
|
15660
|
+
};
|
|
15661
|
+
}
|
|
15662
|
+
|
|
14267
15663
|
// packages/workit-core/src/core/task-engine.ts
|
|
15664
|
+
var workspaceVerification = (root) => {
|
|
15665
|
+
try {
|
|
15666
|
+
return resolveAutonomy(root).verification;
|
|
15667
|
+
} catch {
|
|
15668
|
+
return "self";
|
|
15669
|
+
}
|
|
15670
|
+
};
|
|
14268
15671
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
14269
15672
|
kind,
|
|
14270
15673
|
host: context.caller.host,
|
|
@@ -14457,22 +15860,7 @@ var mapRef = (ref, ids) => {
|
|
|
14457
15860
|
return ref;
|
|
14458
15861
|
return { ...ref, id: ids.get(ref.id) ?? ref.id };
|
|
14459
15862
|
};
|
|
14460
|
-
var portableRefs = (refs) => refs.filter((ref) => ref.kind === "file" || ref.kind === "record");
|
|
14461
15863
|
var portableRef = (ref) => ref && (ref.kind === "file" || ref.kind === "record") ? ref : null;
|
|
14462
|
-
var portableFact = (fact) => {
|
|
14463
|
-
const refs = portableRefs(fact.refs);
|
|
14464
|
-
return fact.basis === "observed" && refs.length === 0 ? { ...fact, basis: "unknown", refs } : { ...fact, refs };
|
|
14465
|
-
};
|
|
14466
|
-
var portableSignal = (signal) => {
|
|
14467
|
-
const refs = portableRefs(signal.refs);
|
|
14468
|
-
return signal.basis === "observed" && refs.length === 0 ? {
|
|
14469
|
-
...signal,
|
|
14470
|
-
value: "unknown",
|
|
14471
|
-
basis: "unknown",
|
|
14472
|
-
reason: "portable supporting reference was removed",
|
|
14473
|
-
refs
|
|
14474
|
-
} : { ...signal, refs };
|
|
14475
|
-
};
|
|
14476
15864
|
var importedProvenance = (context) => ({
|
|
14477
15865
|
kind: "imported",
|
|
14478
15866
|
host: context.caller.host,
|
|
@@ -14484,18 +15872,6 @@ var portableTask = (task) => {
|
|
|
14484
15872
|
if (typeof stripped !== "object" || stripped === null || Array.isArray(stripped))
|
|
14485
15873
|
throw new TypeError("portable task rewrite produced a non-record");
|
|
14486
15874
|
const clone = structuredClone(stripped);
|
|
14487
|
-
const demote = (facts, signals, consequences) => {
|
|
14488
|
-
for (const fact of facts)
|
|
14489
|
-
Object.assign(fact, portableFact(fact));
|
|
14490
|
-
for (const name of Object.keys(signals)) {
|
|
14491
|
-
const signal = signals[name];
|
|
14492
|
-
Object.assign(signal, portableSignal(signal));
|
|
14493
|
-
}
|
|
14494
|
-
for (const consequence of consequences)
|
|
14495
|
-
Object.assign(consequence.fact, portableFact(consequence.fact));
|
|
14496
|
-
};
|
|
14497
|
-
for (const entry of clone.assessments)
|
|
14498
|
-
demote(entry.data.facts, entry.data.signals, entry.data.consequences);
|
|
14499
15875
|
clone.candidates = [];
|
|
14500
15876
|
return clone;
|
|
14501
15877
|
};
|
|
@@ -14512,6 +15888,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
14512
15888
|
for (const entry of [
|
|
14513
15889
|
source.intent,
|
|
14514
15890
|
...source.assessments,
|
|
15891
|
+
...source.judgments ?? [],
|
|
14515
15892
|
...source.evidence,
|
|
14516
15893
|
...source.decisions,
|
|
14517
15894
|
...source.findings,
|
|
@@ -14535,9 +15912,16 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
14535
15912
|
id: fresh(entry.id),
|
|
14536
15913
|
recordedAt: timestamp,
|
|
14537
15914
|
provenance: imported,
|
|
14538
|
-
data: rewriteRecordRefs(data,
|
|
15915
|
+
data: rewriteRecordRefs(data, legacyAssessmentSchema, remap)
|
|
14539
15916
|
};
|
|
14540
15917
|
});
|
|
15918
|
+
const judgments = source.judgments?.map((entry) => ({
|
|
15919
|
+
...entry,
|
|
15920
|
+
id: fresh(entry.id),
|
|
15921
|
+
recordedAt: timestamp,
|
|
15922
|
+
provenance: imported,
|
|
15923
|
+
data: rewriteRecordRefs(entry.data, judgmentSchema, remap)
|
|
15924
|
+
}));
|
|
14541
15925
|
const evidence = source.evidence.map((entry) => ({
|
|
14542
15926
|
...entry,
|
|
14543
15927
|
id: fresh(entry.id),
|
|
@@ -14605,6 +15989,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
14605
15989
|
status: "paused",
|
|
14606
15990
|
closure: null,
|
|
14607
15991
|
assessments,
|
|
15992
|
+
...judgments ? { judgments } : {},
|
|
14608
15993
|
evidence,
|
|
14609
15994
|
decisions: decisions.map((entry) => {
|
|
14610
15995
|
const data = rewriteRecordRefs(entry.data, decisionSchema, remap);
|
|
@@ -14674,7 +16059,8 @@ class WorkitCore {
|
|
|
14674
16059
|
retryOmittedRevisions(request, run) {
|
|
14675
16060
|
return retryFilledRevisions(filledRevisions(request), run);
|
|
14676
16061
|
}
|
|
14677
|
-
implicitTask(family,
|
|
16062
|
+
implicitTask(family, raw) {
|
|
16063
|
+
const request = normalizeOperationInput(family, raw, this.context.caller.host);
|
|
14678
16064
|
if (typeof request !== "object" || request === null || Array.isArray(request))
|
|
14679
16065
|
return success(null, null, request);
|
|
14680
16066
|
const value = request;
|
|
@@ -14928,7 +16314,11 @@ class WorkitCore {
|
|
|
14928
16314
|
if ((this.context.workerId ?? null) !== null)
|
|
14929
16315
|
return failure2("permission_denied", "helpers cannot change task requirements");
|
|
14930
16316
|
this.fillRevisions(input, task.data);
|
|
14931
|
-
const
|
|
16317
|
+
const judged = normalizeJudgment(input.judgment, latestJudgment(task.data));
|
|
16318
|
+
if (!judged.ok)
|
|
16319
|
+
return judged;
|
|
16320
|
+
const judgment = judged.data.judgment;
|
|
16321
|
+
const resolved = this.resolve(task.data, judgment);
|
|
14932
16322
|
if (!resolved.ok)
|
|
14933
16323
|
return resolved;
|
|
14934
16324
|
if (input.action === "preview")
|
|
@@ -14939,16 +16329,17 @@ class WorkitCore {
|
|
|
14939
16329
|
if (current.status === "closed")
|
|
14940
16330
|
return failure2("invalid_transition", "closed task cannot be assessed");
|
|
14941
16331
|
const nextChange = diffPolicy(current.policy, resolved.data, "policy reassessed", mutation.now);
|
|
14942
|
-
const
|
|
16332
|
+
const entry = {
|
|
14943
16333
|
id: newId(),
|
|
14944
16334
|
recordedAt: mutation.now,
|
|
14945
16335
|
provenance: provenance(this.context),
|
|
14946
|
-
data:
|
|
16336
|
+
data: judgment
|
|
14947
16337
|
};
|
|
14948
16338
|
const next = {
|
|
14949
16339
|
...current,
|
|
14950
16340
|
constraints: this.context.constraints,
|
|
14951
|
-
|
|
16341
|
+
judgments: [...current.judgments ?? [], entry],
|
|
16342
|
+
critical: [...new Set([...current.critical ?? [], "judgments"])],
|
|
14952
16343
|
policy: resolved.data,
|
|
14953
16344
|
policyChanges: nextChange ? [...current.policyChanges, nextChange] : current.policyChanges
|
|
14954
16345
|
};
|
|
@@ -15150,9 +16541,14 @@ class WorkitCore {
|
|
|
15150
16541
|
return workspace;
|
|
15151
16542
|
if (!workspace.data)
|
|
15152
16543
|
return failure2("not_found", "workspace not found");
|
|
15153
|
-
|
|
16544
|
+
const binding = {
|
|
16545
|
+
...input.binding,
|
|
16546
|
+
taskId: input.binding.taskId ?? task.data.id,
|
|
16547
|
+
workspaceId: input.binding.workspaceId ?? workspace.data.id
|
|
16548
|
+
};
|
|
16549
|
+
if (binding.taskId !== task.data.id || binding.workspaceId !== workspace.data.id)
|
|
15154
16550
|
return failure2("invalid_input", "decision task or workspace binding is invalid");
|
|
15155
|
-
const content = verifyDecisionContentAtRoot(this.store.root,
|
|
16551
|
+
const content = verifyDecisionContentAtRoot(this.store.root, binding);
|
|
15156
16552
|
if (!content.ok)
|
|
15157
16553
|
return content;
|
|
15158
16554
|
const knownRequirements = new Set(task.data.policy?.requirements.map((item) => item.id) ?? []);
|
|
@@ -15160,7 +16556,7 @@ class WorkitCore {
|
|
|
15160
16556
|
return failure2("invalid_input", "decision references an unknown requirement");
|
|
15161
16557
|
const base = {
|
|
15162
16558
|
purpose: input.purpose,
|
|
15163
|
-
binding
|
|
16559
|
+
binding,
|
|
15164
16560
|
response: input.response,
|
|
15165
16561
|
requirementIds: input.requirementIds,
|
|
15166
16562
|
revoked: null
|
|
@@ -15519,16 +16915,12 @@ class WorkitCore {
|
|
|
15519
16915
|
now: trustedNow(this.context)
|
|
15520
16916
|
});
|
|
15521
16917
|
}
|
|
15522
|
-
resolve(task,
|
|
16918
|
+
resolve(task, judgment) {
|
|
15523
16919
|
return resolvePolicy({
|
|
15524
16920
|
intent: task.intent.data,
|
|
15525
|
-
|
|
16921
|
+
judgment,
|
|
15526
16922
|
constraints: this.context.constraints,
|
|
15527
|
-
|
|
15528
|
-
decisions: task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data),
|
|
15529
|
-
findings: task.findings.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data),
|
|
15530
|
-
requirements: task.origin || task.policy === null ? [] : task.policy.requirements
|
|
15531
|
-
}
|
|
16923
|
+
verification: workspaceVerification(this.context.root)
|
|
15532
16924
|
});
|
|
15533
16925
|
}
|
|
15534
16926
|
summary(task) {
|
|
@@ -15854,9 +17246,9 @@ class WorkitCore {
|
|
|
15854
17246
|
}
|
|
15855
17247
|
}
|
|
15856
17248
|
// packages/workit-core/src/core/repo-context.ts
|
|
15857
|
-
import { spawnSync as
|
|
15858
|
-
import { existsSync as
|
|
15859
|
-
import
|
|
17249
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
17250
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
|
|
17251
|
+
import path16 from "node:path";
|
|
15860
17252
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
15861
17253
|
try {
|
|
15862
17254
|
gitRevisionParts(value);
|
|
@@ -15883,7 +17275,7 @@ var invalidRange = (cwd) => ({
|
|
|
15883
17275
|
cwd
|
|
15884
17276
|
});
|
|
15885
17277
|
var runGit = (cwd, args) => {
|
|
15886
|
-
const result =
|
|
17278
|
+
const result = spawnSync9("git", args, { cwd, encoding: "utf8" });
|
|
15887
17279
|
return {
|
|
15888
17280
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
15889
17281
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -15894,11 +17286,11 @@ var repoRoot = (cwd) => {
|
|
|
15894
17286
|
const r = runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
15895
17287
|
return r.exitCode === 0 && r.stdout ? r.stdout : cwd;
|
|
15896
17288
|
};
|
|
15897
|
-
var
|
|
17289
|
+
var currentBranch2 = (cwd) => {
|
|
15898
17290
|
const r = runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
15899
17291
|
return r.exitCode === 0 && r.stdout ? r.stdout : "unknown";
|
|
15900
17292
|
};
|
|
15901
|
-
var
|
|
17293
|
+
var defaultBase2 = (cwd) => {
|
|
15902
17294
|
for (const ref of [
|
|
15903
17295
|
"origin/main",
|
|
15904
17296
|
"main",
|
|
@@ -15912,11 +17304,11 @@ var defaultBase = (cwd) => {
|
|
|
15912
17304
|
}
|
|
15913
17305
|
return "HEAD~1";
|
|
15914
17306
|
};
|
|
15915
|
-
var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${
|
|
17307
|
+
var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${defaultBase2(cwd)}...HEAD`;
|
|
15916
17308
|
var isProtectedBranch = (branch) => ["main", "master", "develop", "prod", "production"].includes(branch);
|
|
15917
17309
|
var isPrBranch = (branch) => branch.startsWith("feature/") || branch.startsWith("bugfix/");
|
|
15918
17310
|
function resolvePrBranchContext(cwd) {
|
|
15919
|
-
const branch =
|
|
17311
|
+
const branch = currentBranch2(cwd);
|
|
15920
17312
|
if (isProtectedBranch(branch)) {
|
|
15921
17313
|
return {
|
|
15922
17314
|
ok: false,
|
|
@@ -15990,7 +17382,7 @@ var findPrTemplate = (cwd) => {
|
|
|
15990
17382
|
]) {
|
|
15991
17383
|
const parts = rel.split("/");
|
|
15992
17384
|
const base = parts[parts.length - 1];
|
|
15993
|
-
const dir = parts.length > 1 ?
|
|
17385
|
+
const dir = parts.length > 1 ? path16.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
15994
17386
|
const actual = probeCaseInsensitive(dir, base);
|
|
15995
17387
|
if (actual)
|
|
15996
17388
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -16021,8 +17413,8 @@ var diffStatForRange = (cwd, range) => {
|
|
|
16021
17413
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
16022
17414
|
};
|
|
16023
17415
|
var packageScripts = (cwd, keys) => {
|
|
16024
|
-
const pkgPath =
|
|
16025
|
-
if (!
|
|
17416
|
+
const pkgPath = path16.join(cwd, "package.json");
|
|
17417
|
+
if (!existsSync6(pkgPath))
|
|
16026
17418
|
return [];
|
|
16027
17419
|
try {
|
|
16028
17420
|
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
@@ -16047,9 +17439,9 @@ var documentationFiles = (cwd) => {
|
|
|
16047
17439
|
if (entry.isDirectory()) {
|
|
16048
17440
|
if (excluded.has(entry.name))
|
|
16049
17441
|
continue;
|
|
16050
|
-
walk(
|
|
17442
|
+
walk(path16.join(dir, entry.name), depth + 1);
|
|
16051
17443
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
16052
|
-
matches.push("./" +
|
|
17444
|
+
matches.push("./" + path16.relative(cwd, path16.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
16053
17445
|
}
|
|
16054
17446
|
}
|
|
16055
17447
|
};
|
|
@@ -16057,7 +17449,7 @@ var documentationFiles = (cwd) => {
|
|
|
16057
17449
|
return matches.toSorted().slice(0, 200);
|
|
16058
17450
|
};
|
|
16059
17451
|
var readTrimmed2 = (file, maxLines) => {
|
|
16060
|
-
if (!
|
|
17452
|
+
if (!existsSync6(file))
|
|
16061
17453
|
return "";
|
|
16062
17454
|
const lines = readFileSync5(file, "utf8").split(`
|
|
16063
17455
|
`);
|
|
@@ -16065,7 +17457,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
16065
17457
|
`);
|
|
16066
17458
|
};
|
|
16067
17459
|
function prReadyContext(root, range) {
|
|
16068
|
-
const cwd =
|
|
17460
|
+
const cwd = path16.resolve(repoRoot(root));
|
|
16069
17461
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
16070
17462
|
return invalidRange(cwd);
|
|
16071
17463
|
let stdout = "";
|
|
@@ -16081,7 +17473,7 @@ function prReadyContext(root, range) {
|
|
|
16081
17473
|
stdout += printSection("Repository");
|
|
16082
17474
|
stdout += `root: ${cwd}
|
|
16083
17475
|
`;
|
|
16084
|
-
stdout += `branch: ${
|
|
17476
|
+
stdout += `branch: ${currentBranch2(cwd)}
|
|
16085
17477
|
`;
|
|
16086
17478
|
stdout += `range: ${range}
|
|
16087
17479
|
`;
|
|
@@ -16123,7 +17515,7 @@ ${status.stderr}` : "");
|
|
|
16123
17515
|
stdout += `template_path: ${template}
|
|
16124
17516
|
|
|
16125
17517
|
`;
|
|
16126
|
-
stdout += readTrimmed2(
|
|
17518
|
+
stdout += readTrimmed2(path16.join(cwd, template), 220);
|
|
16127
17519
|
} else {
|
|
16128
17520
|
stdout += `template_path: none
|
|
16129
17521
|
|
|
@@ -16133,7 +17525,7 @@ ${status.stderr}` : "");
|
|
|
16133
17525
|
stdout += `
|
|
16134
17526
|
`;
|
|
16135
17527
|
stdout += printSection("Recent Validation Signals");
|
|
16136
|
-
if (
|
|
17528
|
+
if (existsSync6(path16.join(cwd, "package.json"))) {
|
|
16137
17529
|
stdout += `package.json detected. Common scripts:
|
|
16138
17530
|
`;
|
|
16139
17531
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -16142,7 +17534,7 @@ ${status.stderr}` : "");
|
|
|
16142
17534
|
`) + `
|
|
16143
17535
|
`;
|
|
16144
17536
|
}
|
|
16145
|
-
if (
|
|
17537
|
+
if (existsSync6(path16.join(cwd, "Cargo.toml")) || existsSync6(path16.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
16146
17538
|
stdout += `Rust project detected.
|
|
16147
17539
|
`;
|
|
16148
17540
|
}
|
|
@@ -16175,7 +17567,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
16175
17567
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
16176
17568
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
16177
17569
|
function changelogContext(root, range) {
|
|
16178
|
-
const cwd =
|
|
17570
|
+
const cwd = path16.resolve(repoRoot(root));
|
|
16179
17571
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
16180
17572
|
return invalidRange(cwd);
|
|
16181
17573
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -16185,7 +17577,7 @@ function changelogContext(root, range) {
|
|
|
16185
17577
|
stdout += printSection("Repository");
|
|
16186
17578
|
stdout += `root: ${cwd}
|
|
16187
17579
|
`;
|
|
16188
|
-
stdout += `branch: ${
|
|
17580
|
+
stdout += `branch: ${currentBranch2(cwd)}
|
|
16189
17581
|
`;
|
|
16190
17582
|
stdout += `range: ${resolvedRange}
|
|
16191
17583
|
`;
|
|
@@ -16193,8 +17585,8 @@ function changelogContext(root, range) {
|
|
|
16193
17585
|
stdout += CHANGELOG_RULES + `
|
|
16194
17586
|
`;
|
|
16195
17587
|
stdout += printSection("Existing CHANGELOG.md");
|
|
16196
|
-
const changelogPath =
|
|
16197
|
-
if (
|
|
17588
|
+
const changelogPath = path16.join(cwd, "CHANGELOG.md");
|
|
17589
|
+
if (existsSync6(changelogPath)) {
|
|
16198
17590
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
16199
17591
|
`;
|
|
16200
17592
|
} else {
|
|
@@ -16213,7 +17605,7 @@ function changelogContext(root, range) {
|
|
|
16213
17605
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
16214
17606
|
}
|
|
16215
17607
|
function docsRefreshContext(root, range) {
|
|
16216
|
-
const cwd =
|
|
17608
|
+
const cwd = path16.resolve(repoRoot(root));
|
|
16217
17609
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
16218
17610
|
return invalidRange(cwd);
|
|
16219
17611
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -16223,7 +17615,7 @@ function docsRefreshContext(root, range) {
|
|
|
16223
17615
|
stdout += printSection("Repository");
|
|
16224
17616
|
stdout += `root: ${cwd}
|
|
16225
17617
|
`;
|
|
16226
|
-
stdout += `branch: ${
|
|
17618
|
+
stdout += `branch: ${currentBranch2(cwd)}
|
|
16227
17619
|
`;
|
|
16228
17620
|
stdout += `range: ${resolvedRange}
|
|
16229
17621
|
`;
|
|
@@ -16235,8 +17627,8 @@ function docsRefreshContext(root, range) {
|
|
|
16235
17627
|
`) + `
|
|
16236
17628
|
`;
|
|
16237
17629
|
stdout += printSection("README Preview");
|
|
16238
|
-
const readme =
|
|
16239
|
-
if (
|
|
17630
|
+
const readme = path16.join(cwd, "README.md");
|
|
17631
|
+
if (existsSync6(readme)) {
|
|
16240
17632
|
stdout += readTrimmed2(readme, 220) + `
|
|
16241
17633
|
`;
|
|
16242
17634
|
} else {
|
|
@@ -16244,8 +17636,8 @@ function docsRefreshContext(root, range) {
|
|
|
16244
17636
|
`;
|
|
16245
17637
|
}
|
|
16246
17638
|
stdout += printSection("Package Scripts");
|
|
16247
|
-
const pkgPath =
|
|
16248
|
-
if (
|
|
17639
|
+
const pkgPath = path16.join(cwd, "package.json");
|
|
17640
|
+
if (existsSync6(pkgPath)) {
|
|
16249
17641
|
try {
|
|
16250
17642
|
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
16251
17643
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
@@ -16258,7 +17650,7 @@ function docsRefreshContext(root, range) {
|
|
|
16258
17650
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
16259
17651
|
}
|
|
16260
17652
|
function releaseNotesContext(root, rangeOrTag) {
|
|
16261
|
-
const cwd =
|
|
17653
|
+
const cwd = path16.resolve(repoRoot(root));
|
|
16262
17654
|
if (!rangeOrTag) {
|
|
16263
17655
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
16264
17656
|
`, exitCode: 1, cwd };
|
|
@@ -16272,7 +17664,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
16272
17664
|
stdout += printSection("Repository");
|
|
16273
17665
|
stdout += `root: ${cwd}
|
|
16274
17666
|
`;
|
|
16275
|
-
stdout += `branch: ${
|
|
17667
|
+
stdout += `branch: ${currentBranch2(cwd)}
|
|
16276
17668
|
`;
|
|
16277
17669
|
stdout += `requested: ${rangeOrTag}
|
|
16278
17670
|
`;
|
|
@@ -16296,7 +17688,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
16296
17688
|
`;
|
|
16297
17689
|
stdout += printSection("Existing Release Files");
|
|
16298
17690
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
16299
|
-
if (
|
|
17691
|
+
if (existsSync6(path16.join(cwd, rel)))
|
|
16300
17692
|
stdout += rel + `
|
|
16301
17693
|
`;
|
|
16302
17694
|
}
|
|
@@ -16324,12 +17716,12 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
16324
17716
|
}
|
|
16325
17717
|
|
|
16326
17718
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
16327
|
-
import { spawnSync as
|
|
17719
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
16328
17720
|
|
|
16329
17721
|
// packages/workit-core/src/core/pr-create.ts
|
|
16330
|
-
import
|
|
16331
|
-
import
|
|
16332
|
-
import { spawnSync as
|
|
17722
|
+
import fs13 from "node:fs";
|
|
17723
|
+
import path17 from "node:path";
|
|
17724
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
16333
17725
|
// packages/workit-core/src/forge/pr-body.ts
|
|
16334
17726
|
function parseGhIssue(value) {
|
|
16335
17727
|
const m = /issues\/(\d+)/.exec(value);
|
|
@@ -16339,13 +17731,13 @@ function parseGhIssue(value) {
|
|
|
16339
17731
|
// packages/workit-core/src/core/pr-create.ts
|
|
16340
17732
|
function whichOnPath(tool) {
|
|
16341
17733
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
16342
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
17734
|
+
for (const dir of (process.env.PATH ?? "").split(path17.delimiter)) {
|
|
16343
17735
|
if (!dir)
|
|
16344
17736
|
continue;
|
|
16345
17737
|
for (const name of names) {
|
|
16346
|
-
const candidate =
|
|
17738
|
+
const candidate = path17.join(dir, name);
|
|
16347
17739
|
try {
|
|
16348
|
-
|
|
17740
|
+
fs13.accessSync(candidate, fs13.constants.X_OK);
|
|
16349
17741
|
return candidate;
|
|
16350
17742
|
} catch {}
|
|
16351
17743
|
}
|
|
@@ -16354,7 +17746,7 @@ function whichOnPath(tool) {
|
|
|
16354
17746
|
}
|
|
16355
17747
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
16356
17748
|
var pushRemote = (cwd) => {
|
|
16357
|
-
const result =
|
|
17749
|
+
const result = spawnSync10("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
16358
17750
|
cwd,
|
|
16359
17751
|
encoding: "utf8"
|
|
16360
17752
|
});
|
|
@@ -16385,7 +17777,7 @@ var defaultCli = (bin, available) => {
|
|
|
16385
17777
|
if (!available)
|
|
16386
17778
|
return null;
|
|
16387
17779
|
return (args, root) => {
|
|
16388
|
-
const out =
|
|
17780
|
+
const out = spawnSync11(bin, args, {
|
|
16389
17781
|
cwd: root,
|
|
16390
17782
|
encoding: "utf8",
|
|
16391
17783
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -16463,8 +17855,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
16463
17855
|
};
|
|
16464
17856
|
|
|
16465
17857
|
// packages/workit-core/src/core/youtrack.ts
|
|
16466
|
-
import
|
|
16467
|
-
import
|
|
17858
|
+
import fs14 from "node:fs";
|
|
17859
|
+
import path18 from "node:path";
|
|
16468
17860
|
|
|
16469
17861
|
// packages/workit-core/src/core/templates.ts
|
|
16470
17862
|
var repoRoot2 = assetRoot();
|
|
@@ -16477,22 +17869,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
16477
17869
|
// packages/workit-core/src/core/youtrack.ts
|
|
16478
17870
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
16479
17871
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
16480
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
16481
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
17872
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(configDir(), "youtrack.json");
|
|
17873
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(resolveConfigDir(), "youtrack.json");
|
|
16482
17874
|
var youTrackTokenModeOk = (p) => {
|
|
16483
17875
|
if (process.platform === "win32")
|
|
16484
17876
|
return true;
|
|
16485
|
-
const mode =
|
|
17877
|
+
const mode = fs14.statSync(p).mode & 511;
|
|
16486
17878
|
return mode === 384;
|
|
16487
17879
|
};
|
|
16488
17880
|
function readYouTrackConfig(required) {
|
|
16489
17881
|
const cfgPath = youTrackConfigPath();
|
|
16490
|
-
if (!
|
|
17882
|
+
if (!fs14.existsSync(cfgPath)) {
|
|
16491
17883
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
16492
17884
|
}
|
|
16493
17885
|
let parsed;
|
|
16494
17886
|
try {
|
|
16495
|
-
parsed = JSON.parse(
|
|
17887
|
+
parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
|
|
16496
17888
|
} catch {
|
|
16497
17889
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
16498
17890
|
}
|
|
@@ -16503,11 +17895,11 @@ function readYouTrackConfig(required) {
|
|
|
16503
17895
|
}
|
|
16504
17896
|
var readYouTrackContextConfig = () => {
|
|
16505
17897
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
16506
|
-
if (!
|
|
17898
|
+
if (!fs14.existsSync(cfgPath))
|
|
16507
17899
|
return { data: {} };
|
|
16508
17900
|
let parsed;
|
|
16509
17901
|
try {
|
|
16510
|
-
parsed = JSON.parse(
|
|
17902
|
+
parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
|
|
16511
17903
|
} catch {
|
|
16512
17904
|
return { error: "YouTrack configuration is unavailable" };
|
|
16513
17905
|
}
|
|
@@ -16546,12 +17938,12 @@ var youTrackToken = () => {
|
|
|
16546
17938
|
if ("error" in loaded)
|
|
16547
17939
|
return loaded;
|
|
16548
17940
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
16549
|
-
const tokenPath = tokenFile ?
|
|
16550
|
-
if (!tokenPath || !
|
|
17941
|
+
const tokenPath = tokenFile ? path18.isAbsolute(tokenFile) ? path18.resolve(tokenFile) : path18.resolve(process.cwd(), tokenFile) : "";
|
|
17942
|
+
if (!tokenPath || !fs14.existsSync(tokenPath))
|
|
16551
17943
|
return { error: "missing youtrack.token" };
|
|
16552
17944
|
if (!youTrackTokenModeOk(tokenPath))
|
|
16553
17945
|
return { error: "youtrack.token mode must be 0600" };
|
|
16554
|
-
const token =
|
|
17946
|
+
const token = fs14.readFileSync(tokenPath, "utf8").trim();
|
|
16555
17947
|
if (!token)
|
|
16556
17948
|
return { error: "empty token file" };
|
|
16557
17949
|
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
@@ -16616,9 +18008,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
16616
18008
|
} catch {
|
|
16617
18009
|
continue;
|
|
16618
18010
|
}
|
|
16619
|
-
if (!
|
|
18011
|
+
if (!fs14.existsSync(full))
|
|
16620
18012
|
continue;
|
|
16621
|
-
const text =
|
|
18013
|
+
const text = fs14.readFileSync(full, "utf8");
|
|
16622
18014
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
16623
18015
|
if (m)
|
|
16624
18016
|
return m[1];
|
|
@@ -16903,22 +18295,22 @@ function resolveGitRevision(root, value) {
|
|
|
16903
18295
|
}
|
|
16904
18296
|
function resolveInside(root, candidate) {
|
|
16905
18297
|
const base = realpathSync5(root);
|
|
16906
|
-
const target =
|
|
16907
|
-
if (target !== base && !target.startsWith(base +
|
|
18298
|
+
const target = path19.resolve(base, candidate);
|
|
18299
|
+
if (target !== base && !target.startsWith(base + path19.sep)) {
|
|
16908
18300
|
throw new Error("path must stay inside repository root");
|
|
16909
18301
|
}
|
|
16910
18302
|
let ancestor = target;
|
|
16911
|
-
while (!
|
|
16912
|
-
ancestor =
|
|
18303
|
+
while (!existsSync7(ancestor))
|
|
18304
|
+
ancestor = path19.dirname(ancestor);
|
|
16913
18305
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
16914
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
18306
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path19.sep)) {
|
|
16915
18307
|
throw new Error("path must stay inside repository root");
|
|
16916
18308
|
}
|
|
16917
18309
|
return target;
|
|
16918
18310
|
}
|
|
16919
18311
|
function run2(root, executable, args, env = {}) {
|
|
16920
18312
|
const cwd = realpathSync5(root);
|
|
16921
|
-
const result =
|
|
18313
|
+
const result = spawnSync12(executable, args, {
|
|
16922
18314
|
cwd,
|
|
16923
18315
|
encoding: "utf8",
|
|
16924
18316
|
env: { ...process.env, ...env }
|
|
@@ -16955,7 +18347,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
16955
18347
|
};
|
|
16956
18348
|
});
|
|
16957
18349
|
// packages/workit-core/src/hooks/context.ts
|
|
16958
|
-
import
|
|
18350
|
+
import path20 from "node:path";
|
|
16959
18351
|
var MIGRATION_PENDING_NOTE = "workit migration pending — run `workit task status`";
|
|
16960
18352
|
var migrationPending = (result) => !result.ok && result.code === "needs_input" && (result.error ?? "").startsWith(MIGRATION_PENDING);
|
|
16961
18353
|
var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
|
|
@@ -17034,7 +18426,7 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
17034
18426
|
return null;
|
|
17035
18427
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
17036
18428
|
const observed = task.data.evidence.some(isObservedCheck);
|
|
17037
|
-
const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [
|
|
18429
|
+
const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path20.resolve(store.root, ref.path)] : []));
|
|
17038
18430
|
const observedFiles = filesKey(files);
|
|
17039
18431
|
const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
|
|
17040
18432
|
if (!compact.ok)
|
|
@@ -17051,17 +18443,424 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
17051
18443
|
cache.delete(cache.keys().next().value);
|
|
17052
18444
|
return compact.data;
|
|
17053
18445
|
}
|
|
18446
|
+
var utcNow = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
18447
|
+
var hookOperationContext = (input, descriptor) => ({
|
|
18448
|
+
root: input.cwd,
|
|
18449
|
+
caller: { host: input.host, actor: input.session.id },
|
|
18450
|
+
callerAttested: false,
|
|
18451
|
+
capabilities: capabilitiesFor(descriptor, { "session.start": true }),
|
|
18452
|
+
constraints: [],
|
|
18453
|
+
now: utcNow
|
|
18454
|
+
});
|
|
18455
|
+
var sessionContextText = (input, descriptor, options) => {
|
|
18456
|
+
const session = { host: input.host, handle: input.session.id };
|
|
18457
|
+
let compact = "";
|
|
18458
|
+
let offer = null;
|
|
18459
|
+
try {
|
|
18460
|
+
const store = new TaskStore(input.cwd);
|
|
18461
|
+
const listed = store.listTaskIndex();
|
|
18462
|
+
if (migrationPending(listed))
|
|
18463
|
+
return `<workit-contract>
|
|
18464
|
+
${invariantBootstrap()}
|
|
18465
|
+
${MIGRATION_PENDING_NOTE}${options.addendum ? `
|
|
18466
|
+
${options.addendum}` : ""}
|
|
18467
|
+
</workit-contract>`;
|
|
18468
|
+
if (!listed.ok)
|
|
18469
|
+
throw new Error(listed.error);
|
|
18470
|
+
const entry = currentTaskEntry(listed.data, session, descriptor.context.task) ?? implicitTaskEntry(store, listed.data, session);
|
|
18471
|
+
const text = entry ? entryCompactContext(store, entry, session, hookOperationContext(input, descriptor)) : null;
|
|
18472
|
+
if (text)
|
|
18473
|
+
compact = `
|
|
18474
|
+
<workit-task-context>${text}</workit-task-context>`;
|
|
18475
|
+
if (options.offer)
|
|
18476
|
+
offer = unfinishedTaskOffer(listed.data, session, entry?.id ?? null);
|
|
18477
|
+
} catch {
|
|
18478
|
+
compact = `
|
|
18479
|
+
[workit diagnostic: task state unavailable]`;
|
|
18480
|
+
}
|
|
18481
|
+
return `<workit-contract>
|
|
18482
|
+
${invariantBootstrap()}${compact}${offer ? `
|
|
18483
|
+
${offer}` : ""}${options.addendum ? `
|
|
18484
|
+
${options.addendum}` : ""}
|
|
18485
|
+
</workit-contract>`;
|
|
18486
|
+
};
|
|
18487
|
+
var turnContextText = (input, descriptor) => {
|
|
18488
|
+
try {
|
|
18489
|
+
const text = sessionCompactContext(new TaskStore(input.cwd), { host: input.host, handle: input.session.id }, hookOperationContext(input, descriptor), descriptor.context.task);
|
|
18490
|
+
if (text === MIGRATION_PENDING_NOTE)
|
|
18491
|
+
return text;
|
|
18492
|
+
return text ? `<workit-task-context>${text}</workit-task-context>` : null;
|
|
18493
|
+
} catch {
|
|
18494
|
+
return null;
|
|
18495
|
+
}
|
|
18496
|
+
};
|
|
17054
18497
|
// packages/workit-core/src/hooks/policy.ts
|
|
17055
18498
|
var shellPolicy = (cwd, command) => {
|
|
17056
18499
|
const policy = shellBranchPolicyViolation(cwd, command);
|
|
17057
18500
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
17058
18501
|
};
|
|
18502
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
18503
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
18504
|
+
import path21 from "node:path";
|
|
18505
|
+
var NONE2 = { kind: "none" };
|
|
18506
|
+
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
18507
|
+
var canonical2 = (file) => {
|
|
18508
|
+
let rest = "";
|
|
18509
|
+
for (let dir = path21.resolve(file);; dir = path21.dirname(dir)) {
|
|
18510
|
+
try {
|
|
18511
|
+
return path21.join(realpathSync6(dir), rest);
|
|
18512
|
+
} catch {
|
|
18513
|
+
if (path21.dirname(dir) === dir)
|
|
18514
|
+
return path21.resolve(file);
|
|
18515
|
+
rest = rest ? path21.join(path21.basename(dir), rest) : path21.basename(dir);
|
|
18516
|
+
}
|
|
18517
|
+
}
|
|
18518
|
+
};
|
|
18519
|
+
var exempt = (cwd, root, file, plans) => {
|
|
18520
|
+
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
18521
|
+
const relative = path21.relative(fold(canonical2(root)), fold(canonical2(path21.resolve(cwd, file))));
|
|
18522
|
+
if (relative.startsWith("..") || path21.isAbsolute(relative))
|
|
18523
|
+
return true;
|
|
18524
|
+
const posix = relative.split(path21.sep).join("/");
|
|
18525
|
+
const segments = posix.split("/");
|
|
18526
|
+
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
18527
|
+
};
|
|
18528
|
+
var segmentsOf = (command) => {
|
|
18529
|
+
const segments = [];
|
|
18530
|
+
let current = { words: [], redirects: [] };
|
|
18531
|
+
let word = "";
|
|
18532
|
+
let inWord = false;
|
|
18533
|
+
let pending = null;
|
|
18534
|
+
const heredocs = [];
|
|
18535
|
+
const flushWord = () => {
|
|
18536
|
+
if (!inWord)
|
|
18537
|
+
return;
|
|
18538
|
+
if (pending === "redirect") {
|
|
18539
|
+
if (!word.startsWith("&"))
|
|
18540
|
+
current.redirects.push(word);
|
|
18541
|
+
} else if (pending === "heredoc")
|
|
18542
|
+
heredocs.push(word.replace(/^-/, ""));
|
|
18543
|
+
else if (pending !== "input")
|
|
18544
|
+
current.words.push(word);
|
|
18545
|
+
pending = null;
|
|
18546
|
+
word = "";
|
|
18547
|
+
inWord = false;
|
|
18548
|
+
};
|
|
18549
|
+
const flushSegment = () => {
|
|
18550
|
+
flushWord();
|
|
18551
|
+
if (current.words.length || current.redirects.length)
|
|
18552
|
+
segments.push(current);
|
|
18553
|
+
current = { words: [], redirects: [] };
|
|
18554
|
+
};
|
|
18555
|
+
for (let index = 0;index < command.length; index++) {
|
|
18556
|
+
const char = command[index];
|
|
18557
|
+
if (char === "'" || char === '"') {
|
|
18558
|
+
const end = command.indexOf(char, index + 1);
|
|
18559
|
+
word += command.slice(index + 1, end < 0 ? undefined : end);
|
|
18560
|
+
inWord = true;
|
|
18561
|
+
index = end < 0 ? command.length : end;
|
|
18562
|
+
} else if (char === "\\" && index + 1 < command.length) {
|
|
18563
|
+
word += command[++index];
|
|
18564
|
+
inWord = true;
|
|
18565
|
+
} else if (char === `
|
|
18566
|
+
`) {
|
|
18567
|
+
flushSegment();
|
|
18568
|
+
while (heredocs.length) {
|
|
18569
|
+
const delimiter = heredocs.shift();
|
|
18570
|
+
const lines = command.slice(index + 1).split(`
|
|
18571
|
+
`);
|
|
18572
|
+
const at = lines.findIndex((line) => line.trim() === delimiter);
|
|
18573
|
+
const skipped = (at < 0 ? lines : lines.slice(0, at + 1)).join(`
|
|
18574
|
+
`).length + 1;
|
|
18575
|
+
index += skipped;
|
|
18576
|
+
}
|
|
18577
|
+
} else if (/\s/.test(char))
|
|
18578
|
+
flushWord();
|
|
18579
|
+
else if (";&|()".includes(char)) {
|
|
18580
|
+
if (char === "&" && command[index + 1] === ">")
|
|
18581
|
+
continue;
|
|
18582
|
+
flushSegment();
|
|
18583
|
+
} else if (char === ">") {
|
|
18584
|
+
if (inWord && /^\d$/.test(word)) {
|
|
18585
|
+
word = "";
|
|
18586
|
+
inWord = false;
|
|
18587
|
+
} else
|
|
18588
|
+
flushWord();
|
|
18589
|
+
if (command[index + 1] === ">")
|
|
18590
|
+
index++;
|
|
18591
|
+
if (command[index + 1] === "&") {
|
|
18592
|
+
index++;
|
|
18593
|
+
word = "&";
|
|
18594
|
+
inWord = true;
|
|
18595
|
+
}
|
|
18596
|
+
pending = "redirect";
|
|
18597
|
+
} else if (char === "<") {
|
|
18598
|
+
flushWord();
|
|
18599
|
+
if (command[index + 1] === "<") {
|
|
18600
|
+
index++;
|
|
18601
|
+
if (command[index + 1] === "<")
|
|
18602
|
+
index++;
|
|
18603
|
+
pending = "heredoc";
|
|
18604
|
+
} else
|
|
18605
|
+
pending = "input";
|
|
18606
|
+
} else {
|
|
18607
|
+
word += char;
|
|
18608
|
+
inWord = true;
|
|
18609
|
+
}
|
|
18610
|
+
}
|
|
18611
|
+
flushSegment();
|
|
18612
|
+
return segments;
|
|
18613
|
+
};
|
|
18614
|
+
var PREFIXES = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
18615
|
+
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
18616
|
+
var editsOf = (input) => {
|
|
18617
|
+
let words = input;
|
|
18618
|
+
while (words.length && (PREFIXES.has(words[0]) || /^\w+=/.test(words[0])))
|
|
18619
|
+
words = words.slice(1);
|
|
18620
|
+
const [verb, ...args] = words;
|
|
18621
|
+
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
18622
|
+
if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
|
|
18623
|
+
return [];
|
|
18624
|
+
switch (name) {
|
|
18625
|
+
case "touch":
|
|
18626
|
+
case "mkdir":
|
|
18627
|
+
case "rm":
|
|
18628
|
+
case "rmdir":
|
|
18629
|
+
case "tee":
|
|
18630
|
+
case "mv":
|
|
18631
|
+
case "truncate":
|
|
18632
|
+
return operands(args);
|
|
18633
|
+
case "cp":
|
|
18634
|
+
case "ln":
|
|
18635
|
+
case "install": {
|
|
18636
|
+
const target = args.indexOf("-t");
|
|
18637
|
+
if (target >= 0)
|
|
18638
|
+
return args[target + 1] ? [args[target + 1]] : null;
|
|
18639
|
+
const last = operands(args).at(-1);
|
|
18640
|
+
return last ? [last] : null;
|
|
18641
|
+
}
|
|
18642
|
+
case "dd":
|
|
18643
|
+
return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
|
|
18644
|
+
case "sed":
|
|
18645
|
+
case "perl": {
|
|
18646
|
+
if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
|
|
18647
|
+
return [];
|
|
18648
|
+
const scripted = args.some((item) => item === "-e" || item === "-f");
|
|
18649
|
+
const files = [];
|
|
18650
|
+
for (let index = 0;index < args.length; index++) {
|
|
18651
|
+
if (args[index] === "-e" || args[index] === "-f")
|
|
18652
|
+
index++;
|
|
18653
|
+
else if (!args[index].startsWith("-"))
|
|
18654
|
+
files.push(args[index]);
|
|
18655
|
+
}
|
|
18656
|
+
return scripted ? files : files.slice(1);
|
|
18657
|
+
}
|
|
18658
|
+
case "patch":
|
|
18659
|
+
return null;
|
|
18660
|
+
case "set-content":
|
|
18661
|
+
case "add-content":
|
|
18662
|
+
case "out-file":
|
|
18663
|
+
case "new-item":
|
|
18664
|
+
case "ni":
|
|
18665
|
+
case "remove-item":
|
|
18666
|
+
case "del":
|
|
18667
|
+
case "copy-item":
|
|
18668
|
+
case "move-item": {
|
|
18669
|
+
const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
|
|
18670
|
+
if (flag >= 0)
|
|
18671
|
+
return args[flag + 1] ? [args[flag + 1]] : null;
|
|
18672
|
+
const files = operands(args);
|
|
18673
|
+
if (name === "move-item")
|
|
18674
|
+
return files.length ? files : null;
|
|
18675
|
+
const file = name === "copy-item" ? files.at(-1) : files[0];
|
|
18676
|
+
return file ? [file] : null;
|
|
18677
|
+
}
|
|
18678
|
+
case "git": {
|
|
18679
|
+
const sub = operands(args)[0];
|
|
18680
|
+
if (sub === "apply")
|
|
18681
|
+
return null;
|
|
18682
|
+
if (sub === "restore" || sub === "mv" || sub === "rm")
|
|
18683
|
+
return operands(args).slice(1);
|
|
18684
|
+
if (sub === "checkout" && args.includes("--"))
|
|
18685
|
+
return args.slice(args.indexOf("--") + 1);
|
|
18686
|
+
return [];
|
|
18687
|
+
}
|
|
18688
|
+
default:
|
|
18689
|
+
return [];
|
|
18690
|
+
}
|
|
18691
|
+
};
|
|
18692
|
+
var shellWrites = (command) => {
|
|
18693
|
+
const targets = [];
|
|
18694
|
+
let unknown = false;
|
|
18695
|
+
for (const segment of segmentsOf(command)) {
|
|
18696
|
+
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
18697
|
+
const edits = editsOf(segment.words);
|
|
18698
|
+
if (edits === null)
|
|
18699
|
+
unknown = true;
|
|
18700
|
+
else
|
|
18701
|
+
targets.push(...edits);
|
|
18702
|
+
}
|
|
18703
|
+
if (!unknown && targets.length === 0)
|
|
18704
|
+
return { writes: false, targets: null };
|
|
18705
|
+
return { writes: true, targets: unknown ? null : targets };
|
|
18706
|
+
};
|
|
18707
|
+
var cache2 = new Map;
|
|
18708
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path21.resolve(root, file)) ?? "-").join("\x00");
|
|
18709
|
+
var gateFor = (cwd) => {
|
|
18710
|
+
const store = new TaskStore(cwd);
|
|
18711
|
+
const listed = store.listTaskIndex();
|
|
18712
|
+
if (!listed.ok)
|
|
18713
|
+
return null;
|
|
18714
|
+
const key = store.currentKey();
|
|
18715
|
+
if (!key.ok)
|
|
18716
|
+
return null;
|
|
18717
|
+
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
18718
|
+
if (!entry)
|
|
18719
|
+
return { blockers: [], root: store.root, plans: new Set };
|
|
18720
|
+
const ledger = ledgerPath(store.root);
|
|
18721
|
+
const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
|
|
18722
|
+
const hit = cache2.get(store.root);
|
|
18723
|
+
if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
|
|
18724
|
+
return hit;
|
|
18725
|
+
const task = store.readTask(entry.id);
|
|
18726
|
+
if (!task.ok)
|
|
18727
|
+
return null;
|
|
18728
|
+
const workspace = store.readWorkspace();
|
|
18729
|
+
const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
|
|
18730
|
+
const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
|
|
18731
|
+
const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
|
|
18732
|
+
return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
|
|
18733
|
+
});
|
|
18734
|
+
const gate = {
|
|
18735
|
+
blockers,
|
|
18736
|
+
root: store.root,
|
|
18737
|
+
plans,
|
|
18738
|
+
key: cacheKey,
|
|
18739
|
+
planKey: planKeyOf(store.root, plans)
|
|
18740
|
+
};
|
|
18741
|
+
cache2.set(store.root, gate);
|
|
18742
|
+
if (cache2.size > 32)
|
|
18743
|
+
cache2.delete(cache2.keys().next().value);
|
|
18744
|
+
return gate;
|
|
18745
|
+
};
|
|
18746
|
+
function writeGate(cwd, paths) {
|
|
18747
|
+
try {
|
|
18748
|
+
const gate = gateFor(cwd);
|
|
18749
|
+
if (!gate || gate.blockers.length === 0)
|
|
18750
|
+
return NONE2;
|
|
18751
|
+
if (paths && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
|
|
18752
|
+
return NONE2;
|
|
18753
|
+
const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
|
|
18754
|
+
return {
|
|
18755
|
+
kind: "deny",
|
|
18756
|
+
reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
|
|
18757
|
+
unblock: gate.blockers[0].reason
|
|
18758
|
+
};
|
|
18759
|
+
} catch {
|
|
18760
|
+
return NONE2;
|
|
18761
|
+
}
|
|
18762
|
+
}
|
|
18763
|
+
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
18764
|
+
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18765
|
+
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
18766
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
18767
|
+
var WRITE_TOOLS = new Set([
|
|
18768
|
+
"write",
|
|
18769
|
+
"edit",
|
|
18770
|
+
"multiedit",
|
|
18771
|
+
"notebookedit",
|
|
18772
|
+
"applypatch",
|
|
18773
|
+
"patch",
|
|
18774
|
+
"delete",
|
|
18775
|
+
"remove",
|
|
18776
|
+
"rename",
|
|
18777
|
+
"mkdir",
|
|
18778
|
+
"mv",
|
|
18779
|
+
"cp",
|
|
18780
|
+
"touch",
|
|
18781
|
+
"strreplace",
|
|
18782
|
+
"searchreplace",
|
|
18783
|
+
"editfile",
|
|
18784
|
+
"writefile"
|
|
18785
|
+
]);
|
|
18786
|
+
var PATH_KEYS = ["file_path", "filePath", "notebook_path", "path", "target_file", "file"];
|
|
18787
|
+
var writePaths = (input) => {
|
|
18788
|
+
if (!isRecord4(input))
|
|
18789
|
+
return [];
|
|
18790
|
+
const paths = PATH_KEYS.map((key) => input[key]).filter(nonEmpty2);
|
|
18791
|
+
for (const value of Object.values(input))
|
|
18792
|
+
if (typeof value === "string")
|
|
18793
|
+
for (const match of value.matchAll(/^\*\*\* (?:Add|Update|Delete) File: (.+)$/gm))
|
|
18794
|
+
paths.push(match[1].trim());
|
|
18795
|
+
return [...new Set(paths)];
|
|
18796
|
+
};
|
|
17059
18797
|
// packages/workit-core/src/hooks/handle.ts
|
|
18798
|
+
var usable2 = (value) => value === "native" || value === "partial";
|
|
18799
|
+
var NONE3 = { kind: "none" };
|
|
17060
18800
|
var offered = new Set;
|
|
18801
|
+
var CLAUDE_WORKTREE_IMPLEMENTER = "workit:implementer";
|
|
17061
18802
|
var CLAUDE_JUDGES = new Set(["workit:verifier", "workit:reviewer"]);
|
|
17062
|
-
|
|
17063
|
-
var
|
|
17064
|
-
|
|
18803
|
+
var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
|
|
18804
|
+
var subagentStartText = (host, session, descriptor, event) => {
|
|
18805
|
+
if (host === "claude_code" && event.agentType === CLAUDE_WORKTREE_IMPLEMENTER)
|
|
18806
|
+
return `Workit observed ${descriptor.label} subagent ${event.agentId} (${event.agentType}) working in its own git worktree: it may edit and commit there, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
18807
|
+
const readOnly = `Workit observed ${descriptor.label} subagent ${event.agentId} (${event.agentType}) as read-only/agent-guided.`;
|
|
18808
|
+
if (host === "claude_code" && event.agentType && CLAUDE_JUDGES.has(event.agentType) && event.agentId)
|
|
18809
|
+
return `${readOnly} Its own Workit session is ${subagentSession(session, event.agentId)}: record verdicts with \`workit ledger verdict <result> --session ${subagentSession(session, event.agentId)} ...\` so the ledger tells it apart from the author.`;
|
|
18810
|
+
return readOnly;
|
|
18811
|
+
};
|
|
18812
|
+
function handleHook(input, deps) {
|
|
18813
|
+
const { descriptor } = deps;
|
|
18814
|
+
const event = input.event;
|
|
18815
|
+
switch (event.kind) {
|
|
18816
|
+
case "session.start": {
|
|
18817
|
+
if (!usable2(descriptor.context.sessionStart))
|
|
18818
|
+
return NONE3;
|
|
18819
|
+
const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
|
|
18820
|
+
const offer = event.source === "startup" && (key === null || !offered.has(key));
|
|
18821
|
+
if (offer && key !== null)
|
|
18822
|
+
offered.add(key);
|
|
18823
|
+
const advisory = usable2(descriptor.events["write.pre"].support) ? null : `Workit cannot gate file writes on ${descriptor.label} (no pre-write hook): before-write requirements (an open product choice, a missing plan) are advisory here, so settle them before editing.`;
|
|
18824
|
+
const addendum = [deps.addendum, advisory].filter(Boolean).join(`
|
|
18825
|
+
`) || null;
|
|
18826
|
+
return {
|
|
18827
|
+
kind: "context",
|
|
18828
|
+
text: sessionContextText(input, descriptor, { offer, addendum })
|
|
18829
|
+
};
|
|
18830
|
+
}
|
|
18831
|
+
case "context.turn": {
|
|
18832
|
+
if (!usable2(descriptor.context.perTurn))
|
|
18833
|
+
return NONE3;
|
|
18834
|
+
const text = turnContextText(input, descriptor);
|
|
18835
|
+
return text ? { kind: "context", text } : NONE3;
|
|
18836
|
+
}
|
|
18837
|
+
case "shell.pre": {
|
|
18838
|
+
const policy = usable2(descriptor.shellPolicy.deny) ? shellPolicy(input.cwd, event.command) : NONE3;
|
|
18839
|
+
if (policy.kind !== "none" || !usable2(descriptor.events["write.pre"].support))
|
|
18840
|
+
return policy;
|
|
18841
|
+
const writes = shellWrites(event.command);
|
|
18842
|
+
return writes.writes ? writeGate(input.cwd, writes.targets) : NONE3;
|
|
18843
|
+
}
|
|
18844
|
+
case "write.pre":
|
|
18845
|
+
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE3;
|
|
18846
|
+
case "subagent.start":
|
|
18847
|
+
return {
|
|
18848
|
+
kind: "context",
|
|
18849
|
+
text: subagentStartText(input.host, input.session.id ?? null, descriptor, event)
|
|
18850
|
+
};
|
|
18851
|
+
case "compact.pre":
|
|
18852
|
+
return {
|
|
18853
|
+
kind: "notice",
|
|
18854
|
+
userMessage: "Workit context may be stale after compaction; re-run inspection or resume before acting."
|
|
18855
|
+
};
|
|
18856
|
+
case "tool.pre":
|
|
18857
|
+
case "shell.post":
|
|
18858
|
+
case "subagent.stop":
|
|
18859
|
+
case "prompt.submit":
|
|
18860
|
+
case "stop":
|
|
18861
|
+
return NONE3;
|
|
18862
|
+
}
|
|
18863
|
+
}
|
|
17065
18864
|
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
17066
18865
|
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
17067
18866
|
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
@@ -17088,6 +18887,7 @@ var PI_DESCRIPTOR = {
|
|
|
17088
18887
|
"context.turn": { support: "native", native: "before_agent_start" },
|
|
17089
18888
|
"shell.pre": { support: "native", native: "tool_call" },
|
|
17090
18889
|
"tool.pre": { support: "native", native: "tool_call" },
|
|
18890
|
+
"write.pre": { support: "native", native: "tool_call" },
|
|
17091
18891
|
"shell.post": { support: "native", native: "tool_result" },
|
|
17092
18892
|
"subagent.start": { support: "none", native: null },
|
|
17093
18893
|
"subagent.stop": { support: "none", native: null },
|
|
@@ -17206,7 +19006,7 @@ var output = (result) => ({
|
|
|
17206
19006
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
17207
19007
|
details: result
|
|
17208
19008
|
});
|
|
17209
|
-
var schemaFor = (family) => ({
|
|
19009
|
+
var schemaFor = (family) => flatOperationJsonSchema(family, { stringTolerant: true });
|
|
17210
19010
|
var decodeStrings = (value) => {
|
|
17211
19011
|
if (typeof value === "string") {
|
|
17212
19012
|
try {
|
|
@@ -17228,10 +19028,9 @@ var trustedForMutation = (ctx, action) => {
|
|
|
17228
19028
|
return failure2("permission_denied", "Pi project is not trusted for Workit mutations");
|
|
17229
19029
|
};
|
|
17230
19030
|
var executeFamily = async (family, input, ctx) => {
|
|
17231
|
-
const
|
|
17232
|
-
const parsed = parsedStrict.ok ? parsedStrict : parseOperation(family, decodeStrings(input));
|
|
19031
|
+
const parsed = parseAdvertisedOperation(family, input, "pi");
|
|
17233
19032
|
if (!parsed.ok)
|
|
17234
|
-
return output(
|
|
19033
|
+
return output(parsed);
|
|
17235
19034
|
const trust = trustedForMutation(ctx, parsed.data.action);
|
|
17236
19035
|
if (!trust.ok)
|
|
17237
19036
|
return output(trust);
|
|
@@ -17244,7 +19043,7 @@ var registerWorkitTools = (pi, options = {}) => {
|
|
|
17244
19043
|
pi.registerTool({
|
|
17245
19044
|
name: `workit_${family}`,
|
|
17246
19045
|
label: `workit_${family}`,
|
|
17247
|
-
description:
|
|
19046
|
+
description: operationDescription(family),
|
|
17248
19047
|
parameters: schemaFor(family),
|
|
17249
19048
|
async execute(_toolCallId, input, _signal, _onUpdate, ctx) {
|
|
17250
19049
|
return executeFamily(family, input, ctx);
|
|
@@ -17266,27 +19065,42 @@ var registerWorkitTools = (pi, options = {}) => {
|
|
|
17266
19065
|
});
|
|
17267
19066
|
};
|
|
17268
19067
|
var enforceToolPolicy = (event, ctx) => {
|
|
19068
|
+
const gate = (hookEvent) => {
|
|
19069
|
+
const decision = handleHook({
|
|
19070
|
+
host: "pi",
|
|
19071
|
+
cwd: ctx.cwd,
|
|
19072
|
+
session: { id: "", agentId: null, agentType: null, parentId: null },
|
|
19073
|
+
permissionMode: null,
|
|
19074
|
+
transcriptPath: null,
|
|
19075
|
+
event: hookEvent
|
|
19076
|
+
}, { descriptor: PI_DESCRIPTOR, addendum: null });
|
|
19077
|
+
return decision.kind === "deny" ? { block: true, reason: decision.reason } : undefined;
|
|
19078
|
+
};
|
|
17269
19079
|
if (event.toolName === "bash") {
|
|
17270
19080
|
const command = event.input?.command;
|
|
17271
|
-
|
|
17272
|
-
return decision?.kind === "deny" ? { block: true, reason: decision.reason } : undefined;
|
|
19081
|
+
return typeof command === "string" ? gate({ kind: "shell.pre", command, toolUseId: null }) : undefined;
|
|
17273
19082
|
}
|
|
17274
19083
|
if (event.toolName !== "write" && event.toolName !== "edit")
|
|
17275
19084
|
return;
|
|
17276
19085
|
if (!ctx.isProjectTrusted())
|
|
17277
19086
|
return { block: true, reason: "Pi project is not trusted" };
|
|
17278
|
-
return
|
|
19087
|
+
return gate({
|
|
19088
|
+
kind: "write.pre",
|
|
19089
|
+
tool: event.toolName,
|
|
19090
|
+
paths: writePaths(event.input),
|
|
19091
|
+
toolUseId: null
|
|
19092
|
+
});
|
|
17279
19093
|
};
|
|
17280
19094
|
|
|
17281
19095
|
// packages/workit-pi/src/launches.ts
|
|
17282
19096
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
17283
|
-
import
|
|
17284
|
-
import
|
|
19097
|
+
import os4 from "node:os";
|
|
19098
|
+
import path22 from "node:path";
|
|
17285
19099
|
var launchDir = (root) => {
|
|
17286
19100
|
const location = resolveStore(root);
|
|
17287
|
-
return location instanceof Error ? null :
|
|
19101
|
+
return location instanceof Error ? null : path22.join(location.dir, "pi-launches");
|
|
17288
19102
|
};
|
|
17289
|
-
var fileFor = (dir, workerId) =>
|
|
19103
|
+
var fileFor = (dir, workerId) => path22.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
|
|
17290
19104
|
var recordLaunch = (root, attempt) => {
|
|
17291
19105
|
const dir = launchDir(root);
|
|
17292
19106
|
if (!dir)
|
|
@@ -17296,7 +19110,7 @@ var recordLaunch = (root, attempt) => {
|
|
|
17296
19110
|
writeFileSync3(fileFor(dir, attempt.workerId), JSON.stringify({
|
|
17297
19111
|
...attempt,
|
|
17298
19112
|
pid: process.pid,
|
|
17299
|
-
host:
|
|
19113
|
+
host: os4.hostname(),
|
|
17300
19114
|
at: new Date().toISOString()
|
|
17301
19115
|
}), { flag: "wx" });
|
|
17302
19116
|
return true;
|
|
@@ -17310,7 +19124,7 @@ var clearLaunch = (root, workerId) => {
|
|
|
17310
19124
|
rmSync2(fileFor(dir, workerId), { force: true });
|
|
17311
19125
|
};
|
|
17312
19126
|
var alive = (attempt) => {
|
|
17313
|
-
if (attempt.host !==
|
|
19127
|
+
if (attempt.host !== os4.hostname())
|
|
17314
19128
|
return true;
|
|
17315
19129
|
try {
|
|
17316
19130
|
process.kill(attempt.pid, 0);
|
|
@@ -17332,7 +19146,7 @@ var abandonedLaunches = (root) => {
|
|
|
17332
19146
|
const found = [];
|
|
17333
19147
|
for (const name of names) {
|
|
17334
19148
|
try {
|
|
17335
|
-
const attempt = JSON.parse(readFileSync6(
|
|
19149
|
+
const attempt = JSON.parse(readFileSync6(path22.join(dir, name), "utf8"));
|
|
17336
19150
|
if (typeof attempt.workerId === "string" && typeof attempt.session === "string") {
|
|
17337
19151
|
if (!alive(attempt))
|
|
17338
19152
|
found.push(attempt);
|
|
@@ -17350,9 +19164,9 @@ import { randomUUID as randomUUID4 } from "node:crypto";
|
|
|
17350
19164
|
// packages/workit-pi/src/worker-protocol.ts
|
|
17351
19165
|
var MAX_WORKER_LINE_BYTES = 64 * 1024;
|
|
17352
19166
|
var MAX_WORKER_STDERR_BYTES = 64 * 1024;
|
|
17353
|
-
var
|
|
19167
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null;
|
|
17354
19168
|
var parseEvent2 = (value) => {
|
|
17355
|
-
if (!
|
|
19169
|
+
if (!isRecord5(value) || typeof value.type !== "string")
|
|
17356
19170
|
return null;
|
|
17357
19171
|
if (value.type === "workit_worker_ready") {
|
|
17358
19172
|
if (typeof value.workerId !== "string" || typeof value.sessionId !== "string")
|