@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/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 spawnSync10 } from "node:child_process";
8
- import { existsSync as existsSync6, realpathSync as realpathSync5 } from "node:fs";
9
- import path16 from "node:path";
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 factSchema = object({
7934
- statement: text,
7935
- basis: _enum(["observed", "inferred", "unknown"]),
7936
- refs: array(refSchema)
7937
- }).strict().check((ctx) => {
7938
- if (ctx.value.basis === "observed" && ctx.value.refs.length === 0)
7939
- ctx.issues.push({
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(assessmentSchema)),
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
- assessment: assessmentSchema
8370
+ judgment: rawJudgment
8421
8371
  }),
8422
- preview: operation({ action: literal("preview"), ...taskId, assessment: assessmentSchema }),
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 isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
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 (!isObject2(value) || typeof value.v !== "number")
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" || !isObject2(value.data))
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 isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
10071
- var isKey = (value) => isObject3(value) && typeof value.key === "string" && (value.kind === "branch" || value.kind === "detached" || value.kind === "dir") && (value.branch === null || typeof value.branch === "string");
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) => isObject3(value) && Object.keys(value).length === 1 && typeof value[BLOB_REF] === "string" && /^[0-9a-f]{64}$/.test(value[BLOB_REF]) ? value[BLOB_REF] : null;
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 (!isObject3(value))
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 (isObject3(value)) {
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 (!isObject3(data.record))
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 && isObject3(data.legacy))
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 isObject4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
10198
- var hostSession = (value) => isObject4(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
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 (!isObject4(value) || value.v !== 1 || value.task !== taskId || typeof value.seq !== "number" || typeof value.lastId !== "string" || typeof value.lastStart !== "number" || typeof value.end !== "number" || !isObject4(value.record))
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 (!isObject4(value) || value.version !== INDEX_VERSION || !isObject4(value.tasks))
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 (isObject4(entry) && entry.id === id && typeof entry.file === "string" && typeof entry.status === "string" && typeof entry.updatedAt === "string" && typeof entry.workspaceId === "string" && Array.isArray(entry.sessions) && isObject4(entry.progress) && isObject4(entry.source))
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 (isObject4(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
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 = isObject4(value) && isObject4(value.runtime) ? value.runtime.updatedWith : null;
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 (isObject4(value) && isObject4(value.store) && value.store.format === "workit-store")
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 isObject4(value) && isObject4(value.store) && value.store.format === "workit-store";
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 createHash6 } from "node:crypto";
11829
- import * as fs10 from "node:fs";
11830
- import { spawnSync as spawnSync5 } from "node:child_process";
11831
- import path9 from "node:path";
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
- // packages/workit-core/src/core/task-evaluation.ts
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
- if (value.includes("\\") || value.split("/").includes(".."))
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 normalized = path9.posix.normalize(value);
12173
- if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
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
- return normalized === "." ? "." : normalized.replace(/\/$/, "");
12176
- };
12177
- var canonicalScope = (scope) => {
12178
- const paths = scope.paths.length ? scope.paths : ["."];
12179
- const normalizedPaths = paths.map(canonicalPath);
12180
- const normalizedExclusions = scope.exclusions.map(canonicalPath);
12181
- if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
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
- description: scope.description,
12185
- paths: [...new Set(normalizedPaths)],
12186
- exclusions: [...new Set(normalizedExclusions)]
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 excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
12190
- var scopeMatches = (value, scope) => {
12191
- const normalized = canonicalScope(scope);
12192
- if (!normalized)
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 gitPaths = (root) => {
12208
- const result = spawnSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
12209
- cwd: root,
12210
- encoding: "buffer"
12211
- });
12212
- if (result.status !== 0) {
12213
- const stderr = result.stderr?.toString("utf8") ?? "";
12214
- return {
12215
- paths: [],
12216
- uncertain: !/not a git repository/i.test(stderr),
12217
- git: !/not a git repository/i.test(stderr)
12218
- };
12219
- }
12220
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")) : [];
12221
- const stagedDeleted = spawnSync5("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
12222
- if (stagedDeleted.status !== 0) {
12223
- const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
12224
- if (!/not a git repository/i.test(stderr))
12225
- return { paths, uncertain: true, git: true };
12226
- } else if (stagedDeleted.stdout) {
12227
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")));
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 { paths: [...new Set(paths)], uncertain: false, git: true };
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
- var walk = (root, directory, output) => {
12232
- let names;
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
- names = fs10.readdirSync(directory);
12235
- } catch {
12236
- return true;
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
- let uncertain = false;
12239
- for (const name of names) {
12240
- if (name === ".git" || name === ".workit")
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
- const target = path9.join(directory, name);
12243
- let stat;
12639
+ let parsed;
12244
12640
  try {
12245
- stat = fs10.lstatSync(target);
12641
+ parsed = JSON.parse(trimmed);
12246
12642
  } catch {
12247
- uncertain = true;
12643
+ skipped += 1;
12248
12644
  continue;
12249
12645
  }
12250
- const item = relative(root, target);
12251
- output.add(item);
12252
- if (stat.isDirectory() && !stat.isSymbolicLink())
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
- roots.push(target);
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 = fs10.realpathSync(root);
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 (!fs10.existsSync(target))
13168
+ if (!fs11.existsSync(target))
12298
13169
  continue;
12299
13170
  let stat;
12300
13171
  try {
12301
- stat = fs10.lstatSync(target);
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 = path9.join(checkout, item);
13188
+ const target = path11.join(checkout, item);
12318
13189
  try {
12319
- const stat = fs10.lstatSync(target);
13190
+ const stat = fs11.lstatSync(target);
12320
13191
  if (stat.isSymbolicLink()) {
12321
- const link = fs10.readlinkSync(target);
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(fs10.readFileSync(target)),
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 = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
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 = fs10.realpathSync(checkoutRoot);
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 = path9.resolve(root, reference.path);
12502
- if (target !== root && !target.startsWith(`${root}${path9.sep}`))
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 = path9.relative(root, target);
13380
+ const below = path11.relative(root, target);
12510
13381
  let current = root;
12511
- for (const segment of below.split(path9.sep)) {
13382
+ for (const segment of below.split(path11.sep)) {
12512
13383
  if (!segment)
12513
13384
  continue;
12514
- current = path9.join(current, segment);
12515
- const stat = fs10.lstatSync(current);
12516
- const real = fs10.realpathSync(current);
12517
- if (real !== root && !real.startsWith(`${root}${path9.sep}`))
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(fs10.readFileSync(target)) !== reference.digest)
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: never record a passing verdict on work your session
12874
- wrote. The lead starts each verifier with its own session
12875
- (\`WORKIT_SESSION_ID=<lead>-v<n>\`; Claude Code subagents get one from the
12876
- hook), and the verifier records \`workit ledger verdict\`.
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 createHash8 } from "node:crypto";
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 existsSync4,
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 path10 from "node:path";
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 ?? path10.join(process.env.XDG_CONFIG_HOME || path10.join(os2.homedir(), ".config"), "workit");
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 = path10.join(process.env.XDG_CONFIG_HOME || path10.join(os2.homedir(), ".config"), "workflow-toolkit");
13178
- if (!existsSync4(legacy)) {
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 && existsSync4(dir)) {
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 = path10.join(legacy, entry.name);
13191
- const dest = path10.join(dir, entry.name);
13192
- if (existsSync4(dest))
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 = path10.join(dir ?? configDir(), "config.json");
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(path10.join(dir, "config.json"), JSON.stringify(config, null, 2) + `
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((p) => new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"));
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 createHash7 } from "node:crypto";
14223
+ import { createHash as createHash8 } from "node:crypto";
13321
14224
  import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
13322
- import path11 from "node:path";
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({ feature: nonBlank, release: nonBlank, hotfix: nonBlank }).strict(),
13360
- baseBranch: nonBlank,
13361
- mergeBackBranches: array(nonBlank),
13362
- pullRequestTarget: nonBlank,
13363
- tagNamespace: string2(),
13364
- versionSource: versionSourceSchema,
13365
- requiredChecks: array(nonBlank)
13366
- }).strict().superRefine((track, context) => {
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.kind === "package-json" && (path11.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
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) => createHash7("sha256").update(contents ?? "<missing>").digest("hex");
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 = path11.join(dir, "workspaces.json");
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 resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
13643
- const file = path11.join(configDir(), "workspaces.json");
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
- if (!selected)
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 fs11 from "node:fs";
13707
- import path12 from "node:path";
13708
- import { spawnSync as spawnSync6 } from "node:child_process";
13709
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path12.join(configDir(), "vcs.json");
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 = spawnSync6("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
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 = spawnSync6("ssh", ["-G", host], {
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 = fs11.readFileSync(file, "utf8");
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
- function vcsConfig(mode, cwd) {
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 wp = ws?.branchPolicy ?? {};
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: path12.resolve(cfgPath),
15210
+ configPath: path14.resolve(cfgPath),
13866
15211
  provider,
13867
- defaultTargetBranch: defaultTarget,
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 = spawnSync6("git", ["remote", "get-url", "--push", "--all", "origin"], {
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) => spawnSync6("glab", ["api", ...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 = spawnSync6("gh", [
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 = createHash8("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
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
- var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
14040
-
14041
- // packages/workit-core/src/core/route-intent.ts
14042
- var shellBranchTarget = (command) => {
14043
- if (!command.trim() || /[\r\n;&|<>`'"$()\\*?{}~!#]/u.test(command) || command.includes("[") || command.includes("]"))
14044
- return null;
14045
- const args = command.trim().split(/\s+/u);
14046
- if (args[0] !== "git")
14047
- return null;
14048
- const option = args[2];
14049
- 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;
14050
- return target && !target.startsWith("-") ? target : null;
14051
- };
14052
- var shellBranchPolicyViolation = (root, command) => {
14053
- const target = shellBranchTarget(command);
14054
- return target === null ? null : validateBranchNameFor(root, target);
14055
- };
14056
- // packages/workit-core/src/core/task-engine.ts
14057
- import { realpathSync as realpathSync4 } from "node:fs";
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 normalized = normalizeResolverInput(input);
14346
- if (!normalized.ok)
14347
- return normalized;
14348
- const requirements = resolveRequirements(normalized.data);
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(normalized.data)),
14352
- requirements
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", invalidFields(parsed.error));
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, assessmentSchema, remap)
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, request) {
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 resolved = this.resolve(task.data, input.assessment);
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 assessment = {
16385
+ const entry = {
15062
16386
  id: newId(),
15063
16387
  recordedAt: mutation.now,
15064
16388
  provenance: provenance(this.context),
15065
- data: input.assessment
16389
+ data: judgment
15066
16390
  };
15067
16391
  const next = {
15068
16392
  ...current,
15069
16393
  constraints: this.context.constraints,
15070
- assessments: [...current.assessments, assessment],
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
- if (input.binding.taskId !== task.data.id || input.binding.workspaceId !== workspace.data.id)
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, input.binding);
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: input.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, assessment) {
16971
+ resolve(task, judgment) {
15642
16972
  return resolvePolicy({
15643
16973
  intent: task.intent.data,
15644
- assessment,
16974
+ judgment,
15645
16975
  constraints: this.context.constraints,
15646
- prior: {
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 spawnSync7 } from "node:child_process";
15977
- import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
15978
- import path13 from "node:path";
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 = spawnSync7("git", args, { cwd, encoding: "utf8" });
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 currentBranch = (cwd) => {
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 defaultBase = (cwd) => {
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 : `${defaultBase(cwd)}...HEAD`;
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 = currentBranch(cwd);
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 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
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 = path13.join(cwd, "package.json");
16144
- if (!existsSync5(pkgPath))
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(path13.join(dir, entry.name), depth + 1);
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("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
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 (!existsSync5(file))
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 = path13.resolve(repoRoot(root));
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: ${currentBranch(cwd)}
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(path13.join(cwd, template), 220);
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 (existsSync5(path13.join(cwd, "package.json"))) {
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 (existsSync5(path13.join(cwd, "Cargo.toml")) || existsSync5(path13.join(cwd, "src-tauri/Cargo.toml"))) {
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 = path13.resolve(repoRoot(root));
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: ${currentBranch(cwd)}
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 = path13.join(cwd, "CHANGELOG.md");
16316
- if (existsSync5(changelogPath)) {
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 = path13.resolve(repoRoot(root));
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: ${currentBranch(cwd)}
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 = path13.join(cwd, "README.md");
16358
- if (existsSync5(readme)) {
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 = path13.join(cwd, "package.json");
16367
- if (existsSync5(pkgPath)) {
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 = path13.resolve(repoRoot(root));
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: ${currentBranch(cwd)}
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 (existsSync5(path13.join(cwd, rel)))
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 spawnSync9 } from "node:child_process";
17772
+ import { spawnSync as spawnSync11 } from "node:child_process";
16447
17773
 
16448
17774
  // packages/workit-core/src/core/pr-create.ts
16449
- import fs12 from "node:fs";
16450
- import path14 from "node:path";
16451
- import { spawnSync as spawnSync8 } from "node:child_process";
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(path14.delimiter)) {
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 = path14.join(dir, name);
17791
+ const candidate = path17.join(dir, name);
16466
17792
  try {
16467
- fs12.accessSync(candidate, fs12.constants.X_OK);
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 = spawnSync8("git", ["remote", "get-url", "--push", "--all", "origin"], {
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 = spawnSync9(bin, args, {
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 fs13 from "node:fs";
16586
- import path15 from "node:path";
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 ?? path15.join(configDir(), "youtrack.json");
16600
- var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path15.join(resolveConfigDir(), "youtrack.json");
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 = fs13.statSync(p).mode & 511;
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 (!fs13.existsSync(cfgPath)) {
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(fs13.readFileSync(cfgPath, "utf8"));
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 (!fs13.existsSync(cfgPath))
17951
+ if (!fs14.existsSync(cfgPath))
16626
17952
  return { data: {} };
16627
17953
  let parsed;
16628
17954
  try {
16629
- parsed = JSON.parse(fs13.readFileSync(cfgPath, "utf8"));
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 ? path15.isAbsolute(tokenFile) ? path15.resolve(tokenFile) : path15.resolve(process.cwd(), tokenFile) : "";
16669
- if (!tokenPath || !fs13.existsSync(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 = fs13.readFileSync(tokenPath, "utf8").trim();
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 ?? path15.join(path15.dirname(loaded.path), "youtrack.token"));
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: path15.resolve(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 (!fs13.existsSync(full))
18100
+ if (!fs14.existsSync(full))
16775
18101
  continue;
16776
- const text = fs13.readFileSync(full, "utf8");
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 = path16.resolve(base, candidate);
17069
- if (target !== base && !target.startsWith(base + path16.sep)) {
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 (!existsSync6(ancestor))
17074
- ancestor = path16.dirname(ancestor);
18399
+ while (!existsSync7(ancestor))
18400
+ ancestor = path19.dirname(ancestor);
17075
18401
  const canonicalAncestor = realpathSync5(ancestor);
17076
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path16.sep)) {
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 = spawnSync10(executable, args, {
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 path17 from "node:path";
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" ? [path17.resolve(store.root, ref.path)] : []));
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 path22 from "node:path";
18913
+ import path26 from "node:path";
17308
18914
 
17309
18915
  // packages/workit-core/src/core/gitignore.ts
17310
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
17311
- import path18 from "node:path";
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 = path18.join(workspaceRoot, ".gitignore");
17332
- const existing = existsSync7(file) ? readFileSync6(file, "utf8") : "";
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
- writeFileSync3(file, existing + separator + (existing ? `
18953
+ writeFileSync4(file, existing + separator + (existing ? `
17348
18954
  ` : "") + append.join(`
17349
18955
  `) + `
17350
18956
  `, "utf8");
17351
- } else if (!existsSync7(file)) {
17352
- writeFileSync3(file, "", "utf8");
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 existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
17359
- import path19 from "node:path";
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 = () => path19.join(repoRoot3, "templates", "hygiene");
18967
+ var templatesDir = () => path23.join(repoRoot3, "templates", "hygiene");
17362
18968
  var packageJson = (root) => {
17363
- if (!existsSync8(path19.join(root, "package.json")))
18969
+ if (!existsSync9(path23.join(root, "package.json")))
17364
18970
  return null;
17365
18971
  try {
17366
- return JSON.parse(readFileSync7(path19.join(root, "package.json"), "utf8"));
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 (existsSync8(path19.join(root, "LICENSE")))
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 path19.basename(root) === "workflow-toolkit";
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 (existsSync8(path19.join(root, file)))
19006
+ if (existsSync9(path23.join(root, file)))
17401
19007
  continue;
17402
- const tpl = path19.join(templatesDir(), file);
17403
- if (!existsSync8(tpl))
19008
+ const tpl = path23.join(templatesDir(), file);
19009
+ if (!existsSync9(tpl))
17404
19010
  continue;
17405
- const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g, path19.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
17406
- planned.push({ path: path19.join(root, file), content });
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
- writeFileSync4(planned.path, planned.content, "utf8");
17416
- created.push(path19.basename(planned.path));
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 fs14 from "node:fs";
17423
- import path21 from "node:path";
17424
- import { spawnSync as spawnSync12 } from "node:child_process";
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 path20 from "node:path";
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 spawnSync11 } from "node:child_process";
17456
- var PREFIXES = {
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 = spawnSync11("git", ["branch", "--list", name], {
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: 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: 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: 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: 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 = path20.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
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 ?? path20.basename(workspace_root));
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(path20.dirname(wsPath), { recursive: true });
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(fs14.readFileSync(p, "utf8"));
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 = spawnSync12("git", ["remote", "get-url", "origin"], {
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 ?? path21.join(dir, "youtrack.token"),
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
- fs14.mkdirSync(dir, { recursive: true });
19263
+ fs15.mkdirSync(dir, { recursive: true });
17681
19264
  switch (action) {
17682
19265
  case "youtrack_json": {
17683
- const out = path21.join(dir, "youtrack.json");
17684
- fs14.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
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 = path21.join(dir, "youtrack.token");
19272
+ const p = path25.join(dir, "youtrack.token");
17690
19273
  const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER2 + `
17691
19274
  `, 384) === "preserved";
17692
- const abs = path21.resolve(p);
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 = path21.join(dir, "youtrack.json");
17705
- const tokenOut = path21.join(dir, "youtrack.token");
17706
- fs14.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
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 = path21.resolve(jsonOut);
17711
- const tokenPath = path21.resolve(tokenOut);
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 = path21.join(dir, "vcs.json");
17764
- fs14.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
19346
+ const jsonOut = path25.join(dir, "vcs.json");
19347
+ fs15.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
17765
19348
  `, "utf8");
17766
- const configPath = path21.resolve(jsonOut);
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: path22.join(configDir(), "config.json"), ...next }));
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 realpathSync6 } from "node:fs";
17902
- import path23 from "node:path";
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 realpathSync6(expected) === realpathSync6(observed);
19490
+ return realpathSync7(expected) === realpathSync7(observed);
17908
19491
  } catch {
17909
- return path23.resolve(expected) === path23.resolve(observed);
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: `Workit ${family} operations backed by the shared task contract.`,
17923
- input: { type: "object", ...boundedOperationJsonSchema(family, OPERATION_SCHEMA_DEPTH) }
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 realpathSync7 } from "node:fs";
17981
- import path24 from "node:path";
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 = realpathSync7(cwd ? path24.resolve(context.directory, cwd) : context.directory);
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.action !== "shell" || event.effect === "deny")
20098
+ if (event.effect === "deny")
18515
20099
  return;
18516
- for (const resource of event.resources) {
18517
- if (typeof resource !== "string")
18518
- continue;
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
- event.effect = "deny";
18522
- event.message = decision.reason;
18523
- return;
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 existsSync10, readFileSync as readFileSync9 } from "node:fs";
18530
- import path26 from "node:path";
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 existsSync9, realpathSync as realpathSync8 } from "node:fs";
18563
- import path25 from "node:path";
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 realpathSync8(path25.dirname(fileURLToPath2(import.meta.url)));
20163
+ return realpathSync9(path29.dirname(fileURLToPath2(import.meta.url)));
18568
20164
  } catch {
18569
- return path25.dirname(fileURLToPath2(import.meta.url));
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
- path25.resolve(dir, ".."),
18576
- path25.resolve(dir, "..", ".."),
18577
- path25.resolve(dir, "..", "..", "..")
20171
+ path29.resolve(dir, ".."),
20172
+ path29.resolve(dir, "..", ".."),
20173
+ path29.resolve(dir, "..", "..", "..")
18578
20174
  ]) {
18579
- if (existsSync9(path25.join(candidate, "package.json")))
20175
+ if (existsSync10(path29.join(candidate, "package.json")))
18580
20176
  return candidate;
18581
20177
  }
18582
- return path25.resolve(dir, "..");
20178
+ return path29.resolve(dir, "..");
18583
20179
  };
18584
20180
  var skillsRoot = () => {
18585
20181
  const root = packageRoot2();
18586
- const canonical = path25.resolve(root, "..", "workit-core", "skills");
18587
- if (existsSync9(path25.join(root, "src")) && existsSync9(canonical))
20182
+ const canonical = path29.resolve(root, "..", "workit-core", "skills");
20183
+ if (existsSync10(path29.join(root, "src")) && existsSync10(canonical))
18588
20184
  return canonical;
18589
- return path25.join(root, "assets", "skills");
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 (!existsSync10(skillsDir))
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" && path26.resolve(skill.path).startsWith(skillsDir + path26.sep);
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 = path26.join(skillsDir, id, "SKILL.md");
18621
- if (!userSkills.has(id) && existsSync10(file))
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 existsSync11, readdirSync as readdirSync5 } from "node:fs";
18668
- import path27 from "node:path";
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 (!existsSync11(directory))
20267
+ if (!existsSync12(directory))
18672
20268
  return [];
18673
20269
  return readdirSync5(directory, { withFileTypes: true }).flatMap((entry) => {
18674
- const file = path27.join(directory, entry.name);
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(path27.join(packageRoot2(), "src")),
18682
- path27.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
18683
- ...typescriptFiles(path27.join(packageRoot2(), "..", "workit-core", "src", "core"))
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) => existsSync11(file)).toSorted();
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 = parseOperation(family, input);
20335
+ const parsed = parseAdvertisedOperation(family, input, "opencode");
18740
20336
  if (!parsed.ok)
18741
20337
  return parsed;
18742
20338
  const core = new WorkitCore(store, {