@brainervirus/workit-opencode 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/assets/skills/workit-deslop/SKILL.md +1 -1
- package/assets/skills/workit-implement/SKILL.md +9 -4
- package/assets/skills/workit-review/SKILL.md +2 -0
- package/assets/skills/workit-shape/SKILL.md +1 -1
- package/assets/skills/workit-ship/SKILL.md +1 -1
- package/dist/plugin.js +2533 -937
- package/package.json +2 -2
package/dist/plugin.js
CHANGED
|
@@ -4,9 +4,9 @@ function define(plugin) {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
// packages/workit-core/src/core.ts
|
|
7
|
-
import { spawnSync as
|
|
8
|
-
import { existsSync as
|
|
9
|
-
import
|
|
7
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
8
|
+
import { existsSync as existsSync7, realpathSync as realpathSync5 } from "node:fs";
|
|
9
|
+
import path19 from "node:path";
|
|
10
10
|
|
|
11
11
|
// packages/workit-core/src/core/task-contract.ts
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -7430,6 +7430,14 @@ function object(shape, params) {
|
|
|
7430
7430
|
};
|
|
7431
7431
|
return new ZodObject(def);
|
|
7432
7432
|
}
|
|
7433
|
+
function looseObject(shape, params) {
|
|
7434
|
+
return new ZodObject({
|
|
7435
|
+
type: "object",
|
|
7436
|
+
shape,
|
|
7437
|
+
catchall: unknown(),
|
|
7438
|
+
...normalizeParams(params)
|
|
7439
|
+
});
|
|
7440
|
+
}
|
|
7433
7441
|
var ZodUnion = /* @__PURE__ */ $constructor("ZodUnion", (inst, def) => {
|
|
7434
7442
|
$ZodUnion.init(inst, def);
|
|
7435
7443
|
ZodType.init(inst, def);
|
|
@@ -7930,76 +7938,16 @@ var provenanceSchema = object({
|
|
|
7930
7938
|
workerId: nullableId
|
|
7931
7939
|
}).strict();
|
|
7932
7940
|
var entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict();
|
|
7933
|
-
var
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
code: "custom",
|
|
7941
|
-
input: ctx.value,
|
|
7942
|
-
message: "observed facts require supporting references",
|
|
7943
|
-
path: ["refs"]
|
|
7944
|
-
});
|
|
7945
|
-
});
|
|
7946
|
-
var signalSchema = object({
|
|
7947
|
-
value: union([boolean2(), literal("unknown")]),
|
|
7948
|
-
basis: _enum(["observed", "inferred", "unknown"]),
|
|
7949
|
-
reason: text,
|
|
7941
|
+
var RISK_TIERS = ["trivial", "normal", "high"];
|
|
7942
|
+
var judgmentSchema = object({
|
|
7943
|
+
riskTier: _enum(RISK_TIERS),
|
|
7944
|
+
behaviorChange: boolean2(),
|
|
7945
|
+
productChoiceOpen: boolean2(),
|
|
7946
|
+
needsPlan: boolean2(),
|
|
7947
|
+
note: text.nullable(),
|
|
7950
7948
|
refs: array(refSchema)
|
|
7951
|
-
}).strict().check((ctx) => {
|
|
7952
|
-
const signal = ctx.value;
|
|
7953
|
-
const valid = signal.value === "unknown" ? signal.basis === "unknown" : signal.basis === "observed" || signal.basis === "inferred";
|
|
7954
|
-
if (!valid)
|
|
7955
|
-
ctx.issues.push({
|
|
7956
|
-
code: "custom",
|
|
7957
|
-
input: signal,
|
|
7958
|
-
message: "signal value and basis disagree",
|
|
7959
|
-
path: []
|
|
7960
|
-
});
|
|
7961
|
-
if (signal.basis === "observed" && signal.refs.length === 0)
|
|
7962
|
-
ctx.issues.push({
|
|
7963
|
-
code: "custom",
|
|
7964
|
-
input: signal,
|
|
7965
|
-
message: "observed signals require supporting references",
|
|
7966
|
-
path: ["refs"]
|
|
7967
|
-
});
|
|
7968
|
-
});
|
|
7969
|
-
var signalSet = object({
|
|
7970
|
-
approachUnknown: signalSchema,
|
|
7971
|
-
productChoiceOpen: signalSchema,
|
|
7972
|
-
behaviorChange: signalSchema,
|
|
7973
|
-
mechanicalLowRisk: signalSchema,
|
|
7974
|
-
durableAgreementNeeded: signalSchema,
|
|
7975
|
-
coordinationPlanNeeded: signalSchema,
|
|
7976
|
-
helperUseful: signalSchema,
|
|
7977
|
-
testFirstPractical: signalSchema
|
|
7978
|
-
}).strict();
|
|
7979
|
-
var consequenceSchema = object({
|
|
7980
|
-
area: _enum([
|
|
7981
|
-
"behavior",
|
|
7982
|
-
"public_contract",
|
|
7983
|
-
"data",
|
|
7984
|
-
"security",
|
|
7985
|
-
"operations",
|
|
7986
|
-
"reversibility",
|
|
7987
|
-
"consumers"
|
|
7988
|
-
]),
|
|
7989
|
-
fact: factSchema
|
|
7990
|
-
}).strict();
|
|
7991
|
-
var verificationSchema = object({
|
|
7992
|
-
claim: text,
|
|
7993
|
-
scope: scopeSchema,
|
|
7994
|
-
availableChecks: array(refSchema),
|
|
7995
|
-
gaps: array(text)
|
|
7996
|
-
}).strict();
|
|
7997
|
-
var assessmentSchema = object({
|
|
7998
|
-
facts: array(factSchema),
|
|
7999
|
-
signals: signalSet,
|
|
8000
|
-
consequences: array(consequenceSchema),
|
|
8001
|
-
verification: array(verificationSchema)
|
|
8002
7949
|
}).strict();
|
|
7950
|
+
var legacyAssessmentSchema = looseObject({});
|
|
8003
7951
|
var constraintSchema = object({
|
|
8004
7952
|
id: nonEmpty,
|
|
8005
7953
|
kind: _enum(["host", "project", "user"]),
|
|
@@ -8236,7 +8184,8 @@ var taskRecordSchema = object({
|
|
|
8236
8184
|
progress: progressSchema,
|
|
8237
8185
|
pauseReason: text.nullable().optional(),
|
|
8238
8186
|
runtime: runtimeSchema.optional(),
|
|
8239
|
-
assessments: array(entrySchema(
|
|
8187
|
+
assessments: array(entrySchema(legacyAssessmentSchema)),
|
|
8188
|
+
judgments: array(entrySchema(judgmentSchema)).optional(),
|
|
8240
8189
|
policy: policySchema.nullable(),
|
|
8241
8190
|
policyChanges: array(policyChangeSchema),
|
|
8242
8191
|
candidates: array(candidateSchema),
|
|
@@ -8412,14 +8361,15 @@ var taskOperations = {
|
|
|
8412
8361
|
decisionIds: array(id).optional()
|
|
8413
8362
|
})
|
|
8414
8363
|
};
|
|
8364
|
+
var rawJudgment = record(string2(), unknown());
|
|
8415
8365
|
var policyOperations = {
|
|
8416
8366
|
assess: operation({
|
|
8417
8367
|
action: literal("assess"),
|
|
8418
8368
|
...taskId,
|
|
8419
8369
|
expectedRevision: revision.optional(),
|
|
8420
|
-
|
|
8370
|
+
judgment: rawJudgment
|
|
8421
8371
|
}),
|
|
8422
|
-
preview: operation({ action: literal("preview"), ...taskId,
|
|
8372
|
+
preview: operation({ action: literal("preview"), ...taskId, judgment: rawJudgment }),
|
|
8423
8373
|
explain: operation({ action: literal("explain"), ...taskId })
|
|
8424
8374
|
};
|
|
8425
8375
|
var evidenceOperations = {
|
|
@@ -8458,7 +8408,10 @@ var decisionOperations = {
|
|
|
8458
8408
|
...taskId,
|
|
8459
8409
|
expectedRevision: revision.optional(),
|
|
8460
8410
|
purpose: decisionSchema.shape.purpose,
|
|
8461
|
-
binding: decisionSchema.shape.binding
|
|
8411
|
+
binding: decisionSchema.shape.binding.extend({
|
|
8412
|
+
taskId: id.optional(),
|
|
8413
|
+
workspaceId: id.optional()
|
|
8414
|
+
}),
|
|
8462
8415
|
response: decisionSchema.shape.response,
|
|
8463
8416
|
requirementIds: array(digest)
|
|
8464
8417
|
}),
|
|
@@ -8512,6 +8465,15 @@ var operationSchemas = {
|
|
|
8512
8465
|
worker: discriminatedUnion("action", Object.values(workerOperations)),
|
|
8513
8466
|
state: discriminatedUnion("action", Object.values(stateOperations))
|
|
8514
8467
|
};
|
|
8468
|
+
var OPERATION_ACTIONS = {
|
|
8469
|
+
task: Object.keys(taskOperations),
|
|
8470
|
+
policy: Object.keys(policyOperations),
|
|
8471
|
+
evidence: Object.keys(evidenceOperations),
|
|
8472
|
+
finding: Object.keys(findingOperations),
|
|
8473
|
+
decision: Object.keys(decisionOperations),
|
|
8474
|
+
worker: Object.keys(workerOperations),
|
|
8475
|
+
state: Object.keys(stateOperations)
|
|
8476
|
+
};
|
|
8515
8477
|
var withImplicitTask = (options) => Object.values(options).map((option) => ("taskId" in option.shape) ? option.extend({ taskId: id.optional() }) : option);
|
|
8516
8478
|
var advertised = (options) => discriminatedUnion("action", withImplicitTask(options));
|
|
8517
8479
|
var advertisedOperationSchemas = {
|
|
@@ -8545,103 +8507,6 @@ function parseOperation(family, input) {
|
|
|
8545
8507
|
}));
|
|
8546
8508
|
return failure2("invalid_input", "operation input is invalid", { fields });
|
|
8547
8509
|
}
|
|
8548
|
-
function operationJsonSchema(family) {
|
|
8549
|
-
return toJSONSchema(advertisedOperationSchemas[family], { target: "draft-2020-12" });
|
|
8550
|
-
}
|
|
8551
|
-
var OPERATION_SCHEMA_DEPTH = 1;
|
|
8552
|
-
var schemaShapeDescription = (value, depth = 0) => {
|
|
8553
|
-
if (!value || typeof value !== "object")
|
|
8554
|
-
return "value";
|
|
8555
|
-
const schema = value;
|
|
8556
|
-
if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
|
|
8557
|
-
const options = Array.isArray(schema.oneOf) ? schema.oneOf : schema.anyOf;
|
|
8558
|
-
return `(${options.map((option) => schemaShapeDescription(option, depth)).join("|")})`;
|
|
8559
|
-
}
|
|
8560
|
-
if (schema.const !== undefined)
|
|
8561
|
-
return `=${JSON.stringify(schema.const)}`;
|
|
8562
|
-
if (Array.isArray(schema.enum))
|
|
8563
|
-
return `[${schema.enum.map(String).join("|")}]`;
|
|
8564
|
-
const type = Array.isArray(schema.type) ? schema.type.join("/") : schema.type;
|
|
8565
|
-
if (type === "array")
|
|
8566
|
-
return `arr<${schemaShapeDescription(schema.items, depth)}>`;
|
|
8567
|
-
if (type === "object" && schema.properties && typeof schema.properties === "object") {
|
|
8568
|
-
if (depth >= 2)
|
|
8569
|
-
return "obj";
|
|
8570
|
-
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
8571
|
-
const properties = schema.properties;
|
|
8572
|
-
const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child, depth + 1)}`);
|
|
8573
|
-
return `obj{${fields.join(",")}}`;
|
|
8574
|
-
}
|
|
8575
|
-
if (typeof type !== "string")
|
|
8576
|
-
return "?";
|
|
8577
|
-
return type.split("/").map((part) => ({ string: "str", boolean: "bool", number: "num", null: "null" })[part] ?? part).join("|");
|
|
8578
|
-
};
|
|
8579
|
-
var canonicalFieldsDescription = (value) => {
|
|
8580
|
-
if (Array.isArray(value))
|
|
8581
|
-
return `Canonical object fields: ${value.join(", ")}. Workit validates the complete nested value.`;
|
|
8582
|
-
const isZodType = (schema) => ("_zod" in schema) && typeof schema._zod === "object";
|
|
8583
|
-
const schema = isZodType(value) ? toJSONSchema(value, { target: "draft-2020-12" }) : value;
|
|
8584
|
-
if (!schema.properties || typeof schema.properties !== "object") {
|
|
8585
|
-
const variants = Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : null;
|
|
8586
|
-
return variants ? `Canonical shapes: ${variants.map((variant) => schemaShapeDescription(variant)).join(" | ")}. Full nested value is validated.` : "Canonical nested value; Workit validates the complete value.";
|
|
8587
|
-
}
|
|
8588
|
-
const properties = schema.properties;
|
|
8589
|
-
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
8590
|
-
const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child)}`);
|
|
8591
|
-
return `Fields (! required): ${fields.join(", ")}. Full nested value is validated.`;
|
|
8592
|
-
};
|
|
8593
|
-
function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, stringifiedObjects = false) {
|
|
8594
|
-
const tolerate = (projected) => {
|
|
8595
|
-
if (!stringifiedObjects)
|
|
8596
|
-
return projected;
|
|
8597
|
-
if (projected.properties && typeof projected.properties === "object")
|
|
8598
|
-
return { ...projected, type: ["object", "string"] };
|
|
8599
|
-
if (projected.items)
|
|
8600
|
-
return { ...projected, type: ["array", "string"] };
|
|
8601
|
-
if ("const" in projected && (typeof projected.const === "number" || typeof projected.const === "boolean"))
|
|
8602
|
-
return { anyOf: [projected, { const: String(projected.const) }] };
|
|
8603
|
-
if (projected.type === "object")
|
|
8604
|
-
return { ...projected, type: ["object", "string"] };
|
|
8605
|
-
if (projected.type === "array")
|
|
8606
|
-
return { ...projected, type: ["array", "string"] };
|
|
8607
|
-
return projected;
|
|
8608
|
-
};
|
|
8609
|
-
const collapse = (node, currentDepth) => {
|
|
8610
|
-
if (Array.isArray(node))
|
|
8611
|
-
return node.map((item) => collapse(item, currentDepth));
|
|
8612
|
-
if (typeof node !== "object" || node === null)
|
|
8613
|
-
return node;
|
|
8614
|
-
const record = node;
|
|
8615
|
-
if (record.properties && typeof record.properties === "object") {
|
|
8616
|
-
const properties = record.properties;
|
|
8617
|
-
const fields = Object.keys(properties);
|
|
8618
|
-
if (currentDepth >= maxDepth || fields.length === 0) {
|
|
8619
|
-
const description = stringifiedObjects ? `${canonicalFieldsDescription(record)} A JSON-encoded string is also accepted.` : canonicalFieldsDescription(record);
|
|
8620
|
-
return tolerate({ type: "object", description });
|
|
8621
|
-
}
|
|
8622
|
-
return tolerate({
|
|
8623
|
-
...record,
|
|
8624
|
-
properties: Object.fromEntries(Object.entries(record.properties).map(([key, child]) => [
|
|
8625
|
-
key,
|
|
8626
|
-
collapse(child, currentDepth + 1)
|
|
8627
|
-
]))
|
|
8628
|
-
});
|
|
8629
|
-
}
|
|
8630
|
-
if (record.items)
|
|
8631
|
-
return tolerate({ ...record, items: collapse(record.items, currentDepth) });
|
|
8632
|
-
const composed = ["anyOf", "oneOf", "allOf"].filter((key) => Array.isArray(record[key]));
|
|
8633
|
-
if (composed.length > 0) {
|
|
8634
|
-
const projected = { ...record };
|
|
8635
|
-
for (const key of composed)
|
|
8636
|
-
projected[key] = record[key].map((item) => collapse(item, currentDepth));
|
|
8637
|
-
return projected;
|
|
8638
|
-
}
|
|
8639
|
-
if ("const" in record)
|
|
8640
|
-
return tolerate({ ...record });
|
|
8641
|
-
return node;
|
|
8642
|
-
};
|
|
8643
|
-
return collapse(operationJsonSchema(family), 0);
|
|
8644
|
-
}
|
|
8645
8510
|
function canonical(value, seen) {
|
|
8646
8511
|
if (value === null || typeof value === "boolean" || typeof value === "string") {
|
|
8647
8512
|
if (typeof value === "string" && invalidUnicode(value))
|
|
@@ -8730,6 +8595,402 @@ function requirementId(input) {
|
|
|
8730
8595
|
}
|
|
8731
8596
|
});
|
|
8732
8597
|
}
|
|
8598
|
+
// packages/workit-core/src/core/operation-input.ts
|
|
8599
|
+
var str = (d, values) => ({
|
|
8600
|
+
type: "string",
|
|
8601
|
+
...values ? { enum: values } : {},
|
|
8602
|
+
...d ? { d } : {}
|
|
8603
|
+
});
|
|
8604
|
+
var list = (d) => ({ type: "array", ...d ? { d } : {} });
|
|
8605
|
+
var bool = (d) => ({ type: "boolean", ...d ? { d } : {} });
|
|
8606
|
+
var int2 = (d) => ({ type: "integer", ...d ? { d } : {} });
|
|
8607
|
+
var scopeFields = {
|
|
8608
|
+
paths: list("Scope paths (default: whole checkout)."),
|
|
8609
|
+
exclusions: list(),
|
|
8610
|
+
scope: str("Scope description.")
|
|
8611
|
+
};
|
|
8612
|
+
var refs = list("Paths or URLs.");
|
|
8613
|
+
var FLAT = {
|
|
8614
|
+
task: {
|
|
8615
|
+
fields: {
|
|
8616
|
+
objective: str(),
|
|
8617
|
+
...scopeFields,
|
|
8618
|
+
refs,
|
|
8619
|
+
reason: str(),
|
|
8620
|
+
summary: str(),
|
|
8621
|
+
nextAction: str(),
|
|
8622
|
+
blockers: list(),
|
|
8623
|
+
outcome: str(undefined, ["verified", "accepted_limitations", "stopped"]),
|
|
8624
|
+
decisionIds: list(),
|
|
8625
|
+
status: str(undefined, ["open", "closed", "all"]),
|
|
8626
|
+
query: str(),
|
|
8627
|
+
limit: int2(),
|
|
8628
|
+
view: str(undefined, ["summary", "full"])
|
|
8629
|
+
}
|
|
8630
|
+
},
|
|
8631
|
+
policy: {
|
|
8632
|
+
fields: {
|
|
8633
|
+
riskTier: str("low=trivial, medium=normal.", ["trivial", "normal", "high", "low", "medium"]),
|
|
8634
|
+
behaviorChange: bool("Observable behavior changes."),
|
|
8635
|
+
productChoiceOpen: bool("A product/preference choice is still open."),
|
|
8636
|
+
needsPlan: bool("Needs a written plan or spec first."),
|
|
8637
|
+
note: str(),
|
|
8638
|
+
refs: list("Plan/spec paths or URLs.")
|
|
8639
|
+
}
|
|
8640
|
+
},
|
|
8641
|
+
evidence: {
|
|
8642
|
+
fields: {
|
|
8643
|
+
kind: str(undefined, ["check", "review", "investigation", "artifact"]),
|
|
8644
|
+
claim: str(),
|
|
8645
|
+
result: str(undefined, ["passed", "failed", "missing", "skipped"]),
|
|
8646
|
+
summary: str(),
|
|
8647
|
+
exitCode: int2(),
|
|
8648
|
+
refs,
|
|
8649
|
+
requirementIds: list(),
|
|
8650
|
+
candidateId: str(),
|
|
8651
|
+
beforeCandidateId: str(),
|
|
8652
|
+
reviewSession: str("Reviewer session handle (review evidence).")
|
|
8653
|
+
}
|
|
8654
|
+
},
|
|
8655
|
+
finding: {
|
|
8656
|
+
fields: {
|
|
8657
|
+
claim: str(),
|
|
8658
|
+
consequence: str(),
|
|
8659
|
+
...scopeFields,
|
|
8660
|
+
candidateId: str(),
|
|
8661
|
+
refs,
|
|
8662
|
+
findingId: str(),
|
|
8663
|
+
disposition: str(undefined, ["open", "fixed", "dismissed", "deferred"]),
|
|
8664
|
+
reason: str(),
|
|
8665
|
+
evidenceIds: list(),
|
|
8666
|
+
decisionIds: list()
|
|
8667
|
+
}
|
|
8668
|
+
},
|
|
8669
|
+
decision: {
|
|
8670
|
+
fields: {
|
|
8671
|
+
purpose: str(undefined, ["design", "action", "limitation", "preference"]),
|
|
8672
|
+
response: str(undefined, ["approved", "rejected", "stated"]),
|
|
8673
|
+
presented: str("What the user was asked."),
|
|
8674
|
+
choice: str("The user's stated choice (response=stated)."),
|
|
8675
|
+
...scopeFields,
|
|
8676
|
+
refs,
|
|
8677
|
+
requirementIds: list(),
|
|
8678
|
+
decisionId: str(),
|
|
8679
|
+
reason: str()
|
|
8680
|
+
}
|
|
8681
|
+
},
|
|
8682
|
+
worker: {
|
|
8683
|
+
fields: {
|
|
8684
|
+
role: str(undefined, ["investigator", "reviewer", "implementer"]),
|
|
8685
|
+
objective: str(),
|
|
8686
|
+
...scopeFields,
|
|
8687
|
+
decisionIds: list(),
|
|
8688
|
+
requirementIds: list(),
|
|
8689
|
+
candidateId: str(),
|
|
8690
|
+
stoppingCondition: str(),
|
|
8691
|
+
workerId: str(),
|
|
8692
|
+
outcome: str(undefined, ["completed", "failed", "cancelled"]),
|
|
8693
|
+
summary: str(),
|
|
8694
|
+
evidenceIds: list(),
|
|
8695
|
+
findingIds: list(),
|
|
8696
|
+
reason: str()
|
|
8697
|
+
}
|
|
8698
|
+
},
|
|
8699
|
+
state: {
|
|
8700
|
+
fields: { bundle: { type: "object", d: "An export bundle (object or JSON string)." } }
|
|
8701
|
+
}
|
|
8702
|
+
};
|
|
8703
|
+
var COMMON = {
|
|
8704
|
+
taskId: str("Omit for this branch's task."),
|
|
8705
|
+
expectedRevision: str(),
|
|
8706
|
+
expectedWorkspaceRevision: str()
|
|
8707
|
+
};
|
|
8708
|
+
var DESCRIPTIONS = {
|
|
8709
|
+
task: "Workit task lifecycle for this branch's task (optional continuity).",
|
|
8710
|
+
policy: "Judge the work in four flat calls (riskTier, behaviorChange, productChoiceOpen, needsPlan); Workit derives the checks, verdicts, decisions and plan it needs.",
|
|
8711
|
+
evidence: "Record a note-level evidence entry; only `workit check` runs satisfy check gates.",
|
|
8712
|
+
finding: "Record or resolve a review finding.",
|
|
8713
|
+
decision: "Record or revoke a user decision (never an authorization).",
|
|
8714
|
+
worker: "Assign, report or cancel a bounded helper.",
|
|
8715
|
+
state: "Export or import a task handoff bundle."
|
|
8716
|
+
};
|
|
8717
|
+
var operationDescription = (family) => `${DESCRIPTIONS[family]} Flat fields; omit taskId and revisions.`;
|
|
8718
|
+
var READ_TASK_FIELDS = new Set(["status", "query", "limit", "view"]);
|
|
8719
|
+
var READ_ONLY_ACTIONS = new Set(["list", "inspect", "preview", "explain", "export"]);
|
|
8720
|
+
function flatOperationJsonSchema(family, options = {}) {
|
|
8721
|
+
const spec = FLAT[family];
|
|
8722
|
+
const actions = options.readOnly ? OPERATION_ACTIONS[family].filter((action) => READ_ONLY_ACTIONS.has(action)) : [...OPERATION_ACTIONS[family]];
|
|
8723
|
+
if (!actions.length)
|
|
8724
|
+
return null;
|
|
8725
|
+
const property = (field) => {
|
|
8726
|
+
const base = field.type === "array" ? { type: "array", items: { type: "string" } } : field.type === "object" ? { type: "object" } : { type: field.type };
|
|
8727
|
+
const tolerant = options.stringTolerant && field.type !== "string" ? { type: [field.type, "string"] } : {};
|
|
8728
|
+
return {
|
|
8729
|
+
...base,
|
|
8730
|
+
...tolerant,
|
|
8731
|
+
...field.enum ? { enum: field.enum } : {},
|
|
8732
|
+
...field.d ? { description: field.d } : {}
|
|
8733
|
+
};
|
|
8734
|
+
};
|
|
8735
|
+
const properties = { action: { type: "string", enum: actions } };
|
|
8736
|
+
const readField = (name) => name === "taskId" || (family === "task" ? READ_TASK_FIELDS.has(name) : family === "policy");
|
|
8737
|
+
for (const [name, field] of Object.entries({ ...COMMON, ...spec.fields }))
|
|
8738
|
+
if (options.readOnly ? readField(name) : !(family === "state" && name === "expectedRevision"))
|
|
8739
|
+
properties[name] = property(field);
|
|
8740
|
+
return {
|
|
8741
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
8742
|
+
type: "object",
|
|
8743
|
+
properties,
|
|
8744
|
+
required: ["action"]
|
|
8745
|
+
};
|
|
8746
|
+
}
|
|
8747
|
+
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8748
|
+
var decoded = (value) => {
|
|
8749
|
+
if (typeof value !== "string")
|
|
8750
|
+
return value;
|
|
8751
|
+
const trimmed = value.trim();
|
|
8752
|
+
if (!/^[[{]/.test(trimmed) && !/^(true|false|null|-?\d+)$/.test(trimmed))
|
|
8753
|
+
return value;
|
|
8754
|
+
try {
|
|
8755
|
+
return JSON.parse(trimmed);
|
|
8756
|
+
} catch {
|
|
8757
|
+
return value;
|
|
8758
|
+
}
|
|
8759
|
+
};
|
|
8760
|
+
var strings = (value) => {
|
|
8761
|
+
const plain = decoded(value);
|
|
8762
|
+
if (typeof plain === "string")
|
|
8763
|
+
return plain.trim() ? [plain] : [];
|
|
8764
|
+
return plain;
|
|
8765
|
+
};
|
|
8766
|
+
var refOf = (value) => {
|
|
8767
|
+
if (typeof value !== "string")
|
|
8768
|
+
return value;
|
|
8769
|
+
return /^https?:\/\//i.test(value) ? { kind: "external", url: value } : { kind: "file", path: value.replace(/^\.\//, ""), digest: null };
|
|
8770
|
+
};
|
|
8771
|
+
var refList = (value) => {
|
|
8772
|
+
const items = strings(value);
|
|
8773
|
+
return Array.isArray(items) ? items.map(refOf) : items;
|
|
8774
|
+
};
|
|
8775
|
+
var take = (flat, keys, map = decoded) => {
|
|
8776
|
+
const out = {};
|
|
8777
|
+
for (const key of keys)
|
|
8778
|
+
if (key in flat) {
|
|
8779
|
+
out[key] = map(flat[key]);
|
|
8780
|
+
delete flat[key];
|
|
8781
|
+
}
|
|
8782
|
+
return out;
|
|
8783
|
+
};
|
|
8784
|
+
var nest = (request, key, flat, defaults = {}) => {
|
|
8785
|
+
const nested = isObject2(decoded(request[key])) ? decoded(request[key]) : null;
|
|
8786
|
+
if (!nested && !Object.keys(flat).length)
|
|
8787
|
+
return;
|
|
8788
|
+
request[key] = { ...defaults, ...flat, ...nested };
|
|
8789
|
+
};
|
|
8790
|
+
var scopeOf = (flat, description) => {
|
|
8791
|
+
const scope = decoded(flat.scope);
|
|
8792
|
+
const has = "paths" in flat || "exclusions" in flat || typeof scope === "string";
|
|
8793
|
+
const out = isObject2(scope) ? scope : has ? {
|
|
8794
|
+
description: typeof scope === "string" ? scope : String(description ?? ""),
|
|
8795
|
+
paths: strings(flat.paths ?? ["."]),
|
|
8796
|
+
exclusions: strings(flat.exclusions ?? [])
|
|
8797
|
+
} : null;
|
|
8798
|
+
delete flat.scope;
|
|
8799
|
+
delete flat.paths;
|
|
8800
|
+
delete flat.exclusions;
|
|
8801
|
+
return out;
|
|
8802
|
+
};
|
|
8803
|
+
var defaultScope = (description) => ({
|
|
8804
|
+
description: typeof description === "string" ? description : "",
|
|
8805
|
+
paths: ["."],
|
|
8806
|
+
exclusions: []
|
|
8807
|
+
});
|
|
8808
|
+
var LISTS = [
|
|
8809
|
+
"decisionIds",
|
|
8810
|
+
"evidenceIds",
|
|
8811
|
+
"findingIds",
|
|
8812
|
+
"requirementIds",
|
|
8813
|
+
"refs",
|
|
8814
|
+
"blockers"
|
|
8815
|
+
];
|
|
8816
|
+
var ENVELOPE = new Set([
|
|
8817
|
+
"schemaVersion",
|
|
8818
|
+
"action",
|
|
8819
|
+
"taskId",
|
|
8820
|
+
"expectedRevision",
|
|
8821
|
+
"expectedWorkspaceRevision",
|
|
8822
|
+
"judgment"
|
|
8823
|
+
]);
|
|
8824
|
+
function normalizeOperationInput(family, input, host) {
|
|
8825
|
+
if (!isObject2(input))
|
|
8826
|
+
return input;
|
|
8827
|
+
const request = { ...input };
|
|
8828
|
+
if (!("schemaVersion" in request))
|
|
8829
|
+
request.schemaVersion = 1;
|
|
8830
|
+
else
|
|
8831
|
+
request.schemaVersion = decoded(request.schemaVersion);
|
|
8832
|
+
for (const key of ["limit", "exitCode"])
|
|
8833
|
+
if (key in request)
|
|
8834
|
+
request[key] = decoded(request[key]);
|
|
8835
|
+
const action = request.action;
|
|
8836
|
+
const flat = request;
|
|
8837
|
+
switch (family) {
|
|
8838
|
+
case "task": {
|
|
8839
|
+
if (action === "start" || action === "revise") {
|
|
8840
|
+
const intent = isObject2(decoded(request.intent)) ? decoded(request.intent) : {};
|
|
8841
|
+
const objective = flat.objective ?? intent.objective;
|
|
8842
|
+
const scope = scopeOf(flat, objective);
|
|
8843
|
+
const fields = take(flat, ["objective"]);
|
|
8844
|
+
const authorityRefs = "refs" in flat ? refList(flat.refs) : undefined;
|
|
8845
|
+
delete flat.refs;
|
|
8846
|
+
nest(request, "intent", {
|
|
8847
|
+
...fields,
|
|
8848
|
+
...scope ? { scope } : {},
|
|
8849
|
+
...authorityRefs ? { authorityRefs } : {}
|
|
8850
|
+
}, { scope: defaultScope(objective), authorityRefs: [] });
|
|
8851
|
+
} else if (action === "progress") {
|
|
8852
|
+
const fields = take(flat, ["summary", "nextAction"]);
|
|
8853
|
+
const blockers = "blockers" in flat ? strings(flat.blockers) : undefined;
|
|
8854
|
+
delete flat.blockers;
|
|
8855
|
+
nest(request, "progress", {
|
|
8856
|
+
...fields,
|
|
8857
|
+
...Array.isArray(blockers) ? {
|
|
8858
|
+
blockers: blockers.map((item) => typeof item === "string" ? { reason: item, dependentAction: "", refs: [] } : item)
|
|
8859
|
+
} : {}
|
|
8860
|
+
}, { nextAction: null, blockers: [] });
|
|
8861
|
+
} else if (action === "close" && "decisionIds" in flat)
|
|
8862
|
+
flat.decisionIds = strings(flat.decisionIds);
|
|
8863
|
+
break;
|
|
8864
|
+
}
|
|
8865
|
+
case "policy": {
|
|
8866
|
+
if (action === "assess" || action === "preview") {
|
|
8867
|
+
const judgment = isObject2(decoded(request.judgment)) ? { ...decoded(request.judgment) } : {};
|
|
8868
|
+
for (const key of Object.keys(flat))
|
|
8869
|
+
if (!ENVELOPE.has(key)) {
|
|
8870
|
+
if (!(key in judgment))
|
|
8871
|
+
judgment[key] = decoded(flat[key]);
|
|
8872
|
+
delete flat[key];
|
|
8873
|
+
}
|
|
8874
|
+
request.judgment = judgment;
|
|
8875
|
+
}
|
|
8876
|
+
break;
|
|
8877
|
+
}
|
|
8878
|
+
case "evidence": {
|
|
8879
|
+
if (action === "record") {
|
|
8880
|
+
const review = "reviewSession" in flat ? decoded(flat.reviewSession) : undefined;
|
|
8881
|
+
delete flat.reviewSession;
|
|
8882
|
+
const fields = take(flat, [
|
|
8883
|
+
"kind",
|
|
8884
|
+
"claim",
|
|
8885
|
+
"result",
|
|
8886
|
+
"summary",
|
|
8887
|
+
"exitCode",
|
|
8888
|
+
"candidateId",
|
|
8889
|
+
"beforeCandidateId",
|
|
8890
|
+
"reviewContext"
|
|
8891
|
+
]);
|
|
8892
|
+
if ("refs" in flat)
|
|
8893
|
+
fields.refs = refList(flat.refs);
|
|
8894
|
+
if ("requirementIds" in flat)
|
|
8895
|
+
fields.requirementIds = strings(flat.requirementIds);
|
|
8896
|
+
delete flat.refs;
|
|
8897
|
+
delete flat.requirementIds;
|
|
8898
|
+
if (typeof review === "string" && review)
|
|
8899
|
+
fields.reviewContext = { kind: "host", host, handle: review };
|
|
8900
|
+
nest(request, "evidence", fields, {
|
|
8901
|
+
summary: "",
|
|
8902
|
+
refs: [],
|
|
8903
|
+
requirementIds: [],
|
|
8904
|
+
exitCode: null,
|
|
8905
|
+
reviewContext: null
|
|
8906
|
+
});
|
|
8907
|
+
}
|
|
8908
|
+
break;
|
|
8909
|
+
}
|
|
8910
|
+
case "finding": {
|
|
8911
|
+
if (action === "record") {
|
|
8912
|
+
const scope = scopeOf(flat, flat.claim);
|
|
8913
|
+
if (scope)
|
|
8914
|
+
request.scope = scope;
|
|
8915
|
+
else if (!("scope" in request))
|
|
8916
|
+
request.scope = defaultScope(request.claim);
|
|
8917
|
+
request.refs = "refs" in flat ? refList(flat.refs) : [];
|
|
8918
|
+
}
|
|
8919
|
+
break;
|
|
8920
|
+
}
|
|
8921
|
+
case "decision": {
|
|
8922
|
+
if (action === "record") {
|
|
8923
|
+
const binding = isObject2(decoded(request.binding)) ? decoded(request.binding) : {};
|
|
8924
|
+
const presented = flat.presented ?? binding.presented;
|
|
8925
|
+
const scope = scopeOf(flat, presented);
|
|
8926
|
+
const fields = take(flat, ["presented"]);
|
|
8927
|
+
const choice = flat.choice;
|
|
8928
|
+
delete flat.choice;
|
|
8929
|
+
const contentRefs = "refs" in flat ? refList(flat.refs) : undefined;
|
|
8930
|
+
delete flat.refs;
|
|
8931
|
+
nest(request, "binding", {
|
|
8932
|
+
...fields,
|
|
8933
|
+
...scope ? { scope } : {},
|
|
8934
|
+
...contentRefs ? { contentRefs } : {},
|
|
8935
|
+
...typeof choice === "string" ? { statedChoice: { ref: "choice", text: choice } } : {}
|
|
8936
|
+
}, { scope: defaultScope(presented), contentRefs: [] });
|
|
8937
|
+
}
|
|
8938
|
+
break;
|
|
8939
|
+
}
|
|
8940
|
+
case "worker": {
|
|
8941
|
+
if (action === "assign") {
|
|
8942
|
+
const objective = flat.objective;
|
|
8943
|
+
const scope = scopeOf(flat, objective);
|
|
8944
|
+
const fields = take(flat, ["role", "objective", "candidateId", "stoppingCondition"]);
|
|
8945
|
+
for (const key of ["decisionIds", "requirementIds"])
|
|
8946
|
+
if (key in flat) {
|
|
8947
|
+
fields[key] = strings(flat[key]);
|
|
8948
|
+
delete flat[key];
|
|
8949
|
+
}
|
|
8950
|
+
nest(request, "assignment", { ...fields, ...scope ? { scope } : {} }, { scope: defaultScope(objective), decisionIds: [], requirementIds: [] });
|
|
8951
|
+
} else if (action === "report") {
|
|
8952
|
+
const fields = take(flat, ["outcome", "summary"]);
|
|
8953
|
+
for (const key of ["evidenceIds", "findingIds"])
|
|
8954
|
+
if (key in flat) {
|
|
8955
|
+
fields[key] = strings(flat[key]);
|
|
8956
|
+
delete flat[key];
|
|
8957
|
+
}
|
|
8958
|
+
nest(request, "report", fields, { evidenceIds: [], findingIds: [] });
|
|
8959
|
+
}
|
|
8960
|
+
break;
|
|
8961
|
+
}
|
|
8962
|
+
case "state": {
|
|
8963
|
+
if ("bundle" in request)
|
|
8964
|
+
request.bundle = decoded(request.bundle);
|
|
8965
|
+
break;
|
|
8966
|
+
}
|
|
8967
|
+
}
|
|
8968
|
+
const defaults = {
|
|
8969
|
+
finding: { resolve: ["evidenceIds", "decisionIds"] },
|
|
8970
|
+
decision: { record: ["requirementIds"] }
|
|
8971
|
+
};
|
|
8972
|
+
for (const key of defaults[family]?.[String(action)] ?? [])
|
|
8973
|
+
if (!(key in request))
|
|
8974
|
+
request[key] = [];
|
|
8975
|
+
for (const key of LISTS)
|
|
8976
|
+
if (key in request && typeof request[key] === "string")
|
|
8977
|
+
request[key] = strings(request[key]);
|
|
8978
|
+
return request;
|
|
8979
|
+
}
|
|
8980
|
+
function parseAdvertisedOperation(family, input, host) {
|
|
8981
|
+
const request = normalizeOperationInput(family, input, host);
|
|
8982
|
+
if (isObject2(request) && request.schemaVersion !== SCHEMA_VERSION)
|
|
8983
|
+
return failure2("unsupported_version", "unsupported schema version", { operation: family });
|
|
8984
|
+
const parsed = advertisedOperationSchemas[family].safeParse(request);
|
|
8985
|
+
if (parsed.success)
|
|
8986
|
+
return success(null, null, request);
|
|
8987
|
+
return failure2("invalid_input", "operation input is invalid", {
|
|
8988
|
+
fields: parsed.error.issues.map((issue) => ({
|
|
8989
|
+
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
8990
|
+
reason: issue.message
|
|
8991
|
+
}))
|
|
8992
|
+
});
|
|
8993
|
+
}
|
|
8733
8994
|
// packages/workit-core/src/core/task-store.ts
|
|
8734
8995
|
import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
|
|
8735
8996
|
import * as fs7 from "node:fs";
|
|
@@ -9784,7 +10045,7 @@ class LogDamage extends Error {
|
|
|
9784
10045
|
this.upgrade = upgrade;
|
|
9785
10046
|
}
|
|
9786
10047
|
}
|
|
9787
|
-
var
|
|
10048
|
+
var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9788
10049
|
var parseEvent = (line, file, offset) => {
|
|
9789
10050
|
let value;
|
|
9790
10051
|
try {
|
|
@@ -9792,11 +10053,11 @@ var parseEvent = (line, file, offset) => {
|
|
|
9792
10053
|
} catch {
|
|
9793
10054
|
throw new LogDamage(`${file}: unreadable event at byte ${offset}`);
|
|
9794
10055
|
}
|
|
9795
|
-
if (!
|
|
10056
|
+
if (!isObject3(value) || typeof value.v !== "number")
|
|
9796
10057
|
throw new LogDamage(`${file}: malformed event at byte ${offset}`);
|
|
9797
10058
|
if (value.v > EVENT_VERSION)
|
|
9798
10059
|
throw new LogDamage(`${file}: event format v${value.v} was written by a newer Workit; upgrade Workit to read this task`, true);
|
|
9799
|
-
if (typeof value.seq !== "number" || !Number.isInteger(value.seq) || typeof value.type !== "string" || typeof value.id !== "string" || typeof value.task !== "string" || !
|
|
10060
|
+
if (typeof value.seq !== "number" || !Number.isInteger(value.seq) || typeof value.type !== "string" || typeof value.id !== "string" || typeof value.task !== "string" || !isObject3(value.data))
|
|
9800
10061
|
throw new LogDamage(`${file}: malformed event at byte ${offset}`);
|
|
9801
10062
|
return value;
|
|
9802
10063
|
};
|
|
@@ -10067,13 +10328,13 @@ var KNOWN_TYPES = new Set([
|
|
|
10067
10328
|
"decision.updated",
|
|
10068
10329
|
"worker.updated"
|
|
10069
10330
|
]);
|
|
10070
|
-
var
|
|
10071
|
-
var isKey = (value) =>
|
|
10331
|
+
var isObject4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10332
|
+
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");
|
|
10072
10333
|
var BLOB_REF = "$blob";
|
|
10073
|
-
var blobRef = (value) =>
|
|
10334
|
+
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;
|
|
10074
10335
|
var blobDigest = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
10075
10336
|
var toBlob = (value, blobs) => {
|
|
10076
|
-
if (!
|
|
10337
|
+
if (!isObject4(value))
|
|
10077
10338
|
return value;
|
|
10078
10339
|
const digest = blobDigest(value);
|
|
10079
10340
|
blobs.write(digest, value);
|
|
@@ -10097,7 +10358,7 @@ var decode2 = (value, blobs) => {
|
|
|
10097
10358
|
return blobs.read(digest);
|
|
10098
10359
|
if (Array.isArray(value))
|
|
10099
10360
|
return value.map((item) => decode2(item, blobs));
|
|
10100
|
-
if (
|
|
10361
|
+
if (isObject4(value)) {
|
|
10101
10362
|
const out = {};
|
|
10102
10363
|
for (const [key, item] of Object.entries(value))
|
|
10103
10364
|
out[key] = decode2(item, blobs);
|
|
@@ -10116,7 +10377,7 @@ function reduce(state, event, blobs) {
|
|
|
10116
10377
|
}
|
|
10117
10378
|
let next = state;
|
|
10118
10379
|
if ("record" in data) {
|
|
10119
|
-
if (!
|
|
10380
|
+
if (!isObject4(data.record))
|
|
10120
10381
|
throw new LogDamage(`event ${event.seq} carries a malformed record`);
|
|
10121
10382
|
next = {
|
|
10122
10383
|
record: decode2(data.record, blobs),
|
|
@@ -10138,7 +10399,7 @@ function reduce(state, event, blobs) {
|
|
|
10138
10399
|
}
|
|
10139
10400
|
if ("key" in data)
|
|
10140
10401
|
next.key = isKey(data.key) ? data.key : null;
|
|
10141
|
-
if ("legacy" in data &&
|
|
10402
|
+
if ("legacy" in data && isObject4(data.legacy))
|
|
10142
10403
|
next.legacy = {
|
|
10143
10404
|
path: String(data.legacy.path ?? ""),
|
|
10144
10405
|
digest: String(data.legacy.digest ?? "")
|
|
@@ -10194,8 +10455,8 @@ var AUTO_COMPACT_BYTES = 2 * 1024 * 1024;
|
|
|
10194
10455
|
var TOMBSTONE = Symbol("tombstone");
|
|
10195
10456
|
var STALE_TEMPORARY_MS = 60 * 60000;
|
|
10196
10457
|
var INDEX_VERSION = 3;
|
|
10197
|
-
var
|
|
10198
|
-
var hostSession = (value) =>
|
|
10458
|
+
var isObject5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10459
|
+
var hostSession = (value) => isObject5(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
|
|
10199
10460
|
var indexEntry = (task, key, legacy, file) => {
|
|
10200
10461
|
const sessions = [];
|
|
10201
10462
|
const intent = hostSession(task.intent.provenance.session);
|
|
@@ -11008,7 +11269,7 @@ class TaskStore {
|
|
|
11008
11269
|
readSnapshot(taskId) {
|
|
11009
11270
|
try {
|
|
11010
11271
|
const value = JSON.parse(fs7.readFileSync(this.snapshotPath(taskId), "utf8"));
|
|
11011
|
-
if (!
|
|
11272
|
+
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))
|
|
11012
11273
|
return null;
|
|
11013
11274
|
return value;
|
|
11014
11275
|
} catch {
|
|
@@ -11354,11 +11615,11 @@ class TaskStore {
|
|
|
11354
11615
|
readIndex() {
|
|
11355
11616
|
try {
|
|
11356
11617
|
const value = JSON.parse(fs7.readFileSync(this.paths().index, "utf8"));
|
|
11357
|
-
if (!
|
|
11618
|
+
if (!isObject5(value) || value.version !== INDEX_VERSION || !isObject5(value.tasks))
|
|
11358
11619
|
return {};
|
|
11359
11620
|
const tasks = {};
|
|
11360
11621
|
for (const [id, entry] of Object.entries(value.tasks))
|
|
11361
|
-
if (
|
|
11622
|
+
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))
|
|
11362
11623
|
tasks[id] = entry;
|
|
11363
11624
|
return tasks;
|
|
11364
11625
|
} catch {
|
|
@@ -11402,12 +11663,12 @@ class TaskStore {
|
|
|
11402
11663
|
return this.parseValue(value, schema, label);
|
|
11403
11664
|
}
|
|
11404
11665
|
parseValue(value, schema, label) {
|
|
11405
|
-
if (
|
|
11666
|
+
if (isObject5(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
11406
11667
|
return failure2("unsupported_version", `unsupported ${label} schema version`);
|
|
11407
11668
|
const parsed = parseStoredRecord(schema, value);
|
|
11408
11669
|
if (parsed.success)
|
|
11409
11670
|
return success(null, null, parsed.data);
|
|
11410
|
-
const writerVersion =
|
|
11671
|
+
const writerVersion = isObject5(value) && isObject5(value.runtime) ? value.runtime.updatedWith : null;
|
|
11411
11672
|
if (parsed.critical.length > 0)
|
|
11412
11673
|
return failure2("recovery_required", `${label} record requires fields this Workit cannot read (${parsed.critical.join(", ")}); upgrade Workit before mutating this checkout`);
|
|
11413
11674
|
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
@@ -11609,7 +11870,7 @@ class TaskStore {
|
|
|
11609
11870
|
}
|
|
11610
11871
|
try {
|
|
11611
11872
|
const value = JSON.parse(bytes);
|
|
11612
|
-
if (
|
|
11873
|
+
if (isObject5(value) && isObject5(value.store) && value.store.format === "workit-store")
|
|
11613
11874
|
return TOMBSTONE;
|
|
11614
11875
|
} catch {}
|
|
11615
11876
|
return { bytes };
|
|
@@ -11654,7 +11915,7 @@ class TaskStore {
|
|
|
11654
11915
|
isStub(file) {
|
|
11655
11916
|
try {
|
|
11656
11917
|
const value = JSON.parse(fs7.readFileSync(file, "utf8"));
|
|
11657
|
-
return
|
|
11918
|
+
return isObject5(value) && isObject5(value.store) && value.store.format === "workit-store";
|
|
11658
11919
|
} catch {
|
|
11659
11920
|
return false;
|
|
11660
11921
|
}
|
|
@@ -11825,10 +12086,10 @@ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
|
11825
12086
|
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
11826
12087
|
|
|
11827
12088
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
11828
|
-
import { createHash as
|
|
11829
|
-
import * as
|
|
11830
|
-
import { spawnSync as
|
|
11831
|
-
import
|
|
12089
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
12090
|
+
import * as fs11 from "node:fs";
|
|
12091
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
12092
|
+
import path11 from "node:path";
|
|
11832
12093
|
|
|
11833
12094
|
// packages/workit-core/src/check-config.ts
|
|
11834
12095
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
@@ -12068,6 +12329,7 @@ var line = (run) => {
|
|
|
12068
12329
|
const value = run.stdout.trim();
|
|
12069
12330
|
return value ? value : null;
|
|
12070
12331
|
};
|
|
12332
|
+
var safeArg = (value) => value.length > 0 && !value.startsWith("-");
|
|
12071
12333
|
function headSha(cwd) {
|
|
12072
12334
|
return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
|
|
12073
12335
|
}
|
|
@@ -12159,124 +12421,733 @@ function worktreeSignal(cwd, options = {}) {
|
|
|
12159
12421
|
}
|
|
12160
12422
|
return `sig:${hash.digest("hex")}`;
|
|
12161
12423
|
}
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
var digestBytes = (value) => createHash6("sha256").update(value).digest("hex");
|
|
12165
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path9.sep}`);
|
|
12166
|
-
var relative = (root, candidate) => path9.relative(root, candidate).split(path9.sep).join("/") || ".";
|
|
12167
|
-
var canonicalPath = (value) => {
|
|
12168
|
-
if (typeof value !== "string" || !value)
|
|
12424
|
+
function mergeBase(cwd, a, b) {
|
|
12425
|
+
if (!safeArg(a) || !safeArg(b))
|
|
12169
12426
|
return null;
|
|
12170
|
-
|
|
12427
|
+
return line(git2(cwd, ["merge-base", a, b]));
|
|
12428
|
+
}
|
|
12429
|
+
function patchId(cwd, base, head) {
|
|
12430
|
+
if (!safeArg(base) || !safeArg(head))
|
|
12171
12431
|
return null;
|
|
12172
|
-
const
|
|
12173
|
-
|
|
12432
|
+
const diff = git2(cwd, [
|
|
12433
|
+
"diff",
|
|
12434
|
+
"-U3",
|
|
12435
|
+
"--diff-algorithm=myers",
|
|
12436
|
+
"--indent-heuristic",
|
|
12437
|
+
"--no-color",
|
|
12438
|
+
"--no-ext-diff",
|
|
12439
|
+
"--no-textconv",
|
|
12440
|
+
"--no-renames",
|
|
12441
|
+
"--full-index",
|
|
12442
|
+
"--src-prefix=a/",
|
|
12443
|
+
"--dst-prefix=b/",
|
|
12444
|
+
`${base}...${head}`,
|
|
12445
|
+
"--"
|
|
12446
|
+
]);
|
|
12447
|
+
if (!diff.ok || !diff.stdout.trim())
|
|
12174
12448
|
return null;
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
|
|
12178
|
-
|
|
12179
|
-
|
|
12180
|
-
|
|
12181
|
-
|
|
12449
|
+
const id = git2(cwd, ["patch-id", "--stable"], { input: diff.stdout });
|
|
12450
|
+
return line(id)?.split(/\s+/u)[0] ?? null;
|
|
12451
|
+
}
|
|
12452
|
+
function remoteNames(cwd) {
|
|
12453
|
+
return (git2(cwd, ["remote"]).stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
|
|
12454
|
+
}
|
|
12455
|
+
function resolveRef(cwd, ref) {
|
|
12456
|
+
if (!safeArg(ref))
|
|
12182
12457
|
return null;
|
|
12458
|
+
return line(git2(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
12459
|
+
}
|
|
12460
|
+
var config2 = (cwd, key) => line(git2(cwd, ["config", "--get", key]));
|
|
12461
|
+
function currentBranch(cwd) {
|
|
12462
|
+
return line(git2(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
|
|
12463
|
+
}
|
|
12464
|
+
function pushRemoteName(cwd, branch) {
|
|
12465
|
+
const name = branch ?? currentBranch(cwd);
|
|
12466
|
+
const remotes = remoteNames(cwd);
|
|
12467
|
+
const candidates = [
|
|
12468
|
+
name ? config2(cwd, `branch.${name}.pushRemote`) : null,
|
|
12469
|
+
config2(cwd, "remote.pushDefault"),
|
|
12470
|
+
name ? config2(cwd, `branch.${name}.remote`) : null
|
|
12471
|
+
];
|
|
12472
|
+
for (const candidate of candidates)
|
|
12473
|
+
if (candidate && candidate !== "." && remotes.includes(candidate))
|
|
12474
|
+
return candidate;
|
|
12475
|
+
if (remotes.includes("origin"))
|
|
12476
|
+
return "origin";
|
|
12477
|
+
return remotes.length === 1 ? remotes[0] : null;
|
|
12478
|
+
}
|
|
12479
|
+
|
|
12480
|
+
// packages/workit-core/src/core/policy/derive.ts
|
|
12481
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
12482
|
+
import path10 from "node:path";
|
|
12483
|
+
|
|
12484
|
+
// packages/workit-core/src/ledger.ts
|
|
12485
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
12486
|
+
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
12487
|
+
import fs10 from "node:fs";
|
|
12488
|
+
import path9 from "node:path";
|
|
12489
|
+
var LEDGER_VERSION = 1;
|
|
12490
|
+
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
12491
|
+
var FAILING = new Set(["failed", "blocked"]);
|
|
12492
|
+
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
12493
|
+
var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
|
|
12494
|
+
var err = (code, error, unblock) => ({
|
|
12495
|
+
ok: false,
|
|
12496
|
+
code,
|
|
12497
|
+
error,
|
|
12498
|
+
...unblock ? { unblock } : {}
|
|
12499
|
+
});
|
|
12500
|
+
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
12501
|
+
const run = spawnSync5("git", args, {
|
|
12502
|
+
cwd,
|
|
12503
|
+
encoding: "utf8",
|
|
12504
|
+
timeout: timeoutMs,
|
|
12505
|
+
killSignal: "SIGKILL",
|
|
12506
|
+
windowsHide: true,
|
|
12507
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
12508
|
+
env: {
|
|
12509
|
+
...process.env,
|
|
12510
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
12511
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
12512
|
+
LC_ALL: "C",
|
|
12513
|
+
LANGUAGE: "C"
|
|
12514
|
+
}
|
|
12515
|
+
});
|
|
12183
12516
|
return {
|
|
12184
|
-
|
|
12185
|
-
|
|
12186
|
-
|
|
12517
|
+
ok: run.status === 0,
|
|
12518
|
+
stdout: run.stdout ?? "",
|
|
12519
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
12187
12520
|
};
|
|
12188
12521
|
};
|
|
12189
|
-
var
|
|
12190
|
-
|
|
12191
|
-
const
|
|
12192
|
-
|
|
12193
|
-
return false;
|
|
12194
|
-
const paths = normalized.paths;
|
|
12195
|
-
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
12196
|
-
};
|
|
12197
|
-
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
12198
|
-
var compareCodeUnits2 = (left, right) => {
|
|
12199
|
-
const length = Math.min(left.length, right.length);
|
|
12200
|
-
for (let index = 0;index < length; index += 1) {
|
|
12201
|
-
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
12202
|
-
if (difference !== 0)
|
|
12203
|
-
return difference;
|
|
12204
|
-
}
|
|
12205
|
-
return left.length - right.length;
|
|
12522
|
+
var git3 = (cwd, args) => {
|
|
12523
|
+
const run = gitRun2(cwd, args);
|
|
12524
|
+
const value = run.ok ? run.stdout.trim() : "";
|
|
12525
|
+
return value ? value : null;
|
|
12206
12526
|
};
|
|
12207
|
-
var
|
|
12208
|
-
|
|
12209
|
-
|
|
12210
|
-
|
|
12211
|
-
});
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
|
|
12219
|
-
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
12227
|
-
|
|
12527
|
+
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
12528
|
+
function resolveCommit(cwd, ref) {
|
|
12529
|
+
if (!safeRef(ref))
|
|
12530
|
+
return null;
|
|
12531
|
+
return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
12532
|
+
}
|
|
12533
|
+
function branchHead(cwd, branch) {
|
|
12534
|
+
if (!safeRef(branch))
|
|
12535
|
+
return null;
|
|
12536
|
+
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
12537
|
+
}
|
|
12538
|
+
function defaultBase(cwd) {
|
|
12539
|
+
const originHead = git3(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
|
|
12540
|
+
if (originHead && resolveCommit(cwd, originHead))
|
|
12541
|
+
return originHead;
|
|
12542
|
+
for (const candidate of ["origin/main", "origin/master", "main", "master"])
|
|
12543
|
+
if (resolveCommit(cwd, candidate))
|
|
12544
|
+
return candidate;
|
|
12545
|
+
return null;
|
|
12546
|
+
}
|
|
12547
|
+
function diffHash(cwd, base, head) {
|
|
12548
|
+
if (!safeRef(base) || !safeRef(head))
|
|
12549
|
+
return null;
|
|
12550
|
+
const diff = gitRun2(cwd, [
|
|
12551
|
+
"diff",
|
|
12552
|
+
"-U3",
|
|
12553
|
+
"--diff-algorithm=myers",
|
|
12554
|
+
"--indent-heuristic",
|
|
12555
|
+
"--no-color",
|
|
12556
|
+
"--no-ext-diff",
|
|
12557
|
+
"--no-textconv",
|
|
12558
|
+
"--no-renames",
|
|
12559
|
+
"--full-index",
|
|
12560
|
+
"--binary",
|
|
12561
|
+
"--src-prefix=a/",
|
|
12562
|
+
"--dst-prefix=b/",
|
|
12563
|
+
`${base}...${head}`,
|
|
12564
|
+
"--"
|
|
12565
|
+
], GIT_TIMEOUTS.worktree);
|
|
12566
|
+
if (!diff.ok || !diff.stdout)
|
|
12567
|
+
return null;
|
|
12568
|
+
const hash = createHash6("sha256");
|
|
12569
|
+
for (const line of diff.stdout.split(`
|
|
12570
|
+
`)) {
|
|
12571
|
+
if (line.startsWith("index "))
|
|
12572
|
+
continue;
|
|
12573
|
+
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
12574
|
+
hash.update(`
|
|
12575
|
+
`);
|
|
12228
12576
|
}
|
|
12229
|
-
return {
|
|
12577
|
+
return `sha256:${hash.digest("hex")}`;
|
|
12578
|
+
}
|
|
12579
|
+
function storeRoot(cwd) {
|
|
12580
|
+
const location = resolveStore(path9.resolve(cwd));
|
|
12581
|
+
if (location instanceof Error)
|
|
12582
|
+
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");
|
|
12583
|
+
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
12584
|
+
}
|
|
12585
|
+
function ledgerPath(cwd) {
|
|
12586
|
+
const root = storeRoot(cwd);
|
|
12587
|
+
return root.ok ? { ok: true, value: path9.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
12588
|
+
}
|
|
12589
|
+
var NETWORK_FS_TYPES = new Set([
|
|
12590
|
+
26985,
|
|
12591
|
+
20859,
|
|
12592
|
+
4266872130,
|
|
12593
|
+
4283649346,
|
|
12594
|
+
1702057286,
|
|
12595
|
+
1397113167,
|
|
12596
|
+
12805120,
|
|
12597
|
+
16914839,
|
|
12598
|
+
1196443219,
|
|
12599
|
+
198183888
|
|
12600
|
+
]);
|
|
12601
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12602
|
+
var str2 = (value) => typeof value === "string" ? value : null;
|
|
12603
|
+
var normalizeActor = (value) => {
|
|
12604
|
+
const actor = isRecord2(value) ? value : {};
|
|
12605
|
+
return {
|
|
12606
|
+
host: str2(actor.host) ?? "unknown",
|
|
12607
|
+
session: str2(actor.session),
|
|
12608
|
+
agentId: str2(actor.agentId)
|
|
12609
|
+
};
|
|
12230
12610
|
};
|
|
12231
|
-
|
|
12232
|
-
|
|
12611
|
+
function supersedeAllowed(by, target) {
|
|
12612
|
+
if (by.type !== target.type)
|
|
12613
|
+
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
12614
|
+
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
12615
|
+
return "only the session that wrote a row can supersede it";
|
|
12616
|
+
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
12617
|
+
return "a self verdict cannot replace an independent verdict";
|
|
12618
|
+
return null;
|
|
12619
|
+
}
|
|
12620
|
+
function readLedger(cwd) {
|
|
12621
|
+
const file = ledgerPath(cwd);
|
|
12622
|
+
if (!file.ok)
|
|
12623
|
+
return file;
|
|
12624
|
+
let text = "";
|
|
12233
12625
|
try {
|
|
12234
|
-
|
|
12235
|
-
} catch {
|
|
12236
|
-
|
|
12626
|
+
text = fs10.readFileSync(file.value, "utf8");
|
|
12627
|
+
} catch (error) {
|
|
12628
|
+
if (error.code === "ENOENT")
|
|
12629
|
+
return { ok: true, value: { path: file.value, rows: [], skipped: 0 } };
|
|
12630
|
+
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
12237
12631
|
}
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12632
|
+
const rows = [];
|
|
12633
|
+
let skipped = 0;
|
|
12634
|
+
for (const raw of text.split(`
|
|
12635
|
+
`)) {
|
|
12636
|
+
const trimmed = raw.trim();
|
|
12637
|
+
if (!trimmed)
|
|
12241
12638
|
continue;
|
|
12242
|
-
|
|
12243
|
-
let stat;
|
|
12639
|
+
let parsed;
|
|
12244
12640
|
try {
|
|
12245
|
-
|
|
12641
|
+
parsed = JSON.parse(trimmed);
|
|
12246
12642
|
} catch {
|
|
12247
|
-
|
|
12643
|
+
skipped += 1;
|
|
12248
12644
|
continue;
|
|
12249
12645
|
}
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
uncertain = walk(root, target, output) || uncertain;
|
|
12254
|
-
}
|
|
12255
|
-
return uncertain;
|
|
12256
|
-
};
|
|
12257
|
-
var scopeRoots = (root, scope) => {
|
|
12258
|
-
const roots = [];
|
|
12259
|
-
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
12260
|
-
const target = path9.resolve(root, item);
|
|
12261
|
-
if (!inside(root, target))
|
|
12262
|
-
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` }] });
|
|
12263
|
-
let ancestor = target;
|
|
12264
|
-
while (!fs10.existsSync(ancestor) && ancestor !== root)
|
|
12265
|
-
ancestor = path9.dirname(ancestor);
|
|
12266
|
-
try {
|
|
12267
|
-
if (!inside(root, fs10.realpathSync(ancestor)))
|
|
12268
|
-
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` }] });
|
|
12269
|
-
} catch {
|
|
12270
|
-
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
12646
|
+
if (!isRecord2(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
|
|
12647
|
+
skipped += 1;
|
|
12648
|
+
continue;
|
|
12271
12649
|
}
|
|
12272
|
-
|
|
12650
|
+
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
12651
|
+
rows.push({
|
|
12652
|
+
...parsed,
|
|
12653
|
+
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
12654
|
+
id: parsed.id,
|
|
12655
|
+
at: parsed.at,
|
|
12656
|
+
type: parsed.type,
|
|
12657
|
+
actor: normalizeActor(parsed.actor),
|
|
12658
|
+
branch: str2(parsed.branch),
|
|
12659
|
+
head: str2(parsed.head),
|
|
12660
|
+
base: str2(parsed.base),
|
|
12661
|
+
baseSha: str2(parsed.baseSha),
|
|
12662
|
+
patchId: str2(parsed.patchId),
|
|
12663
|
+
diffHash: str2(parsed.diffHash),
|
|
12664
|
+
tree: str2(parsed.tree),
|
|
12665
|
+
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
12666
|
+
...pr === undefined ? {} : { pr },
|
|
12667
|
+
...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
12668
|
+
seq: rows.length + 1,
|
|
12669
|
+
superseded: false,
|
|
12670
|
+
supersedeIgnored: false
|
|
12671
|
+
});
|
|
12672
|
+
}
|
|
12673
|
+
const byId = new Map;
|
|
12674
|
+
for (const row of rows) {
|
|
12675
|
+
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
12676
|
+
if (target && supersedeAllowed(row, target) === null)
|
|
12677
|
+
target.superseded = true;
|
|
12678
|
+
else if (row.supersedes)
|
|
12679
|
+
row.supersedeIgnored = true;
|
|
12680
|
+
if (!byId.has(row.id))
|
|
12681
|
+
byId.set(row.id, row);
|
|
12682
|
+
}
|
|
12683
|
+
return { ok: true, value: { path: file.value, rows, skipped } };
|
|
12684
|
+
}
|
|
12685
|
+
function verdictBasis(verdict, current) {
|
|
12686
|
+
if (verdict.head && verdict.head === current.head)
|
|
12687
|
+
return "fresh";
|
|
12688
|
+
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
12689
|
+
return "carried";
|
|
12690
|
+
return "stale";
|
|
12691
|
+
}
|
|
12692
|
+
var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded;
|
|
12693
|
+
function trailerSessions(cwd, base, head) {
|
|
12694
|
+
if (!base || !head || !safeRef(base) || !safeRef(head))
|
|
12695
|
+
return [];
|
|
12696
|
+
const out = git3(cwd, [
|
|
12697
|
+
"log",
|
|
12698
|
+
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
12699
|
+
`${base}..${head}`
|
|
12700
|
+
]);
|
|
12701
|
+
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
12702
|
+
}
|
|
12703
|
+
function authorSessions(cwd, rows, branch, range = { base: null, head: null }) {
|
|
12704
|
+
const sessions = new Set;
|
|
12705
|
+
for (const row of rows) {
|
|
12706
|
+
if (row.branch !== branch || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
12707
|
+
continue;
|
|
12708
|
+
const session = str2(row.session) ?? row.actor.session;
|
|
12709
|
+
if (session)
|
|
12710
|
+
sessions.add(session);
|
|
12711
|
+
}
|
|
12712
|
+
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
12713
|
+
sessions.add(session);
|
|
12714
|
+
return sessions;
|
|
12715
|
+
}
|
|
12716
|
+
var independenceReasons = (row, authors) => {
|
|
12717
|
+
const reasons = [];
|
|
12718
|
+
if (!row.actor.session)
|
|
12719
|
+
reasons.push("no_session");
|
|
12720
|
+
else if (authors.has(row.actor.session))
|
|
12721
|
+
reasons.push("author_session");
|
|
12722
|
+
if (row.self === true && !reasons.includes("no_session"))
|
|
12723
|
+
reasons.push("self");
|
|
12724
|
+
return reasons;
|
|
12725
|
+
};
|
|
12726
|
+
function effectiveOf(rows, authors) {
|
|
12727
|
+
let effective = null;
|
|
12728
|
+
for (const row of rows) {
|
|
12729
|
+
if (!effective) {
|
|
12730
|
+
effective = row;
|
|
12731
|
+
continue;
|
|
12732
|
+
}
|
|
12733
|
+
const rowIndependent = independenceReasons(row, authors).length === 0;
|
|
12734
|
+
const effIndependent = independenceReasons(effective, authors).length === 0;
|
|
12735
|
+
if (effIndependent && !rowIndependent)
|
|
12736
|
+
continue;
|
|
12737
|
+
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session)
|
|
12738
|
+
continue;
|
|
12739
|
+
effective = row;
|
|
12740
|
+
}
|
|
12741
|
+
return effective;
|
|
12742
|
+
}
|
|
12743
|
+
function restackCarries(rows, branch, from, to) {
|
|
12744
|
+
if (!from || !to)
|
|
12745
|
+
return false;
|
|
12746
|
+
const edges = new Map;
|
|
12747
|
+
for (const row of rows)
|
|
12748
|
+
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
12749
|
+
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
12750
|
+
const seen = new Set([from]);
|
|
12751
|
+
const queue = [from];
|
|
12752
|
+
while (queue.length) {
|
|
12753
|
+
const next = queue.shift();
|
|
12754
|
+
if (next === to)
|
|
12755
|
+
return true;
|
|
12756
|
+
for (const target of edges.get(next) ?? [])
|
|
12757
|
+
if (!seen.has(target)) {
|
|
12758
|
+
seen.add(target);
|
|
12759
|
+
queue.push(target);
|
|
12760
|
+
}
|
|
12761
|
+
}
|
|
12762
|
+
return false;
|
|
12763
|
+
}
|
|
12764
|
+
function checkVerdicts(cwd, branch, rows) {
|
|
12765
|
+
const head = branchHead(cwd, branch);
|
|
12766
|
+
const fallbackBase = defaultBase(cwd);
|
|
12767
|
+
const keyCache = new Map;
|
|
12768
|
+
const keyFor = (base) => {
|
|
12769
|
+
if (!base || !head)
|
|
12770
|
+
return { patchId: null, diffHash: null };
|
|
12771
|
+
let key = keyCache.get(base);
|
|
12772
|
+
if (!key) {
|
|
12773
|
+
key = { patchId: patchId(cwd, base, head), diffHash: diffHash(cwd, base, head) };
|
|
12774
|
+
keyCache.set(base, key);
|
|
12775
|
+
}
|
|
12776
|
+
return key;
|
|
12777
|
+
};
|
|
12778
|
+
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head });
|
|
12779
|
+
const byKind = new Map;
|
|
12780
|
+
for (const row of rows)
|
|
12781
|
+
if (row.branch === branch && isLiveVerdict(row)) {
|
|
12782
|
+
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
12783
|
+
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
12784
|
+
}
|
|
12785
|
+
const verdicts = [];
|
|
12786
|
+
for (const [kind, list] of byKind) {
|
|
12787
|
+
const row = effectiveOf(list, authors);
|
|
12788
|
+
if (!row)
|
|
12789
|
+
continue;
|
|
12790
|
+
let basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
12791
|
+
if (basis === "stale" && restackCarries(rows, branch, row.head, head))
|
|
12792
|
+
basis = "carried";
|
|
12793
|
+
const current = basis !== "stale";
|
|
12794
|
+
const independence = independenceReasons(row, authors);
|
|
12795
|
+
const reasons = [
|
|
12796
|
+
...current ? [] : ["stale"],
|
|
12797
|
+
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
12798
|
+
...independence
|
|
12799
|
+
];
|
|
12800
|
+
verdicts.push({
|
|
12801
|
+
kind,
|
|
12802
|
+
basis,
|
|
12803
|
+
current,
|
|
12804
|
+
independent: independence.length === 0,
|
|
12805
|
+
accepted: reasons.length === 0,
|
|
12806
|
+
reasons,
|
|
12807
|
+
verdict: row
|
|
12808
|
+
});
|
|
12809
|
+
}
|
|
12810
|
+
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
12811
|
+
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
12812
|
+
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
12813
|
+
const newestAccepted = verdicts.findLast((entry) => entry.accepted) ?? null;
|
|
12814
|
+
const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
12815
|
+
accepted: false,
|
|
12816
|
+
verdict: failing?.verdict ?? null,
|
|
12817
|
+
reasons: failing ? ["failing_verdict"] : newestCurrent?.reasons ?? ["no_verdict"]
|
|
12818
|
+
};
|
|
12819
|
+
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
12820
|
+
return {
|
|
12821
|
+
branch,
|
|
12822
|
+
head,
|
|
12823
|
+
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
12824
|
+
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
12825
|
+
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
12826
|
+
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
12827
|
+
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
12828
|
+
accepted,
|
|
12829
|
+
verdicts,
|
|
12830
|
+
authors: [...authors].toSorted()
|
|
12831
|
+
};
|
|
12832
|
+
}
|
|
12833
|
+
var OBSERVED_ONLY = new Set(["check", ...PR_ROW_TYPES]);
|
|
12834
|
+
|
|
12835
|
+
// packages/workit-core/src/core/policy/derive.ts
|
|
12836
|
+
var RULES = {
|
|
12837
|
+
test: "check:test",
|
|
12838
|
+
self: "verdict:self",
|
|
12839
|
+
nonAuthor: "verdict:non-author",
|
|
12840
|
+
verified: "verdict:verified",
|
|
12841
|
+
product: "decision:product",
|
|
12842
|
+
plan: "plan"
|
|
12843
|
+
};
|
|
12844
|
+
var derived2 = (spec) => ({
|
|
12845
|
+
...spec,
|
|
12846
|
+
id: requirementId({
|
|
12847
|
+
ruleId: spec.ruleId,
|
|
12848
|
+
scope: spec.scope,
|
|
12849
|
+
satisfaction: spec.satisfaction,
|
|
12850
|
+
before: spec.before,
|
|
12851
|
+
dependentAction: spec.dependentAction
|
|
12852
|
+
})
|
|
12853
|
+
});
|
|
12854
|
+
var sorted = (values) => [...new Set(values)].toSorted();
|
|
12855
|
+
var normalizedScope = (scope) => ({
|
|
12856
|
+
description: scope.description,
|
|
12857
|
+
paths: sorted(scope.paths),
|
|
12858
|
+
exclusions: sorted(scope.exclusions)
|
|
12859
|
+
});
|
|
12860
|
+
function deriveRequirements(judgment, scope, constraints, verification = "self") {
|
|
12861
|
+
const out = [];
|
|
12862
|
+
const base = { scope, dependentAction: null };
|
|
12863
|
+
if (judgment.behaviorChange)
|
|
12864
|
+
out.push(derived2({
|
|
12865
|
+
...base,
|
|
12866
|
+
ruleId: RULES.test,
|
|
12867
|
+
dimension: "testing",
|
|
12868
|
+
reason: "Behavior changes: the configured test check must pass on the final tree.",
|
|
12869
|
+
satisfaction: "Run `workit check test` on the final tree.",
|
|
12870
|
+
before: "close",
|
|
12871
|
+
acceptanceAllowed: false
|
|
12872
|
+
}));
|
|
12873
|
+
if (judgment.behaviorChange && judgment.riskTier === "normal")
|
|
12874
|
+
out.push(verification === "independent" ? derived2({
|
|
12875
|
+
...base,
|
|
12876
|
+
ruleId: RULES.nonAuthor,
|
|
12877
|
+
dimension: "review",
|
|
12878
|
+
reason: "This workspace requires independent verification: a session that did not author the change records the verdict.",
|
|
12879
|
+
satisfaction: "An independent session records a passing verdict: `workit ledger verdict verified --how <what it exercised>`.",
|
|
12880
|
+
before: "close",
|
|
12881
|
+
acceptanceAllowed: false
|
|
12882
|
+
}) : derived2({
|
|
12883
|
+
...base,
|
|
12884
|
+
ruleId: RULES.self,
|
|
12885
|
+
dimension: "review",
|
|
12886
|
+
reason: "A behavior change needs a recorded verdict; the author's own is allowed and is shown as self-reviewed, never verified.",
|
|
12887
|
+
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).",
|
|
12888
|
+
before: "close",
|
|
12889
|
+
acceptanceAllowed: false
|
|
12890
|
+
}));
|
|
12891
|
+
if (judgment.riskTier === "high")
|
|
12892
|
+
out.push(derived2({
|
|
12893
|
+
...base,
|
|
12894
|
+
ruleId: RULES.verified,
|
|
12895
|
+
dimension: "review",
|
|
12896
|
+
reason: "High risk: an independent verifier must exercise the live surface.",
|
|
12897
|
+
satisfaction: "An independent verifier runs the real surface and records `workit ledger verdict verified --kind live --how <what it exercised>`.",
|
|
12898
|
+
before: "close",
|
|
12899
|
+
acceptanceAllowed: false
|
|
12900
|
+
}));
|
|
12901
|
+
if (judgment.productChoiceOpen)
|
|
12902
|
+
out.push(derived2({
|
|
12903
|
+
...base,
|
|
12904
|
+
ruleId: RULES.product,
|
|
12905
|
+
dimension: "decisions",
|
|
12906
|
+
reason: "A product or preference choice is open; the user decides it before code is written.",
|
|
12907
|
+
satisfaction: 'Ask the user (with your recommendation), then record the answer: `workit ledger decision "<choice>" --why "<reason>"`.',
|
|
12908
|
+
before: "write",
|
|
12909
|
+
acceptanceAllowed: false
|
|
12910
|
+
}));
|
|
12911
|
+
if (judgment.needsPlan || judgment.riskTier === "high")
|
|
12912
|
+
out.push(derived2({
|
|
12913
|
+
...base,
|
|
12914
|
+
ruleId: RULES.plan,
|
|
12915
|
+
dimension: "artifacts",
|
|
12916
|
+
reason: judgment.needsPlan ? "The work needs a written plan or spec before implementation." : "High-risk work needs a written plan before implementation.",
|
|
12917
|
+
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>`.",
|
|
12918
|
+
before: "write",
|
|
12919
|
+
acceptanceAllowed: true
|
|
12920
|
+
}));
|
|
12921
|
+
for (const constraint of constraints)
|
|
12922
|
+
for (const obligation of constraint.requires)
|
|
12923
|
+
out.push(derived2({
|
|
12924
|
+
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
12925
|
+
kind: obligation.kind,
|
|
12926
|
+
scope: normalizedScope(obligation.scope),
|
|
12927
|
+
refs: obligation.refs.map((ref) => JSON.stringify(ref)).toSorted(),
|
|
12928
|
+
method: obligation.method,
|
|
12929
|
+
before: obligation.before,
|
|
12930
|
+
dependentAction: obligation.dependentAction
|
|
12931
|
+
})}`,
|
|
12932
|
+
dimension: obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge"),
|
|
12933
|
+
scope: obligation.scope,
|
|
12934
|
+
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
12935
|
+
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
12936
|
+
before: obligation.before,
|
|
12937
|
+
dependentAction: obligation.dependentAction,
|
|
12938
|
+
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
12939
|
+
}));
|
|
12940
|
+
const seen = new Set;
|
|
12941
|
+
return out.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true));
|
|
12942
|
+
}
|
|
12943
|
+
var latestJudgment = (task) => task.judgments?.at(-1)?.data ?? null;
|
|
12944
|
+
var ledgerView = (root) => {
|
|
12945
|
+
let branch;
|
|
12946
|
+
let rows;
|
|
12947
|
+
return {
|
|
12948
|
+
branch: () => branch === undefined ? branch = currentBranch(root) : branch,
|
|
12949
|
+
rows: () => {
|
|
12950
|
+
if (rows === undefined) {
|
|
12951
|
+
const read = readLedger(root);
|
|
12952
|
+
rows = read.ok ? read.value.rows : [];
|
|
12953
|
+
}
|
|
12954
|
+
return rows;
|
|
12955
|
+
}
|
|
12956
|
+
};
|
|
12957
|
+
};
|
|
12958
|
+
var productDecision = (task, ledger) => {
|
|
12959
|
+
const decisions = task.decisions.filter((entry) => entry.data.revoked === null && (entry.data.response === "approved" || entry.data.response === "stated") && entry.data.purpose !== "limitation");
|
|
12960
|
+
if (decisions.length)
|
|
12961
|
+
return {
|
|
12962
|
+
met: true,
|
|
12963
|
+
reason: "the user's decision is recorded on the task",
|
|
12964
|
+
decisionIds: decisions.map((entry) => entry.id)
|
|
12965
|
+
};
|
|
12966
|
+
const since = Date.parse(task.createdAt);
|
|
12967
|
+
const branch = ledger.branch();
|
|
12968
|
+
const row = ledger.rows().findLast((item) => item.type === "decision" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
|
|
12969
|
+
return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
|
|
12970
|
+
met: false,
|
|
12971
|
+
reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or re-judge productChoiceOpen=false if it is not open)',
|
|
12972
|
+
decisionIds: []
|
|
12973
|
+
};
|
|
12974
|
+
};
|
|
12975
|
+
var planRecorded = (task, root) => {
|
|
12976
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync3(path10.resolve(root, ref.path)));
|
|
12977
|
+
if (cited)
|
|
12978
|
+
return {
|
|
12979
|
+
met: true,
|
|
12980
|
+
reason: `plan recorded: ${cited.kind === "file" ? cited.path : cited.kind === "external" ? cited.url : ""}`,
|
|
12981
|
+
decisionIds: []
|
|
12982
|
+
};
|
|
12983
|
+
const artifact = task.evidence.some((entry) => entry.data.kind === "artifact" && entry.data.result === "passed");
|
|
12984
|
+
return artifact ? { met: true, reason: "a passing artifact records the plan", decisionIds: [] } : {
|
|
12985
|
+
met: false,
|
|
12986
|
+
reason: "no plan recorded: write it (docs/ and Markdown writes stay allowed), then `workit policy assess --ref <path>`",
|
|
12987
|
+
decisionIds: []
|
|
12988
|
+
};
|
|
12989
|
+
};
|
|
12990
|
+
var verdict = (requirement, root, ledger) => {
|
|
12991
|
+
const branch = ledger.branch();
|
|
12992
|
+
if (!branch)
|
|
12993
|
+
return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
|
|
12994
|
+
const check = checkVerdicts(root, branch, ledger.rows());
|
|
12995
|
+
const failing = check.accepted.reasons.includes("failing_verdict");
|
|
12996
|
+
const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
|
|
12997
|
+
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)));
|
|
12998
|
+
if (met)
|
|
12999
|
+
return {
|
|
13000
|
+
met: true,
|
|
13001
|
+
reason: `${met.accepted ? "accepted independent" : "self-reviewed (author's own)"} ${String(met.verdict.result)} verdict ${met.verdict.id}`,
|
|
13002
|
+
decisionIds: []
|
|
13003
|
+
};
|
|
13004
|
+
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, " ");
|
|
13005
|
+
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>\``;
|
|
13006
|
+
return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
|
|
13007
|
+
};
|
|
13008
|
+
function derivedRuleStatus(task, requirement, root, ledger) {
|
|
13009
|
+
switch (requirement.ruleId) {
|
|
13010
|
+
case RULES.product:
|
|
13011
|
+
return productDecision(task, ledger);
|
|
13012
|
+
case RULES.plan:
|
|
13013
|
+
return planRecorded(task, root);
|
|
13014
|
+
case RULES.self:
|
|
13015
|
+
case RULES.nonAuthor:
|
|
13016
|
+
case RULES.verified:
|
|
13017
|
+
return verdict(requirement, root, ledger);
|
|
13018
|
+
default:
|
|
13019
|
+
return null;
|
|
13020
|
+
}
|
|
13021
|
+
}
|
|
13022
|
+
function writeBlockers(task, root) {
|
|
13023
|
+
if (task.status === "closed" || !task.policy)
|
|
13024
|
+
return [];
|
|
13025
|
+
const ledger = ledgerView(root);
|
|
13026
|
+
return task.policy.requirements.flatMap((requirement) => {
|
|
13027
|
+
if (requirement.before !== "write")
|
|
13028
|
+
return [];
|
|
13029
|
+
const status = derivedRuleStatus(task, requirement, root, ledger);
|
|
13030
|
+
return status && !status.met ? [{ ruleId: requirement.ruleId, reason: status.reason }] : [];
|
|
13031
|
+
});
|
|
13032
|
+
}
|
|
13033
|
+
|
|
13034
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
13035
|
+
var digestBytes = (value) => createHash7("sha256").update(value).digest("hex");
|
|
13036
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path11.sep}`);
|
|
13037
|
+
var relative = (root, candidate) => path11.relative(root, candidate).split(path11.sep).join("/") || ".";
|
|
13038
|
+
var canonicalPath = (value) => {
|
|
13039
|
+
if (typeof value !== "string" || !value)
|
|
13040
|
+
return null;
|
|
13041
|
+
if (value.includes("\\") || value.split("/").includes(".."))
|
|
13042
|
+
return null;
|
|
13043
|
+
const normalized = path11.posix.normalize(value);
|
|
13044
|
+
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
13045
|
+
return null;
|
|
13046
|
+
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
13047
|
+
};
|
|
13048
|
+
var canonicalScope = (scope) => {
|
|
13049
|
+
const paths = scope.paths.length ? scope.paths : ["."];
|
|
13050
|
+
const normalizedPaths = paths.map(canonicalPath);
|
|
13051
|
+
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
13052
|
+
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
13053
|
+
return null;
|
|
13054
|
+
return {
|
|
13055
|
+
description: scope.description,
|
|
13056
|
+
paths: [...new Set(normalizedPaths)],
|
|
13057
|
+
exclusions: [...new Set(normalizedExclusions)]
|
|
13058
|
+
};
|
|
13059
|
+
};
|
|
13060
|
+
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
13061
|
+
var scopeMatches = (value, scope) => {
|
|
13062
|
+
const normalized = canonicalScope(scope);
|
|
13063
|
+
if (!normalized)
|
|
13064
|
+
return false;
|
|
13065
|
+
const paths = normalized.paths;
|
|
13066
|
+
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
13067
|
+
};
|
|
13068
|
+
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
13069
|
+
var compareCodeUnits2 = (left, right) => {
|
|
13070
|
+
const length = Math.min(left.length, right.length);
|
|
13071
|
+
for (let index = 0;index < length; index += 1) {
|
|
13072
|
+
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
13073
|
+
if (difference !== 0)
|
|
13074
|
+
return difference;
|
|
13075
|
+
}
|
|
13076
|
+
return left.length - right.length;
|
|
13077
|
+
};
|
|
13078
|
+
var gitPaths = (root) => {
|
|
13079
|
+
const result = spawnSync6("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
13080
|
+
cwd: root,
|
|
13081
|
+
encoding: "buffer"
|
|
13082
|
+
});
|
|
13083
|
+
if (result.status !== 0) {
|
|
13084
|
+
const stderr = result.stderr?.toString("utf8") ?? "";
|
|
13085
|
+
return {
|
|
13086
|
+
paths: [],
|
|
13087
|
+
uncertain: !/not a git repository/i.test(stderr),
|
|
13088
|
+
git: !/not a git repository/i.test(stderr)
|
|
13089
|
+
};
|
|
13090
|
+
}
|
|
13091
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")) : [];
|
|
13092
|
+
const stagedDeleted = spawnSync6("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
13093
|
+
if (stagedDeleted.status !== 0) {
|
|
13094
|
+
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
13095
|
+
if (!/not a git repository/i.test(stderr))
|
|
13096
|
+
return { paths, uncertain: true, git: true };
|
|
13097
|
+
} else if (stagedDeleted.stdout) {
|
|
13098
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")));
|
|
13099
|
+
}
|
|
13100
|
+
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
13101
|
+
};
|
|
13102
|
+
var walk = (root, directory, output) => {
|
|
13103
|
+
let names;
|
|
13104
|
+
try {
|
|
13105
|
+
names = fs11.readdirSync(directory);
|
|
13106
|
+
} catch {
|
|
13107
|
+
return true;
|
|
13108
|
+
}
|
|
13109
|
+
let uncertain = false;
|
|
13110
|
+
for (const name of names) {
|
|
13111
|
+
if (name === ".git" || name === ".workit")
|
|
13112
|
+
continue;
|
|
13113
|
+
const target = path11.join(directory, name);
|
|
13114
|
+
let stat;
|
|
13115
|
+
try {
|
|
13116
|
+
stat = fs11.lstatSync(target);
|
|
13117
|
+
} catch {
|
|
13118
|
+
uncertain = true;
|
|
13119
|
+
continue;
|
|
13120
|
+
}
|
|
13121
|
+
const item = relative(root, target);
|
|
13122
|
+
output.add(item);
|
|
13123
|
+
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
13124
|
+
uncertain = walk(root, target, output) || uncertain;
|
|
13125
|
+
}
|
|
13126
|
+
return uncertain;
|
|
13127
|
+
};
|
|
13128
|
+
var scopeRoots = (root, scope) => {
|
|
13129
|
+
const roots = [];
|
|
13130
|
+
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
13131
|
+
const target = path11.resolve(root, item);
|
|
13132
|
+
if (!inside(root, target))
|
|
13133
|
+
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` }] });
|
|
13134
|
+
let ancestor = target;
|
|
13135
|
+
while (!fs11.existsSync(ancestor) && ancestor !== root)
|
|
13136
|
+
ancestor = path11.dirname(ancestor);
|
|
13137
|
+
try {
|
|
13138
|
+
if (!inside(root, fs11.realpathSync(ancestor)))
|
|
13139
|
+
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` }] });
|
|
13140
|
+
} catch {
|
|
13141
|
+
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
13142
|
+
}
|
|
13143
|
+
roots.push(target);
|
|
12273
13144
|
}
|
|
12274
13145
|
return success(null, null, roots);
|
|
12275
13146
|
};
|
|
12276
13147
|
function captureCandidate(root, scope, environment = []) {
|
|
12277
13148
|
let checkout;
|
|
12278
13149
|
try {
|
|
12279
|
-
checkout =
|
|
13150
|
+
checkout = fs11.realpathSync(root);
|
|
12280
13151
|
} catch {
|
|
12281
13152
|
return failure2("invalid_input", "candidate root does not exist");
|
|
12282
13153
|
}
|
|
@@ -12294,11 +13165,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12294
13165
|
if (item === ".git" || item === ".workit")
|
|
12295
13166
|
continue;
|
|
12296
13167
|
names.add(item);
|
|
12297
|
-
if (!
|
|
13168
|
+
if (!fs11.existsSync(target))
|
|
12298
13169
|
continue;
|
|
12299
13170
|
let stat;
|
|
12300
13171
|
try {
|
|
12301
|
-
stat =
|
|
13172
|
+
stat = fs11.lstatSync(target);
|
|
12302
13173
|
} catch {
|
|
12303
13174
|
uncertain = true;
|
|
12304
13175
|
continue;
|
|
@@ -12314,17 +13185,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12314
13185
|
for (const item of [...names].toSorted()) {
|
|
12315
13186
|
if (!scopeMatches(item, normalizedScope))
|
|
12316
13187
|
continue;
|
|
12317
|
-
const target =
|
|
13188
|
+
const target = path11.join(checkout, item);
|
|
12318
13189
|
try {
|
|
12319
|
-
const stat =
|
|
13190
|
+
const stat = fs11.lstatSync(target);
|
|
12320
13191
|
if (stat.isSymbolicLink()) {
|
|
12321
|
-
const link =
|
|
13192
|
+
const link = fs11.readlinkSync(target);
|
|
12322
13193
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
12323
13194
|
} else if (stat.isFile()) {
|
|
12324
13195
|
files.push({
|
|
12325
13196
|
path: item,
|
|
12326
13197
|
kind: "file",
|
|
12327
|
-
digest: digestBytes(
|
|
13198
|
+
digest: digestBytes(fs11.readFileSync(target)),
|
|
12328
13199
|
executable: (stat.mode & 73) !== 0
|
|
12329
13200
|
});
|
|
12330
13201
|
} else if (!stat.isDirectory()) {
|
|
@@ -12349,7 +13220,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12349
13220
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
12350
13221
|
if (values.some(({ value }) => value === null))
|
|
12351
13222
|
completeness = "uncertain";
|
|
12352
|
-
const headResult =
|
|
13223
|
+
const headResult = spawnSync6("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
12353
13224
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
12354
13225
|
completeness = "uncertain";
|
|
12355
13226
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -12489,7 +13360,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
12489
13360
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
12490
13361
|
let root;
|
|
12491
13362
|
try {
|
|
12492
|
-
root =
|
|
13363
|
+
root = fs11.realpathSync(checkoutRoot);
|
|
12493
13364
|
} catch {
|
|
12494
13365
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
12495
13366
|
}
|
|
@@ -12498,23 +13369,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
12498
13369
|
continue;
|
|
12499
13370
|
if (!reference.digest)
|
|
12500
13371
|
return failure2("invalid_input", "document references require a byte digest");
|
|
12501
|
-
const target =
|
|
12502
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
13372
|
+
const target = path11.resolve(root, reference.path);
|
|
13373
|
+
if (target !== root && !target.startsWith(`${root}${path11.sep}`))
|
|
12503
13374
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
12504
13375
|
fields: [
|
|
12505
13376
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
12506
13377
|
]
|
|
12507
13378
|
});
|
|
12508
13379
|
try {
|
|
12509
|
-
const below =
|
|
13380
|
+
const below = path11.relative(root, target);
|
|
12510
13381
|
let current = root;
|
|
12511
|
-
for (const segment of below.split(
|
|
13382
|
+
for (const segment of below.split(path11.sep)) {
|
|
12512
13383
|
if (!segment)
|
|
12513
13384
|
continue;
|
|
12514
|
-
current =
|
|
12515
|
-
const stat =
|
|
12516
|
-
const real =
|
|
12517
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
13385
|
+
current = path11.join(current, segment);
|
|
13386
|
+
const stat = fs11.lstatSync(current);
|
|
13387
|
+
const real = fs11.realpathSync(current);
|
|
13388
|
+
if (real !== root && !real.startsWith(`${root}${path11.sep}`))
|
|
12518
13389
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
12519
13390
|
fields: [
|
|
12520
13391
|
{
|
|
@@ -12530,7 +13401,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
12530
13401
|
if (current !== target && !stat.isDirectory())
|
|
12531
13402
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
12532
13403
|
}
|
|
12533
|
-
if (digestBytes(
|
|
13404
|
+
if (digestBytes(fs11.readFileSync(target)) !== reference.digest)
|
|
12534
13405
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
12535
13406
|
} catch {
|
|
12536
13407
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -12615,7 +13486,35 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
12615
13486
|
decisionIds: [],
|
|
12616
13487
|
reason: "stored policy version is unsupported; reassessment is required"
|
|
12617
13488
|
}));
|
|
13489
|
+
const ledger = ledgerView(checkoutRoot);
|
|
12618
13490
|
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
13491
|
+
const derived = derivedRuleStatus(task, requirement, checkoutRoot, ledger);
|
|
13492
|
+
if (derived) {
|
|
13493
|
+
if (derived.met)
|
|
13494
|
+
return {
|
|
13495
|
+
requirementId: requirement.id,
|
|
13496
|
+
status: "satisfied",
|
|
13497
|
+
evidenceIds: [],
|
|
13498
|
+
decisionIds: derived.decisionIds,
|
|
13499
|
+
reason: derived.reason
|
|
13500
|
+
};
|
|
13501
|
+
const waived = requirement.acceptanceAllowed ? applicableDecision(task, workspace, requirement, checkoutRoot) : [];
|
|
13502
|
+
if (waived.length)
|
|
13503
|
+
return {
|
|
13504
|
+
requirementId: requirement.id,
|
|
13505
|
+
status: "accepted_limitation",
|
|
13506
|
+
evidenceIds: [],
|
|
13507
|
+
decisionIds: waived.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
|
|
13508
|
+
reason: "an applicable approved limitation permits the missing evidence"
|
|
13509
|
+
};
|
|
13510
|
+
return {
|
|
13511
|
+
requirementId: requirement.id,
|
|
13512
|
+
status: "unsatisfied",
|
|
13513
|
+
evidenceIds: [],
|
|
13514
|
+
decisionIds: [],
|
|
13515
|
+
reason: derived.reason
|
|
13516
|
+
};
|
|
13517
|
+
}
|
|
12619
13518
|
const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
|
|
12620
13519
|
if (gate?.satisfied.length)
|
|
12621
13520
|
return {
|
|
@@ -12870,10 +13769,10 @@ Autonomy contract:
|
|
|
12870
13769
|
inferred, or guess. Prose is a note: only a configured check the CLI ran
|
|
12871
13770
|
(\`workit check <name>\`) satisfies a gate; an ad-hoc
|
|
12872
13771
|
\`workit check -- <cmd>\` never does.
|
|
12873
|
-
- Author is not verifier:
|
|
12874
|
-
|
|
12875
|
-
|
|
12876
|
-
|
|
13772
|
+
- Author is not verifier: your own verdict is \`--self\` (after an observed
|
|
13773
|
+
\`workit check test\`), shown as self-reviewed, never verified. High risk,
|
|
13774
|
+
\`verification: independent\` and merge need a verifier with its own
|
|
13775
|
+
session (\`WORKIT_SESSION_ID=<lead>-v<n>\`; Claude Code subagents get one).
|
|
12877
13776
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
12878
13777
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
12879
13778
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
@@ -12886,7 +13785,9 @@ PATH, \`npx -y @brainervirus/workit-cli\`). \`busy\` is retryable
|
|
|
12886
13785
|
The task families (task, policy, evidence, finding, decision, worker, state)
|
|
12887
13786
|
are optional continuity for tracked work; each branch has one implicit task
|
|
12888
13787
|
that the first recording creates, and a solo edit needs no task. A decision
|
|
12889
|
-
records a user choice; it never authorizes an effect.
|
|
13788
|
+
records a user choice; it never authorizes an effect. Judge work once with
|
|
13789
|
+
\`workit policy assess --judge risk= behavior= product-choice= plan=\`; an
|
|
13790
|
+
open choice or missing plan blocks edits until recorded.
|
|
12890
13791
|
Omit revisions; a revision_conflict means one you passed is stale, so re-read.
|
|
12891
13792
|
Across repositories, bind each item to its checkout, branch and endpoint,
|
|
12892
13793
|
resolve competing targets before a mutation, and reconcile an uncertain
|
|
@@ -13024,7 +13925,7 @@ var exportDigest = (bundle) => sha256({
|
|
|
13024
13925
|
task: bundle.task
|
|
13025
13926
|
});
|
|
13026
13927
|
// packages/workit-core/src/core/branch.ts
|
|
13027
|
-
import { createHash as
|
|
13928
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
13028
13929
|
|
|
13029
13930
|
// packages/workit-core/src/core/git.ts
|
|
13030
13931
|
import { execFileSync } from "node:child_process";
|
|
@@ -13098,14 +13999,14 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
13098
13999
|
import {
|
|
13099
14000
|
copyFileSync as copyFileSync2,
|
|
13100
14001
|
cpSync as cpSync2,
|
|
13101
|
-
existsSync as
|
|
14002
|
+
existsSync as existsSync5,
|
|
13102
14003
|
mkdirSync as mkdirSync2,
|
|
13103
14004
|
readFileSync as readFileSync3,
|
|
13104
14005
|
readdirSync as readdirSync3,
|
|
13105
14006
|
writeFileSync as writeFileSync2
|
|
13106
14007
|
} from "node:fs";
|
|
13107
14008
|
import os2 from "node:os";
|
|
13108
|
-
import
|
|
14009
|
+
import path12 from "node:path";
|
|
13109
14010
|
|
|
13110
14011
|
// packages/workit-core/src/core/boundary.ts
|
|
13111
14012
|
import { statSync as statSync2 } from "node:fs";
|
|
@@ -13164,7 +14065,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
13164
14065
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
13165
14066
|
};
|
|
13166
14067
|
};
|
|
13167
|
-
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
14068
|
+
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");
|
|
13168
14069
|
var migratedDir = null;
|
|
13169
14070
|
var migrationFailed = false;
|
|
13170
14071
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -13174,12 +14075,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
13174
14075
|
migratedDir = dir;
|
|
13175
14076
|
return dir;
|
|
13176
14077
|
}
|
|
13177
|
-
const legacy =
|
|
13178
|
-
if (!
|
|
14078
|
+
const legacy = path12.join(process.env.XDG_CONFIG_HOME || path12.join(os2.homedir(), ".config"), "workflow-toolkit");
|
|
14079
|
+
if (!existsSync5(legacy)) {
|
|
13179
14080
|
migratedDir = dir;
|
|
13180
14081
|
return dir;
|
|
13181
14082
|
}
|
|
13182
|
-
if (!migrationFailed &&
|
|
14083
|
+
if (!migrationFailed && existsSync5(dir)) {
|
|
13183
14084
|
migratedDir = dir;
|
|
13184
14085
|
return dir;
|
|
13185
14086
|
}
|
|
@@ -13187,9 +14088,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
13187
14088
|
mkdirSync2(dir, { recursive: true });
|
|
13188
14089
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
13189
14090
|
for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
|
|
13190
|
-
const src =
|
|
13191
|
-
const dest =
|
|
13192
|
-
if (
|
|
14091
|
+
const src = path12.join(legacy, entry.name);
|
|
14092
|
+
const dest = path12.join(dir, entry.name);
|
|
14093
|
+
if (existsSync5(dest))
|
|
13193
14094
|
continue;
|
|
13194
14095
|
try {
|
|
13195
14096
|
if (entry.isDirectory())
|
|
@@ -13260,7 +14161,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
13260
14161
|
};
|
|
13261
14162
|
};
|
|
13262
14163
|
var readConfigTyped = (dir) => {
|
|
13263
|
-
const file =
|
|
14164
|
+
const file = path12.join(dir ?? configDir(), "config.json");
|
|
13264
14165
|
return parseConfigResult(readSafe(file), file);
|
|
13265
14166
|
};
|
|
13266
14167
|
var readConfig = () => {
|
|
@@ -13278,9 +14179,11 @@ var mergeConfigValues = (input, current) => ({
|
|
|
13278
14179
|
var writeConfig = (config) => {
|
|
13279
14180
|
const dir = configDir();
|
|
13280
14181
|
mkdirSync2(dir, { recursive: true });
|
|
13281
|
-
writeFileSync2(
|
|
14182
|
+
writeFileSync2(path12.join(dir, "config.json"), JSON.stringify(config, null, 2) + `
|
|
13282
14183
|
`, "utf8");
|
|
13283
14184
|
};
|
|
14185
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path12.join(configDir(), "vcs.json");
|
|
14186
|
+
var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
|
|
13284
14187
|
var resolveBranchPolicy = (config, workspace) => {
|
|
13285
14188
|
const wp = workspace?.branchPolicy ?? {};
|
|
13286
14189
|
const preset = Object.hasOwn(PRESETS, wp.preset) ? wp.preset : config.branchPolicy?.preset ?? "gitflow";
|
|
@@ -13288,7 +14191,7 @@ var resolveBranchPolicy = (config, workspace) => {
|
|
|
13288
14191
|
allowed: wp.allowed,
|
|
13289
14192
|
protectedNames: wp.protected
|
|
13290
14193
|
}, config);
|
|
13291
|
-
const allowed = merged.allowed.map(
|
|
14194
|
+
const allowed = merged.allowed.map(branchGlobPattern);
|
|
13292
14195
|
return {
|
|
13293
14196
|
preset,
|
|
13294
14197
|
allowed,
|
|
@@ -13317,9 +14220,9 @@ var resolveCommitPolicy = (config, workspace) => {
|
|
|
13317
14220
|
};
|
|
13318
14221
|
|
|
13319
14222
|
// packages/workit-core/src/core/workspaces.ts
|
|
13320
|
-
import { createHash as
|
|
14223
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
13321
14224
|
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
13322
|
-
import
|
|
14225
|
+
import path13 from "node:path";
|
|
13323
14226
|
var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
|
|
13324
14227
|
var branchPreset = custom((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
|
|
13325
14228
|
var commitPreset = custom((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
|
|
@@ -13353,26 +14256,31 @@ var versionSourceSchema = discriminatedUnion("kind", [
|
|
|
13353
14256
|
object({ kind: literal("manual") }).strict()
|
|
13354
14257
|
]);
|
|
13355
14258
|
var releaseTrackSchema = object({
|
|
13356
|
-
strategy: branchPreset,
|
|
14259
|
+
strategy: branchPreset.optional(),
|
|
13357
14260
|
productionBranch: nonBlank,
|
|
13358
14261
|
integrationBranch: nonBlank,
|
|
13359
|
-
naming: object({
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
14262
|
+
naming: object({
|
|
14263
|
+
feature: nonBlank.optional(),
|
|
14264
|
+
release: nonBlank.optional(),
|
|
14265
|
+
hotfix: nonBlank.optional()
|
|
14266
|
+
}).passthrough().optional(),
|
|
14267
|
+
baseBranch: nonBlank.optional(),
|
|
14268
|
+
mergeBackBranches: array(nonBlank).optional(),
|
|
14269
|
+
pullRequestTarget: nonBlank.optional(),
|
|
14270
|
+
tagNamespace: string2().optional(),
|
|
14271
|
+
versionSource: versionSourceSchema.optional(),
|
|
14272
|
+
requiredChecks: array(nonBlank).optional(),
|
|
14273
|
+
critical: array(nonBlank).optional()
|
|
14274
|
+
}).passthrough().superRefine((track, context) => {
|
|
13367
14275
|
for (const [field, values] of [
|
|
13368
|
-
["mergeBackBranches", track.mergeBackBranches],
|
|
13369
|
-
["requiredChecks", track.requiredChecks]
|
|
14276
|
+
["mergeBackBranches", track.mergeBackBranches ?? []],
|
|
14277
|
+
["requiredChecks", track.requiredChecks ?? []]
|
|
13370
14278
|
]) {
|
|
13371
14279
|
if (new Set(values).size !== values.length) {
|
|
13372
14280
|
context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
|
|
13373
14281
|
}
|
|
13374
14282
|
}
|
|
13375
|
-
if (track.versionSource
|
|
14283
|
+
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(".."))) {
|
|
13376
14284
|
context.addIssue({
|
|
13377
14285
|
code: "custom",
|
|
13378
14286
|
path: ["versionSource", "path"],
|
|
@@ -13391,6 +14299,7 @@ var workspaceConfigSchema = object({
|
|
|
13391
14299
|
}).passthrough().optional(),
|
|
13392
14300
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
13393
14301
|
defaultEndpoint: _enum(["commit", "pr"]).optional(),
|
|
14302
|
+
verification: _enum(["self", "independent"]).optional(),
|
|
13394
14303
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
13395
14304
|
kind,
|
|
13396
14305
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -13431,7 +14340,7 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
|
|
|
13431
14340
|
names.add(workspace.name);
|
|
13432
14341
|
}
|
|
13433
14342
|
});
|
|
13434
|
-
var workspacesRevision = (contents) =>
|
|
14343
|
+
var workspacesRevision = (contents) => createHash8("sha256").update(contents ?? "<missing>").digest("hex");
|
|
13435
14344
|
var validateWorkspacesDocument = (parsed, file) => {
|
|
13436
14345
|
if (!isConfigObject(parsed)) {
|
|
13437
14346
|
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
@@ -13469,7 +14378,7 @@ var validateWorkspacesDocument = (parsed, file) => {
|
|
|
13469
14378
|
};
|
|
13470
14379
|
};
|
|
13471
14380
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
13472
|
-
const file =
|
|
14381
|
+
const file = path13.join(dir, "workspaces.json");
|
|
13473
14382
|
let raw;
|
|
13474
14383
|
try {
|
|
13475
14384
|
raw = readFileSync4(file, "utf8");
|
|
@@ -13639,8 +14548,8 @@ var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd,
|
|
|
13639
14548
|
var runtimeWorkspaceIndexSchema = object({
|
|
13640
14549
|
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
13641
14550
|
}).passthrough();
|
|
13642
|
-
var
|
|
13643
|
-
const file =
|
|
14551
|
+
var matchRuntimeWorkspace = (cwd) => {
|
|
14552
|
+
const file = path13.join(configDir(), "workspaces.json");
|
|
13644
14553
|
let raw;
|
|
13645
14554
|
try {
|
|
13646
14555
|
raw = readFileSync4(file, "utf8");
|
|
@@ -13674,8 +14583,17 @@ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
|
13674
14583
|
});
|
|
13675
14584
|
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
13676
14585
|
const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
|
|
13677
|
-
|
|
14586
|
+
return selected ? { file, selected } : null;
|
|
14587
|
+
};
|
|
14588
|
+
var resolveRuntimeReleaseTracks = (cwd) => {
|
|
14589
|
+
const matched = matchRuntimeWorkspace(cwd);
|
|
14590
|
+
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
14591
|
+
};
|
|
14592
|
+
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
14593
|
+
const matched = matchRuntimeWorkspace(cwd);
|
|
14594
|
+
if (!matched)
|
|
13678
14595
|
return null;
|
|
14596
|
+
const { file, selected } = matched;
|
|
13679
14597
|
const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
|
|
13680
14598
|
if (!policy.success) {
|
|
13681
14599
|
const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
|
|
@@ -13701,15 +14619,423 @@ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
|
|
|
13701
14619
|
source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
|
|
13702
14620
|
};
|
|
13703
14621
|
};
|
|
14622
|
+
function workspaceDefaultTarget(cwd, globalDefault) {
|
|
14623
|
+
const ws = resolveRuntimeWorkspaceVcs(cwd);
|
|
14624
|
+
const wp = ws?.branchPolicy ?? {};
|
|
14625
|
+
const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
|
|
14626
|
+
const selected = resolveRuntimeWorkspacePolicy(cwd, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
14627
|
+
const policyDefault = resolveBranchPolicy(readConfig(), selected.policy ? { branchPolicy: selected.policy } : null).defaultTargetBranch;
|
|
14628
|
+
return String(ws?.vcs?.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : globalDefault ?? policyDefault) ?? "develop");
|
|
14629
|
+
}
|
|
13704
14630
|
|
|
13705
14631
|
// packages/workit-core/src/core/vcs-config.ts
|
|
13706
|
-
import
|
|
13707
|
-
import
|
|
13708
|
-
import { spawnSync as
|
|
13709
|
-
|
|
14632
|
+
import fs12 from "node:fs";
|
|
14633
|
+
import path14 from "node:path";
|
|
14634
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
14635
|
+
|
|
14636
|
+
// packages/workit-core/src/core/release-tracks.ts
|
|
14637
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
14638
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
14639
|
+
class ReleaseTrackError extends Error {
|
|
14640
|
+
}
|
|
14641
|
+
var KNOWN_FIELDS = new Set([
|
|
14642
|
+
"strategy",
|
|
14643
|
+
"productionBranch",
|
|
14644
|
+
"integrationBranch",
|
|
14645
|
+
"naming",
|
|
14646
|
+
"baseBranch",
|
|
14647
|
+
"mergeBackBranches",
|
|
14648
|
+
"pullRequestTarget",
|
|
14649
|
+
"tagNamespace",
|
|
14650
|
+
"versionSource",
|
|
14651
|
+
"requiredChecks",
|
|
14652
|
+
"critical"
|
|
14653
|
+
]);
|
|
14654
|
+
var BRANCH_FIELDS = ["productionBranch", "integrationBranch", "baseBranch", "pullRequestTarget"];
|
|
14655
|
+
var PLACEHOLDER = /\{[^}]*\}/gu;
|
|
14656
|
+
var HAS_PLACEHOLDER = /\{[^}]*\}/u;
|
|
14657
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14658
|
+
var branchName = (value) => typeof value === "string" && value.trim() && !value.trim().startsWith("-") ? value.trim() : null;
|
|
14659
|
+
var namedBranches = (entry) => [
|
|
14660
|
+
...BRANCH_FIELDS.map((field) => branchName(entry[field])),
|
|
14661
|
+
...Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : []
|
|
14662
|
+
].filter((name) => name !== null);
|
|
14663
|
+
var literalOf = (template) => template.replace(PLACEHOLDER, "").replace(/[*/]/gu, "").trim();
|
|
14664
|
+
function readReleaseTracks(raw) {
|
|
14665
|
+
const out = { tracks: [], issues: [], error: null, protectedBranches: [] };
|
|
14666
|
+
if (raw === undefined || raw === null)
|
|
14667
|
+
return out;
|
|
14668
|
+
if (!isRecord3(raw)) {
|
|
14669
|
+
out.issues.push("releaseTracks is not an object; ignored");
|
|
14670
|
+
return out;
|
|
14671
|
+
}
|
|
14672
|
+
const fatal = [];
|
|
14673
|
+
const protectedNames = new Set;
|
|
14674
|
+
for (const [name, entry] of Object.entries(raw).toSorted(([a], [b]) => a < b ? -1 : 1)) {
|
|
14675
|
+
const label = `release track ${JSON.stringify(name)}`;
|
|
14676
|
+
if (!isRecord3(entry)) {
|
|
14677
|
+
out.issues.push(`${label} is not an object; ignored`);
|
|
14678
|
+
continue;
|
|
14679
|
+
}
|
|
14680
|
+
for (const branch of namedBranches(entry))
|
|
14681
|
+
protectedNames.add(branch);
|
|
14682
|
+
if (!name.trim()) {
|
|
14683
|
+
out.issues.push("a release track with a blank name was ignored");
|
|
14684
|
+
continue;
|
|
14685
|
+
}
|
|
14686
|
+
let critical = [];
|
|
14687
|
+
if (Array.isArray(entry.critical))
|
|
14688
|
+
critical = entry.critical.filter((item) => typeof item === "string");
|
|
14689
|
+
else if (entry.critical !== undefined)
|
|
14690
|
+
out.issues.push(`${label} critical: must be a list of field names; ignored`);
|
|
14691
|
+
const isCritical = (field) => critical.some((item) => field === item || field.startsWith(`${item}.`));
|
|
14692
|
+
const problem = (field, message) => {
|
|
14693
|
+
if (isCritical(field))
|
|
14694
|
+
fatal.push(`${label} ${field}: ${message}`);
|
|
14695
|
+
else
|
|
14696
|
+
out.issues.push(`${label} ${field}: ${message}`);
|
|
14697
|
+
};
|
|
14698
|
+
for (const key of Object.keys(entry))
|
|
14699
|
+
if (!KNOWN_FIELDS.has(key))
|
|
14700
|
+
problem(key, "unknown field, ignored");
|
|
14701
|
+
const productionBranch = branchName(entry.productionBranch);
|
|
14702
|
+
const integrationBranch = branchName(entry.integrationBranch);
|
|
14703
|
+
if (!productionBranch)
|
|
14704
|
+
problem("productionBranch", "missing or not a branch name");
|
|
14705
|
+
if (!integrationBranch)
|
|
14706
|
+
problem("integrationBranch", "missing or not a branch name");
|
|
14707
|
+
const optionalBranch = (field) => {
|
|
14708
|
+
if (entry[field] === undefined)
|
|
14709
|
+
return null;
|
|
14710
|
+
const value = branchName(entry[field]);
|
|
14711
|
+
if (!value)
|
|
14712
|
+
problem(field, "not a branch name; using the integration branch");
|
|
14713
|
+
return value;
|
|
14714
|
+
};
|
|
14715
|
+
const baseBranch = optionalBranch("baseBranch");
|
|
14716
|
+
const pullRequestTarget = optionalBranch("pullRequestTarget");
|
|
14717
|
+
let mergeBackBranches = [];
|
|
14718
|
+
if (entry.mergeBackBranches !== undefined) {
|
|
14719
|
+
const list = Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : [null];
|
|
14720
|
+
if (list.some((item) => item === null))
|
|
14721
|
+
problem("mergeBackBranches", "must be a list of branch names; ignored");
|
|
14722
|
+
else
|
|
14723
|
+
mergeBackBranches = [...new Set(list)];
|
|
14724
|
+
}
|
|
14725
|
+
const naming = {};
|
|
14726
|
+
if (entry.naming !== undefined) {
|
|
14727
|
+
if (!isRecord3(entry.naming))
|
|
14728
|
+
problem("naming", "not an object; ignored");
|
|
14729
|
+
else
|
|
14730
|
+
for (const kind of ["feature", "release", "hotfix"]) {
|
|
14731
|
+
const template = entry.naming[kind];
|
|
14732
|
+
if (template === undefined)
|
|
14733
|
+
continue;
|
|
14734
|
+
if (typeof template !== "string" || !template.trim())
|
|
14735
|
+
problem(`naming.${kind}`, "not a template string; ignored");
|
|
14736
|
+
else if (!literalOf(template))
|
|
14737
|
+
problem(`naming.${kind}`, "needs literal text besides placeholders; ignored");
|
|
14738
|
+
else
|
|
14739
|
+
naming[kind] = template.trim();
|
|
14740
|
+
}
|
|
14741
|
+
}
|
|
14742
|
+
if (!productionBranch || !integrationBranch) {
|
|
14743
|
+
out.issues.push(`${label} was skipped: it needs a productionBranch and integrationBranch`);
|
|
14744
|
+
continue;
|
|
14745
|
+
}
|
|
14746
|
+
out.tracks.push({
|
|
14747
|
+
name,
|
|
14748
|
+
productionBranch,
|
|
14749
|
+
integrationBranch,
|
|
14750
|
+
baseBranch: baseBranch ?? integrationBranch,
|
|
14751
|
+
pullRequestTarget: pullRequestTarget ?? integrationBranch,
|
|
14752
|
+
mergeBackBranches,
|
|
14753
|
+
naming
|
|
14754
|
+
});
|
|
14755
|
+
}
|
|
14756
|
+
out.protectedBranches = [...protectedNames];
|
|
14757
|
+
if (fatal.length)
|
|
14758
|
+
out.error = `workspace releaseTracks cannot be read by this Workit (${fatal.join("; ")}); upgrade Workit or fix the track`;
|
|
14759
|
+
return out;
|
|
14760
|
+
}
|
|
14761
|
+
var trackBranches = (tracks) => [
|
|
14762
|
+
...new Set(tracks.flatMap((track) => [
|
|
14763
|
+
track.productionBranch,
|
|
14764
|
+
track.integrationBranch,
|
|
14765
|
+
track.baseBranch,
|
|
14766
|
+
track.pullRequestTarget,
|
|
14767
|
+
...track.mergeBackBranches
|
|
14768
|
+
]))
|
|
14769
|
+
];
|
|
14770
|
+
var namingGlob = (template) => HAS_PLACEHOLDER.test(template) ? template.replace(PLACEHOLDER, "*") : `${template.replace(/\/?\*?$/u, "")}/*`;
|
|
14771
|
+
var IMPLICIT = "";
|
|
14772
|
+
var ownedBy = (tracks, branch) => tracks.filter((track) => [track.integrationBranch, track.productionBranch, track.baseBranch].includes(branch));
|
|
14773
|
+
var shortRef = (ref, remotes) => {
|
|
14774
|
+
const bare = ref.replace(/^refs\/heads\//u, "").replace(/^refs\/remotes\/[^/]+\//u, "");
|
|
14775
|
+
const remote = remotes.find((name) => bare.startsWith(`${name}/`));
|
|
14776
|
+
return remote ? bare.slice(remote.length + 1) : bare;
|
|
14777
|
+
};
|
|
14778
|
+
var MAX_RECORDED_HOPS = 10;
|
|
14779
|
+
function resolveReleaseTrack(input) {
|
|
14780
|
+
const tracks = [...input.tracks];
|
|
14781
|
+
const issues = [...input.issues ?? []];
|
|
14782
|
+
const requested = input.requested?.trim();
|
|
14783
|
+
if (tracks.length === 0) {
|
|
14784
|
+
if (requested)
|
|
14785
|
+
return {
|
|
14786
|
+
status: "invalid",
|
|
14787
|
+
error: `no release tracks configured for this workspace; --track ${requested} cannot apply`,
|
|
14788
|
+
issues
|
|
14789
|
+
};
|
|
14790
|
+
return { status: "none", issues };
|
|
14791
|
+
}
|
|
14792
|
+
const names = tracks.map((track) => track.name).join(", ");
|
|
14793
|
+
let envProblem = null;
|
|
14794
|
+
const done = (track, source, detail, warnings = [], ambiguous = null) => ({
|
|
14795
|
+
status: "resolved",
|
|
14796
|
+
track: track && track.name !== IMPLICIT ? track : null,
|
|
14797
|
+
source,
|
|
14798
|
+
detail,
|
|
14799
|
+
warnings: [...issues, ...envProblem ? [envProblem] : [], ...warnings],
|
|
14800
|
+
tracks,
|
|
14801
|
+
blocking: envProblem ?? ambiguous
|
|
14802
|
+
});
|
|
14803
|
+
if (requested) {
|
|
14804
|
+
const named = tracks.find((track) => track.name === requested);
|
|
14805
|
+
if (!named)
|
|
14806
|
+
return {
|
|
14807
|
+
status: "invalid",
|
|
14808
|
+
error: `no release track ${JSON.stringify(requested)}; configured: ${names}`,
|
|
14809
|
+
issues
|
|
14810
|
+
};
|
|
14811
|
+
return done(named, "flag", `track ${named.name} was requested explicitly`);
|
|
14812
|
+
}
|
|
14813
|
+
const owned = (branch) => branch !== null && tracks.some((track) => [
|
|
14814
|
+
track.integrationBranch,
|
|
14815
|
+
track.productionBranch,
|
|
14816
|
+
track.baseBranch,
|
|
14817
|
+
track.pullRequestTarget
|
|
14818
|
+
].includes(branch));
|
|
14819
|
+
const implicit = input.defaultBranch && !owned(input.defaultBranch) ? {
|
|
14820
|
+
name: IMPLICIT,
|
|
14821
|
+
productionBranch: input.defaultBranch,
|
|
14822
|
+
integrationBranch: input.defaultBranch,
|
|
14823
|
+
baseBranch: input.defaultBranch,
|
|
14824
|
+
pullRequestTarget: input.defaultBranch,
|
|
14825
|
+
mergeBackBranches: [],
|
|
14826
|
+
naming: {}
|
|
14827
|
+
} : null;
|
|
14828
|
+
const candidates = implicit ? [...tracks, implicit] : tracks;
|
|
14829
|
+
const label = (track) => track.name === IMPLICIT ? `the workspace default line (${track.integrationBranch})` : `track ${track.name}`;
|
|
14830
|
+
const fallback = (why) => {
|
|
14831
|
+
const byDefault = input.defaultBranch ? tracks.filter((track) => track.integrationBranch === input.defaultBranch || track.pullRequestTarget === input.defaultBranch) : [];
|
|
14832
|
+
const track = byDefault.length === 1 ? byDefault[0] : null;
|
|
14833
|
+
const target = track ? `track ${track.name}` : `default target ${input.defaultBranch ?? "(none)"}`;
|
|
14834
|
+
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;
|
|
14835
|
+
return done(track, "default", `${why}; using the workspace ${target}`, [
|
|
14836
|
+
`release track not determined: ${why}; using the workspace ${target} (pass --track <name> to choose)`
|
|
14837
|
+
], ambiguous);
|
|
14838
|
+
};
|
|
14839
|
+
const branch = input.branch;
|
|
14840
|
+
if (branch) {
|
|
14841
|
+
const owners = ownedBy(candidates, branch);
|
|
14842
|
+
if (owners.length === 1)
|
|
14843
|
+
return done(owners[0], "branch", `${branch} is a branch of ${label(owners[0])}`);
|
|
14844
|
+
if (owners.length > 1)
|
|
14845
|
+
return fallback(`${branch} belongs to more than one track (${owners.map((track) => track.name).join(", ")})`);
|
|
14846
|
+
}
|
|
14847
|
+
const env = input.envRequested?.trim();
|
|
14848
|
+
if (env) {
|
|
14849
|
+
const named = tracks.find((track) => track.name === env);
|
|
14850
|
+
if (named)
|
|
14851
|
+
return done(named, "env", `WORKFLOW_RELEASE_TRACK selects track ${named.name}`);
|
|
14852
|
+
envProblem = `WORKFLOW_RELEASE_TRACK=${env} names no release track of this workspace (${names}); ignored`;
|
|
14853
|
+
}
|
|
14854
|
+
if (!branch)
|
|
14855
|
+
return fallback("HEAD is detached");
|
|
14856
|
+
if (tracks.length === 1 && !implicit)
|
|
14857
|
+
return done(tracks[0], "only-track", `${tracks[0].name} is the only track`);
|
|
14858
|
+
let remotes = null;
|
|
14859
|
+
const short = (ref) => shortRef(ref, remotes ??= input.probe.remotes());
|
|
14860
|
+
const ownerOf = (ref) => {
|
|
14861
|
+
const owner = ownedBy(candidates, short(ref));
|
|
14862
|
+
return owner.length === 1 ? owner[0] : null;
|
|
14863
|
+
};
|
|
14864
|
+
const followRecorded = (start) => {
|
|
14865
|
+
let cursor = start;
|
|
14866
|
+
const seen = new Set([start]);
|
|
14867
|
+
for (let hop = 0;hop < MAX_RECORDED_HOPS; hop += 1) {
|
|
14868
|
+
const recorded = input.probe.recordedBase(cursor);
|
|
14869
|
+
if (!recorded)
|
|
14870
|
+
return null;
|
|
14871
|
+
const base = short(recorded);
|
|
14872
|
+
const owner = ownerOf(base);
|
|
14873
|
+
if (owner)
|
|
14874
|
+
return owner;
|
|
14875
|
+
if (ownedBy(candidates, base).length > 1 || seen.has(base))
|
|
14876
|
+
return null;
|
|
14877
|
+
seen.add(base);
|
|
14878
|
+
cursor = base;
|
|
14879
|
+
}
|
|
14880
|
+
return null;
|
|
14881
|
+
};
|
|
14882
|
+
const recordedOwner = followRecorded(branch);
|
|
14883
|
+
if (recordedOwner)
|
|
14884
|
+
return done(recordedOwner, "recorded-base", `${branch} was created on ${label(recordedOwner)}'s line (recorded base)`);
|
|
14885
|
+
for (const source of ["reflog", "checkout"]) {
|
|
14886
|
+
const ref = source === "reflog" ? input.probe.createdFrom(branch) : input.probe.checkedOutFrom(branch);
|
|
14887
|
+
if (!ref)
|
|
14888
|
+
continue;
|
|
14889
|
+
const owner = ownerOf(ref) ?? followRecorded(short(ref));
|
|
14890
|
+
if (owner)
|
|
14891
|
+
return done(owner, source, `${branch} was created from ${short(ref)} (${source === "reflog" ? "branch reflog" : "HEAD reflog"})`);
|
|
14892
|
+
}
|
|
14893
|
+
const forks = [];
|
|
14894
|
+
for (const track of candidates)
|
|
14895
|
+
for (const ref of new Set([track.integrationBranch, track.baseBranch])) {
|
|
14896
|
+
const sha = input.probe.mergeBase(branch, ref);
|
|
14897
|
+
if (sha)
|
|
14898
|
+
forks.push({ track, ref, sha });
|
|
14899
|
+
}
|
|
14900
|
+
if (forks.length === 0)
|
|
14901
|
+
return fallback(`no track branch could be compared with ${branch} (fetch the track branches)`);
|
|
14902
|
+
const ancestry = new Map;
|
|
14903
|
+
const descends = (older, newer) => {
|
|
14904
|
+
if (older === newer)
|
|
14905
|
+
return false;
|
|
14906
|
+
const key = `${older}>${newer}`;
|
|
14907
|
+
if (!ancestry.has(key))
|
|
14908
|
+
ancestry.set(key, input.probe.isAncestor(older, newer));
|
|
14909
|
+
return ancestry.get(key);
|
|
14910
|
+
};
|
|
14911
|
+
const winners = forks.filter((fork) => forks.every((other) => other.track === fork.track || descends(other.sha, fork.sha)));
|
|
14912
|
+
const winningTracks = [...new Set(winners.map((fork) => fork.track))];
|
|
14913
|
+
if (winningTracks.length === 1 && forks.some((fork) => fork.track !== winningTracks[0])) {
|
|
14914
|
+
const win = winners[0];
|
|
14915
|
+
return done(win.track, "ancestry", `${branch} forked from ${win.ref} at ${win.sha.slice(0, 12)}, after every other line's fork point`);
|
|
14916
|
+
}
|
|
14917
|
+
if (winningTracks.length === 1)
|
|
14918
|
+
return fallback(`only ${label(winningTracks[0])} could be compared with ${branch}`);
|
|
14919
|
+
const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
|
|
14920
|
+
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 ")}`);
|
|
14921
|
+
}
|
|
14922
|
+
var git4 = (cwd, args) => {
|
|
14923
|
+
const run = spawnSync7("git", args, {
|
|
14924
|
+
cwd,
|
|
14925
|
+
encoding: "utf8",
|
|
14926
|
+
timeout: 20000,
|
|
14927
|
+
windowsHide: true,
|
|
14928
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
14929
|
+
});
|
|
14930
|
+
return run.status === 0 ? (run.stdout ?? "").trim() : null;
|
|
14931
|
+
};
|
|
14932
|
+
var recordedBaseKey = (branch) => `branch.${branch}.workitBase`;
|
|
14933
|
+
var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
14934
|
+
var reflog = (cwd, ref) => (git4(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
|
|
14935
|
+
`).map((line) => {
|
|
14936
|
+
const [sha = "", selector = "", ...subject] = line.split("\t");
|
|
14937
|
+
return {
|
|
14938
|
+
sha,
|
|
14939
|
+
time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
|
|
14940
|
+
subject: subject.join("\t")
|
|
14941
|
+
};
|
|
14942
|
+
}).filter((entry) => entry.sha && Number.isFinite(entry.time));
|
|
14943
|
+
function gitTrackProbe(cwd) {
|
|
14944
|
+
let remote;
|
|
14945
|
+
let remotes;
|
|
14946
|
+
const tip = (branch) => {
|
|
14947
|
+
remote ??= pushRemoteName(cwd) ?? "origin";
|
|
14948
|
+
return resolveRef(cwd, `refs/remotes/${remote}/${branch}`) ?? resolveRef(cwd, `refs/heads/${branch}`);
|
|
14949
|
+
};
|
|
14950
|
+
return {
|
|
14951
|
+
remotes: () => remotes ??= remoteNames(cwd),
|
|
14952
|
+
recordedBase: (branch) => git4(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
|
|
14953
|
+
createdFrom: (branch) => {
|
|
14954
|
+
const log = git4(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
|
|
14955
|
+
const oldest = log?.split(`
|
|
14956
|
+
`).findLast(Boolean) ?? "";
|
|
14957
|
+
const match = /^branch: Created from (\S+)$/u.exec(oldest);
|
|
14958
|
+
return match && match[1] !== "HEAD" ? match[1] : null;
|
|
14959
|
+
},
|
|
14960
|
+
checkedOutFrom: (branch) => {
|
|
14961
|
+
const own = reflog(cwd, `refs/heads/${branch}`);
|
|
14962
|
+
const created = own.at(-1);
|
|
14963
|
+
if (!created || created.subject !== "branch: Created from HEAD")
|
|
14964
|
+
return null;
|
|
14965
|
+
const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
|
|
14966
|
+
const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
|
|
14967
|
+
return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
|
|
14968
|
+
},
|
|
14969
|
+
mergeBase: (branch, trackBranch) => {
|
|
14970
|
+
const base = tip(trackBranch);
|
|
14971
|
+
return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
|
|
14972
|
+
},
|
|
14973
|
+
isAncestor: (ancestor, descendant) => git4(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
|
|
14974
|
+
};
|
|
14975
|
+
}
|
|
14976
|
+
function workspaceReleaseTracks(cwd) {
|
|
14977
|
+
const raw = resolveRuntimeReleaseTracks(cwd);
|
|
14978
|
+
return { workspace: raw?.workspace ?? null, ...readReleaseTracks(raw?.releaseTracks) };
|
|
14979
|
+
}
|
|
14980
|
+
var memo3 = new Map;
|
|
14981
|
+
var MEMO_LIMIT = 64;
|
|
14982
|
+
function resolveTrackFor(cwd, options) {
|
|
14983
|
+
const read = workspaceReleaseTracks(cwd);
|
|
14984
|
+
if (read.error)
|
|
14985
|
+
return { status: "invalid", error: read.error, issues: read.issues };
|
|
14986
|
+
if (read.tracks.length === 0 && !options.track?.trim())
|
|
14987
|
+
return { status: "none", issues: read.issues };
|
|
14988
|
+
const branch = options.branch === undefined ? currentBranch(cwd) : options.branch;
|
|
14989
|
+
const envRequested = process.env.WORKFLOW_RELEASE_TRACK?.trim() || null;
|
|
14990
|
+
const input = {
|
|
14991
|
+
tracks: read.tracks,
|
|
14992
|
+
requested: options.track ?? null,
|
|
14993
|
+
envRequested,
|
|
14994
|
+
branch,
|
|
14995
|
+
defaultBranch: options.defaultBranch,
|
|
14996
|
+
issues: read.issues
|
|
14997
|
+
};
|
|
14998
|
+
let key = null;
|
|
14999
|
+
if (!options.probe) {
|
|
15000
|
+
const refs = git4(cwd, [
|
|
15001
|
+
"for-each-ref",
|
|
15002
|
+
"--format=%(refname) %(objectname)",
|
|
15003
|
+
"refs/heads",
|
|
15004
|
+
"refs/remotes"
|
|
15005
|
+
]) ?? "";
|
|
15006
|
+
const head = git4(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
|
|
15007
|
+
const own = branch ? [
|
|
15008
|
+
git4(cwd, [
|
|
15009
|
+
"log",
|
|
15010
|
+
"-g",
|
|
15011
|
+
"-n",
|
|
15012
|
+
"1",
|
|
15013
|
+
"--date=unix",
|
|
15014
|
+
"--format=%H %gd %gs",
|
|
15015
|
+
`refs/heads/${branch}`,
|
|
15016
|
+
"--"
|
|
15017
|
+
]) ?? "",
|
|
15018
|
+
git4(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
|
|
15019
|
+
] : [];
|
|
15020
|
+
key = createHash9("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
|
|
15021
|
+
const hit = memo3.get(key);
|
|
15022
|
+
if (hit)
|
|
15023
|
+
return hit;
|
|
15024
|
+
}
|
|
15025
|
+
const resolution = resolveReleaseTrack({ ...input, probe: options.probe ?? gitTrackProbe(cwd) });
|
|
15026
|
+
if (key) {
|
|
15027
|
+
if (memo3.size >= MEMO_LIMIT)
|
|
15028
|
+
memo3.delete(memo3.keys().next().value);
|
|
15029
|
+
memo3.set(key, resolution);
|
|
15030
|
+
}
|
|
15031
|
+
return resolution;
|
|
15032
|
+
}
|
|
15033
|
+
|
|
15034
|
+
// packages/workit-core/src/core/vcs-config.ts
|
|
15035
|
+
var workspacesPath = () => path14.join(configDir(), "workspaces.json");
|
|
13710
15036
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
13711
15037
|
var remoteProvider = (cwd) => {
|
|
13712
|
-
const r =
|
|
15038
|
+
const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
13713
15039
|
if (r.status !== 0)
|
|
13714
15040
|
return null;
|
|
13715
15041
|
const url = (r.stdout ?? "").trim();
|
|
@@ -13746,7 +15072,7 @@ var remoteEndpoint = (remote) => {
|
|
|
13746
15072
|
var configuredSshEndpoint = (host) => {
|
|
13747
15073
|
if (host.startsWith("-"))
|
|
13748
15074
|
return null;
|
|
13749
|
-
const resolved =
|
|
15075
|
+
const resolved = spawnSync8("ssh", ["-G", host], {
|
|
13750
15076
|
encoding: "utf8",
|
|
13751
15077
|
timeout: 5000,
|
|
13752
15078
|
maxBuffer: 1024 * 1024,
|
|
@@ -13796,7 +15122,7 @@ var readVcsConfig = () => {
|
|
|
13796
15122
|
const file = vcsConfigPath();
|
|
13797
15123
|
let raw;
|
|
13798
15124
|
try {
|
|
13799
|
-
raw =
|
|
15125
|
+
raw = fs12.readFileSync(file, "utf8");
|
|
13800
15126
|
} catch {
|
|
13801
15127
|
return { status: "missing", path: file, config: {} };
|
|
13802
15128
|
}
|
|
@@ -13811,7 +15137,16 @@ var readVcsConfig = () => {
|
|
|
13811
15137
|
}
|
|
13812
15138
|
return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
|
|
13813
15139
|
};
|
|
13814
|
-
|
|
15140
|
+
var trackSummary = (resolution) => resolution.status === "resolved" ? {
|
|
15141
|
+
name: resolution.track?.name ?? null,
|
|
15142
|
+
source: resolution.source,
|
|
15143
|
+
detail: resolution.detail,
|
|
15144
|
+
warnings: resolution.warnings,
|
|
15145
|
+
track: resolution.track,
|
|
15146
|
+
tracks: resolution.tracks,
|
|
15147
|
+
blocking: resolution.blocking
|
|
15148
|
+
} : null;
|
|
15149
|
+
function vcsConfig(mode, cwd, options = {}) {
|
|
13815
15150
|
const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
|
|
13816
15151
|
const wsVcs = ws?.vcs ?? {};
|
|
13817
15152
|
const wsYt = ws?.youtrack ?? {};
|
|
@@ -13821,10 +15156,7 @@ function vcsConfig(mode, cwd) {
|
|
|
13821
15156
|
const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
|
|
13822
15157
|
const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
|
|
13823
15158
|
const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
|
|
13824
|
-
const
|
|
13825
|
-
const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
|
|
13826
|
-
const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
|
|
13827
|
-
const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
|
|
15159
|
+
const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
|
|
13828
15160
|
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
13829
15161
|
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
13830
15162
|
let issuesProvider = null;
|
|
@@ -13836,11 +15168,24 @@ function vcsConfig(mode, cwd) {
|
|
|
13836
15168
|
if (mode === "resolve") {
|
|
13837
15169
|
if (cfgStatus === "malformed")
|
|
13838
15170
|
return { ok: false, error: cfgError, configPath: cfgPath };
|
|
15171
|
+
const trackResolution = resolveTrackFor(root, {
|
|
15172
|
+
track: options.track,
|
|
15173
|
+
...options.branch !== undefined ? { branch: options.branch } : {},
|
|
15174
|
+
defaultBranch: workspaceDefault
|
|
15175
|
+
});
|
|
15176
|
+
if (trackResolution.status === "invalid")
|
|
15177
|
+
return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
|
|
15178
|
+
const releaseTrack = trackSummary(trackResolution);
|
|
15179
|
+
const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
|
|
15180
|
+
const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
|
|
13839
15181
|
return {
|
|
13840
15182
|
ok: true,
|
|
13841
15183
|
workspace_name: ws?.name ?? null,
|
|
13842
15184
|
provider,
|
|
13843
15185
|
defaultTargetBranch: defaultTarget,
|
|
15186
|
+
workspaceDefaultTargetBranch: workspaceDefault,
|
|
15187
|
+
baseBranch,
|
|
15188
|
+
releaseTrack,
|
|
13844
15189
|
link_issues: linkIssues,
|
|
13845
15190
|
youtrack_base_url: youtrackBaseUrl,
|
|
13846
15191
|
issues_provider: issuesProvider,
|
|
@@ -13862,9 +15207,9 @@ function vcsConfig(mode, cwd) {
|
|
|
13862
15207
|
const prov = cfg[provider] ?? {};
|
|
13863
15208
|
const out = {
|
|
13864
15209
|
ok: true,
|
|
13865
|
-
configPath:
|
|
15210
|
+
configPath: path14.resolve(cfgPath),
|
|
13866
15211
|
provider,
|
|
13867
|
-
defaultTargetBranch:
|
|
15212
|
+
defaultTargetBranch: workspaceDefault,
|
|
13868
15213
|
pr: cfg.pr ?? {},
|
|
13869
15214
|
workspace_name: ws?.name ?? null,
|
|
13870
15215
|
link_issues: linkIssues,
|
|
@@ -13900,7 +15245,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
13900
15245
|
`).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
|
|
13901
15246
|
descriptionPreview: desc.slice(0, 600)
|
|
13902
15247
|
});
|
|
13903
|
-
const remoteResult =
|
|
15248
|
+
const remoteResult = spawnSync8("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
13904
15249
|
cwd,
|
|
13905
15250
|
encoding: "utf8"
|
|
13906
15251
|
});
|
|
@@ -13916,7 +15261,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
13916
15261
|
if (!hostingApiHostMatches(remote, host))
|
|
13917
15262
|
return { ok: false, error: "PR style target does not match the provider host" };
|
|
13918
15263
|
if (provider === "gitlab") {
|
|
13919
|
-
const run = (args) =>
|
|
15264
|
+
const run = (args) => spawnSync8("glab", ["api", ...args], {
|
|
13920
15265
|
cwd,
|
|
13921
15266
|
encoding: "utf8",
|
|
13922
15267
|
env
|
|
@@ -13936,7 +15281,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
13936
15281
|
});
|
|
13937
15282
|
}
|
|
13938
15283
|
} else if (provider === "github") {
|
|
13939
|
-
const r =
|
|
15284
|
+
const r = spawnSync8("gh", [
|
|
13940
15285
|
"pr",
|
|
13941
15286
|
"list",
|
|
13942
15287
|
"--repo",
|
|
@@ -13969,11 +15314,35 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
13969
15314
|
}
|
|
13970
15315
|
|
|
13971
15316
|
// packages/workit-core/src/core/branch.ts
|
|
15317
|
+
var withReleaseTracks = (workspaceRoot, policy) => {
|
|
15318
|
+
const read = workspaceReleaseTracks(workspaceRoot);
|
|
15319
|
+
if (read.error)
|
|
15320
|
+
throw new ReleaseTrackError(read.error);
|
|
15321
|
+
if (read.tracks.length === 0 && read.protectedBranches.length === 0)
|
|
15322
|
+
return policy;
|
|
15323
|
+
const known = new Set(policy.allowed.map((pattern) => pattern.source));
|
|
15324
|
+
const allowed = [...policy.allowed];
|
|
15325
|
+
for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
|
|
15326
|
+
const pattern = branchGlobPattern(glob);
|
|
15327
|
+
if (!known.has(pattern.source)) {
|
|
15328
|
+
known.add(pattern.source);
|
|
15329
|
+
allowed.push(pattern);
|
|
15330
|
+
}
|
|
15331
|
+
}
|
|
15332
|
+
return {
|
|
15333
|
+
...policy,
|
|
15334
|
+
allowed,
|
|
15335
|
+
protected: new Set([
|
|
15336
|
+
...policy.protected,
|
|
15337
|
+
...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
|
|
15338
|
+
])
|
|
15339
|
+
};
|
|
15340
|
+
};
|
|
13972
15341
|
function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
13973
15342
|
const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
13974
15343
|
const config = readConfig();
|
|
13975
15344
|
return kind === "branch" ? {
|
|
13976
|
-
branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
|
|
15345
|
+
branchPolicy: withReleaseTracks(workspaceRoot, resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null)),
|
|
13977
15346
|
provenance: { branchPolicy: selected.source }
|
|
13978
15347
|
} : {
|
|
13979
15348
|
commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
|
|
@@ -14004,358 +15373,86 @@ var validateBranchNamePolicy = (name, policy, source) => {
|
|
|
14004
15373
|
error: `allowed_pattern: branch ${JSON.stringify(name)} violates ${source} policy; ${correction}`
|
|
14005
15374
|
};
|
|
14006
15375
|
}
|
|
14007
|
-
return { ok: true };
|
|
14008
|
-
};
|
|
14009
|
-
var branchPolicySnapshotFor = (workspaceRoot) => {
|
|
14010
|
-
const resolved = effectiveWorkspacePolicy(workspaceRoot, "branch");
|
|
14011
|
-
const branchPolicy = resolved.branchPolicy;
|
|
14012
|
-
const source = resolved.provenance.branchPolicy;
|
|
14013
|
-
const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
|
|
14014
|
-
const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
|
|
14015
|
-
const fingerprint =
|
|
14016
|
-
return {
|
|
14017
|
-
branchPolicy,
|
|
14018
|
-
namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
|
|
14019
|
-
source,
|
|
14020
|
-
fingerprint
|
|
14021
|
-
};
|
|
14022
|
-
};
|
|
14023
|
-
var validateBranchNameFor = (workspaceRoot, name) => {
|
|
14024
|
-
try {
|
|
14025
|
-
const resolved = branchPolicySnapshotFor(workspaceRoot);
|
|
14026
|
-
return validateBranchNamePolicy(name, resolved.namePolicy, resolved.source);
|
|
14027
|
-
} catch (error) {
|
|
14028
|
-
const correction = "repair or select a valid workspace policy before creating a branch";
|
|
14029
|
-
return {
|
|
14030
|
-
ok: false,
|
|
14031
|
-
rule: "policy_unavailable",
|
|
14032
|
-
source: "workspaces.json",
|
|
14033
|
-
attempted: name,
|
|
14034
|
-
correction,
|
|
14035
|
-
error: `policy_unavailable: cannot validate branch ${JSON.stringify(name)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
|
|
14036
|
-
};
|
|
14037
|
-
}
|
|
14038
|
-
};
|
|
14039
|
-
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14044
|
-
|
|
14045
|
-
|
|
14046
|
-
|
|
14047
|
-
|
|
14048
|
-
const
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
|
|
14056
|
-
|
|
14057
|
-
|
|
14058
|
-
|
|
14059
|
-
// packages/workit-core/src/core/policy-resolver.ts
|
|
14060
|
-
var resolverInputSchema = object({
|
|
14061
|
-
intent: intentSchema,
|
|
14062
|
-
assessment: assessmentSchema,
|
|
14063
|
-
constraints: array(constraintSchema),
|
|
14064
|
-
prior: object({
|
|
14065
|
-
decisions: array(decisionSchema),
|
|
14066
|
-
findings: array(findingSchema),
|
|
14067
|
-
requirements: array(requirementSchema)
|
|
14068
|
-
}).strict(),
|
|
14069
|
-
preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
|
|
14070
|
-
}).strict();
|
|
14071
|
-
var compareCodeUnits3 = (left, right) => {
|
|
14072
|
-
const length = Math.min(left.length, right.length);
|
|
14073
|
-
for (let index = 0;index < length; index += 1) {
|
|
14074
|
-
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
14075
|
-
if (difference !== 0)
|
|
14076
|
-
return difference;
|
|
14077
|
-
}
|
|
14078
|
-
return left.length - right.length;
|
|
14079
|
-
};
|
|
14080
|
-
var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits3);
|
|
14081
|
-
var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right)));
|
|
14082
|
-
var normalizedScope = (scope) => ({
|
|
14083
|
-
description: scope.description,
|
|
14084
|
-
paths: stableUnique(scope.paths),
|
|
14085
|
-
exclusions: stableUnique(scope.exclusions)
|
|
14086
|
-
});
|
|
14087
|
-
var normalizedRefList = (values) => stableList(values);
|
|
14088
|
-
var normalizeAssessment = (assessment) => ({
|
|
14089
|
-
facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
|
|
14090
|
-
signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
|
|
14091
|
-
name,
|
|
14092
|
-
{ ...signal, refs: normalizedRefList(signal.refs) }
|
|
14093
|
-
])),
|
|
14094
|
-
consequences: stableList(assessment.consequences.map((consequence) => ({
|
|
14095
|
-
...consequence,
|
|
14096
|
-
fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
|
|
14097
|
-
}))),
|
|
14098
|
-
verification: stableList(assessment.verification.map((verification) => ({
|
|
14099
|
-
...verification,
|
|
14100
|
-
scope: normalizedScope(verification.scope),
|
|
14101
|
-
availableChecks: normalizedRefList(verification.availableChecks),
|
|
14102
|
-
gaps: stableUnique(verification.gaps)
|
|
14103
|
-
})))
|
|
14104
|
-
});
|
|
14105
|
-
var normalizeConstraint = (constraint) => ({
|
|
14106
|
-
...constraint,
|
|
14107
|
-
source: constraint.source,
|
|
14108
|
-
requires: stableList(constraint.requires.map((requirement) => ({
|
|
14109
|
-
...requirement,
|
|
14110
|
-
scope: normalizedScope(requirement.scope),
|
|
14111
|
-
refs: normalizedRefList(requirement.refs)
|
|
14112
|
-
})))
|
|
14113
|
-
});
|
|
14114
|
-
var normalizeDecision = (decision) => ({
|
|
14115
|
-
...decision,
|
|
14116
|
-
binding: {
|
|
14117
|
-
...decision.binding,
|
|
14118
|
-
scope: normalizedScope(decision.binding.scope),
|
|
14119
|
-
contentRefs: normalizedRefList(decision.binding.contentRefs)
|
|
14120
|
-
},
|
|
14121
|
-
requirementIds: stableUnique(decision.requirementIds)
|
|
14122
|
-
});
|
|
14123
|
-
var normalizeIntent = (intent) => ({
|
|
14124
|
-
...intent,
|
|
14125
|
-
scope: normalizedScope(intent.scope),
|
|
14126
|
-
authorityRefs: normalizedRefList(intent.authorityRefs)
|
|
14127
|
-
});
|
|
14128
|
-
var normalizePrior = (prior) => ({
|
|
14129
|
-
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
14130
|
-
findings: stableList(prior.findings.map((finding) => ({
|
|
14131
|
-
...finding,
|
|
14132
|
-
scope: normalizedScope(finding.scope),
|
|
14133
|
-
refs: normalizedRefList(finding.refs),
|
|
14134
|
-
resolution: finding.resolution ? {
|
|
14135
|
-
...finding.resolution,
|
|
14136
|
-
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
14137
|
-
decisionIds: stableUnique(finding.resolution.decisionIds)
|
|
14138
|
-
} : null
|
|
14139
|
-
}))),
|
|
14140
|
-
requirements: stableList(prior.requirements.map((requirement) => ({
|
|
14141
|
-
...requirement,
|
|
14142
|
-
scope: normalizedScope(requirement.scope)
|
|
14143
|
-
})))
|
|
14144
|
-
});
|
|
14145
|
-
var invalidFields = (error) => ({
|
|
14146
|
-
fields: error.issues.map((issue) => ({
|
|
14147
|
-
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
14148
|
-
reason: issue.message
|
|
14149
|
-
}))
|
|
14150
|
-
});
|
|
14151
|
-
function normalizeResolverInput(input) {
|
|
14152
|
-
const parsed = resolverInputSchema.safeParse(input);
|
|
14153
|
-
if (!parsed.success)
|
|
14154
|
-
return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
14155
|
-
const value = parsed.data;
|
|
14156
|
-
const signals = value.assessment.signals;
|
|
14157
|
-
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
14158
|
-
return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
14159
|
-
fields: [
|
|
14160
|
-
{
|
|
14161
|
-
path: "assessment.signals",
|
|
14162
|
-
reason: "contradictory behaviorChange and mechanicalLowRisk"
|
|
14163
|
-
}
|
|
14164
|
-
]
|
|
14165
|
-
});
|
|
14166
|
-
const ids = value.constraints.map((constraint) => constraint.id);
|
|
14167
|
-
if (new Set(ids).size !== ids.length)
|
|
14168
|
-
return failure2("invalid_input", "constraint IDs must be unique", {
|
|
14169
|
-
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
14170
|
-
});
|
|
14171
|
-
if (value.preferences?.fast && value.preferences.thorough)
|
|
14172
|
-
return failure2("invalid_input", "fast and thorough preferences contradict", {
|
|
14173
|
-
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
|
|
14174
|
-
});
|
|
14175
|
-
const normalized = {
|
|
14176
|
-
intent: normalizeIntent(value.intent),
|
|
14177
|
-
assessment: normalizeAssessment(value.assessment),
|
|
14178
|
-
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
14179
|
-
prior: normalizePrior(value.prior),
|
|
14180
|
-
...value.preferences ? { preferences: { ...value.preferences } } : {},
|
|
14181
|
-
policyVersion: POLICY_VERSION
|
|
14182
|
-
};
|
|
14183
|
-
return success(null, null, normalized);
|
|
14184
|
-
}
|
|
14185
|
-
var requirement = (spec) => ({
|
|
14186
|
-
...spec,
|
|
14187
|
-
id: requirementId({
|
|
14188
|
-
ruleId: spec.ruleId,
|
|
14189
|
-
scope: spec.scope,
|
|
14190
|
-
satisfaction: spec.satisfaction,
|
|
14191
|
-
before: spec.before,
|
|
14192
|
-
dependentAction: spec.dependentAction
|
|
14193
|
-
})
|
|
14194
|
-
});
|
|
14195
|
-
var signalReason = (input, name) => input.assessment.signals[name].reason;
|
|
14196
|
-
function resolveRequirements(input) {
|
|
14197
|
-
const { signals } = input.assessment;
|
|
14198
|
-
const scope = input.intent.scope;
|
|
14199
|
-
const requirements = [];
|
|
14200
|
-
const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
|
|
14201
|
-
const hasUnknownFact = [
|
|
14202
|
-
...input.assessment.facts,
|
|
14203
|
-
...input.assessment.consequences.map(({ fact }) => fact)
|
|
14204
|
-
].some((fact) => fact.basis === "unknown");
|
|
14205
|
-
if (hasUnknownSignal || hasUnknownFact)
|
|
14206
|
-
requirements.push(requirement({
|
|
14207
|
-
ruleId: "consequential-unknown",
|
|
14208
|
-
dimension: "investigation",
|
|
14209
|
-
scope,
|
|
14210
|
-
reason: `Resolve consequential uncertainty before the dependent action: ${[
|
|
14211
|
-
...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
|
|
14212
|
-
...hasUnknownFact ? ["unknown fact"] : []
|
|
14213
|
-
].join(", ")}`,
|
|
14214
|
-
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
14215
|
-
before: "dependent_action",
|
|
14216
|
-
dependentAction: null,
|
|
14217
|
-
acceptanceAllowed: false
|
|
14218
|
-
}));
|
|
14219
|
-
if (signals.productChoiceOpen.value === true)
|
|
14220
|
-
requirements.push(requirement({
|
|
14221
|
-
ruleId: "product-decision",
|
|
14222
|
-
dimension: "decisions",
|
|
14223
|
-
scope,
|
|
14224
|
-
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
14225
|
-
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
14226
|
-
before: "dependent_action",
|
|
14227
|
-
dependentAction: null,
|
|
14228
|
-
acceptanceAllowed: false
|
|
14229
|
-
}));
|
|
14230
|
-
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
14231
|
-
if (signals.behaviorChange.value === true)
|
|
14232
|
-
requirements.push(requirement({
|
|
14233
|
-
ruleId: "behavioral-verification",
|
|
14234
|
-
dimension: "testing",
|
|
14235
|
-
scope,
|
|
14236
|
-
reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
|
|
14237
|
-
satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
|
|
14238
|
-
before: "close",
|
|
14239
|
-
dependentAction: null,
|
|
14240
|
-
acceptanceAllowed: false
|
|
14241
|
-
}), requirement({
|
|
14242
|
-
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
14243
|
-
dimension: "review",
|
|
14244
|
-
scope,
|
|
14245
|
-
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.",
|
|
14246
|
-
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.",
|
|
14247
|
-
before: "close",
|
|
14248
|
-
dependentAction: null,
|
|
14249
|
-
acceptanceAllowed: !independentBehaviorReview
|
|
14250
|
-
}));
|
|
14251
|
-
else if (signals.mechanicalLowRisk.value === true)
|
|
14252
|
-
requirements.push(requirement({
|
|
14253
|
-
ruleId: "mechanical-existing-checks",
|
|
14254
|
-
dimension: "verification",
|
|
14255
|
-
scope,
|
|
14256
|
-
reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
|
|
14257
|
-
satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
|
|
14258
|
-
before: "close",
|
|
14259
|
-
dependentAction: null,
|
|
14260
|
-
acceptanceAllowed: true
|
|
14261
|
-
}), requirement({
|
|
14262
|
-
ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
|
|
14263
|
-
dimension: "review",
|
|
14264
|
-
scope,
|
|
14265
|
-
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.",
|
|
14266
|
-
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.",
|
|
14267
|
-
before: "close",
|
|
14268
|
-
dependentAction: null,
|
|
14269
|
-
acceptanceAllowed: !input.preferences?.thorough
|
|
14270
|
-
}));
|
|
14271
|
-
if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
|
|
14272
|
-
requirements.push(requirement({
|
|
14273
|
-
ruleId: "pre-pr-cleanup",
|
|
14274
|
-
dimension: "verification",
|
|
14275
|
-
scope,
|
|
14276
|
-
reason: "Implementation output must be deslopped before it is delivered.",
|
|
14277
|
-
satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
|
|
14278
|
-
before: "dependent_action",
|
|
14279
|
-
dependentAction: "hosting.pull_request",
|
|
14280
|
-
acceptanceAllowed: true
|
|
14281
|
-
}));
|
|
14282
|
-
if (signals.durableAgreementNeeded.value === true)
|
|
14283
|
-
requirements.push(requirement({
|
|
14284
|
-
ruleId: "durable-spec",
|
|
14285
|
-
dimension: "artifacts",
|
|
14286
|
-
scope,
|
|
14287
|
-
reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
|
|
14288
|
-
satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
|
|
14289
|
-
before: "write",
|
|
14290
|
-
dependentAction: null,
|
|
14291
|
-
acceptanceAllowed: true
|
|
14292
|
-
}));
|
|
14293
|
-
if (signals.coordinationPlanNeeded.value === true)
|
|
14294
|
-
requirements.push(requirement({
|
|
14295
|
-
ruleId: "coordination-plan",
|
|
14296
|
-
dimension: "continuity",
|
|
14297
|
-
scope,
|
|
14298
|
-
reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
|
|
14299
|
-
satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
|
|
14300
|
-
before: "dependent_action",
|
|
14301
|
-
dependentAction: null,
|
|
14302
|
-
acceptanceAllowed: true
|
|
14303
|
-
}));
|
|
14304
|
-
if (signals.helperUseful.value === true && !input.preferences?.fast)
|
|
14305
|
-
requirements.push(requirement({
|
|
14306
|
-
ruleId: "helper-usefulness",
|
|
14307
|
-
dimension: "delegation",
|
|
14308
|
-
scope,
|
|
14309
|
-
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
14310
|
-
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
14311
|
-
before: "dependent_action",
|
|
14312
|
-
dependentAction: null,
|
|
14313
|
-
acceptanceAllowed: true
|
|
14314
|
-
}));
|
|
14315
|
-
for (const constraint of input.constraints)
|
|
14316
|
-
for (const obligation of constraint.requires) {
|
|
14317
|
-
const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
|
|
14318
|
-
requirements.push(requirement({
|
|
14319
|
-
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
14320
|
-
kind: obligation.kind,
|
|
14321
|
-
scope: normalizedScope(obligation.scope),
|
|
14322
|
-
refs: normalizedRefList(obligation.refs),
|
|
14323
|
-
method: obligation.method,
|
|
14324
|
-
before: obligation.before,
|
|
14325
|
-
dependentAction: obligation.dependentAction
|
|
14326
|
-
})}`,
|
|
14327
|
-
dimension,
|
|
14328
|
-
scope: obligation.scope,
|
|
14329
|
-
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
14330
|
-
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
14331
|
-
before: obligation.before,
|
|
14332
|
-
dependentAction: obligation.dependentAction,
|
|
14333
|
-
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
14334
|
-
}));
|
|
14335
|
-
}
|
|
14336
|
-
const seen = new Set;
|
|
14337
|
-
return requirements.filter((item) => {
|
|
14338
|
-
if (seen.has(item.id))
|
|
14339
|
-
return false;
|
|
14340
|
-
seen.add(item.id);
|
|
14341
|
-
return true;
|
|
14342
|
-
});
|
|
14343
|
-
}
|
|
15376
|
+
return { ok: true };
|
|
15377
|
+
};
|
|
15378
|
+
var branchPolicySnapshotFor = (workspaceRoot) => {
|
|
15379
|
+
const resolved = effectiveWorkspacePolicy(workspaceRoot, "branch");
|
|
15380
|
+
const branchPolicy = resolved.branchPolicy;
|
|
15381
|
+
const source = resolved.provenance.branchPolicy;
|
|
15382
|
+
const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
|
|
15383
|
+
const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
|
|
15384
|
+
const fingerprint = createHash10("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
|
|
15385
|
+
return {
|
|
15386
|
+
branchPolicy,
|
|
15387
|
+
namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
|
|
15388
|
+
source,
|
|
15389
|
+
fingerprint
|
|
15390
|
+
};
|
|
15391
|
+
};
|
|
15392
|
+
var validateBranchNameFor = (workspaceRoot, name) => {
|
|
15393
|
+
try {
|
|
15394
|
+
const resolved = branchPolicySnapshotFor(workspaceRoot);
|
|
15395
|
+
return validateBranchNamePolicy(name, resolved.namePolicy, resolved.source);
|
|
15396
|
+
} catch (error) {
|
|
15397
|
+
const correction = "repair or select a valid workspace policy before creating a branch";
|
|
15398
|
+
return {
|
|
15399
|
+
ok: false,
|
|
15400
|
+
rule: "policy_unavailable",
|
|
15401
|
+
source: "workspaces.json",
|
|
15402
|
+
attempted: name,
|
|
15403
|
+
correction,
|
|
15404
|
+
error: `policy_unavailable: cannot validate branch ${JSON.stringify(name)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
|
|
15405
|
+
};
|
|
15406
|
+
}
|
|
15407
|
+
};
|
|
15408
|
+
|
|
15409
|
+
// packages/workit-core/src/core/route-intent.ts
|
|
15410
|
+
var shellBranchTarget = (command) => {
|
|
15411
|
+
if (!command.trim() || /[\r\n;&|<>`'"$()\\*?{}~!#]/u.test(command) || command.includes("[") || command.includes("]"))
|
|
15412
|
+
return null;
|
|
15413
|
+
const args = command.trim().split(/\s+/u);
|
|
15414
|
+
if (args[0] !== "git")
|
|
15415
|
+
return null;
|
|
15416
|
+
const option = args[2];
|
|
15417
|
+
const target = args[1] === "switch" && ["-c", "--create", "-C", "--force-create"].includes(option ?? "") && (args.length === 4 || args.length === 5) ? args[3] : args[1] === "checkout" && ["-b", "-B"].includes(option ?? "") && (args.length === 4 || args.length === 5) ? args[3] : args[1] === "branch" && (args.length === 3 || args.length === 4) ? args[2] : null;
|
|
15418
|
+
return target && !target.startsWith("-") ? target : null;
|
|
15419
|
+
};
|
|
15420
|
+
var shellBranchPolicyViolation = (root, command) => {
|
|
15421
|
+
const target = shellBranchTarget(command);
|
|
15422
|
+
return target === null ? null : validateBranchNameFor(root, target);
|
|
15423
|
+
};
|
|
15424
|
+
// packages/workit-core/src/core/task-engine.ts
|
|
15425
|
+
import { realpathSync as realpathSync4 } from "node:fs";
|
|
15426
|
+
|
|
15427
|
+
// packages/workit-core/src/core/policy-resolver.ts
|
|
14344
15428
|
function resolvePolicy(input) {
|
|
14345
|
-
const
|
|
14346
|
-
if (
|
|
14347
|
-
return
|
|
14348
|
-
|
|
15429
|
+
const ids = input.constraints.map((constraint) => constraint.id);
|
|
15430
|
+
if (new Set(ids).size !== ids.length)
|
|
15431
|
+
return failure2("invalid_input", "constraint IDs must be unique", {
|
|
15432
|
+
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
15433
|
+
});
|
|
14349
15434
|
const policy = {
|
|
14350
15435
|
policyVersion: POLICY_VERSION,
|
|
14351
|
-
inputDigest: sha256(canonicalJson(
|
|
14352
|
-
|
|
15436
|
+
inputDigest: sha256(canonicalJson({
|
|
15437
|
+
judgment: input.judgment,
|
|
15438
|
+
scope: input.intent.scope,
|
|
15439
|
+
constraints: input.constraints,
|
|
15440
|
+
verification: input.verification ?? "self",
|
|
15441
|
+
policyVersion: POLICY_VERSION
|
|
15442
|
+
})),
|
|
15443
|
+
requirements: deriveRequirements(input.judgment, input.intent.scope, input.constraints, input.verification)
|
|
14353
15444
|
};
|
|
14354
15445
|
const parsed = policySchema.safeParse(policy);
|
|
14355
15446
|
if (!parsed.success)
|
|
14356
|
-
return failure2("invalid_input", "resolved policy is invalid",
|
|
15447
|
+
return failure2("invalid_input", "resolved policy is invalid", {
|
|
15448
|
+
fields: parsed.error.issues.map((issue) => ({
|
|
15449
|
+
path: issue.path.join("."),
|
|
15450
|
+
reason: issue.message
|
|
15451
|
+
}))
|
|
15452
|
+
});
|
|
14357
15453
|
return success(null, null, parsed.data);
|
|
14358
15454
|
}
|
|
15455
|
+
var compareCodeUnits3 = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
14359
15456
|
function diffPolicy(previous, next, reason, now) {
|
|
14360
15457
|
if (!policySchema.safeParse(previous).success && previous !== null)
|
|
14361
15458
|
throw new TypeError("previous policy is invalid");
|
|
@@ -14383,7 +15480,247 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
14383
15480
|
return parsed.data;
|
|
14384
15481
|
}
|
|
14385
15482
|
|
|
15483
|
+
// packages/workit-core/src/core/policy/judgment.ts
|
|
15484
|
+
var isObject6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15485
|
+
var squash = (key) => key.toLowerCase().replace(/[_-]/g, "");
|
|
15486
|
+
var ALIASES = {
|
|
15487
|
+
riskTier: ["risktier", "risk", "risklevel", "tier"],
|
|
15488
|
+
behaviorChange: ["behaviorchange", "behavior", "behaviour", "behaviourchange"],
|
|
15489
|
+
productChoiceOpen: ["productchoiceopen", "openproductchoice", "productchoice", "openchoice"],
|
|
15490
|
+
needsPlan: ["needsplan", "plan", "needsspec", "spec"],
|
|
15491
|
+
note: ["note", "notes", "reason", "why"],
|
|
15492
|
+
refs: ["refs", "ref", "references"]
|
|
15493
|
+
};
|
|
15494
|
+
var LEGACY_KEYS = new Set(["assessment", "facts", "signals", "consequences", "verification"]);
|
|
15495
|
+
var RISK = {
|
|
15496
|
+
trivial: "trivial",
|
|
15497
|
+
low: "trivial",
|
|
15498
|
+
minor: "trivial",
|
|
15499
|
+
mechanical: "trivial",
|
|
15500
|
+
none: "trivial",
|
|
15501
|
+
normal: "normal",
|
|
15502
|
+
medium: "normal",
|
|
15503
|
+
med: "normal",
|
|
15504
|
+
moderate: "normal",
|
|
15505
|
+
high: "high",
|
|
15506
|
+
critical: "high",
|
|
15507
|
+
major: "high"
|
|
15508
|
+
};
|
|
15509
|
+
var YES = new Set(["true", "yes", "y", "1", "on"]);
|
|
15510
|
+
var NO = new Set(["false", "no", "n", "0", "off", "none"]);
|
|
15511
|
+
var flag = (value, unknownAs) => {
|
|
15512
|
+
if (typeof value === "boolean")
|
|
15513
|
+
return { ok: true, value };
|
|
15514
|
+
if (typeof value === "number" && (value === 0 || value === 1))
|
|
15515
|
+
return { ok: true, value: value === 1 };
|
|
15516
|
+
if (typeof value !== "string")
|
|
15517
|
+
return { ok: false };
|
|
15518
|
+
const text = value.trim().toLowerCase();
|
|
15519
|
+
if (YES.has(text))
|
|
15520
|
+
return { ok: true, value: true };
|
|
15521
|
+
if (NO.has(text))
|
|
15522
|
+
return { ok: true, value: false };
|
|
15523
|
+
if (text === "unknown" || text === "maybe")
|
|
15524
|
+
return { ok: true, value: unknownAs };
|
|
15525
|
+
return { ok: false };
|
|
15526
|
+
};
|
|
15527
|
+
var fromLegacy = (raw, previous) => {
|
|
15528
|
+
const source = isObject6(raw.assessment) ? raw.assessment : raw;
|
|
15529
|
+
const signals = isObject6(source.signals) ? source.signals : {};
|
|
15530
|
+
const value = (name) => isObject6(signals[name]) ? signals[name].value : undefined;
|
|
15531
|
+
const known = (name, fallback) => typeof value(name) === "boolean" ? value(name) : fallback;
|
|
15532
|
+
const behaviorChange = known("behaviorChange", previous?.behaviorChange ?? false);
|
|
15533
|
+
const consequences = Array.isArray(source.consequences) ? source.consequences : [];
|
|
15534
|
+
const freshReview = value("behaviorChange") === true && consequences.some((item) => isObject6(item) && ["security", "data", "public_contract", "operations"].includes(String(item.area)));
|
|
15535
|
+
return {
|
|
15536
|
+
riskTier: freshReview ? "high" : value("mechanicalLowRisk") === true && !behaviorChange ? "trivial" : "normal",
|
|
15537
|
+
behaviorChange,
|
|
15538
|
+
productChoiceOpen: known("productChoiceOpen", previous?.productChoiceOpen ?? false),
|
|
15539
|
+
needsPlan: known("durableAgreementNeeded", previous?.needsPlan ?? false) || value("coordinationPlanNeeded") === true
|
|
15540
|
+
};
|
|
15541
|
+
};
|
|
15542
|
+
var DEFAULT = {
|
|
15543
|
+
riskTier: "normal",
|
|
15544
|
+
behaviorChange: false,
|
|
15545
|
+
productChoiceOpen: false,
|
|
15546
|
+
needsPlan: false,
|
|
15547
|
+
note: null,
|
|
15548
|
+
refs: []
|
|
15549
|
+
};
|
|
15550
|
+
function normalizeJudgment(raw, previous) {
|
|
15551
|
+
const input = isObject6(raw) ? raw : {};
|
|
15552
|
+
const legacy = Object.keys(input).some((key) => LEGACY_KEYS.has(key));
|
|
15553
|
+
const base = { ...DEFAULT, ...previous, refs: [...previous?.refs ?? []] };
|
|
15554
|
+
const judgment = legacy ? { ...base, ...fromLegacy(input, previous) } : { ...base };
|
|
15555
|
+
const ignored = [];
|
|
15556
|
+
const fields = [];
|
|
15557
|
+
for (const [key, value] of Object.entries(input)) {
|
|
15558
|
+
if (LEGACY_KEYS.has(key))
|
|
15559
|
+
continue;
|
|
15560
|
+
const name = Object.keys(ALIASES).find((field) => ALIASES[field].includes(squash(key)));
|
|
15561
|
+
if (!name) {
|
|
15562
|
+
ignored.push(key);
|
|
15563
|
+
continue;
|
|
15564
|
+
}
|
|
15565
|
+
if (name === "riskTier") {
|
|
15566
|
+
const tier = typeof value === "string" ? RISK[value.trim().toLowerCase()] : undefined;
|
|
15567
|
+
if (tier)
|
|
15568
|
+
judgment.riskTier = tier;
|
|
15569
|
+
else
|
|
15570
|
+
fields.push({ path: key, reason: "expected trivial|normal|high (or low|medium)" });
|
|
15571
|
+
} else if (name === "note") {
|
|
15572
|
+
judgment.note = typeof value === "string" && value.trim() ? value.trim() : null;
|
|
15573
|
+
} else if (name === "refs") {
|
|
15574
|
+
const items = Array.isArray(value) ? value : [value];
|
|
15575
|
+
judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
|
|
15576
|
+
} else {
|
|
15577
|
+
if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
|
|
15578
|
+
judgment.needsPlan = true;
|
|
15579
|
+
judgment.refs.push(refOf(value.trim()));
|
|
15580
|
+
continue;
|
|
15581
|
+
}
|
|
15582
|
+
const parsed = flag(value, name === "behaviorChange");
|
|
15583
|
+
if (parsed.ok)
|
|
15584
|
+
judgment[name] = parsed.value;
|
|
15585
|
+
else
|
|
15586
|
+
fields.push({ path: key, reason: "expected true|false (yes|no accepted)" });
|
|
15587
|
+
}
|
|
15588
|
+
}
|
|
15589
|
+
if (fields.length)
|
|
15590
|
+
return failure2("invalid_input", "judgment values are invalid", { fields });
|
|
15591
|
+
const seen = new Set;
|
|
15592
|
+
judgment.refs = judgment.refs.filter((ref) => {
|
|
15593
|
+
const key = JSON.stringify(ref);
|
|
15594
|
+
return seen.has(key) ? false : (seen.add(key), true);
|
|
15595
|
+
});
|
|
15596
|
+
const parsed = judgmentSchema.safeParse(judgment);
|
|
15597
|
+
if (!parsed.success)
|
|
15598
|
+
return failure2("invalid_input", "judgment is invalid", {
|
|
15599
|
+
fields: parsed.error.issues.map((issue) => ({
|
|
15600
|
+
path: issue.path.join("."),
|
|
15601
|
+
reason: issue.message
|
|
15602
|
+
}))
|
|
15603
|
+
});
|
|
15604
|
+
return success(null, null, { judgment: parsed.data, ignored, legacy });
|
|
15605
|
+
}
|
|
15606
|
+
|
|
15607
|
+
// packages/workit-core/src/autonomy.ts
|
|
15608
|
+
import os3 from "node:os";
|
|
15609
|
+
import path15 from "node:path";
|
|
15610
|
+
|
|
15611
|
+
// packages/workit-core/src/core/safe-write.ts
|
|
15612
|
+
import {
|
|
15613
|
+
closeSync as closeSync2,
|
|
15614
|
+
fchmodSync,
|
|
15615
|
+
fsyncSync,
|
|
15616
|
+
lstatSync as lstatSync3,
|
|
15617
|
+
mkdirSync as mkdirSync3,
|
|
15618
|
+
openSync as openSync2,
|
|
15619
|
+
renameSync as renameSync2,
|
|
15620
|
+
unlinkSync,
|
|
15621
|
+
writeFileSync as writeFileSync3
|
|
15622
|
+
} from "node:fs";
|
|
15623
|
+
function writeFileExclusive(file, content, mode) {
|
|
15624
|
+
try {
|
|
15625
|
+
writeFileSync3(file, content, { encoding: "utf8", flag: "wx", mode });
|
|
15626
|
+
return "created";
|
|
15627
|
+
} catch (err) {
|
|
15628
|
+
if (err.code === "EEXIST")
|
|
15629
|
+
return "preserved";
|
|
15630
|
+
throw err;
|
|
15631
|
+
}
|
|
15632
|
+
}
|
|
15633
|
+
|
|
15634
|
+
// packages/workit-core/src/autonomy.ts
|
|
15635
|
+
var accountHome = () => {
|
|
15636
|
+
try {
|
|
15637
|
+
return os3.userInfo().homedir;
|
|
15638
|
+
} catch {
|
|
15639
|
+
return os3.homedir();
|
|
15640
|
+
}
|
|
15641
|
+
};
|
|
15642
|
+
var grantsHome = { resolve: accountHome };
|
|
15643
|
+
var grantsDir = () => path15.join(grantsHome.resolve(), ".config", "workit");
|
|
15644
|
+
var grantsOverride = () => {
|
|
15645
|
+
const configured = path15.resolve(resolveConfigDir());
|
|
15646
|
+
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`;
|
|
15647
|
+
};
|
|
15648
|
+
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
15649
|
+
var DEFAULT_GRANTS = Object.freeze({
|
|
15650
|
+
push: true,
|
|
15651
|
+
pr: true,
|
|
15652
|
+
merge: false,
|
|
15653
|
+
release: false,
|
|
15654
|
+
rerun: true
|
|
15655
|
+
});
|
|
15656
|
+
var DEFAULT_ENDPOINT = "commit";
|
|
15657
|
+
var DEFAULT_VERIFICATION = "self";
|
|
15658
|
+
var verificationOf = (entry) => entry?.verification === "independent" ? "independent" : "self";
|
|
15659
|
+
var endpointOf = (entry) => entry?.defaultEndpoint === "pr" ? "pr" : "commit";
|
|
15660
|
+
var isGrantValue = (value) => value === true || value === false || value === "verified";
|
|
15661
|
+
var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
|
|
15662
|
+
var fromAutoApprove = (value) => {
|
|
15663
|
+
if (value === true)
|
|
15664
|
+
return { push: true, pr: true, merge: "verified" };
|
|
15665
|
+
if (!Array.isArray(value))
|
|
15666
|
+
return null;
|
|
15667
|
+
const grants = {};
|
|
15668
|
+
for (const item of value)
|
|
15669
|
+
if (item === "push" || item === "pr")
|
|
15670
|
+
grants[item] = true;
|
|
15671
|
+
else if (item === "merge")
|
|
15672
|
+
grants.merge = "verified";
|
|
15673
|
+
return Object.keys(grants).length > 0 ? grants : null;
|
|
15674
|
+
};
|
|
15675
|
+
function configuredGrants(entry) {
|
|
15676
|
+
const record = entry ?? {};
|
|
15677
|
+
const raw = record.autonomy;
|
|
15678
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
15679
|
+
const grants = {};
|
|
15680
|
+
for (const kind of GRANT_KINDS) {
|
|
15681
|
+
const value = raw[kind];
|
|
15682
|
+
if (isGrantValue(value))
|
|
15683
|
+
grants[kind] = normalized(kind, value);
|
|
15684
|
+
}
|
|
15685
|
+
return { grants, source: "autonomy" };
|
|
15686
|
+
}
|
|
15687
|
+
const legacy = fromAutoApprove(record.autoApprove);
|
|
15688
|
+
return legacy ? { grants: legacy, source: "autoApprove" } : { grants: {}, source: "default" };
|
|
15689
|
+
}
|
|
15690
|
+
function resolveAutonomy(cwd) {
|
|
15691
|
+
const override = grantsOverride();
|
|
15692
|
+
if (override)
|
|
15693
|
+
return {
|
|
15694
|
+
workspace: null,
|
|
15695
|
+
grants: { ...DEFAULT_GRANTS },
|
|
15696
|
+
configured: [],
|
|
15697
|
+
source: "default",
|
|
15698
|
+
accountConfigured: false,
|
|
15699
|
+
defaultEndpoint: DEFAULT_ENDPOINT,
|
|
15700
|
+
verification: DEFAULT_VERIFICATION,
|
|
15701
|
+
note: override
|
|
15702
|
+
};
|
|
15703
|
+
const workspace = resolveWorkspaceFrom(cwd, grantsDir(), process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined);
|
|
15704
|
+
const { grants, source } = configuredGrants(workspace);
|
|
15705
|
+
return {
|
|
15706
|
+
workspace: workspace?.name ?? null,
|
|
15707
|
+
grants: { ...DEFAULT_GRANTS, ...grants },
|
|
15708
|
+
configured: GRANT_KINDS.filter((kind) => grants[kind] !== undefined),
|
|
15709
|
+
source,
|
|
15710
|
+
accountConfigured: Boolean(workspace?.vcs?.account),
|
|
15711
|
+
defaultEndpoint: endpointOf(workspace),
|
|
15712
|
+
verification: verificationOf(workspace)
|
|
15713
|
+
};
|
|
15714
|
+
}
|
|
15715
|
+
|
|
14386
15716
|
// packages/workit-core/src/core/task-engine.ts
|
|
15717
|
+
var workspaceVerification = (root) => {
|
|
15718
|
+
try {
|
|
15719
|
+
return resolveAutonomy(root).verification;
|
|
15720
|
+
} catch {
|
|
15721
|
+
return "self";
|
|
15722
|
+
}
|
|
15723
|
+
};
|
|
14387
15724
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
14388
15725
|
kind,
|
|
14389
15726
|
host: context.caller.host,
|
|
@@ -14576,22 +15913,7 @@ var mapRef = (ref, ids) => {
|
|
|
14576
15913
|
return ref;
|
|
14577
15914
|
return { ...ref, id: ids.get(ref.id) ?? ref.id };
|
|
14578
15915
|
};
|
|
14579
|
-
var portableRefs = (refs) => refs.filter((ref) => ref.kind === "file" || ref.kind === "record");
|
|
14580
15916
|
var portableRef = (ref) => ref && (ref.kind === "file" || ref.kind === "record") ? ref : null;
|
|
14581
|
-
var portableFact = (fact) => {
|
|
14582
|
-
const refs = portableRefs(fact.refs);
|
|
14583
|
-
return fact.basis === "observed" && refs.length === 0 ? { ...fact, basis: "unknown", refs } : { ...fact, refs };
|
|
14584
|
-
};
|
|
14585
|
-
var portableSignal = (signal) => {
|
|
14586
|
-
const refs = portableRefs(signal.refs);
|
|
14587
|
-
return signal.basis === "observed" && refs.length === 0 ? {
|
|
14588
|
-
...signal,
|
|
14589
|
-
value: "unknown",
|
|
14590
|
-
basis: "unknown",
|
|
14591
|
-
reason: "portable supporting reference was removed",
|
|
14592
|
-
refs
|
|
14593
|
-
} : { ...signal, refs };
|
|
14594
|
-
};
|
|
14595
15917
|
var importedProvenance = (context) => ({
|
|
14596
15918
|
kind: "imported",
|
|
14597
15919
|
host: context.caller.host,
|
|
@@ -14603,18 +15925,6 @@ var portableTask = (task) => {
|
|
|
14603
15925
|
if (typeof stripped !== "object" || stripped === null || Array.isArray(stripped))
|
|
14604
15926
|
throw new TypeError("portable task rewrite produced a non-record");
|
|
14605
15927
|
const clone = structuredClone(stripped);
|
|
14606
|
-
const demote = (facts, signals, consequences) => {
|
|
14607
|
-
for (const fact of facts)
|
|
14608
|
-
Object.assign(fact, portableFact(fact));
|
|
14609
|
-
for (const name of Object.keys(signals)) {
|
|
14610
|
-
const signal = signals[name];
|
|
14611
|
-
Object.assign(signal, portableSignal(signal));
|
|
14612
|
-
}
|
|
14613
|
-
for (const consequence of consequences)
|
|
14614
|
-
Object.assign(consequence.fact, portableFact(consequence.fact));
|
|
14615
|
-
};
|
|
14616
|
-
for (const entry of clone.assessments)
|
|
14617
|
-
demote(entry.data.facts, entry.data.signals, entry.data.consequences);
|
|
14618
15928
|
clone.candidates = [];
|
|
14619
15929
|
return clone;
|
|
14620
15930
|
};
|
|
@@ -14631,6 +15941,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
14631
15941
|
for (const entry of [
|
|
14632
15942
|
source.intent,
|
|
14633
15943
|
...source.assessments,
|
|
15944
|
+
...source.judgments ?? [],
|
|
14634
15945
|
...source.evidence,
|
|
14635
15946
|
...source.decisions,
|
|
14636
15947
|
...source.findings,
|
|
@@ -14654,9 +15965,16 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
14654
15965
|
id: fresh(entry.id),
|
|
14655
15966
|
recordedAt: timestamp,
|
|
14656
15967
|
provenance: imported,
|
|
14657
|
-
data: rewriteRecordRefs(data,
|
|
15968
|
+
data: rewriteRecordRefs(data, legacyAssessmentSchema, remap)
|
|
14658
15969
|
};
|
|
14659
15970
|
});
|
|
15971
|
+
const judgments = source.judgments?.map((entry) => ({
|
|
15972
|
+
...entry,
|
|
15973
|
+
id: fresh(entry.id),
|
|
15974
|
+
recordedAt: timestamp,
|
|
15975
|
+
provenance: imported,
|
|
15976
|
+
data: rewriteRecordRefs(entry.data, judgmentSchema, remap)
|
|
15977
|
+
}));
|
|
14660
15978
|
const evidence = source.evidence.map((entry) => ({
|
|
14661
15979
|
...entry,
|
|
14662
15980
|
id: fresh(entry.id),
|
|
@@ -14724,6 +16042,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
14724
16042
|
status: "paused",
|
|
14725
16043
|
closure: null,
|
|
14726
16044
|
assessments,
|
|
16045
|
+
...judgments ? { judgments } : {},
|
|
14727
16046
|
evidence,
|
|
14728
16047
|
decisions: decisions.map((entry) => {
|
|
14729
16048
|
const data = rewriteRecordRefs(entry.data, decisionSchema, remap);
|
|
@@ -14793,7 +16112,8 @@ class WorkitCore {
|
|
|
14793
16112
|
retryOmittedRevisions(request, run) {
|
|
14794
16113
|
return retryFilledRevisions(filledRevisions(request), run);
|
|
14795
16114
|
}
|
|
14796
|
-
implicitTask(family,
|
|
16115
|
+
implicitTask(family, raw) {
|
|
16116
|
+
const request = normalizeOperationInput(family, raw, this.context.caller.host);
|
|
14797
16117
|
if (typeof request !== "object" || request === null || Array.isArray(request))
|
|
14798
16118
|
return success(null, null, request);
|
|
14799
16119
|
const value = request;
|
|
@@ -15047,7 +16367,11 @@ class WorkitCore {
|
|
|
15047
16367
|
if ((this.context.workerId ?? null) !== null)
|
|
15048
16368
|
return failure2("permission_denied", "helpers cannot change task requirements");
|
|
15049
16369
|
this.fillRevisions(input, task.data);
|
|
15050
|
-
const
|
|
16370
|
+
const judged = normalizeJudgment(input.judgment, latestJudgment(task.data));
|
|
16371
|
+
if (!judged.ok)
|
|
16372
|
+
return judged;
|
|
16373
|
+
const judgment = judged.data.judgment;
|
|
16374
|
+
const resolved = this.resolve(task.data, judgment);
|
|
15051
16375
|
if (!resolved.ok)
|
|
15052
16376
|
return resolved;
|
|
15053
16377
|
if (input.action === "preview")
|
|
@@ -15058,16 +16382,17 @@ class WorkitCore {
|
|
|
15058
16382
|
if (current.status === "closed")
|
|
15059
16383
|
return failure2("invalid_transition", "closed task cannot be assessed");
|
|
15060
16384
|
const nextChange = diffPolicy(current.policy, resolved.data, "policy reassessed", mutation.now);
|
|
15061
|
-
const
|
|
16385
|
+
const entry = {
|
|
15062
16386
|
id: newId(),
|
|
15063
16387
|
recordedAt: mutation.now,
|
|
15064
16388
|
provenance: provenance(this.context),
|
|
15065
|
-
data:
|
|
16389
|
+
data: judgment
|
|
15066
16390
|
};
|
|
15067
16391
|
const next = {
|
|
15068
16392
|
...current,
|
|
15069
16393
|
constraints: this.context.constraints,
|
|
15070
|
-
|
|
16394
|
+
judgments: [...current.judgments ?? [], entry],
|
|
16395
|
+
critical: [...new Set([...current.critical ?? [], "judgments"])],
|
|
15071
16396
|
policy: resolved.data,
|
|
15072
16397
|
policyChanges: nextChange ? [...current.policyChanges, nextChange] : current.policyChanges
|
|
15073
16398
|
};
|
|
@@ -15269,9 +16594,14 @@ class WorkitCore {
|
|
|
15269
16594
|
return workspace;
|
|
15270
16595
|
if (!workspace.data)
|
|
15271
16596
|
return failure2("not_found", "workspace not found");
|
|
15272
|
-
|
|
16597
|
+
const binding = {
|
|
16598
|
+
...input.binding,
|
|
16599
|
+
taskId: input.binding.taskId ?? task.data.id,
|
|
16600
|
+
workspaceId: input.binding.workspaceId ?? workspace.data.id
|
|
16601
|
+
};
|
|
16602
|
+
if (binding.taskId !== task.data.id || binding.workspaceId !== workspace.data.id)
|
|
15273
16603
|
return failure2("invalid_input", "decision task or workspace binding is invalid");
|
|
15274
|
-
const content = verifyDecisionContentAtRoot(this.store.root,
|
|
16604
|
+
const content = verifyDecisionContentAtRoot(this.store.root, binding);
|
|
15275
16605
|
if (!content.ok)
|
|
15276
16606
|
return content;
|
|
15277
16607
|
const knownRequirements = new Set(task.data.policy?.requirements.map((item) => item.id) ?? []);
|
|
@@ -15279,7 +16609,7 @@ class WorkitCore {
|
|
|
15279
16609
|
return failure2("invalid_input", "decision references an unknown requirement");
|
|
15280
16610
|
const base = {
|
|
15281
16611
|
purpose: input.purpose,
|
|
15282
|
-
binding
|
|
16612
|
+
binding,
|
|
15283
16613
|
response: input.response,
|
|
15284
16614
|
requirementIds: input.requirementIds,
|
|
15285
16615
|
revoked: null
|
|
@@ -15638,16 +16968,12 @@ class WorkitCore {
|
|
|
15638
16968
|
now: trustedNow(this.context)
|
|
15639
16969
|
});
|
|
15640
16970
|
}
|
|
15641
|
-
resolve(task,
|
|
16971
|
+
resolve(task, judgment) {
|
|
15642
16972
|
return resolvePolicy({
|
|
15643
16973
|
intent: task.intent.data,
|
|
15644
|
-
|
|
16974
|
+
judgment,
|
|
15645
16975
|
constraints: this.context.constraints,
|
|
15646
|
-
|
|
15647
|
-
decisions: task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data),
|
|
15648
|
-
findings: task.findings.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data),
|
|
15649
|
-
requirements: task.origin || task.policy === null ? [] : task.policy.requirements
|
|
15650
|
-
}
|
|
16976
|
+
verification: workspaceVerification(this.context.root)
|
|
15651
16977
|
});
|
|
15652
16978
|
}
|
|
15653
16979
|
summary(task) {
|
|
@@ -15973,9 +17299,9 @@ class WorkitCore {
|
|
|
15973
17299
|
}
|
|
15974
17300
|
}
|
|
15975
17301
|
// packages/workit-core/src/core/repo-context.ts
|
|
15976
|
-
import { spawnSync as
|
|
15977
|
-
import { existsSync as
|
|
15978
|
-
import
|
|
17302
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
17303
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
|
|
17304
|
+
import path16 from "node:path";
|
|
15979
17305
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
15980
17306
|
try {
|
|
15981
17307
|
gitRevisionParts(value);
|
|
@@ -16002,7 +17328,7 @@ var invalidRange = (cwd) => ({
|
|
|
16002
17328
|
cwd
|
|
16003
17329
|
});
|
|
16004
17330
|
var runGit = (cwd, args) => {
|
|
16005
|
-
const result =
|
|
17331
|
+
const result = spawnSync9("git", args, { cwd, encoding: "utf8" });
|
|
16006
17332
|
return {
|
|
16007
17333
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
16008
17334
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -16013,11 +17339,11 @@ var repoRoot = (cwd) => {
|
|
|
16013
17339
|
const r = runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
16014
17340
|
return r.exitCode === 0 && r.stdout ? r.stdout : cwd;
|
|
16015
17341
|
};
|
|
16016
|
-
var
|
|
17342
|
+
var currentBranch2 = (cwd) => {
|
|
16017
17343
|
const r = runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
16018
17344
|
return r.exitCode === 0 && r.stdout ? r.stdout : "unknown";
|
|
16019
17345
|
};
|
|
16020
|
-
var
|
|
17346
|
+
var defaultBase2 = (cwd) => {
|
|
16021
17347
|
for (const ref of [
|
|
16022
17348
|
"origin/main",
|
|
16023
17349
|
"main",
|
|
@@ -16031,11 +17357,11 @@ var defaultBase = (cwd) => {
|
|
|
16031
17357
|
}
|
|
16032
17358
|
return "HEAD~1";
|
|
16033
17359
|
};
|
|
16034
|
-
var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${
|
|
17360
|
+
var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${defaultBase2(cwd)}...HEAD`;
|
|
16035
17361
|
var isProtectedBranch = (branch) => ["main", "master", "develop", "prod", "production"].includes(branch);
|
|
16036
17362
|
var isPrBranch = (branch) => branch.startsWith("feature/") || branch.startsWith("bugfix/");
|
|
16037
17363
|
function resolvePrBranchContext(cwd) {
|
|
16038
|
-
const branch =
|
|
17364
|
+
const branch = currentBranch2(cwd);
|
|
16039
17365
|
if (isProtectedBranch(branch)) {
|
|
16040
17366
|
return {
|
|
16041
17367
|
ok: false,
|
|
@@ -16109,7 +17435,7 @@ var findPrTemplate = (cwd) => {
|
|
|
16109
17435
|
]) {
|
|
16110
17436
|
const parts = rel.split("/");
|
|
16111
17437
|
const base = parts[parts.length - 1];
|
|
16112
|
-
const dir = parts.length > 1 ?
|
|
17438
|
+
const dir = parts.length > 1 ? path16.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
16113
17439
|
const actual = probeCaseInsensitive(dir, base);
|
|
16114
17440
|
if (actual)
|
|
16115
17441
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -16140,8 +17466,8 @@ var diffStatForRange = (cwd, range) => {
|
|
|
16140
17466
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
16141
17467
|
};
|
|
16142
17468
|
var packageScripts = (cwd, keys) => {
|
|
16143
|
-
const pkgPath =
|
|
16144
|
-
if (!
|
|
17469
|
+
const pkgPath = path16.join(cwd, "package.json");
|
|
17470
|
+
if (!existsSync6(pkgPath))
|
|
16145
17471
|
return [];
|
|
16146
17472
|
try {
|
|
16147
17473
|
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
@@ -16166,9 +17492,9 @@ var documentationFiles = (cwd) => {
|
|
|
16166
17492
|
if (entry.isDirectory()) {
|
|
16167
17493
|
if (excluded.has(entry.name))
|
|
16168
17494
|
continue;
|
|
16169
|
-
walk(
|
|
17495
|
+
walk(path16.join(dir, entry.name), depth + 1);
|
|
16170
17496
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
16171
|
-
matches.push("./" +
|
|
17497
|
+
matches.push("./" + path16.relative(cwd, path16.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
16172
17498
|
}
|
|
16173
17499
|
}
|
|
16174
17500
|
};
|
|
@@ -16176,7 +17502,7 @@ var documentationFiles = (cwd) => {
|
|
|
16176
17502
|
return matches.toSorted().slice(0, 200);
|
|
16177
17503
|
};
|
|
16178
17504
|
var readTrimmed2 = (file, maxLines) => {
|
|
16179
|
-
if (!
|
|
17505
|
+
if (!existsSync6(file))
|
|
16180
17506
|
return "";
|
|
16181
17507
|
const lines = readFileSync5(file, "utf8").split(`
|
|
16182
17508
|
`);
|
|
@@ -16184,7 +17510,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
16184
17510
|
`);
|
|
16185
17511
|
};
|
|
16186
17512
|
function prReadyContext(root, range) {
|
|
16187
|
-
const cwd =
|
|
17513
|
+
const cwd = path16.resolve(repoRoot(root));
|
|
16188
17514
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
16189
17515
|
return invalidRange(cwd);
|
|
16190
17516
|
let stdout = "";
|
|
@@ -16200,7 +17526,7 @@ function prReadyContext(root, range) {
|
|
|
16200
17526
|
stdout += printSection("Repository");
|
|
16201
17527
|
stdout += `root: ${cwd}
|
|
16202
17528
|
`;
|
|
16203
|
-
stdout += `branch: ${
|
|
17529
|
+
stdout += `branch: ${currentBranch2(cwd)}
|
|
16204
17530
|
`;
|
|
16205
17531
|
stdout += `range: ${range}
|
|
16206
17532
|
`;
|
|
@@ -16242,7 +17568,7 @@ ${status.stderr}` : "");
|
|
|
16242
17568
|
stdout += `template_path: ${template}
|
|
16243
17569
|
|
|
16244
17570
|
`;
|
|
16245
|
-
stdout += readTrimmed2(
|
|
17571
|
+
stdout += readTrimmed2(path16.join(cwd, template), 220);
|
|
16246
17572
|
} else {
|
|
16247
17573
|
stdout += `template_path: none
|
|
16248
17574
|
|
|
@@ -16252,7 +17578,7 @@ ${status.stderr}` : "");
|
|
|
16252
17578
|
stdout += `
|
|
16253
17579
|
`;
|
|
16254
17580
|
stdout += printSection("Recent Validation Signals");
|
|
16255
|
-
if (
|
|
17581
|
+
if (existsSync6(path16.join(cwd, "package.json"))) {
|
|
16256
17582
|
stdout += `package.json detected. Common scripts:
|
|
16257
17583
|
`;
|
|
16258
17584
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -16261,7 +17587,7 @@ ${status.stderr}` : "");
|
|
|
16261
17587
|
`) + `
|
|
16262
17588
|
`;
|
|
16263
17589
|
}
|
|
16264
|
-
if (
|
|
17590
|
+
if (existsSync6(path16.join(cwd, "Cargo.toml")) || existsSync6(path16.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
16265
17591
|
stdout += `Rust project detected.
|
|
16266
17592
|
`;
|
|
16267
17593
|
}
|
|
@@ -16294,7 +17620,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
16294
17620
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
16295
17621
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
16296
17622
|
function changelogContext(root, range) {
|
|
16297
|
-
const cwd =
|
|
17623
|
+
const cwd = path16.resolve(repoRoot(root));
|
|
16298
17624
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
16299
17625
|
return invalidRange(cwd);
|
|
16300
17626
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -16304,7 +17630,7 @@ function changelogContext(root, range) {
|
|
|
16304
17630
|
stdout += printSection("Repository");
|
|
16305
17631
|
stdout += `root: ${cwd}
|
|
16306
17632
|
`;
|
|
16307
|
-
stdout += `branch: ${
|
|
17633
|
+
stdout += `branch: ${currentBranch2(cwd)}
|
|
16308
17634
|
`;
|
|
16309
17635
|
stdout += `range: ${resolvedRange}
|
|
16310
17636
|
`;
|
|
@@ -16312,8 +17638,8 @@ function changelogContext(root, range) {
|
|
|
16312
17638
|
stdout += CHANGELOG_RULES + `
|
|
16313
17639
|
`;
|
|
16314
17640
|
stdout += printSection("Existing CHANGELOG.md");
|
|
16315
|
-
const changelogPath =
|
|
16316
|
-
if (
|
|
17641
|
+
const changelogPath = path16.join(cwd, "CHANGELOG.md");
|
|
17642
|
+
if (existsSync6(changelogPath)) {
|
|
16317
17643
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
16318
17644
|
`;
|
|
16319
17645
|
} else {
|
|
@@ -16332,7 +17658,7 @@ function changelogContext(root, range) {
|
|
|
16332
17658
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
16333
17659
|
}
|
|
16334
17660
|
function docsRefreshContext(root, range) {
|
|
16335
|
-
const cwd =
|
|
17661
|
+
const cwd = path16.resolve(repoRoot(root));
|
|
16336
17662
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
16337
17663
|
return invalidRange(cwd);
|
|
16338
17664
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -16342,7 +17668,7 @@ function docsRefreshContext(root, range) {
|
|
|
16342
17668
|
stdout += printSection("Repository");
|
|
16343
17669
|
stdout += `root: ${cwd}
|
|
16344
17670
|
`;
|
|
16345
|
-
stdout += `branch: ${
|
|
17671
|
+
stdout += `branch: ${currentBranch2(cwd)}
|
|
16346
17672
|
`;
|
|
16347
17673
|
stdout += `range: ${resolvedRange}
|
|
16348
17674
|
`;
|
|
@@ -16354,8 +17680,8 @@ function docsRefreshContext(root, range) {
|
|
|
16354
17680
|
`) + `
|
|
16355
17681
|
`;
|
|
16356
17682
|
stdout += printSection("README Preview");
|
|
16357
|
-
const readme =
|
|
16358
|
-
if (
|
|
17683
|
+
const readme = path16.join(cwd, "README.md");
|
|
17684
|
+
if (existsSync6(readme)) {
|
|
16359
17685
|
stdout += readTrimmed2(readme, 220) + `
|
|
16360
17686
|
`;
|
|
16361
17687
|
} else {
|
|
@@ -16363,8 +17689,8 @@ function docsRefreshContext(root, range) {
|
|
|
16363
17689
|
`;
|
|
16364
17690
|
}
|
|
16365
17691
|
stdout += printSection("Package Scripts");
|
|
16366
|
-
const pkgPath =
|
|
16367
|
-
if (
|
|
17692
|
+
const pkgPath = path16.join(cwd, "package.json");
|
|
17693
|
+
if (existsSync6(pkgPath)) {
|
|
16368
17694
|
try {
|
|
16369
17695
|
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
16370
17696
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
@@ -16377,7 +17703,7 @@ function docsRefreshContext(root, range) {
|
|
|
16377
17703
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
16378
17704
|
}
|
|
16379
17705
|
function releaseNotesContext(root, rangeOrTag) {
|
|
16380
|
-
const cwd =
|
|
17706
|
+
const cwd = path16.resolve(repoRoot(root));
|
|
16381
17707
|
if (!rangeOrTag) {
|
|
16382
17708
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
16383
17709
|
`, exitCode: 1, cwd };
|
|
@@ -16391,7 +17717,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
16391
17717
|
stdout += printSection("Repository");
|
|
16392
17718
|
stdout += `root: ${cwd}
|
|
16393
17719
|
`;
|
|
16394
|
-
stdout += `branch: ${
|
|
17720
|
+
stdout += `branch: ${currentBranch2(cwd)}
|
|
16395
17721
|
`;
|
|
16396
17722
|
stdout += `requested: ${rangeOrTag}
|
|
16397
17723
|
`;
|
|
@@ -16415,7 +17741,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
16415
17741
|
`;
|
|
16416
17742
|
stdout += printSection("Existing Release Files");
|
|
16417
17743
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
16418
|
-
if (
|
|
17744
|
+
if (existsSync6(path16.join(cwd, rel)))
|
|
16419
17745
|
stdout += rel + `
|
|
16420
17746
|
`;
|
|
16421
17747
|
}
|
|
@@ -16443,12 +17769,12 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
16443
17769
|
}
|
|
16444
17770
|
|
|
16445
17771
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
16446
|
-
import { spawnSync as
|
|
17772
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
16447
17773
|
|
|
16448
17774
|
// packages/workit-core/src/core/pr-create.ts
|
|
16449
|
-
import
|
|
16450
|
-
import
|
|
16451
|
-
import { spawnSync as
|
|
17775
|
+
import fs13 from "node:fs";
|
|
17776
|
+
import path17 from "node:path";
|
|
17777
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
16452
17778
|
// packages/workit-core/src/forge/pr-body.ts
|
|
16453
17779
|
function parseGhIssue(value) {
|
|
16454
17780
|
const m = /issues\/(\d+)/.exec(value);
|
|
@@ -16458,13 +17784,13 @@ function parseGhIssue(value) {
|
|
|
16458
17784
|
// packages/workit-core/src/core/pr-create.ts
|
|
16459
17785
|
function whichOnPath(tool) {
|
|
16460
17786
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
16461
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
17787
|
+
for (const dir of (process.env.PATH ?? "").split(path17.delimiter)) {
|
|
16462
17788
|
if (!dir)
|
|
16463
17789
|
continue;
|
|
16464
17790
|
for (const name of names) {
|
|
16465
|
-
const candidate =
|
|
17791
|
+
const candidate = path17.join(dir, name);
|
|
16466
17792
|
try {
|
|
16467
|
-
|
|
17793
|
+
fs13.accessSync(candidate, fs13.constants.X_OK);
|
|
16468
17794
|
return candidate;
|
|
16469
17795
|
} catch {}
|
|
16470
17796
|
}
|
|
@@ -16473,7 +17799,7 @@ function whichOnPath(tool) {
|
|
|
16473
17799
|
}
|
|
16474
17800
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
16475
17801
|
var pushRemote = (cwd) => {
|
|
16476
|
-
const result =
|
|
17802
|
+
const result = spawnSync10("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
16477
17803
|
cwd,
|
|
16478
17804
|
encoding: "utf8"
|
|
16479
17805
|
});
|
|
@@ -16504,7 +17830,7 @@ var defaultCli = (bin, available) => {
|
|
|
16504
17830
|
if (!available)
|
|
16505
17831
|
return null;
|
|
16506
17832
|
return (args, root) => {
|
|
16507
|
-
const out =
|
|
17833
|
+
const out = spawnSync11(bin, args, {
|
|
16508
17834
|
cwd: root,
|
|
16509
17835
|
encoding: "utf8",
|
|
16510
17836
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -16582,8 +17908,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
16582
17908
|
};
|
|
16583
17909
|
|
|
16584
17910
|
// packages/workit-core/src/core/youtrack.ts
|
|
16585
|
-
import
|
|
16586
|
-
import
|
|
17911
|
+
import fs14 from "node:fs";
|
|
17912
|
+
import path18 from "node:path";
|
|
16587
17913
|
|
|
16588
17914
|
// packages/workit-core/src/core/templates.ts
|
|
16589
17915
|
var repoRoot2 = assetRoot();
|
|
@@ -16596,22 +17922,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
16596
17922
|
// packages/workit-core/src/core/youtrack.ts
|
|
16597
17923
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
16598
17924
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
16599
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
16600
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
17925
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(configDir(), "youtrack.json");
|
|
17926
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(resolveConfigDir(), "youtrack.json");
|
|
16601
17927
|
var youTrackTokenModeOk = (p) => {
|
|
16602
17928
|
if (process.platform === "win32")
|
|
16603
17929
|
return true;
|
|
16604
|
-
const mode =
|
|
17930
|
+
const mode = fs14.statSync(p).mode & 511;
|
|
16605
17931
|
return mode === 384;
|
|
16606
17932
|
};
|
|
16607
17933
|
function readYouTrackConfig(required) {
|
|
16608
17934
|
const cfgPath = youTrackConfigPath();
|
|
16609
|
-
if (!
|
|
17935
|
+
if (!fs14.existsSync(cfgPath)) {
|
|
16610
17936
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
16611
17937
|
}
|
|
16612
17938
|
let parsed;
|
|
16613
17939
|
try {
|
|
16614
|
-
parsed = JSON.parse(
|
|
17940
|
+
parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
|
|
16615
17941
|
} catch {
|
|
16616
17942
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
16617
17943
|
}
|
|
@@ -16622,11 +17948,11 @@ function readYouTrackConfig(required) {
|
|
|
16622
17948
|
}
|
|
16623
17949
|
var readYouTrackContextConfig = () => {
|
|
16624
17950
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
16625
|
-
if (!
|
|
17951
|
+
if (!fs14.existsSync(cfgPath))
|
|
16626
17952
|
return { data: {} };
|
|
16627
17953
|
let parsed;
|
|
16628
17954
|
try {
|
|
16629
|
-
parsed = JSON.parse(
|
|
17955
|
+
parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
|
|
16630
17956
|
} catch {
|
|
16631
17957
|
return { error: "YouTrack configuration is unavailable" };
|
|
16632
17958
|
}
|
|
@@ -16665,12 +17991,12 @@ var youTrackToken = () => {
|
|
|
16665
17991
|
if ("error" in loaded)
|
|
16666
17992
|
return loaded;
|
|
16667
17993
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
16668
|
-
const tokenPath = tokenFile ?
|
|
16669
|
-
if (!tokenPath || !
|
|
17994
|
+
const tokenPath = tokenFile ? path18.isAbsolute(tokenFile) ? path18.resolve(tokenFile) : path18.resolve(process.cwd(), tokenFile) : "";
|
|
17995
|
+
if (!tokenPath || !fs14.existsSync(tokenPath))
|
|
16670
17996
|
return { error: "missing youtrack.token" };
|
|
16671
17997
|
if (!youTrackTokenModeOk(tokenPath))
|
|
16672
17998
|
return { error: "youtrack.token mode must be 0600" };
|
|
16673
|
-
const token =
|
|
17999
|
+
const token = fs14.readFileSync(tokenPath, "utf8").trim();
|
|
16674
18000
|
if (!token)
|
|
16675
18001
|
return { error: "empty token file" };
|
|
16676
18002
|
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
@@ -16716,7 +18042,7 @@ function youTrackTokenCreateUrl() {
|
|
|
16716
18042
|
const desc = String(defaults.description ?? "OpenCode workit — /wk-issue-update and /wk-meetings");
|
|
16717
18043
|
const scopes = Array.isArray(defaults.scopes) ? defaults.scopes : ["YouTrack"];
|
|
16718
18044
|
const base = String(config.baseUrl ?? "").replace(/\/+$/, "");
|
|
16719
|
-
const tokenFile = String(config.tokenFile ??
|
|
18045
|
+
const tokenFile = String(config.tokenFile ?? path18.join(path18.dirname(loaded.path), "youtrack.token"));
|
|
16720
18046
|
const tab = String(defaults.profileTab ?? "account-security");
|
|
16721
18047
|
const createUrl = base ? `${base}/users/me?${new URLSearchParams({ tab })}` : null;
|
|
16722
18048
|
const docsUrl = "https://www.jetbrains.com/help/youtrack/cloud/manage-permanent-token.html";
|
|
@@ -16725,7 +18051,7 @@ function youTrackTokenCreateUrl() {
|
|
|
16725
18051
|
tokenName: name,
|
|
16726
18052
|
tokenDescription: desc,
|
|
16727
18053
|
scopes,
|
|
16728
|
-
tokenFile:
|
|
18054
|
+
tokenFile: path18.resolve(tokenFile),
|
|
16729
18055
|
createUrl,
|
|
16730
18056
|
docsUrl,
|
|
16731
18057
|
prefillSupported: false,
|
|
@@ -16771,9 +18097,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
16771
18097
|
} catch {
|
|
16772
18098
|
continue;
|
|
16773
18099
|
}
|
|
16774
|
-
if (!
|
|
18100
|
+
if (!fs14.existsSync(full))
|
|
16775
18101
|
continue;
|
|
16776
|
-
const text =
|
|
18102
|
+
const text = fs14.readFileSync(full, "utf8");
|
|
16777
18103
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
16778
18104
|
if (m)
|
|
16779
18105
|
return m[1];
|
|
@@ -17065,22 +18391,22 @@ function resolveGitRevision(root, value) {
|
|
|
17065
18391
|
}
|
|
17066
18392
|
function resolveInside(root, candidate) {
|
|
17067
18393
|
const base = realpathSync5(root);
|
|
17068
|
-
const target =
|
|
17069
|
-
if (target !== base && !target.startsWith(base +
|
|
18394
|
+
const target = path19.resolve(base, candidate);
|
|
18395
|
+
if (target !== base && !target.startsWith(base + path19.sep)) {
|
|
17070
18396
|
throw new Error("path must stay inside repository root");
|
|
17071
18397
|
}
|
|
17072
18398
|
let ancestor = target;
|
|
17073
|
-
while (!
|
|
17074
|
-
ancestor =
|
|
18399
|
+
while (!existsSync7(ancestor))
|
|
18400
|
+
ancestor = path19.dirname(ancestor);
|
|
17075
18401
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
17076
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
18402
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path19.sep)) {
|
|
17077
18403
|
throw new Error("path must stay inside repository root");
|
|
17078
18404
|
}
|
|
17079
18405
|
return target;
|
|
17080
18406
|
}
|
|
17081
18407
|
function run2(root, executable, args, env = {}) {
|
|
17082
18408
|
const cwd = realpathSync5(root);
|
|
17083
|
-
const result =
|
|
18409
|
+
const result = spawnSync12(executable, args, {
|
|
17084
18410
|
cwd,
|
|
17085
18411
|
encoding: "utf8",
|
|
17086
18412
|
env: { ...process.env, ...env }
|
|
@@ -17117,7 +18443,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
17117
18443
|
};
|
|
17118
18444
|
});
|
|
17119
18445
|
// packages/workit-core/src/hooks/context.ts
|
|
17120
|
-
import
|
|
18446
|
+
import path20 from "node:path";
|
|
17121
18447
|
var MIGRATION_PENDING_NOTE = "workit migration pending — run `workit task status`";
|
|
17122
18448
|
var migrationPending = (result) => !result.ok && result.code === "needs_input" && (result.error ?? "").startsWith(MIGRATION_PENDING);
|
|
17123
18449
|
var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
|
|
@@ -17196,7 +18522,7 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
17196
18522
|
return null;
|
|
17197
18523
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
17198
18524
|
const observed = task.data.evidence.some(isObservedCheck);
|
|
17199
|
-
const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [
|
|
18525
|
+
const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path20.resolve(store.root, ref.path)] : []));
|
|
17200
18526
|
const observedFiles = filesKey(files);
|
|
17201
18527
|
const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
|
|
17202
18528
|
if (!compact.ok)
|
|
@@ -17218,12 +18544,291 @@ var shellPolicy = (cwd, command) => {
|
|
|
17218
18544
|
const policy = shellBranchPolicyViolation(cwd, command);
|
|
17219
18545
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
17220
18546
|
};
|
|
18547
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
18548
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
18549
|
+
import path21 from "node:path";
|
|
18550
|
+
var NONE2 = { kind: "none" };
|
|
18551
|
+
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
18552
|
+
var canonical2 = (file) => {
|
|
18553
|
+
let rest = "";
|
|
18554
|
+
for (let dir = path21.resolve(file);; dir = path21.dirname(dir)) {
|
|
18555
|
+
try {
|
|
18556
|
+
return path21.join(realpathSync6(dir), rest);
|
|
18557
|
+
} catch {
|
|
18558
|
+
if (path21.dirname(dir) === dir)
|
|
18559
|
+
return path21.resolve(file);
|
|
18560
|
+
rest = rest ? path21.join(path21.basename(dir), rest) : path21.basename(dir);
|
|
18561
|
+
}
|
|
18562
|
+
}
|
|
18563
|
+
};
|
|
18564
|
+
var exempt = (cwd, root, file, plans) => {
|
|
18565
|
+
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
18566
|
+
const relative = path21.relative(fold(canonical2(root)), fold(canonical2(path21.resolve(cwd, file))));
|
|
18567
|
+
if (relative.startsWith("..") || path21.isAbsolute(relative))
|
|
18568
|
+
return true;
|
|
18569
|
+
const posix = relative.split(path21.sep).join("/");
|
|
18570
|
+
const segments = posix.split("/");
|
|
18571
|
+
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
18572
|
+
};
|
|
18573
|
+
var segmentsOf = (command) => {
|
|
18574
|
+
const segments = [];
|
|
18575
|
+
let current = { words: [], redirects: [] };
|
|
18576
|
+
let word = "";
|
|
18577
|
+
let inWord = false;
|
|
18578
|
+
let pending = null;
|
|
18579
|
+
const heredocs = [];
|
|
18580
|
+
const flushWord = () => {
|
|
18581
|
+
if (!inWord)
|
|
18582
|
+
return;
|
|
18583
|
+
if (pending === "redirect") {
|
|
18584
|
+
if (!word.startsWith("&"))
|
|
18585
|
+
current.redirects.push(word);
|
|
18586
|
+
} else if (pending === "heredoc")
|
|
18587
|
+
heredocs.push(word.replace(/^-/, ""));
|
|
18588
|
+
else if (pending !== "input")
|
|
18589
|
+
current.words.push(word);
|
|
18590
|
+
pending = null;
|
|
18591
|
+
word = "";
|
|
18592
|
+
inWord = false;
|
|
18593
|
+
};
|
|
18594
|
+
const flushSegment = () => {
|
|
18595
|
+
flushWord();
|
|
18596
|
+
if (current.words.length || current.redirects.length)
|
|
18597
|
+
segments.push(current);
|
|
18598
|
+
current = { words: [], redirects: [] };
|
|
18599
|
+
};
|
|
18600
|
+
for (let index = 0;index < command.length; index++) {
|
|
18601
|
+
const char = command[index];
|
|
18602
|
+
if (char === "'" || char === '"') {
|
|
18603
|
+
const end = command.indexOf(char, index + 1);
|
|
18604
|
+
word += command.slice(index + 1, end < 0 ? undefined : end);
|
|
18605
|
+
inWord = true;
|
|
18606
|
+
index = end < 0 ? command.length : end;
|
|
18607
|
+
} else if (char === "\\" && index + 1 < command.length) {
|
|
18608
|
+
word += command[++index];
|
|
18609
|
+
inWord = true;
|
|
18610
|
+
} else if (char === `
|
|
18611
|
+
`) {
|
|
18612
|
+
flushSegment();
|
|
18613
|
+
while (heredocs.length) {
|
|
18614
|
+
const delimiter = heredocs.shift();
|
|
18615
|
+
const lines = command.slice(index + 1).split(`
|
|
18616
|
+
`);
|
|
18617
|
+
const at = lines.findIndex((line) => line.trim() === delimiter);
|
|
18618
|
+
const skipped = (at < 0 ? lines : lines.slice(0, at + 1)).join(`
|
|
18619
|
+
`).length + 1;
|
|
18620
|
+
index += skipped;
|
|
18621
|
+
}
|
|
18622
|
+
} else if (/\s/.test(char))
|
|
18623
|
+
flushWord();
|
|
18624
|
+
else if (";&|()".includes(char)) {
|
|
18625
|
+
if (char === "&" && command[index + 1] === ">")
|
|
18626
|
+
continue;
|
|
18627
|
+
flushSegment();
|
|
18628
|
+
} else if (char === ">") {
|
|
18629
|
+
if (inWord && /^\d$/.test(word)) {
|
|
18630
|
+
word = "";
|
|
18631
|
+
inWord = false;
|
|
18632
|
+
} else
|
|
18633
|
+
flushWord();
|
|
18634
|
+
if (command[index + 1] === ">")
|
|
18635
|
+
index++;
|
|
18636
|
+
if (command[index + 1] === "&") {
|
|
18637
|
+
index++;
|
|
18638
|
+
word = "&";
|
|
18639
|
+
inWord = true;
|
|
18640
|
+
}
|
|
18641
|
+
pending = "redirect";
|
|
18642
|
+
} else if (char === "<") {
|
|
18643
|
+
flushWord();
|
|
18644
|
+
if (command[index + 1] === "<") {
|
|
18645
|
+
index++;
|
|
18646
|
+
if (command[index + 1] === "<")
|
|
18647
|
+
index++;
|
|
18648
|
+
pending = "heredoc";
|
|
18649
|
+
} else
|
|
18650
|
+
pending = "input";
|
|
18651
|
+
} else {
|
|
18652
|
+
word += char;
|
|
18653
|
+
inWord = true;
|
|
18654
|
+
}
|
|
18655
|
+
}
|
|
18656
|
+
flushSegment();
|
|
18657
|
+
return segments;
|
|
18658
|
+
};
|
|
18659
|
+
var PREFIXES = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
18660
|
+
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
18661
|
+
var editsOf = (input) => {
|
|
18662
|
+
let words = input;
|
|
18663
|
+
while (words.length && (PREFIXES.has(words[0]) || /^\w+=/.test(words[0])))
|
|
18664
|
+
words = words.slice(1);
|
|
18665
|
+
const [verb, ...args] = words;
|
|
18666
|
+
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
18667
|
+
if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
|
|
18668
|
+
return [];
|
|
18669
|
+
switch (name) {
|
|
18670
|
+
case "touch":
|
|
18671
|
+
case "mkdir":
|
|
18672
|
+
case "rm":
|
|
18673
|
+
case "rmdir":
|
|
18674
|
+
case "tee":
|
|
18675
|
+
case "mv":
|
|
18676
|
+
case "truncate":
|
|
18677
|
+
return operands(args);
|
|
18678
|
+
case "cp":
|
|
18679
|
+
case "ln":
|
|
18680
|
+
case "install": {
|
|
18681
|
+
const target = args.indexOf("-t");
|
|
18682
|
+
if (target >= 0)
|
|
18683
|
+
return args[target + 1] ? [args[target + 1]] : null;
|
|
18684
|
+
const last = operands(args).at(-1);
|
|
18685
|
+
return last ? [last] : null;
|
|
18686
|
+
}
|
|
18687
|
+
case "dd":
|
|
18688
|
+
return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
|
|
18689
|
+
case "sed":
|
|
18690
|
+
case "perl": {
|
|
18691
|
+
if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
|
|
18692
|
+
return [];
|
|
18693
|
+
const scripted = args.some((item) => item === "-e" || item === "-f");
|
|
18694
|
+
const files = [];
|
|
18695
|
+
for (let index = 0;index < args.length; index++) {
|
|
18696
|
+
if (args[index] === "-e" || args[index] === "-f")
|
|
18697
|
+
index++;
|
|
18698
|
+
else if (!args[index].startsWith("-"))
|
|
18699
|
+
files.push(args[index]);
|
|
18700
|
+
}
|
|
18701
|
+
return scripted ? files : files.slice(1);
|
|
18702
|
+
}
|
|
18703
|
+
case "patch":
|
|
18704
|
+
return null;
|
|
18705
|
+
case "set-content":
|
|
18706
|
+
case "add-content":
|
|
18707
|
+
case "out-file":
|
|
18708
|
+
case "new-item":
|
|
18709
|
+
case "ni":
|
|
18710
|
+
case "remove-item":
|
|
18711
|
+
case "del":
|
|
18712
|
+
case "copy-item":
|
|
18713
|
+
case "move-item": {
|
|
18714
|
+
const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
|
|
18715
|
+
if (flag >= 0)
|
|
18716
|
+
return args[flag + 1] ? [args[flag + 1]] : null;
|
|
18717
|
+
const files = operands(args);
|
|
18718
|
+
if (name === "move-item")
|
|
18719
|
+
return files.length ? files : null;
|
|
18720
|
+
const file = name === "copy-item" ? files.at(-1) : files[0];
|
|
18721
|
+
return file ? [file] : null;
|
|
18722
|
+
}
|
|
18723
|
+
case "git": {
|
|
18724
|
+
const sub = operands(args)[0];
|
|
18725
|
+
if (sub === "apply")
|
|
18726
|
+
return null;
|
|
18727
|
+
if (sub === "restore" || sub === "mv" || sub === "rm")
|
|
18728
|
+
return operands(args).slice(1);
|
|
18729
|
+
if (sub === "checkout" && args.includes("--"))
|
|
18730
|
+
return args.slice(args.indexOf("--") + 1);
|
|
18731
|
+
return [];
|
|
18732
|
+
}
|
|
18733
|
+
default:
|
|
18734
|
+
return [];
|
|
18735
|
+
}
|
|
18736
|
+
};
|
|
18737
|
+
var shellWrites = (command) => {
|
|
18738
|
+
const targets = [];
|
|
18739
|
+
let unknown = false;
|
|
18740
|
+
for (const segment of segmentsOf(command)) {
|
|
18741
|
+
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
18742
|
+
const edits = editsOf(segment.words);
|
|
18743
|
+
if (edits === null)
|
|
18744
|
+
unknown = true;
|
|
18745
|
+
else
|
|
18746
|
+
targets.push(...edits);
|
|
18747
|
+
}
|
|
18748
|
+
if (!unknown && targets.length === 0)
|
|
18749
|
+
return { writes: false, targets: null };
|
|
18750
|
+
return { writes: true, targets: unknown ? null : targets };
|
|
18751
|
+
};
|
|
18752
|
+
var cache2 = new Map;
|
|
18753
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path21.resolve(root, file)) ?? "-").join("\x00");
|
|
18754
|
+
var gateFor = (cwd) => {
|
|
18755
|
+
const store = new TaskStore(cwd);
|
|
18756
|
+
const listed = store.listTaskIndex();
|
|
18757
|
+
if (!listed.ok)
|
|
18758
|
+
return null;
|
|
18759
|
+
const key = store.currentKey();
|
|
18760
|
+
if (!key.ok)
|
|
18761
|
+
return null;
|
|
18762
|
+
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
18763
|
+
if (!entry)
|
|
18764
|
+
return { blockers: [], root: store.root, plans: new Set };
|
|
18765
|
+
const ledger = ledgerPath(store.root);
|
|
18766
|
+
const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
|
|
18767
|
+
const hit = cache2.get(store.root);
|
|
18768
|
+
if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
|
|
18769
|
+
return hit;
|
|
18770
|
+
const task = store.readTask(entry.id);
|
|
18771
|
+
if (!task.ok)
|
|
18772
|
+
return null;
|
|
18773
|
+
const workspace = store.readWorkspace();
|
|
18774
|
+
const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
|
|
18775
|
+
const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
|
|
18776
|
+
const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
|
|
18777
|
+
return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
|
|
18778
|
+
});
|
|
18779
|
+
const gate = {
|
|
18780
|
+
blockers,
|
|
18781
|
+
root: store.root,
|
|
18782
|
+
plans,
|
|
18783
|
+
key: cacheKey,
|
|
18784
|
+
planKey: planKeyOf(store.root, plans)
|
|
18785
|
+
};
|
|
18786
|
+
cache2.set(store.root, gate);
|
|
18787
|
+
if (cache2.size > 32)
|
|
18788
|
+
cache2.delete(cache2.keys().next().value);
|
|
18789
|
+
return gate;
|
|
18790
|
+
};
|
|
18791
|
+
function writeGate(cwd, paths) {
|
|
18792
|
+
try {
|
|
18793
|
+
const gate = gateFor(cwd);
|
|
18794
|
+
if (!gate || gate.blockers.length === 0)
|
|
18795
|
+
return NONE2;
|
|
18796
|
+
if (paths && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
|
|
18797
|
+
return NONE2;
|
|
18798
|
+
const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
|
|
18799
|
+
return {
|
|
18800
|
+
kind: "deny",
|
|
18801
|
+
reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
|
|
18802
|
+
unblock: gate.blockers[0].reason
|
|
18803
|
+
};
|
|
18804
|
+
} catch {
|
|
18805
|
+
return NONE2;
|
|
18806
|
+
}
|
|
18807
|
+
}
|
|
18808
|
+
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
18809
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
18810
|
+
var WRITE_TOOLS = new Set([
|
|
18811
|
+
"write",
|
|
18812
|
+
"edit",
|
|
18813
|
+
"multiedit",
|
|
18814
|
+
"notebookedit",
|
|
18815
|
+
"applypatch",
|
|
18816
|
+
"patch",
|
|
18817
|
+
"delete",
|
|
18818
|
+
"remove",
|
|
18819
|
+
"rename",
|
|
18820
|
+
"mkdir",
|
|
18821
|
+
"mv",
|
|
18822
|
+
"cp",
|
|
18823
|
+
"touch",
|
|
18824
|
+
"strreplace",
|
|
18825
|
+
"searchreplace",
|
|
18826
|
+
"editfile",
|
|
18827
|
+
"writefile"
|
|
18828
|
+
]);
|
|
17221
18829
|
// packages/workit-core/src/hooks/handle.ts
|
|
17222
18830
|
var offered = new Set;
|
|
17223
18831
|
var CLAUDE_JUDGES = new Set(["workit:verifier", "workit:reviewer"]);
|
|
17224
|
-
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
17225
|
-
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
17226
|
-
|
|
17227
18832
|
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
17228
18833
|
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
17229
18834
|
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
@@ -17250,6 +18855,7 @@ var OPENCODE_DESCRIPTOR = {
|
|
|
17250
18855
|
"context.turn": { support: "native", native: 'session.hook("context")' },
|
|
17251
18856
|
"shell.pre": { support: "native", native: 'permission.hook("evaluate")' },
|
|
17252
18857
|
"tool.pre": { support: "native", native: 'tool.hook("execute.before")' },
|
|
18858
|
+
"write.pre": { support: "native", native: 'permission.hook("evaluate")' },
|
|
17253
18859
|
"shell.post": { support: "native", native: 'tool.hook("execute.after")' },
|
|
17254
18860
|
"subagent.start": { support: "native", native: 'tool.hook("execute.before") subagent' },
|
|
17255
18861
|
"subagent.stop": { support: "native", native: 'tool.hook("execute.after") subagent' },
|
|
@@ -17304,11 +18910,11 @@ var OPENCODE_DESCRIPTOR = {
|
|
|
17304
18910
|
]
|
|
17305
18911
|
};
|
|
17306
18912
|
// packages/workit-opencode/src/shared/init-apply.ts
|
|
17307
|
-
import
|
|
18913
|
+
import path26 from "node:path";
|
|
17308
18914
|
|
|
17309
18915
|
// packages/workit-core/src/core/gitignore.ts
|
|
17310
|
-
import { existsSync as
|
|
17311
|
-
import
|
|
18916
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
18917
|
+
import path22 from "node:path";
|
|
17312
18918
|
var GITIGNORE_ENTRIES = [
|
|
17313
18919
|
"# workit: SDD working state (never commit)",
|
|
17314
18920
|
"docs/*/sdd/",
|
|
@@ -17328,8 +18934,8 @@ var GITIGNORE_ENTRIES = [
|
|
|
17328
18934
|
var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
17329
18935
|
if (!confirmed)
|
|
17330
18936
|
return { ok: false, error: "confirmed: true required" };
|
|
17331
|
-
const file =
|
|
17332
|
-
const existing =
|
|
18937
|
+
const file = path22.join(workspaceRoot, ".gitignore");
|
|
18938
|
+
const existing = existsSync8(file) ? readFileSync6(file, "utf8") : "";
|
|
17333
18939
|
const existingLines = new Set(existing.split(`
|
|
17334
18940
|
`).map((l) => l.trim()).filter(Boolean));
|
|
17335
18941
|
const added = [];
|
|
@@ -17344,37 +18950,37 @@ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
|
|
|
17344
18950
|
const separator = existing && !existing.endsWith(`
|
|
17345
18951
|
`) ? `
|
|
17346
18952
|
` : "";
|
|
17347
|
-
|
|
18953
|
+
writeFileSync4(file, existing + separator + (existing ? `
|
|
17348
18954
|
` : "") + append.join(`
|
|
17349
18955
|
`) + `
|
|
17350
18956
|
`, "utf8");
|
|
17351
|
-
} else if (!
|
|
17352
|
-
|
|
18957
|
+
} else if (!existsSync8(file)) {
|
|
18958
|
+
writeFileSync4(file, "", "utf8");
|
|
17353
18959
|
}
|
|
17354
18960
|
return { ok: true, path: file, added };
|
|
17355
18961
|
};
|
|
17356
18962
|
|
|
17357
18963
|
// packages/workit-core/src/core/hygiene.ts
|
|
17358
|
-
import { existsSync as
|
|
17359
|
-
import
|
|
18964
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
18965
|
+
import path23 from "node:path";
|
|
17360
18966
|
var repoRoot3 = assetRoot();
|
|
17361
|
-
var templatesDir = () =>
|
|
18967
|
+
var templatesDir = () => path23.join(repoRoot3, "templates", "hygiene");
|
|
17362
18968
|
var packageJson = (root) => {
|
|
17363
|
-
if (!
|
|
18969
|
+
if (!existsSync9(path23.join(root, "package.json")))
|
|
17364
18970
|
return null;
|
|
17365
18971
|
try {
|
|
17366
|
-
return JSON.parse(readFileSync7(
|
|
18972
|
+
return JSON.parse(readFileSync7(path23.join(root, "package.json"), "utf8"));
|
|
17367
18973
|
} catch {
|
|
17368
18974
|
return null;
|
|
17369
18975
|
}
|
|
17370
18976
|
};
|
|
17371
18977
|
var isOpenSource = (root) => {
|
|
17372
|
-
if (
|
|
18978
|
+
if (existsSync9(path23.join(root, "LICENSE")))
|
|
17373
18979
|
return true;
|
|
17374
18980
|
const pkg = packageJson(root);
|
|
17375
18981
|
if (pkg && !pkg.private)
|
|
17376
18982
|
return true;
|
|
17377
|
-
return
|
|
18983
|
+
return path23.basename(root) === "workflow-toolkit";
|
|
17378
18984
|
};
|
|
17379
18985
|
var licenseHolder = (root) => {
|
|
17380
18986
|
const pkg = packageJson(root);
|
|
@@ -17397,13 +19003,13 @@ var planHygieneFiles = (root, opts = {}) => {
|
|
|
17397
19003
|
files.push("LICENSE", "CONTRIBUTING.md");
|
|
17398
19004
|
const planned = [];
|
|
17399
19005
|
for (const file of files) {
|
|
17400
|
-
if (
|
|
19006
|
+
if (existsSync9(path23.join(root, file)))
|
|
17401
19007
|
continue;
|
|
17402
|
-
const tpl =
|
|
17403
|
-
if (!
|
|
19008
|
+
const tpl = path23.join(templatesDir(), file);
|
|
19009
|
+
if (!existsSync9(tpl))
|
|
17404
19010
|
continue;
|
|
17405
|
-
const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g,
|
|
17406
|
-
planned.push({ path:
|
|
19011
|
+
const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g, path23.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
|
|
19012
|
+
planned.push({ path: path23.join(root, file), content });
|
|
17407
19013
|
}
|
|
17408
19014
|
return planned;
|
|
17409
19015
|
};
|
|
@@ -17412,48 +19018,25 @@ var ensureHygieneFiles = (root, opts) => {
|
|
|
17412
19018
|
return { ok: false, error: "confirmed: true required" };
|
|
17413
19019
|
const created = [];
|
|
17414
19020
|
for (const planned of planHygieneFiles(root, opts)) {
|
|
17415
|
-
|
|
17416
|
-
created.push(
|
|
19021
|
+
writeFileSync5(planned.path, planned.content, "utf8");
|
|
19022
|
+
created.push(path23.basename(planned.path));
|
|
17417
19023
|
}
|
|
17418
19024
|
return { ok: true, created };
|
|
17419
19025
|
};
|
|
17420
19026
|
|
|
17421
19027
|
// packages/workit-core/src/core/init.ts
|
|
17422
|
-
import
|
|
17423
|
-
import
|
|
17424
|
-
import { spawnSync as
|
|
17425
|
-
|
|
17426
|
-
// packages/workit-core/src/core/safe-write.ts
|
|
17427
|
-
import {
|
|
17428
|
-
closeSync as closeSync2,
|
|
17429
|
-
fchmodSync,
|
|
17430
|
-
fsyncSync,
|
|
17431
|
-
lstatSync as lstatSync3,
|
|
17432
|
-
mkdirSync as mkdirSync3,
|
|
17433
|
-
openSync as openSync2,
|
|
17434
|
-
renameSync as renameSync2,
|
|
17435
|
-
unlinkSync,
|
|
17436
|
-
writeFileSync as writeFileSync5
|
|
17437
|
-
} from "node:fs";
|
|
17438
|
-
function writeFileExclusive(file, content, mode) {
|
|
17439
|
-
try {
|
|
17440
|
-
writeFileSync5(file, content, { encoding: "utf8", flag: "wx", mode });
|
|
17441
|
-
return "created";
|
|
17442
|
-
} catch (err) {
|
|
17443
|
-
if (err.code === "EEXIST")
|
|
17444
|
-
return "preserved";
|
|
17445
|
-
throw err;
|
|
17446
|
-
}
|
|
17447
|
-
}
|
|
19028
|
+
import fs15 from "node:fs";
|
|
19029
|
+
import path25 from "node:path";
|
|
19030
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
17448
19031
|
|
|
17449
19032
|
// packages/workit-core/src/core/workspace-branch-policy.ts
|
|
17450
19033
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
17451
|
-
import
|
|
19034
|
+
import path24 from "node:path";
|
|
17452
19035
|
import { isDeepStrictEqual } from "node:util";
|
|
17453
19036
|
|
|
17454
19037
|
// packages/workit-core/src/core/branch-policy.ts
|
|
17455
|
-
import { spawnSync as
|
|
17456
|
-
var
|
|
19038
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
19039
|
+
var PREFIXES2 = {
|
|
17457
19040
|
feature: "feature/*",
|
|
17458
19041
|
bugfix: "bugfix/*",
|
|
17459
19042
|
release: "release/*",
|
|
@@ -17461,7 +19044,7 @@ var PREFIXES = {
|
|
|
17461
19044
|
};
|
|
17462
19045
|
var detectBranchPolicy = (workspaceRoot) => {
|
|
17463
19046
|
const branchExists = (name) => {
|
|
17464
|
-
const r =
|
|
19047
|
+
const r = spawnSync13("git", ["branch", "--list", name], {
|
|
17465
19048
|
cwd: workspaceRoot,
|
|
17466
19049
|
encoding: "utf8"
|
|
17467
19050
|
});
|
|
@@ -17478,7 +19061,7 @@ var detectBranchPolicy = (workspaceRoot) => {
|
|
|
17478
19061
|
integration: "merge",
|
|
17479
19062
|
protected: root ? [root, "develop"] : ["develop"],
|
|
17480
19063
|
allowed: [...PRESETS.gitflow.allowed],
|
|
17481
|
-
prefixes:
|
|
19064
|
+
prefixes: PREFIXES2
|
|
17482
19065
|
};
|
|
17483
19066
|
}
|
|
17484
19067
|
if (root === "main") {
|
|
@@ -17488,7 +19071,7 @@ var detectBranchPolicy = (workspaceRoot) => {
|
|
|
17488
19071
|
integration: "pr",
|
|
17489
19072
|
protected: [root],
|
|
17490
19073
|
allowed: ["*"],
|
|
17491
|
-
prefixes:
|
|
19074
|
+
prefixes: PREFIXES2
|
|
17492
19075
|
};
|
|
17493
19076
|
}
|
|
17494
19077
|
if (root === "master") {
|
|
@@ -17498,7 +19081,7 @@ var detectBranchPolicy = (workspaceRoot) => {
|
|
|
17498
19081
|
integration: "pr",
|
|
17499
19082
|
protected: [root],
|
|
17500
19083
|
allowed: ["*"],
|
|
17501
|
-
prefixes:
|
|
19084
|
+
prefixes: PREFIXES2
|
|
17502
19085
|
};
|
|
17503
19086
|
}
|
|
17504
19087
|
return {
|
|
@@ -17507,7 +19090,7 @@ var detectBranchPolicy = (workspaceRoot) => {
|
|
|
17507
19090
|
integration: "merge",
|
|
17508
19091
|
protected: [],
|
|
17509
19092
|
allowed: [],
|
|
17510
|
-
prefixes:
|
|
19093
|
+
prefixes: PREFIXES2
|
|
17511
19094
|
};
|
|
17512
19095
|
};
|
|
17513
19096
|
|
|
@@ -17521,7 +19104,7 @@ var readFileSafe = (p) => {
|
|
|
17521
19104
|
};
|
|
17522
19105
|
function applyWorkspaceBranchPolicy(opts) {
|
|
17523
19106
|
const { workspace_root, env = process.env } = opts;
|
|
17524
|
-
const dir =
|
|
19107
|
+
const dir = path24.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
17525
19108
|
const {
|
|
17526
19109
|
status,
|
|
17527
19110
|
path: wsPath,
|
|
@@ -17533,7 +19116,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
17533
19116
|
if (status === "malformed" || status === "invalid")
|
|
17534
19117
|
return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
|
|
17535
19118
|
const detection = detectBranchPolicy(workspace_root);
|
|
17536
|
-
const name = String(env.WORKFLOW_BP_NAME ??
|
|
19119
|
+
const name = String(env.WORKFLOW_BP_NAME ?? path24.basename(workspace_root));
|
|
17537
19120
|
const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
|
|
17538
19121
|
const policy = {
|
|
17539
19122
|
preset: detection.preset,
|
|
@@ -17582,7 +19165,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
17582
19165
|
ok: false,
|
|
17583
19166
|
error: `workspaces.json changed while applying the branch policy: ${wsPath}`
|
|
17584
19167
|
};
|
|
17585
|
-
mkdirSync4(
|
|
19168
|
+
mkdirSync4(path24.dirname(wsPath), { recursive: true });
|
|
17586
19169
|
const tmp = `${wsPath}.${process.pid}.tmp`;
|
|
17587
19170
|
try {
|
|
17588
19171
|
writeFileSync6(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + `
|
|
@@ -17617,7 +19200,7 @@ function applyWorkspaceBranchPolicy(opts) {
|
|
|
17617
19200
|
var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
|
|
17618
19201
|
var readJson = (p) => {
|
|
17619
19202
|
try {
|
|
17620
|
-
const parsed = JSON.parse(
|
|
19203
|
+
const parsed = JSON.parse(fs15.readFileSync(p, "utf8"));
|
|
17621
19204
|
return isConfigObject(parsed) ? parsed : null;
|
|
17622
19205
|
} catch {
|
|
17623
19206
|
return null;
|
|
@@ -17629,7 +19212,7 @@ var resolveInitProvider = (cwd) => {
|
|
|
17629
19212
|
return env.toLowerCase();
|
|
17630
19213
|
const root = cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
17631
19214
|
try {
|
|
17632
|
-
const out =
|
|
19215
|
+
const out = spawnSync14("git", ["remote", "get-url", "origin"], {
|
|
17633
19216
|
cwd: root,
|
|
17634
19217
|
encoding: "utf8"
|
|
17635
19218
|
});
|
|
@@ -17647,7 +19230,7 @@ var youtrackJsonContent = (dir) => {
|
|
|
17647
19230
|
const meetingIssue = process.env.WORKFLOW_YT_MEETING_ISSUE;
|
|
17648
19231
|
return {
|
|
17649
19232
|
baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://youtrack.example.com",
|
|
17650
|
-
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ??
|
|
19233
|
+
tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path25.join(dir, "youtrack.token"),
|
|
17651
19234
|
...meetingIssue ? {
|
|
17652
19235
|
meetingIssue,
|
|
17653
19236
|
meetingIssues: { general: { issue: meetingIssue, label: "General meetings" } }
|
|
@@ -17677,19 +19260,19 @@ var vcsJsonContent = () => {
|
|
|
17677
19260
|
};
|
|
17678
19261
|
function initApplyData(action, env = process.env) {
|
|
17679
19262
|
const dir = String(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
|
|
17680
|
-
|
|
19263
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
17681
19264
|
switch (action) {
|
|
17682
19265
|
case "youtrack_json": {
|
|
17683
|
-
const out =
|
|
17684
|
-
|
|
19266
|
+
const out = path25.join(dir, "youtrack.json");
|
|
19267
|
+
fs15.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
17685
19268
|
`, "utf8");
|
|
17686
19269
|
return { action, ok: true, path: out };
|
|
17687
19270
|
}
|
|
17688
19271
|
case "youtrack_token_placeholder": {
|
|
17689
|
-
const p =
|
|
19272
|
+
const p = path25.join(dir, "youtrack.token");
|
|
17690
19273
|
const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER2 + `
|
|
17691
19274
|
`, 384) === "preserved";
|
|
17692
|
-
const abs =
|
|
19275
|
+
const abs = path25.resolve(p);
|
|
17693
19276
|
return {
|
|
17694
19277
|
action,
|
|
17695
19278
|
ok: true,
|
|
@@ -17701,14 +19284,14 @@ function initApplyData(action, env = process.env) {
|
|
|
17701
19284
|
};
|
|
17702
19285
|
}
|
|
17703
19286
|
case "youtrack_scaffold": {
|
|
17704
|
-
const jsonOut =
|
|
17705
|
-
const tokenOut =
|
|
17706
|
-
|
|
19287
|
+
const jsonOut = path25.join(dir, "youtrack.json");
|
|
19288
|
+
const tokenOut = path25.join(dir, "youtrack.token");
|
|
19289
|
+
fs15.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
|
|
17707
19290
|
`, "utf8");
|
|
17708
19291
|
const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER2 + `
|
|
17709
19292
|
`, 384) === "preserved";
|
|
17710
|
-
const configPath =
|
|
17711
|
-
const tokenPath =
|
|
19293
|
+
const configPath = path25.resolve(jsonOut);
|
|
19294
|
+
const tokenPath = path25.resolve(tokenOut);
|
|
17712
19295
|
const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
|
|
17713
19296
|
process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
|
|
17714
19297
|
let tokenCreate = {};
|
|
@@ -17760,10 +19343,10 @@ function initApplyData(action, env = process.env) {
|
|
|
17760
19343
|
}
|
|
17761
19344
|
}
|
|
17762
19345
|
case "vcs_scaffold": {
|
|
17763
|
-
const jsonOut =
|
|
17764
|
-
|
|
19346
|
+
const jsonOut = path25.join(dir, "vcs.json");
|
|
19347
|
+
fs15.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
|
|
17765
19348
|
`, "utf8");
|
|
17766
|
-
const configPath =
|
|
19349
|
+
const configPath = path25.resolve(jsonOut);
|
|
17767
19350
|
const prev = process.env.WORKFLOW_VCS_CONFIG;
|
|
17768
19351
|
process.env.WORKFLOW_VCS_CONFIG = configPath;
|
|
17769
19352
|
try {
|
|
@@ -17882,7 +19465,7 @@ var executeInitApply = (args, directory, runtime) => {
|
|
|
17882
19465
|
protectedNames: args.branch_policy_protected
|
|
17883
19466
|
}, current);
|
|
17884
19467
|
writeConfig(next);
|
|
17885
|
-
return output(ok({ action: "config", path:
|
|
19468
|
+
return output(ok({ action: "config", path: path26.join(configDir(), "config.json"), ...next }));
|
|
17886
19469
|
}
|
|
17887
19470
|
const env = Object.fromEntries(Object.entries({
|
|
17888
19471
|
WORKFLOW_YT_BASE_URL: args.base_url,
|
|
@@ -17898,15 +19481,15 @@ var executeInitApply = (args, directory, runtime) => {
|
|
|
17898
19481
|
};
|
|
17899
19482
|
|
|
17900
19483
|
// packages/workit-opencode/src/shared/session.ts
|
|
17901
|
-
import { realpathSync as
|
|
17902
|
-
import
|
|
19484
|
+
import { realpathSync as realpathSync7 } from "node:fs";
|
|
19485
|
+
import path27 from "node:path";
|
|
17903
19486
|
var sameWorkspace = (expected, observed) => {
|
|
17904
19487
|
if (typeof observed !== "string" || !observed)
|
|
17905
19488
|
return false;
|
|
17906
19489
|
try {
|
|
17907
|
-
return
|
|
19490
|
+
return realpathSync7(expected) === realpathSync7(observed);
|
|
17908
19491
|
} catch {
|
|
17909
|
-
return
|
|
19492
|
+
return path27.resolve(expected) === path27.resolve(observed);
|
|
17910
19493
|
}
|
|
17911
19494
|
};
|
|
17912
19495
|
|
|
@@ -17919,8 +19502,8 @@ var objectSchema = (properties, required) => ({
|
|
|
17919
19502
|
});
|
|
17920
19503
|
var familyTools = OPERATION_FAMILIES.map((family) => ({
|
|
17921
19504
|
name: `workit_${family}`,
|
|
17922
|
-
description:
|
|
17923
|
-
input:
|
|
19505
|
+
description: operationDescription(family),
|
|
19506
|
+
input: flatOperationJsonSchema(family)
|
|
17924
19507
|
}));
|
|
17925
19508
|
var WORKIT_TOOL_CATALOG = [
|
|
17926
19509
|
...familyTools,
|
|
@@ -17977,8 +19560,8 @@ var workitFamilyOf = (toolName) => {
|
|
|
17977
19560
|
};
|
|
17978
19561
|
|
|
17979
19562
|
// packages/workit-opencode/src/tools/context.ts
|
|
17980
|
-
import { realpathSync as
|
|
17981
|
-
import
|
|
19563
|
+
import { realpathSync as realpathSync8 } from "node:fs";
|
|
19564
|
+
import path28 from "node:path";
|
|
17982
19565
|
var createContextTool = () => ({
|
|
17983
19566
|
description: "Read Git, pull request, YouTrack, issue, changelog, release, or affected-file context.",
|
|
17984
19567
|
execute: async (args, context) => {
|
|
@@ -17988,7 +19571,7 @@ var createContextTool = () => ({
|
|
|
17988
19571
|
let root;
|
|
17989
19572
|
try {
|
|
17990
19573
|
const cwd = parsed.data.cwd;
|
|
17991
|
-
root =
|
|
19574
|
+
root = realpathSync8(cwd ? path28.resolve(context.directory, cwd) : context.directory);
|
|
17992
19575
|
} catch {
|
|
17993
19576
|
return JSON.stringify(failure2("invalid_input", "context target directory cannot be resolved"), null, 2);
|
|
17994
19577
|
}
|
|
@@ -18510,24 +20093,37 @@ var injectCompactionContext = (root, sessionID, system) => {
|
|
|
18510
20093
|
};
|
|
18511
20094
|
|
|
18512
20095
|
// packages/workit-opencode/src/v2/permissions.ts
|
|
20096
|
+
var WRITE_ACTIONS = new Set(["edit", "write", "patch"]);
|
|
18513
20097
|
var evaluateShellPermission = (root, event) => {
|
|
18514
|
-
if (event.
|
|
20098
|
+
if (event.effect === "deny")
|
|
18515
20099
|
return;
|
|
18516
|
-
|
|
18517
|
-
|
|
18518
|
-
|
|
20100
|
+
const deny = (reason) => {
|
|
20101
|
+
event.effect = "deny";
|
|
20102
|
+
event.message = reason;
|
|
20103
|
+
};
|
|
20104
|
+
const resources = event.resources.filter((resource) => typeof resource === "string");
|
|
20105
|
+
if (WRITE_ACTIONS.has(event.action)) {
|
|
20106
|
+
const decision = writeGate(root, resources);
|
|
20107
|
+
if (decision.kind === "deny")
|
|
20108
|
+
deny(decision.reason);
|
|
20109
|
+
return;
|
|
20110
|
+
}
|
|
20111
|
+
if (event.action !== "shell")
|
|
20112
|
+
return;
|
|
20113
|
+
for (const resource of resources) {
|
|
18519
20114
|
const decision = shellPolicy(root, resource);
|
|
18520
|
-
if (decision.kind === "deny")
|
|
18521
|
-
|
|
18522
|
-
|
|
18523
|
-
|
|
18524
|
-
|
|
20115
|
+
if (decision.kind === "deny")
|
|
20116
|
+
return deny(decision.reason);
|
|
20117
|
+
const writes = shellWrites(resource);
|
|
20118
|
+
const gated = writes.writes ? writeGate(root, writes.targets) : null;
|
|
20119
|
+
if (gated?.kind === "deny")
|
|
20120
|
+
return deny(gated.reason);
|
|
18525
20121
|
}
|
|
18526
20122
|
};
|
|
18527
20123
|
|
|
18528
20124
|
// packages/workit-opencode/src/v2/registry.ts
|
|
18529
|
-
import { existsSync as
|
|
18530
|
-
import
|
|
20125
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
|
|
20126
|
+
import path30 from "node:path";
|
|
18531
20127
|
|
|
18532
20128
|
// packages/workit-core/src/core/skill-manifests.ts
|
|
18533
20129
|
var WORKIT_METHOD_SKILLS = [
|
|
@@ -18559,34 +20155,34 @@ var WORKIT_SKILL_ALIASES = {
|
|
|
18559
20155
|
if (false) {}
|
|
18560
20156
|
|
|
18561
20157
|
// packages/workit-opencode/src/shared/assets.ts
|
|
18562
|
-
import { existsSync as
|
|
18563
|
-
import
|
|
20158
|
+
import { existsSync as existsSync10, realpathSync as realpathSync9 } from "node:fs";
|
|
20159
|
+
import path29 from "node:path";
|
|
18564
20160
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
18565
20161
|
var moduleDir = () => {
|
|
18566
20162
|
try {
|
|
18567
|
-
return
|
|
20163
|
+
return realpathSync9(path29.dirname(fileURLToPath2(import.meta.url)));
|
|
18568
20164
|
} catch {
|
|
18569
|
-
return
|
|
20165
|
+
return path29.dirname(fileURLToPath2(import.meta.url));
|
|
18570
20166
|
}
|
|
18571
20167
|
};
|
|
18572
20168
|
var packageRoot2 = () => {
|
|
18573
20169
|
const dir = moduleDir();
|
|
18574
20170
|
for (const candidate of [
|
|
18575
|
-
|
|
18576
|
-
|
|
18577
|
-
|
|
20171
|
+
path29.resolve(dir, ".."),
|
|
20172
|
+
path29.resolve(dir, "..", ".."),
|
|
20173
|
+
path29.resolve(dir, "..", "..", "..")
|
|
18578
20174
|
]) {
|
|
18579
|
-
if (
|
|
20175
|
+
if (existsSync10(path29.join(candidate, "package.json")))
|
|
18580
20176
|
return candidate;
|
|
18581
20177
|
}
|
|
18582
|
-
return
|
|
20178
|
+
return path29.resolve(dir, "..");
|
|
18583
20179
|
};
|
|
18584
20180
|
var skillsRoot = () => {
|
|
18585
20181
|
const root = packageRoot2();
|
|
18586
|
-
const canonical =
|
|
18587
|
-
if (
|
|
20182
|
+
const canonical = path29.resolve(root, "..", "workit-core", "skills");
|
|
20183
|
+
if (existsSync10(path29.join(root, "src")) && existsSync10(canonical))
|
|
18588
20184
|
return canonical;
|
|
18589
|
-
return
|
|
20185
|
+
return path29.join(root, "assets", "skills");
|
|
18590
20186
|
};
|
|
18591
20187
|
|
|
18592
20188
|
// packages/workit-opencode/src/v2/registry.ts
|
|
@@ -18610,15 +20206,15 @@ var parseFrontmatter = (markdown) => {
|
|
|
18610
20206
|
};
|
|
18611
20207
|
var registerSkills = async (ctx) => {
|
|
18612
20208
|
const skillsDir = skillsRoot();
|
|
18613
|
-
if (!
|
|
20209
|
+
if (!existsSync11(skillsDir))
|
|
18614
20210
|
return new Set;
|
|
18615
20211
|
const listed = await ctx.skill.list().catch(() => ({ data: [] }));
|
|
18616
|
-
const own = (skill) => typeof skill.path === "string" &&
|
|
20212
|
+
const own = (skill) => typeof skill.path === "string" && path30.resolve(skill.path).startsWith(skillsDir + path30.sep);
|
|
18617
20213
|
const userSkills = new Set((listed?.data ?? []).filter((skill) => !own(skill)).map((skill) => skill.id));
|
|
18618
20214
|
const packaged = new Map;
|
|
18619
20215
|
for (const id of WORKIT_METHOD_SKILLS) {
|
|
18620
|
-
const file =
|
|
18621
|
-
if (!userSkills.has(id) &&
|
|
20216
|
+
const file = path30.join(skillsDir, id, "SKILL.md");
|
|
20217
|
+
if (!userSkills.has(id) && existsSync11(file))
|
|
18622
20218
|
packaged.set(id, file);
|
|
18623
20219
|
}
|
|
18624
20220
|
await ctx.skill.transform((editor) => {
|
|
@@ -18664,25 +20260,25 @@ var registerCommands = async (ctx, registeredSkills) => {
|
|
|
18664
20260
|
};
|
|
18665
20261
|
|
|
18666
20262
|
// packages/workit-opencode/src/stale-sources.ts
|
|
18667
|
-
import { existsSync as
|
|
18668
|
-
import
|
|
20263
|
+
import { existsSync as existsSync12, readdirSync as readdirSync5 } from "node:fs";
|
|
20264
|
+
import path31 from "node:path";
|
|
18669
20265
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
18670
20266
|
var typescriptFiles = (directory) => {
|
|
18671
|
-
if (!
|
|
20267
|
+
if (!existsSync12(directory))
|
|
18672
20268
|
return [];
|
|
18673
20269
|
return readdirSync5(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
18674
|
-
const file =
|
|
20270
|
+
const file = path31.join(directory, entry.name);
|
|
18675
20271
|
return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
|
|
18676
20272
|
});
|
|
18677
20273
|
};
|
|
18678
20274
|
var pluginSourceFiles = [
|
|
18679
20275
|
...new Set([
|
|
18680
20276
|
fileURLToPath3(import.meta.url),
|
|
18681
|
-
...typescriptFiles(
|
|
18682
|
-
|
|
18683
|
-
...typescriptFiles(
|
|
20277
|
+
...typescriptFiles(path31.join(packageRoot2(), "src")),
|
|
20278
|
+
path31.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
|
|
20279
|
+
...typescriptFiles(path31.join(packageRoot2(), "..", "workit-core", "src", "core"))
|
|
18684
20280
|
])
|
|
18685
|
-
].filter((file) =>
|
|
20281
|
+
].filter((file) => existsSync12(file)).toSorted();
|
|
18686
20282
|
|
|
18687
20283
|
// packages/workit-opencode/src/v2/plugin.ts
|
|
18688
20284
|
var sessionFacts = async (ctx, sessionID) => {
|
|
@@ -18736,7 +20332,7 @@ var setup = async (ctx) => {
|
|
|
18736
20332
|
});
|
|
18737
20333
|
const contextTool = createContextTool();
|
|
18738
20334
|
const runFamily = (family, input, session, store, workerId) => {
|
|
18739
|
-
const parsed =
|
|
20335
|
+
const parsed = parseAdvertisedOperation(family, input, "opencode");
|
|
18740
20336
|
if (!parsed.ok)
|
|
18741
20337
|
return parsed;
|
|
18742
20338
|
const core = new WorkitCore(store, {
|