@brainervirus/workit-codex 6.0.1 → 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.
@@ -6621,13 +6621,13 @@ var require_dist = __commonJS(function(exports, module) {
6621
6621
  });
6622
6622
 
6623
6623
  // packages/workit-codex/scripts/launch-mcp.ts
6624
- import { existsSync as existsSync8, realpathSync as realpathSync7, statSync as statSync3 } from "node:fs";
6625
- import path18 from "node:path";
6624
+ import { existsSync as existsSync9, realpathSync as realpathSync7, statSync as statSync3 } from "node:fs";
6625
+ import path21 from "node:path";
6626
6626
 
6627
6627
  // packages/workit-core/src/core.ts
6628
- import { spawnSync as spawnSync10 } from "node:child_process";
6629
- import { existsSync as existsSync6, realpathSync as realpathSync5 } from "node:fs";
6630
- import path16 from "node:path";
6628
+ import { spawnSync as spawnSync11 } from "node:child_process";
6629
+ import { existsSync as existsSync7, realpathSync as realpathSync5 } from "node:fs";
6630
+ import path19 from "node:path";
6631
6631
 
6632
6632
  // packages/workit-core/src/core/task-contract.ts
6633
6633
  import { createHash, randomUUID } from "node:crypto";
@@ -12854,12 +12854,6 @@ var numberProcessor = (schema, ctx, _json, params) => {
12854
12854
  var booleanProcessor = (_schema, _ctx, json, _params) => {
12855
12855
  json.type = "boolean";
12856
12856
  };
12857
- var bigintProcessor = (schema, ctx, json, params) => {
12858
- handleUnrepresentable(schema, ctx, json, params, "BigInt cannot be represented in JSON Schema");
12859
- };
12860
- var symbolProcessor = (schema, ctx, json, params) => {
12861
- handleUnrepresentable(schema, ctx, json, params, "Symbols cannot be represented in JSON Schema");
12862
- };
12863
12857
  var nullProcessor = (_schema, ctx, json, _params) => {
12864
12858
  if (ctx.target === "openapi-3.0") {
12865
12859
  json.type = "string";
@@ -12869,20 +12863,10 @@ var nullProcessor = (_schema, ctx, json, _params) => {
12869
12863
  json.type = "null";
12870
12864
  }
12871
12865
  };
12872
- var undefinedProcessor = (schema, ctx, json, params) => {
12873
- handleUnrepresentable(schema, ctx, json, params, "Undefined cannot be represented in JSON Schema");
12874
- };
12875
- var voidProcessor = (schema, ctx, json, params) => {
12876
- handleUnrepresentable(schema, ctx, json, params, "Void cannot be represented in JSON Schema");
12877
- };
12878
12866
  var neverProcessor = (_schema, _ctx, json, _params) => {
12879
12867
  json.not = {};
12880
12868
  };
12881
- var anyProcessor = (_schema, _ctx, _json, _params) => {};
12882
12869
  var unknownProcessor = (_schema, _ctx, _json, _params) => {};
12883
- var dateProcessor = (schema, ctx, json, params) => {
12884
- handleUnrepresentable(schema, ctx, json, params, "Date cannot be represented in JSON Schema");
12885
- };
12886
12870
  var enumProcessor = (schema, _ctx, json, _params) => {
12887
12871
  const def = schema._zod.def;
12888
12872
  const values = getEnumValues(def.entries);
@@ -12935,54 +12919,12 @@ var literalProcessor = (schema, ctx, json, params) => {
12935
12919
  json.enum = vals;
12936
12920
  }
12937
12921
  };
12938
- var nanProcessor = (schema, ctx, json, params) => {
12939
- handleUnrepresentable(schema, ctx, json, params, "NaN cannot be represented in JSON Schema");
12940
- };
12941
- var templateLiteralProcessor = (schema, _ctx, json, _params) => {
12942
- const _json = json;
12943
- const pattern = schema._zod.pattern;
12944
- if (!pattern)
12945
- throw new Error("Pattern not found in template literal");
12946
- _json.type = "string";
12947
- _json.pattern = pattern.source;
12948
- };
12949
- var fileProcessor = (schema, _ctx, json, _params) => {
12950
- const _json = json;
12951
- _json.type = "string";
12952
- _json.format = "binary";
12953
- _json.contentEncoding = "binary";
12954
- const { minimum, maximum, mime } = aggregateChecks(schema);
12955
- if (minimum !== undefined)
12956
- _json.minLength = minimum;
12957
- if (maximum !== undefined)
12958
- _json.maxLength = maximum;
12959
- if (!mime)
12960
- return;
12961
- if (mime.length === 0)
12962
- _json.not = {};
12963
- else if (mime.length === 1)
12964
- _json.contentMediaType = mime[0];
12965
- else
12966
- _json.anyOf = mime.map((m) => ({ contentMediaType: m }));
12967
- };
12968
- var successProcessor = (_schema, _ctx, json, _params) => {
12969
- json.type = "boolean";
12970
- };
12971
12922
  var customProcessor = (schema, ctx, json, params) => {
12972
12923
  handleUnrepresentable(schema, ctx, json, params, "Custom types cannot be represented in JSON Schema");
12973
12924
  };
12974
- var functionProcessor = (schema, ctx, json, params) => {
12975
- handleUnrepresentable(schema, ctx, json, params, "Function types cannot be represented in JSON Schema");
12976
- };
12977
12925
  var transformProcessor = (schema, ctx, json, params) => {
12978
12926
  handleUnrepresentable(schema, ctx, json, params, "Transforms cannot be represented in JSON Schema");
12979
12927
  };
12980
- var mapProcessor = (schema, ctx, json, params) => {
12981
- handleUnrepresentable(schema, ctx, json, params, "Map cannot be represented in JSON Schema");
12982
- };
12983
- var setProcessor = (schema, ctx, json, params) => {
12984
- handleUnrepresentable(schema, ctx, json, params, "Set cannot be represented in JSON Schema");
12985
- };
12986
12928
  var arrayProcessor = (schema, ctx, _json, params) => {
12987
12929
  const json = _json;
12988
12930
  const def = schema._zod.def;
@@ -13076,70 +13018,6 @@ var intersectionProcessor = (schema, ctx, json, params) => {
13076
13018
  json.allOf = allOf;
13077
13019
  ctx.intersections.push(allOf);
13078
13020
  };
13079
- var tupleProcessor = (schema, ctx, _json, params) => {
13080
- const json = _json;
13081
- const def = schema._zod.def;
13082
- json.type = "array";
13083
- const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
13084
- const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
13085
- const prefixItems = def.items.map((x, i) => processSchema(x, ctx, {
13086
- ...params,
13087
- path: [...params.path, prefixPath, i]
13088
- }));
13089
- const rest = def.rest ? processSchema(def.rest, ctx, {
13090
- ...params,
13091
- path: [...params.path, restPath, ...ctx.target === "openapi-3.0" ? [def.items.length] : []]
13092
- }) : null;
13093
- let minItems = def.items.length;
13094
- while (minItems > 0) {
13095
- const item = def.items[minItems - 1];
13096
- const optional = ctx.io === "input" ? inputOptin(item) !== undefined : item._zod.optout === "optional";
13097
- if (!optional)
13098
- break;
13099
- minItems--;
13100
- }
13101
- const maxItems = def.items.length;
13102
- const isClosed = !def.rest;
13103
- if (ctx.target === "draft-2020-12") {
13104
- json.prefixItems = prefixItems;
13105
- if (isClosed) {
13106
- json.items = false;
13107
- } else if (rest) {
13108
- json.items = rest;
13109
- }
13110
- if (minItems > 0)
13111
- json.minItems = minItems;
13112
- if (isClosed)
13113
- json.maxItems = maxItems;
13114
- } else if (ctx.target === "openapi-3.0") {
13115
- json.items = {
13116
- anyOf: prefixItems
13117
- };
13118
- if (rest) {
13119
- json.items.anyOf.push(rest);
13120
- }
13121
- if (minItems > 0)
13122
- json.minItems = minItems;
13123
- if (isClosed)
13124
- json.maxItems = maxItems;
13125
- } else {
13126
- json.items = prefixItems;
13127
- if (isClosed) {
13128
- json.additionalItems = false;
13129
- } else if (rest) {
13130
- json.additionalItems = rest;
13131
- }
13132
- if (minItems > 0)
13133
- json.minItems = minItems;
13134
- if (isClosed)
13135
- json.maxItems = maxItems;
13136
- }
13137
- const { minimum, maximum } = aggregateChecks(schema);
13138
- if (typeof minimum === "number")
13139
- json.minItems = minimum;
13140
- if (typeof maximum === "number")
13141
- json.maxItems = maximum;
13142
- };
13143
13021
  function stringifyKeyNames(bySchema, json, visited) {
13144
13022
  if (json.$ref) {
13145
13023
  if (visited.has(json))
@@ -13326,99 +13204,12 @@ var readonlyProcessor = (schema, ctx, json, params) => {
13326
13204
  seen.ref = def.innerType;
13327
13205
  json.readOnly = true;
13328
13206
  };
13329
- var promiseProcessor = (schema, ctx, _json, params) => {
13330
- const def = schema._zod.def;
13331
- processSchema(def.innerType, ctx, params);
13332
- const seen = ctx.seen.get(schema);
13333
- seen.ref = def.innerType;
13334
- };
13335
13207
  var optionalProcessor = (schema, ctx, _json, params) => {
13336
13208
  const def = schema._zod.def;
13337
13209
  processSchema(def.innerType, ctx, params);
13338
13210
  const seen = ctx.seen.get(schema);
13339
13211
  seen.ref = def.innerType;
13340
13212
  };
13341
- var lazyProcessor = (schema, ctx, _json, params) => {
13342
- const innerType = schema._zod.innerType;
13343
- processSchema(innerType, ctx, params);
13344
- const seen = ctx.seen.get(schema);
13345
- seen.ref = innerType;
13346
- };
13347
- var allProcessors = {
13348
- string: stringProcessor,
13349
- number: numberProcessor,
13350
- boolean: booleanProcessor,
13351
- bigint: bigintProcessor,
13352
- symbol: symbolProcessor,
13353
- null: nullProcessor,
13354
- undefined: undefinedProcessor,
13355
- void: voidProcessor,
13356
- never: neverProcessor,
13357
- any: anyProcessor,
13358
- unknown: unknownProcessor,
13359
- date: dateProcessor,
13360
- enum: enumProcessor,
13361
- literal: literalProcessor,
13362
- nan: nanProcessor,
13363
- template_literal: templateLiteralProcessor,
13364
- file: fileProcessor,
13365
- success: successProcessor,
13366
- custom: customProcessor,
13367
- function: functionProcessor,
13368
- transform: transformProcessor,
13369
- map: mapProcessor,
13370
- set: setProcessor,
13371
- array: arrayProcessor,
13372
- object: objectProcessor,
13373
- union: unionProcessor,
13374
- intersection: intersectionProcessor,
13375
- tuple: tupleProcessor,
13376
- record: recordProcessor,
13377
- nullable: nullableProcessor,
13378
- nonoptional: nonoptionalProcessor,
13379
- default: defaultProcessor,
13380
- prefault: prefaultProcessor,
13381
- catch: catchProcessor,
13382
- pipe: pipeProcessor,
13383
- readonly: readonlyProcessor,
13384
- promise: promiseProcessor,
13385
- optional: optionalProcessor,
13386
- lazy: lazyProcessor
13387
- };
13388
- function toJSONSchema(input, params) {
13389
- if ("_idmap" in input) {
13390
- const registry = input;
13391
- const ctx = initializeContext({ ...params, processors: allProcessors });
13392
- const defs = {};
13393
- for (const entry of registry._idmap.entries()) {
13394
- const [_, schema] = entry;
13395
- processSchema(schema, ctx);
13396
- }
13397
- const schemas = {};
13398
- const external = {
13399
- registry,
13400
- uri: params?.uri,
13401
- defs
13402
- };
13403
- ctx.external = external;
13404
- for (const entry of registry._idmap.entries()) {
13405
- const [key, schema] = entry;
13406
- extractDefs(ctx, schema);
13407
- assignProp(schemas, key, finalize(ctx, schema));
13408
- }
13409
- if (Object.keys(defs).length > 0) {
13410
- const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions";
13411
- schemas.__shared = {
13412
- [defsSegment]: defs
13413
- };
13414
- }
13415
- return { schemas };
13416
- }
13417
- const ctx = initializeContext({ ...params, processors: allProcessors });
13418
- processSchema(input, ctx);
13419
- extractDefs(ctx, input);
13420
- return finalize(ctx, input);
13421
- }
13422
13213
  // node_modules/zod/v4/classic/errors.js
13423
13214
  var _installedErrorProtos = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);
13424
13215
  function _lazyMethod(proto, key, make) {
@@ -14610,76 +14401,16 @@ var provenanceSchema = object({
14610
14401
  workerId: nullableId
14611
14402
  }).strict();
14612
14403
  var entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict();
14613
- var factSchema = object({
14614
- statement: text,
14615
- basis: _enum(["observed", "inferred", "unknown"]),
14616
- refs: array(refSchema)
14617
- }).strict().check((ctx) => {
14618
- if (ctx.value.basis === "observed" && ctx.value.refs.length === 0)
14619
- ctx.issues.push({
14620
- code: "custom",
14621
- input: ctx.value,
14622
- message: "observed facts require supporting references",
14623
- path: ["refs"]
14624
- });
14625
- });
14626
- var signalSchema = object({
14627
- value: union([boolean2(), literal("unknown")]),
14628
- basis: _enum(["observed", "inferred", "unknown"]),
14629
- reason: text,
14404
+ var RISK_TIERS = ["trivial", "normal", "high"];
14405
+ var judgmentSchema = object({
14406
+ riskTier: _enum(RISK_TIERS),
14407
+ behaviorChange: boolean2(),
14408
+ productChoiceOpen: boolean2(),
14409
+ needsPlan: boolean2(),
14410
+ note: text.nullable(),
14630
14411
  refs: array(refSchema)
14631
- }).strict().check((ctx) => {
14632
- const signal = ctx.value;
14633
- const valid = signal.value === "unknown" ? signal.basis === "unknown" : signal.basis === "observed" || signal.basis === "inferred";
14634
- if (!valid)
14635
- ctx.issues.push({
14636
- code: "custom",
14637
- input: signal,
14638
- message: "signal value and basis disagree",
14639
- path: []
14640
- });
14641
- if (signal.basis === "observed" && signal.refs.length === 0)
14642
- ctx.issues.push({
14643
- code: "custom",
14644
- input: signal,
14645
- message: "observed signals require supporting references",
14646
- path: ["refs"]
14647
- });
14648
- });
14649
- var signalSet = object({
14650
- approachUnknown: signalSchema,
14651
- productChoiceOpen: signalSchema,
14652
- behaviorChange: signalSchema,
14653
- mechanicalLowRisk: signalSchema,
14654
- durableAgreementNeeded: signalSchema,
14655
- coordinationPlanNeeded: signalSchema,
14656
- helperUseful: signalSchema,
14657
- testFirstPractical: signalSchema
14658
- }).strict();
14659
- var consequenceSchema = object({
14660
- area: _enum([
14661
- "behavior",
14662
- "public_contract",
14663
- "data",
14664
- "security",
14665
- "operations",
14666
- "reversibility",
14667
- "consumers"
14668
- ]),
14669
- fact: factSchema
14670
- }).strict();
14671
- var verificationSchema = object({
14672
- claim: text,
14673
- scope: scopeSchema,
14674
- availableChecks: array(refSchema),
14675
- gaps: array(text)
14676
- }).strict();
14677
- var assessmentSchema = object({
14678
- facts: array(factSchema),
14679
- signals: signalSet,
14680
- consequences: array(consequenceSchema),
14681
- verification: array(verificationSchema)
14682
14412
  }).strict();
14413
+ var legacyAssessmentSchema = looseObject({});
14683
14414
  var constraintSchema = object({
14684
14415
  id: nonEmpty,
14685
14416
  kind: _enum(["host", "project", "user"]),
@@ -14916,7 +14647,8 @@ var taskRecordSchema = object({
14916
14647
  progress: progressSchema,
14917
14648
  pauseReason: text.nullable().optional(),
14918
14649
  runtime: runtimeSchema.optional(),
14919
- assessments: array(entrySchema(assessmentSchema)),
14650
+ assessments: array(entrySchema(legacyAssessmentSchema)),
14651
+ judgments: array(entrySchema(judgmentSchema)).optional(),
14920
14652
  policy: policySchema.nullable(),
14921
14653
  policyChanges: array(policyChangeSchema),
14922
14654
  candidates: array(candidateSchema),
@@ -15092,14 +14824,15 @@ var taskOperations = {
15092
14824
  decisionIds: array(id).optional()
15093
14825
  })
15094
14826
  };
14827
+ var rawJudgment = record(string2(), unknown());
15095
14828
  var policyOperations = {
15096
14829
  assess: operation({
15097
14830
  action: literal("assess"),
15098
14831
  ...taskId,
15099
14832
  expectedRevision: revision.optional(),
15100
- assessment: assessmentSchema
14833
+ judgment: rawJudgment
15101
14834
  }),
15102
- preview: operation({ action: literal("preview"), ...taskId, assessment: assessmentSchema }),
14835
+ preview: operation({ action: literal("preview"), ...taskId, judgment: rawJudgment }),
15103
14836
  explain: operation({ action: literal("explain"), ...taskId })
15104
14837
  };
15105
14838
  var evidenceOperations = {
@@ -15138,7 +14871,10 @@ var decisionOperations = {
15138
14871
  ...taskId,
15139
14872
  expectedRevision: revision.optional(),
15140
14873
  purpose: decisionSchema.shape.purpose,
15141
- binding: decisionSchema.shape.binding,
14874
+ binding: decisionSchema.shape.binding.extend({
14875
+ taskId: id.optional(),
14876
+ workspaceId: id.optional()
14877
+ }),
15142
14878
  response: decisionSchema.shape.response,
15143
14879
  requirementIds: array(digest)
15144
14880
  }),
@@ -15192,6 +14928,15 @@ var operationSchemas = {
15192
14928
  worker: discriminatedUnion("action", Object.values(workerOperations)),
15193
14929
  state: discriminatedUnion("action", Object.values(stateOperations))
15194
14930
  };
14931
+ var OPERATION_ACTIONS = {
14932
+ task: Object.keys(taskOperations),
14933
+ policy: Object.keys(policyOperations),
14934
+ evidence: Object.keys(evidenceOperations),
14935
+ finding: Object.keys(findingOperations),
14936
+ decision: Object.keys(decisionOperations),
14937
+ worker: Object.keys(workerOperations),
14938
+ state: Object.keys(stateOperations)
14939
+ };
15195
14940
  var withImplicitTask = (options) => Object.values(options).map((option) => ("taskId" in option.shape) ? option.extend({ taskId: id.optional() }) : option);
15196
14941
  var advertised = (options) => discriminatedUnion("action", withImplicitTask(options));
15197
14942
  var advertisedOperationSchemas = {
@@ -15225,103 +14970,6 @@ function parseOperation(family, input) {
15225
14970
  }));
15226
14971
  return failure2("invalid_input", "operation input is invalid", { fields });
15227
14972
  }
15228
- function operationJsonSchema(family) {
15229
- return toJSONSchema(advertisedOperationSchemas[family], { target: "draft-2020-12" });
15230
- }
15231
- var OPERATION_SCHEMA_DEPTH = 1;
15232
- var schemaShapeDescription = (value, depth = 0) => {
15233
- if (!value || typeof value !== "object")
15234
- return "value";
15235
- const schema = value;
15236
- if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
15237
- const options = Array.isArray(schema.oneOf) ? schema.oneOf : schema.anyOf;
15238
- return `(${options.map((option) => schemaShapeDescription(option, depth)).join("|")})`;
15239
- }
15240
- if (schema.const !== undefined)
15241
- return `=${JSON.stringify(schema.const)}`;
15242
- if (Array.isArray(schema.enum))
15243
- return `[${schema.enum.map(String).join("|")}]`;
15244
- const type = Array.isArray(schema.type) ? schema.type.join("/") : schema.type;
15245
- if (type === "array")
15246
- return `arr<${schemaShapeDescription(schema.items, depth)}>`;
15247
- if (type === "object" && schema.properties && typeof schema.properties === "object") {
15248
- if (depth >= 2)
15249
- return "obj";
15250
- const required = new Set(Array.isArray(schema.required) ? schema.required : []);
15251
- const properties = schema.properties;
15252
- const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child, depth + 1)}`);
15253
- return `obj{${fields.join(",")}}`;
15254
- }
15255
- if (typeof type !== "string")
15256
- return "?";
15257
- return type.split("/").map((part) => ({ string: "str", boolean: "bool", number: "num", null: "null" })[part] ?? part).join("|");
15258
- };
15259
- var canonicalFieldsDescription = (value) => {
15260
- if (Array.isArray(value))
15261
- return `Canonical object fields: ${value.join(", ")}. Workit validates the complete nested value.`;
15262
- const isZodType = (schema) => ("_zod" in schema) && typeof schema._zod === "object";
15263
- const schema = isZodType(value) ? toJSONSchema(value, { target: "draft-2020-12" }) : value;
15264
- if (!schema.properties || typeof schema.properties !== "object") {
15265
- const variants = Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : null;
15266
- return variants ? `Canonical shapes: ${variants.map((variant) => schemaShapeDescription(variant)).join(" | ")}. Full nested value is validated.` : "Canonical nested value; Workit validates the complete value.";
15267
- }
15268
- const properties = schema.properties;
15269
- const required = new Set(Array.isArray(schema.required) ? schema.required : []);
15270
- const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child)}`);
15271
- return `Fields (! required): ${fields.join(", ")}. Full nested value is validated.`;
15272
- };
15273
- function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, stringifiedObjects = false) {
15274
- const tolerate = (projected) => {
15275
- if (!stringifiedObjects)
15276
- return projected;
15277
- if (projected.properties && typeof projected.properties === "object")
15278
- return { ...projected, type: ["object", "string"] };
15279
- if (projected.items)
15280
- return { ...projected, type: ["array", "string"] };
15281
- if ("const" in projected && (typeof projected.const === "number" || typeof projected.const === "boolean"))
15282
- return { anyOf: [projected, { const: String(projected.const) }] };
15283
- if (projected.type === "object")
15284
- return { ...projected, type: ["object", "string"] };
15285
- if (projected.type === "array")
15286
- return { ...projected, type: ["array", "string"] };
15287
- return projected;
15288
- };
15289
- const collapse = (node, currentDepth) => {
15290
- if (Array.isArray(node))
15291
- return node.map((item) => collapse(item, currentDepth));
15292
- if (typeof node !== "object" || node === null)
15293
- return node;
15294
- const record = node;
15295
- if (record.properties && typeof record.properties === "object") {
15296
- const properties = record.properties;
15297
- const fields = Object.keys(properties);
15298
- if (currentDepth >= maxDepth || fields.length === 0) {
15299
- const description = stringifiedObjects ? `${canonicalFieldsDescription(record)} A JSON-encoded string is also accepted.` : canonicalFieldsDescription(record);
15300
- return tolerate({ type: "object", description });
15301
- }
15302
- return tolerate({
15303
- ...record,
15304
- properties: Object.fromEntries(Object.entries(record.properties).map(([key, child]) => [
15305
- key,
15306
- collapse(child, currentDepth + 1)
15307
- ]))
15308
- });
15309
- }
15310
- if (record.items)
15311
- return tolerate({ ...record, items: collapse(record.items, currentDepth) });
15312
- const composed = ["anyOf", "oneOf", "allOf"].filter((key) => Array.isArray(record[key]));
15313
- if (composed.length > 0) {
15314
- const projected = { ...record };
15315
- for (const key of composed)
15316
- projected[key] = record[key].map((item) => collapse(item, currentDepth));
15317
- return projected;
15318
- }
15319
- if ("const" in record)
15320
- return tolerate({ ...record });
15321
- return node;
15322
- };
15323
- return collapse(operationJsonSchema(family), 0);
15324
- }
15325
14973
  function canonical(value, seen) {
15326
14974
  if (value === null || typeof value === "boolean" || typeof value === "string") {
15327
14975
  if (typeof value === "string" && invalidUnicode(value))
@@ -15410,6 +15058,402 @@ function requirementId(input) {
15410
15058
  }
15411
15059
  });
15412
15060
  }
15061
+ // packages/workit-core/src/core/operation-input.ts
15062
+ var str = (d, values) => ({
15063
+ type: "string",
15064
+ ...values ? { enum: values } : {},
15065
+ ...d ? { d } : {}
15066
+ });
15067
+ var list = (d) => ({ type: "array", ...d ? { d } : {} });
15068
+ var bool = (d) => ({ type: "boolean", ...d ? { d } : {} });
15069
+ var int2 = (d) => ({ type: "integer", ...d ? { d } : {} });
15070
+ var scopeFields = {
15071
+ paths: list("Scope paths (default: whole checkout)."),
15072
+ exclusions: list(),
15073
+ scope: str("Scope description.")
15074
+ };
15075
+ var refs = list("Paths or URLs.");
15076
+ var FLAT = {
15077
+ task: {
15078
+ fields: {
15079
+ objective: str(),
15080
+ ...scopeFields,
15081
+ refs,
15082
+ reason: str(),
15083
+ summary: str(),
15084
+ nextAction: str(),
15085
+ blockers: list(),
15086
+ outcome: str(undefined, ["verified", "accepted_limitations", "stopped"]),
15087
+ decisionIds: list(),
15088
+ status: str(undefined, ["open", "closed", "all"]),
15089
+ query: str(),
15090
+ limit: int2(),
15091
+ view: str(undefined, ["summary", "full"])
15092
+ }
15093
+ },
15094
+ policy: {
15095
+ fields: {
15096
+ riskTier: str("low=trivial, medium=normal.", ["trivial", "normal", "high", "low", "medium"]),
15097
+ behaviorChange: bool("Observable behavior changes."),
15098
+ productChoiceOpen: bool("A product/preference choice is still open."),
15099
+ needsPlan: bool("Needs a written plan or spec first."),
15100
+ note: str(),
15101
+ refs: list("Plan/spec paths or URLs.")
15102
+ }
15103
+ },
15104
+ evidence: {
15105
+ fields: {
15106
+ kind: str(undefined, ["check", "review", "investigation", "artifact"]),
15107
+ claim: str(),
15108
+ result: str(undefined, ["passed", "failed", "missing", "skipped"]),
15109
+ summary: str(),
15110
+ exitCode: int2(),
15111
+ refs,
15112
+ requirementIds: list(),
15113
+ candidateId: str(),
15114
+ beforeCandidateId: str(),
15115
+ reviewSession: str("Reviewer session handle (review evidence).")
15116
+ }
15117
+ },
15118
+ finding: {
15119
+ fields: {
15120
+ claim: str(),
15121
+ consequence: str(),
15122
+ ...scopeFields,
15123
+ candidateId: str(),
15124
+ refs,
15125
+ findingId: str(),
15126
+ disposition: str(undefined, ["open", "fixed", "dismissed", "deferred"]),
15127
+ reason: str(),
15128
+ evidenceIds: list(),
15129
+ decisionIds: list()
15130
+ }
15131
+ },
15132
+ decision: {
15133
+ fields: {
15134
+ purpose: str(undefined, ["design", "action", "limitation", "preference"]),
15135
+ response: str(undefined, ["approved", "rejected", "stated"]),
15136
+ presented: str("What the user was asked."),
15137
+ choice: str("The user's stated choice (response=stated)."),
15138
+ ...scopeFields,
15139
+ refs,
15140
+ requirementIds: list(),
15141
+ decisionId: str(),
15142
+ reason: str()
15143
+ }
15144
+ },
15145
+ worker: {
15146
+ fields: {
15147
+ role: str(undefined, ["investigator", "reviewer", "implementer"]),
15148
+ objective: str(),
15149
+ ...scopeFields,
15150
+ decisionIds: list(),
15151
+ requirementIds: list(),
15152
+ candidateId: str(),
15153
+ stoppingCondition: str(),
15154
+ workerId: str(),
15155
+ outcome: str(undefined, ["completed", "failed", "cancelled"]),
15156
+ summary: str(),
15157
+ evidenceIds: list(),
15158
+ findingIds: list(),
15159
+ reason: str()
15160
+ }
15161
+ },
15162
+ state: {
15163
+ fields: { bundle: { type: "object", d: "An export bundle (object or JSON string)." } }
15164
+ }
15165
+ };
15166
+ var COMMON = {
15167
+ taskId: str("Omit for this branch's task."),
15168
+ expectedRevision: str(),
15169
+ expectedWorkspaceRevision: str()
15170
+ };
15171
+ var DESCRIPTIONS = {
15172
+ task: "Workit task lifecycle for this branch's task (optional continuity).",
15173
+ policy: "Judge the work in four flat calls (riskTier, behaviorChange, productChoiceOpen, needsPlan); Workit derives the checks, verdicts, decisions and plan it needs.",
15174
+ evidence: "Record a note-level evidence entry; only `workit check` runs satisfy check gates.",
15175
+ finding: "Record or resolve a review finding.",
15176
+ decision: "Record or revoke a user decision (never an authorization).",
15177
+ worker: "Assign, report or cancel a bounded helper.",
15178
+ state: "Export or import a task handoff bundle."
15179
+ };
15180
+ var operationDescription = (family) => `${DESCRIPTIONS[family]} Flat fields; omit taskId and revisions.`;
15181
+ var READ_TASK_FIELDS = new Set(["status", "query", "limit", "view"]);
15182
+ var READ_ONLY_ACTIONS = new Set(["list", "inspect", "preview", "explain", "export"]);
15183
+ function flatOperationJsonSchema(family, options = {}) {
15184
+ const spec = FLAT[family];
15185
+ const actions = options.readOnly ? OPERATION_ACTIONS[family].filter((action) => READ_ONLY_ACTIONS.has(action)) : [...OPERATION_ACTIONS[family]];
15186
+ if (!actions.length)
15187
+ return null;
15188
+ const property = (field) => {
15189
+ const base = field.type === "array" ? { type: "array", items: { type: "string" } } : field.type === "object" ? { type: "object" } : { type: field.type };
15190
+ const tolerant = options.stringTolerant && field.type !== "string" ? { type: [field.type, "string"] } : {};
15191
+ return {
15192
+ ...base,
15193
+ ...tolerant,
15194
+ ...field.enum ? { enum: field.enum } : {},
15195
+ ...field.d ? { description: field.d } : {}
15196
+ };
15197
+ };
15198
+ const properties = { action: { type: "string", enum: actions } };
15199
+ const readField = (name) => name === "taskId" || (family === "task" ? READ_TASK_FIELDS.has(name) : family === "policy");
15200
+ for (const [name, field] of Object.entries({ ...COMMON, ...spec.fields }))
15201
+ if (options.readOnly ? readField(name) : !(family === "state" && name === "expectedRevision"))
15202
+ properties[name] = property(field);
15203
+ return {
15204
+ $schema: "https://json-schema.org/draft/2020-12/schema",
15205
+ type: "object",
15206
+ properties,
15207
+ required: ["action"]
15208
+ };
15209
+ }
15210
+ var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
15211
+ var decoded = (value) => {
15212
+ if (typeof value !== "string")
15213
+ return value;
15214
+ const trimmed = value.trim();
15215
+ if (!/^[[{]/.test(trimmed) && !/^(true|false|null|-?\d+)$/.test(trimmed))
15216
+ return value;
15217
+ try {
15218
+ return JSON.parse(trimmed);
15219
+ } catch {
15220
+ return value;
15221
+ }
15222
+ };
15223
+ var strings = (value) => {
15224
+ const plain = decoded(value);
15225
+ if (typeof plain === "string")
15226
+ return plain.trim() ? [plain] : [];
15227
+ return plain;
15228
+ };
15229
+ var refOf = (value) => {
15230
+ if (typeof value !== "string")
15231
+ return value;
15232
+ return /^https?:\/\//i.test(value) ? { kind: "external", url: value } : { kind: "file", path: value.replace(/^\.\//, ""), digest: null };
15233
+ };
15234
+ var refList = (value) => {
15235
+ const items = strings(value);
15236
+ return Array.isArray(items) ? items.map(refOf) : items;
15237
+ };
15238
+ var take = (flat, keys, map = decoded) => {
15239
+ const out = {};
15240
+ for (const key of keys)
15241
+ if (key in flat) {
15242
+ out[key] = map(flat[key]);
15243
+ delete flat[key];
15244
+ }
15245
+ return out;
15246
+ };
15247
+ var nest = (request, key, flat, defaults = {}) => {
15248
+ const nested = isObject2(decoded(request[key])) ? decoded(request[key]) : null;
15249
+ if (!nested && !Object.keys(flat).length)
15250
+ return;
15251
+ request[key] = { ...defaults, ...flat, ...nested };
15252
+ };
15253
+ var scopeOf = (flat, description) => {
15254
+ const scope = decoded(flat.scope);
15255
+ const has = "paths" in flat || "exclusions" in flat || typeof scope === "string";
15256
+ const out = isObject2(scope) ? scope : has ? {
15257
+ description: typeof scope === "string" ? scope : String(description ?? ""),
15258
+ paths: strings(flat.paths ?? ["."]),
15259
+ exclusions: strings(flat.exclusions ?? [])
15260
+ } : null;
15261
+ delete flat.scope;
15262
+ delete flat.paths;
15263
+ delete flat.exclusions;
15264
+ return out;
15265
+ };
15266
+ var defaultScope = (description) => ({
15267
+ description: typeof description === "string" ? description : "",
15268
+ paths: ["."],
15269
+ exclusions: []
15270
+ });
15271
+ var LISTS = [
15272
+ "decisionIds",
15273
+ "evidenceIds",
15274
+ "findingIds",
15275
+ "requirementIds",
15276
+ "refs",
15277
+ "blockers"
15278
+ ];
15279
+ var ENVELOPE = new Set([
15280
+ "schemaVersion",
15281
+ "action",
15282
+ "taskId",
15283
+ "expectedRevision",
15284
+ "expectedWorkspaceRevision",
15285
+ "judgment"
15286
+ ]);
15287
+ function normalizeOperationInput(family, input, host) {
15288
+ if (!isObject2(input))
15289
+ return input;
15290
+ const request = { ...input };
15291
+ if (!("schemaVersion" in request))
15292
+ request.schemaVersion = 1;
15293
+ else
15294
+ request.schemaVersion = decoded(request.schemaVersion);
15295
+ for (const key of ["limit", "exitCode"])
15296
+ if (key in request)
15297
+ request[key] = decoded(request[key]);
15298
+ const action = request.action;
15299
+ const flat = request;
15300
+ switch (family) {
15301
+ case "task": {
15302
+ if (action === "start" || action === "revise") {
15303
+ const intent = isObject2(decoded(request.intent)) ? decoded(request.intent) : {};
15304
+ const objective = flat.objective ?? intent.objective;
15305
+ const scope = scopeOf(flat, objective);
15306
+ const fields = take(flat, ["objective"]);
15307
+ const authorityRefs = "refs" in flat ? refList(flat.refs) : undefined;
15308
+ delete flat.refs;
15309
+ nest(request, "intent", {
15310
+ ...fields,
15311
+ ...scope ? { scope } : {},
15312
+ ...authorityRefs ? { authorityRefs } : {}
15313
+ }, { scope: defaultScope(objective), authorityRefs: [] });
15314
+ } else if (action === "progress") {
15315
+ const fields = take(flat, ["summary", "nextAction"]);
15316
+ const blockers = "blockers" in flat ? strings(flat.blockers) : undefined;
15317
+ delete flat.blockers;
15318
+ nest(request, "progress", {
15319
+ ...fields,
15320
+ ...Array.isArray(blockers) ? {
15321
+ blockers: blockers.map((item) => typeof item === "string" ? { reason: item, dependentAction: "", refs: [] } : item)
15322
+ } : {}
15323
+ }, { nextAction: null, blockers: [] });
15324
+ } else if (action === "close" && "decisionIds" in flat)
15325
+ flat.decisionIds = strings(flat.decisionIds);
15326
+ break;
15327
+ }
15328
+ case "policy": {
15329
+ if (action === "assess" || action === "preview") {
15330
+ const judgment = isObject2(decoded(request.judgment)) ? { ...decoded(request.judgment) } : {};
15331
+ for (const key of Object.keys(flat))
15332
+ if (!ENVELOPE.has(key)) {
15333
+ if (!(key in judgment))
15334
+ judgment[key] = decoded(flat[key]);
15335
+ delete flat[key];
15336
+ }
15337
+ request.judgment = judgment;
15338
+ }
15339
+ break;
15340
+ }
15341
+ case "evidence": {
15342
+ if (action === "record") {
15343
+ const review = "reviewSession" in flat ? decoded(flat.reviewSession) : undefined;
15344
+ delete flat.reviewSession;
15345
+ const fields = take(flat, [
15346
+ "kind",
15347
+ "claim",
15348
+ "result",
15349
+ "summary",
15350
+ "exitCode",
15351
+ "candidateId",
15352
+ "beforeCandidateId",
15353
+ "reviewContext"
15354
+ ]);
15355
+ if ("refs" in flat)
15356
+ fields.refs = refList(flat.refs);
15357
+ if ("requirementIds" in flat)
15358
+ fields.requirementIds = strings(flat.requirementIds);
15359
+ delete flat.refs;
15360
+ delete flat.requirementIds;
15361
+ if (typeof review === "string" && review)
15362
+ fields.reviewContext = { kind: "host", host, handle: review };
15363
+ nest(request, "evidence", fields, {
15364
+ summary: "",
15365
+ refs: [],
15366
+ requirementIds: [],
15367
+ exitCode: null,
15368
+ reviewContext: null
15369
+ });
15370
+ }
15371
+ break;
15372
+ }
15373
+ case "finding": {
15374
+ if (action === "record") {
15375
+ const scope = scopeOf(flat, flat.claim);
15376
+ if (scope)
15377
+ request.scope = scope;
15378
+ else if (!("scope" in request))
15379
+ request.scope = defaultScope(request.claim);
15380
+ request.refs = "refs" in flat ? refList(flat.refs) : [];
15381
+ }
15382
+ break;
15383
+ }
15384
+ case "decision": {
15385
+ if (action === "record") {
15386
+ const binding = isObject2(decoded(request.binding)) ? decoded(request.binding) : {};
15387
+ const presented = flat.presented ?? binding.presented;
15388
+ const scope = scopeOf(flat, presented);
15389
+ const fields = take(flat, ["presented"]);
15390
+ const choice = flat.choice;
15391
+ delete flat.choice;
15392
+ const contentRefs = "refs" in flat ? refList(flat.refs) : undefined;
15393
+ delete flat.refs;
15394
+ nest(request, "binding", {
15395
+ ...fields,
15396
+ ...scope ? { scope } : {},
15397
+ ...contentRefs ? { contentRefs } : {},
15398
+ ...typeof choice === "string" ? { statedChoice: { ref: "choice", text: choice } } : {}
15399
+ }, { scope: defaultScope(presented), contentRefs: [] });
15400
+ }
15401
+ break;
15402
+ }
15403
+ case "worker": {
15404
+ if (action === "assign") {
15405
+ const objective = flat.objective;
15406
+ const scope = scopeOf(flat, objective);
15407
+ const fields = take(flat, ["role", "objective", "candidateId", "stoppingCondition"]);
15408
+ for (const key of ["decisionIds", "requirementIds"])
15409
+ if (key in flat) {
15410
+ fields[key] = strings(flat[key]);
15411
+ delete flat[key];
15412
+ }
15413
+ nest(request, "assignment", { ...fields, ...scope ? { scope } : {} }, { scope: defaultScope(objective), decisionIds: [], requirementIds: [] });
15414
+ } else if (action === "report") {
15415
+ const fields = take(flat, ["outcome", "summary"]);
15416
+ for (const key of ["evidenceIds", "findingIds"])
15417
+ if (key in flat) {
15418
+ fields[key] = strings(flat[key]);
15419
+ delete flat[key];
15420
+ }
15421
+ nest(request, "report", fields, { evidenceIds: [], findingIds: [] });
15422
+ }
15423
+ break;
15424
+ }
15425
+ case "state": {
15426
+ if ("bundle" in request)
15427
+ request.bundle = decoded(request.bundle);
15428
+ break;
15429
+ }
15430
+ }
15431
+ const defaults = {
15432
+ finding: { resolve: ["evidenceIds", "decisionIds"] },
15433
+ decision: { record: ["requirementIds"] }
15434
+ };
15435
+ for (const key of defaults[family]?.[String(action)] ?? [])
15436
+ if (!(key in request))
15437
+ request[key] = [];
15438
+ for (const key of LISTS)
15439
+ if (key in request && typeof request[key] === "string")
15440
+ request[key] = strings(request[key]);
15441
+ return request;
15442
+ }
15443
+ function parseAdvertisedOperation(family, input, host) {
15444
+ const request = normalizeOperationInput(family, input, host);
15445
+ if (isObject2(request) && request.schemaVersion !== SCHEMA_VERSION)
15446
+ return failure2("unsupported_version", "unsupported schema version", { operation: family });
15447
+ const parsed = advertisedOperationSchemas[family].safeParse(request);
15448
+ if (parsed.success)
15449
+ return success(null, null, request);
15450
+ return failure2("invalid_input", "operation input is invalid", {
15451
+ fields: parsed.error.issues.map((issue) => ({
15452
+ path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
15453
+ reason: issue.message
15454
+ }))
15455
+ });
15456
+ }
15413
15457
  // packages/workit-core/src/core/task-store.ts
15414
15458
  import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
15415
15459
  import * as fs7 from "node:fs";
@@ -16464,7 +16508,7 @@ class LogDamage extends Error {
16464
16508
  this.upgrade = upgrade;
16465
16509
  }
16466
16510
  }
16467
- var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16511
+ var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16468
16512
  var parseEvent = (line, file, offset) => {
16469
16513
  let value;
16470
16514
  try {
@@ -16472,11 +16516,11 @@ var parseEvent = (line, file, offset) => {
16472
16516
  } catch {
16473
16517
  throw new LogDamage(`${file}: unreadable event at byte ${offset}`);
16474
16518
  }
16475
- if (!isObject2(value) || typeof value.v !== "number")
16519
+ if (!isObject3(value) || typeof value.v !== "number")
16476
16520
  throw new LogDamage(`${file}: malformed event at byte ${offset}`);
16477
16521
  if (value.v > EVENT_VERSION)
16478
16522
  throw new LogDamage(`${file}: event format v${value.v} was written by a newer Workit; upgrade Workit to read this task`, true);
16479
- if (typeof value.seq !== "number" || !Number.isInteger(value.seq) || typeof value.type !== "string" || typeof value.id !== "string" || typeof value.task !== "string" || !isObject2(value.data))
16523
+ if (typeof value.seq !== "number" || !Number.isInteger(value.seq) || typeof value.type !== "string" || typeof value.id !== "string" || typeof value.task !== "string" || !isObject3(value.data))
16480
16524
  throw new LogDamage(`${file}: malformed event at byte ${offset}`);
16481
16525
  return value;
16482
16526
  };
@@ -16747,13 +16791,13 @@ var KNOWN_TYPES = new Set([
16747
16791
  "decision.updated",
16748
16792
  "worker.updated"
16749
16793
  ]);
16750
- var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16751
- 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");
16794
+ var isObject4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16795
+ 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");
16752
16796
  var BLOB_REF = "$blob";
16753
- 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;
16797
+ 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;
16754
16798
  var blobDigest = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
16755
16799
  var toBlob = (value, blobs) => {
16756
- if (!isObject3(value))
16800
+ if (!isObject4(value))
16757
16801
  return value;
16758
16802
  const digest = blobDigest(value);
16759
16803
  blobs.write(digest, value);
@@ -16777,7 +16821,7 @@ var decode2 = (value, blobs) => {
16777
16821
  return blobs.read(digest);
16778
16822
  if (Array.isArray(value))
16779
16823
  return value.map((item) => decode2(item, blobs));
16780
- if (isObject3(value)) {
16824
+ if (isObject4(value)) {
16781
16825
  const out = {};
16782
16826
  for (const [key, item] of Object.entries(value))
16783
16827
  out[key] = decode2(item, blobs);
@@ -16796,7 +16840,7 @@ function reduce(state, event, blobs) {
16796
16840
  }
16797
16841
  let next = state;
16798
16842
  if ("record" in data) {
16799
- if (!isObject3(data.record))
16843
+ if (!isObject4(data.record))
16800
16844
  throw new LogDamage(`event ${event.seq} carries a malformed record`);
16801
16845
  next = {
16802
16846
  record: decode2(data.record, blobs),
@@ -16818,7 +16862,7 @@ function reduce(state, event, blobs) {
16818
16862
  }
16819
16863
  if ("key" in data)
16820
16864
  next.key = isKey(data.key) ? data.key : null;
16821
- if ("legacy" in data && isObject3(data.legacy))
16865
+ if ("legacy" in data && isObject4(data.legacy))
16822
16866
  next.legacy = {
16823
16867
  path: String(data.legacy.path ?? ""),
16824
16868
  digest: String(data.legacy.digest ?? "")
@@ -16874,8 +16918,8 @@ var AUTO_COMPACT_BYTES = 2 * 1024 * 1024;
16874
16918
  var TOMBSTONE = Symbol("tombstone");
16875
16919
  var STALE_TEMPORARY_MS = 60 * 60000;
16876
16920
  var INDEX_VERSION = 3;
16877
- var isObject4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16878
- var hostSession = (value) => isObject4(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
16921
+ var isObject5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16922
+ var hostSession = (value) => isObject5(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
16879
16923
  var indexEntry = (task, key, legacy, file) => {
16880
16924
  const sessions = [];
16881
16925
  const intent = hostSession(task.intent.provenance.session);
@@ -17688,7 +17732,7 @@ class TaskStore {
17688
17732
  readSnapshot(taskId) {
17689
17733
  try {
17690
17734
  const value = JSON.parse(fs7.readFileSync(this.snapshotPath(taskId), "utf8"));
17691
- 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))
17735
+ 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))
17692
17736
  return null;
17693
17737
  return value;
17694
17738
  } catch {
@@ -18034,11 +18078,11 @@ class TaskStore {
18034
18078
  readIndex() {
18035
18079
  try {
18036
18080
  const value = JSON.parse(fs7.readFileSync(this.paths().index, "utf8"));
18037
- if (!isObject4(value) || value.version !== INDEX_VERSION || !isObject4(value.tasks))
18081
+ if (!isObject5(value) || value.version !== INDEX_VERSION || !isObject5(value.tasks))
18038
18082
  return {};
18039
18083
  const tasks = {};
18040
18084
  for (const [id, entry] of Object.entries(value.tasks))
18041
- 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))
18085
+ 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))
18042
18086
  tasks[id] = entry;
18043
18087
  return tasks;
18044
18088
  } catch {
@@ -18082,12 +18126,12 @@ class TaskStore {
18082
18126
  return this.parseValue(value, schema, label);
18083
18127
  }
18084
18128
  parseValue(value, schema, label) {
18085
- if (isObject4(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
18129
+ if (isObject5(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
18086
18130
  return failure2("unsupported_version", `unsupported ${label} schema version`);
18087
18131
  const parsed = parseStoredRecord(schema, value);
18088
18132
  if (parsed.success)
18089
18133
  return success(null, null, parsed.data);
18090
- const writerVersion = isObject4(value) && isObject4(value.runtime) ? value.runtime.updatedWith : null;
18134
+ const writerVersion = isObject5(value) && isObject5(value.runtime) ? value.runtime.updatedWith : null;
18091
18135
  if (parsed.critical.length > 0)
18092
18136
  return failure2("recovery_required", `${label} record requires fields this Workit cannot read (${parsed.critical.join(", ")}); upgrade Workit before mutating this checkout`);
18093
18137
  if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
@@ -18289,7 +18333,7 @@ class TaskStore {
18289
18333
  }
18290
18334
  try {
18291
18335
  const value = JSON.parse(bytes);
18292
- if (isObject4(value) && isObject4(value.store) && value.store.format === "workit-store")
18336
+ if (isObject5(value) && isObject5(value.store) && value.store.format === "workit-store")
18293
18337
  return TOMBSTONE;
18294
18338
  } catch {}
18295
18339
  return { bytes };
@@ -18334,7 +18378,7 @@ class TaskStore {
18334
18378
  isStub(file) {
18335
18379
  try {
18336
18380
  const value = JSON.parse(fs7.readFileSync(file, "utf8"));
18337
- return isObject4(value) && isObject4(value.store) && value.store.format === "workit-store";
18381
+ return isObject5(value) && isObject5(value.store) && value.store.format === "workit-store";
18338
18382
  } catch {
18339
18383
  return false;
18340
18384
  }
@@ -18505,10 +18549,10 @@ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
18505
18549
  var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
18506
18550
 
18507
18551
  // packages/workit-core/src/core/task-evaluation.ts
18508
- import { createHash as createHash6 } from "node:crypto";
18509
- import * as fs10 from "node:fs";
18510
- import { spawnSync as spawnSync5 } from "node:child_process";
18511
- import path9 from "node:path";
18552
+ import { createHash as createHash7 } from "node:crypto";
18553
+ import * as fs11 from "node:fs";
18554
+ import { spawnSync as spawnSync6 } from "node:child_process";
18555
+ import path11 from "node:path";
18512
18556
 
18513
18557
  // packages/workit-core/src/check-config.ts
18514
18558
  import { spawnSync as spawnSync3 } from "node:child_process";
@@ -18748,6 +18792,7 @@ var line = (run) => {
18748
18792
  const value = run.stdout.trim();
18749
18793
  return value ? value : null;
18750
18794
  };
18795
+ var safeArg = (value) => value.length > 0 && !value.startsWith("-");
18751
18796
  function headSha(cwd) {
18752
18797
  return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
18753
18798
  }
@@ -18839,53 +18884,622 @@ function worktreeSignal(cwd, options = {}) {
18839
18884
  }
18840
18885
  return `sig:${hash.digest("hex")}`;
18841
18886
  }
18887
+ function patchId(cwd, base, head) {
18888
+ if (!safeArg(base) || !safeArg(head))
18889
+ return null;
18890
+ const diff = git2(cwd, [
18891
+ "diff",
18892
+ "-U3",
18893
+ "--diff-algorithm=myers",
18894
+ "--indent-heuristic",
18895
+ "--no-color",
18896
+ "--no-ext-diff",
18897
+ "--no-textconv",
18898
+ "--no-renames",
18899
+ "--full-index",
18900
+ "--src-prefix=a/",
18901
+ "--dst-prefix=b/",
18902
+ `${base}...${head}`,
18903
+ "--"
18904
+ ]);
18905
+ if (!diff.ok || !diff.stdout.trim())
18906
+ return null;
18907
+ const id = git2(cwd, ["patch-id", "--stable"], { input: diff.stdout });
18908
+ return line(id)?.split(/\s+/u)[0] ?? null;
18909
+ }
18910
+ function currentBranch(cwd) {
18911
+ return line(git2(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
18912
+ }
18842
18913
 
18843
- // packages/workit-core/src/core/task-evaluation.ts
18844
- var digestBytes = (value) => createHash6("sha256").update(value).digest("hex");
18845
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path9.sep}`);
18846
- var relative = (root, candidate) => path9.relative(root, candidate).split(path9.sep).join("/") || ".";
18847
- var canonicalPath = (value) => {
18848
- if (typeof value !== "string" || !value)
18914
+ // packages/workit-core/src/core/policy/derive.ts
18915
+ import { existsSync as existsSync3 } from "node:fs";
18916
+ import path10 from "node:path";
18917
+
18918
+ // packages/workit-core/src/ledger.ts
18919
+ import { spawnSync as spawnSync5 } from "node:child_process";
18920
+ import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
18921
+ import fs10 from "node:fs";
18922
+ import path9 from "node:path";
18923
+ var LEDGER_VERSION = 1;
18924
+ var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
18925
+ var FAILING = new Set(["failed", "blocked"]);
18926
+ var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
18927
+ var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
18928
+ var err = (code, error, unblock) => ({
18929
+ ok: false,
18930
+ code,
18931
+ error,
18932
+ ...unblock ? { unblock } : {}
18933
+ });
18934
+ var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
18935
+ const run = spawnSync5("git", args, {
18936
+ cwd,
18937
+ encoding: "utf8",
18938
+ timeout: timeoutMs,
18939
+ killSignal: "SIGKILL",
18940
+ windowsHide: true,
18941
+ maxBuffer: 256 * 1024 * 1024,
18942
+ env: {
18943
+ ...process.env,
18944
+ GIT_TERMINAL_PROMPT: "0",
18945
+ GIT_OPTIONAL_LOCKS: "0",
18946
+ LC_ALL: "C",
18947
+ LANGUAGE: "C"
18948
+ }
18949
+ });
18950
+ return {
18951
+ ok: run.status === 0,
18952
+ stdout: run.stdout ?? "",
18953
+ stderr: run.error ? String(run.error.message) : run.stderr ?? ""
18954
+ };
18955
+ };
18956
+ var git3 = (cwd, args) => {
18957
+ const run = gitRun2(cwd, args);
18958
+ const value = run.ok ? run.stdout.trim() : "";
18959
+ return value ? value : null;
18960
+ };
18961
+ var safeRef = (value) => value.length > 0 && !value.startsWith("-");
18962
+ function resolveCommit(cwd, ref) {
18963
+ if (!safeRef(ref))
18849
18964
  return null;
18850
- if (value.includes("\\") || value.split("/").includes(".."))
18965
+ return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
18966
+ }
18967
+ function branchHead(cwd, branch) {
18968
+ if (!safeRef(branch))
18851
18969
  return null;
18852
- const normalized = path9.posix.normalize(value);
18853
- if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
18970
+ return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
18971
+ }
18972
+ function defaultBase(cwd) {
18973
+ const originHead = git3(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
18974
+ if (originHead && resolveCommit(cwd, originHead))
18975
+ return originHead;
18976
+ for (const candidate of ["origin/main", "origin/master", "main", "master"])
18977
+ if (resolveCommit(cwd, candidate))
18978
+ return candidate;
18979
+ return null;
18980
+ }
18981
+ function diffHash(cwd, base, head) {
18982
+ if (!safeRef(base) || !safeRef(head))
18854
18983
  return null;
18855
- return normalized === "." ? "." : normalized.replace(/\/$/, "");
18856
- };
18857
- var canonicalScope = (scope) => {
18858
- const paths = scope.paths.length ? scope.paths : ["."];
18859
- const normalizedPaths = paths.map(canonicalPath);
18860
- const normalizedExclusions = scope.exclusions.map(canonicalPath);
18861
- if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
18984
+ const diff = gitRun2(cwd, [
18985
+ "diff",
18986
+ "-U3",
18987
+ "--diff-algorithm=myers",
18988
+ "--indent-heuristic",
18989
+ "--no-color",
18990
+ "--no-ext-diff",
18991
+ "--no-textconv",
18992
+ "--no-renames",
18993
+ "--full-index",
18994
+ "--binary",
18995
+ "--src-prefix=a/",
18996
+ "--dst-prefix=b/",
18997
+ `${base}...${head}`,
18998
+ "--"
18999
+ ], GIT_TIMEOUTS.worktree);
19000
+ if (!diff.ok || !diff.stdout)
18862
19001
  return null;
19002
+ const hash = createHash6("sha256");
19003
+ for (const line of diff.stdout.split(`
19004
+ `)) {
19005
+ if (line.startsWith("index "))
19006
+ continue;
19007
+ hash.update(line.startsWith("@@ ") ? "@@" : line);
19008
+ hash.update(`
19009
+ `);
19010
+ }
19011
+ return `sha256:${hash.digest("hex")}`;
19012
+ }
19013
+ function storeRoot(cwd) {
19014
+ const location = resolveStore(path9.resolve(cwd));
19015
+ if (location instanceof Error)
19016
+ 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");
19017
+ return { ok: true, value: { root: location.dir, shared: location.shared } };
19018
+ }
19019
+ function ledgerPath(cwd) {
19020
+ const root = storeRoot(cwd);
19021
+ return root.ok ? { ok: true, value: path9.join(root.value.root, "ledger", "ledger.jsonl") } : root;
19022
+ }
19023
+ var NETWORK_FS_TYPES = new Set([
19024
+ 26985,
19025
+ 20859,
19026
+ 4266872130,
19027
+ 4283649346,
19028
+ 1702057286,
19029
+ 1397113167,
19030
+ 12805120,
19031
+ 16914839,
19032
+ 1196443219,
19033
+ 198183888
19034
+ ]);
19035
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19036
+ var str2 = (value) => typeof value === "string" ? value : null;
19037
+ var normalizeActor = (value) => {
19038
+ const actor = isRecord2(value) ? value : {};
18863
19039
  return {
18864
- description: scope.description,
18865
- paths: [...new Set(normalizedPaths)],
18866
- exclusions: [...new Set(normalizedExclusions)]
19040
+ host: str2(actor.host) ?? "unknown",
19041
+ session: str2(actor.session),
19042
+ agentId: str2(actor.agentId)
18867
19043
  };
18868
19044
  };
18869
- var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
18870
- var scopeMatches = (value, scope) => {
18871
- const normalized = canonicalScope(scope);
18872
- if (!normalized)
18873
- return false;
18874
- const paths = normalized.paths;
18875
- return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
18876
- };
18877
- var pathRelevant = (value, scope) => scopeMatches(value, scope);
18878
- var compareCodeUnits2 = (left, right) => {
18879
- const length = Math.min(left.length, right.length);
18880
- for (let index = 0;index < length; index += 1) {
18881
- const difference = left.charCodeAt(index) - right.charCodeAt(index);
18882
- if (difference !== 0)
18883
- return difference;
19045
+ function supersedeAllowed(by, target) {
19046
+ if (by.type !== target.type)
19047
+ return `a ${by.type} row cannot supersede a ${target.type} row`;
19048
+ if (!by.actor.session || by.actor.session !== target.actor.session)
19049
+ return "only the session that wrote a row can supersede it";
19050
+ if (by.type === "verdict" && by.self === true && target.self !== true)
19051
+ return "a self verdict cannot replace an independent verdict";
19052
+ return null;
19053
+ }
19054
+ function readLedger(cwd) {
19055
+ const file = ledgerPath(cwd);
19056
+ if (!file.ok)
19057
+ return file;
19058
+ let text = "";
19059
+ try {
19060
+ text = fs10.readFileSync(file.value, "utf8");
19061
+ } catch (error) {
19062
+ if (error.code === "ENOENT")
19063
+ return { ok: true, value: { path: file.value, rows: [], skipped: 0 } };
19064
+ return err("unavailable", `cannot read ${file.value}: ${error.message}`);
19065
+ }
19066
+ const rows = [];
19067
+ let skipped = 0;
19068
+ for (const raw of text.split(`
19069
+ `)) {
19070
+ const trimmed = raw.trim();
19071
+ if (!trimmed)
19072
+ continue;
19073
+ let parsed;
19074
+ try {
19075
+ parsed = JSON.parse(trimmed);
19076
+ } catch {
19077
+ skipped += 1;
19078
+ continue;
19079
+ }
19080
+ if (!isRecord2(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
19081
+ skipped += 1;
19082
+ continue;
19083
+ }
19084
+ const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
19085
+ rows.push({
19086
+ ...parsed,
19087
+ v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
19088
+ id: parsed.id,
19089
+ at: parsed.at,
19090
+ type: parsed.type,
19091
+ actor: normalizeActor(parsed.actor),
19092
+ branch: str2(parsed.branch),
19093
+ head: str2(parsed.head),
19094
+ base: str2(parsed.base),
19095
+ baseSha: str2(parsed.baseSha),
19096
+ patchId: str2(parsed.patchId),
19097
+ diffHash: str2(parsed.diffHash),
19098
+ tree: str2(parsed.tree),
19099
+ dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
19100
+ ...pr === undefined ? {} : { pr },
19101
+ ...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
19102
+ seq: rows.length + 1,
19103
+ superseded: false,
19104
+ supersedeIgnored: false
19105
+ });
18884
19106
  }
18885
- return left.length - right.length;
18886
- };
19107
+ const byId = new Map;
19108
+ for (const row of rows) {
19109
+ const target = row.supersedes ? byId.get(row.supersedes) : undefined;
19110
+ if (target && supersedeAllowed(row, target) === null)
19111
+ target.superseded = true;
19112
+ else if (row.supersedes)
19113
+ row.supersedeIgnored = true;
19114
+ if (!byId.has(row.id))
19115
+ byId.set(row.id, row);
19116
+ }
19117
+ return { ok: true, value: { path: file.value, rows, skipped } };
19118
+ }
19119
+ function verdictBasis(verdict, current) {
19120
+ if (verdict.head && verdict.head === current.head)
19121
+ return "fresh";
19122
+ if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
19123
+ return "carried";
19124
+ return "stale";
19125
+ }
19126
+ var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded;
19127
+ function trailerSessions(cwd, base, head) {
19128
+ if (!base || !head || !safeRef(base) || !safeRef(head))
19129
+ return [];
19130
+ const out = git3(cwd, [
19131
+ "log",
19132
+ "--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
19133
+ `${base}..${head}`
19134
+ ]);
19135
+ return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
19136
+ }
19137
+ function authorSessions(cwd, rows, branch, range = { base: null, head: null }) {
19138
+ const sessions = new Set;
19139
+ for (const row of rows) {
19140
+ if (row.branch !== branch || row.type !== "commit.recorded" && row.type !== "task.opened")
19141
+ continue;
19142
+ const session = str2(row.session) ?? row.actor.session;
19143
+ if (session)
19144
+ sessions.add(session);
19145
+ }
19146
+ for (const session of trailerSessions(cwd, range.base, range.head))
19147
+ sessions.add(session);
19148
+ return sessions;
19149
+ }
19150
+ var independenceReasons = (row, authors) => {
19151
+ const reasons = [];
19152
+ if (!row.actor.session)
19153
+ reasons.push("no_session");
19154
+ else if (authors.has(row.actor.session))
19155
+ reasons.push("author_session");
19156
+ if (row.self === true && !reasons.includes("no_session"))
19157
+ reasons.push("self");
19158
+ return reasons;
19159
+ };
19160
+ function effectiveOf(rows, authors) {
19161
+ let effective = null;
19162
+ for (const row of rows) {
19163
+ if (!effective) {
19164
+ effective = row;
19165
+ continue;
19166
+ }
19167
+ const rowIndependent = independenceReasons(row, authors).length === 0;
19168
+ const effIndependent = independenceReasons(effective, authors).length === 0;
19169
+ if (effIndependent && !rowIndependent)
19170
+ continue;
19171
+ if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session)
19172
+ continue;
19173
+ effective = row;
19174
+ }
19175
+ return effective;
19176
+ }
19177
+ function restackCarries(rows, branch, from, to) {
19178
+ if (!from || !to)
19179
+ return false;
19180
+ const edges = new Map;
19181
+ for (const row of rows)
19182
+ if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
19183
+ edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
19184
+ const seen = new Set([from]);
19185
+ const queue = [from];
19186
+ while (queue.length) {
19187
+ const next = queue.shift();
19188
+ if (next === to)
19189
+ return true;
19190
+ for (const target of edges.get(next) ?? [])
19191
+ if (!seen.has(target)) {
19192
+ seen.add(target);
19193
+ queue.push(target);
19194
+ }
19195
+ }
19196
+ return false;
19197
+ }
19198
+ function checkVerdicts(cwd, branch, rows) {
19199
+ const head = branchHead(cwd, branch);
19200
+ const fallbackBase = defaultBase(cwd);
19201
+ const keyCache = new Map;
19202
+ const keyFor = (base) => {
19203
+ if (!base || !head)
19204
+ return { patchId: null, diffHash: null };
19205
+ let key = keyCache.get(base);
19206
+ if (!key) {
19207
+ key = { patchId: patchId(cwd, base, head), diffHash: diffHash(cwd, base, head) };
19208
+ keyCache.set(base, key);
19209
+ }
19210
+ return key;
19211
+ };
19212
+ const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head });
19213
+ const byKind = new Map;
19214
+ for (const row of rows)
19215
+ if (row.branch === branch && isLiveVerdict(row)) {
19216
+ const kind = typeof row.kind === "string" ? row.kind : "review";
19217
+ byKind.set(kind, [...byKind.get(kind) ?? [], row]);
19218
+ }
19219
+ const verdicts = [];
19220
+ for (const [kind, list] of byKind) {
19221
+ const row = effectiveOf(list, authors);
19222
+ if (!row)
19223
+ continue;
19224
+ let basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
19225
+ if (basis === "stale" && restackCarries(rows, branch, row.head, head))
19226
+ basis = "carried";
19227
+ const current = basis !== "stale";
19228
+ const independence = independenceReasons(row, authors);
19229
+ const reasons = [
19230
+ ...current ? [] : ["stale"],
19231
+ ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
19232
+ ...independence
19233
+ ];
19234
+ verdicts.push({
19235
+ kind,
19236
+ basis,
19237
+ current,
19238
+ independent: independence.length === 0,
19239
+ accepted: reasons.length === 0,
19240
+ reasons,
19241
+ verdict: row
19242
+ });
19243
+ }
19244
+ verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
19245
+ const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
19246
+ const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
19247
+ const newestAccepted = verdicts.findLast((entry) => entry.accepted) ?? null;
19248
+ const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
19249
+ accepted: false,
19250
+ verdict: failing?.verdict ?? null,
19251
+ reasons: failing ? ["failing_verdict"] : newestCurrent?.reasons ?? ["no_verdict"]
19252
+ };
19253
+ const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
19254
+ return {
19255
+ branch,
19256
+ head,
19257
+ review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
19258
+ selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
19259
+ base: newestCurrent?.verdict.base ?? fallbackBase,
19260
+ patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
19261
+ current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
19262
+ accepted,
19263
+ verdicts,
19264
+ authors: [...authors].toSorted()
19265
+ };
19266
+ }
19267
+ var OBSERVED_ONLY = new Set(["check", ...PR_ROW_TYPES]);
19268
+
19269
+ // packages/workit-core/src/core/policy/derive.ts
19270
+ var RULES = {
19271
+ test: "check:test",
19272
+ self: "verdict:self",
19273
+ nonAuthor: "verdict:non-author",
19274
+ verified: "verdict:verified",
19275
+ product: "decision:product",
19276
+ plan: "plan"
19277
+ };
19278
+ var derived2 = (spec) => ({
19279
+ ...spec,
19280
+ id: requirementId({
19281
+ ruleId: spec.ruleId,
19282
+ scope: spec.scope,
19283
+ satisfaction: spec.satisfaction,
19284
+ before: spec.before,
19285
+ dependentAction: spec.dependentAction
19286
+ })
19287
+ });
19288
+ var sorted = (values) => [...new Set(values)].toSorted();
19289
+ var normalizedScope = (scope) => ({
19290
+ description: scope.description,
19291
+ paths: sorted(scope.paths),
19292
+ exclusions: sorted(scope.exclusions)
19293
+ });
19294
+ function deriveRequirements(judgment, scope, constraints, verification = "self") {
19295
+ const out = [];
19296
+ const base = { scope, dependentAction: null };
19297
+ if (judgment.behaviorChange)
19298
+ out.push(derived2({
19299
+ ...base,
19300
+ ruleId: RULES.test,
19301
+ dimension: "testing",
19302
+ reason: "Behavior changes: the configured test check must pass on the final tree.",
19303
+ satisfaction: "Run `workit check test` on the final tree.",
19304
+ before: "close",
19305
+ acceptanceAllowed: false
19306
+ }));
19307
+ if (judgment.behaviorChange && judgment.riskTier === "normal")
19308
+ out.push(verification === "independent" ? derived2({
19309
+ ...base,
19310
+ ruleId: RULES.nonAuthor,
19311
+ dimension: "review",
19312
+ reason: "This workspace requires independent verification: a session that did not author the change records the verdict.",
19313
+ satisfaction: "An independent session records a passing verdict: `workit ledger verdict verified --how <what it exercised>`.",
19314
+ before: "close",
19315
+ acceptanceAllowed: false
19316
+ }) : derived2({
19317
+ ...base,
19318
+ ruleId: RULES.self,
19319
+ dimension: "review",
19320
+ reason: "A behavior change needs a recorded verdict; the author's own is allowed and is shown as self-reviewed, never verified.",
19321
+ 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).",
19322
+ before: "close",
19323
+ acceptanceAllowed: false
19324
+ }));
19325
+ if (judgment.riskTier === "high")
19326
+ out.push(derived2({
19327
+ ...base,
19328
+ ruleId: RULES.verified,
19329
+ dimension: "review",
19330
+ reason: "High risk: an independent verifier must exercise the live surface.",
19331
+ satisfaction: "An independent verifier runs the real surface and records `workit ledger verdict verified --kind live --how <what it exercised>`.",
19332
+ before: "close",
19333
+ acceptanceAllowed: false
19334
+ }));
19335
+ if (judgment.productChoiceOpen)
19336
+ out.push(derived2({
19337
+ ...base,
19338
+ ruleId: RULES.product,
19339
+ dimension: "decisions",
19340
+ reason: "A product or preference choice is open; the user decides it before code is written.",
19341
+ satisfaction: 'Ask the user (with your recommendation), then record the answer: `workit ledger decision "<choice>" --why "<reason>"`.',
19342
+ before: "write",
19343
+ acceptanceAllowed: false
19344
+ }));
19345
+ if (judgment.needsPlan || judgment.riskTier === "high")
19346
+ out.push(derived2({
19347
+ ...base,
19348
+ ruleId: RULES.plan,
19349
+ dimension: "artifacts",
19350
+ reason: judgment.needsPlan ? "The work needs a written plan or spec before implementation." : "High-risk work needs a written plan before implementation.",
19351
+ 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>`.",
19352
+ before: "write",
19353
+ acceptanceAllowed: true
19354
+ }));
19355
+ for (const constraint of constraints)
19356
+ for (const obligation of constraint.requires)
19357
+ out.push(derived2({
19358
+ ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
19359
+ kind: obligation.kind,
19360
+ scope: normalizedScope(obligation.scope),
19361
+ refs: obligation.refs.map((ref) => JSON.stringify(ref)).toSorted(),
19362
+ method: obligation.method,
19363
+ before: obligation.before,
19364
+ dependentAction: obligation.dependentAction
19365
+ })}`,
19366
+ dimension: obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge"),
19367
+ scope: obligation.scope,
19368
+ reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
19369
+ satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
19370
+ before: obligation.before,
19371
+ dependentAction: obligation.dependentAction,
19372
+ acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
19373
+ }));
19374
+ const seen = new Set;
19375
+ return out.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true));
19376
+ }
19377
+ var latestJudgment = (task) => task.judgments?.at(-1)?.data ?? null;
19378
+ var ledgerView = (root) => {
19379
+ let branch;
19380
+ let rows;
19381
+ return {
19382
+ branch: () => branch === undefined ? branch = currentBranch(root) : branch,
19383
+ rows: () => {
19384
+ if (rows === undefined) {
19385
+ const read = readLedger(root);
19386
+ rows = read.ok ? read.value.rows : [];
19387
+ }
19388
+ return rows;
19389
+ }
19390
+ };
19391
+ };
19392
+ var productDecision = (task, ledger) => {
19393
+ const decisions = task.decisions.filter((entry) => entry.data.revoked === null && (entry.data.response === "approved" || entry.data.response === "stated") && entry.data.purpose !== "limitation");
19394
+ if (decisions.length)
19395
+ return {
19396
+ met: true,
19397
+ reason: "the user's decision is recorded on the task",
19398
+ decisionIds: decisions.map((entry) => entry.id)
19399
+ };
19400
+ const since = Date.parse(task.createdAt);
19401
+ const branch = ledger.branch();
19402
+ const row = ledger.rows().findLast((item) => item.type === "decision" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
19403
+ return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
19404
+ met: false,
19405
+ reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or re-judge productChoiceOpen=false if it is not open)',
19406
+ decisionIds: []
19407
+ };
19408
+ };
19409
+ var planRecorded = (task, root) => {
19410
+ const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync3(path10.resolve(root, ref.path)));
19411
+ if (cited)
19412
+ return {
19413
+ met: true,
19414
+ reason: `plan recorded: ${cited.kind === "file" ? cited.path : cited.kind === "external" ? cited.url : ""}`,
19415
+ decisionIds: []
19416
+ };
19417
+ const artifact = task.evidence.some((entry) => entry.data.kind === "artifact" && entry.data.result === "passed");
19418
+ return artifact ? { met: true, reason: "a passing artifact records the plan", decisionIds: [] } : {
19419
+ met: false,
19420
+ reason: "no plan recorded: write it (docs/ and Markdown writes stay allowed), then `workit policy assess --ref <path>`",
19421
+ decisionIds: []
19422
+ };
19423
+ };
19424
+ var verdict = (requirement, root, ledger) => {
19425
+ const branch = ledger.branch();
19426
+ if (!branch)
19427
+ return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
19428
+ const check = checkVerdicts(root, branch, ledger.rows());
19429
+ const failing = check.accepted.reasons.includes("failing_verdict");
19430
+ const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
19431
+ 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)));
19432
+ if (met)
19433
+ return {
19434
+ met: true,
19435
+ reason: `${met.accepted ? "accepted independent" : "self-reviewed (author's own)"} ${String(met.verdict.result)} verdict ${met.verdict.id}`,
19436
+ decisionIds: []
19437
+ };
19438
+ 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, " ");
19439
+ 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>\``;
19440
+ return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
19441
+ };
19442
+ function derivedRuleStatus(task, requirement, root, ledger) {
19443
+ switch (requirement.ruleId) {
19444
+ case RULES.product:
19445
+ return productDecision(task, ledger);
19446
+ case RULES.plan:
19447
+ return planRecorded(task, root);
19448
+ case RULES.self:
19449
+ case RULES.nonAuthor:
19450
+ case RULES.verified:
19451
+ return verdict(requirement, root, ledger);
19452
+ default:
19453
+ return null;
19454
+ }
19455
+ }
19456
+
19457
+ // packages/workit-core/src/core/task-evaluation.ts
19458
+ var digestBytes = (value) => createHash7("sha256").update(value).digest("hex");
19459
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path11.sep}`);
19460
+ var relative = (root, candidate) => path11.relative(root, candidate).split(path11.sep).join("/") || ".";
19461
+ var canonicalPath = (value) => {
19462
+ if (typeof value !== "string" || !value)
19463
+ return null;
19464
+ if (value.includes("\\") || value.split("/").includes(".."))
19465
+ return null;
19466
+ const normalized = path11.posix.normalize(value);
19467
+ if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
19468
+ return null;
19469
+ return normalized === "." ? "." : normalized.replace(/\/$/, "");
19470
+ };
19471
+ var canonicalScope = (scope) => {
19472
+ const paths = scope.paths.length ? scope.paths : ["."];
19473
+ const normalizedPaths = paths.map(canonicalPath);
19474
+ const normalizedExclusions = scope.exclusions.map(canonicalPath);
19475
+ if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
19476
+ return null;
19477
+ return {
19478
+ description: scope.description,
19479
+ paths: [...new Set(normalizedPaths)],
19480
+ exclusions: [...new Set(normalizedExclusions)]
19481
+ };
19482
+ };
19483
+ var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
19484
+ var scopeMatches = (value, scope) => {
19485
+ const normalized = canonicalScope(scope);
19486
+ if (!normalized)
19487
+ return false;
19488
+ const paths = normalized.paths;
19489
+ return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
19490
+ };
19491
+ var pathRelevant = (value, scope) => scopeMatches(value, scope);
19492
+ var compareCodeUnits2 = (left, right) => {
19493
+ const length = Math.min(left.length, right.length);
19494
+ for (let index = 0;index < length; index += 1) {
19495
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
19496
+ if (difference !== 0)
19497
+ return difference;
19498
+ }
19499
+ return left.length - right.length;
19500
+ };
18887
19501
  var gitPaths = (root) => {
18888
- const result = spawnSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
19502
+ const result = spawnSync6("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
18889
19503
  cwd: root,
18890
19504
  encoding: "buffer"
18891
19505
  });
@@ -18897,21 +19511,21 @@ var gitPaths = (root) => {
18897
19511
  git: !/not a git repository/i.test(stderr)
18898
19512
  };
18899
19513
  }
18900
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")) : [];
18901
- const stagedDeleted = spawnSync5("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
19514
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")) : [];
19515
+ const stagedDeleted = spawnSync6("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
18902
19516
  if (stagedDeleted.status !== 0) {
18903
19517
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
18904
19518
  if (!/not a git repository/i.test(stderr))
18905
19519
  return { paths, uncertain: true, git: true };
18906
19520
  } else if (stagedDeleted.stdout) {
18907
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")));
19521
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")));
18908
19522
  }
18909
19523
  return { paths: [...new Set(paths)], uncertain: false, git: true };
18910
19524
  };
18911
19525
  var walk = (root, directory, output) => {
18912
19526
  let names;
18913
19527
  try {
18914
- names = fs10.readdirSync(directory);
19528
+ names = fs11.readdirSync(directory);
18915
19529
  } catch {
18916
19530
  return true;
18917
19531
  }
@@ -18919,10 +19533,10 @@ var walk = (root, directory, output) => {
18919
19533
  for (const name of names) {
18920
19534
  if (name === ".git" || name === ".workit")
18921
19535
  continue;
18922
- const target = path9.join(directory, name);
19536
+ const target = path11.join(directory, name);
18923
19537
  let stat;
18924
19538
  try {
18925
- stat = fs10.lstatSync(target);
19539
+ stat = fs11.lstatSync(target);
18926
19540
  } catch {
18927
19541
  uncertain = true;
18928
19542
  continue;
@@ -18937,14 +19551,14 @@ var walk = (root, directory, output) => {
18937
19551
  var scopeRoots = (root, scope) => {
18938
19552
  const roots = [];
18939
19553
  for (const item of scope.paths.length ? scope.paths : ["."]) {
18940
- const target = path9.resolve(root, item);
19554
+ const target = path11.resolve(root, item);
18941
19555
  if (!inside(root, target))
18942
19556
  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` }] });
18943
19557
  let ancestor = target;
18944
- while (!fs10.existsSync(ancestor) && ancestor !== root)
18945
- ancestor = path9.dirname(ancestor);
19558
+ while (!fs11.existsSync(ancestor) && ancestor !== root)
19559
+ ancestor = path11.dirname(ancestor);
18946
19560
  try {
18947
- if (!inside(root, fs10.realpathSync(ancestor)))
19561
+ if (!inside(root, fs11.realpathSync(ancestor)))
18948
19562
  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` }] });
18949
19563
  } catch {
18950
19564
  return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
@@ -18956,7 +19570,7 @@ var scopeRoots = (root, scope) => {
18956
19570
  function captureCandidate(root, scope, environment = []) {
18957
19571
  let checkout;
18958
19572
  try {
18959
- checkout = fs10.realpathSync(root);
19573
+ checkout = fs11.realpathSync(root);
18960
19574
  } catch {
18961
19575
  return failure2("invalid_input", "candidate root does not exist");
18962
19576
  }
@@ -18974,11 +19588,11 @@ function captureCandidate(root, scope, environment = []) {
18974
19588
  if (item === ".git" || item === ".workit")
18975
19589
  continue;
18976
19590
  names.add(item);
18977
- if (!fs10.existsSync(target))
19591
+ if (!fs11.existsSync(target))
18978
19592
  continue;
18979
19593
  let stat;
18980
19594
  try {
18981
- stat = fs10.lstatSync(target);
19595
+ stat = fs11.lstatSync(target);
18982
19596
  } catch {
18983
19597
  uncertain = true;
18984
19598
  continue;
@@ -18994,17 +19608,17 @@ function captureCandidate(root, scope, environment = []) {
18994
19608
  for (const item of [...names].toSorted()) {
18995
19609
  if (!scopeMatches(item, normalizedScope))
18996
19610
  continue;
18997
- const target = path9.join(checkout, item);
19611
+ const target = path11.join(checkout, item);
18998
19612
  try {
18999
- const stat = fs10.lstatSync(target);
19613
+ const stat = fs11.lstatSync(target);
19000
19614
  if (stat.isSymbolicLink()) {
19001
- const link = fs10.readlinkSync(target);
19615
+ const link = fs11.readlinkSync(target);
19002
19616
  files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
19003
19617
  } else if (stat.isFile()) {
19004
19618
  files.push({
19005
19619
  path: item,
19006
19620
  kind: "file",
19007
- digest: digestBytes(fs10.readFileSync(target)),
19621
+ digest: digestBytes(fs11.readFileSync(target)),
19008
19622
  executable: (stat.mode & 73) !== 0
19009
19623
  });
19010
19624
  } else if (!stat.isDirectory()) {
@@ -19029,7 +19643,7 @@ function captureCandidate(root, scope, environment = []) {
19029
19643
  const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
19030
19644
  if (values.some(({ value }) => value === null))
19031
19645
  completeness = "uncertain";
19032
- const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
19646
+ const headResult = spawnSync6("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
19033
19647
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
19034
19648
  completeness = "uncertain";
19035
19649
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -19169,7 +19783,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
19169
19783
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
19170
19784
  let root;
19171
19785
  try {
19172
- root = fs10.realpathSync(checkoutRoot);
19786
+ root = fs11.realpathSync(checkoutRoot);
19173
19787
  } catch {
19174
19788
  return failure2("permission_denied", "checkout root is unavailable");
19175
19789
  }
@@ -19178,23 +19792,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
19178
19792
  continue;
19179
19793
  if (!reference.digest)
19180
19794
  return failure2("invalid_input", "document references require a byte digest");
19181
- const target = path9.resolve(root, reference.path);
19182
- if (target !== root && !target.startsWith(`${root}${path9.sep}`))
19795
+ const target = path11.resolve(root, reference.path);
19796
+ if (target !== root && !target.startsWith(`${root}${path11.sep}`))
19183
19797
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
19184
19798
  fields: [
19185
19799
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
19186
19800
  ]
19187
19801
  });
19188
19802
  try {
19189
- const below = path9.relative(root, target);
19803
+ const below = path11.relative(root, target);
19190
19804
  let current = root;
19191
- for (const segment of below.split(path9.sep)) {
19805
+ for (const segment of below.split(path11.sep)) {
19192
19806
  if (!segment)
19193
19807
  continue;
19194
- current = path9.join(current, segment);
19195
- const stat = fs10.lstatSync(current);
19196
- const real = fs10.realpathSync(current);
19197
- if (real !== root && !real.startsWith(`${root}${path9.sep}`))
19808
+ current = path11.join(current, segment);
19809
+ const stat = fs11.lstatSync(current);
19810
+ const real = fs11.realpathSync(current);
19811
+ if (real !== root && !real.startsWith(`${root}${path11.sep}`))
19198
19812
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
19199
19813
  fields: [
19200
19814
  {
@@ -19210,7 +19824,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
19210
19824
  if (current !== target && !stat.isDirectory())
19211
19825
  return failure2("invalid_input", "document reference ancestor is not a directory");
19212
19826
  }
19213
- if (digestBytes(fs10.readFileSync(target)) !== reference.digest)
19827
+ if (digestBytes(fs11.readFileSync(target)) !== reference.digest)
19214
19828
  return failure2("permission_denied", "decision document bytes have changed");
19215
19829
  } catch {
19216
19830
  return failure2("invalid_input", "decision document is unavailable");
@@ -19295,7 +19909,35 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
19295
19909
  decisionIds: [],
19296
19910
  reason: "stored policy version is unsupported; reassessment is required"
19297
19911
  }));
19912
+ const ledger = ledgerView(checkoutRoot);
19298
19913
  return (task.policy?.requirements ?? []).map((requirement) => {
19914
+ const derived = derivedRuleStatus(task, requirement, checkoutRoot, ledger);
19915
+ if (derived) {
19916
+ if (derived.met)
19917
+ return {
19918
+ requirementId: requirement.id,
19919
+ status: "satisfied",
19920
+ evidenceIds: [],
19921
+ decisionIds: derived.decisionIds,
19922
+ reason: derived.reason
19923
+ };
19924
+ const waived = requirement.acceptanceAllowed ? applicableDecision(task, workspace, requirement, checkoutRoot) : [];
19925
+ if (waived.length)
19926
+ return {
19927
+ requirementId: requirement.id,
19928
+ status: "accepted_limitation",
19929
+ evidenceIds: [],
19930
+ decisionIds: waived.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
19931
+ reason: "an applicable approved limitation permits the missing evidence"
19932
+ };
19933
+ return {
19934
+ requirementId: requirement.id,
19935
+ status: "unsatisfied",
19936
+ evidenceIds: [],
19937
+ decisionIds: [],
19938
+ reason: derived.reason
19939
+ };
19940
+ }
19299
19941
  const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
19300
19942
  if (gate?.satisfied.length)
19301
19943
  return {
@@ -19710,14 +20352,14 @@ var gitContext = (workspaceRoot, paths = []) => {
19710
20352
  import {
19711
20353
  copyFileSync as copyFileSync2,
19712
20354
  cpSync as cpSync2,
19713
- existsSync as existsSync4,
20355
+ existsSync as existsSync5,
19714
20356
  mkdirSync as mkdirSync2,
19715
20357
  readFileSync as readFileSync3,
19716
20358
  readdirSync as readdirSync3,
19717
20359
  writeFileSync as writeFileSync2
19718
20360
  } from "node:fs";
19719
20361
  import os2 from "node:os";
19720
- import path10 from "node:path";
20362
+ import path12 from "node:path";
19721
20363
 
19722
20364
  // packages/workit-core/src/core/boundary.ts
19723
20365
  import { statSync as statSync2 } from "node:fs";
@@ -19776,7 +20418,7 @@ var mergePreset = (preset, input = {}, current = {
19776
20418
  protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
19777
20419
  };
19778
20420
  };
19779
- 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");
20421
+ 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");
19780
20422
  var migratedDir = null;
19781
20423
  var migrationFailed = false;
19782
20424
  var ensureConfigDir = (dir = resolveConfigDir()) => {
@@ -19786,12 +20428,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
19786
20428
  migratedDir = dir;
19787
20429
  return dir;
19788
20430
  }
19789
- const legacy = path10.join(process.env.XDG_CONFIG_HOME || path10.join(os2.homedir(), ".config"), "workflow-toolkit");
19790
- if (!existsSync4(legacy)) {
20431
+ const legacy = path12.join(process.env.XDG_CONFIG_HOME || path12.join(os2.homedir(), ".config"), "workflow-toolkit");
20432
+ if (!existsSync5(legacy)) {
19791
20433
  migratedDir = dir;
19792
20434
  return dir;
19793
20435
  }
19794
- if (!migrationFailed && existsSync4(dir)) {
20436
+ if (!migrationFailed && existsSync5(dir)) {
19795
20437
  migratedDir = dir;
19796
20438
  return dir;
19797
20439
  }
@@ -19799,9 +20441,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
19799
20441
  mkdirSync2(dir, { recursive: true });
19800
20442
  diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
19801
20443
  for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
19802
- const src = path10.join(legacy, entry.name);
19803
- const dest = path10.join(dir, entry.name);
19804
- if (existsSync4(dest))
20444
+ const src = path12.join(legacy, entry.name);
20445
+ const dest = path12.join(dir, entry.name);
20446
+ if (existsSync5(dest))
19805
20447
  continue;
19806
20448
  try {
19807
20449
  if (entry.isDirectory())
@@ -19872,7 +20514,7 @@ var parseConfigResult = (raw, file) => {
19872
20514
  };
19873
20515
  };
19874
20516
  var readConfigTyped = (dir) => {
19875
- const file = path10.join(dir ?? configDir(), "config.json");
20517
+ const file = path12.join(dir ?? configDir(), "config.json");
19876
20518
  return parseConfigResult(readSafe(file), file);
19877
20519
  };
19878
20520
  var readConfig = () => {
@@ -19917,8 +20559,9 @@ var resolveCommitPolicy = (config, workspace) => {
19917
20559
  };
19918
20560
 
19919
20561
  // packages/workit-core/src/core/workspaces.ts
20562
+ import { createHash as createHash8 } from "node:crypto";
19920
20563
  import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
19921
- import path11 from "node:path";
20564
+ import path13 from "node:path";
19922
20565
  var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
19923
20566
  var branchPreset = custom((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
19924
20567
  var commitPreset = custom((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
@@ -19971,7 +20614,7 @@ var releaseTrackSchema = object({
19971
20614
  context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
19972
20615
  }
19973
20616
  }
19974
- 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(".."))) {
20617
+ 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(".."))) {
19975
20618
  context.addIssue({
19976
20619
  code: "custom",
19977
20620
  path: ["versionSource", "path"],
@@ -19990,6 +20633,7 @@ var workspaceConfigSchema = object({
19990
20633
  }).passthrough().optional(),
19991
20634
  autoApprove: union([boolean2(), array(nonBlank)]).optional(),
19992
20635
  defaultEndpoint: _enum(["commit", "pr"]).optional(),
20636
+ verification: _enum(["self", "independent"]).optional(),
19993
20637
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
19994
20638
  kind,
19995
20639
  union([boolean2(), literal("verified")]).optional()
@@ -20030,6 +20674,72 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
20030
20674
  names.add(workspace.name);
20031
20675
  }
20032
20676
  });
20677
+ var workspacesRevision = (contents) => createHash8("sha256").update(contents ?? "<missing>").digest("hex");
20678
+ var validateWorkspacesDocument = (parsed, file) => {
20679
+ if (!isConfigObject(parsed)) {
20680
+ return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
20681
+ }
20682
+ const validated = workspacesFileSchema.safeParse(parsed, { reportInput: true });
20683
+ if (!validated.success) {
20684
+ const detail = validated.error.issues.map((issue) => {
20685
+ const field = String(issue.path[issue.path.length - 1] ?? "");
20686
+ const attempted = ["provider", "preset", "strategy"].includes(field) && typeof issue.input === "string" ? ` (${JSON.stringify(issue.input)})` : "";
20687
+ return `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}${attempted}`;
20688
+ }).join("; ");
20689
+ return {
20690
+ status: "invalid",
20691
+ path: file,
20692
+ entries: [],
20693
+ error: `${file} has invalid workspace configuration: ${detail}`
20694
+ };
20695
+ }
20696
+ for (const [index, workspace] of (validated.data.workspaces ?? []).entries()) {
20697
+ const glob = validateWorkspaceGlob(workspace.glob);
20698
+ if (!glob.ok) {
20699
+ return {
20700
+ status: "invalid",
20701
+ path: file,
20702
+ entries: [],
20703
+ error: `${file} workspaces.${index}.glob: ${glob.error}`
20704
+ };
20705
+ }
20706
+ }
20707
+ return {
20708
+ status: "valid",
20709
+ path: file,
20710
+ entries: validated.data.workspaces ?? [],
20711
+ document: parsed
20712
+ };
20713
+ };
20714
+ var readWorkspacesResult = (dir = configDir()) => {
20715
+ const file = path13.join(dir, "workspaces.json");
20716
+ let raw;
20717
+ try {
20718
+ raw = readFileSync4(file, "utf8");
20719
+ } catch (error) {
20720
+ if (error.code === "ENOENT")
20721
+ return { status: "missing", path: file, entries: [], revision: workspacesRevision(null) };
20722
+ return {
20723
+ status: "malformed",
20724
+ path: file,
20725
+ entries: [],
20726
+ error: `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
20727
+ };
20728
+ }
20729
+ let parsed;
20730
+ try {
20731
+ parsed = JSON.parse(raw);
20732
+ } catch {
20733
+ return {
20734
+ status: "malformed",
20735
+ path: file,
20736
+ entries: [],
20737
+ revision: workspacesRevision(raw),
20738
+ error: `${file} is not valid JSON`
20739
+ };
20740
+ }
20741
+ return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
20742
+ };
20033
20743
  var globToRegExp = (glob) => {
20034
20744
  let out = "";
20035
20745
  for (let i = 0;i < glob.length; i++) {
@@ -20127,6 +20837,30 @@ var selectWorkspaceMatch = (matches, cwd, workspaceName) => {
20127
20837
  }
20128
20838
  return mostSpecific[0];
20129
20839
  };
20840
+ var resolveWorkspaceFromEntries = (cwd, entries, workspaceName) => {
20841
+ const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
20842
+ const matches = [];
20843
+ for (const ws of entries) {
20844
+ const glob = ws.glob.replaceAll("\\", "/");
20845
+ const canonical = canonicalGlob(glob);
20846
+ let matched = false;
20847
+ for (const target of targets) {
20848
+ if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
20849
+ matched = true;
20850
+ break;
20851
+ }
20852
+ }
20853
+ if (matched)
20854
+ matches.push(ws);
20855
+ }
20856
+ return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
20857
+ };
20858
+ var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
20859
+ const result = readWorkspacesResult(dir);
20860
+ if (result.status === "malformed" || result.status === "invalid")
20861
+ throw new Error(result.error);
20862
+ return resolveWorkspaceFromEntries(cwd, result.entries, workspaceName);
20863
+ };
20130
20864
  var runtimeWorkspacePolicySchema = (kind) => {
20131
20865
  const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
20132
20866
  const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object({
@@ -20149,7 +20883,7 @@ var runtimeWorkspaceIndexSchema = object({
20149
20883
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
20150
20884
  }).passthrough();
20151
20885
  var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
20152
- const file = path11.join(configDir(), "workspaces.json");
20886
+ const file = path13.join(configDir(), "workspaces.json");
20153
20887
  let raw;
20154
20888
  try {
20155
20889
  raw = readFileSync4(file, "utf8");
@@ -20212,13 +20946,13 @@ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
20212
20946
  };
20213
20947
 
20214
20948
  // packages/workit-core/src/core/vcs-config.ts
20215
- import fs11 from "node:fs";
20216
- import path12 from "node:path";
20217
- import { spawnSync as spawnSync6 } from "node:child_process";
20218
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path12.join(configDir(), "vcs.json");
20949
+ import fs12 from "node:fs";
20950
+ import path14 from "node:path";
20951
+ import { spawnSync as spawnSync7 } from "node:child_process";
20952
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path14.join(configDir(), "vcs.json");
20219
20953
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
20220
20954
  var remoteProvider = (cwd) => {
20221
- const r = spawnSync6("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
20955
+ const r = spawnSync7("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
20222
20956
  if (r.status !== 0)
20223
20957
  return null;
20224
20958
  const url = (r.stdout ?? "").trim();
@@ -20255,7 +20989,7 @@ var remoteEndpoint = (remote) => {
20255
20989
  var configuredSshEndpoint = (host) => {
20256
20990
  if (host.startsWith("-"))
20257
20991
  return null;
20258
- const resolved = spawnSync6("ssh", ["-G", host], {
20992
+ const resolved = spawnSync7("ssh", ["-G", host], {
20259
20993
  encoding: "utf8",
20260
20994
  timeout: 5000,
20261
20995
  maxBuffer: 1024 * 1024,
@@ -20305,7 +21039,7 @@ var readVcsConfig = () => {
20305
21039
  const file = vcsConfigPath();
20306
21040
  let raw;
20307
21041
  try {
20308
- raw = fs11.readFileSync(file, "utf8");
21042
+ raw = fs12.readFileSync(file, "utf8");
20309
21043
  } catch {
20310
21044
  return { status: "missing", path: file, config: {} };
20311
21045
  }
@@ -20371,7 +21105,7 @@ function vcsConfig(mode, cwd) {
20371
21105
  const prov = cfg[provider] ?? {};
20372
21106
  const out = {
20373
21107
  ok: true,
20374
- configPath: path12.resolve(cfgPath),
21108
+ configPath: path14.resolve(cfgPath),
20375
21109
  provider,
20376
21110
  defaultTargetBranch: defaultTarget,
20377
21111
  pr: cfg.pr ?? {},
@@ -20409,7 +21143,7 @@ function mergedPrStyle(limit = 6, cwd) {
20409
21143
  `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
20410
21144
  descriptionPreview: desc.slice(0, 600)
20411
21145
  });
20412
- const remoteResult = spawnSync6("git", ["remote", "get-url", "--push", "--all", "origin"], {
21146
+ const remoteResult = spawnSync7("git", ["remote", "get-url", "--push", "--all", "origin"], {
20413
21147
  cwd,
20414
21148
  encoding: "utf8"
20415
21149
  });
@@ -20425,7 +21159,7 @@ function mergedPrStyle(limit = 6, cwd) {
20425
21159
  if (!hostingApiHostMatches(remote, host))
20426
21160
  return { ok: false, error: "PR style target does not match the provider host" };
20427
21161
  if (provider === "gitlab") {
20428
- const run = (args) => spawnSync6("glab", ["api", ...args], {
21162
+ const run = (args) => spawnSync7("glab", ["api", ...args], {
20429
21163
  cwd,
20430
21164
  encoding: "utf8",
20431
21165
  env
@@ -20445,7 +21179,7 @@ function mergedPrStyle(limit = 6, cwd) {
20445
21179
  });
20446
21180
  }
20447
21181
  } else if (provider === "github") {
20448
- const r = spawnSync6("gh", [
21182
+ const r = spawnSync7("gh", [
20449
21183
  "pr",
20450
21184
  "list",
20451
21185
  "--repo",
@@ -20494,305 +21228,34 @@ var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspaceP
20494
21228
  import { realpathSync as realpathSync4 } from "node:fs";
20495
21229
 
20496
21230
  // packages/workit-core/src/core/policy-resolver.ts
20497
- var resolverInputSchema = object({
20498
- intent: intentSchema,
20499
- assessment: assessmentSchema,
20500
- constraints: array(constraintSchema),
20501
- prior: object({
20502
- decisions: array(decisionSchema),
20503
- findings: array(findingSchema),
20504
- requirements: array(requirementSchema)
20505
- }).strict(),
20506
- preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
20507
- }).strict();
20508
- var compareCodeUnits3 = (left, right) => {
20509
- const length = Math.min(left.length, right.length);
20510
- for (let index = 0;index < length; index += 1) {
20511
- const difference = left.charCodeAt(index) - right.charCodeAt(index);
20512
- if (difference !== 0)
20513
- return difference;
20514
- }
20515
- return left.length - right.length;
20516
- };
20517
- var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits3);
20518
- var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right)));
20519
- var normalizedScope = (scope) => ({
20520
- description: scope.description,
20521
- paths: stableUnique(scope.paths),
20522
- exclusions: stableUnique(scope.exclusions)
20523
- });
20524
- var normalizedRefList = (values) => stableList(values);
20525
- var normalizeAssessment = (assessment) => ({
20526
- facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
20527
- signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
20528
- name,
20529
- { ...signal, refs: normalizedRefList(signal.refs) }
20530
- ])),
20531
- consequences: stableList(assessment.consequences.map((consequence) => ({
20532
- ...consequence,
20533
- fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
20534
- }))),
20535
- verification: stableList(assessment.verification.map((verification) => ({
20536
- ...verification,
20537
- scope: normalizedScope(verification.scope),
20538
- availableChecks: normalizedRefList(verification.availableChecks),
20539
- gaps: stableUnique(verification.gaps)
20540
- })))
20541
- });
20542
- var normalizeConstraint = (constraint) => ({
20543
- ...constraint,
20544
- source: constraint.source,
20545
- requires: stableList(constraint.requires.map((requirement) => ({
20546
- ...requirement,
20547
- scope: normalizedScope(requirement.scope),
20548
- refs: normalizedRefList(requirement.refs)
20549
- })))
20550
- });
20551
- var normalizeDecision = (decision) => ({
20552
- ...decision,
20553
- binding: {
20554
- ...decision.binding,
20555
- scope: normalizedScope(decision.binding.scope),
20556
- contentRefs: normalizedRefList(decision.binding.contentRefs)
20557
- },
20558
- requirementIds: stableUnique(decision.requirementIds)
20559
- });
20560
- var normalizeIntent = (intent) => ({
20561
- ...intent,
20562
- scope: normalizedScope(intent.scope),
20563
- authorityRefs: normalizedRefList(intent.authorityRefs)
20564
- });
20565
- var normalizePrior = (prior) => ({
20566
- decisions: stableList(prior.decisions.map(normalizeDecision)),
20567
- findings: stableList(prior.findings.map((finding) => ({
20568
- ...finding,
20569
- scope: normalizedScope(finding.scope),
20570
- refs: normalizedRefList(finding.refs),
20571
- resolution: finding.resolution ? {
20572
- ...finding.resolution,
20573
- evidenceIds: stableUnique(finding.resolution.evidenceIds),
20574
- decisionIds: stableUnique(finding.resolution.decisionIds)
20575
- } : null
20576
- }))),
20577
- requirements: stableList(prior.requirements.map((requirement) => ({
20578
- ...requirement,
20579
- scope: normalizedScope(requirement.scope)
20580
- })))
20581
- });
20582
- var invalidFields = (error) => ({
20583
- fields: error.issues.map((issue) => ({
20584
- path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
20585
- reason: issue.message
20586
- }))
20587
- });
20588
- function normalizeResolverInput(input) {
20589
- const parsed = resolverInputSchema.safeParse(input);
20590
- if (!parsed.success)
20591
- return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
20592
- const value = parsed.data;
20593
- const signals = value.assessment.signals;
20594
- if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
20595
- return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
20596
- fields: [
20597
- {
20598
- path: "assessment.signals",
20599
- reason: "contradictory behaviorChange and mechanicalLowRisk"
20600
- }
20601
- ]
20602
- });
20603
- const ids = value.constraints.map((constraint) => constraint.id);
21231
+ function resolvePolicy(input) {
21232
+ const ids = input.constraints.map((constraint) => constraint.id);
20604
21233
  if (new Set(ids).size !== ids.length)
20605
21234
  return failure2("invalid_input", "constraint IDs must be unique", {
20606
21235
  fields: [{ path: "constraints", reason: "duplicate constraint id" }]
20607
21236
  });
20608
- if (value.preferences?.fast && value.preferences.thorough)
20609
- return failure2("invalid_input", "fast and thorough preferences contradict", {
20610
- fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
20611
- });
20612
- const normalized = {
20613
- intent: normalizeIntent(value.intent),
20614
- assessment: normalizeAssessment(value.assessment),
20615
- constraints: stableList(value.constraints.map(normalizeConstraint)),
20616
- prior: normalizePrior(value.prior),
20617
- ...value.preferences ? { preferences: { ...value.preferences } } : {},
20618
- policyVersion: POLICY_VERSION
20619
- };
20620
- return success(null, null, normalized);
20621
- }
20622
- var requirement = (spec) => ({
20623
- ...spec,
20624
- id: requirementId({
20625
- ruleId: spec.ruleId,
20626
- scope: spec.scope,
20627
- satisfaction: spec.satisfaction,
20628
- before: spec.before,
20629
- dependentAction: spec.dependentAction
20630
- })
20631
- });
20632
- var signalReason = (input, name) => input.assessment.signals[name].reason;
20633
- function resolveRequirements(input) {
20634
- const { signals } = input.assessment;
20635
- const scope = input.intent.scope;
20636
- const requirements = [];
20637
- const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
20638
- const hasUnknownFact = [
20639
- ...input.assessment.facts,
20640
- ...input.assessment.consequences.map(({ fact }) => fact)
20641
- ].some((fact) => fact.basis === "unknown");
20642
- if (hasUnknownSignal || hasUnknownFact)
20643
- requirements.push(requirement({
20644
- ruleId: "consequential-unknown",
20645
- dimension: "investigation",
20646
- scope,
20647
- reason: `Resolve consequential uncertainty before the dependent action: ${[
20648
- ...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
20649
- ...hasUnknownFact ? ["unknown fact"] : []
20650
- ].join(", ")}`,
20651
- satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
20652
- before: "dependent_action",
20653
- dependentAction: null,
20654
- acceptanceAllowed: false
20655
- }));
20656
- if (signals.productChoiceOpen.value === true)
20657
- requirements.push(requirement({
20658
- ruleId: "product-decision",
20659
- dimension: "decisions",
20660
- scope,
20661
- reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
20662
- satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
20663
- before: "dependent_action",
20664
- dependentAction: null,
20665
- acceptanceAllowed: false
20666
- }));
20667
- const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
20668
- if (signals.behaviorChange.value === true)
20669
- requirements.push(requirement({
20670
- ruleId: "behavioral-verification",
20671
- dimension: "testing",
20672
- scope,
20673
- reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
20674
- satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
20675
- before: "close",
20676
- dependentAction: null,
20677
- acceptanceAllowed: false
20678
- }), requirement({
20679
- ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
20680
- dimension: "review",
20681
- scope,
20682
- 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.",
20683
- 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.",
20684
- before: "close",
20685
- dependentAction: null,
20686
- acceptanceAllowed: !independentBehaviorReview
20687
- }));
20688
- else if (signals.mechanicalLowRisk.value === true)
20689
- requirements.push(requirement({
20690
- ruleId: "mechanical-existing-checks",
20691
- dimension: "verification",
20692
- scope,
20693
- reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
20694
- satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
20695
- before: "close",
20696
- dependentAction: null,
20697
- acceptanceAllowed: true
20698
- }), requirement({
20699
- ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
20700
- dimension: "review",
20701
- scope,
20702
- 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.",
20703
- 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.",
20704
- before: "close",
20705
- dependentAction: null,
20706
- acceptanceAllowed: !input.preferences?.thorough
20707
- }));
20708
- if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
20709
- requirements.push(requirement({
20710
- ruleId: "pre-pr-cleanup",
20711
- dimension: "verification",
20712
- scope,
20713
- reason: "Implementation output must be deslopped before it is delivered.",
20714
- satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
20715
- before: "dependent_action",
20716
- dependentAction: "hosting.pull_request",
20717
- acceptanceAllowed: true
20718
- }));
20719
- if (signals.durableAgreementNeeded.value === true)
20720
- requirements.push(requirement({
20721
- ruleId: "durable-spec",
20722
- dimension: "artifacts",
20723
- scope,
20724
- reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
20725
- satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
20726
- before: "write",
20727
- dependentAction: null,
20728
- acceptanceAllowed: true
20729
- }));
20730
- if (signals.coordinationPlanNeeded.value === true)
20731
- requirements.push(requirement({
20732
- ruleId: "coordination-plan",
20733
- dimension: "continuity",
20734
- scope,
20735
- reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
20736
- satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
20737
- before: "dependent_action",
20738
- dependentAction: null,
20739
- acceptanceAllowed: true
20740
- }));
20741
- if (signals.helperUseful.value === true && !input.preferences?.fast)
20742
- requirements.push(requirement({
20743
- ruleId: "helper-usefulness",
20744
- dimension: "delegation",
20745
- scope,
20746
- reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
20747
- satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
20748
- before: "dependent_action",
20749
- dependentAction: null,
20750
- acceptanceAllowed: true
20751
- }));
20752
- for (const constraint of input.constraints)
20753
- for (const obligation of constraint.requires) {
20754
- const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
20755
- requirements.push(requirement({
20756
- ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
20757
- kind: obligation.kind,
20758
- scope: normalizedScope(obligation.scope),
20759
- refs: normalizedRefList(obligation.refs),
20760
- method: obligation.method,
20761
- before: obligation.before,
20762
- dependentAction: obligation.dependentAction
20763
- })}`,
20764
- dimension,
20765
- scope: obligation.scope,
20766
- reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
20767
- satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
20768
- before: obligation.before,
20769
- dependentAction: obligation.dependentAction,
20770
- acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
20771
- }));
20772
- }
20773
- const seen = new Set;
20774
- return requirements.filter((item) => {
20775
- if (seen.has(item.id))
20776
- return false;
20777
- seen.add(item.id);
20778
- return true;
20779
- });
20780
- }
20781
- function resolvePolicy(input) {
20782
- const normalized = normalizeResolverInput(input);
20783
- if (!normalized.ok)
20784
- return normalized;
20785
- const requirements = resolveRequirements(normalized.data);
20786
21237
  const policy = {
20787
21238
  policyVersion: POLICY_VERSION,
20788
- inputDigest: sha256(canonicalJson(normalized.data)),
20789
- requirements
21239
+ inputDigest: sha256(canonicalJson({
21240
+ judgment: input.judgment,
21241
+ scope: input.intent.scope,
21242
+ constraints: input.constraints,
21243
+ verification: input.verification ?? "self",
21244
+ policyVersion: POLICY_VERSION
21245
+ })),
21246
+ requirements: deriveRequirements(input.judgment, input.intent.scope, input.constraints, input.verification)
20790
21247
  };
20791
21248
  const parsed = policySchema.safeParse(policy);
20792
21249
  if (!parsed.success)
20793
- return failure2("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
21250
+ return failure2("invalid_input", "resolved policy is invalid", {
21251
+ fields: parsed.error.issues.map((issue) => ({
21252
+ path: issue.path.join("."),
21253
+ reason: issue.message
21254
+ }))
21255
+ });
20794
21256
  return success(null, null, parsed.data);
20795
21257
  }
21258
+ var compareCodeUnits3 = (left, right) => left < right ? -1 : left > right ? 1 : 0;
20796
21259
  function diffPolicy(previous, next, reason, now) {
20797
21260
  if (!policySchema.safeParse(previous).success && previous !== null)
20798
21261
  throw new TypeError("previous policy is invalid");
@@ -20820,7 +21283,222 @@ function diffPolicy(previous, next, reason, now) {
20820
21283
  return parsed.data;
20821
21284
  }
20822
21285
 
21286
+ // packages/workit-core/src/core/policy/judgment.ts
21287
+ var isObject6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21288
+ var squash = (key) => key.toLowerCase().replace(/[_-]/g, "");
21289
+ var ALIASES = {
21290
+ riskTier: ["risktier", "risk", "risklevel", "tier"],
21291
+ behaviorChange: ["behaviorchange", "behavior", "behaviour", "behaviourchange"],
21292
+ productChoiceOpen: ["productchoiceopen", "openproductchoice", "productchoice", "openchoice"],
21293
+ needsPlan: ["needsplan", "plan", "needsspec", "spec"],
21294
+ note: ["note", "notes", "reason", "why"],
21295
+ refs: ["refs", "ref", "references"]
21296
+ };
21297
+ var LEGACY_KEYS = new Set(["assessment", "facts", "signals", "consequences", "verification"]);
21298
+ var RISK = {
21299
+ trivial: "trivial",
21300
+ low: "trivial",
21301
+ minor: "trivial",
21302
+ mechanical: "trivial",
21303
+ none: "trivial",
21304
+ normal: "normal",
21305
+ medium: "normal",
21306
+ med: "normal",
21307
+ moderate: "normal",
21308
+ high: "high",
21309
+ critical: "high",
21310
+ major: "high"
21311
+ };
21312
+ var YES = new Set(["true", "yes", "y", "1", "on"]);
21313
+ var NO = new Set(["false", "no", "n", "0", "off", "none"]);
21314
+ var flag = (value, unknownAs) => {
21315
+ if (typeof value === "boolean")
21316
+ return { ok: true, value };
21317
+ if (typeof value === "number" && (value === 0 || value === 1))
21318
+ return { ok: true, value: value === 1 };
21319
+ if (typeof value !== "string")
21320
+ return { ok: false };
21321
+ const text = value.trim().toLowerCase();
21322
+ if (YES.has(text))
21323
+ return { ok: true, value: true };
21324
+ if (NO.has(text))
21325
+ return { ok: true, value: false };
21326
+ if (text === "unknown" || text === "maybe")
21327
+ return { ok: true, value: unknownAs };
21328
+ return { ok: false };
21329
+ };
21330
+ var fromLegacy = (raw, previous) => {
21331
+ const source = isObject6(raw.assessment) ? raw.assessment : raw;
21332
+ const signals = isObject6(source.signals) ? source.signals : {};
21333
+ const value = (name) => isObject6(signals[name]) ? signals[name].value : undefined;
21334
+ const known = (name, fallback) => typeof value(name) === "boolean" ? value(name) : fallback;
21335
+ const behaviorChange = known("behaviorChange", previous?.behaviorChange ?? false);
21336
+ const consequences = Array.isArray(source.consequences) ? source.consequences : [];
21337
+ const freshReview = value("behaviorChange") === true && consequences.some((item) => isObject6(item) && ["security", "data", "public_contract", "operations"].includes(String(item.area)));
21338
+ return {
21339
+ riskTier: freshReview ? "high" : value("mechanicalLowRisk") === true && !behaviorChange ? "trivial" : "normal",
21340
+ behaviorChange,
21341
+ productChoiceOpen: known("productChoiceOpen", previous?.productChoiceOpen ?? false),
21342
+ needsPlan: known("durableAgreementNeeded", previous?.needsPlan ?? false) || value("coordinationPlanNeeded") === true
21343
+ };
21344
+ };
21345
+ var DEFAULT = {
21346
+ riskTier: "normal",
21347
+ behaviorChange: false,
21348
+ productChoiceOpen: false,
21349
+ needsPlan: false,
21350
+ note: null,
21351
+ refs: []
21352
+ };
21353
+ function normalizeJudgment(raw, previous) {
21354
+ const input = isObject6(raw) ? raw : {};
21355
+ const legacy = Object.keys(input).some((key) => LEGACY_KEYS.has(key));
21356
+ const base = { ...DEFAULT, ...previous, refs: [...previous?.refs ?? []] };
21357
+ const judgment = legacy ? { ...base, ...fromLegacy(input, previous) } : { ...base };
21358
+ const ignored = [];
21359
+ const fields = [];
21360
+ for (const [key, value] of Object.entries(input)) {
21361
+ if (LEGACY_KEYS.has(key))
21362
+ continue;
21363
+ const name = Object.keys(ALIASES).find((field) => ALIASES[field].includes(squash(key)));
21364
+ if (!name) {
21365
+ ignored.push(key);
21366
+ continue;
21367
+ }
21368
+ if (name === "riskTier") {
21369
+ const tier = typeof value === "string" ? RISK[value.trim().toLowerCase()] : undefined;
21370
+ if (tier)
21371
+ judgment.riskTier = tier;
21372
+ else
21373
+ fields.push({ path: key, reason: "expected trivial|normal|high (or low|medium)" });
21374
+ } else if (name === "note") {
21375
+ judgment.note = typeof value === "string" && value.trim() ? value.trim() : null;
21376
+ } else if (name === "refs") {
21377
+ const items = Array.isArray(value) ? value : [value];
21378
+ judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
21379
+ } else {
21380
+ if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
21381
+ judgment.needsPlan = true;
21382
+ judgment.refs.push(refOf(value.trim()));
21383
+ continue;
21384
+ }
21385
+ const parsed = flag(value, name === "behaviorChange");
21386
+ if (parsed.ok)
21387
+ judgment[name] = parsed.value;
21388
+ else
21389
+ fields.push({ path: key, reason: "expected true|false (yes|no accepted)" });
21390
+ }
21391
+ }
21392
+ if (fields.length)
21393
+ return failure2("invalid_input", "judgment values are invalid", { fields });
21394
+ const seen = new Set;
21395
+ judgment.refs = judgment.refs.filter((ref) => {
21396
+ const key = JSON.stringify(ref);
21397
+ return seen.has(key) ? false : (seen.add(key), true);
21398
+ });
21399
+ const parsed = judgmentSchema.safeParse(judgment);
21400
+ if (!parsed.success)
21401
+ return failure2("invalid_input", "judgment is invalid", {
21402
+ fields: parsed.error.issues.map((issue) => ({
21403
+ path: issue.path.join("."),
21404
+ reason: issue.message
21405
+ }))
21406
+ });
21407
+ return success(null, null, { judgment: parsed.data, ignored, legacy });
21408
+ }
21409
+
21410
+ // packages/workit-core/src/autonomy.ts
21411
+ import os3 from "node:os";
21412
+ import path15 from "node:path";
21413
+ var accountHome = () => {
21414
+ try {
21415
+ return os3.userInfo().homedir;
21416
+ } catch {
21417
+ return os3.homedir();
21418
+ }
21419
+ };
21420
+ var grantsHome = { resolve: accountHome };
21421
+ var grantsDir = () => path15.join(grantsHome.resolve(), ".config", "workit");
21422
+ var grantsOverride = () => {
21423
+ const configured = path15.resolve(resolveConfigDir());
21424
+ 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`;
21425
+ };
21426
+ var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
21427
+ var DEFAULT_GRANTS = Object.freeze({
21428
+ push: true,
21429
+ pr: true,
21430
+ merge: false,
21431
+ release: false,
21432
+ rerun: true
21433
+ });
21434
+ var DEFAULT_ENDPOINT = "commit";
21435
+ var DEFAULT_VERIFICATION = "self";
21436
+ var verificationOf = (entry) => entry?.verification === "independent" ? "independent" : "self";
21437
+ var endpointOf = (entry) => entry?.defaultEndpoint === "pr" ? "pr" : "commit";
21438
+ var isGrantValue = (value) => value === true || value === false || value === "verified";
21439
+ var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
21440
+ var fromAutoApprove = (value) => {
21441
+ if (value === true)
21442
+ return { push: true, pr: true, merge: "verified" };
21443
+ if (!Array.isArray(value))
21444
+ return null;
21445
+ const grants = {};
21446
+ for (const item of value)
21447
+ if (item === "push" || item === "pr")
21448
+ grants[item] = true;
21449
+ else if (item === "merge")
21450
+ grants.merge = "verified";
21451
+ return Object.keys(grants).length > 0 ? grants : null;
21452
+ };
21453
+ function configuredGrants(entry) {
21454
+ const record = entry ?? {};
21455
+ const raw = record.autonomy;
21456
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
21457
+ const grants = {};
21458
+ for (const kind of GRANT_KINDS) {
21459
+ const value = raw[kind];
21460
+ if (isGrantValue(value))
21461
+ grants[kind] = normalized(kind, value);
21462
+ }
21463
+ return { grants, source: "autonomy" };
21464
+ }
21465
+ const legacy = fromAutoApprove(record.autoApprove);
21466
+ return legacy ? { grants: legacy, source: "autoApprove" } : { grants: {}, source: "default" };
21467
+ }
21468
+ function resolveAutonomy(cwd) {
21469
+ const override = grantsOverride();
21470
+ if (override)
21471
+ return {
21472
+ workspace: null,
21473
+ grants: { ...DEFAULT_GRANTS },
21474
+ configured: [],
21475
+ source: "default",
21476
+ accountConfigured: false,
21477
+ defaultEndpoint: DEFAULT_ENDPOINT,
21478
+ verification: DEFAULT_VERIFICATION,
21479
+ note: override
21480
+ };
21481
+ const workspace = resolveWorkspaceFrom(cwd, grantsDir(), process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined);
21482
+ const { grants, source } = configuredGrants(workspace);
21483
+ return {
21484
+ workspace: workspace?.name ?? null,
21485
+ grants: { ...DEFAULT_GRANTS, ...grants },
21486
+ configured: GRANT_KINDS.filter((kind) => grants[kind] !== undefined),
21487
+ source,
21488
+ accountConfigured: Boolean(workspace?.vcs?.account),
21489
+ defaultEndpoint: endpointOf(workspace),
21490
+ verification: verificationOf(workspace)
21491
+ };
21492
+ }
21493
+
20823
21494
  // packages/workit-core/src/core/task-engine.ts
21495
+ var workspaceVerification = (root) => {
21496
+ try {
21497
+ return resolveAutonomy(root).verification;
21498
+ } catch {
21499
+ return "self";
21500
+ }
21501
+ };
20824
21502
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
20825
21503
  kind,
20826
21504
  host: context.caller.host,
@@ -21013,22 +21691,7 @@ var mapRef = (ref, ids) => {
21013
21691
  return ref;
21014
21692
  return { ...ref, id: ids.get(ref.id) ?? ref.id };
21015
21693
  };
21016
- var portableRefs = (refs) => refs.filter((ref) => ref.kind === "file" || ref.kind === "record");
21017
21694
  var portableRef = (ref) => ref && (ref.kind === "file" || ref.kind === "record") ? ref : null;
21018
- var portableFact = (fact) => {
21019
- const refs = portableRefs(fact.refs);
21020
- return fact.basis === "observed" && refs.length === 0 ? { ...fact, basis: "unknown", refs } : { ...fact, refs };
21021
- };
21022
- var portableSignal = (signal) => {
21023
- const refs = portableRefs(signal.refs);
21024
- return signal.basis === "observed" && refs.length === 0 ? {
21025
- ...signal,
21026
- value: "unknown",
21027
- basis: "unknown",
21028
- reason: "portable supporting reference was removed",
21029
- refs
21030
- } : { ...signal, refs };
21031
- };
21032
21695
  var importedProvenance = (context) => ({
21033
21696
  kind: "imported",
21034
21697
  host: context.caller.host,
@@ -21040,18 +21703,6 @@ var portableTask = (task) => {
21040
21703
  if (typeof stripped !== "object" || stripped === null || Array.isArray(stripped))
21041
21704
  throw new TypeError("portable task rewrite produced a non-record");
21042
21705
  const clone = structuredClone(stripped);
21043
- const demote = (facts, signals, consequences) => {
21044
- for (const fact of facts)
21045
- Object.assign(fact, portableFact(fact));
21046
- for (const name of Object.keys(signals)) {
21047
- const signal = signals[name];
21048
- Object.assign(signal, portableSignal(signal));
21049
- }
21050
- for (const consequence of consequences)
21051
- Object.assign(consequence.fact, portableFact(consequence.fact));
21052
- };
21053
- for (const entry of clone.assessments)
21054
- demote(entry.data.facts, entry.data.signals, entry.data.consequences);
21055
21706
  clone.candidates = [];
21056
21707
  return clone;
21057
21708
  };
@@ -21068,6 +21719,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
21068
21719
  for (const entry of [
21069
21720
  source.intent,
21070
21721
  ...source.assessments,
21722
+ ...source.judgments ?? [],
21071
21723
  ...source.evidence,
21072
21724
  ...source.decisions,
21073
21725
  ...source.findings,
@@ -21091,9 +21743,16 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
21091
21743
  id: fresh(entry.id),
21092
21744
  recordedAt: timestamp,
21093
21745
  provenance: imported,
21094
- data: rewriteRecordRefs(data, assessmentSchema, remap)
21746
+ data: rewriteRecordRefs(data, legacyAssessmentSchema, remap)
21095
21747
  };
21096
21748
  });
21749
+ const judgments = source.judgments?.map((entry) => ({
21750
+ ...entry,
21751
+ id: fresh(entry.id),
21752
+ recordedAt: timestamp,
21753
+ provenance: imported,
21754
+ data: rewriteRecordRefs(entry.data, judgmentSchema, remap)
21755
+ }));
21097
21756
  const evidence = source.evidence.map((entry) => ({
21098
21757
  ...entry,
21099
21758
  id: fresh(entry.id),
@@ -21161,6 +21820,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
21161
21820
  status: "paused",
21162
21821
  closure: null,
21163
21822
  assessments,
21823
+ ...judgments ? { judgments } : {},
21164
21824
  evidence,
21165
21825
  decisions: decisions.map((entry) => {
21166
21826
  const data = rewriteRecordRefs(entry.data, decisionSchema, remap);
@@ -21230,7 +21890,8 @@ class WorkitCore {
21230
21890
  retryOmittedRevisions(request, run) {
21231
21891
  return retryFilledRevisions(filledRevisions(request), run);
21232
21892
  }
21233
- implicitTask(family, request) {
21893
+ implicitTask(family, raw) {
21894
+ const request = normalizeOperationInput(family, raw, this.context.caller.host);
21234
21895
  if (typeof request !== "object" || request === null || Array.isArray(request))
21235
21896
  return success(null, null, request);
21236
21897
  const value = request;
@@ -21484,7 +22145,11 @@ class WorkitCore {
21484
22145
  if ((this.context.workerId ?? null) !== null)
21485
22146
  return failure2("permission_denied", "helpers cannot change task requirements");
21486
22147
  this.fillRevisions(input, task.data);
21487
- const resolved = this.resolve(task.data, input.assessment);
22148
+ const judged = normalizeJudgment(input.judgment, latestJudgment(task.data));
22149
+ if (!judged.ok)
22150
+ return judged;
22151
+ const judgment = judged.data.judgment;
22152
+ const resolved = this.resolve(task.data, judgment);
21488
22153
  if (!resolved.ok)
21489
22154
  return resolved;
21490
22155
  if (input.action === "preview")
@@ -21495,16 +22160,17 @@ class WorkitCore {
21495
22160
  if (current.status === "closed")
21496
22161
  return failure2("invalid_transition", "closed task cannot be assessed");
21497
22162
  const nextChange = diffPolicy(current.policy, resolved.data, "policy reassessed", mutation.now);
21498
- const assessment = {
22163
+ const entry = {
21499
22164
  id: newId(),
21500
22165
  recordedAt: mutation.now,
21501
22166
  provenance: provenance(this.context),
21502
- data: input.assessment
22167
+ data: judgment
21503
22168
  };
21504
22169
  const next = {
21505
22170
  ...current,
21506
22171
  constraints: this.context.constraints,
21507
- assessments: [...current.assessments, assessment],
22172
+ judgments: [...current.judgments ?? [], entry],
22173
+ critical: [...new Set([...current.critical ?? [], "judgments"])],
21508
22174
  policy: resolved.data,
21509
22175
  policyChanges: nextChange ? [...current.policyChanges, nextChange] : current.policyChanges
21510
22176
  };
@@ -21706,9 +22372,14 @@ class WorkitCore {
21706
22372
  return workspace;
21707
22373
  if (!workspace.data)
21708
22374
  return failure2("not_found", "workspace not found");
21709
- if (input.binding.taskId !== task.data.id || input.binding.workspaceId !== workspace.data.id)
22375
+ const binding = {
22376
+ ...input.binding,
22377
+ taskId: input.binding.taskId ?? task.data.id,
22378
+ workspaceId: input.binding.workspaceId ?? workspace.data.id
22379
+ };
22380
+ if (binding.taskId !== task.data.id || binding.workspaceId !== workspace.data.id)
21710
22381
  return failure2("invalid_input", "decision task or workspace binding is invalid");
21711
- const content = verifyDecisionContentAtRoot(this.store.root, input.binding);
22382
+ const content = verifyDecisionContentAtRoot(this.store.root, binding);
21712
22383
  if (!content.ok)
21713
22384
  return content;
21714
22385
  const knownRequirements = new Set(task.data.policy?.requirements.map((item) => item.id) ?? []);
@@ -21716,7 +22387,7 @@ class WorkitCore {
21716
22387
  return failure2("invalid_input", "decision references an unknown requirement");
21717
22388
  const base = {
21718
22389
  purpose: input.purpose,
21719
- binding: input.binding,
22390
+ binding,
21720
22391
  response: input.response,
21721
22392
  requirementIds: input.requirementIds,
21722
22393
  revoked: null
@@ -22075,16 +22746,12 @@ class WorkitCore {
22075
22746
  now: trustedNow(this.context)
22076
22747
  });
22077
22748
  }
22078
- resolve(task, assessment) {
22749
+ resolve(task, judgment) {
22079
22750
  return resolvePolicy({
22080
22751
  intent: task.intent.data,
22081
- assessment,
22752
+ judgment,
22082
22753
  constraints: this.context.constraints,
22083
- prior: {
22084
- decisions: task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data),
22085
- findings: task.findings.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data),
22086
- requirements: task.origin || task.policy === null ? [] : task.policy.requirements
22087
- }
22754
+ verification: workspaceVerification(this.context.root)
22088
22755
  });
22089
22756
  }
22090
22757
  summary(task) {
@@ -22410,9 +23077,9 @@ class WorkitCore {
22410
23077
  }
22411
23078
  }
22412
23079
  // packages/workit-core/src/core/repo-context.ts
22413
- import { spawnSync as spawnSync7 } from "node:child_process";
22414
- import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
22415
- import path13 from "node:path";
23080
+ import { spawnSync as spawnSync8 } from "node:child_process";
23081
+ import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
23082
+ import path16 from "node:path";
22416
23083
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
22417
23084
  try {
22418
23085
  gitRevisionParts(value);
@@ -22439,7 +23106,7 @@ var invalidRange = (cwd) => ({
22439
23106
  cwd
22440
23107
  });
22441
23108
  var runGit = (cwd, args) => {
22442
- const result = spawnSync7("git", args, { cwd, encoding: "utf8" });
23109
+ const result = spawnSync8("git", args, { cwd, encoding: "utf8" });
22443
23110
  return {
22444
23111
  stdout: (result.stdout ?? "").trimEnd(),
22445
23112
  stderr: (result.stderr ?? "").trimEnd(),
@@ -22450,11 +23117,11 @@ var repoRoot = (cwd) => {
22450
23117
  const r = runGit(cwd, ["rev-parse", "--show-toplevel"]);
22451
23118
  return r.exitCode === 0 && r.stdout ? r.stdout : cwd;
22452
23119
  };
22453
- var currentBranch = (cwd) => {
23120
+ var currentBranch2 = (cwd) => {
22454
23121
  const r = runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
22455
23122
  return r.exitCode === 0 && r.stdout ? r.stdout : "unknown";
22456
23123
  };
22457
- var defaultBase = (cwd) => {
23124
+ var defaultBase2 = (cwd) => {
22458
23125
  for (const ref of [
22459
23126
  "origin/main",
22460
23127
  "main",
@@ -22468,11 +23135,11 @@ var defaultBase = (cwd) => {
22468
23135
  }
22469
23136
  return "HEAD~1";
22470
23137
  };
22471
- var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${defaultBase(cwd)}...HEAD`;
23138
+ var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${defaultBase2(cwd)}...HEAD`;
22472
23139
  var isProtectedBranch = (branch) => ["main", "master", "develop", "prod", "production"].includes(branch);
22473
23140
  var isPrBranch = (branch) => branch.startsWith("feature/") || branch.startsWith("bugfix/");
22474
23141
  function resolvePrBranchContext(cwd) {
22475
- const branch = currentBranch(cwd);
23142
+ const branch = currentBranch2(cwd);
22476
23143
  if (isProtectedBranch(branch)) {
22477
23144
  return {
22478
23145
  ok: false,
@@ -22546,7 +23213,7 @@ var findPrTemplate = (cwd) => {
22546
23213
  ]) {
22547
23214
  const parts = rel.split("/");
22548
23215
  const base = parts[parts.length - 1];
22549
- const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
23216
+ const dir = parts.length > 1 ? path16.join(cwd, ...parts.slice(0, -1)) : cwd;
22550
23217
  const actual = probeCaseInsensitive(dir, base);
22551
23218
  if (actual)
22552
23219
  return [...parts.slice(0, -1), actual].join("/");
@@ -22577,8 +23244,8 @@ var diffStatForRange = (cwd, range) => {
22577
23244
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
22578
23245
  };
22579
23246
  var packageScripts = (cwd, keys) => {
22580
- const pkgPath = path13.join(cwd, "package.json");
22581
- if (!existsSync5(pkgPath))
23247
+ const pkgPath = path16.join(cwd, "package.json");
23248
+ if (!existsSync6(pkgPath))
22582
23249
  return [];
22583
23250
  try {
22584
23251
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
@@ -22603,9 +23270,9 @@ var documentationFiles = (cwd) => {
22603
23270
  if (entry.isDirectory()) {
22604
23271
  if (excluded.has(entry.name))
22605
23272
  continue;
22606
- walk(path13.join(dir, entry.name), depth + 1);
23273
+ walk(path16.join(dir, entry.name), depth + 1);
22607
23274
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
22608
- matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
23275
+ matches.push("./" + path16.relative(cwd, path16.join(dir, entry.name)).replaceAll("\\", "/"));
22609
23276
  }
22610
23277
  }
22611
23278
  };
@@ -22613,7 +23280,7 @@ var documentationFiles = (cwd) => {
22613
23280
  return matches.toSorted().slice(0, 200);
22614
23281
  };
22615
23282
  var readTrimmed2 = (file, maxLines) => {
22616
- if (!existsSync5(file))
23283
+ if (!existsSync6(file))
22617
23284
  return "";
22618
23285
  const lines = readFileSync5(file, "utf8").split(`
22619
23286
  `);
@@ -22621,7 +23288,7 @@ var readTrimmed2 = (file, maxLines) => {
22621
23288
  `);
22622
23289
  };
22623
23290
  function prReadyContext(root, range) {
22624
- const cwd = path13.resolve(repoRoot(root));
23291
+ const cwd = path16.resolve(repoRoot(root));
22625
23292
  if (range !== undefined && !isResolvableContextRange(cwd, range))
22626
23293
  return invalidRange(cwd);
22627
23294
  let stdout = "";
@@ -22637,7 +23304,7 @@ function prReadyContext(root, range) {
22637
23304
  stdout += printSection("Repository");
22638
23305
  stdout += `root: ${cwd}
22639
23306
  `;
22640
- stdout += `branch: ${currentBranch(cwd)}
23307
+ stdout += `branch: ${currentBranch2(cwd)}
22641
23308
  `;
22642
23309
  stdout += `range: ${range}
22643
23310
  `;
@@ -22679,7 +23346,7 @@ ${status.stderr}` : "");
22679
23346
  stdout += `template_path: ${template}
22680
23347
 
22681
23348
  `;
22682
- stdout += readTrimmed2(path13.join(cwd, template), 220);
23349
+ stdout += readTrimmed2(path16.join(cwd, template), 220);
22683
23350
  } else {
22684
23351
  stdout += `template_path: none
22685
23352
 
@@ -22689,7 +23356,7 @@ ${status.stderr}` : "");
22689
23356
  stdout += `
22690
23357
  `;
22691
23358
  stdout += printSection("Recent Validation Signals");
22692
- if (existsSync5(path13.join(cwd, "package.json"))) {
23359
+ if (existsSync6(path16.join(cwd, "package.json"))) {
22693
23360
  stdout += `package.json detected. Common scripts:
22694
23361
  `;
22695
23362
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -22698,7 +23365,7 @@ ${status.stderr}` : "");
22698
23365
  `) + `
22699
23366
  `;
22700
23367
  }
22701
- if (existsSync5(path13.join(cwd, "Cargo.toml")) || existsSync5(path13.join(cwd, "src-tauri/Cargo.toml"))) {
23368
+ if (existsSync6(path16.join(cwd, "Cargo.toml")) || existsSync6(path16.join(cwd, "src-tauri/Cargo.toml"))) {
22702
23369
  stdout += `Rust project detected.
22703
23370
  `;
22704
23371
  }
@@ -22731,7 +23398,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
22731
23398
  - Apply with the host-native editor, preserving the repository changelog conventions.
22732
23399
  - If Unreleased already has duplicate category headings, normalize_only first.`;
22733
23400
  function changelogContext(root, range) {
22734
- const cwd = path13.resolve(repoRoot(root));
23401
+ const cwd = path16.resolve(repoRoot(root));
22735
23402
  if (range !== undefined && !isResolvableContextRange(cwd, range))
22736
23403
  return invalidRange(cwd);
22737
23404
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -22741,7 +23408,7 @@ function changelogContext(root, range) {
22741
23408
  stdout += printSection("Repository");
22742
23409
  stdout += `root: ${cwd}
22743
23410
  `;
22744
- stdout += `branch: ${currentBranch(cwd)}
23411
+ stdout += `branch: ${currentBranch2(cwd)}
22745
23412
  `;
22746
23413
  stdout += `range: ${resolvedRange}
22747
23414
  `;
@@ -22749,8 +23416,8 @@ function changelogContext(root, range) {
22749
23416
  stdout += CHANGELOG_RULES + `
22750
23417
  `;
22751
23418
  stdout += printSection("Existing CHANGELOG.md");
22752
- const changelogPath = path13.join(cwd, "CHANGELOG.md");
22753
- if (existsSync5(changelogPath)) {
23419
+ const changelogPath = path16.join(cwd, "CHANGELOG.md");
23420
+ if (existsSync6(changelogPath)) {
22754
23421
  stdout += readTrimmed2(changelogPath, 260) + `
22755
23422
  `;
22756
23423
  } else {
@@ -22769,7 +23436,7 @@ function changelogContext(root, range) {
22769
23436
  return { stdout, stderr: "", exitCode: 0, cwd };
22770
23437
  }
22771
23438
  function docsRefreshContext(root, range) {
22772
- const cwd = path13.resolve(repoRoot(root));
23439
+ const cwd = path16.resolve(repoRoot(root));
22773
23440
  if (range !== undefined && !isResolvableContextRange(cwd, range))
22774
23441
  return invalidRange(cwd);
22775
23442
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -22779,7 +23446,7 @@ function docsRefreshContext(root, range) {
22779
23446
  stdout += printSection("Repository");
22780
23447
  stdout += `root: ${cwd}
22781
23448
  `;
22782
- stdout += `branch: ${currentBranch(cwd)}
23449
+ stdout += `branch: ${currentBranch2(cwd)}
22783
23450
  `;
22784
23451
  stdout += `range: ${resolvedRange}
22785
23452
  `;
@@ -22791,8 +23458,8 @@ function docsRefreshContext(root, range) {
22791
23458
  `) + `
22792
23459
  `;
22793
23460
  stdout += printSection("README Preview");
22794
- const readme = path13.join(cwd, "README.md");
22795
- if (existsSync5(readme)) {
23461
+ const readme = path16.join(cwd, "README.md");
23462
+ if (existsSync6(readme)) {
22796
23463
  stdout += readTrimmed2(readme, 220) + `
22797
23464
  `;
22798
23465
  } else {
@@ -22800,8 +23467,8 @@ function docsRefreshContext(root, range) {
22800
23467
  `;
22801
23468
  }
22802
23469
  stdout += printSection("Package Scripts");
22803
- const pkgPath = path13.join(cwd, "package.json");
22804
- if (existsSync5(pkgPath)) {
23470
+ const pkgPath = path16.join(cwd, "package.json");
23471
+ if (existsSync6(pkgPath)) {
22805
23472
  try {
22806
23473
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
22807
23474
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
@@ -22814,7 +23481,7 @@ function docsRefreshContext(root, range) {
22814
23481
  return { stdout, stderr: "", exitCode: 0, cwd };
22815
23482
  }
22816
23483
  function releaseNotesContext(root, rangeOrTag) {
22817
- const cwd = path13.resolve(repoRoot(root));
23484
+ const cwd = path16.resolve(repoRoot(root));
22818
23485
  if (!rangeOrTag) {
22819
23486
  return { stdout: "", stderr: `ERROR: release tag or range required
22820
23487
  `, exitCode: 1, cwd };
@@ -22828,7 +23495,7 @@ function releaseNotesContext(root, rangeOrTag) {
22828
23495
  stdout += printSection("Repository");
22829
23496
  stdout += `root: ${cwd}
22830
23497
  `;
22831
- stdout += `branch: ${currentBranch(cwd)}
23498
+ stdout += `branch: ${currentBranch2(cwd)}
22832
23499
  `;
22833
23500
  stdout += `requested: ${rangeOrTag}
22834
23501
  `;
@@ -22852,7 +23519,7 @@ function releaseNotesContext(root, rangeOrTag) {
22852
23519
  `;
22853
23520
  stdout += printSection("Existing Release Files");
22854
23521
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
22855
- if (existsSync5(path13.join(cwd, rel)))
23522
+ if (existsSync6(path16.join(cwd, rel)))
22856
23523
  stdout += rel + `
22857
23524
  `;
22858
23525
  }
@@ -22880,12 +23547,12 @@ function releaseNotesContext(root, rangeOrTag) {
22880
23547
  }
22881
23548
 
22882
23549
  // packages/workit-core/src/core/tracker-issues.ts
22883
- import { spawnSync as spawnSync9 } from "node:child_process";
23550
+ import { spawnSync as spawnSync10 } from "node:child_process";
22884
23551
 
22885
23552
  // packages/workit-core/src/core/pr-create.ts
22886
- import fs12 from "node:fs";
22887
- import path14 from "node:path";
22888
- import { spawnSync as spawnSync8 } from "node:child_process";
23553
+ import fs13 from "node:fs";
23554
+ import path17 from "node:path";
23555
+ import { spawnSync as spawnSync9 } from "node:child_process";
22889
23556
  // packages/workit-core/src/forge/pr-body.ts
22890
23557
  function parseGhIssue(value) {
22891
23558
  const m = /issues\/(\d+)/.exec(value);
@@ -22895,13 +23562,13 @@ function parseGhIssue(value) {
22895
23562
  // packages/workit-core/src/core/pr-create.ts
22896
23563
  function whichOnPath(tool) {
22897
23564
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
22898
- for (const dir of (process.env.PATH ?? "").split(path14.delimiter)) {
23565
+ for (const dir of (process.env.PATH ?? "").split(path17.delimiter)) {
22899
23566
  if (!dir)
22900
23567
  continue;
22901
23568
  for (const name of names) {
22902
- const candidate = path14.join(dir, name);
23569
+ const candidate = path17.join(dir, name);
22903
23570
  try {
22904
- fs12.accessSync(candidate, fs12.constants.X_OK);
23571
+ fs13.accessSync(candidate, fs13.constants.X_OK);
22905
23572
  return candidate;
22906
23573
  } catch {}
22907
23574
  }
@@ -22910,7 +23577,7 @@ function whichOnPath(tool) {
22910
23577
  }
22911
23578
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
22912
23579
  var pushRemote = (cwd) => {
22913
- const result = spawnSync8("git", ["remote", "get-url", "--push", "--all", "origin"], {
23580
+ const result = spawnSync9("git", ["remote", "get-url", "--push", "--all", "origin"], {
22914
23581
  cwd,
22915
23582
  encoding: "utf8"
22916
23583
  });
@@ -22941,7 +23608,7 @@ var defaultCli = (bin, available) => {
22941
23608
  if (!available)
22942
23609
  return null;
22943
23610
  return (args, root) => {
22944
- const out = spawnSync9(bin, args, {
23611
+ const out = spawnSync10(bin, args, {
22945
23612
  cwd: root,
22946
23613
  encoding: "utf8",
22947
23614
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -23019,8 +23686,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
23019
23686
  };
23020
23687
 
23021
23688
  // packages/workit-core/src/core/youtrack.ts
23022
- import fs13 from "node:fs";
23023
- import path15 from "node:path";
23689
+ import fs14 from "node:fs";
23690
+ import path18 from "node:path";
23024
23691
 
23025
23692
  // packages/workit-core/src/core/templates.ts
23026
23693
  var repoRoot2 = assetRoot();
@@ -23033,22 +23700,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
23033
23700
  // packages/workit-core/src/core/youtrack.ts
23034
23701
  var ISSUE_RE = /^[A-Z]+-\d+$/;
23035
23702
  var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
23036
- var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path15.join(configDir(), "youtrack.json");
23037
- var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path15.join(resolveConfigDir(), "youtrack.json");
23703
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(configDir(), "youtrack.json");
23704
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(resolveConfigDir(), "youtrack.json");
23038
23705
  var youTrackTokenModeOk = (p) => {
23039
23706
  if (process.platform === "win32")
23040
23707
  return true;
23041
- const mode = fs13.statSync(p).mode & 511;
23708
+ const mode = fs14.statSync(p).mode & 511;
23042
23709
  return mode === 384;
23043
23710
  };
23044
23711
  function readYouTrackConfig(required) {
23045
23712
  const cfgPath = youTrackConfigPath();
23046
- if (!fs13.existsSync(cfgPath)) {
23713
+ if (!fs14.existsSync(cfgPath)) {
23047
23714
  return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
23048
23715
  }
23049
23716
  let parsed;
23050
23717
  try {
23051
- parsed = JSON.parse(fs13.readFileSync(cfgPath, "utf8"));
23718
+ parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
23052
23719
  } catch {
23053
23720
  return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
23054
23721
  }
@@ -23059,11 +23726,11 @@ function readYouTrackConfig(required) {
23059
23726
  }
23060
23727
  var readYouTrackContextConfig = () => {
23061
23728
  const cfgPath = youTrackReadOnlyConfigPath();
23062
- if (!fs13.existsSync(cfgPath))
23729
+ if (!fs14.existsSync(cfgPath))
23063
23730
  return { data: {} };
23064
23731
  let parsed;
23065
23732
  try {
23066
- parsed = JSON.parse(fs13.readFileSync(cfgPath, "utf8"));
23733
+ parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
23067
23734
  } catch {
23068
23735
  return { error: "YouTrack configuration is unavailable" };
23069
23736
  }
@@ -23102,12 +23769,12 @@ var youTrackToken = () => {
23102
23769
  if ("error" in loaded)
23103
23770
  return loaded;
23104
23771
  const tokenFile = String(loaded.config.tokenFile ?? "");
23105
- const tokenPath = tokenFile ? path15.isAbsolute(tokenFile) ? path15.resolve(tokenFile) : path15.resolve(process.cwd(), tokenFile) : "";
23106
- if (!tokenPath || !fs13.existsSync(tokenPath))
23772
+ const tokenPath = tokenFile ? path18.isAbsolute(tokenFile) ? path18.resolve(tokenFile) : path18.resolve(process.cwd(), tokenFile) : "";
23773
+ if (!tokenPath || !fs14.existsSync(tokenPath))
23107
23774
  return { error: "missing youtrack.token" };
23108
23775
  if (!youTrackTokenModeOk(tokenPath))
23109
23776
  return { error: "youtrack.token mode must be 0600" };
23110
- const token = fs13.readFileSync(tokenPath, "utf8").trim();
23777
+ const token = fs14.readFileSync(tokenPath, "utf8").trim();
23111
23778
  if (!token)
23112
23779
  return { error: "empty token file" };
23113
23780
  if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
@@ -23172,9 +23839,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
23172
23839
  } catch {
23173
23840
  continue;
23174
23841
  }
23175
- if (!fs13.existsSync(full))
23842
+ if (!fs14.existsSync(full))
23176
23843
  continue;
23177
- const text = fs13.readFileSync(full, "utf8");
23844
+ const text = fs14.readFileSync(full, "utf8");
23178
23845
  const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
23179
23846
  if (m)
23180
23847
  return m[1];
@@ -23444,22 +24111,22 @@ function resolveGitRevision(root, value) {
23444
24111
  }
23445
24112
  function resolveInside(root, candidate) {
23446
24113
  const base = realpathSync5(root);
23447
- const target = path16.resolve(base, candidate);
23448
- if (target !== base && !target.startsWith(base + path16.sep)) {
24114
+ const target = path19.resolve(base, candidate);
24115
+ if (target !== base && !target.startsWith(base + path19.sep)) {
23449
24116
  throw new Error("path must stay inside repository root");
23450
24117
  }
23451
24118
  let ancestor = target;
23452
- while (!existsSync6(ancestor))
23453
- ancestor = path16.dirname(ancestor);
24119
+ while (!existsSync7(ancestor))
24120
+ ancestor = path19.dirname(ancestor);
23454
24121
  const canonicalAncestor = realpathSync5(ancestor);
23455
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path16.sep)) {
24122
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path19.sep)) {
23456
24123
  throw new Error("path must stay inside repository root");
23457
24124
  }
23458
24125
  return target;
23459
24126
  }
23460
24127
  function run2(root, executable, args, env = {}) {
23461
24128
  const cwd = realpathSync5(root);
23462
- const result = spawnSync10(executable, args, {
24129
+ const result = spawnSync11(executable, args, {
23463
24130
  cwd,
23464
24131
  encoding: "utf8",
23465
24132
  env: { ...process.env, ...env }
@@ -23483,8 +24150,8 @@ var SUPPORT_MATRIX = {
23483
24150
  };
23484
24151
 
23485
24152
  // packages/workit-mcp/src/server.ts
23486
- import { existsSync as existsSync7, readFileSync as readFileSync6, realpathSync as realpathSync6 } from "node:fs";
23487
- import path17 from "node:path";
24153
+ import { existsSync as existsSync8, readFileSync as readFileSync6, realpathSync as realpathSync6 } from "node:fs";
24154
+ import path20 from "node:path";
23488
24155
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
23489
24156
  function isZ4Schema(s) {
23490
24157
  const schema = s;
@@ -25863,7 +26530,7 @@ class StdioServerTransport {
25863
26530
  import { fileURLToPath as fileURLToPath2 } from "node:url";
25864
26531
 
25865
26532
  // packages/workit-core/src/core/logger.ts
25866
- import os3 from "node:os";
26533
+ import os4 from "node:os";
25867
26534
  var REDACTED = "[REDACTED]";
25868
26535
  var SECRET_VALUE = /\b(?:Bearer|Basic|Digest|Token)\s+\S+/gi;
25869
26536
  var KEY_EQ_VALUE = /\b([A-Za-z0-9_-]*(?:token|secret|password|passwd|apikey|api[_-]?key|authorization|credential|bearer)[A-Za-z0-9_-]*)([:=]\s*).+/gi;
@@ -25906,7 +26573,7 @@ var SENSITIVE_WORDS = new Set([
25906
26573
  ]);
25907
26574
  var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
25908
26575
  var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25909
- var homePattern = new RegExp(`${escapeRegExp(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
26576
+ var homePattern = new RegExp(`${escapeRegExp(os4.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
25910
26577
  var applyPatterns = (value) => {
25911
26578
  let out = value;
25912
26579
  out = out.replace(SECRET_VALUE, REDACTED);
@@ -25957,13 +26624,13 @@ var rootVariants = (workspaceRoot) => {
25957
26624
  if (!workspaceRoot)
25958
26625
  return [];
25959
26626
  const roots = new Set;
25960
- for (const candidate of [workspaceRoot, path17.resolve(workspaceRoot)]) {
25961
- if (path17.isAbsolute(candidate) && candidate !== path17.parse(candidate).root)
26627
+ for (const candidate of [workspaceRoot, path20.resolve(workspaceRoot)]) {
26628
+ if (path20.isAbsolute(candidate) && candidate !== path20.parse(candidate).root)
25962
26629
  roots.add(candidate);
25963
26630
  }
25964
26631
  try {
25965
26632
  const canonical = realpathSync6(workspaceRoot);
25966
- if (canonical !== path17.parse(canonical).root)
26633
+ if (canonical !== path20.parse(canonical).root)
25967
26634
  roots.add(canonical);
25968
26635
  } catch {}
25969
26636
  return [...roots].toSorted((left, right) => right.length - left.length);
@@ -25985,39 +26652,20 @@ function assertMcpHost(host) {
25985
26652
  throw new Error("MCP host must be cursor, codex_cli, or codex_desktop");
25986
26653
  }
25987
26654
  }
25988
- var operationDescription = (family) => `Workit ${family} operations. Inputs are validated by the shared Workit contract.`;
25989
- var READ_ONLY_ACTIONS = new Set(["list", "inspect", "preview", "explain", "export"]);
25990
- var requiresCallerIdentity = (input) => typeof input !== "object" || input === null || !READ_ONLY_ACTIONS.has(String(input.action));
25991
- var toolInputSchema = (family) => {
25992
- const schema = boundedOperationJsonSchema(family);
25993
- return { type: "object", ...schema };
25994
- };
25995
- var schemaBranches = (schema) => Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : [schema];
25996
- var readOnlyBranches = (family) => schemaBranches(boundedOperationJsonSchema(family)).filter((branch) => {
25997
- const action = branch.properties?.action?.const;
25998
- return typeof action === "string" && READ_ONLY_ACTIONS.has(action);
25999
- });
26000
- var advertisedToolSchemas = (attested) => {
26001
- if (attested)
26002
- return OPERATION_FAMILIES.map((family) => ({
26655
+ var READ_ONLY_ACTIONS2 = new Set(["list", "inspect", "preview", "explain", "export"]);
26656
+ var requiresCallerIdentity = (input) => typeof input !== "object" || input === null || !READ_ONLY_ACTIONS2.has(String(input.action));
26657
+ var advertisedToolSchemas = (attested) => OPERATION_FAMILIES.flatMap((family) => {
26658
+ const schema = flatOperationJsonSchema(family, { readOnly: !attested });
26659
+ if (!schema)
26660
+ return [];
26661
+ return [
26662
+ {
26003
26663
  name: `workit_${family}`,
26004
- description: operationDescription(family),
26005
- inputSchema: toolInputSchema(family)
26006
- }));
26007
- return OPERATION_FAMILIES.flatMap((family) => {
26008
- const branches = readOnlyBranches(family);
26009
- if (!branches.length)
26010
- return [];
26011
- const schema = boundedOperationJsonSchema(family);
26012
- return [
26013
- {
26014
- name: `workit_${family}`,
26015
- description: `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
26016
- inputSchema: { type: "object", ...schema, oneOf: branches }
26017
- }
26018
- ];
26019
- });
26020
- };
26664
+ description: attested ? operationDescription(family) : `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
26665
+ inputSchema: schema
26666
+ }
26667
+ ];
26668
+ });
26021
26669
  var sanitizeFailure = (result, workspaceRoot) => {
26022
26670
  if (result.ok)
26023
26671
  return result;
@@ -26064,12 +26712,12 @@ var thrownResult = (tool, error, workspaceRoot) => {
26064
26712
  };
26065
26713
  function createMcpServer(host, contextProvider) {
26066
26714
  assertMcpHost(host);
26067
- const here = path17.dirname(fileURLToPath2(import.meta.url));
26715
+ const here = path20.dirname(fileURLToPath2(import.meta.url));
26068
26716
  const sourceMarker = markSourcesLoaded([
26069
- path17.join(here, "server.ts"),
26070
- path17.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
26071
- path17.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
26072
- ].filter((file) => existsSync7(file)));
26717
+ path20.join(here, "server.ts"),
26718
+ path20.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
26719
+ path20.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
26720
+ ].filter((file) => existsSync8(file)));
26073
26721
  let staleWarned = false;
26074
26722
  const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
26075
26723
  server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -26193,7 +26841,7 @@ function createMcpServer(host, contextProvider) {
26193
26841
  details: { operation: family }
26194
26842
  }, workspaceRoot);
26195
26843
  }
26196
- const parsed = parseOperation(family, request.params.arguments);
26844
+ const parsed = parseAdvertisedOperation(family, request.params.arguments, host);
26197
26845
  if (!parsed.ok)
26198
26846
  return resultForClient(parsed, workspaceRoot);
26199
26847
  if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
@@ -26260,12 +26908,34 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
26260
26908
  // packages/workit-core/src/hooks/context.ts
26261
26909
  var signals = new Map;
26262
26910
  var cache = new Map;
26911
+ // packages/workit-core/src/hooks/write-gate.ts
26912
+ var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
26913
+ var PREFIXES = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
26914
+ var cache2 = new Map;
26915
+ // packages/workit-core/src/hooks/hosts/fields.ts
26916
+ var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
26917
+ var WRITE_TOOLS = new Set([
26918
+ "write",
26919
+ "edit",
26920
+ "multiedit",
26921
+ "notebookedit",
26922
+ "applypatch",
26923
+ "patch",
26924
+ "delete",
26925
+ "remove",
26926
+ "rename",
26927
+ "mkdir",
26928
+ "mv",
26929
+ "cp",
26930
+ "touch",
26931
+ "strreplace",
26932
+ "searchreplace",
26933
+ "editfile",
26934
+ "writefile"
26935
+ ]);
26263
26936
  // packages/workit-core/src/hooks/handle.ts
26264
26937
  var offered = new Set;
26265
26938
  var CLAUDE_JUDGES = new Set(["workit:verifier", "workit:reviewer"]);
26266
- // packages/workit-core/src/hooks/hosts/fields.ts
26267
- var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
26268
-
26269
26939
  // packages/workit-core/src/hooks/hosts/claude-code.ts
26270
26940
  var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
26271
26941
  var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
@@ -26287,6 +26957,7 @@ var CODEX_DESCRIPTOR = {
26287
26957
  "context.turn": undocumented,
26288
26958
  "shell.pre": { support: "native", native: "PreToolUse" },
26289
26959
  "tool.pre": { support: "native", native: "PreToolUse" },
26960
+ "write.pre": { support: "undocumented", native: null },
26290
26961
  "shell.post": undocumented,
26291
26962
  "subagent.start": { support: "native", native: "SubagentStart" },
26292
26963
  "subagent.stop": { support: "native", native: "SubagentStop" },
@@ -26399,8 +27070,8 @@ var codexQualification = (host) => host === "codex_cli" ? { surface: host, cli:
26399
27070
  var resolveCodexWorkspaceRoot = (pluginRoot, env) => {
26400
27071
  const explicit = env.WORKFLOW_WORKSPACE_ROOT;
26401
27072
  const inherited = env.PWD;
26402
- const candidate = explicit ?? (inherited && path18.resolve(inherited) !== path18.resolve(pluginRoot) ? inherited : undefined);
26403
- if (!candidate || !path18.isAbsolute(candidate) || !existsSync8(candidate))
27073
+ const candidate = explicit ?? (inherited && path21.resolve(inherited) !== path21.resolve(pluginRoot) ? inherited : undefined);
27074
+ if (!candidate || !path21.isAbsolute(candidate) || !existsSync9(candidate))
26404
27075
  return null;
26405
27076
  try {
26406
27077
  if (!statSync3(candidate).isDirectory())