@brainervirus/workit-opencode 6.0.0 → 7.0.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 spawnSync11 } 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,704 @@ 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)
12169
- return null;
12170
- if (value.includes("\\") || value.split("/").includes(".."))
12424
+ function patchId(cwd, base, head) {
12425
+ if (!safeArg(base) || !safeArg(head))
12171
12426
  return null;
12172
- const normalized = path9.posix.normalize(value);
12173
- if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
12174
- 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))
12427
+ const diff = git2(cwd, [
12428
+ "diff",
12429
+ "-U3",
12430
+ "--diff-algorithm=myers",
12431
+ "--indent-heuristic",
12432
+ "--no-color",
12433
+ "--no-ext-diff",
12434
+ "--no-textconv",
12435
+ "--no-renames",
12436
+ "--full-index",
12437
+ "--src-prefix=a/",
12438
+ "--dst-prefix=b/",
12439
+ `${base}...${head}`,
12440
+ "--"
12441
+ ]);
12442
+ if (!diff.ok || !diff.stdout.trim())
12182
12443
  return null;
12444
+ const id = git2(cwd, ["patch-id", "--stable"], { input: diff.stdout });
12445
+ return line(id)?.split(/\s+/u)[0] ?? null;
12446
+ }
12447
+ function currentBranch(cwd) {
12448
+ return line(git2(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
12449
+ }
12450
+
12451
+ // packages/workit-core/src/core/policy/derive.ts
12452
+ import { existsSync as existsSync3 } from "node:fs";
12453
+ import path10 from "node:path";
12454
+
12455
+ // packages/workit-core/src/ledger.ts
12456
+ import { spawnSync as spawnSync5 } from "node:child_process";
12457
+ import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
12458
+ import fs10 from "node:fs";
12459
+ import path9 from "node:path";
12460
+ var LEDGER_VERSION = 1;
12461
+ var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
12462
+ var FAILING = new Set(["failed", "blocked"]);
12463
+ var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
12464
+ var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
12465
+ var err = (code, error, unblock) => ({
12466
+ ok: false,
12467
+ code,
12468
+ error,
12469
+ ...unblock ? { unblock } : {}
12470
+ });
12471
+ var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
12472
+ const run = spawnSync5("git", args, {
12473
+ cwd,
12474
+ encoding: "utf8",
12475
+ timeout: timeoutMs,
12476
+ killSignal: "SIGKILL",
12477
+ windowsHide: true,
12478
+ maxBuffer: 256 * 1024 * 1024,
12479
+ env: {
12480
+ ...process.env,
12481
+ GIT_TERMINAL_PROMPT: "0",
12482
+ GIT_OPTIONAL_LOCKS: "0",
12483
+ LC_ALL: "C",
12484
+ LANGUAGE: "C"
12485
+ }
12486
+ });
12183
12487
  return {
12184
- description: scope.description,
12185
- paths: [...new Set(normalizedPaths)],
12186
- exclusions: [...new Set(normalizedExclusions)]
12488
+ ok: run.status === 0,
12489
+ stdout: run.stdout ?? "",
12490
+ stderr: run.error ? String(run.error.message) : run.stderr ?? ""
12187
12491
  };
12188
12492
  };
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;
12493
+ var git3 = (cwd, args) => {
12494
+ const run = gitRun2(cwd, args);
12495
+ const value = run.ok ? run.stdout.trim() : "";
12496
+ return value ? value : null;
12206
12497
  };
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("/")));
12498
+ var safeRef = (value) => value.length > 0 && !value.startsWith("-");
12499
+ function resolveCommit(cwd, ref) {
12500
+ if (!safeRef(ref))
12501
+ return null;
12502
+ return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
12503
+ }
12504
+ function branchHead(cwd, branch) {
12505
+ if (!safeRef(branch))
12506
+ return null;
12507
+ return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
12508
+ }
12509
+ function defaultBase(cwd) {
12510
+ const originHead = git3(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
12511
+ if (originHead && resolveCommit(cwd, originHead))
12512
+ return originHead;
12513
+ for (const candidate of ["origin/main", "origin/master", "main", "master"])
12514
+ if (resolveCommit(cwd, candidate))
12515
+ return candidate;
12516
+ return null;
12517
+ }
12518
+ function diffHash(cwd, base, head) {
12519
+ if (!safeRef(base) || !safeRef(head))
12520
+ return null;
12521
+ const diff = gitRun2(cwd, [
12522
+ "diff",
12523
+ "-U3",
12524
+ "--diff-algorithm=myers",
12525
+ "--indent-heuristic",
12526
+ "--no-color",
12527
+ "--no-ext-diff",
12528
+ "--no-textconv",
12529
+ "--no-renames",
12530
+ "--full-index",
12531
+ "--binary",
12532
+ "--src-prefix=a/",
12533
+ "--dst-prefix=b/",
12534
+ `${base}...${head}`,
12535
+ "--"
12536
+ ], GIT_TIMEOUTS.worktree);
12537
+ if (!diff.ok || !diff.stdout)
12538
+ return null;
12539
+ const hash = createHash6("sha256");
12540
+ for (const line of diff.stdout.split(`
12541
+ `)) {
12542
+ if (line.startsWith("index "))
12543
+ continue;
12544
+ hash.update(line.startsWith("@@ ") ? "@@" : line);
12545
+ hash.update(`
12546
+ `);
12228
12547
  }
12229
- return { paths: [...new Set(paths)], uncertain: false, git: true };
12548
+ return `sha256:${hash.digest("hex")}`;
12549
+ }
12550
+ function storeRoot(cwd) {
12551
+ const location = resolveStore(path9.resolve(cwd));
12552
+ if (location instanceof Error)
12553
+ 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");
12554
+ return { ok: true, value: { root: location.dir, shared: location.shared } };
12555
+ }
12556
+ function ledgerPath(cwd) {
12557
+ const root = storeRoot(cwd);
12558
+ return root.ok ? { ok: true, value: path9.join(root.value.root, "ledger", "ledger.jsonl") } : root;
12559
+ }
12560
+ var NETWORK_FS_TYPES = new Set([
12561
+ 26985,
12562
+ 20859,
12563
+ 4266872130,
12564
+ 4283649346,
12565
+ 1702057286,
12566
+ 1397113167,
12567
+ 12805120,
12568
+ 16914839,
12569
+ 1196443219,
12570
+ 198183888
12571
+ ]);
12572
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
12573
+ var str2 = (value) => typeof value === "string" ? value : null;
12574
+ var normalizeActor = (value) => {
12575
+ const actor = isRecord2(value) ? value : {};
12576
+ return {
12577
+ host: str2(actor.host) ?? "unknown",
12578
+ session: str2(actor.session),
12579
+ agentId: str2(actor.agentId)
12580
+ };
12230
12581
  };
12231
- var walk = (root, directory, output) => {
12232
- let names;
12582
+ function supersedeAllowed(by, target) {
12583
+ if (by.type !== target.type)
12584
+ return `a ${by.type} row cannot supersede a ${target.type} row`;
12585
+ if (!by.actor.session || by.actor.session !== target.actor.session)
12586
+ return "only the session that wrote a row can supersede it";
12587
+ if (by.type === "verdict" && by.self === true && target.self !== true)
12588
+ return "a self verdict cannot replace an independent verdict";
12589
+ return null;
12590
+ }
12591
+ function readLedger(cwd) {
12592
+ const file = ledgerPath(cwd);
12593
+ if (!file.ok)
12594
+ return file;
12595
+ let text = "";
12233
12596
  try {
12234
- names = fs10.readdirSync(directory);
12235
- } catch {
12236
- return true;
12597
+ text = fs10.readFileSync(file.value, "utf8");
12598
+ } catch (error) {
12599
+ if (error.code === "ENOENT")
12600
+ return { ok: true, value: { path: file.value, rows: [], skipped: 0 } };
12601
+ return err("unavailable", `cannot read ${file.value}: ${error.message}`);
12237
12602
  }
12238
- let uncertain = false;
12239
- for (const name of names) {
12240
- if (name === ".git" || name === ".workit")
12603
+ const rows = [];
12604
+ let skipped = 0;
12605
+ for (const raw of text.split(`
12606
+ `)) {
12607
+ const trimmed = raw.trim();
12608
+ if (!trimmed)
12241
12609
  continue;
12242
- const target = path9.join(directory, name);
12243
- let stat;
12610
+ let parsed;
12244
12611
  try {
12245
- stat = fs10.lstatSync(target);
12612
+ parsed = JSON.parse(trimmed);
12246
12613
  } catch {
12247
- uncertain = true;
12614
+ skipped += 1;
12248
12615
  continue;
12249
12616
  }
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}`);
12617
+ if (!isRecord2(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
12618
+ skipped += 1;
12619
+ continue;
12271
12620
  }
12272
- roots.push(target);
12621
+ const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
12622
+ rows.push({
12623
+ ...parsed,
12624
+ v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
12625
+ id: parsed.id,
12626
+ at: parsed.at,
12627
+ type: parsed.type,
12628
+ actor: normalizeActor(parsed.actor),
12629
+ branch: str2(parsed.branch),
12630
+ head: str2(parsed.head),
12631
+ base: str2(parsed.base),
12632
+ baseSha: str2(parsed.baseSha),
12633
+ patchId: str2(parsed.patchId),
12634
+ diffHash: str2(parsed.diffHash),
12635
+ tree: str2(parsed.tree),
12636
+ dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
12637
+ ...pr === undefined ? {} : { pr },
12638
+ ...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
12639
+ seq: rows.length + 1,
12640
+ superseded: false,
12641
+ supersedeIgnored: false
12642
+ });
12273
12643
  }
12274
- return success(null, null, roots);
12644
+ const byId = new Map;
12645
+ for (const row of rows) {
12646
+ const target = row.supersedes ? byId.get(row.supersedes) : undefined;
12647
+ if (target && supersedeAllowed(row, target) === null)
12648
+ target.superseded = true;
12649
+ else if (row.supersedes)
12650
+ row.supersedeIgnored = true;
12651
+ if (!byId.has(row.id))
12652
+ byId.set(row.id, row);
12653
+ }
12654
+ return { ok: true, value: { path: file.value, rows, skipped } };
12655
+ }
12656
+ function verdictBasis(verdict, current) {
12657
+ if (verdict.head && verdict.head === current.head)
12658
+ return "fresh";
12659
+ if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
12660
+ return "carried";
12661
+ return "stale";
12662
+ }
12663
+ var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded;
12664
+ function trailerSessions(cwd, base, head) {
12665
+ if (!base || !head || !safeRef(base) || !safeRef(head))
12666
+ return [];
12667
+ const out = git3(cwd, [
12668
+ "log",
12669
+ "--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
12670
+ `${base}..${head}`
12671
+ ]);
12672
+ return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
12673
+ }
12674
+ function authorSessions(cwd, rows, branch, range = { base: null, head: null }) {
12675
+ const sessions = new Set;
12676
+ for (const row of rows) {
12677
+ if (row.branch !== branch || row.type !== "commit.recorded" && row.type !== "task.opened")
12678
+ continue;
12679
+ const session = str2(row.session) ?? row.actor.session;
12680
+ if (session)
12681
+ sessions.add(session);
12682
+ }
12683
+ for (const session of trailerSessions(cwd, range.base, range.head))
12684
+ sessions.add(session);
12685
+ return sessions;
12686
+ }
12687
+ var independenceReasons = (row, authors) => {
12688
+ const reasons = [];
12689
+ if (!row.actor.session)
12690
+ reasons.push("no_session");
12691
+ else if (authors.has(row.actor.session))
12692
+ reasons.push("author_session");
12693
+ if (row.self === true && !reasons.includes("no_session"))
12694
+ reasons.push("self");
12695
+ return reasons;
12696
+ };
12697
+ function effectiveOf(rows, authors) {
12698
+ let effective = null;
12699
+ for (const row of rows) {
12700
+ if (!effective) {
12701
+ effective = row;
12702
+ continue;
12703
+ }
12704
+ const rowIndependent = independenceReasons(row, authors).length === 0;
12705
+ const effIndependent = independenceReasons(effective, authors).length === 0;
12706
+ if (effIndependent && !rowIndependent)
12707
+ continue;
12708
+ if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session)
12709
+ continue;
12710
+ effective = row;
12711
+ }
12712
+ return effective;
12713
+ }
12714
+ function restackCarries(rows, branch, from, to) {
12715
+ if (!from || !to)
12716
+ return false;
12717
+ const edges = new Map;
12718
+ for (const row of rows)
12719
+ if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
12720
+ edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
12721
+ const seen = new Set([from]);
12722
+ const queue = [from];
12723
+ while (queue.length) {
12724
+ const next = queue.shift();
12725
+ if (next === to)
12726
+ return true;
12727
+ for (const target of edges.get(next) ?? [])
12728
+ if (!seen.has(target)) {
12729
+ seen.add(target);
12730
+ queue.push(target);
12731
+ }
12732
+ }
12733
+ return false;
12734
+ }
12735
+ function checkVerdicts(cwd, branch, rows) {
12736
+ const head = branchHead(cwd, branch);
12737
+ const fallbackBase = defaultBase(cwd);
12738
+ const keyCache = new Map;
12739
+ const keyFor = (base) => {
12740
+ if (!base || !head)
12741
+ return { patchId: null, diffHash: null };
12742
+ let key = keyCache.get(base);
12743
+ if (!key) {
12744
+ key = { patchId: patchId(cwd, base, head), diffHash: diffHash(cwd, base, head) };
12745
+ keyCache.set(base, key);
12746
+ }
12747
+ return key;
12748
+ };
12749
+ const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head });
12750
+ const byKind = new Map;
12751
+ for (const row of rows)
12752
+ if (row.branch === branch && isLiveVerdict(row)) {
12753
+ const kind = typeof row.kind === "string" ? row.kind : "review";
12754
+ byKind.set(kind, [...byKind.get(kind) ?? [], row]);
12755
+ }
12756
+ const verdicts = [];
12757
+ for (const [kind, list] of byKind) {
12758
+ const row = effectiveOf(list, authors);
12759
+ if (!row)
12760
+ continue;
12761
+ let basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
12762
+ if (basis === "stale" && restackCarries(rows, branch, row.head, head))
12763
+ basis = "carried";
12764
+ const current = basis !== "stale";
12765
+ const independence = independenceReasons(row, authors);
12766
+ const reasons = [
12767
+ ...current ? [] : ["stale"],
12768
+ ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
12769
+ ...independence
12770
+ ];
12771
+ verdicts.push({
12772
+ kind,
12773
+ basis,
12774
+ current,
12775
+ independent: independence.length === 0,
12776
+ accepted: reasons.length === 0,
12777
+ reasons,
12778
+ verdict: row
12779
+ });
12780
+ }
12781
+ verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
12782
+ const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
12783
+ const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
12784
+ const newestAccepted = verdicts.findLast((entry) => entry.accepted) ?? null;
12785
+ const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
12786
+ accepted: false,
12787
+ verdict: failing?.verdict ?? null,
12788
+ reasons: failing ? ["failing_verdict"] : newestCurrent?.reasons ?? ["no_verdict"]
12789
+ };
12790
+ const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
12791
+ return {
12792
+ branch,
12793
+ head,
12794
+ review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
12795
+ selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
12796
+ base: newestCurrent?.verdict.base ?? fallbackBase,
12797
+ patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
12798
+ current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
12799
+ accepted,
12800
+ verdicts,
12801
+ authors: [...authors].toSorted()
12802
+ };
12803
+ }
12804
+ var OBSERVED_ONLY = new Set(["check", ...PR_ROW_TYPES]);
12805
+
12806
+ // packages/workit-core/src/core/policy/derive.ts
12807
+ var RULES = {
12808
+ test: "check:test",
12809
+ self: "verdict:self",
12810
+ nonAuthor: "verdict:non-author",
12811
+ verified: "verdict:verified",
12812
+ product: "decision:product",
12813
+ plan: "plan"
12814
+ };
12815
+ var derived2 = (spec) => ({
12816
+ ...spec,
12817
+ id: requirementId({
12818
+ ruleId: spec.ruleId,
12819
+ scope: spec.scope,
12820
+ satisfaction: spec.satisfaction,
12821
+ before: spec.before,
12822
+ dependentAction: spec.dependentAction
12823
+ })
12824
+ });
12825
+ var sorted = (values) => [...new Set(values)].toSorted();
12826
+ var normalizedScope = (scope) => ({
12827
+ description: scope.description,
12828
+ paths: sorted(scope.paths),
12829
+ exclusions: sorted(scope.exclusions)
12830
+ });
12831
+ function deriveRequirements(judgment, scope, constraints, verification = "self") {
12832
+ const out = [];
12833
+ const base = { scope, dependentAction: null };
12834
+ if (judgment.behaviorChange)
12835
+ out.push(derived2({
12836
+ ...base,
12837
+ ruleId: RULES.test,
12838
+ dimension: "testing",
12839
+ reason: "Behavior changes: the configured test check must pass on the final tree.",
12840
+ satisfaction: "Run `workit check test` on the final tree.",
12841
+ before: "close",
12842
+ acceptanceAllowed: false
12843
+ }));
12844
+ if (judgment.behaviorChange && judgment.riskTier === "normal")
12845
+ out.push(verification === "independent" ? derived2({
12846
+ ...base,
12847
+ ruleId: RULES.nonAuthor,
12848
+ dimension: "review",
12849
+ reason: "This workspace requires independent verification: a session that did not author the change records the verdict.",
12850
+ satisfaction: "An independent session records a passing verdict: `workit ledger verdict verified --how <what it exercised>`.",
12851
+ before: "close",
12852
+ acceptanceAllowed: false
12853
+ }) : derived2({
12854
+ ...base,
12855
+ ruleId: RULES.self,
12856
+ dimension: "review",
12857
+ reason: "A behavior change needs a recorded verdict; the author's own is allowed and is shown as self-reviewed, never verified.",
12858
+ 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).",
12859
+ before: "close",
12860
+ acceptanceAllowed: false
12861
+ }));
12862
+ if (judgment.riskTier === "high")
12863
+ out.push(derived2({
12864
+ ...base,
12865
+ ruleId: RULES.verified,
12866
+ dimension: "review",
12867
+ reason: "High risk: an independent verifier must exercise the live surface.",
12868
+ satisfaction: "An independent verifier runs the real surface and records `workit ledger verdict verified --kind live --how <what it exercised>`.",
12869
+ before: "close",
12870
+ acceptanceAllowed: false
12871
+ }));
12872
+ if (judgment.productChoiceOpen)
12873
+ out.push(derived2({
12874
+ ...base,
12875
+ ruleId: RULES.product,
12876
+ dimension: "decisions",
12877
+ reason: "A product or preference choice is open; the user decides it before code is written.",
12878
+ satisfaction: 'Ask the user (with your recommendation), then record the answer: `workit ledger decision "<choice>" --why "<reason>"`.',
12879
+ before: "write",
12880
+ acceptanceAllowed: false
12881
+ }));
12882
+ if (judgment.needsPlan || judgment.riskTier === "high")
12883
+ out.push(derived2({
12884
+ ...base,
12885
+ ruleId: RULES.plan,
12886
+ dimension: "artifacts",
12887
+ reason: judgment.needsPlan ? "The work needs a written plan or spec before implementation." : "High-risk work needs a written plan before implementation.",
12888
+ 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>`.",
12889
+ before: "write",
12890
+ acceptanceAllowed: true
12891
+ }));
12892
+ for (const constraint of constraints)
12893
+ for (const obligation of constraint.requires)
12894
+ out.push(derived2({
12895
+ ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
12896
+ kind: obligation.kind,
12897
+ scope: normalizedScope(obligation.scope),
12898
+ refs: obligation.refs.map((ref) => JSON.stringify(ref)).toSorted(),
12899
+ method: obligation.method,
12900
+ before: obligation.before,
12901
+ dependentAction: obligation.dependentAction
12902
+ })}`,
12903
+ dimension: obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge"),
12904
+ scope: obligation.scope,
12905
+ reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
12906
+ satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
12907
+ before: obligation.before,
12908
+ dependentAction: obligation.dependentAction,
12909
+ acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
12910
+ }));
12911
+ const seen = new Set;
12912
+ return out.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true));
12913
+ }
12914
+ var latestJudgment = (task) => task.judgments?.at(-1)?.data ?? null;
12915
+ var ledgerView = (root) => {
12916
+ let branch;
12917
+ let rows;
12918
+ return {
12919
+ branch: () => branch === undefined ? branch = currentBranch(root) : branch,
12920
+ rows: () => {
12921
+ if (rows === undefined) {
12922
+ const read = readLedger(root);
12923
+ rows = read.ok ? read.value.rows : [];
12924
+ }
12925
+ return rows;
12926
+ }
12927
+ };
12928
+ };
12929
+ var productDecision = (task, ledger) => {
12930
+ const decisions = task.decisions.filter((entry) => entry.data.revoked === null && (entry.data.response === "approved" || entry.data.response === "stated") && entry.data.purpose !== "limitation");
12931
+ if (decisions.length)
12932
+ return {
12933
+ met: true,
12934
+ reason: "the user's decision is recorded on the task",
12935
+ decisionIds: decisions.map((entry) => entry.id)
12936
+ };
12937
+ const since = Date.parse(task.createdAt);
12938
+ const branch = ledger.branch();
12939
+ const row = ledger.rows().findLast((item) => item.type === "decision" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
12940
+ return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
12941
+ met: false,
12942
+ reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or re-judge productChoiceOpen=false if it is not open)',
12943
+ decisionIds: []
12944
+ };
12945
+ };
12946
+ var planRecorded = (task, root) => {
12947
+ const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync3(path10.resolve(root, ref.path)));
12948
+ if (cited)
12949
+ return {
12950
+ met: true,
12951
+ reason: `plan recorded: ${cited.kind === "file" ? cited.path : cited.kind === "external" ? cited.url : ""}`,
12952
+ decisionIds: []
12953
+ };
12954
+ const artifact = task.evidence.some((entry) => entry.data.kind === "artifact" && entry.data.result === "passed");
12955
+ return artifact ? { met: true, reason: "a passing artifact records the plan", decisionIds: [] } : {
12956
+ met: false,
12957
+ reason: "no plan recorded: write it (docs/ and Markdown writes stay allowed), then `workit policy assess --ref <path>`",
12958
+ decisionIds: []
12959
+ };
12960
+ };
12961
+ var verdict = (requirement, root, ledger) => {
12962
+ const branch = ledger.branch();
12963
+ if (!branch)
12964
+ return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
12965
+ const check = checkVerdicts(root, branch, ledger.rows());
12966
+ const failing = check.accepted.reasons.includes("failing_verdict");
12967
+ const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
12968
+ 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)));
12969
+ if (met)
12970
+ return {
12971
+ met: true,
12972
+ reason: `${met.accepted ? "accepted independent" : "self-reviewed (author's own)"} ${String(met.verdict.result)} verdict ${met.verdict.id}`,
12973
+ decisionIds: []
12974
+ };
12975
+ 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, " ");
12976
+ 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>\``;
12977
+ return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
12978
+ };
12979
+ function derivedRuleStatus(task, requirement, root, ledger) {
12980
+ switch (requirement.ruleId) {
12981
+ case RULES.product:
12982
+ return productDecision(task, ledger);
12983
+ case RULES.plan:
12984
+ return planRecorded(task, root);
12985
+ case RULES.self:
12986
+ case RULES.nonAuthor:
12987
+ case RULES.verified:
12988
+ return verdict(requirement, root, ledger);
12989
+ default:
12990
+ return null;
12991
+ }
12992
+ }
12993
+ function writeBlockers(task, root) {
12994
+ if (task.status === "closed" || !task.policy)
12995
+ return [];
12996
+ const ledger = ledgerView(root);
12997
+ return task.policy.requirements.flatMap((requirement) => {
12998
+ if (requirement.before !== "write")
12999
+ return [];
13000
+ const status = derivedRuleStatus(task, requirement, root, ledger);
13001
+ return status && !status.met ? [{ ruleId: requirement.ruleId, reason: status.reason }] : [];
13002
+ });
13003
+ }
13004
+
13005
+ // packages/workit-core/src/core/task-evaluation.ts
13006
+ var digestBytes = (value) => createHash7("sha256").update(value).digest("hex");
13007
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path11.sep}`);
13008
+ var relative = (root, candidate) => path11.relative(root, candidate).split(path11.sep).join("/") || ".";
13009
+ var canonicalPath = (value) => {
13010
+ if (typeof value !== "string" || !value)
13011
+ return null;
13012
+ if (value.includes("\\") || value.split("/").includes(".."))
13013
+ return null;
13014
+ const normalized = path11.posix.normalize(value);
13015
+ if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
13016
+ return null;
13017
+ return normalized === "." ? "." : normalized.replace(/\/$/, "");
13018
+ };
13019
+ var canonicalScope = (scope) => {
13020
+ const paths = scope.paths.length ? scope.paths : ["."];
13021
+ const normalizedPaths = paths.map(canonicalPath);
13022
+ const normalizedExclusions = scope.exclusions.map(canonicalPath);
13023
+ if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
13024
+ return null;
13025
+ return {
13026
+ description: scope.description,
13027
+ paths: [...new Set(normalizedPaths)],
13028
+ exclusions: [...new Set(normalizedExclusions)]
13029
+ };
13030
+ };
13031
+ var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
13032
+ var scopeMatches = (value, scope) => {
13033
+ const normalized = canonicalScope(scope);
13034
+ if (!normalized)
13035
+ return false;
13036
+ const paths = normalized.paths;
13037
+ return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
13038
+ };
13039
+ var pathRelevant = (value, scope) => scopeMatches(value, scope);
13040
+ var compareCodeUnits2 = (left, right) => {
13041
+ const length = Math.min(left.length, right.length);
13042
+ for (let index = 0;index < length; index += 1) {
13043
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
13044
+ if (difference !== 0)
13045
+ return difference;
13046
+ }
13047
+ return left.length - right.length;
13048
+ };
13049
+ var gitPaths = (root) => {
13050
+ const result = spawnSync6("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
13051
+ cwd: root,
13052
+ encoding: "buffer"
13053
+ });
13054
+ if (result.status !== 0) {
13055
+ const stderr = result.stderr?.toString("utf8") ?? "";
13056
+ return {
13057
+ paths: [],
13058
+ uncertain: !/not a git repository/i.test(stderr),
13059
+ git: !/not a git repository/i.test(stderr)
13060
+ };
13061
+ }
13062
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")) : [];
13063
+ const stagedDeleted = spawnSync6("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
13064
+ if (stagedDeleted.status !== 0) {
13065
+ const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
13066
+ if (!/not a git repository/i.test(stderr))
13067
+ return { paths, uncertain: true, git: true };
13068
+ } else if (stagedDeleted.stdout) {
13069
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")));
13070
+ }
13071
+ return { paths: [...new Set(paths)], uncertain: false, git: true };
13072
+ };
13073
+ var walk = (root, directory, output) => {
13074
+ let names;
13075
+ try {
13076
+ names = fs11.readdirSync(directory);
13077
+ } catch {
13078
+ return true;
13079
+ }
13080
+ let uncertain = false;
13081
+ for (const name of names) {
13082
+ if (name === ".git" || name === ".workit")
13083
+ continue;
13084
+ const target = path11.join(directory, name);
13085
+ let stat;
13086
+ try {
13087
+ stat = fs11.lstatSync(target);
13088
+ } catch {
13089
+ uncertain = true;
13090
+ continue;
13091
+ }
13092
+ const item = relative(root, target);
13093
+ output.add(item);
13094
+ if (stat.isDirectory() && !stat.isSymbolicLink())
13095
+ uncertain = walk(root, target, output) || uncertain;
13096
+ }
13097
+ return uncertain;
13098
+ };
13099
+ var scopeRoots = (root, scope) => {
13100
+ const roots = [];
13101
+ for (const item of scope.paths.length ? scope.paths : ["."]) {
13102
+ const target = path11.resolve(root, item);
13103
+ if (!inside(root, target))
13104
+ 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` }] });
13105
+ let ancestor = target;
13106
+ while (!fs11.existsSync(ancestor) && ancestor !== root)
13107
+ ancestor = path11.dirname(ancestor);
13108
+ try {
13109
+ if (!inside(root, fs11.realpathSync(ancestor)))
13110
+ 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` }] });
13111
+ } catch {
13112
+ return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
13113
+ }
13114
+ roots.push(target);
13115
+ }
13116
+ return success(null, null, roots);
12275
13117
  };
12276
13118
  function captureCandidate(root, scope, environment = []) {
12277
13119
  let checkout;
12278
13120
  try {
12279
- checkout = fs10.realpathSync(root);
13121
+ checkout = fs11.realpathSync(root);
12280
13122
  } catch {
12281
13123
  return failure2("invalid_input", "candidate root does not exist");
12282
13124
  }
@@ -12294,11 +13136,11 @@ function captureCandidate(root, scope, environment = []) {
12294
13136
  if (item === ".git" || item === ".workit")
12295
13137
  continue;
12296
13138
  names.add(item);
12297
- if (!fs10.existsSync(target))
13139
+ if (!fs11.existsSync(target))
12298
13140
  continue;
12299
13141
  let stat;
12300
13142
  try {
12301
- stat = fs10.lstatSync(target);
13143
+ stat = fs11.lstatSync(target);
12302
13144
  } catch {
12303
13145
  uncertain = true;
12304
13146
  continue;
@@ -12314,17 +13156,17 @@ function captureCandidate(root, scope, environment = []) {
12314
13156
  for (const item of [...names].toSorted()) {
12315
13157
  if (!scopeMatches(item, normalizedScope))
12316
13158
  continue;
12317
- const target = path9.join(checkout, item);
13159
+ const target = path11.join(checkout, item);
12318
13160
  try {
12319
- const stat = fs10.lstatSync(target);
13161
+ const stat = fs11.lstatSync(target);
12320
13162
  if (stat.isSymbolicLink()) {
12321
- const link = fs10.readlinkSync(target);
13163
+ const link = fs11.readlinkSync(target);
12322
13164
  files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
12323
13165
  } else if (stat.isFile()) {
12324
13166
  files.push({
12325
13167
  path: item,
12326
13168
  kind: "file",
12327
- digest: digestBytes(fs10.readFileSync(target)),
13169
+ digest: digestBytes(fs11.readFileSync(target)),
12328
13170
  executable: (stat.mode & 73) !== 0
12329
13171
  });
12330
13172
  } else if (!stat.isDirectory()) {
@@ -12349,7 +13191,7 @@ function captureCandidate(root, scope, environment = []) {
12349
13191
  const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
12350
13192
  if (values.some(({ value }) => value === null))
12351
13193
  completeness = "uncertain";
12352
- const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
13194
+ const headResult = spawnSync6("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
12353
13195
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
12354
13196
  completeness = "uncertain";
12355
13197
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -12489,7 +13331,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
12489
13331
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
12490
13332
  let root;
12491
13333
  try {
12492
- root = fs10.realpathSync(checkoutRoot);
13334
+ root = fs11.realpathSync(checkoutRoot);
12493
13335
  } catch {
12494
13336
  return failure2("permission_denied", "checkout root is unavailable");
12495
13337
  }
@@ -12498,23 +13340,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
12498
13340
  continue;
12499
13341
  if (!reference.digest)
12500
13342
  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}`))
13343
+ const target = path11.resolve(root, reference.path);
13344
+ if (target !== root && !target.startsWith(`${root}${path11.sep}`))
12503
13345
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
12504
13346
  fields: [
12505
13347
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
12506
13348
  ]
12507
13349
  });
12508
13350
  try {
12509
- const below = path9.relative(root, target);
13351
+ const below = path11.relative(root, target);
12510
13352
  let current = root;
12511
- for (const segment of below.split(path9.sep)) {
13353
+ for (const segment of below.split(path11.sep)) {
12512
13354
  if (!segment)
12513
13355
  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}`))
13356
+ current = path11.join(current, segment);
13357
+ const stat = fs11.lstatSync(current);
13358
+ const real = fs11.realpathSync(current);
13359
+ if (real !== root && !real.startsWith(`${root}${path11.sep}`))
12518
13360
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
12519
13361
  fields: [
12520
13362
  {
@@ -12530,7 +13372,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
12530
13372
  if (current !== target && !stat.isDirectory())
12531
13373
  return failure2("invalid_input", "document reference ancestor is not a directory");
12532
13374
  }
12533
- if (digestBytes(fs10.readFileSync(target)) !== reference.digest)
13375
+ if (digestBytes(fs11.readFileSync(target)) !== reference.digest)
12534
13376
  return failure2("permission_denied", "decision document bytes have changed");
12535
13377
  } catch {
12536
13378
  return failure2("invalid_input", "decision document is unavailable");
@@ -12615,7 +13457,35 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
12615
13457
  decisionIds: [],
12616
13458
  reason: "stored policy version is unsupported; reassessment is required"
12617
13459
  }));
13460
+ const ledger = ledgerView(checkoutRoot);
12618
13461
  return (task.policy?.requirements ?? []).map((requirement) => {
13462
+ const derived = derivedRuleStatus(task, requirement, checkoutRoot, ledger);
13463
+ if (derived) {
13464
+ if (derived.met)
13465
+ return {
13466
+ requirementId: requirement.id,
13467
+ status: "satisfied",
13468
+ evidenceIds: [],
13469
+ decisionIds: derived.decisionIds,
13470
+ reason: derived.reason
13471
+ };
13472
+ const waived = requirement.acceptanceAllowed ? applicableDecision(task, workspace, requirement, checkoutRoot) : [];
13473
+ if (waived.length)
13474
+ return {
13475
+ requirementId: requirement.id,
13476
+ status: "accepted_limitation",
13477
+ evidenceIds: [],
13478
+ decisionIds: waived.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
13479
+ reason: "an applicable approved limitation permits the missing evidence"
13480
+ };
13481
+ return {
13482
+ requirementId: requirement.id,
13483
+ status: "unsatisfied",
13484
+ evidenceIds: [],
13485
+ decisionIds: [],
13486
+ reason: derived.reason
13487
+ };
13488
+ }
12619
13489
  const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
12620
13490
  if (gate?.satisfied.length)
12621
13491
  return {
@@ -12870,10 +13740,10 @@ Autonomy contract:
12870
13740
  inferred, or guess. Prose is a note: only a configured check the CLI ran
12871
13741
  (\`workit check <name>\`) satisfies a gate; an ad-hoc
12872
13742
  \`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\`.
13743
+ - Author is not verifier: your own verdict is \`--self\` (after an observed
13744
+ \`workit check test\`), shown as self-reviewed, never verified. High risk,
13745
+ \`verification: independent\` and merge need a verifier with its own
13746
+ session (\`WORKIT_SESSION_ID=<lead>-v<n>\`; Claude Code subagents get one).
12877
13747
  - A local-commit endpoint does not imply PR readiness, and a local commit alone
12878
13748
  is not evidence of a requested remote push. Before reporting delivery,
12879
13749
  reconcile every requested item and observe it (\`workit verify-delivery\`).
@@ -12886,7 +13756,9 @@ PATH, \`npx -y @brainervirus/workit-cli\`). \`busy\` is retryable
12886
13756
  The task families (task, policy, evidence, finding, decision, worker, state)
12887
13757
  are optional continuity for tracked work; each branch has one implicit task
12888
13758
  that the first recording creates, and a solo edit needs no task. A decision
12889
- records a user choice; it never authorizes an effect.
13759
+ records a user choice; it never authorizes an effect. Judge work once with
13760
+ \`workit policy assess --judge risk= behavior= product-choice= plan=\`; an
13761
+ open choice or missing plan blocks edits until recorded.
12890
13762
  Omit revisions; a revision_conflict means one you passed is stale, so re-read.
12891
13763
  Across repositories, bind each item to its checkout, branch and endpoint,
12892
13764
  resolve competing targets before a mutation, and reconcile an uncertain
@@ -13024,7 +13896,7 @@ var exportDigest = (bundle) => sha256({
13024
13896
  task: bundle.task
13025
13897
  });
13026
13898
  // packages/workit-core/src/core/branch.ts
13027
- import { createHash as createHash8 } from "node:crypto";
13899
+ import { createHash as createHash9 } from "node:crypto";
13028
13900
 
13029
13901
  // packages/workit-core/src/core/git.ts
13030
13902
  import { execFileSync } from "node:child_process";
@@ -13098,14 +13970,14 @@ var gitContext = (workspaceRoot, paths = []) => {
13098
13970
  import {
13099
13971
  copyFileSync as copyFileSync2,
13100
13972
  cpSync as cpSync2,
13101
- existsSync as existsSync4,
13973
+ existsSync as existsSync5,
13102
13974
  mkdirSync as mkdirSync2,
13103
13975
  readFileSync as readFileSync3,
13104
13976
  readdirSync as readdirSync3,
13105
13977
  writeFileSync as writeFileSync2
13106
13978
  } from "node:fs";
13107
13979
  import os2 from "node:os";
13108
- import path10 from "node:path";
13980
+ import path12 from "node:path";
13109
13981
 
13110
13982
  // packages/workit-core/src/core/boundary.ts
13111
13983
  import { statSync as statSync2 } from "node:fs";
@@ -13164,7 +14036,7 @@ var mergePreset = (preset, input = {}, current = {
13164
14036
  protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
13165
14037
  };
13166
14038
  };
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");
14039
+ 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
14040
  var migratedDir = null;
13169
14041
  var migrationFailed = false;
13170
14042
  var ensureConfigDir = (dir = resolveConfigDir()) => {
@@ -13174,12 +14046,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
13174
14046
  migratedDir = dir;
13175
14047
  return dir;
13176
14048
  }
13177
- const legacy = path10.join(process.env.XDG_CONFIG_HOME || path10.join(os2.homedir(), ".config"), "workflow-toolkit");
13178
- if (!existsSync4(legacy)) {
14049
+ const legacy = path12.join(process.env.XDG_CONFIG_HOME || path12.join(os2.homedir(), ".config"), "workflow-toolkit");
14050
+ if (!existsSync5(legacy)) {
13179
14051
  migratedDir = dir;
13180
14052
  return dir;
13181
14053
  }
13182
- if (!migrationFailed && existsSync4(dir)) {
14054
+ if (!migrationFailed && existsSync5(dir)) {
13183
14055
  migratedDir = dir;
13184
14056
  return dir;
13185
14057
  }
@@ -13187,9 +14059,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
13187
14059
  mkdirSync2(dir, { recursive: true });
13188
14060
  diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
13189
14061
  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))
14062
+ const src = path12.join(legacy, entry.name);
14063
+ const dest = path12.join(dir, entry.name);
14064
+ if (existsSync5(dest))
13193
14065
  continue;
13194
14066
  try {
13195
14067
  if (entry.isDirectory())
@@ -13260,7 +14132,7 @@ var parseConfigResult = (raw, file) => {
13260
14132
  };
13261
14133
  };
13262
14134
  var readConfigTyped = (dir) => {
13263
- const file = path10.join(dir ?? configDir(), "config.json");
14135
+ const file = path12.join(dir ?? configDir(), "config.json");
13264
14136
  return parseConfigResult(readSafe(file), file);
13265
14137
  };
13266
14138
  var readConfig = () => {
@@ -13278,7 +14150,7 @@ var mergeConfigValues = (input, current) => ({
13278
14150
  var writeConfig = (config) => {
13279
14151
  const dir = configDir();
13280
14152
  mkdirSync2(dir, { recursive: true });
13281
- writeFileSync2(path10.join(dir, "config.json"), JSON.stringify(config, null, 2) + `
14153
+ writeFileSync2(path12.join(dir, "config.json"), JSON.stringify(config, null, 2) + `
13282
14154
  `, "utf8");
13283
14155
  };
13284
14156
  var resolveBranchPolicy = (config, workspace) => {
@@ -13317,9 +14189,9 @@ var resolveCommitPolicy = (config, workspace) => {
13317
14189
  };
13318
14190
 
13319
14191
  // packages/workit-core/src/core/workspaces.ts
13320
- import { createHash as createHash7 } from "node:crypto";
14192
+ import { createHash as createHash8 } from "node:crypto";
13321
14193
  import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
13322
- import path11 from "node:path";
14194
+ import path13 from "node:path";
13323
14195
  var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
13324
14196
  var branchPreset = custom((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
13325
14197
  var commitPreset = custom((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
@@ -13372,7 +14244,7 @@ var releaseTrackSchema = object({
13372
14244
  context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
13373
14245
  }
13374
14246
  }
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(".."))) {
14247
+ 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
14248
  context.addIssue({
13377
14249
  code: "custom",
13378
14250
  path: ["versionSource", "path"],
@@ -13391,6 +14263,7 @@ var workspaceConfigSchema = object({
13391
14263
  }).passthrough().optional(),
13392
14264
  autoApprove: union([boolean2(), array(nonBlank)]).optional(),
13393
14265
  defaultEndpoint: _enum(["commit", "pr"]).optional(),
14266
+ verification: _enum(["self", "independent"]).optional(),
13394
14267
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
13395
14268
  kind,
13396
14269
  union([boolean2(), literal("verified")]).optional()
@@ -13431,7 +14304,7 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
13431
14304
  names.add(workspace.name);
13432
14305
  }
13433
14306
  });
13434
- var workspacesRevision = (contents) => createHash7("sha256").update(contents ?? "<missing>").digest("hex");
14307
+ var workspacesRevision = (contents) => createHash8("sha256").update(contents ?? "<missing>").digest("hex");
13435
14308
  var validateWorkspacesDocument = (parsed, file) => {
13436
14309
  if (!isConfigObject(parsed)) {
13437
14310
  return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
@@ -13469,7 +14342,7 @@ var validateWorkspacesDocument = (parsed, file) => {
13469
14342
  };
13470
14343
  };
13471
14344
  var readWorkspacesResult = (dir = configDir()) => {
13472
- const file = path11.join(dir, "workspaces.json");
14345
+ const file = path13.join(dir, "workspaces.json");
13473
14346
  let raw;
13474
14347
  try {
13475
14348
  raw = readFileSync4(file, "utf8");
@@ -13640,7 +14513,7 @@ var runtimeWorkspaceIndexSchema = object({
13640
14513
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
13641
14514
  }).passthrough();
13642
14515
  var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
13643
- const file = path11.join(configDir(), "workspaces.json");
14516
+ const file = path13.join(configDir(), "workspaces.json");
13644
14517
  let raw;
13645
14518
  try {
13646
14519
  raw = readFileSync4(file, "utf8");
@@ -13703,13 +14576,13 @@ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
13703
14576
  };
13704
14577
 
13705
14578
  // 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");
14579
+ import fs12 from "node:fs";
14580
+ import path14 from "node:path";
14581
+ import { spawnSync as spawnSync7 } from "node:child_process";
14582
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path14.join(configDir(), "vcs.json");
13710
14583
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
13711
14584
  var remoteProvider = (cwd) => {
13712
- const r = spawnSync6("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
14585
+ const r = spawnSync7("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
13713
14586
  if (r.status !== 0)
13714
14587
  return null;
13715
14588
  const url = (r.stdout ?? "").trim();
@@ -13746,7 +14619,7 @@ var remoteEndpoint = (remote) => {
13746
14619
  var configuredSshEndpoint = (host) => {
13747
14620
  if (host.startsWith("-"))
13748
14621
  return null;
13749
- const resolved = spawnSync6("ssh", ["-G", host], {
14622
+ const resolved = spawnSync7("ssh", ["-G", host], {
13750
14623
  encoding: "utf8",
13751
14624
  timeout: 5000,
13752
14625
  maxBuffer: 1024 * 1024,
@@ -13796,7 +14669,7 @@ var readVcsConfig = () => {
13796
14669
  const file = vcsConfigPath();
13797
14670
  let raw;
13798
14671
  try {
13799
- raw = fs11.readFileSync(file, "utf8");
14672
+ raw = fs12.readFileSync(file, "utf8");
13800
14673
  } catch {
13801
14674
  return { status: "missing", path: file, config: {} };
13802
14675
  }
@@ -13862,7 +14735,7 @@ function vcsConfig(mode, cwd) {
13862
14735
  const prov = cfg[provider] ?? {};
13863
14736
  const out = {
13864
14737
  ok: true,
13865
- configPath: path12.resolve(cfgPath),
14738
+ configPath: path14.resolve(cfgPath),
13866
14739
  provider,
13867
14740
  defaultTargetBranch: defaultTarget,
13868
14741
  pr: cfg.pr ?? {},
@@ -13900,7 +14773,7 @@ function mergedPrStyle(limit = 6, cwd) {
13900
14773
  `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
13901
14774
  descriptionPreview: desc.slice(0, 600)
13902
14775
  });
13903
- const remoteResult = spawnSync6("git", ["remote", "get-url", "--push", "--all", "origin"], {
14776
+ const remoteResult = spawnSync7("git", ["remote", "get-url", "--push", "--all", "origin"], {
13904
14777
  cwd,
13905
14778
  encoding: "utf8"
13906
14779
  });
@@ -13916,7 +14789,7 @@ function mergedPrStyle(limit = 6, cwd) {
13916
14789
  if (!hostingApiHostMatches(remote, host))
13917
14790
  return { ok: false, error: "PR style target does not match the provider host" };
13918
14791
  if (provider === "gitlab") {
13919
- const run = (args) => spawnSync6("glab", ["api", ...args], {
14792
+ const run = (args) => spawnSync7("glab", ["api", ...args], {
13920
14793
  cwd,
13921
14794
  encoding: "utf8",
13922
14795
  env
@@ -13936,7 +14809,7 @@ function mergedPrStyle(limit = 6, cwd) {
13936
14809
  });
13937
14810
  }
13938
14811
  } else if (provider === "github") {
13939
- const r = spawnSync6("gh", [
14812
+ const r = spawnSync7("gh", [
13940
14813
  "pr",
13941
14814
  "list",
13942
14815
  "--repo",
@@ -14006,356 +14879,85 @@ var validateBranchNamePolicy = (name, policy, source) => {
14006
14879
  }
14007
14880
  return { ok: true };
14008
14881
  };
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
- }
14882
+ var branchPolicySnapshotFor = (workspaceRoot) => {
14883
+ const resolved = effectiveWorkspacePolicy(workspaceRoot, "branch");
14884
+ const branchPolicy = resolved.branchPolicy;
14885
+ const source = resolved.provenance.branchPolicy;
14886
+ const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
14887
+ const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
14888
+ const fingerprint = createHash9("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
14889
+ return {
14890
+ branchPolicy,
14891
+ namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
14892
+ source,
14893
+ fingerprint
14894
+ };
14895
+ };
14896
+ var validateBranchNameFor = (workspaceRoot, name) => {
14897
+ try {
14898
+ const resolved = branchPolicySnapshotFor(workspaceRoot);
14899
+ return validateBranchNamePolicy(name, resolved.namePolicy, resolved.source);
14900
+ } catch (error) {
14901
+ const correction = "repair or select a valid workspace policy before creating a branch";
14902
+ return {
14903
+ ok: false,
14904
+ rule: "policy_unavailable",
14905
+ source: "workspaces.json",
14906
+ attempted: name,
14907
+ correction,
14908
+ error: `policy_unavailable: cannot validate branch ${JSON.stringify(name)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
14909
+ };
14910
+ }
14911
+ };
14912
+ var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
14913
+
14914
+ // packages/workit-core/src/core/route-intent.ts
14915
+ var shellBranchTarget = (command) => {
14916
+ if (!command.trim() || /[\r\n;&|<>`'"$()\\*?{}~!#]/u.test(command) || command.includes("[") || command.includes("]"))
14917
+ return null;
14918
+ const args = command.trim().split(/\s+/u);
14919
+ if (args[0] !== "git")
14920
+ return null;
14921
+ const option = args[2];
14922
+ 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;
14923
+ return target && !target.startsWith("-") ? target : null;
14924
+ };
14925
+ var shellBranchPolicyViolation = (root, command) => {
14926
+ const target = shellBranchTarget(command);
14927
+ return target === null ? null : validateBranchNameFor(root, target);
14928
+ };
14929
+ // packages/workit-core/src/core/task-engine.ts
14930
+ import { realpathSync as realpathSync4 } from "node:fs";
14931
+
14932
+ // packages/workit-core/src/core/policy-resolver.ts
14344
14933
  function resolvePolicy(input) {
14345
- const normalized = normalizeResolverInput(input);
14346
- if (!normalized.ok)
14347
- return normalized;
14348
- const requirements = resolveRequirements(normalized.data);
14934
+ const ids = input.constraints.map((constraint) => constraint.id);
14935
+ if (new Set(ids).size !== ids.length)
14936
+ return failure2("invalid_input", "constraint IDs must be unique", {
14937
+ fields: [{ path: "constraints", reason: "duplicate constraint id" }]
14938
+ });
14349
14939
  const policy = {
14350
14940
  policyVersion: POLICY_VERSION,
14351
- inputDigest: sha256(canonicalJson(normalized.data)),
14352
- requirements
14941
+ inputDigest: sha256(canonicalJson({
14942
+ judgment: input.judgment,
14943
+ scope: input.intent.scope,
14944
+ constraints: input.constraints,
14945
+ verification: input.verification ?? "self",
14946
+ policyVersion: POLICY_VERSION
14947
+ })),
14948
+ requirements: deriveRequirements(input.judgment, input.intent.scope, input.constraints, input.verification)
14353
14949
  };
14354
14950
  const parsed = policySchema.safeParse(policy);
14355
14951
  if (!parsed.success)
14356
- return failure2("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
14952
+ return failure2("invalid_input", "resolved policy is invalid", {
14953
+ fields: parsed.error.issues.map((issue) => ({
14954
+ path: issue.path.join("."),
14955
+ reason: issue.message
14956
+ }))
14957
+ });
14357
14958
  return success(null, null, parsed.data);
14358
14959
  }
14960
+ var compareCodeUnits3 = (left, right) => left < right ? -1 : left > right ? 1 : 0;
14359
14961
  function diffPolicy(previous, next, reason, now) {
14360
14962
  if (!policySchema.safeParse(previous).success && previous !== null)
14361
14963
  throw new TypeError("previous policy is invalid");
@@ -14383,7 +14985,247 @@ function diffPolicy(previous, next, reason, now) {
14383
14985
  return parsed.data;
14384
14986
  }
14385
14987
 
14988
+ // packages/workit-core/src/core/policy/judgment.ts
14989
+ var isObject6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
14990
+ var squash = (key) => key.toLowerCase().replace(/[_-]/g, "");
14991
+ var ALIASES = {
14992
+ riskTier: ["risktier", "risk", "risklevel", "tier"],
14993
+ behaviorChange: ["behaviorchange", "behavior", "behaviour", "behaviourchange"],
14994
+ productChoiceOpen: ["productchoiceopen", "openproductchoice", "productchoice", "openchoice"],
14995
+ needsPlan: ["needsplan", "plan", "needsspec", "spec"],
14996
+ note: ["note", "notes", "reason", "why"],
14997
+ refs: ["refs", "ref", "references"]
14998
+ };
14999
+ var LEGACY_KEYS = new Set(["assessment", "facts", "signals", "consequences", "verification"]);
15000
+ var RISK = {
15001
+ trivial: "trivial",
15002
+ low: "trivial",
15003
+ minor: "trivial",
15004
+ mechanical: "trivial",
15005
+ none: "trivial",
15006
+ normal: "normal",
15007
+ medium: "normal",
15008
+ med: "normal",
15009
+ moderate: "normal",
15010
+ high: "high",
15011
+ critical: "high",
15012
+ major: "high"
15013
+ };
15014
+ var YES = new Set(["true", "yes", "y", "1", "on"]);
15015
+ var NO = new Set(["false", "no", "n", "0", "off", "none"]);
15016
+ var flag = (value, unknownAs) => {
15017
+ if (typeof value === "boolean")
15018
+ return { ok: true, value };
15019
+ if (typeof value === "number" && (value === 0 || value === 1))
15020
+ return { ok: true, value: value === 1 };
15021
+ if (typeof value !== "string")
15022
+ return { ok: false };
15023
+ const text = value.trim().toLowerCase();
15024
+ if (YES.has(text))
15025
+ return { ok: true, value: true };
15026
+ if (NO.has(text))
15027
+ return { ok: true, value: false };
15028
+ if (text === "unknown" || text === "maybe")
15029
+ return { ok: true, value: unknownAs };
15030
+ return { ok: false };
15031
+ };
15032
+ var fromLegacy = (raw, previous) => {
15033
+ const source = isObject6(raw.assessment) ? raw.assessment : raw;
15034
+ const signals = isObject6(source.signals) ? source.signals : {};
15035
+ const value = (name) => isObject6(signals[name]) ? signals[name].value : undefined;
15036
+ const known = (name, fallback) => typeof value(name) === "boolean" ? value(name) : fallback;
15037
+ const behaviorChange = known("behaviorChange", previous?.behaviorChange ?? false);
15038
+ const consequences = Array.isArray(source.consequences) ? source.consequences : [];
15039
+ const freshReview = value("behaviorChange") === true && consequences.some((item) => isObject6(item) && ["security", "data", "public_contract", "operations"].includes(String(item.area)));
15040
+ return {
15041
+ riskTier: freshReview ? "high" : value("mechanicalLowRisk") === true && !behaviorChange ? "trivial" : "normal",
15042
+ behaviorChange,
15043
+ productChoiceOpen: known("productChoiceOpen", previous?.productChoiceOpen ?? false),
15044
+ needsPlan: known("durableAgreementNeeded", previous?.needsPlan ?? false) || value("coordinationPlanNeeded") === true
15045
+ };
15046
+ };
15047
+ var DEFAULT = {
15048
+ riskTier: "normal",
15049
+ behaviorChange: false,
15050
+ productChoiceOpen: false,
15051
+ needsPlan: false,
15052
+ note: null,
15053
+ refs: []
15054
+ };
15055
+ function normalizeJudgment(raw, previous) {
15056
+ const input = isObject6(raw) ? raw : {};
15057
+ const legacy = Object.keys(input).some((key) => LEGACY_KEYS.has(key));
15058
+ const base = { ...DEFAULT, ...previous, refs: [...previous?.refs ?? []] };
15059
+ const judgment = legacy ? { ...base, ...fromLegacy(input, previous) } : { ...base };
15060
+ const ignored = [];
15061
+ const fields = [];
15062
+ for (const [key, value] of Object.entries(input)) {
15063
+ if (LEGACY_KEYS.has(key))
15064
+ continue;
15065
+ const name = Object.keys(ALIASES).find((field) => ALIASES[field].includes(squash(key)));
15066
+ if (!name) {
15067
+ ignored.push(key);
15068
+ continue;
15069
+ }
15070
+ if (name === "riskTier") {
15071
+ const tier = typeof value === "string" ? RISK[value.trim().toLowerCase()] : undefined;
15072
+ if (tier)
15073
+ judgment.riskTier = tier;
15074
+ else
15075
+ fields.push({ path: key, reason: "expected trivial|normal|high (or low|medium)" });
15076
+ } else if (name === "note") {
15077
+ judgment.note = typeof value === "string" && value.trim() ? value.trim() : null;
15078
+ } else if (name === "refs") {
15079
+ const items = Array.isArray(value) ? value : [value];
15080
+ judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
15081
+ } else {
15082
+ if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
15083
+ judgment.needsPlan = true;
15084
+ judgment.refs.push(refOf(value.trim()));
15085
+ continue;
15086
+ }
15087
+ const parsed = flag(value, name === "behaviorChange");
15088
+ if (parsed.ok)
15089
+ judgment[name] = parsed.value;
15090
+ else
15091
+ fields.push({ path: key, reason: "expected true|false (yes|no accepted)" });
15092
+ }
15093
+ }
15094
+ if (fields.length)
15095
+ return failure2("invalid_input", "judgment values are invalid", { fields });
15096
+ const seen = new Set;
15097
+ judgment.refs = judgment.refs.filter((ref) => {
15098
+ const key = JSON.stringify(ref);
15099
+ return seen.has(key) ? false : (seen.add(key), true);
15100
+ });
15101
+ const parsed = judgmentSchema.safeParse(judgment);
15102
+ if (!parsed.success)
15103
+ return failure2("invalid_input", "judgment is invalid", {
15104
+ fields: parsed.error.issues.map((issue) => ({
15105
+ path: issue.path.join("."),
15106
+ reason: issue.message
15107
+ }))
15108
+ });
15109
+ return success(null, null, { judgment: parsed.data, ignored, legacy });
15110
+ }
15111
+
15112
+ // packages/workit-core/src/autonomy.ts
15113
+ import os3 from "node:os";
15114
+ import path15 from "node:path";
15115
+
15116
+ // packages/workit-core/src/core/safe-write.ts
15117
+ import {
15118
+ closeSync as closeSync2,
15119
+ fchmodSync,
15120
+ fsyncSync,
15121
+ lstatSync as lstatSync3,
15122
+ mkdirSync as mkdirSync3,
15123
+ openSync as openSync2,
15124
+ renameSync as renameSync2,
15125
+ unlinkSync,
15126
+ writeFileSync as writeFileSync3
15127
+ } from "node:fs";
15128
+ function writeFileExclusive(file, content, mode) {
15129
+ try {
15130
+ writeFileSync3(file, content, { encoding: "utf8", flag: "wx", mode });
15131
+ return "created";
15132
+ } catch (err) {
15133
+ if (err.code === "EEXIST")
15134
+ return "preserved";
15135
+ throw err;
15136
+ }
15137
+ }
15138
+
15139
+ // packages/workit-core/src/autonomy.ts
15140
+ var accountHome = () => {
15141
+ try {
15142
+ return os3.userInfo().homedir;
15143
+ } catch {
15144
+ return os3.homedir();
15145
+ }
15146
+ };
15147
+ var grantsHome = { resolve: accountHome };
15148
+ var grantsDir = () => path15.join(grantsHome.resolve(), ".config", "workit");
15149
+ var grantsOverride = () => {
15150
+ const configured = path15.resolve(resolveConfigDir());
15151
+ 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`;
15152
+ };
15153
+ var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
15154
+ var DEFAULT_GRANTS = Object.freeze({
15155
+ push: true,
15156
+ pr: true,
15157
+ merge: false,
15158
+ release: false,
15159
+ rerun: true
15160
+ });
15161
+ var DEFAULT_ENDPOINT = "commit";
15162
+ var DEFAULT_VERIFICATION = "self";
15163
+ var verificationOf = (entry) => entry?.verification === "independent" ? "independent" : "self";
15164
+ var endpointOf = (entry) => entry?.defaultEndpoint === "pr" ? "pr" : "commit";
15165
+ var isGrantValue = (value) => value === true || value === false || value === "verified";
15166
+ var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
15167
+ var fromAutoApprove = (value) => {
15168
+ if (value === true)
15169
+ return { push: true, pr: true, merge: "verified" };
15170
+ if (!Array.isArray(value))
15171
+ return null;
15172
+ const grants = {};
15173
+ for (const item of value)
15174
+ if (item === "push" || item === "pr")
15175
+ grants[item] = true;
15176
+ else if (item === "merge")
15177
+ grants.merge = "verified";
15178
+ return Object.keys(grants).length > 0 ? grants : null;
15179
+ };
15180
+ function configuredGrants(entry) {
15181
+ const record = entry ?? {};
15182
+ const raw = record.autonomy;
15183
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
15184
+ const grants = {};
15185
+ for (const kind of GRANT_KINDS) {
15186
+ const value = raw[kind];
15187
+ if (isGrantValue(value))
15188
+ grants[kind] = normalized(kind, value);
15189
+ }
15190
+ return { grants, source: "autonomy" };
15191
+ }
15192
+ const legacy = fromAutoApprove(record.autoApprove);
15193
+ return legacy ? { grants: legacy, source: "autoApprove" } : { grants: {}, source: "default" };
15194
+ }
15195
+ function resolveAutonomy(cwd) {
15196
+ const override = grantsOverride();
15197
+ if (override)
15198
+ return {
15199
+ workspace: null,
15200
+ grants: { ...DEFAULT_GRANTS },
15201
+ configured: [],
15202
+ source: "default",
15203
+ accountConfigured: false,
15204
+ defaultEndpoint: DEFAULT_ENDPOINT,
15205
+ verification: DEFAULT_VERIFICATION,
15206
+ note: override
15207
+ };
15208
+ const workspace = resolveWorkspaceFrom(cwd, grantsDir(), process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined);
15209
+ const { grants, source } = configuredGrants(workspace);
15210
+ return {
15211
+ workspace: workspace?.name ?? null,
15212
+ grants: { ...DEFAULT_GRANTS, ...grants },
15213
+ configured: GRANT_KINDS.filter((kind) => grants[kind] !== undefined),
15214
+ source,
15215
+ accountConfigured: Boolean(workspace?.vcs?.account),
15216
+ defaultEndpoint: endpointOf(workspace),
15217
+ verification: verificationOf(workspace)
15218
+ };
15219
+ }
15220
+
14386
15221
  // packages/workit-core/src/core/task-engine.ts
15222
+ var workspaceVerification = (root) => {
15223
+ try {
15224
+ return resolveAutonomy(root).verification;
15225
+ } catch {
15226
+ return "self";
15227
+ }
15228
+ };
14387
15229
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
14388
15230
  kind,
14389
15231
  host: context.caller.host,
@@ -14576,22 +15418,7 @@ var mapRef = (ref, ids) => {
14576
15418
  return ref;
14577
15419
  return { ...ref, id: ids.get(ref.id) ?? ref.id };
14578
15420
  };
14579
- var portableRefs = (refs) => refs.filter((ref) => ref.kind === "file" || ref.kind === "record");
14580
15421
  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
15422
  var importedProvenance = (context) => ({
14596
15423
  kind: "imported",
14597
15424
  host: context.caller.host,
@@ -14603,18 +15430,6 @@ var portableTask = (task) => {
14603
15430
  if (typeof stripped !== "object" || stripped === null || Array.isArray(stripped))
14604
15431
  throw new TypeError("portable task rewrite produced a non-record");
14605
15432
  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
15433
  clone.candidates = [];
14619
15434
  return clone;
14620
15435
  };
@@ -14631,6 +15446,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
14631
15446
  for (const entry of [
14632
15447
  source.intent,
14633
15448
  ...source.assessments,
15449
+ ...source.judgments ?? [],
14634
15450
  ...source.evidence,
14635
15451
  ...source.decisions,
14636
15452
  ...source.findings,
@@ -14654,9 +15470,16 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
14654
15470
  id: fresh(entry.id),
14655
15471
  recordedAt: timestamp,
14656
15472
  provenance: imported,
14657
- data: rewriteRecordRefs(data, assessmentSchema, remap)
15473
+ data: rewriteRecordRefs(data, legacyAssessmentSchema, remap)
14658
15474
  };
14659
15475
  });
15476
+ const judgments = source.judgments?.map((entry) => ({
15477
+ ...entry,
15478
+ id: fresh(entry.id),
15479
+ recordedAt: timestamp,
15480
+ provenance: imported,
15481
+ data: rewriteRecordRefs(entry.data, judgmentSchema, remap)
15482
+ }));
14660
15483
  const evidence = source.evidence.map((entry) => ({
14661
15484
  ...entry,
14662
15485
  id: fresh(entry.id),
@@ -14724,6 +15547,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
14724
15547
  status: "paused",
14725
15548
  closure: null,
14726
15549
  assessments,
15550
+ ...judgments ? { judgments } : {},
14727
15551
  evidence,
14728
15552
  decisions: decisions.map((entry) => {
14729
15553
  const data = rewriteRecordRefs(entry.data, decisionSchema, remap);
@@ -14793,7 +15617,8 @@ class WorkitCore {
14793
15617
  retryOmittedRevisions(request, run) {
14794
15618
  return retryFilledRevisions(filledRevisions(request), run);
14795
15619
  }
14796
- implicitTask(family, request) {
15620
+ implicitTask(family, raw) {
15621
+ const request = normalizeOperationInput(family, raw, this.context.caller.host);
14797
15622
  if (typeof request !== "object" || request === null || Array.isArray(request))
14798
15623
  return success(null, null, request);
14799
15624
  const value = request;
@@ -15047,7 +15872,11 @@ class WorkitCore {
15047
15872
  if ((this.context.workerId ?? null) !== null)
15048
15873
  return failure2("permission_denied", "helpers cannot change task requirements");
15049
15874
  this.fillRevisions(input, task.data);
15050
- const resolved = this.resolve(task.data, input.assessment);
15875
+ const judged = normalizeJudgment(input.judgment, latestJudgment(task.data));
15876
+ if (!judged.ok)
15877
+ return judged;
15878
+ const judgment = judged.data.judgment;
15879
+ const resolved = this.resolve(task.data, judgment);
15051
15880
  if (!resolved.ok)
15052
15881
  return resolved;
15053
15882
  if (input.action === "preview")
@@ -15058,16 +15887,17 @@ class WorkitCore {
15058
15887
  if (current.status === "closed")
15059
15888
  return failure2("invalid_transition", "closed task cannot be assessed");
15060
15889
  const nextChange = diffPolicy(current.policy, resolved.data, "policy reassessed", mutation.now);
15061
- const assessment = {
15890
+ const entry = {
15062
15891
  id: newId(),
15063
15892
  recordedAt: mutation.now,
15064
15893
  provenance: provenance(this.context),
15065
- data: input.assessment
15894
+ data: judgment
15066
15895
  };
15067
15896
  const next = {
15068
15897
  ...current,
15069
15898
  constraints: this.context.constraints,
15070
- assessments: [...current.assessments, assessment],
15899
+ judgments: [...current.judgments ?? [], entry],
15900
+ critical: [...new Set([...current.critical ?? [], "judgments"])],
15071
15901
  policy: resolved.data,
15072
15902
  policyChanges: nextChange ? [...current.policyChanges, nextChange] : current.policyChanges
15073
15903
  };
@@ -15269,9 +16099,14 @@ class WorkitCore {
15269
16099
  return workspace;
15270
16100
  if (!workspace.data)
15271
16101
  return failure2("not_found", "workspace not found");
15272
- if (input.binding.taskId !== task.data.id || input.binding.workspaceId !== workspace.data.id)
16102
+ const binding = {
16103
+ ...input.binding,
16104
+ taskId: input.binding.taskId ?? task.data.id,
16105
+ workspaceId: input.binding.workspaceId ?? workspace.data.id
16106
+ };
16107
+ if (binding.taskId !== task.data.id || binding.workspaceId !== workspace.data.id)
15273
16108
  return failure2("invalid_input", "decision task or workspace binding is invalid");
15274
- const content = verifyDecisionContentAtRoot(this.store.root, input.binding);
16109
+ const content = verifyDecisionContentAtRoot(this.store.root, binding);
15275
16110
  if (!content.ok)
15276
16111
  return content;
15277
16112
  const knownRequirements = new Set(task.data.policy?.requirements.map((item) => item.id) ?? []);
@@ -15279,7 +16114,7 @@ class WorkitCore {
15279
16114
  return failure2("invalid_input", "decision references an unknown requirement");
15280
16115
  const base = {
15281
16116
  purpose: input.purpose,
15282
- binding: input.binding,
16117
+ binding,
15283
16118
  response: input.response,
15284
16119
  requirementIds: input.requirementIds,
15285
16120
  revoked: null
@@ -15638,16 +16473,12 @@ class WorkitCore {
15638
16473
  now: trustedNow(this.context)
15639
16474
  });
15640
16475
  }
15641
- resolve(task, assessment) {
16476
+ resolve(task, judgment) {
15642
16477
  return resolvePolicy({
15643
16478
  intent: task.intent.data,
15644
- assessment,
16479
+ judgment,
15645
16480
  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
- }
16481
+ verification: workspaceVerification(this.context.root)
15651
16482
  });
15652
16483
  }
15653
16484
  summary(task) {
@@ -15973,9 +16804,9 @@ class WorkitCore {
15973
16804
  }
15974
16805
  }
15975
16806
  // 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";
16807
+ import { spawnSync as spawnSync8 } from "node:child_process";
16808
+ import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
16809
+ import path16 from "node:path";
15979
16810
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
15980
16811
  try {
15981
16812
  gitRevisionParts(value);
@@ -16002,7 +16833,7 @@ var invalidRange = (cwd) => ({
16002
16833
  cwd
16003
16834
  });
16004
16835
  var runGit = (cwd, args) => {
16005
- const result = spawnSync7("git", args, { cwd, encoding: "utf8" });
16836
+ const result = spawnSync8("git", args, { cwd, encoding: "utf8" });
16006
16837
  return {
16007
16838
  stdout: (result.stdout ?? "").trimEnd(),
16008
16839
  stderr: (result.stderr ?? "").trimEnd(),
@@ -16013,11 +16844,11 @@ var repoRoot = (cwd) => {
16013
16844
  const r = runGit(cwd, ["rev-parse", "--show-toplevel"]);
16014
16845
  return r.exitCode === 0 && r.stdout ? r.stdout : cwd;
16015
16846
  };
16016
- var currentBranch = (cwd) => {
16847
+ var currentBranch2 = (cwd) => {
16017
16848
  const r = runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
16018
16849
  return r.exitCode === 0 && r.stdout ? r.stdout : "unknown";
16019
16850
  };
16020
- var defaultBase = (cwd) => {
16851
+ var defaultBase2 = (cwd) => {
16021
16852
  for (const ref of [
16022
16853
  "origin/main",
16023
16854
  "main",
@@ -16031,11 +16862,11 @@ var defaultBase = (cwd) => {
16031
16862
  }
16032
16863
  return "HEAD~1";
16033
16864
  };
16034
- var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${defaultBase(cwd)}...HEAD`;
16865
+ var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${defaultBase2(cwd)}...HEAD`;
16035
16866
  var isProtectedBranch = (branch) => ["main", "master", "develop", "prod", "production"].includes(branch);
16036
16867
  var isPrBranch = (branch) => branch.startsWith("feature/") || branch.startsWith("bugfix/");
16037
16868
  function resolvePrBranchContext(cwd) {
16038
- const branch = currentBranch(cwd);
16869
+ const branch = currentBranch2(cwd);
16039
16870
  if (isProtectedBranch(branch)) {
16040
16871
  return {
16041
16872
  ok: false,
@@ -16109,7 +16940,7 @@ var findPrTemplate = (cwd) => {
16109
16940
  ]) {
16110
16941
  const parts = rel.split("/");
16111
16942
  const base = parts[parts.length - 1];
16112
- const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
16943
+ const dir = parts.length > 1 ? path16.join(cwd, ...parts.slice(0, -1)) : cwd;
16113
16944
  const actual = probeCaseInsensitive(dir, base);
16114
16945
  if (actual)
16115
16946
  return [...parts.slice(0, -1), actual].join("/");
@@ -16140,8 +16971,8 @@ var diffStatForRange = (cwd, range) => {
16140
16971
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
16141
16972
  };
16142
16973
  var packageScripts = (cwd, keys) => {
16143
- const pkgPath = path13.join(cwd, "package.json");
16144
- if (!existsSync5(pkgPath))
16974
+ const pkgPath = path16.join(cwd, "package.json");
16975
+ if (!existsSync6(pkgPath))
16145
16976
  return [];
16146
16977
  try {
16147
16978
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
@@ -16166,9 +16997,9 @@ var documentationFiles = (cwd) => {
16166
16997
  if (entry.isDirectory()) {
16167
16998
  if (excluded.has(entry.name))
16168
16999
  continue;
16169
- walk(path13.join(dir, entry.name), depth + 1);
17000
+ walk(path16.join(dir, entry.name), depth + 1);
16170
17001
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
16171
- matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
17002
+ matches.push("./" + path16.relative(cwd, path16.join(dir, entry.name)).replaceAll("\\", "/"));
16172
17003
  }
16173
17004
  }
16174
17005
  };
@@ -16176,7 +17007,7 @@ var documentationFiles = (cwd) => {
16176
17007
  return matches.toSorted().slice(0, 200);
16177
17008
  };
16178
17009
  var readTrimmed2 = (file, maxLines) => {
16179
- if (!existsSync5(file))
17010
+ if (!existsSync6(file))
16180
17011
  return "";
16181
17012
  const lines = readFileSync5(file, "utf8").split(`
16182
17013
  `);
@@ -16184,7 +17015,7 @@ var readTrimmed2 = (file, maxLines) => {
16184
17015
  `);
16185
17016
  };
16186
17017
  function prReadyContext(root, range) {
16187
- const cwd = path13.resolve(repoRoot(root));
17018
+ const cwd = path16.resolve(repoRoot(root));
16188
17019
  if (range !== undefined && !isResolvableContextRange(cwd, range))
16189
17020
  return invalidRange(cwd);
16190
17021
  let stdout = "";
@@ -16200,7 +17031,7 @@ function prReadyContext(root, range) {
16200
17031
  stdout += printSection("Repository");
16201
17032
  stdout += `root: ${cwd}
16202
17033
  `;
16203
- stdout += `branch: ${currentBranch(cwd)}
17034
+ stdout += `branch: ${currentBranch2(cwd)}
16204
17035
  `;
16205
17036
  stdout += `range: ${range}
16206
17037
  `;
@@ -16242,7 +17073,7 @@ ${status.stderr}` : "");
16242
17073
  stdout += `template_path: ${template}
16243
17074
 
16244
17075
  `;
16245
- stdout += readTrimmed2(path13.join(cwd, template), 220);
17076
+ stdout += readTrimmed2(path16.join(cwd, template), 220);
16246
17077
  } else {
16247
17078
  stdout += `template_path: none
16248
17079
 
@@ -16252,7 +17083,7 @@ ${status.stderr}` : "");
16252
17083
  stdout += `
16253
17084
  `;
16254
17085
  stdout += printSection("Recent Validation Signals");
16255
- if (existsSync5(path13.join(cwd, "package.json"))) {
17086
+ if (existsSync6(path16.join(cwd, "package.json"))) {
16256
17087
  stdout += `package.json detected. Common scripts:
16257
17088
  `;
16258
17089
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -16261,7 +17092,7 @@ ${status.stderr}` : "");
16261
17092
  `) + `
16262
17093
  `;
16263
17094
  }
16264
- if (existsSync5(path13.join(cwd, "Cargo.toml")) || existsSync5(path13.join(cwd, "src-tauri/Cargo.toml"))) {
17095
+ if (existsSync6(path16.join(cwd, "Cargo.toml")) || existsSync6(path16.join(cwd, "src-tauri/Cargo.toml"))) {
16265
17096
  stdout += `Rust project detected.
16266
17097
  `;
16267
17098
  }
@@ -16294,7 +17125,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
16294
17125
  - Apply with the host-native editor, preserving the repository changelog conventions.
16295
17126
  - If Unreleased already has duplicate category headings, normalize_only first.`;
16296
17127
  function changelogContext(root, range) {
16297
- const cwd = path13.resolve(repoRoot(root));
17128
+ const cwd = path16.resolve(repoRoot(root));
16298
17129
  if (range !== undefined && !isResolvableContextRange(cwd, range))
16299
17130
  return invalidRange(cwd);
16300
17131
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -16304,7 +17135,7 @@ function changelogContext(root, range) {
16304
17135
  stdout += printSection("Repository");
16305
17136
  stdout += `root: ${cwd}
16306
17137
  `;
16307
- stdout += `branch: ${currentBranch(cwd)}
17138
+ stdout += `branch: ${currentBranch2(cwd)}
16308
17139
  `;
16309
17140
  stdout += `range: ${resolvedRange}
16310
17141
  `;
@@ -16312,8 +17143,8 @@ function changelogContext(root, range) {
16312
17143
  stdout += CHANGELOG_RULES + `
16313
17144
  `;
16314
17145
  stdout += printSection("Existing CHANGELOG.md");
16315
- const changelogPath = path13.join(cwd, "CHANGELOG.md");
16316
- if (existsSync5(changelogPath)) {
17146
+ const changelogPath = path16.join(cwd, "CHANGELOG.md");
17147
+ if (existsSync6(changelogPath)) {
16317
17148
  stdout += readTrimmed2(changelogPath, 260) + `
16318
17149
  `;
16319
17150
  } else {
@@ -16332,7 +17163,7 @@ function changelogContext(root, range) {
16332
17163
  return { stdout, stderr: "", exitCode: 0, cwd };
16333
17164
  }
16334
17165
  function docsRefreshContext(root, range) {
16335
- const cwd = path13.resolve(repoRoot(root));
17166
+ const cwd = path16.resolve(repoRoot(root));
16336
17167
  if (range !== undefined && !isResolvableContextRange(cwd, range))
16337
17168
  return invalidRange(cwd);
16338
17169
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -16342,7 +17173,7 @@ function docsRefreshContext(root, range) {
16342
17173
  stdout += printSection("Repository");
16343
17174
  stdout += `root: ${cwd}
16344
17175
  `;
16345
- stdout += `branch: ${currentBranch(cwd)}
17176
+ stdout += `branch: ${currentBranch2(cwd)}
16346
17177
  `;
16347
17178
  stdout += `range: ${resolvedRange}
16348
17179
  `;
@@ -16354,8 +17185,8 @@ function docsRefreshContext(root, range) {
16354
17185
  `) + `
16355
17186
  `;
16356
17187
  stdout += printSection("README Preview");
16357
- const readme = path13.join(cwd, "README.md");
16358
- if (existsSync5(readme)) {
17188
+ const readme = path16.join(cwd, "README.md");
17189
+ if (existsSync6(readme)) {
16359
17190
  stdout += readTrimmed2(readme, 220) + `
16360
17191
  `;
16361
17192
  } else {
@@ -16363,8 +17194,8 @@ function docsRefreshContext(root, range) {
16363
17194
  `;
16364
17195
  }
16365
17196
  stdout += printSection("Package Scripts");
16366
- const pkgPath = path13.join(cwd, "package.json");
16367
- if (existsSync5(pkgPath)) {
17197
+ const pkgPath = path16.join(cwd, "package.json");
17198
+ if (existsSync6(pkgPath)) {
16368
17199
  try {
16369
17200
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
16370
17201
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
@@ -16377,7 +17208,7 @@ function docsRefreshContext(root, range) {
16377
17208
  return { stdout, stderr: "", exitCode: 0, cwd };
16378
17209
  }
16379
17210
  function releaseNotesContext(root, rangeOrTag) {
16380
- const cwd = path13.resolve(repoRoot(root));
17211
+ const cwd = path16.resolve(repoRoot(root));
16381
17212
  if (!rangeOrTag) {
16382
17213
  return { stdout: "", stderr: `ERROR: release tag or range required
16383
17214
  `, exitCode: 1, cwd };
@@ -16391,7 +17222,7 @@ function releaseNotesContext(root, rangeOrTag) {
16391
17222
  stdout += printSection("Repository");
16392
17223
  stdout += `root: ${cwd}
16393
17224
  `;
16394
- stdout += `branch: ${currentBranch(cwd)}
17225
+ stdout += `branch: ${currentBranch2(cwd)}
16395
17226
  `;
16396
17227
  stdout += `requested: ${rangeOrTag}
16397
17228
  `;
@@ -16415,7 +17246,7 @@ function releaseNotesContext(root, rangeOrTag) {
16415
17246
  `;
16416
17247
  stdout += printSection("Existing Release Files");
16417
17248
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
16418
- if (existsSync5(path13.join(cwd, rel)))
17249
+ if (existsSync6(path16.join(cwd, rel)))
16419
17250
  stdout += rel + `
16420
17251
  `;
16421
17252
  }
@@ -16443,12 +17274,12 @@ function releaseNotesContext(root, rangeOrTag) {
16443
17274
  }
16444
17275
 
16445
17276
  // packages/workit-core/src/core/tracker-issues.ts
16446
- import { spawnSync as spawnSync9 } from "node:child_process";
17277
+ import { spawnSync as spawnSync10 } from "node:child_process";
16447
17278
 
16448
17279
  // 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";
17280
+ import fs13 from "node:fs";
17281
+ import path17 from "node:path";
17282
+ import { spawnSync as spawnSync9 } from "node:child_process";
16452
17283
  // packages/workit-core/src/forge/pr-body.ts
16453
17284
  function parseGhIssue(value) {
16454
17285
  const m = /issues\/(\d+)/.exec(value);
@@ -16458,13 +17289,13 @@ function parseGhIssue(value) {
16458
17289
  // packages/workit-core/src/core/pr-create.ts
16459
17290
  function whichOnPath(tool) {
16460
17291
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
16461
- for (const dir of (process.env.PATH ?? "").split(path14.delimiter)) {
17292
+ for (const dir of (process.env.PATH ?? "").split(path17.delimiter)) {
16462
17293
  if (!dir)
16463
17294
  continue;
16464
17295
  for (const name of names) {
16465
- const candidate = path14.join(dir, name);
17296
+ const candidate = path17.join(dir, name);
16466
17297
  try {
16467
- fs12.accessSync(candidate, fs12.constants.X_OK);
17298
+ fs13.accessSync(candidate, fs13.constants.X_OK);
16468
17299
  return candidate;
16469
17300
  } catch {}
16470
17301
  }
@@ -16473,7 +17304,7 @@ function whichOnPath(tool) {
16473
17304
  }
16474
17305
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
16475
17306
  var pushRemote = (cwd) => {
16476
- const result = spawnSync8("git", ["remote", "get-url", "--push", "--all", "origin"], {
17307
+ const result = spawnSync9("git", ["remote", "get-url", "--push", "--all", "origin"], {
16477
17308
  cwd,
16478
17309
  encoding: "utf8"
16479
17310
  });
@@ -16504,7 +17335,7 @@ var defaultCli = (bin, available) => {
16504
17335
  if (!available)
16505
17336
  return null;
16506
17337
  return (args, root) => {
16507
- const out = spawnSync9(bin, args, {
17338
+ const out = spawnSync10(bin, args, {
16508
17339
  cwd: root,
16509
17340
  encoding: "utf8",
16510
17341
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -16582,8 +17413,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
16582
17413
  };
16583
17414
 
16584
17415
  // packages/workit-core/src/core/youtrack.ts
16585
- import fs13 from "node:fs";
16586
- import path15 from "node:path";
17416
+ import fs14 from "node:fs";
17417
+ import path18 from "node:path";
16587
17418
 
16588
17419
  // packages/workit-core/src/core/templates.ts
16589
17420
  var repoRoot2 = assetRoot();
@@ -16596,22 +17427,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
16596
17427
  // packages/workit-core/src/core/youtrack.ts
16597
17428
  var ISSUE_RE = /^[A-Z]+-\d+$/;
16598
17429
  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");
17430
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(configDir(), "youtrack.json");
17431
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(resolveConfigDir(), "youtrack.json");
16601
17432
  var youTrackTokenModeOk = (p) => {
16602
17433
  if (process.platform === "win32")
16603
17434
  return true;
16604
- const mode = fs13.statSync(p).mode & 511;
17435
+ const mode = fs14.statSync(p).mode & 511;
16605
17436
  return mode === 384;
16606
17437
  };
16607
17438
  function readYouTrackConfig(required) {
16608
17439
  const cfgPath = youTrackConfigPath();
16609
- if (!fs13.existsSync(cfgPath)) {
17440
+ if (!fs14.existsSync(cfgPath)) {
16610
17441
  return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
16611
17442
  }
16612
17443
  let parsed;
16613
17444
  try {
16614
- parsed = JSON.parse(fs13.readFileSync(cfgPath, "utf8"));
17445
+ parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
16615
17446
  } catch {
16616
17447
  return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
16617
17448
  }
@@ -16622,11 +17453,11 @@ function readYouTrackConfig(required) {
16622
17453
  }
16623
17454
  var readYouTrackContextConfig = () => {
16624
17455
  const cfgPath = youTrackReadOnlyConfigPath();
16625
- if (!fs13.existsSync(cfgPath))
17456
+ if (!fs14.existsSync(cfgPath))
16626
17457
  return { data: {} };
16627
17458
  let parsed;
16628
17459
  try {
16629
- parsed = JSON.parse(fs13.readFileSync(cfgPath, "utf8"));
17460
+ parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
16630
17461
  } catch {
16631
17462
  return { error: "YouTrack configuration is unavailable" };
16632
17463
  }
@@ -16665,12 +17496,12 @@ var youTrackToken = () => {
16665
17496
  if ("error" in loaded)
16666
17497
  return loaded;
16667
17498
  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))
17499
+ const tokenPath = tokenFile ? path18.isAbsolute(tokenFile) ? path18.resolve(tokenFile) : path18.resolve(process.cwd(), tokenFile) : "";
17500
+ if (!tokenPath || !fs14.existsSync(tokenPath))
16670
17501
  return { error: "missing youtrack.token" };
16671
17502
  if (!youTrackTokenModeOk(tokenPath))
16672
17503
  return { error: "youtrack.token mode must be 0600" };
16673
- const token = fs13.readFileSync(tokenPath, "utf8").trim();
17504
+ const token = fs14.readFileSync(tokenPath, "utf8").trim();
16674
17505
  if (!token)
16675
17506
  return { error: "empty token file" };
16676
17507
  if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
@@ -16716,7 +17547,7 @@ function youTrackTokenCreateUrl() {
16716
17547
  const desc = String(defaults.description ?? "OpenCode workit — /wk-issue-update and /wk-meetings");
16717
17548
  const scopes = Array.isArray(defaults.scopes) ? defaults.scopes : ["YouTrack"];
16718
17549
  const base = String(config.baseUrl ?? "").replace(/\/+$/, "");
16719
- const tokenFile = String(config.tokenFile ?? path15.join(path15.dirname(loaded.path), "youtrack.token"));
17550
+ const tokenFile = String(config.tokenFile ?? path18.join(path18.dirname(loaded.path), "youtrack.token"));
16720
17551
  const tab = String(defaults.profileTab ?? "account-security");
16721
17552
  const createUrl = base ? `${base}/users/me?${new URLSearchParams({ tab })}` : null;
16722
17553
  const docsUrl = "https://www.jetbrains.com/help/youtrack/cloud/manage-permanent-token.html";
@@ -16725,7 +17556,7 @@ function youTrackTokenCreateUrl() {
16725
17556
  tokenName: name,
16726
17557
  tokenDescription: desc,
16727
17558
  scopes,
16728
- tokenFile: path15.resolve(tokenFile),
17559
+ tokenFile: path18.resolve(tokenFile),
16729
17560
  createUrl,
16730
17561
  docsUrl,
16731
17562
  prefillSupported: false,
@@ -16771,9 +17602,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
16771
17602
  } catch {
16772
17603
  continue;
16773
17604
  }
16774
- if (!fs13.existsSync(full))
17605
+ if (!fs14.existsSync(full))
16775
17606
  continue;
16776
- const text = fs13.readFileSync(full, "utf8");
17607
+ const text = fs14.readFileSync(full, "utf8");
16777
17608
  const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
16778
17609
  if (m)
16779
17610
  return m[1];
@@ -17065,22 +17896,22 @@ function resolveGitRevision(root, value) {
17065
17896
  }
17066
17897
  function resolveInside(root, candidate) {
17067
17898
  const base = realpathSync5(root);
17068
- const target = path16.resolve(base, candidate);
17069
- if (target !== base && !target.startsWith(base + path16.sep)) {
17899
+ const target = path19.resolve(base, candidate);
17900
+ if (target !== base && !target.startsWith(base + path19.sep)) {
17070
17901
  throw new Error("path must stay inside repository root");
17071
17902
  }
17072
17903
  let ancestor = target;
17073
- while (!existsSync6(ancestor))
17074
- ancestor = path16.dirname(ancestor);
17904
+ while (!existsSync7(ancestor))
17905
+ ancestor = path19.dirname(ancestor);
17075
17906
  const canonicalAncestor = realpathSync5(ancestor);
17076
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path16.sep)) {
17907
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path19.sep)) {
17077
17908
  throw new Error("path must stay inside repository root");
17078
17909
  }
17079
17910
  return target;
17080
17911
  }
17081
17912
  function run2(root, executable, args, env = {}) {
17082
17913
  const cwd = realpathSync5(root);
17083
- const result = spawnSync10(executable, args, {
17914
+ const result = spawnSync11(executable, args, {
17084
17915
  cwd,
17085
17916
  encoding: "utf8",
17086
17917
  env: { ...process.env, ...env }
@@ -17117,7 +17948,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
17117
17948
  };
17118
17949
  });
17119
17950
  // packages/workit-core/src/hooks/context.ts
17120
- import path17 from "node:path";
17951
+ import path20 from "node:path";
17121
17952
  var MIGRATION_PENDING_NOTE = "workit migration pending — run `workit task status`";
17122
17953
  var migrationPending = (result) => !result.ok && result.code === "needs_input" && (result.error ?? "").startsWith(MIGRATION_PENDING);
17123
17954
  var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
@@ -17196,7 +18027,7 @@ function entryCompactContext(store, entry, session, context) {
17196
18027
  return null;
17197
18028
  const freshness = { mode: "signal", current: () => recentSignal(store.root) };
17198
18029
  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)] : []));
18030
+ const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path20.resolve(store.root, ref.path)] : []));
17200
18031
  const observedFiles = filesKey(files);
17201
18032
  const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
17202
18033
  if (!compact.ok)
@@ -17218,12 +18049,291 @@ var shellPolicy = (cwd, command) => {
17218
18049
  const policy = shellBranchPolicyViolation(cwd, command);
17219
18050
  return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
17220
18051
  };
18052
+ // packages/workit-core/src/hooks/write-gate.ts
18053
+ import { realpathSync as realpathSync6 } from "node:fs";
18054
+ import path21 from "node:path";
18055
+ var NONE2 = { kind: "none" };
18056
+ var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
18057
+ var canonical2 = (file) => {
18058
+ let rest = "";
18059
+ for (let dir = path21.resolve(file);; dir = path21.dirname(dir)) {
18060
+ try {
18061
+ return path21.join(realpathSync6(dir), rest);
18062
+ } catch {
18063
+ if (path21.dirname(dir) === dir)
18064
+ return path21.resolve(file);
18065
+ rest = rest ? path21.join(path21.basename(dir), rest) : path21.basename(dir);
18066
+ }
18067
+ }
18068
+ };
18069
+ var exempt = (cwd, root, file, plans) => {
18070
+ const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
18071
+ const relative = path21.relative(fold(canonical2(root)), fold(canonical2(path21.resolve(cwd, file))));
18072
+ if (relative.startsWith("..") || path21.isAbsolute(relative))
18073
+ return true;
18074
+ const posix = relative.split(path21.sep).join("/");
18075
+ const segments = posix.split("/");
18076
+ return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
18077
+ };
18078
+ var segmentsOf = (command) => {
18079
+ const segments = [];
18080
+ let current = { words: [], redirects: [] };
18081
+ let word = "";
18082
+ let inWord = false;
18083
+ let pending = null;
18084
+ const heredocs = [];
18085
+ const flushWord = () => {
18086
+ if (!inWord)
18087
+ return;
18088
+ if (pending === "redirect") {
18089
+ if (!word.startsWith("&"))
18090
+ current.redirects.push(word);
18091
+ } else if (pending === "heredoc")
18092
+ heredocs.push(word.replace(/^-/, ""));
18093
+ else if (pending !== "input")
18094
+ current.words.push(word);
18095
+ pending = null;
18096
+ word = "";
18097
+ inWord = false;
18098
+ };
18099
+ const flushSegment = () => {
18100
+ flushWord();
18101
+ if (current.words.length || current.redirects.length)
18102
+ segments.push(current);
18103
+ current = { words: [], redirects: [] };
18104
+ };
18105
+ for (let index = 0;index < command.length; index++) {
18106
+ const char = command[index];
18107
+ if (char === "'" || char === '"') {
18108
+ const end = command.indexOf(char, index + 1);
18109
+ word += command.slice(index + 1, end < 0 ? undefined : end);
18110
+ inWord = true;
18111
+ index = end < 0 ? command.length : end;
18112
+ } else if (char === "\\" && index + 1 < command.length) {
18113
+ word += command[++index];
18114
+ inWord = true;
18115
+ } else if (char === `
18116
+ `) {
18117
+ flushSegment();
18118
+ while (heredocs.length) {
18119
+ const delimiter = heredocs.shift();
18120
+ const lines = command.slice(index + 1).split(`
18121
+ `);
18122
+ const at = lines.findIndex((line) => line.trim() === delimiter);
18123
+ const skipped = (at < 0 ? lines : lines.slice(0, at + 1)).join(`
18124
+ `).length + 1;
18125
+ index += skipped;
18126
+ }
18127
+ } else if (/\s/.test(char))
18128
+ flushWord();
18129
+ else if (";&|()".includes(char)) {
18130
+ if (char === "&" && command[index + 1] === ">")
18131
+ continue;
18132
+ flushSegment();
18133
+ } else if (char === ">") {
18134
+ if (inWord && /^\d$/.test(word)) {
18135
+ word = "";
18136
+ inWord = false;
18137
+ } else
18138
+ flushWord();
18139
+ if (command[index + 1] === ">")
18140
+ index++;
18141
+ if (command[index + 1] === "&") {
18142
+ index++;
18143
+ word = "&";
18144
+ inWord = true;
18145
+ }
18146
+ pending = "redirect";
18147
+ } else if (char === "<") {
18148
+ flushWord();
18149
+ if (command[index + 1] === "<") {
18150
+ index++;
18151
+ if (command[index + 1] === "<")
18152
+ index++;
18153
+ pending = "heredoc";
18154
+ } else
18155
+ pending = "input";
18156
+ } else {
18157
+ word += char;
18158
+ inWord = true;
18159
+ }
18160
+ }
18161
+ flushSegment();
18162
+ return segments;
18163
+ };
18164
+ var PREFIXES = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
18165
+ var operands = (words) => words.filter((item) => !item.startsWith("-"));
18166
+ var editsOf = (input) => {
18167
+ let words = input;
18168
+ while (words.length && (PREFIXES.has(words[0]) || /^\w+=/.test(words[0])))
18169
+ words = words.slice(1);
18170
+ const [verb, ...args] = words;
18171
+ const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
18172
+ if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
18173
+ return [];
18174
+ switch (name) {
18175
+ case "touch":
18176
+ case "mkdir":
18177
+ case "rm":
18178
+ case "rmdir":
18179
+ case "tee":
18180
+ case "mv":
18181
+ case "truncate":
18182
+ return operands(args);
18183
+ case "cp":
18184
+ case "ln":
18185
+ case "install": {
18186
+ const target = args.indexOf("-t");
18187
+ if (target >= 0)
18188
+ return args[target + 1] ? [args[target + 1]] : null;
18189
+ const last = operands(args).at(-1);
18190
+ return last ? [last] : null;
18191
+ }
18192
+ case "dd":
18193
+ return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
18194
+ case "sed":
18195
+ case "perl": {
18196
+ if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
18197
+ return [];
18198
+ const scripted = args.some((item) => item === "-e" || item === "-f");
18199
+ const files = [];
18200
+ for (let index = 0;index < args.length; index++) {
18201
+ if (args[index] === "-e" || args[index] === "-f")
18202
+ index++;
18203
+ else if (!args[index].startsWith("-"))
18204
+ files.push(args[index]);
18205
+ }
18206
+ return scripted ? files : files.slice(1);
18207
+ }
18208
+ case "patch":
18209
+ return null;
18210
+ case "set-content":
18211
+ case "add-content":
18212
+ case "out-file":
18213
+ case "new-item":
18214
+ case "ni":
18215
+ case "remove-item":
18216
+ case "del":
18217
+ case "copy-item":
18218
+ case "move-item": {
18219
+ const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
18220
+ if (flag >= 0)
18221
+ return args[flag + 1] ? [args[flag + 1]] : null;
18222
+ const files = operands(args);
18223
+ if (name === "move-item")
18224
+ return files.length ? files : null;
18225
+ const file = name === "copy-item" ? files.at(-1) : files[0];
18226
+ return file ? [file] : null;
18227
+ }
18228
+ case "git": {
18229
+ const sub = operands(args)[0];
18230
+ if (sub === "apply")
18231
+ return null;
18232
+ if (sub === "restore" || sub === "mv" || sub === "rm")
18233
+ return operands(args).slice(1);
18234
+ if (sub === "checkout" && args.includes("--"))
18235
+ return args.slice(args.indexOf("--") + 1);
18236
+ return [];
18237
+ }
18238
+ default:
18239
+ return [];
18240
+ }
18241
+ };
18242
+ var shellWrites = (command) => {
18243
+ const targets = [];
18244
+ let unknown = false;
18245
+ for (const segment of segmentsOf(command)) {
18246
+ targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
18247
+ const edits = editsOf(segment.words);
18248
+ if (edits === null)
18249
+ unknown = true;
18250
+ else
18251
+ targets.push(...edits);
18252
+ }
18253
+ if (!unknown && targets.length === 0)
18254
+ return { writes: false, targets: null };
18255
+ return { writes: true, targets: unknown ? null : targets };
18256
+ };
18257
+ var cache2 = new Map;
18258
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path21.resolve(root, file)) ?? "-").join("\x00");
18259
+ var gateFor = (cwd) => {
18260
+ const store = new TaskStore(cwd);
18261
+ const listed = store.listTaskIndex();
18262
+ if (!listed.ok)
18263
+ return null;
18264
+ const key = store.currentKey();
18265
+ if (!key.ok)
18266
+ return null;
18267
+ const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
18268
+ if (!entry)
18269
+ return { blockers: [], root: store.root, plans: new Set };
18270
+ const ledger = ledgerPath(store.root);
18271
+ const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
18272
+ const hit = cache2.get(store.root);
18273
+ if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
18274
+ return hit;
18275
+ const task = store.readTask(entry.id);
18276
+ if (!task.ok)
18277
+ return null;
18278
+ const workspace = store.readWorkspace();
18279
+ const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
18280
+ const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
18281
+ const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
18282
+ return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
18283
+ });
18284
+ const gate = {
18285
+ blockers,
18286
+ root: store.root,
18287
+ plans,
18288
+ key: cacheKey,
18289
+ planKey: planKeyOf(store.root, plans)
18290
+ };
18291
+ cache2.set(store.root, gate);
18292
+ if (cache2.size > 32)
18293
+ cache2.delete(cache2.keys().next().value);
18294
+ return gate;
18295
+ };
18296
+ function writeGate(cwd, paths) {
18297
+ try {
18298
+ const gate = gateFor(cwd);
18299
+ if (!gate || gate.blockers.length === 0)
18300
+ return NONE2;
18301
+ if (paths && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
18302
+ return NONE2;
18303
+ const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
18304
+ return {
18305
+ kind: "deny",
18306
+ reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
18307
+ unblock: gate.blockers[0].reason
18308
+ };
18309
+ } catch {
18310
+ return NONE2;
18311
+ }
18312
+ }
18313
+ // packages/workit-core/src/hooks/hosts/fields.ts
18314
+ var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
18315
+ var WRITE_TOOLS = new Set([
18316
+ "write",
18317
+ "edit",
18318
+ "multiedit",
18319
+ "notebookedit",
18320
+ "applypatch",
18321
+ "patch",
18322
+ "delete",
18323
+ "remove",
18324
+ "rename",
18325
+ "mkdir",
18326
+ "mv",
18327
+ "cp",
18328
+ "touch",
18329
+ "strreplace",
18330
+ "searchreplace",
18331
+ "editfile",
18332
+ "writefile"
18333
+ ]);
17221
18334
  // packages/workit-core/src/hooks/handle.ts
17222
18335
  var offered = new Set;
17223
18336
  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
18337
  // packages/workit-core/src/hooks/hosts/claude-code.ts
17228
18338
  var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
17229
18339
  var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
@@ -17250,6 +18360,7 @@ var OPENCODE_DESCRIPTOR = {
17250
18360
  "context.turn": { support: "native", native: 'session.hook("context")' },
17251
18361
  "shell.pre": { support: "native", native: 'permission.hook("evaluate")' },
17252
18362
  "tool.pre": { support: "native", native: 'tool.hook("execute.before")' },
18363
+ "write.pre": { support: "native", native: 'permission.hook("evaluate")' },
17253
18364
  "shell.post": { support: "native", native: 'tool.hook("execute.after")' },
17254
18365
  "subagent.start": { support: "native", native: 'tool.hook("execute.before") subagent' },
17255
18366
  "subagent.stop": { support: "native", native: 'tool.hook("execute.after") subagent' },
@@ -17304,11 +18415,11 @@ var OPENCODE_DESCRIPTOR = {
17304
18415
  ]
17305
18416
  };
17306
18417
  // packages/workit-opencode/src/shared/init-apply.ts
17307
- import path22 from "node:path";
18418
+ import path26 from "node:path";
17308
18419
 
17309
18420
  // 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";
18421
+ import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
18422
+ import path22 from "node:path";
17312
18423
  var GITIGNORE_ENTRIES = [
17313
18424
  "# workit: SDD working state (never commit)",
17314
18425
  "docs/*/sdd/",
@@ -17328,8 +18439,8 @@ var GITIGNORE_ENTRIES = [
17328
18439
  var ensureProjectGitignore = (workspaceRoot, confirmed) => {
17329
18440
  if (!confirmed)
17330
18441
  return { ok: false, error: "confirmed: true required" };
17331
- const file = path18.join(workspaceRoot, ".gitignore");
17332
- const existing = existsSync7(file) ? readFileSync6(file, "utf8") : "";
18442
+ const file = path22.join(workspaceRoot, ".gitignore");
18443
+ const existing = existsSync8(file) ? readFileSync6(file, "utf8") : "";
17333
18444
  const existingLines = new Set(existing.split(`
17334
18445
  `).map((l) => l.trim()).filter(Boolean));
17335
18446
  const added = [];
@@ -17344,37 +18455,37 @@ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
17344
18455
  const separator = existing && !existing.endsWith(`
17345
18456
  `) ? `
17346
18457
  ` : "";
17347
- writeFileSync3(file, existing + separator + (existing ? `
18458
+ writeFileSync4(file, existing + separator + (existing ? `
17348
18459
  ` : "") + append.join(`
17349
18460
  `) + `
17350
18461
  `, "utf8");
17351
- } else if (!existsSync7(file)) {
17352
- writeFileSync3(file, "", "utf8");
18462
+ } else if (!existsSync8(file)) {
18463
+ writeFileSync4(file, "", "utf8");
17353
18464
  }
17354
18465
  return { ok: true, path: file, added };
17355
18466
  };
17356
18467
 
17357
18468
  // 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";
18469
+ import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
18470
+ import path23 from "node:path";
17360
18471
  var repoRoot3 = assetRoot();
17361
- var templatesDir = () => path19.join(repoRoot3, "templates", "hygiene");
18472
+ var templatesDir = () => path23.join(repoRoot3, "templates", "hygiene");
17362
18473
  var packageJson = (root) => {
17363
- if (!existsSync8(path19.join(root, "package.json")))
18474
+ if (!existsSync9(path23.join(root, "package.json")))
17364
18475
  return null;
17365
18476
  try {
17366
- return JSON.parse(readFileSync7(path19.join(root, "package.json"), "utf8"));
18477
+ return JSON.parse(readFileSync7(path23.join(root, "package.json"), "utf8"));
17367
18478
  } catch {
17368
18479
  return null;
17369
18480
  }
17370
18481
  };
17371
18482
  var isOpenSource = (root) => {
17372
- if (existsSync8(path19.join(root, "LICENSE")))
18483
+ if (existsSync9(path23.join(root, "LICENSE")))
17373
18484
  return true;
17374
18485
  const pkg = packageJson(root);
17375
18486
  if (pkg && !pkg.private)
17376
18487
  return true;
17377
- return path19.basename(root) === "workflow-toolkit";
18488
+ return path23.basename(root) === "workflow-toolkit";
17378
18489
  };
17379
18490
  var licenseHolder = (root) => {
17380
18491
  const pkg = packageJson(root);
@@ -17397,13 +18508,13 @@ var planHygieneFiles = (root, opts = {}) => {
17397
18508
  files.push("LICENSE", "CONTRIBUTING.md");
17398
18509
  const planned = [];
17399
18510
  for (const file of files) {
17400
- if (existsSync8(path19.join(root, file)))
18511
+ if (existsSync9(path23.join(root, file)))
17401
18512
  continue;
17402
- const tpl = path19.join(templatesDir(), file);
17403
- if (!existsSync8(tpl))
18513
+ const tpl = path23.join(templatesDir(), file);
18514
+ if (!existsSync9(tpl))
17404
18515
  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 });
18516
+ const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g, path23.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
18517
+ planned.push({ path: path23.join(root, file), content });
17407
18518
  }
17408
18519
  return planned;
17409
18520
  };
@@ -17412,48 +18523,25 @@ var ensureHygieneFiles = (root, opts) => {
17412
18523
  return { ok: false, error: "confirmed: true required" };
17413
18524
  const created = [];
17414
18525
  for (const planned of planHygieneFiles(root, opts)) {
17415
- writeFileSync4(planned.path, planned.content, "utf8");
17416
- created.push(path19.basename(planned.path));
18526
+ writeFileSync5(planned.path, planned.content, "utf8");
18527
+ created.push(path23.basename(planned.path));
17417
18528
  }
17418
18529
  return { ok: true, created };
17419
18530
  };
17420
18531
 
17421
18532
  // 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
- }
18533
+ import fs15 from "node:fs";
18534
+ import path25 from "node:path";
18535
+ import { spawnSync as spawnSync13 } from "node:child_process";
17448
18536
 
17449
18537
  // packages/workit-core/src/core/workspace-branch-policy.ts
17450
18538
  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";
18539
+ import path24 from "node:path";
17452
18540
  import { isDeepStrictEqual } from "node:util";
17453
18541
 
17454
18542
  // packages/workit-core/src/core/branch-policy.ts
17455
- import { spawnSync as spawnSync11 } from "node:child_process";
17456
- var PREFIXES = {
18543
+ import { spawnSync as spawnSync12 } from "node:child_process";
18544
+ var PREFIXES2 = {
17457
18545
  feature: "feature/*",
17458
18546
  bugfix: "bugfix/*",
17459
18547
  release: "release/*",
@@ -17461,7 +18549,7 @@ var PREFIXES = {
17461
18549
  };
17462
18550
  var detectBranchPolicy = (workspaceRoot) => {
17463
18551
  const branchExists = (name) => {
17464
- const r = spawnSync11("git", ["branch", "--list", name], {
18552
+ const r = spawnSync12("git", ["branch", "--list", name], {
17465
18553
  cwd: workspaceRoot,
17466
18554
  encoding: "utf8"
17467
18555
  });
@@ -17478,7 +18566,7 @@ var detectBranchPolicy = (workspaceRoot) => {
17478
18566
  integration: "merge",
17479
18567
  protected: root ? [root, "develop"] : ["develop"],
17480
18568
  allowed: [...PRESETS.gitflow.allowed],
17481
- prefixes: PREFIXES
18569
+ prefixes: PREFIXES2
17482
18570
  };
17483
18571
  }
17484
18572
  if (root === "main") {
@@ -17488,7 +18576,7 @@ var detectBranchPolicy = (workspaceRoot) => {
17488
18576
  integration: "pr",
17489
18577
  protected: [root],
17490
18578
  allowed: ["*"],
17491
- prefixes: PREFIXES
18579
+ prefixes: PREFIXES2
17492
18580
  };
17493
18581
  }
17494
18582
  if (root === "master") {
@@ -17498,7 +18586,7 @@ var detectBranchPolicy = (workspaceRoot) => {
17498
18586
  integration: "pr",
17499
18587
  protected: [root],
17500
18588
  allowed: ["*"],
17501
- prefixes: PREFIXES
18589
+ prefixes: PREFIXES2
17502
18590
  };
17503
18591
  }
17504
18592
  return {
@@ -17507,7 +18595,7 @@ var detectBranchPolicy = (workspaceRoot) => {
17507
18595
  integration: "merge",
17508
18596
  protected: [],
17509
18597
  allowed: [],
17510
- prefixes: PREFIXES
18598
+ prefixes: PREFIXES2
17511
18599
  };
17512
18600
  };
17513
18601
 
@@ -17521,7 +18609,7 @@ var readFileSafe = (p) => {
17521
18609
  };
17522
18610
  function applyWorkspaceBranchPolicy(opts) {
17523
18611
  const { workspace_root, env = process.env } = opts;
17524
- const dir = path20.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
18612
+ const dir = path24.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
17525
18613
  const {
17526
18614
  status,
17527
18615
  path: wsPath,
@@ -17533,7 +18621,7 @@ function applyWorkspaceBranchPolicy(opts) {
17533
18621
  if (status === "malformed" || status === "invalid")
17534
18622
  return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
17535
18623
  const detection = detectBranchPolicy(workspace_root);
17536
- const name = String(env.WORKFLOW_BP_NAME ?? path20.basename(workspace_root));
18624
+ const name = String(env.WORKFLOW_BP_NAME ?? path24.basename(workspace_root));
17537
18625
  const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
17538
18626
  const policy = {
17539
18627
  preset: detection.preset,
@@ -17582,7 +18670,7 @@ function applyWorkspaceBranchPolicy(opts) {
17582
18670
  ok: false,
17583
18671
  error: `workspaces.json changed while applying the branch policy: ${wsPath}`
17584
18672
  };
17585
- mkdirSync4(path20.dirname(wsPath), { recursive: true });
18673
+ mkdirSync4(path24.dirname(wsPath), { recursive: true });
17586
18674
  const tmp = `${wsPath}.${process.pid}.tmp`;
17587
18675
  try {
17588
18676
  writeFileSync6(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + `
@@ -17617,7 +18705,7 @@ function applyWorkspaceBranchPolicy(opts) {
17617
18705
  var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
17618
18706
  var readJson = (p) => {
17619
18707
  try {
17620
- const parsed = JSON.parse(fs14.readFileSync(p, "utf8"));
18708
+ const parsed = JSON.parse(fs15.readFileSync(p, "utf8"));
17621
18709
  return isConfigObject(parsed) ? parsed : null;
17622
18710
  } catch {
17623
18711
  return null;
@@ -17629,7 +18717,7 @@ var resolveInitProvider = (cwd) => {
17629
18717
  return env.toLowerCase();
17630
18718
  const root = cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
17631
18719
  try {
17632
- const out = spawnSync12("git", ["remote", "get-url", "origin"], {
18720
+ const out = spawnSync13("git", ["remote", "get-url", "origin"], {
17633
18721
  cwd: root,
17634
18722
  encoding: "utf8"
17635
18723
  });
@@ -17647,7 +18735,7 @@ var youtrackJsonContent = (dir) => {
17647
18735
  const meetingIssue = process.env.WORKFLOW_YT_MEETING_ISSUE;
17648
18736
  return {
17649
18737
  baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://youtrack.example.com",
17650
- tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path21.join(dir, "youtrack.token"),
18738
+ tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path25.join(dir, "youtrack.token"),
17651
18739
  ...meetingIssue ? {
17652
18740
  meetingIssue,
17653
18741
  meetingIssues: { general: { issue: meetingIssue, label: "General meetings" } }
@@ -17677,19 +18765,19 @@ var vcsJsonContent = () => {
17677
18765
  };
17678
18766
  function initApplyData(action, env = process.env) {
17679
18767
  const dir = String(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
17680
- fs14.mkdirSync(dir, { recursive: true });
18768
+ fs15.mkdirSync(dir, { recursive: true });
17681
18769
  switch (action) {
17682
18770
  case "youtrack_json": {
17683
- const out = path21.join(dir, "youtrack.json");
17684
- fs14.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
18771
+ const out = path25.join(dir, "youtrack.json");
18772
+ fs15.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
17685
18773
  `, "utf8");
17686
18774
  return { action, ok: true, path: out };
17687
18775
  }
17688
18776
  case "youtrack_token_placeholder": {
17689
- const p = path21.join(dir, "youtrack.token");
18777
+ const p = path25.join(dir, "youtrack.token");
17690
18778
  const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER2 + `
17691
18779
  `, 384) === "preserved";
17692
- const abs = path21.resolve(p);
18780
+ const abs = path25.resolve(p);
17693
18781
  return {
17694
18782
  action,
17695
18783
  ok: true,
@@ -17701,14 +18789,14 @@ function initApplyData(action, env = process.env) {
17701
18789
  };
17702
18790
  }
17703
18791
  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) + `
18792
+ const jsonOut = path25.join(dir, "youtrack.json");
18793
+ const tokenOut = path25.join(dir, "youtrack.token");
18794
+ fs15.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
17707
18795
  `, "utf8");
17708
18796
  const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER2 + `
17709
18797
  `, 384) === "preserved";
17710
- const configPath = path21.resolve(jsonOut);
17711
- const tokenPath = path21.resolve(tokenOut);
18798
+ const configPath = path25.resolve(jsonOut);
18799
+ const tokenPath = path25.resolve(tokenOut);
17712
18800
  const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
17713
18801
  process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
17714
18802
  let tokenCreate = {};
@@ -17760,10 +18848,10 @@ function initApplyData(action, env = process.env) {
17760
18848
  }
17761
18849
  }
17762
18850
  case "vcs_scaffold": {
17763
- const jsonOut = path21.join(dir, "vcs.json");
17764
- fs14.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
18851
+ const jsonOut = path25.join(dir, "vcs.json");
18852
+ fs15.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
17765
18853
  `, "utf8");
17766
- const configPath = path21.resolve(jsonOut);
18854
+ const configPath = path25.resolve(jsonOut);
17767
18855
  const prev = process.env.WORKFLOW_VCS_CONFIG;
17768
18856
  process.env.WORKFLOW_VCS_CONFIG = configPath;
17769
18857
  try {
@@ -17882,7 +18970,7 @@ var executeInitApply = (args, directory, runtime) => {
17882
18970
  protectedNames: args.branch_policy_protected
17883
18971
  }, current);
17884
18972
  writeConfig(next);
17885
- return output(ok({ action: "config", path: path22.join(configDir(), "config.json"), ...next }));
18973
+ return output(ok({ action: "config", path: path26.join(configDir(), "config.json"), ...next }));
17886
18974
  }
17887
18975
  const env = Object.fromEntries(Object.entries({
17888
18976
  WORKFLOW_YT_BASE_URL: args.base_url,
@@ -17898,15 +18986,15 @@ var executeInitApply = (args, directory, runtime) => {
17898
18986
  };
17899
18987
 
17900
18988
  // packages/workit-opencode/src/shared/session.ts
17901
- import { realpathSync as realpathSync6 } from "node:fs";
17902
- import path23 from "node:path";
18989
+ import { realpathSync as realpathSync7 } from "node:fs";
18990
+ import path27 from "node:path";
17903
18991
  var sameWorkspace = (expected, observed) => {
17904
18992
  if (typeof observed !== "string" || !observed)
17905
18993
  return false;
17906
18994
  try {
17907
- return realpathSync6(expected) === realpathSync6(observed);
18995
+ return realpathSync7(expected) === realpathSync7(observed);
17908
18996
  } catch {
17909
- return path23.resolve(expected) === path23.resolve(observed);
18997
+ return path27.resolve(expected) === path27.resolve(observed);
17910
18998
  }
17911
18999
  };
17912
19000
 
@@ -17919,8 +19007,8 @@ var objectSchema = (properties, required) => ({
17919
19007
  });
17920
19008
  var familyTools = OPERATION_FAMILIES.map((family) => ({
17921
19009
  name: `workit_${family}`,
17922
- description: `Workit ${family} operations backed by the shared task contract.`,
17923
- input: { type: "object", ...boundedOperationJsonSchema(family, OPERATION_SCHEMA_DEPTH) }
19010
+ description: operationDescription(family),
19011
+ input: flatOperationJsonSchema(family)
17924
19012
  }));
17925
19013
  var WORKIT_TOOL_CATALOG = [
17926
19014
  ...familyTools,
@@ -17977,8 +19065,8 @@ var workitFamilyOf = (toolName) => {
17977
19065
  };
17978
19066
 
17979
19067
  // packages/workit-opencode/src/tools/context.ts
17980
- import { realpathSync as realpathSync7 } from "node:fs";
17981
- import path24 from "node:path";
19068
+ import { realpathSync as realpathSync8 } from "node:fs";
19069
+ import path28 from "node:path";
17982
19070
  var createContextTool = () => ({
17983
19071
  description: "Read Git, pull request, YouTrack, issue, changelog, release, or affected-file context.",
17984
19072
  execute: async (args, context) => {
@@ -17988,7 +19076,7 @@ var createContextTool = () => ({
17988
19076
  let root;
17989
19077
  try {
17990
19078
  const cwd = parsed.data.cwd;
17991
- root = realpathSync7(cwd ? path24.resolve(context.directory, cwd) : context.directory);
19079
+ root = realpathSync8(cwd ? path28.resolve(context.directory, cwd) : context.directory);
17992
19080
  } catch {
17993
19081
  return JSON.stringify(failure2("invalid_input", "context target directory cannot be resolved"), null, 2);
17994
19082
  }
@@ -18510,24 +19598,37 @@ var injectCompactionContext = (root, sessionID, system) => {
18510
19598
  };
18511
19599
 
18512
19600
  // packages/workit-opencode/src/v2/permissions.ts
19601
+ var WRITE_ACTIONS = new Set(["edit", "write", "patch"]);
18513
19602
  var evaluateShellPermission = (root, event) => {
18514
- if (event.action !== "shell" || event.effect === "deny")
19603
+ if (event.effect === "deny")
18515
19604
  return;
18516
- for (const resource of event.resources) {
18517
- if (typeof resource !== "string")
18518
- continue;
19605
+ const deny = (reason) => {
19606
+ event.effect = "deny";
19607
+ event.message = reason;
19608
+ };
19609
+ const resources = event.resources.filter((resource) => typeof resource === "string");
19610
+ if (WRITE_ACTIONS.has(event.action)) {
19611
+ const decision = writeGate(root, resources);
19612
+ if (decision.kind === "deny")
19613
+ deny(decision.reason);
19614
+ return;
19615
+ }
19616
+ if (event.action !== "shell")
19617
+ return;
19618
+ for (const resource of resources) {
18519
19619
  const decision = shellPolicy(root, resource);
18520
- if (decision.kind === "deny") {
18521
- event.effect = "deny";
18522
- event.message = decision.reason;
18523
- return;
18524
- }
19620
+ if (decision.kind === "deny")
19621
+ return deny(decision.reason);
19622
+ const writes = shellWrites(resource);
19623
+ const gated = writes.writes ? writeGate(root, writes.targets) : null;
19624
+ if (gated?.kind === "deny")
19625
+ return deny(gated.reason);
18525
19626
  }
18526
19627
  };
18527
19628
 
18528
19629
  // packages/workit-opencode/src/v2/registry.ts
18529
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
18530
- import path26 from "node:path";
19630
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
19631
+ import path30 from "node:path";
18531
19632
 
18532
19633
  // packages/workit-core/src/core/skill-manifests.ts
18533
19634
  var WORKIT_METHOD_SKILLS = [
@@ -18559,34 +19660,34 @@ var WORKIT_SKILL_ALIASES = {
18559
19660
  if (false) {}
18560
19661
 
18561
19662
  // packages/workit-opencode/src/shared/assets.ts
18562
- import { existsSync as existsSync9, realpathSync as realpathSync8 } from "node:fs";
18563
- import path25 from "node:path";
19663
+ import { existsSync as existsSync10, realpathSync as realpathSync9 } from "node:fs";
19664
+ import path29 from "node:path";
18564
19665
  import { fileURLToPath as fileURLToPath2 } from "node:url";
18565
19666
  var moduleDir = () => {
18566
19667
  try {
18567
- return realpathSync8(path25.dirname(fileURLToPath2(import.meta.url)));
19668
+ return realpathSync9(path29.dirname(fileURLToPath2(import.meta.url)));
18568
19669
  } catch {
18569
- return path25.dirname(fileURLToPath2(import.meta.url));
19670
+ return path29.dirname(fileURLToPath2(import.meta.url));
18570
19671
  }
18571
19672
  };
18572
19673
  var packageRoot2 = () => {
18573
19674
  const dir = moduleDir();
18574
19675
  for (const candidate of [
18575
- path25.resolve(dir, ".."),
18576
- path25.resolve(dir, "..", ".."),
18577
- path25.resolve(dir, "..", "..", "..")
19676
+ path29.resolve(dir, ".."),
19677
+ path29.resolve(dir, "..", ".."),
19678
+ path29.resolve(dir, "..", "..", "..")
18578
19679
  ]) {
18579
- if (existsSync9(path25.join(candidate, "package.json")))
19680
+ if (existsSync10(path29.join(candidate, "package.json")))
18580
19681
  return candidate;
18581
19682
  }
18582
- return path25.resolve(dir, "..");
19683
+ return path29.resolve(dir, "..");
18583
19684
  };
18584
19685
  var skillsRoot = () => {
18585
19686
  const root = packageRoot2();
18586
- const canonical = path25.resolve(root, "..", "workit-core", "skills");
18587
- if (existsSync9(path25.join(root, "src")) && existsSync9(canonical))
19687
+ const canonical = path29.resolve(root, "..", "workit-core", "skills");
19688
+ if (existsSync10(path29.join(root, "src")) && existsSync10(canonical))
18588
19689
  return canonical;
18589
- return path25.join(root, "assets", "skills");
19690
+ return path29.join(root, "assets", "skills");
18590
19691
  };
18591
19692
 
18592
19693
  // packages/workit-opencode/src/v2/registry.ts
@@ -18610,15 +19711,15 @@ var parseFrontmatter = (markdown) => {
18610
19711
  };
18611
19712
  var registerSkills = async (ctx) => {
18612
19713
  const skillsDir = skillsRoot();
18613
- if (!existsSync10(skillsDir))
19714
+ if (!existsSync11(skillsDir))
18614
19715
  return new Set;
18615
19716
  const listed = await ctx.skill.list().catch(() => ({ data: [] }));
18616
- const own = (skill) => typeof skill.path === "string" && path26.resolve(skill.path).startsWith(skillsDir + path26.sep);
19717
+ const own = (skill) => typeof skill.path === "string" && path30.resolve(skill.path).startsWith(skillsDir + path30.sep);
18617
19718
  const userSkills = new Set((listed?.data ?? []).filter((skill) => !own(skill)).map((skill) => skill.id));
18618
19719
  const packaged = new Map;
18619
19720
  for (const id of WORKIT_METHOD_SKILLS) {
18620
- const file = path26.join(skillsDir, id, "SKILL.md");
18621
- if (!userSkills.has(id) && existsSync10(file))
19721
+ const file = path30.join(skillsDir, id, "SKILL.md");
19722
+ if (!userSkills.has(id) && existsSync11(file))
18622
19723
  packaged.set(id, file);
18623
19724
  }
18624
19725
  await ctx.skill.transform((editor) => {
@@ -18664,25 +19765,25 @@ var registerCommands = async (ctx, registeredSkills) => {
18664
19765
  };
18665
19766
 
18666
19767
  // packages/workit-opencode/src/stale-sources.ts
18667
- import { existsSync as existsSync11, readdirSync as readdirSync5 } from "node:fs";
18668
- import path27 from "node:path";
19768
+ import { existsSync as existsSync12, readdirSync as readdirSync5 } from "node:fs";
19769
+ import path31 from "node:path";
18669
19770
  import { fileURLToPath as fileURLToPath3 } from "node:url";
18670
19771
  var typescriptFiles = (directory) => {
18671
- if (!existsSync11(directory))
19772
+ if (!existsSync12(directory))
18672
19773
  return [];
18673
19774
  return readdirSync5(directory, { withFileTypes: true }).flatMap((entry) => {
18674
- const file = path27.join(directory, entry.name);
19775
+ const file = path31.join(directory, entry.name);
18675
19776
  return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
18676
19777
  });
18677
19778
  };
18678
19779
  var pluginSourceFiles = [
18679
19780
  ...new Set([
18680
19781
  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"))
19782
+ ...typescriptFiles(path31.join(packageRoot2(), "src")),
19783
+ path31.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
19784
+ ...typescriptFiles(path31.join(packageRoot2(), "..", "workit-core", "src", "core"))
18684
19785
  ])
18685
- ].filter((file) => existsSync11(file)).toSorted();
19786
+ ].filter((file) => existsSync12(file)).toSorted();
18686
19787
 
18687
19788
  // packages/workit-opencode/src/v2/plugin.ts
18688
19789
  var sessionFacts = async (ctx, sessionID) => {
@@ -18736,7 +19837,7 @@ var setup = async (ctx) => {
18736
19837
  });
18737
19838
  const contextTool = createContextTool();
18738
19839
  const runFamily = (family, input, session, store, workerId) => {
18739
- const parsed = parseOperation(family, input);
19840
+ const parsed = parseAdvertisedOperation(family, input, "opencode");
18740
19841
  if (!parsed.ok)
18741
19842
  return parsed;
18742
19843
  const core = new WorkitCore(store, {