@brainervirus/workit-cursor 6.0.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6622,11 +6622,11 @@ var require_dist = __commonJS(function(exports, module) {
6622
6622
 
6623
6623
  // packages/workit-mcp/src/index.ts
6624
6624
  import { fileURLToPath as fileURLToPath3 } from "node:url";
6625
- import path18 from "node:path";
6625
+ import path21 from "node:path";
6626
6626
 
6627
6627
  // packages/workit-mcp/src/server.ts
6628
- import { existsSync as existsSync7, readFileSync as readFileSync6, realpathSync as realpathSync6 } from "node:fs";
6629
- import path17 from "node:path";
6628
+ import { existsSync as existsSync8, readFileSync as readFileSync6, realpathSync as realpathSync6 } from "node:fs";
6629
+ import path20 from "node:path";
6630
6630
 
6631
6631
  // node_modules/zod/v4/core/util.js
6632
6632
  function getEnumValues(entries) {
@@ -12850,12 +12850,6 @@ var numberProcessor = (schema, ctx, _json, params) => {
12850
12850
  var booleanProcessor = (_schema, _ctx, json, _params) => {
12851
12851
  json.type = "boolean";
12852
12852
  };
12853
- var bigintProcessor = (schema, ctx, json, params) => {
12854
- handleUnrepresentable(schema, ctx, json, params, "BigInt cannot be represented in JSON Schema");
12855
- };
12856
- var symbolProcessor = (schema, ctx, json, params) => {
12857
- handleUnrepresentable(schema, ctx, json, params, "Symbols cannot be represented in JSON Schema");
12858
- };
12859
12853
  var nullProcessor = (_schema, ctx, json, _params) => {
12860
12854
  if (ctx.target === "openapi-3.0") {
12861
12855
  json.type = "string";
@@ -12865,20 +12859,10 @@ var nullProcessor = (_schema, ctx, json, _params) => {
12865
12859
  json.type = "null";
12866
12860
  }
12867
12861
  };
12868
- var undefinedProcessor = (schema, ctx, json, params) => {
12869
- handleUnrepresentable(schema, ctx, json, params, "Undefined cannot be represented in JSON Schema");
12870
- };
12871
- var voidProcessor = (schema, ctx, json, params) => {
12872
- handleUnrepresentable(schema, ctx, json, params, "Void cannot be represented in JSON Schema");
12873
- };
12874
12862
  var neverProcessor = (_schema, _ctx, json, _params) => {
12875
12863
  json.not = {};
12876
12864
  };
12877
- var anyProcessor = (_schema, _ctx, _json, _params) => {};
12878
12865
  var unknownProcessor = (_schema, _ctx, _json, _params) => {};
12879
- var dateProcessor = (schema, ctx, json, params) => {
12880
- handleUnrepresentable(schema, ctx, json, params, "Date cannot be represented in JSON Schema");
12881
- };
12882
12866
  var enumProcessor = (schema, _ctx, json, _params) => {
12883
12867
  const def = schema._zod.def;
12884
12868
  const values = getEnumValues(def.entries);
@@ -12931,54 +12915,12 @@ var literalProcessor = (schema, ctx, json, params) => {
12931
12915
  json.enum = vals;
12932
12916
  }
12933
12917
  };
12934
- var nanProcessor = (schema, ctx, json, params) => {
12935
- handleUnrepresentable(schema, ctx, json, params, "NaN cannot be represented in JSON Schema");
12936
- };
12937
- var templateLiteralProcessor = (schema, _ctx, json, _params) => {
12938
- const _json = json;
12939
- const pattern = schema._zod.pattern;
12940
- if (!pattern)
12941
- throw new Error("Pattern not found in template literal");
12942
- _json.type = "string";
12943
- _json.pattern = pattern.source;
12944
- };
12945
- var fileProcessor = (schema, _ctx, json, _params) => {
12946
- const _json = json;
12947
- _json.type = "string";
12948
- _json.format = "binary";
12949
- _json.contentEncoding = "binary";
12950
- const { minimum, maximum, mime } = aggregateChecks(schema);
12951
- if (minimum !== undefined)
12952
- _json.minLength = minimum;
12953
- if (maximum !== undefined)
12954
- _json.maxLength = maximum;
12955
- if (!mime)
12956
- return;
12957
- if (mime.length === 0)
12958
- _json.not = {};
12959
- else if (mime.length === 1)
12960
- _json.contentMediaType = mime[0];
12961
- else
12962
- _json.anyOf = mime.map((m) => ({ contentMediaType: m }));
12963
- };
12964
- var successProcessor = (_schema, _ctx, json, _params) => {
12965
- json.type = "boolean";
12966
- };
12967
12918
  var customProcessor = (schema, ctx, json, params) => {
12968
12919
  handleUnrepresentable(schema, ctx, json, params, "Custom types cannot be represented in JSON Schema");
12969
12920
  };
12970
- var functionProcessor = (schema, ctx, json, params) => {
12971
- handleUnrepresentable(schema, ctx, json, params, "Function types cannot be represented in JSON Schema");
12972
- };
12973
12921
  var transformProcessor = (schema, ctx, json, params) => {
12974
12922
  handleUnrepresentable(schema, ctx, json, params, "Transforms cannot be represented in JSON Schema");
12975
12923
  };
12976
- var mapProcessor = (schema, ctx, json, params) => {
12977
- handleUnrepresentable(schema, ctx, json, params, "Map cannot be represented in JSON Schema");
12978
- };
12979
- var setProcessor = (schema, ctx, json, params) => {
12980
- handleUnrepresentable(schema, ctx, json, params, "Set cannot be represented in JSON Schema");
12981
- };
12982
12924
  var arrayProcessor = (schema, ctx, _json, params) => {
12983
12925
  const json = _json;
12984
12926
  const def = schema._zod.def;
@@ -13072,70 +13014,6 @@ var intersectionProcessor = (schema, ctx, json, params) => {
13072
13014
  json.allOf = allOf;
13073
13015
  ctx.intersections.push(allOf);
13074
13016
  };
13075
- var tupleProcessor = (schema, ctx, _json, params) => {
13076
- const json = _json;
13077
- const def = schema._zod.def;
13078
- json.type = "array";
13079
- const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
13080
- const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
13081
- const prefixItems = def.items.map((x, i) => processSchema(x, ctx, {
13082
- ...params,
13083
- path: [...params.path, prefixPath, i]
13084
- }));
13085
- const rest = def.rest ? processSchema(def.rest, ctx, {
13086
- ...params,
13087
- path: [...params.path, restPath, ...ctx.target === "openapi-3.0" ? [def.items.length] : []]
13088
- }) : null;
13089
- let minItems = def.items.length;
13090
- while (minItems > 0) {
13091
- const item = def.items[minItems - 1];
13092
- const optional = ctx.io === "input" ? inputOptin(item) !== undefined : item._zod.optout === "optional";
13093
- if (!optional)
13094
- break;
13095
- minItems--;
13096
- }
13097
- const maxItems = def.items.length;
13098
- const isClosed = !def.rest;
13099
- if (ctx.target === "draft-2020-12") {
13100
- json.prefixItems = prefixItems;
13101
- if (isClosed) {
13102
- json.items = false;
13103
- } else if (rest) {
13104
- json.items = rest;
13105
- }
13106
- if (minItems > 0)
13107
- json.minItems = minItems;
13108
- if (isClosed)
13109
- json.maxItems = maxItems;
13110
- } else if (ctx.target === "openapi-3.0") {
13111
- json.items = {
13112
- anyOf: prefixItems
13113
- };
13114
- if (rest) {
13115
- json.items.anyOf.push(rest);
13116
- }
13117
- if (minItems > 0)
13118
- json.minItems = minItems;
13119
- if (isClosed)
13120
- json.maxItems = maxItems;
13121
- } else {
13122
- json.items = prefixItems;
13123
- if (isClosed) {
13124
- json.additionalItems = false;
13125
- } else if (rest) {
13126
- json.additionalItems = rest;
13127
- }
13128
- if (minItems > 0)
13129
- json.minItems = minItems;
13130
- if (isClosed)
13131
- json.maxItems = maxItems;
13132
- }
13133
- const { minimum, maximum } = aggregateChecks(schema);
13134
- if (typeof minimum === "number")
13135
- json.minItems = minimum;
13136
- if (typeof maximum === "number")
13137
- json.maxItems = maximum;
13138
- };
13139
13017
  function stringifyKeyNames(bySchema, json, visited) {
13140
13018
  if (json.$ref) {
13141
13019
  if (visited.has(json))
@@ -13322,99 +13200,12 @@ var readonlyProcessor = (schema, ctx, json, params) => {
13322
13200
  seen.ref = def.innerType;
13323
13201
  json.readOnly = true;
13324
13202
  };
13325
- var promiseProcessor = (schema, ctx, _json, params) => {
13326
- const def = schema._zod.def;
13327
- processSchema(def.innerType, ctx, params);
13328
- const seen = ctx.seen.get(schema);
13329
- seen.ref = def.innerType;
13330
- };
13331
13203
  var optionalProcessor = (schema, ctx, _json, params) => {
13332
13204
  const def = schema._zod.def;
13333
13205
  processSchema(def.innerType, ctx, params);
13334
13206
  const seen = ctx.seen.get(schema);
13335
13207
  seen.ref = def.innerType;
13336
13208
  };
13337
- var lazyProcessor = (schema, ctx, _json, params) => {
13338
- const innerType = schema._zod.innerType;
13339
- processSchema(innerType, ctx, params);
13340
- const seen = ctx.seen.get(schema);
13341
- seen.ref = innerType;
13342
- };
13343
- var allProcessors = {
13344
- string: stringProcessor,
13345
- number: numberProcessor,
13346
- boolean: booleanProcessor,
13347
- bigint: bigintProcessor,
13348
- symbol: symbolProcessor,
13349
- null: nullProcessor,
13350
- undefined: undefinedProcessor,
13351
- void: voidProcessor,
13352
- never: neverProcessor,
13353
- any: anyProcessor,
13354
- unknown: unknownProcessor,
13355
- date: dateProcessor,
13356
- enum: enumProcessor,
13357
- literal: literalProcessor,
13358
- nan: nanProcessor,
13359
- template_literal: templateLiteralProcessor,
13360
- file: fileProcessor,
13361
- success: successProcessor,
13362
- custom: customProcessor,
13363
- function: functionProcessor,
13364
- transform: transformProcessor,
13365
- map: mapProcessor,
13366
- set: setProcessor,
13367
- array: arrayProcessor,
13368
- object: objectProcessor,
13369
- union: unionProcessor,
13370
- intersection: intersectionProcessor,
13371
- tuple: tupleProcessor,
13372
- record: recordProcessor,
13373
- nullable: nullableProcessor,
13374
- nonoptional: nonoptionalProcessor,
13375
- default: defaultProcessor,
13376
- prefault: prefaultProcessor,
13377
- catch: catchProcessor,
13378
- pipe: pipeProcessor,
13379
- readonly: readonlyProcessor,
13380
- promise: promiseProcessor,
13381
- optional: optionalProcessor,
13382
- lazy: lazyProcessor
13383
- };
13384
- function toJSONSchema(input, params) {
13385
- if ("_idmap" in input) {
13386
- const registry = input;
13387
- const ctx = initializeContext({ ...params, processors: allProcessors });
13388
- const defs = {};
13389
- for (const entry of registry._idmap.entries()) {
13390
- const [_, schema] = entry;
13391
- processSchema(schema, ctx);
13392
- }
13393
- const schemas = {};
13394
- const external = {
13395
- registry,
13396
- uri: params?.uri,
13397
- defs
13398
- };
13399
- ctx.external = external;
13400
- for (const entry of registry._idmap.entries()) {
13401
- const [key, schema] = entry;
13402
- extractDefs(ctx, schema);
13403
- assignProp(schemas, key, finalize(ctx, schema));
13404
- }
13405
- if (Object.keys(defs).length > 0) {
13406
- const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions";
13407
- schemas.__shared = {
13408
- [defsSegment]: defs
13409
- };
13410
- }
13411
- return { schemas };
13412
- }
13413
- const ctx = initializeContext({ ...params, processors: allProcessors });
13414
- processSchema(input, ctx);
13415
- extractDefs(ctx, input);
13416
- return finalize(ctx, input);
13417
- }
13418
13209
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
13419
13210
  function isZ4Schema(s) {
13420
13211
  const schema = s;
@@ -16785,9 +16576,9 @@ class StdioServerTransport {
16785
16576
  import { fileURLToPath as fileURLToPath2 } from "node:url";
16786
16577
 
16787
16578
  // packages/workit-core/src/core.ts
16788
- import { spawnSync as spawnSync10 } from "node:child_process";
16789
- import { existsSync as existsSync6, realpathSync as realpathSync5 } from "node:fs";
16790
- import path16 from "node:path";
16579
+ import { spawnSync as spawnSync11 } from "node:child_process";
16580
+ import { existsSync as existsSync7, realpathSync as realpathSync5 } from "node:fs";
16581
+ import path19 from "node:path";
16791
16582
 
16792
16583
  // packages/workit-core/src/core/task-contract.ts
16793
16584
  import { createHash, randomUUID } from "node:crypto";
@@ -16988,76 +16779,16 @@ var provenanceSchema = object2({
16988
16779
  workerId: nullableId
16989
16780
  }).strict();
16990
16781
  var entrySchema = (data) => object2({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict();
16991
- var factSchema = object2({
16992
- statement: text,
16993
- basis: _enum(["observed", "inferred", "unknown"]),
16994
- refs: array(refSchema)
16995
- }).strict().check((ctx) => {
16996
- if (ctx.value.basis === "observed" && ctx.value.refs.length === 0)
16997
- ctx.issues.push({
16998
- code: "custom",
16999
- input: ctx.value,
17000
- message: "observed facts require supporting references",
17001
- path: ["refs"]
17002
- });
17003
- });
17004
- var signalSchema = object2({
17005
- value: union([boolean2(), literal("unknown")]),
17006
- basis: _enum(["observed", "inferred", "unknown"]),
17007
- reason: text,
16782
+ var RISK_TIERS = ["trivial", "normal", "high"];
16783
+ var judgmentSchema = object2({
16784
+ riskTier: _enum(RISK_TIERS),
16785
+ behaviorChange: boolean2(),
16786
+ productChoiceOpen: boolean2(),
16787
+ needsPlan: boolean2(),
16788
+ note: text.nullable(),
17008
16789
  refs: array(refSchema)
17009
- }).strict().check((ctx) => {
17010
- const signal = ctx.value;
17011
- const valid = signal.value === "unknown" ? signal.basis === "unknown" : signal.basis === "observed" || signal.basis === "inferred";
17012
- if (!valid)
17013
- ctx.issues.push({
17014
- code: "custom",
17015
- input: signal,
17016
- message: "signal value and basis disagree",
17017
- path: []
17018
- });
17019
- if (signal.basis === "observed" && signal.refs.length === 0)
17020
- ctx.issues.push({
17021
- code: "custom",
17022
- input: signal,
17023
- message: "observed signals require supporting references",
17024
- path: ["refs"]
17025
- });
17026
- });
17027
- var signalSet = object2({
17028
- approachUnknown: signalSchema,
17029
- productChoiceOpen: signalSchema,
17030
- behaviorChange: signalSchema,
17031
- mechanicalLowRisk: signalSchema,
17032
- durableAgreementNeeded: signalSchema,
17033
- coordinationPlanNeeded: signalSchema,
17034
- helperUseful: signalSchema,
17035
- testFirstPractical: signalSchema
17036
- }).strict();
17037
- var consequenceSchema = object2({
17038
- area: _enum([
17039
- "behavior",
17040
- "public_contract",
17041
- "data",
17042
- "security",
17043
- "operations",
17044
- "reversibility",
17045
- "consumers"
17046
- ]),
17047
- fact: factSchema
17048
- }).strict();
17049
- var verificationSchema = object2({
17050
- claim: text,
17051
- scope: scopeSchema,
17052
- availableChecks: array(refSchema),
17053
- gaps: array(text)
17054
- }).strict();
17055
- var assessmentSchema = object2({
17056
- facts: array(factSchema),
17057
- signals: signalSet,
17058
- consequences: array(consequenceSchema),
17059
- verification: array(verificationSchema)
17060
16790
  }).strict();
16791
+ var legacyAssessmentSchema = looseObject({});
17061
16792
  var constraintSchema = object2({
17062
16793
  id: nonEmpty,
17063
16794
  kind: _enum(["host", "project", "user"]),
@@ -17294,7 +17025,8 @@ var taskRecordSchema = object2({
17294
17025
  progress: progressSchema,
17295
17026
  pauseReason: text.nullable().optional(),
17296
17027
  runtime: runtimeSchema.optional(),
17297
- assessments: array(entrySchema(assessmentSchema)),
17028
+ assessments: array(entrySchema(legacyAssessmentSchema)),
17029
+ judgments: array(entrySchema(judgmentSchema)).optional(),
17298
17030
  policy: policySchema.nullable(),
17299
17031
  policyChanges: array(policyChangeSchema),
17300
17032
  candidates: array(candidateSchema),
@@ -17470,14 +17202,15 @@ var taskOperations = {
17470
17202
  decisionIds: array(id).optional()
17471
17203
  })
17472
17204
  };
17205
+ var rawJudgment = record(string2(), unknown());
17473
17206
  var policyOperations = {
17474
17207
  assess: operation({
17475
17208
  action: literal("assess"),
17476
17209
  ...taskId,
17477
17210
  expectedRevision: revision.optional(),
17478
- assessment: assessmentSchema
17211
+ judgment: rawJudgment
17479
17212
  }),
17480
- preview: operation({ action: literal("preview"), ...taskId, assessment: assessmentSchema }),
17213
+ preview: operation({ action: literal("preview"), ...taskId, judgment: rawJudgment }),
17481
17214
  explain: operation({ action: literal("explain"), ...taskId })
17482
17215
  };
17483
17216
  var evidenceOperations = {
@@ -17516,7 +17249,10 @@ var decisionOperations = {
17516
17249
  ...taskId,
17517
17250
  expectedRevision: revision.optional(),
17518
17251
  purpose: decisionSchema.shape.purpose,
17519
- binding: decisionSchema.shape.binding,
17252
+ binding: decisionSchema.shape.binding.extend({
17253
+ taskId: id.optional(),
17254
+ workspaceId: id.optional()
17255
+ }),
17520
17256
  response: decisionSchema.shape.response,
17521
17257
  requirementIds: array(digest)
17522
17258
  }),
@@ -17570,6 +17306,15 @@ var operationSchemas = {
17570
17306
  worker: discriminatedUnion("action", Object.values(workerOperations)),
17571
17307
  state: discriminatedUnion("action", Object.values(stateOperations))
17572
17308
  };
17309
+ var OPERATION_ACTIONS = {
17310
+ task: Object.keys(taskOperations),
17311
+ policy: Object.keys(policyOperations),
17312
+ evidence: Object.keys(evidenceOperations),
17313
+ finding: Object.keys(findingOperations),
17314
+ decision: Object.keys(decisionOperations),
17315
+ worker: Object.keys(workerOperations),
17316
+ state: Object.keys(stateOperations)
17317
+ };
17573
17318
  var withImplicitTask = (options) => Object.values(options).map((option) => ("taskId" in option.shape) ? option.extend({ taskId: id.optional() }) : option);
17574
17319
  var advertised = (options) => discriminatedUnion("action", withImplicitTask(options));
17575
17320
  var advertisedOperationSchemas = {
@@ -17603,103 +17348,6 @@ function parseOperation(family, input) {
17603
17348
  }));
17604
17349
  return failure2("invalid_input", "operation input is invalid", { fields });
17605
17350
  }
17606
- function operationJsonSchema(family) {
17607
- return toJSONSchema(advertisedOperationSchemas[family], { target: "draft-2020-12" });
17608
- }
17609
- var OPERATION_SCHEMA_DEPTH = 1;
17610
- var schemaShapeDescription = (value, depth = 0) => {
17611
- if (!value || typeof value !== "object")
17612
- return "value";
17613
- const schema = value;
17614
- if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
17615
- const options = Array.isArray(schema.oneOf) ? schema.oneOf : schema.anyOf;
17616
- return `(${options.map((option) => schemaShapeDescription(option, depth)).join("|")})`;
17617
- }
17618
- if (schema.const !== undefined)
17619
- return `=${JSON.stringify(schema.const)}`;
17620
- if (Array.isArray(schema.enum))
17621
- return `[${schema.enum.map(String).join("|")}]`;
17622
- const type = Array.isArray(schema.type) ? schema.type.join("/") : schema.type;
17623
- if (type === "array")
17624
- return `arr<${schemaShapeDescription(schema.items, depth)}>`;
17625
- if (type === "object" && schema.properties && typeof schema.properties === "object") {
17626
- if (depth >= 2)
17627
- return "obj";
17628
- const required = new Set(Array.isArray(schema.required) ? schema.required : []);
17629
- const properties = schema.properties;
17630
- const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child, depth + 1)}`);
17631
- return `obj{${fields.join(",")}}`;
17632
- }
17633
- if (typeof type !== "string")
17634
- return "?";
17635
- return type.split("/").map((part) => ({ string: "str", boolean: "bool", number: "num", null: "null" })[part] ?? part).join("|");
17636
- };
17637
- var canonicalFieldsDescription = (value) => {
17638
- if (Array.isArray(value))
17639
- return `Canonical object fields: ${value.join(", ")}. Workit validates the complete nested value.`;
17640
- const isZodType = (schema) => ("_zod" in schema) && typeof schema._zod === "object";
17641
- const schema = isZodType(value) ? toJSONSchema(value, { target: "draft-2020-12" }) : value;
17642
- if (!schema.properties || typeof schema.properties !== "object") {
17643
- const variants = Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : null;
17644
- return variants ? `Canonical shapes: ${variants.map((variant) => schemaShapeDescription(variant)).join(" | ")}. Full nested value is validated.` : "Canonical nested value; Workit validates the complete value.";
17645
- }
17646
- const properties = schema.properties;
17647
- const required = new Set(Array.isArray(schema.required) ? schema.required : []);
17648
- const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child)}`);
17649
- return `Fields (! required): ${fields.join(", ")}. Full nested value is validated.`;
17650
- };
17651
- function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, stringifiedObjects = false) {
17652
- const tolerate = (projected) => {
17653
- if (!stringifiedObjects)
17654
- return projected;
17655
- if (projected.properties && typeof projected.properties === "object")
17656
- return { ...projected, type: ["object", "string"] };
17657
- if (projected.items)
17658
- return { ...projected, type: ["array", "string"] };
17659
- if ("const" in projected && (typeof projected.const === "number" || typeof projected.const === "boolean"))
17660
- return { anyOf: [projected, { const: String(projected.const) }] };
17661
- if (projected.type === "object")
17662
- return { ...projected, type: ["object", "string"] };
17663
- if (projected.type === "array")
17664
- return { ...projected, type: ["array", "string"] };
17665
- return projected;
17666
- };
17667
- const collapse = (node, currentDepth) => {
17668
- if (Array.isArray(node))
17669
- return node.map((item) => collapse(item, currentDepth));
17670
- if (typeof node !== "object" || node === null)
17671
- return node;
17672
- const record = node;
17673
- if (record.properties && typeof record.properties === "object") {
17674
- const properties = record.properties;
17675
- const fields = Object.keys(properties);
17676
- if (currentDepth >= maxDepth || fields.length === 0) {
17677
- const description = stringifiedObjects ? `${canonicalFieldsDescription(record)} A JSON-encoded string is also accepted.` : canonicalFieldsDescription(record);
17678
- return tolerate({ type: "object", description });
17679
- }
17680
- return tolerate({
17681
- ...record,
17682
- properties: Object.fromEntries(Object.entries(record.properties).map(([key, child]) => [
17683
- key,
17684
- collapse(child, currentDepth + 1)
17685
- ]))
17686
- });
17687
- }
17688
- if (record.items)
17689
- return tolerate({ ...record, items: collapse(record.items, currentDepth) });
17690
- const composed = ["anyOf", "oneOf", "allOf"].filter((key) => Array.isArray(record[key]));
17691
- if (composed.length > 0) {
17692
- const projected = { ...record };
17693
- for (const key of composed)
17694
- projected[key] = record[key].map((item) => collapse(item, currentDepth));
17695
- return projected;
17696
- }
17697
- if ("const" in record)
17698
- return tolerate({ ...record });
17699
- return node;
17700
- };
17701
- return collapse(operationJsonSchema(family), 0);
17702
- }
17703
17351
  function canonical(value, seen) {
17704
17352
  if (value === null || typeof value === "boolean" || typeof value === "string") {
17705
17353
  if (typeof value === "string" && invalidUnicode(value))
@@ -17788,6 +17436,402 @@ function requirementId(input) {
17788
17436
  }
17789
17437
  });
17790
17438
  }
17439
+ // packages/workit-core/src/core/operation-input.ts
17440
+ var str = (d, values) => ({
17441
+ type: "string",
17442
+ ...values ? { enum: values } : {},
17443
+ ...d ? { d } : {}
17444
+ });
17445
+ var list = (d) => ({ type: "array", ...d ? { d } : {} });
17446
+ var bool = (d) => ({ type: "boolean", ...d ? { d } : {} });
17447
+ var int2 = (d) => ({ type: "integer", ...d ? { d } : {} });
17448
+ var scopeFields = {
17449
+ paths: list("Scope paths (default: whole checkout)."),
17450
+ exclusions: list(),
17451
+ scope: str("Scope description.")
17452
+ };
17453
+ var refs = list("Paths or URLs.");
17454
+ var FLAT = {
17455
+ task: {
17456
+ fields: {
17457
+ objective: str(),
17458
+ ...scopeFields,
17459
+ refs,
17460
+ reason: str(),
17461
+ summary: str(),
17462
+ nextAction: str(),
17463
+ blockers: list(),
17464
+ outcome: str(undefined, ["verified", "accepted_limitations", "stopped"]),
17465
+ decisionIds: list(),
17466
+ status: str(undefined, ["open", "closed", "all"]),
17467
+ query: str(),
17468
+ limit: int2(),
17469
+ view: str(undefined, ["summary", "full"])
17470
+ }
17471
+ },
17472
+ policy: {
17473
+ fields: {
17474
+ riskTier: str("low=trivial, medium=normal.", ["trivial", "normal", "high", "low", "medium"]),
17475
+ behaviorChange: bool("Observable behavior changes."),
17476
+ productChoiceOpen: bool("A product/preference choice is still open."),
17477
+ needsPlan: bool("Needs a written plan or spec first."),
17478
+ note: str(),
17479
+ refs: list("Plan/spec paths or URLs.")
17480
+ }
17481
+ },
17482
+ evidence: {
17483
+ fields: {
17484
+ kind: str(undefined, ["check", "review", "investigation", "artifact"]),
17485
+ claim: str(),
17486
+ result: str(undefined, ["passed", "failed", "missing", "skipped"]),
17487
+ summary: str(),
17488
+ exitCode: int2(),
17489
+ refs,
17490
+ requirementIds: list(),
17491
+ candidateId: str(),
17492
+ beforeCandidateId: str(),
17493
+ reviewSession: str("Reviewer session handle (review evidence).")
17494
+ }
17495
+ },
17496
+ finding: {
17497
+ fields: {
17498
+ claim: str(),
17499
+ consequence: str(),
17500
+ ...scopeFields,
17501
+ candidateId: str(),
17502
+ refs,
17503
+ findingId: str(),
17504
+ disposition: str(undefined, ["open", "fixed", "dismissed", "deferred"]),
17505
+ reason: str(),
17506
+ evidenceIds: list(),
17507
+ decisionIds: list()
17508
+ }
17509
+ },
17510
+ decision: {
17511
+ fields: {
17512
+ purpose: str(undefined, ["design", "action", "limitation", "preference"]),
17513
+ response: str(undefined, ["approved", "rejected", "stated"]),
17514
+ presented: str("What the user was asked."),
17515
+ choice: str("The user's stated choice (response=stated)."),
17516
+ ...scopeFields,
17517
+ refs,
17518
+ requirementIds: list(),
17519
+ decisionId: str(),
17520
+ reason: str()
17521
+ }
17522
+ },
17523
+ worker: {
17524
+ fields: {
17525
+ role: str(undefined, ["investigator", "reviewer", "implementer"]),
17526
+ objective: str(),
17527
+ ...scopeFields,
17528
+ decisionIds: list(),
17529
+ requirementIds: list(),
17530
+ candidateId: str(),
17531
+ stoppingCondition: str(),
17532
+ workerId: str(),
17533
+ outcome: str(undefined, ["completed", "failed", "cancelled"]),
17534
+ summary: str(),
17535
+ evidenceIds: list(),
17536
+ findingIds: list(),
17537
+ reason: str()
17538
+ }
17539
+ },
17540
+ state: {
17541
+ fields: { bundle: { type: "object", d: "An export bundle (object or JSON string)." } }
17542
+ }
17543
+ };
17544
+ var COMMON = {
17545
+ taskId: str("Omit for this branch's task."),
17546
+ expectedRevision: str(),
17547
+ expectedWorkspaceRevision: str()
17548
+ };
17549
+ var DESCRIPTIONS = {
17550
+ task: "Workit task lifecycle for this branch's task (optional continuity).",
17551
+ policy: "Judge the work in four flat calls (riskTier, behaviorChange, productChoiceOpen, needsPlan); Workit derives the checks, verdicts, decisions and plan it needs.",
17552
+ evidence: "Record a note-level evidence entry; only `workit check` runs satisfy check gates.",
17553
+ finding: "Record or resolve a review finding.",
17554
+ decision: "Record or revoke a user decision (never an authorization).",
17555
+ worker: "Assign, report or cancel a bounded helper.",
17556
+ state: "Export or import a task handoff bundle."
17557
+ };
17558
+ var operationDescription = (family) => `${DESCRIPTIONS[family]} Flat fields; omit taskId and revisions.`;
17559
+ var READ_TASK_FIELDS = new Set(["status", "query", "limit", "view"]);
17560
+ var READ_ONLY_ACTIONS = new Set(["list", "inspect", "preview", "explain", "export"]);
17561
+ function flatOperationJsonSchema(family, options = {}) {
17562
+ const spec = FLAT[family];
17563
+ const actions = options.readOnly ? OPERATION_ACTIONS[family].filter((action) => READ_ONLY_ACTIONS.has(action)) : [...OPERATION_ACTIONS[family]];
17564
+ if (!actions.length)
17565
+ return null;
17566
+ const property = (field) => {
17567
+ const base = field.type === "array" ? { type: "array", items: { type: "string" } } : field.type === "object" ? { type: "object" } : { type: field.type };
17568
+ const tolerant = options.stringTolerant && field.type !== "string" ? { type: [field.type, "string"] } : {};
17569
+ return {
17570
+ ...base,
17571
+ ...tolerant,
17572
+ ...field.enum ? { enum: field.enum } : {},
17573
+ ...field.d ? { description: field.d } : {}
17574
+ };
17575
+ };
17576
+ const properties = { action: { type: "string", enum: actions } };
17577
+ const readField = (name) => name === "taskId" || (family === "task" ? READ_TASK_FIELDS.has(name) : family === "policy");
17578
+ for (const [name, field] of Object.entries({ ...COMMON, ...spec.fields }))
17579
+ if (options.readOnly ? readField(name) : !(family === "state" && name === "expectedRevision"))
17580
+ properties[name] = property(field);
17581
+ return {
17582
+ $schema: "https://json-schema.org/draft/2020-12/schema",
17583
+ type: "object",
17584
+ properties,
17585
+ required: ["action"]
17586
+ };
17587
+ }
17588
+ var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
17589
+ var decoded = (value) => {
17590
+ if (typeof value !== "string")
17591
+ return value;
17592
+ const trimmed = value.trim();
17593
+ if (!/^[[{]/.test(trimmed) && !/^(true|false|null|-?\d+)$/.test(trimmed))
17594
+ return value;
17595
+ try {
17596
+ return JSON.parse(trimmed);
17597
+ } catch {
17598
+ return value;
17599
+ }
17600
+ };
17601
+ var strings = (value) => {
17602
+ const plain = decoded(value);
17603
+ if (typeof plain === "string")
17604
+ return plain.trim() ? [plain] : [];
17605
+ return plain;
17606
+ };
17607
+ var refOf = (value) => {
17608
+ if (typeof value !== "string")
17609
+ return value;
17610
+ return /^https?:\/\//i.test(value) ? { kind: "external", url: value } : { kind: "file", path: value.replace(/^\.\//, ""), digest: null };
17611
+ };
17612
+ var refList = (value) => {
17613
+ const items = strings(value);
17614
+ return Array.isArray(items) ? items.map(refOf) : items;
17615
+ };
17616
+ var take = (flat, keys, map = decoded) => {
17617
+ const out = {};
17618
+ for (const key of keys)
17619
+ if (key in flat) {
17620
+ out[key] = map(flat[key]);
17621
+ delete flat[key];
17622
+ }
17623
+ return out;
17624
+ };
17625
+ var nest = (request, key, flat, defaults = {}) => {
17626
+ const nested = isObject2(decoded(request[key])) ? decoded(request[key]) : null;
17627
+ if (!nested && !Object.keys(flat).length)
17628
+ return;
17629
+ request[key] = { ...defaults, ...flat, ...nested };
17630
+ };
17631
+ var scopeOf = (flat, description) => {
17632
+ const scope = decoded(flat.scope);
17633
+ const has = "paths" in flat || "exclusions" in flat || typeof scope === "string";
17634
+ const out = isObject2(scope) ? scope : has ? {
17635
+ description: typeof scope === "string" ? scope : String(description ?? ""),
17636
+ paths: strings(flat.paths ?? ["."]),
17637
+ exclusions: strings(flat.exclusions ?? [])
17638
+ } : null;
17639
+ delete flat.scope;
17640
+ delete flat.paths;
17641
+ delete flat.exclusions;
17642
+ return out;
17643
+ };
17644
+ var defaultScope = (description) => ({
17645
+ description: typeof description === "string" ? description : "",
17646
+ paths: ["."],
17647
+ exclusions: []
17648
+ });
17649
+ var LISTS = [
17650
+ "decisionIds",
17651
+ "evidenceIds",
17652
+ "findingIds",
17653
+ "requirementIds",
17654
+ "refs",
17655
+ "blockers"
17656
+ ];
17657
+ var ENVELOPE = new Set([
17658
+ "schemaVersion",
17659
+ "action",
17660
+ "taskId",
17661
+ "expectedRevision",
17662
+ "expectedWorkspaceRevision",
17663
+ "judgment"
17664
+ ]);
17665
+ function normalizeOperationInput(family, input, host) {
17666
+ if (!isObject2(input))
17667
+ return input;
17668
+ const request = { ...input };
17669
+ if (!("schemaVersion" in request))
17670
+ request.schemaVersion = 1;
17671
+ else
17672
+ request.schemaVersion = decoded(request.schemaVersion);
17673
+ for (const key of ["limit", "exitCode"])
17674
+ if (key in request)
17675
+ request[key] = decoded(request[key]);
17676
+ const action = request.action;
17677
+ const flat = request;
17678
+ switch (family) {
17679
+ case "task": {
17680
+ if (action === "start" || action === "revise") {
17681
+ const intent = isObject2(decoded(request.intent)) ? decoded(request.intent) : {};
17682
+ const objective = flat.objective ?? intent.objective;
17683
+ const scope = scopeOf(flat, objective);
17684
+ const fields = take(flat, ["objective"]);
17685
+ const authorityRefs = "refs" in flat ? refList(flat.refs) : undefined;
17686
+ delete flat.refs;
17687
+ nest(request, "intent", {
17688
+ ...fields,
17689
+ ...scope ? { scope } : {},
17690
+ ...authorityRefs ? { authorityRefs } : {}
17691
+ }, { scope: defaultScope(objective), authorityRefs: [] });
17692
+ } else if (action === "progress") {
17693
+ const fields = take(flat, ["summary", "nextAction"]);
17694
+ const blockers = "blockers" in flat ? strings(flat.blockers) : undefined;
17695
+ delete flat.blockers;
17696
+ nest(request, "progress", {
17697
+ ...fields,
17698
+ ...Array.isArray(blockers) ? {
17699
+ blockers: blockers.map((item) => typeof item === "string" ? { reason: item, dependentAction: "", refs: [] } : item)
17700
+ } : {}
17701
+ }, { nextAction: null, blockers: [] });
17702
+ } else if (action === "close" && "decisionIds" in flat)
17703
+ flat.decisionIds = strings(flat.decisionIds);
17704
+ break;
17705
+ }
17706
+ case "policy": {
17707
+ if (action === "assess" || action === "preview") {
17708
+ const judgment = isObject2(decoded(request.judgment)) ? { ...decoded(request.judgment) } : {};
17709
+ for (const key of Object.keys(flat))
17710
+ if (!ENVELOPE.has(key)) {
17711
+ if (!(key in judgment))
17712
+ judgment[key] = decoded(flat[key]);
17713
+ delete flat[key];
17714
+ }
17715
+ request.judgment = judgment;
17716
+ }
17717
+ break;
17718
+ }
17719
+ case "evidence": {
17720
+ if (action === "record") {
17721
+ const review = "reviewSession" in flat ? decoded(flat.reviewSession) : undefined;
17722
+ delete flat.reviewSession;
17723
+ const fields = take(flat, [
17724
+ "kind",
17725
+ "claim",
17726
+ "result",
17727
+ "summary",
17728
+ "exitCode",
17729
+ "candidateId",
17730
+ "beforeCandidateId",
17731
+ "reviewContext"
17732
+ ]);
17733
+ if ("refs" in flat)
17734
+ fields.refs = refList(flat.refs);
17735
+ if ("requirementIds" in flat)
17736
+ fields.requirementIds = strings(flat.requirementIds);
17737
+ delete flat.refs;
17738
+ delete flat.requirementIds;
17739
+ if (typeof review === "string" && review)
17740
+ fields.reviewContext = { kind: "host", host, handle: review };
17741
+ nest(request, "evidence", fields, {
17742
+ summary: "",
17743
+ refs: [],
17744
+ requirementIds: [],
17745
+ exitCode: null,
17746
+ reviewContext: null
17747
+ });
17748
+ }
17749
+ break;
17750
+ }
17751
+ case "finding": {
17752
+ if (action === "record") {
17753
+ const scope = scopeOf(flat, flat.claim);
17754
+ if (scope)
17755
+ request.scope = scope;
17756
+ else if (!("scope" in request))
17757
+ request.scope = defaultScope(request.claim);
17758
+ request.refs = "refs" in flat ? refList(flat.refs) : [];
17759
+ }
17760
+ break;
17761
+ }
17762
+ case "decision": {
17763
+ if (action === "record") {
17764
+ const binding = isObject2(decoded(request.binding)) ? decoded(request.binding) : {};
17765
+ const presented = flat.presented ?? binding.presented;
17766
+ const scope = scopeOf(flat, presented);
17767
+ const fields = take(flat, ["presented"]);
17768
+ const choice = flat.choice;
17769
+ delete flat.choice;
17770
+ const contentRefs = "refs" in flat ? refList(flat.refs) : undefined;
17771
+ delete flat.refs;
17772
+ nest(request, "binding", {
17773
+ ...fields,
17774
+ ...scope ? { scope } : {},
17775
+ ...contentRefs ? { contentRefs } : {},
17776
+ ...typeof choice === "string" ? { statedChoice: { ref: "choice", text: choice } } : {}
17777
+ }, { scope: defaultScope(presented), contentRefs: [] });
17778
+ }
17779
+ break;
17780
+ }
17781
+ case "worker": {
17782
+ if (action === "assign") {
17783
+ const objective = flat.objective;
17784
+ const scope = scopeOf(flat, objective);
17785
+ const fields = take(flat, ["role", "objective", "candidateId", "stoppingCondition"]);
17786
+ for (const key of ["decisionIds", "requirementIds"])
17787
+ if (key in flat) {
17788
+ fields[key] = strings(flat[key]);
17789
+ delete flat[key];
17790
+ }
17791
+ nest(request, "assignment", { ...fields, ...scope ? { scope } : {} }, { scope: defaultScope(objective), decisionIds: [], requirementIds: [] });
17792
+ } else if (action === "report") {
17793
+ const fields = take(flat, ["outcome", "summary"]);
17794
+ for (const key of ["evidenceIds", "findingIds"])
17795
+ if (key in flat) {
17796
+ fields[key] = strings(flat[key]);
17797
+ delete flat[key];
17798
+ }
17799
+ nest(request, "report", fields, { evidenceIds: [], findingIds: [] });
17800
+ }
17801
+ break;
17802
+ }
17803
+ case "state": {
17804
+ if ("bundle" in request)
17805
+ request.bundle = decoded(request.bundle);
17806
+ break;
17807
+ }
17808
+ }
17809
+ const defaults = {
17810
+ finding: { resolve: ["evidenceIds", "decisionIds"] },
17811
+ decision: { record: ["requirementIds"] }
17812
+ };
17813
+ for (const key of defaults[family]?.[String(action)] ?? [])
17814
+ if (!(key in request))
17815
+ request[key] = [];
17816
+ for (const key of LISTS)
17817
+ if (key in request && typeof request[key] === "string")
17818
+ request[key] = strings(request[key]);
17819
+ return request;
17820
+ }
17821
+ function parseAdvertisedOperation(family, input, host) {
17822
+ const request = normalizeOperationInput(family, input, host);
17823
+ if (isObject2(request) && request.schemaVersion !== SCHEMA_VERSION)
17824
+ return failure2("unsupported_version", "unsupported schema version", { operation: family });
17825
+ const parsed = advertisedOperationSchemas[family].safeParse(request);
17826
+ if (parsed.success)
17827
+ return success(null, null, request);
17828
+ return failure2("invalid_input", "operation input is invalid", {
17829
+ fields: parsed.error.issues.map((issue) => ({
17830
+ path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
17831
+ reason: issue.message
17832
+ }))
17833
+ });
17834
+ }
17791
17835
  // packages/workit-core/src/core/task-store.ts
17792
17836
  import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
17793
17837
  import * as fs7 from "node:fs";
@@ -18842,7 +18886,7 @@ class LogDamage extends Error {
18842
18886
  this.upgrade = upgrade;
18843
18887
  }
18844
18888
  }
18845
- var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
18889
+ var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
18846
18890
  var parseEvent = (line, file, offset) => {
18847
18891
  let value;
18848
18892
  try {
@@ -18850,11 +18894,11 @@ var parseEvent = (line, file, offset) => {
18850
18894
  } catch {
18851
18895
  throw new LogDamage(`${file}: unreadable event at byte ${offset}`);
18852
18896
  }
18853
- if (!isObject2(value) || typeof value.v !== "number")
18897
+ if (!isObject3(value) || typeof value.v !== "number")
18854
18898
  throw new LogDamage(`${file}: malformed event at byte ${offset}`);
18855
18899
  if (value.v > EVENT_VERSION)
18856
18900
  throw new LogDamage(`${file}: event format v${value.v} was written by a newer Workit; upgrade Workit to read this task`, true);
18857
- if (typeof value.seq !== "number" || !Number.isInteger(value.seq) || typeof value.type !== "string" || typeof value.id !== "string" || typeof value.task !== "string" || !isObject2(value.data))
18901
+ if (typeof value.seq !== "number" || !Number.isInteger(value.seq) || typeof value.type !== "string" || typeof value.id !== "string" || typeof value.task !== "string" || !isObject3(value.data))
18858
18902
  throw new LogDamage(`${file}: malformed event at byte ${offset}`);
18859
18903
  return value;
18860
18904
  };
@@ -19125,13 +19169,13 @@ var KNOWN_TYPES = new Set([
19125
19169
  "decision.updated",
19126
19170
  "worker.updated"
19127
19171
  ]);
19128
- var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19129
- 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");
19172
+ var isObject4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19173
+ 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");
19130
19174
  var BLOB_REF = "$blob";
19131
- 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;
19175
+ 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;
19132
19176
  var blobDigest = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
19133
19177
  var toBlob = (value, blobs) => {
19134
- if (!isObject3(value))
19178
+ if (!isObject4(value))
19135
19179
  return value;
19136
19180
  const digest = blobDigest(value);
19137
19181
  blobs.write(digest, value);
@@ -19155,7 +19199,7 @@ var decode3 = (value, blobs) => {
19155
19199
  return blobs.read(digest);
19156
19200
  if (Array.isArray(value))
19157
19201
  return value.map((item) => decode3(item, blobs));
19158
- if (isObject3(value)) {
19202
+ if (isObject4(value)) {
19159
19203
  const out = {};
19160
19204
  for (const [key, item] of Object.entries(value))
19161
19205
  out[key] = decode3(item, blobs);
@@ -19174,7 +19218,7 @@ function reduce(state, event, blobs) {
19174
19218
  }
19175
19219
  let next = state;
19176
19220
  if ("record" in data) {
19177
- if (!isObject3(data.record))
19221
+ if (!isObject4(data.record))
19178
19222
  throw new LogDamage(`event ${event.seq} carries a malformed record`);
19179
19223
  next = {
19180
19224
  record: decode3(data.record, blobs),
@@ -19196,7 +19240,7 @@ function reduce(state, event, blobs) {
19196
19240
  }
19197
19241
  if ("key" in data)
19198
19242
  next.key = isKey(data.key) ? data.key : null;
19199
- if ("legacy" in data && isObject3(data.legacy))
19243
+ if ("legacy" in data && isObject4(data.legacy))
19200
19244
  next.legacy = {
19201
19245
  path: String(data.legacy.path ?? ""),
19202
19246
  digest: String(data.legacy.digest ?? "")
@@ -19252,8 +19296,8 @@ var AUTO_COMPACT_BYTES = 2 * 1024 * 1024;
19252
19296
  var TOMBSTONE = Symbol("tombstone");
19253
19297
  var STALE_TEMPORARY_MS = 60 * 60000;
19254
19298
  var INDEX_VERSION = 3;
19255
- var isObject4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19256
- var hostSession = (value) => isObject4(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
19299
+ var isObject5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19300
+ var hostSession = (value) => isObject5(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
19257
19301
  var indexEntry = (task, key, legacy, file) => {
19258
19302
  const sessions = [];
19259
19303
  const intent = hostSession(task.intent.provenance.session);
@@ -20066,7 +20110,7 @@ class TaskStore {
20066
20110
  readSnapshot(taskId) {
20067
20111
  try {
20068
20112
  const value = JSON.parse(fs7.readFileSync(this.snapshotPath(taskId), "utf8"));
20069
- 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))
20113
+ 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))
20070
20114
  return null;
20071
20115
  return value;
20072
20116
  } catch {
@@ -20412,11 +20456,11 @@ class TaskStore {
20412
20456
  readIndex() {
20413
20457
  try {
20414
20458
  const value = JSON.parse(fs7.readFileSync(this.paths().index, "utf8"));
20415
- if (!isObject4(value) || value.version !== INDEX_VERSION || !isObject4(value.tasks))
20459
+ if (!isObject5(value) || value.version !== INDEX_VERSION || !isObject5(value.tasks))
20416
20460
  return {};
20417
20461
  const tasks = {};
20418
20462
  for (const [id, entry] of Object.entries(value.tasks))
20419
- 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))
20463
+ 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))
20420
20464
  tasks[id] = entry;
20421
20465
  return tasks;
20422
20466
  } catch {
@@ -20460,12 +20504,12 @@ class TaskStore {
20460
20504
  return this.parseValue(value, schema, label);
20461
20505
  }
20462
20506
  parseValue(value, schema, label) {
20463
- if (isObject4(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
20507
+ if (isObject5(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
20464
20508
  return failure2("unsupported_version", `unsupported ${label} schema version`);
20465
20509
  const parsed = parseStoredRecord(schema, value);
20466
20510
  if (parsed.success)
20467
20511
  return success(null, null, parsed.data);
20468
- const writerVersion = isObject4(value) && isObject4(value.runtime) ? value.runtime.updatedWith : null;
20512
+ const writerVersion = isObject5(value) && isObject5(value.runtime) ? value.runtime.updatedWith : null;
20469
20513
  if (parsed.critical.length > 0)
20470
20514
  return failure2("recovery_required", `${label} record requires fields this Workit cannot read (${parsed.critical.join(", ")}); upgrade Workit before mutating this checkout`);
20471
20515
  if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
@@ -20667,7 +20711,7 @@ class TaskStore {
20667
20711
  }
20668
20712
  try {
20669
20713
  const value = JSON.parse(bytes);
20670
- if (isObject4(value) && isObject4(value.store) && value.store.format === "workit-store")
20714
+ if (isObject5(value) && isObject5(value.store) && value.store.format === "workit-store")
20671
20715
  return TOMBSTONE;
20672
20716
  } catch {}
20673
20717
  return { bytes };
@@ -20712,7 +20756,7 @@ class TaskStore {
20712
20756
  isStub(file) {
20713
20757
  try {
20714
20758
  const value = JSON.parse(fs7.readFileSync(file, "utf8"));
20715
- return isObject4(value) && isObject4(value.store) && value.store.format === "workit-store";
20759
+ return isObject5(value) && isObject5(value.store) && value.store.format === "workit-store";
20716
20760
  } catch {
20717
20761
  return false;
20718
20762
  }
@@ -20883,10 +20927,10 @@ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
20883
20927
  var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
20884
20928
 
20885
20929
  // packages/workit-core/src/core/task-evaluation.ts
20886
- import { createHash as createHash6 } from "node:crypto";
20887
- import * as fs10 from "node:fs";
20888
- import { spawnSync as spawnSync5 } from "node:child_process";
20889
- import path9 from "node:path";
20930
+ import { createHash as createHash7 } from "node:crypto";
20931
+ import * as fs11 from "node:fs";
20932
+ import { spawnSync as spawnSync6 } from "node:child_process";
20933
+ import path11 from "node:path";
20890
20934
 
20891
20935
  // packages/workit-core/src/check-config.ts
20892
20936
  import { spawnSync as spawnSync3 } from "node:child_process";
@@ -21126,6 +21170,7 @@ var line = (run) => {
21126
21170
  const value = run.stdout.trim();
21127
21171
  return value ? value : null;
21128
21172
  };
21173
+ var safeArg = (value) => value.length > 0 && !value.startsWith("-");
21129
21174
  function headSha(cwd) {
21130
21175
  return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
21131
21176
  }
@@ -21217,79 +21262,648 @@ function worktreeSignal(cwd, options = {}) {
21217
21262
  }
21218
21263
  return `sig:${hash.digest("hex")}`;
21219
21264
  }
21220
-
21221
- // packages/workit-core/src/core/task-evaluation.ts
21222
- var digestBytes = (value) => createHash6("sha256").update(value).digest("hex");
21223
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path9.sep}`);
21224
- var relative = (root, candidate) => path9.relative(root, candidate).split(path9.sep).join("/") || ".";
21225
- var canonicalPath = (value) => {
21226
- if (typeof value !== "string" || !value)
21227
- return null;
21228
- if (value.includes("\\") || value.split("/").includes(".."))
21265
+ function patchId(cwd, base, head) {
21266
+ if (!safeArg(base) || !safeArg(head))
21229
21267
  return null;
21230
- const normalized = path9.posix.normalize(value);
21231
- if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
21232
- return null;
21233
- return normalized === "." ? "." : normalized.replace(/\/$/, "");
21234
- };
21235
- var canonicalScope = (scope) => {
21236
- const paths = scope.paths.length ? scope.paths : ["."];
21237
- const normalizedPaths = paths.map(canonicalPath);
21238
- const normalizedExclusions = scope.exclusions.map(canonicalPath);
21239
- if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
21268
+ const diff = git2(cwd, [
21269
+ "diff",
21270
+ "-U3",
21271
+ "--diff-algorithm=myers",
21272
+ "--indent-heuristic",
21273
+ "--no-color",
21274
+ "--no-ext-diff",
21275
+ "--no-textconv",
21276
+ "--no-renames",
21277
+ "--full-index",
21278
+ "--src-prefix=a/",
21279
+ "--dst-prefix=b/",
21280
+ `${base}...${head}`,
21281
+ "--"
21282
+ ]);
21283
+ if (!diff.ok || !diff.stdout.trim())
21240
21284
  return null;
21285
+ const id = git2(cwd, ["patch-id", "--stable"], { input: diff.stdout });
21286
+ return line(id)?.split(/\s+/u)[0] ?? null;
21287
+ }
21288
+ function currentBranch(cwd) {
21289
+ return line(git2(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
21290
+ }
21291
+
21292
+ // packages/workit-core/src/core/policy/derive.ts
21293
+ import { existsSync as existsSync3 } from "node:fs";
21294
+ import path10 from "node:path";
21295
+
21296
+ // packages/workit-core/src/ledger.ts
21297
+ import { spawnSync as spawnSync5 } from "node:child_process";
21298
+ import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
21299
+ import fs10 from "node:fs";
21300
+ import path9 from "node:path";
21301
+ var LEDGER_VERSION = 1;
21302
+ var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
21303
+ var FAILING = new Set(["failed", "blocked"]);
21304
+ var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
21305
+ var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
21306
+ var err = (code, error, unblock) => ({
21307
+ ok: false,
21308
+ code,
21309
+ error,
21310
+ ...unblock ? { unblock } : {}
21311
+ });
21312
+ var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
21313
+ const run = spawnSync5("git", args, {
21314
+ cwd,
21315
+ encoding: "utf8",
21316
+ timeout: timeoutMs,
21317
+ killSignal: "SIGKILL",
21318
+ windowsHide: true,
21319
+ maxBuffer: 256 * 1024 * 1024,
21320
+ env: {
21321
+ ...process.env,
21322
+ GIT_TERMINAL_PROMPT: "0",
21323
+ GIT_OPTIONAL_LOCKS: "0",
21324
+ LC_ALL: "C",
21325
+ LANGUAGE: "C"
21326
+ }
21327
+ });
21241
21328
  return {
21242
- description: scope.description,
21243
- paths: [...new Set(normalizedPaths)],
21244
- exclusions: [...new Set(normalizedExclusions)]
21329
+ ok: run.status === 0,
21330
+ stdout: run.stdout ?? "",
21331
+ stderr: run.error ? String(run.error.message) : run.stderr ?? ""
21245
21332
  };
21246
21333
  };
21247
- var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
21248
- var scopeMatches = (value, scope) => {
21249
- const normalized = canonicalScope(scope);
21250
- if (!normalized)
21251
- return false;
21252
- const paths = normalized.paths;
21253
- return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
21334
+ var git3 = (cwd, args) => {
21335
+ const run = gitRun2(cwd, args);
21336
+ const value = run.ok ? run.stdout.trim() : "";
21337
+ return value ? value : null;
21254
21338
  };
21255
- var pathRelevant = (value, scope) => scopeMatches(value, scope);
21256
- var compareCodeUnits2 = (left, right) => {
21257
- const length = Math.min(left.length, right.length);
21258
- for (let index = 0;index < length; index += 1) {
21259
- const difference = left.charCodeAt(index) - right.charCodeAt(index);
21260
- if (difference !== 0)
21261
- return difference;
21339
+ var safeRef = (value) => value.length > 0 && !value.startsWith("-");
21340
+ function resolveCommit(cwd, ref) {
21341
+ if (!safeRef(ref))
21342
+ return null;
21343
+ return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
21344
+ }
21345
+ function branchHead(cwd, branch) {
21346
+ if (!safeRef(branch))
21347
+ return null;
21348
+ return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
21349
+ }
21350
+ function defaultBase(cwd) {
21351
+ const originHead = git3(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
21352
+ if (originHead && resolveCommit(cwd, originHead))
21353
+ return originHead;
21354
+ for (const candidate of ["origin/main", "origin/master", "main", "master"])
21355
+ if (resolveCommit(cwd, candidate))
21356
+ return candidate;
21357
+ return null;
21358
+ }
21359
+ function diffHash(cwd, base, head) {
21360
+ if (!safeRef(base) || !safeRef(head))
21361
+ return null;
21362
+ const diff = gitRun2(cwd, [
21363
+ "diff",
21364
+ "-U3",
21365
+ "--diff-algorithm=myers",
21366
+ "--indent-heuristic",
21367
+ "--no-color",
21368
+ "--no-ext-diff",
21369
+ "--no-textconv",
21370
+ "--no-renames",
21371
+ "--full-index",
21372
+ "--binary",
21373
+ "--src-prefix=a/",
21374
+ "--dst-prefix=b/",
21375
+ `${base}...${head}`,
21376
+ "--"
21377
+ ], GIT_TIMEOUTS.worktree);
21378
+ if (!diff.ok || !diff.stdout)
21379
+ return null;
21380
+ const hash = createHash6("sha256");
21381
+ for (const line of diff.stdout.split(`
21382
+ `)) {
21383
+ if (line.startsWith("index "))
21384
+ continue;
21385
+ hash.update(line.startsWith("@@ ") ? "@@" : line);
21386
+ hash.update(`
21387
+ `);
21262
21388
  }
21263
- return left.length - right.length;
21389
+ return `sha256:${hash.digest("hex")}`;
21390
+ }
21391
+ function storeRoot(cwd) {
21392
+ const location = resolveStore(path9.resolve(cwd));
21393
+ if (location instanceof Error)
21394
+ 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");
21395
+ return { ok: true, value: { root: location.dir, shared: location.shared } };
21396
+ }
21397
+ function ledgerPath(cwd) {
21398
+ const root = storeRoot(cwd);
21399
+ return root.ok ? { ok: true, value: path9.join(root.value.root, "ledger", "ledger.jsonl") } : root;
21400
+ }
21401
+ var NETWORK_FS_TYPES = new Set([
21402
+ 26985,
21403
+ 20859,
21404
+ 4266872130,
21405
+ 4283649346,
21406
+ 1702057286,
21407
+ 1397113167,
21408
+ 12805120,
21409
+ 16914839,
21410
+ 1196443219,
21411
+ 198183888
21412
+ ]);
21413
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21414
+ var str2 = (value) => typeof value === "string" ? value : null;
21415
+ var normalizeActor = (value) => {
21416
+ const actor = isRecord2(value) ? value : {};
21417
+ return {
21418
+ host: str2(actor.host) ?? "unknown",
21419
+ session: str2(actor.session),
21420
+ agentId: str2(actor.agentId)
21421
+ };
21264
21422
  };
21265
- var gitPaths = (root) => {
21266
- const result = spawnSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
21267
- cwd: root,
21268
- encoding: "buffer"
21269
- });
21270
- if (result.status !== 0) {
21271
- const stderr = result.stderr?.toString("utf8") ?? "";
21272
- return {
21273
- paths: [],
21423
+ function supersedeAllowed(by, target) {
21424
+ if (by.type !== target.type)
21425
+ return `a ${by.type} row cannot supersede a ${target.type} row`;
21426
+ if (!by.actor.session || by.actor.session !== target.actor.session)
21427
+ return "only the session that wrote a row can supersede it";
21428
+ if (by.type === "verdict" && by.self === true && target.self !== true)
21429
+ return "a self verdict cannot replace an independent verdict";
21430
+ return null;
21431
+ }
21432
+ function readLedger(cwd) {
21433
+ const file = ledgerPath(cwd);
21434
+ if (!file.ok)
21435
+ return file;
21436
+ let text = "";
21437
+ try {
21438
+ text = fs10.readFileSync(file.value, "utf8");
21439
+ } catch (error) {
21440
+ if (error.code === "ENOENT")
21441
+ return { ok: true, value: { path: file.value, rows: [], skipped: 0 } };
21442
+ return err("unavailable", `cannot read ${file.value}: ${error.message}`);
21443
+ }
21444
+ const rows = [];
21445
+ let skipped = 0;
21446
+ for (const raw of text.split(`
21447
+ `)) {
21448
+ const trimmed = raw.trim();
21449
+ if (!trimmed)
21450
+ continue;
21451
+ let parsed;
21452
+ try {
21453
+ parsed = JSON.parse(trimmed);
21454
+ } catch {
21455
+ skipped += 1;
21456
+ continue;
21457
+ }
21458
+ if (!isRecord2(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
21459
+ skipped += 1;
21460
+ continue;
21461
+ }
21462
+ const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
21463
+ rows.push({
21464
+ ...parsed,
21465
+ v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
21466
+ id: parsed.id,
21467
+ at: parsed.at,
21468
+ type: parsed.type,
21469
+ actor: normalizeActor(parsed.actor),
21470
+ branch: str2(parsed.branch),
21471
+ head: str2(parsed.head),
21472
+ base: str2(parsed.base),
21473
+ baseSha: str2(parsed.baseSha),
21474
+ patchId: str2(parsed.patchId),
21475
+ diffHash: str2(parsed.diffHash),
21476
+ tree: str2(parsed.tree),
21477
+ dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
21478
+ ...pr === undefined ? {} : { pr },
21479
+ ...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
21480
+ seq: rows.length + 1,
21481
+ superseded: false,
21482
+ supersedeIgnored: false
21483
+ });
21484
+ }
21485
+ const byId = new Map;
21486
+ for (const row of rows) {
21487
+ const target = row.supersedes ? byId.get(row.supersedes) : undefined;
21488
+ if (target && supersedeAllowed(row, target) === null)
21489
+ target.superseded = true;
21490
+ else if (row.supersedes)
21491
+ row.supersedeIgnored = true;
21492
+ if (!byId.has(row.id))
21493
+ byId.set(row.id, row);
21494
+ }
21495
+ return { ok: true, value: { path: file.value, rows, skipped } };
21496
+ }
21497
+ function verdictBasis(verdict, current) {
21498
+ if (verdict.head && verdict.head === current.head)
21499
+ return "fresh";
21500
+ if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
21501
+ return "carried";
21502
+ return "stale";
21503
+ }
21504
+ var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded;
21505
+ function trailerSessions(cwd, base, head) {
21506
+ if (!base || !head || !safeRef(base) || !safeRef(head))
21507
+ return [];
21508
+ const out = git3(cwd, [
21509
+ "log",
21510
+ "--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
21511
+ `${base}..${head}`
21512
+ ]);
21513
+ return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
21514
+ }
21515
+ function authorSessions(cwd, rows, branch, range = { base: null, head: null }) {
21516
+ const sessions = new Set;
21517
+ for (const row of rows) {
21518
+ if (row.branch !== branch || row.type !== "commit.recorded" && row.type !== "task.opened")
21519
+ continue;
21520
+ const session = str2(row.session) ?? row.actor.session;
21521
+ if (session)
21522
+ sessions.add(session);
21523
+ }
21524
+ for (const session of trailerSessions(cwd, range.base, range.head))
21525
+ sessions.add(session);
21526
+ return sessions;
21527
+ }
21528
+ var independenceReasons = (row, authors) => {
21529
+ const reasons = [];
21530
+ if (!row.actor.session)
21531
+ reasons.push("no_session");
21532
+ else if (authors.has(row.actor.session))
21533
+ reasons.push("author_session");
21534
+ if (row.self === true && !reasons.includes("no_session"))
21535
+ reasons.push("self");
21536
+ return reasons;
21537
+ };
21538
+ function effectiveOf(rows, authors) {
21539
+ let effective = null;
21540
+ for (const row of rows) {
21541
+ if (!effective) {
21542
+ effective = row;
21543
+ continue;
21544
+ }
21545
+ const rowIndependent = independenceReasons(row, authors).length === 0;
21546
+ const effIndependent = independenceReasons(effective, authors).length === 0;
21547
+ if (effIndependent && !rowIndependent)
21548
+ continue;
21549
+ if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session)
21550
+ continue;
21551
+ effective = row;
21552
+ }
21553
+ return effective;
21554
+ }
21555
+ function restackCarries(rows, branch, from, to) {
21556
+ if (!from || !to)
21557
+ return false;
21558
+ const edges = new Map;
21559
+ for (const row of rows)
21560
+ if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
21561
+ edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
21562
+ const seen = new Set([from]);
21563
+ const queue = [from];
21564
+ while (queue.length) {
21565
+ const next = queue.shift();
21566
+ if (next === to)
21567
+ return true;
21568
+ for (const target of edges.get(next) ?? [])
21569
+ if (!seen.has(target)) {
21570
+ seen.add(target);
21571
+ queue.push(target);
21572
+ }
21573
+ }
21574
+ return false;
21575
+ }
21576
+ function checkVerdicts(cwd, branch, rows) {
21577
+ const head = branchHead(cwd, branch);
21578
+ const fallbackBase = defaultBase(cwd);
21579
+ const keyCache = new Map;
21580
+ const keyFor = (base) => {
21581
+ if (!base || !head)
21582
+ return { patchId: null, diffHash: null };
21583
+ let key = keyCache.get(base);
21584
+ if (!key) {
21585
+ key = { patchId: patchId(cwd, base, head), diffHash: diffHash(cwd, base, head) };
21586
+ keyCache.set(base, key);
21587
+ }
21588
+ return key;
21589
+ };
21590
+ const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head });
21591
+ const byKind = new Map;
21592
+ for (const row of rows)
21593
+ if (row.branch === branch && isLiveVerdict(row)) {
21594
+ const kind = typeof row.kind === "string" ? row.kind : "review";
21595
+ byKind.set(kind, [...byKind.get(kind) ?? [], row]);
21596
+ }
21597
+ const verdicts = [];
21598
+ for (const [kind, list] of byKind) {
21599
+ const row = effectiveOf(list, authors);
21600
+ if (!row)
21601
+ continue;
21602
+ let basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
21603
+ if (basis === "stale" && restackCarries(rows, branch, row.head, head))
21604
+ basis = "carried";
21605
+ const current = basis !== "stale";
21606
+ const independence = independenceReasons(row, authors);
21607
+ const reasons = [
21608
+ ...current ? [] : ["stale"],
21609
+ ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
21610
+ ...independence
21611
+ ];
21612
+ verdicts.push({
21613
+ kind,
21614
+ basis,
21615
+ current,
21616
+ independent: independence.length === 0,
21617
+ accepted: reasons.length === 0,
21618
+ reasons,
21619
+ verdict: row
21620
+ });
21621
+ }
21622
+ verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
21623
+ const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
21624
+ const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
21625
+ const newestAccepted = verdicts.findLast((entry) => entry.accepted) ?? null;
21626
+ const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
21627
+ accepted: false,
21628
+ verdict: failing?.verdict ?? null,
21629
+ reasons: failing ? ["failing_verdict"] : newestCurrent?.reasons ?? ["no_verdict"]
21630
+ };
21631
+ const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
21632
+ return {
21633
+ branch,
21634
+ head,
21635
+ review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
21636
+ selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
21637
+ base: newestCurrent?.verdict.base ?? fallbackBase,
21638
+ patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
21639
+ current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
21640
+ accepted,
21641
+ verdicts,
21642
+ authors: [...authors].toSorted()
21643
+ };
21644
+ }
21645
+ var OBSERVED_ONLY = new Set(["check", ...PR_ROW_TYPES]);
21646
+
21647
+ // packages/workit-core/src/core/policy/derive.ts
21648
+ var RULES = {
21649
+ test: "check:test",
21650
+ self: "verdict:self",
21651
+ nonAuthor: "verdict:non-author",
21652
+ verified: "verdict:verified",
21653
+ product: "decision:product",
21654
+ plan: "plan"
21655
+ };
21656
+ var derived2 = (spec) => ({
21657
+ ...spec,
21658
+ id: requirementId({
21659
+ ruleId: spec.ruleId,
21660
+ scope: spec.scope,
21661
+ satisfaction: spec.satisfaction,
21662
+ before: spec.before,
21663
+ dependentAction: spec.dependentAction
21664
+ })
21665
+ });
21666
+ var sorted = (values) => [...new Set(values)].toSorted();
21667
+ var normalizedScope = (scope) => ({
21668
+ description: scope.description,
21669
+ paths: sorted(scope.paths),
21670
+ exclusions: sorted(scope.exclusions)
21671
+ });
21672
+ function deriveRequirements(judgment, scope, constraints, verification = "self") {
21673
+ const out = [];
21674
+ const base = { scope, dependentAction: null };
21675
+ if (judgment.behaviorChange)
21676
+ out.push(derived2({
21677
+ ...base,
21678
+ ruleId: RULES.test,
21679
+ dimension: "testing",
21680
+ reason: "Behavior changes: the configured test check must pass on the final tree.",
21681
+ satisfaction: "Run `workit check test` on the final tree.",
21682
+ before: "close",
21683
+ acceptanceAllowed: false
21684
+ }));
21685
+ if (judgment.behaviorChange && judgment.riskTier === "normal")
21686
+ out.push(verification === "independent" ? derived2({
21687
+ ...base,
21688
+ ruleId: RULES.nonAuthor,
21689
+ dimension: "review",
21690
+ reason: "This workspace requires independent verification: a session that did not author the change records the verdict.",
21691
+ satisfaction: "An independent session records a passing verdict: `workit ledger verdict verified --how <what it exercised>`.",
21692
+ before: "close",
21693
+ acceptanceAllowed: false
21694
+ }) : derived2({
21695
+ ...base,
21696
+ ruleId: RULES.self,
21697
+ dimension: "review",
21698
+ reason: "A behavior change needs a recorded verdict; the author's own is allowed and is shown as self-reviewed, never verified.",
21699
+ 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).",
21700
+ before: "close",
21701
+ acceptanceAllowed: false
21702
+ }));
21703
+ if (judgment.riskTier === "high")
21704
+ out.push(derived2({
21705
+ ...base,
21706
+ ruleId: RULES.verified,
21707
+ dimension: "review",
21708
+ reason: "High risk: an independent verifier must exercise the live surface.",
21709
+ satisfaction: "An independent verifier runs the real surface and records `workit ledger verdict verified --kind live --how <what it exercised>`.",
21710
+ before: "close",
21711
+ acceptanceAllowed: false
21712
+ }));
21713
+ if (judgment.productChoiceOpen)
21714
+ out.push(derived2({
21715
+ ...base,
21716
+ ruleId: RULES.product,
21717
+ dimension: "decisions",
21718
+ reason: "A product or preference choice is open; the user decides it before code is written.",
21719
+ satisfaction: 'Ask the user (with your recommendation), then record the answer: `workit ledger decision "<choice>" --why "<reason>"`.',
21720
+ before: "write",
21721
+ acceptanceAllowed: false
21722
+ }));
21723
+ if (judgment.needsPlan || judgment.riskTier === "high")
21724
+ out.push(derived2({
21725
+ ...base,
21726
+ ruleId: RULES.plan,
21727
+ dimension: "artifacts",
21728
+ reason: judgment.needsPlan ? "The work needs a written plan or spec before implementation." : "High-risk work needs a written plan before implementation.",
21729
+ 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>`.",
21730
+ before: "write",
21731
+ acceptanceAllowed: true
21732
+ }));
21733
+ for (const constraint of constraints)
21734
+ for (const obligation of constraint.requires)
21735
+ out.push(derived2({
21736
+ ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
21737
+ kind: obligation.kind,
21738
+ scope: normalizedScope(obligation.scope),
21739
+ refs: obligation.refs.map((ref) => JSON.stringify(ref)).toSorted(),
21740
+ method: obligation.method,
21741
+ before: obligation.before,
21742
+ dependentAction: obligation.dependentAction
21743
+ })}`,
21744
+ dimension: obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge"),
21745
+ scope: obligation.scope,
21746
+ reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
21747
+ satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
21748
+ before: obligation.before,
21749
+ dependentAction: obligation.dependentAction,
21750
+ acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
21751
+ }));
21752
+ const seen = new Set;
21753
+ return out.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true));
21754
+ }
21755
+ var latestJudgment = (task) => task.judgments?.at(-1)?.data ?? null;
21756
+ var ledgerView = (root) => {
21757
+ let branch;
21758
+ let rows;
21759
+ return {
21760
+ branch: () => branch === undefined ? branch = currentBranch(root) : branch,
21761
+ rows: () => {
21762
+ if (rows === undefined) {
21763
+ const read = readLedger(root);
21764
+ rows = read.ok ? read.value.rows : [];
21765
+ }
21766
+ return rows;
21767
+ }
21768
+ };
21769
+ };
21770
+ var productDecision = (task, ledger) => {
21771
+ const decisions = task.decisions.filter((entry) => entry.data.revoked === null && (entry.data.response === "approved" || entry.data.response === "stated") && entry.data.purpose !== "limitation");
21772
+ if (decisions.length)
21773
+ return {
21774
+ met: true,
21775
+ reason: "the user's decision is recorded on the task",
21776
+ decisionIds: decisions.map((entry) => entry.id)
21777
+ };
21778
+ const since = Date.parse(task.createdAt);
21779
+ const branch = ledger.branch();
21780
+ const row = ledger.rows().findLast((item) => item.type === "decision" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
21781
+ return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
21782
+ met: false,
21783
+ reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or re-judge productChoiceOpen=false if it is not open)',
21784
+ decisionIds: []
21785
+ };
21786
+ };
21787
+ var planRecorded = (task, root) => {
21788
+ const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync3(path10.resolve(root, ref.path)));
21789
+ if (cited)
21790
+ return {
21791
+ met: true,
21792
+ reason: `plan recorded: ${cited.kind === "file" ? cited.path : cited.kind === "external" ? cited.url : ""}`,
21793
+ decisionIds: []
21794
+ };
21795
+ const artifact = task.evidence.some((entry) => entry.data.kind === "artifact" && entry.data.result === "passed");
21796
+ return artifact ? { met: true, reason: "a passing artifact records the plan", decisionIds: [] } : {
21797
+ met: false,
21798
+ reason: "no plan recorded: write it (docs/ and Markdown writes stay allowed), then `workit policy assess --ref <path>`",
21799
+ decisionIds: []
21800
+ };
21801
+ };
21802
+ var verdict = (requirement, root, ledger) => {
21803
+ const branch = ledger.branch();
21804
+ if (!branch)
21805
+ return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
21806
+ const check = checkVerdicts(root, branch, ledger.rows());
21807
+ const failing = check.accepted.reasons.includes("failing_verdict");
21808
+ const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
21809
+ 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)));
21810
+ if (met)
21811
+ return {
21812
+ met: true,
21813
+ reason: `${met.accepted ? "accepted independent" : "self-reviewed (author's own)"} ${String(met.verdict.result)} verdict ${met.verdict.id}`,
21814
+ decisionIds: []
21815
+ };
21816
+ 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, " ");
21817
+ 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>\``;
21818
+ return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
21819
+ };
21820
+ function derivedRuleStatus(task, requirement, root, ledger) {
21821
+ switch (requirement.ruleId) {
21822
+ case RULES.product:
21823
+ return productDecision(task, ledger);
21824
+ case RULES.plan:
21825
+ return planRecorded(task, root);
21826
+ case RULES.self:
21827
+ case RULES.nonAuthor:
21828
+ case RULES.verified:
21829
+ return verdict(requirement, root, ledger);
21830
+ default:
21831
+ return null;
21832
+ }
21833
+ }
21834
+
21835
+ // packages/workit-core/src/core/task-evaluation.ts
21836
+ var digestBytes = (value) => createHash7("sha256").update(value).digest("hex");
21837
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path11.sep}`);
21838
+ var relative = (root, candidate) => path11.relative(root, candidate).split(path11.sep).join("/") || ".";
21839
+ var canonicalPath = (value) => {
21840
+ if (typeof value !== "string" || !value)
21841
+ return null;
21842
+ if (value.includes("\\") || value.split("/").includes(".."))
21843
+ return null;
21844
+ const normalized = path11.posix.normalize(value);
21845
+ if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
21846
+ return null;
21847
+ return normalized === "." ? "." : normalized.replace(/\/$/, "");
21848
+ };
21849
+ var canonicalScope = (scope) => {
21850
+ const paths = scope.paths.length ? scope.paths : ["."];
21851
+ const normalizedPaths = paths.map(canonicalPath);
21852
+ const normalizedExclusions = scope.exclusions.map(canonicalPath);
21853
+ if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
21854
+ return null;
21855
+ return {
21856
+ description: scope.description,
21857
+ paths: [...new Set(normalizedPaths)],
21858
+ exclusions: [...new Set(normalizedExclusions)]
21859
+ };
21860
+ };
21861
+ var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
21862
+ var scopeMatches = (value, scope) => {
21863
+ const normalized = canonicalScope(scope);
21864
+ if (!normalized)
21865
+ return false;
21866
+ const paths = normalized.paths;
21867
+ return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
21868
+ };
21869
+ var pathRelevant = (value, scope) => scopeMatches(value, scope);
21870
+ var compareCodeUnits2 = (left, right) => {
21871
+ const length = Math.min(left.length, right.length);
21872
+ for (let index = 0;index < length; index += 1) {
21873
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
21874
+ if (difference !== 0)
21875
+ return difference;
21876
+ }
21877
+ return left.length - right.length;
21878
+ };
21879
+ var gitPaths = (root) => {
21880
+ const result = spawnSync6("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
21881
+ cwd: root,
21882
+ encoding: "buffer"
21883
+ });
21884
+ if (result.status !== 0) {
21885
+ const stderr = result.stderr?.toString("utf8") ?? "";
21886
+ return {
21887
+ paths: [],
21274
21888
  uncertain: !/not a git repository/i.test(stderr),
21275
21889
  git: !/not a git repository/i.test(stderr)
21276
21890
  };
21277
21891
  }
21278
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")) : [];
21279
- const stagedDeleted = spawnSync5("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
21892
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")) : [];
21893
+ const stagedDeleted = spawnSync6("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
21280
21894
  if (stagedDeleted.status !== 0) {
21281
21895
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
21282
21896
  if (!/not a git repository/i.test(stderr))
21283
21897
  return { paths, uncertain: true, git: true };
21284
21898
  } else if (stagedDeleted.stdout) {
21285
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")));
21899
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")));
21286
21900
  }
21287
21901
  return { paths: [...new Set(paths)], uncertain: false, git: true };
21288
21902
  };
21289
21903
  var walk = (root, directory, output) => {
21290
21904
  let names;
21291
21905
  try {
21292
- names = fs10.readdirSync(directory);
21906
+ names = fs11.readdirSync(directory);
21293
21907
  } catch {
21294
21908
  return true;
21295
21909
  }
@@ -21297,10 +21911,10 @@ var walk = (root, directory, output) => {
21297
21911
  for (const name of names) {
21298
21912
  if (name === ".git" || name === ".workit")
21299
21913
  continue;
21300
- const target = path9.join(directory, name);
21914
+ const target = path11.join(directory, name);
21301
21915
  let stat;
21302
21916
  try {
21303
- stat = fs10.lstatSync(target);
21917
+ stat = fs11.lstatSync(target);
21304
21918
  } catch {
21305
21919
  uncertain = true;
21306
21920
  continue;
@@ -21315,14 +21929,14 @@ var walk = (root, directory, output) => {
21315
21929
  var scopeRoots = (root, scope) => {
21316
21930
  const roots = [];
21317
21931
  for (const item of scope.paths.length ? scope.paths : ["."]) {
21318
- const target = path9.resolve(root, item);
21932
+ const target = path11.resolve(root, item);
21319
21933
  if (!inside(root, target))
21320
21934
  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` }] });
21321
21935
  let ancestor = target;
21322
- while (!fs10.existsSync(ancestor) && ancestor !== root)
21323
- ancestor = path9.dirname(ancestor);
21936
+ while (!fs11.existsSync(ancestor) && ancestor !== root)
21937
+ ancestor = path11.dirname(ancestor);
21324
21938
  try {
21325
- if (!inside(root, fs10.realpathSync(ancestor)))
21939
+ if (!inside(root, fs11.realpathSync(ancestor)))
21326
21940
  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` }] });
21327
21941
  } catch {
21328
21942
  return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
@@ -21334,7 +21948,7 @@ var scopeRoots = (root, scope) => {
21334
21948
  function captureCandidate(root, scope, environment = []) {
21335
21949
  let checkout;
21336
21950
  try {
21337
- checkout = fs10.realpathSync(root);
21951
+ checkout = fs11.realpathSync(root);
21338
21952
  } catch {
21339
21953
  return failure2("invalid_input", "candidate root does not exist");
21340
21954
  }
@@ -21352,11 +21966,11 @@ function captureCandidate(root, scope, environment = []) {
21352
21966
  if (item === ".git" || item === ".workit")
21353
21967
  continue;
21354
21968
  names.add(item);
21355
- if (!fs10.existsSync(target))
21969
+ if (!fs11.existsSync(target))
21356
21970
  continue;
21357
21971
  let stat;
21358
21972
  try {
21359
- stat = fs10.lstatSync(target);
21973
+ stat = fs11.lstatSync(target);
21360
21974
  } catch {
21361
21975
  uncertain = true;
21362
21976
  continue;
@@ -21372,17 +21986,17 @@ function captureCandidate(root, scope, environment = []) {
21372
21986
  for (const item of [...names].toSorted()) {
21373
21987
  if (!scopeMatches(item, normalizedScope))
21374
21988
  continue;
21375
- const target = path9.join(checkout, item);
21989
+ const target = path11.join(checkout, item);
21376
21990
  try {
21377
- const stat = fs10.lstatSync(target);
21991
+ const stat = fs11.lstatSync(target);
21378
21992
  if (stat.isSymbolicLink()) {
21379
- const link = fs10.readlinkSync(target);
21993
+ const link = fs11.readlinkSync(target);
21380
21994
  files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
21381
21995
  } else if (stat.isFile()) {
21382
21996
  files.push({
21383
21997
  path: item,
21384
21998
  kind: "file",
21385
- digest: digestBytes(fs10.readFileSync(target)),
21999
+ digest: digestBytes(fs11.readFileSync(target)),
21386
22000
  executable: (stat.mode & 73) !== 0
21387
22001
  });
21388
22002
  } else if (!stat.isDirectory()) {
@@ -21407,7 +22021,7 @@ function captureCandidate(root, scope, environment = []) {
21407
22021
  const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
21408
22022
  if (values.some(({ value }) => value === null))
21409
22023
  completeness = "uncertain";
21410
- const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
22024
+ const headResult = spawnSync6("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
21411
22025
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
21412
22026
  completeness = "uncertain";
21413
22027
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -21547,7 +22161,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
21547
22161
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
21548
22162
  let root;
21549
22163
  try {
21550
- root = fs10.realpathSync(checkoutRoot);
22164
+ root = fs11.realpathSync(checkoutRoot);
21551
22165
  } catch {
21552
22166
  return failure2("permission_denied", "checkout root is unavailable");
21553
22167
  }
@@ -21556,23 +22170,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
21556
22170
  continue;
21557
22171
  if (!reference.digest)
21558
22172
  return failure2("invalid_input", "document references require a byte digest");
21559
- const target = path9.resolve(root, reference.path);
21560
- if (target !== root && !target.startsWith(`${root}${path9.sep}`))
22173
+ const target = path11.resolve(root, reference.path);
22174
+ if (target !== root && !target.startsWith(`${root}${path11.sep}`))
21561
22175
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
21562
22176
  fields: [
21563
22177
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
21564
22178
  ]
21565
22179
  });
21566
22180
  try {
21567
- const below = path9.relative(root, target);
22181
+ const below = path11.relative(root, target);
21568
22182
  let current = root;
21569
- for (const segment of below.split(path9.sep)) {
22183
+ for (const segment of below.split(path11.sep)) {
21570
22184
  if (!segment)
21571
22185
  continue;
21572
- current = path9.join(current, segment);
21573
- const stat = fs10.lstatSync(current);
21574
- const real = fs10.realpathSync(current);
21575
- if (real !== root && !real.startsWith(`${root}${path9.sep}`))
22186
+ current = path11.join(current, segment);
22187
+ const stat = fs11.lstatSync(current);
22188
+ const real = fs11.realpathSync(current);
22189
+ if (real !== root && !real.startsWith(`${root}${path11.sep}`))
21576
22190
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
21577
22191
  fields: [
21578
22192
  {
@@ -21588,7 +22202,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
21588
22202
  if (current !== target && !stat.isDirectory())
21589
22203
  return failure2("invalid_input", "document reference ancestor is not a directory");
21590
22204
  }
21591
- if (digestBytes(fs10.readFileSync(target)) !== reference.digest)
22205
+ if (digestBytes(fs11.readFileSync(target)) !== reference.digest)
21592
22206
  return failure2("permission_denied", "decision document bytes have changed");
21593
22207
  } catch {
21594
22208
  return failure2("invalid_input", "decision document is unavailable");
@@ -21673,7 +22287,35 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
21673
22287
  decisionIds: [],
21674
22288
  reason: "stored policy version is unsupported; reassessment is required"
21675
22289
  }));
22290
+ const ledger = ledgerView(checkoutRoot);
21676
22291
  return (task.policy?.requirements ?? []).map((requirement) => {
22292
+ const derived = derivedRuleStatus(task, requirement, checkoutRoot, ledger);
22293
+ if (derived) {
22294
+ if (derived.met)
22295
+ return {
22296
+ requirementId: requirement.id,
22297
+ status: "satisfied",
22298
+ evidenceIds: [],
22299
+ decisionIds: derived.decisionIds,
22300
+ reason: derived.reason
22301
+ };
22302
+ const waived = requirement.acceptanceAllowed ? applicableDecision(task, workspace, requirement, checkoutRoot) : [];
22303
+ if (waived.length)
22304
+ return {
22305
+ requirementId: requirement.id,
22306
+ status: "accepted_limitation",
22307
+ evidenceIds: [],
22308
+ decisionIds: waived.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
22309
+ reason: "an applicable approved limitation permits the missing evidence"
22310
+ };
22311
+ return {
22312
+ requirementId: requirement.id,
22313
+ status: "unsatisfied",
22314
+ evidenceIds: [],
22315
+ decisionIds: [],
22316
+ reason: derived.reason
22317
+ };
22318
+ }
21677
22319
  const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
21678
22320
  if (gate?.satisfied.length)
21679
22321
  return {
@@ -22088,14 +22730,14 @@ var gitContext = (workspaceRoot, paths = []) => {
22088
22730
  import {
22089
22731
  copyFileSync as copyFileSync2,
22090
22732
  cpSync as cpSync2,
22091
- existsSync as existsSync4,
22733
+ existsSync as existsSync5,
22092
22734
  mkdirSync as mkdirSync2,
22093
22735
  readFileSync as readFileSync3,
22094
22736
  readdirSync as readdirSync3,
22095
22737
  writeFileSync as writeFileSync2
22096
22738
  } from "node:fs";
22097
22739
  import os2 from "node:os";
22098
- import path10 from "node:path";
22740
+ import path12 from "node:path";
22099
22741
 
22100
22742
  // packages/workit-core/src/core/boundary.ts
22101
22743
  import { statSync as statSync2 } from "node:fs";
@@ -22154,7 +22796,7 @@ var mergePreset = (preset, input = {}, current = {
22154
22796
  protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
22155
22797
  };
22156
22798
  };
22157
- 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");
22799
+ 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");
22158
22800
  var migratedDir = null;
22159
22801
  var migrationFailed = false;
22160
22802
  var ensureConfigDir = (dir = resolveConfigDir()) => {
@@ -22164,12 +22806,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
22164
22806
  migratedDir = dir;
22165
22807
  return dir;
22166
22808
  }
22167
- const legacy = path10.join(process.env.XDG_CONFIG_HOME || path10.join(os2.homedir(), ".config"), "workflow-toolkit");
22168
- if (!existsSync4(legacy)) {
22809
+ const legacy = path12.join(process.env.XDG_CONFIG_HOME || path12.join(os2.homedir(), ".config"), "workflow-toolkit");
22810
+ if (!existsSync5(legacy)) {
22169
22811
  migratedDir = dir;
22170
22812
  return dir;
22171
22813
  }
22172
- if (!migrationFailed && existsSync4(dir)) {
22814
+ if (!migrationFailed && existsSync5(dir)) {
22173
22815
  migratedDir = dir;
22174
22816
  return dir;
22175
22817
  }
@@ -22177,9 +22819,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
22177
22819
  mkdirSync2(dir, { recursive: true });
22178
22820
  diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
22179
22821
  for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
22180
- const src = path10.join(legacy, entry.name);
22181
- const dest = path10.join(dir, entry.name);
22182
- if (existsSync4(dest))
22822
+ const src = path12.join(legacy, entry.name);
22823
+ const dest = path12.join(dir, entry.name);
22824
+ if (existsSync5(dest))
22183
22825
  continue;
22184
22826
  try {
22185
22827
  if (entry.isDirectory())
@@ -22250,7 +22892,7 @@ var parseConfigResult = (raw, file) => {
22250
22892
  };
22251
22893
  };
22252
22894
  var readConfigTyped = (dir) => {
22253
- const file = path10.join(dir ?? configDir(), "config.json");
22895
+ const file = path12.join(dir ?? configDir(), "config.json");
22254
22896
  return parseConfigResult(readSafe(file), file);
22255
22897
  };
22256
22898
  var readConfig = () => {
@@ -22295,8 +22937,9 @@ var resolveCommitPolicy = (config, workspace) => {
22295
22937
  };
22296
22938
 
22297
22939
  // packages/workit-core/src/core/workspaces.ts
22940
+ import { createHash as createHash8 } from "node:crypto";
22298
22941
  import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
22299
- import path11 from "node:path";
22942
+ import path13 from "node:path";
22300
22943
  var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
22301
22944
  var branchPreset = custom((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
22302
22945
  var commitPreset = custom((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
@@ -22349,7 +22992,7 @@ var releaseTrackSchema = object2({
22349
22992
  context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
22350
22993
  }
22351
22994
  }
22352
- 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(".."))) {
22995
+ 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(".."))) {
22353
22996
  context.addIssue({
22354
22997
  code: "custom",
22355
22998
  path: ["versionSource", "path"],
@@ -22368,6 +23011,7 @@ var workspaceConfigSchema = object2({
22368
23011
  }).passthrough().optional(),
22369
23012
  autoApprove: union([boolean2(), array(nonBlank)]).optional(),
22370
23013
  defaultEndpoint: _enum(["commit", "pr"]).optional(),
23014
+ verification: _enum(["self", "independent"]).optional(),
22371
23015
  autonomy: object2(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
22372
23016
  kind,
22373
23017
  union([boolean2(), literal("verified")]).optional()
@@ -22408,6 +23052,72 @@ var workspacesFileSchema = object2({ workspaces: array(workspaceConfigSchema).op
22408
23052
  names.add(workspace.name);
22409
23053
  }
22410
23054
  });
23055
+ var workspacesRevision = (contents) => createHash8("sha256").update(contents ?? "<missing>").digest("hex");
23056
+ var validateWorkspacesDocument = (parsed, file) => {
23057
+ if (!isConfigObject(parsed)) {
23058
+ return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
23059
+ }
23060
+ const validated = workspacesFileSchema.safeParse(parsed, { reportInput: true });
23061
+ if (!validated.success) {
23062
+ const detail = validated.error.issues.map((issue) => {
23063
+ const field = String(issue.path[issue.path.length - 1] ?? "");
23064
+ const attempted = ["provider", "preset", "strategy"].includes(field) && typeof issue.input === "string" ? ` (${JSON.stringify(issue.input)})` : "";
23065
+ return `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}${attempted}`;
23066
+ }).join("; ");
23067
+ return {
23068
+ status: "invalid",
23069
+ path: file,
23070
+ entries: [],
23071
+ error: `${file} has invalid workspace configuration: ${detail}`
23072
+ };
23073
+ }
23074
+ for (const [index, workspace] of (validated.data.workspaces ?? []).entries()) {
23075
+ const glob = validateWorkspaceGlob(workspace.glob);
23076
+ if (!glob.ok) {
23077
+ return {
23078
+ status: "invalid",
23079
+ path: file,
23080
+ entries: [],
23081
+ error: `${file} workspaces.${index}.glob: ${glob.error}`
23082
+ };
23083
+ }
23084
+ }
23085
+ return {
23086
+ status: "valid",
23087
+ path: file,
23088
+ entries: validated.data.workspaces ?? [],
23089
+ document: parsed
23090
+ };
23091
+ };
23092
+ var readWorkspacesResult = (dir = configDir()) => {
23093
+ const file = path13.join(dir, "workspaces.json");
23094
+ let raw;
23095
+ try {
23096
+ raw = readFileSync4(file, "utf8");
23097
+ } catch (error) {
23098
+ if (error.code === "ENOENT")
23099
+ return { status: "missing", path: file, entries: [], revision: workspacesRevision(null) };
23100
+ return {
23101
+ status: "malformed",
23102
+ path: file,
23103
+ entries: [],
23104
+ error: `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
23105
+ };
23106
+ }
23107
+ let parsed;
23108
+ try {
23109
+ parsed = JSON.parse(raw);
23110
+ } catch {
23111
+ return {
23112
+ status: "malformed",
23113
+ path: file,
23114
+ entries: [],
23115
+ revision: workspacesRevision(raw),
23116
+ error: `${file} is not valid JSON`
23117
+ };
23118
+ }
23119
+ return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
23120
+ };
22411
23121
  var globToRegExp = (glob) => {
22412
23122
  let out = "";
22413
23123
  for (let i = 0;i < glob.length; i++) {
@@ -22505,6 +23215,30 @@ var selectWorkspaceMatch = (matches, cwd, workspaceName) => {
22505
23215
  }
22506
23216
  return mostSpecific[0];
22507
23217
  };
23218
+ var resolveWorkspaceFromEntries = (cwd, entries, workspaceName) => {
23219
+ const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
23220
+ const matches = [];
23221
+ for (const ws of entries) {
23222
+ const glob = ws.glob.replaceAll("\\", "/");
23223
+ const canonical = canonicalGlob(glob);
23224
+ let matched = false;
23225
+ for (const target of targets) {
23226
+ if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
23227
+ matched = true;
23228
+ break;
23229
+ }
23230
+ }
23231
+ if (matched)
23232
+ matches.push(ws);
23233
+ }
23234
+ return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
23235
+ };
23236
+ var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
23237
+ const result = readWorkspacesResult(dir);
23238
+ if (result.status === "malformed" || result.status === "invalid")
23239
+ throw new Error(result.error);
23240
+ return resolveWorkspaceFromEntries(cwd, result.entries, workspaceName);
23241
+ };
22508
23242
  var runtimeWorkspacePolicySchema = (kind) => {
22509
23243
  const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
22510
23244
  const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object2({
@@ -22527,7 +23261,7 @@ var runtimeWorkspaceIndexSchema = object2({
22527
23261
  workspaces: array(object2({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
22528
23262
  }).passthrough();
22529
23263
  var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
22530
- const file = path11.join(configDir(), "workspaces.json");
23264
+ const file = path13.join(configDir(), "workspaces.json");
22531
23265
  let raw;
22532
23266
  try {
22533
23267
  raw = readFileSync4(file, "utf8");
@@ -22590,13 +23324,13 @@ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
22590
23324
  };
22591
23325
 
22592
23326
  // packages/workit-core/src/core/vcs-config.ts
22593
- import fs11 from "node:fs";
22594
- import path12 from "node:path";
22595
- import { spawnSync as spawnSync6 } from "node:child_process";
22596
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path12.join(configDir(), "vcs.json");
23327
+ import fs12 from "node:fs";
23328
+ import path14 from "node:path";
23329
+ import { spawnSync as spawnSync7 } from "node:child_process";
23330
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path14.join(configDir(), "vcs.json");
22597
23331
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
22598
23332
  var remoteProvider = (cwd) => {
22599
- const r = spawnSync6("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
23333
+ const r = spawnSync7("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
22600
23334
  if (r.status !== 0)
22601
23335
  return null;
22602
23336
  const url = (r.stdout ?? "").trim();
@@ -22633,7 +23367,7 @@ var remoteEndpoint = (remote) => {
22633
23367
  var configuredSshEndpoint = (host) => {
22634
23368
  if (host.startsWith("-"))
22635
23369
  return null;
22636
- const resolved = spawnSync6("ssh", ["-G", host], {
23370
+ const resolved = spawnSync7("ssh", ["-G", host], {
22637
23371
  encoding: "utf8",
22638
23372
  timeout: 5000,
22639
23373
  maxBuffer: 1024 * 1024,
@@ -22683,7 +23417,7 @@ var readVcsConfig = () => {
22683
23417
  const file = vcsConfigPath();
22684
23418
  let raw;
22685
23419
  try {
22686
- raw = fs11.readFileSync(file, "utf8");
23420
+ raw = fs12.readFileSync(file, "utf8");
22687
23421
  } catch {
22688
23422
  return { status: "missing", path: file, config: {} };
22689
23423
  }
@@ -22749,7 +23483,7 @@ function vcsConfig(mode, cwd) {
22749
23483
  const prov = cfg[provider] ?? {};
22750
23484
  const out = {
22751
23485
  ok: true,
22752
- configPath: path12.resolve(cfgPath),
23486
+ configPath: path14.resolve(cfgPath),
22753
23487
  provider,
22754
23488
  defaultTargetBranch: defaultTarget,
22755
23489
  pr: cfg.pr ?? {},
@@ -22787,7 +23521,7 @@ function mergedPrStyle(limit = 6, cwd) {
22787
23521
  `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
22788
23522
  descriptionPreview: desc.slice(0, 600)
22789
23523
  });
22790
- const remoteResult = spawnSync6("git", ["remote", "get-url", "--push", "--all", "origin"], {
23524
+ const remoteResult = spawnSync7("git", ["remote", "get-url", "--push", "--all", "origin"], {
22791
23525
  cwd,
22792
23526
  encoding: "utf8"
22793
23527
  });
@@ -22803,7 +23537,7 @@ function mergedPrStyle(limit = 6, cwd) {
22803
23537
  if (!hostingApiHostMatches(remote, host))
22804
23538
  return { ok: false, error: "PR style target does not match the provider host" };
22805
23539
  if (provider === "gitlab") {
22806
- const run = (args) => spawnSync6("glab", ["api", ...args], {
23540
+ const run = (args) => spawnSync7("glab", ["api", ...args], {
22807
23541
  cwd,
22808
23542
  encoding: "utf8",
22809
23543
  env
@@ -22823,7 +23557,7 @@ function mergedPrStyle(limit = 6, cwd) {
22823
23557
  });
22824
23558
  }
22825
23559
  } else if (provider === "github") {
22826
- const r = spawnSync6("gh", [
23560
+ const r = spawnSync7("gh", [
22827
23561
  "pr",
22828
23562
  "list",
22829
23563
  "--repo",
@@ -22872,305 +23606,34 @@ var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspaceP
22872
23606
  import { realpathSync as realpathSync4 } from "node:fs";
22873
23607
 
22874
23608
  // packages/workit-core/src/core/policy-resolver.ts
22875
- var resolverInputSchema = object2({
22876
- intent: intentSchema,
22877
- assessment: assessmentSchema,
22878
- constraints: array(constraintSchema),
22879
- prior: object2({
22880
- decisions: array(decisionSchema),
22881
- findings: array(findingSchema),
22882
- requirements: array(requirementSchema)
22883
- }).strict(),
22884
- preferences: object2({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
22885
- }).strict();
22886
- var compareCodeUnits3 = (left, right) => {
22887
- const length = Math.min(left.length, right.length);
22888
- for (let index = 0;index < length; index += 1) {
22889
- const difference = left.charCodeAt(index) - right.charCodeAt(index);
22890
- if (difference !== 0)
22891
- return difference;
22892
- }
22893
- return left.length - right.length;
22894
- };
22895
- var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits3);
22896
- var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right)));
22897
- var normalizedScope = (scope) => ({
22898
- description: scope.description,
22899
- paths: stableUnique(scope.paths),
22900
- exclusions: stableUnique(scope.exclusions)
22901
- });
22902
- var normalizedRefList = (values) => stableList(values);
22903
- var normalizeAssessment = (assessment) => ({
22904
- facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
22905
- signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
22906
- name,
22907
- { ...signal, refs: normalizedRefList(signal.refs) }
22908
- ])),
22909
- consequences: stableList(assessment.consequences.map((consequence) => ({
22910
- ...consequence,
22911
- fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
22912
- }))),
22913
- verification: stableList(assessment.verification.map((verification) => ({
22914
- ...verification,
22915
- scope: normalizedScope(verification.scope),
22916
- availableChecks: normalizedRefList(verification.availableChecks),
22917
- gaps: stableUnique(verification.gaps)
22918
- })))
22919
- });
22920
- var normalizeConstraint = (constraint) => ({
22921
- ...constraint,
22922
- source: constraint.source,
22923
- requires: stableList(constraint.requires.map((requirement) => ({
22924
- ...requirement,
22925
- scope: normalizedScope(requirement.scope),
22926
- refs: normalizedRefList(requirement.refs)
22927
- })))
22928
- });
22929
- var normalizeDecision = (decision) => ({
22930
- ...decision,
22931
- binding: {
22932
- ...decision.binding,
22933
- scope: normalizedScope(decision.binding.scope),
22934
- contentRefs: normalizedRefList(decision.binding.contentRefs)
22935
- },
22936
- requirementIds: stableUnique(decision.requirementIds)
22937
- });
22938
- var normalizeIntent = (intent) => ({
22939
- ...intent,
22940
- scope: normalizedScope(intent.scope),
22941
- authorityRefs: normalizedRefList(intent.authorityRefs)
22942
- });
22943
- var normalizePrior = (prior) => ({
22944
- decisions: stableList(prior.decisions.map(normalizeDecision)),
22945
- findings: stableList(prior.findings.map((finding) => ({
22946
- ...finding,
22947
- scope: normalizedScope(finding.scope),
22948
- refs: normalizedRefList(finding.refs),
22949
- resolution: finding.resolution ? {
22950
- ...finding.resolution,
22951
- evidenceIds: stableUnique(finding.resolution.evidenceIds),
22952
- decisionIds: stableUnique(finding.resolution.decisionIds)
22953
- } : null
22954
- }))),
22955
- requirements: stableList(prior.requirements.map((requirement) => ({
22956
- ...requirement,
22957
- scope: normalizedScope(requirement.scope)
22958
- })))
22959
- });
22960
- var invalidFields = (error) => ({
22961
- fields: error.issues.map((issue) => ({
22962
- path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
22963
- reason: issue.message
22964
- }))
22965
- });
22966
- function normalizeResolverInput(input) {
22967
- const parsed = resolverInputSchema.safeParse(input);
22968
- if (!parsed.success)
22969
- return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
22970
- const value = parsed.data;
22971
- const signals = value.assessment.signals;
22972
- if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
22973
- return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
22974
- fields: [
22975
- {
22976
- path: "assessment.signals",
22977
- reason: "contradictory behaviorChange and mechanicalLowRisk"
22978
- }
22979
- ]
22980
- });
22981
- const ids = value.constraints.map((constraint) => constraint.id);
23609
+ function resolvePolicy(input) {
23610
+ const ids = input.constraints.map((constraint) => constraint.id);
22982
23611
  if (new Set(ids).size !== ids.length)
22983
23612
  return failure2("invalid_input", "constraint IDs must be unique", {
22984
23613
  fields: [{ path: "constraints", reason: "duplicate constraint id" }]
22985
23614
  });
22986
- if (value.preferences?.fast && value.preferences.thorough)
22987
- return failure2("invalid_input", "fast and thorough preferences contradict", {
22988
- fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
22989
- });
22990
- const normalized = {
22991
- intent: normalizeIntent(value.intent),
22992
- assessment: normalizeAssessment(value.assessment),
22993
- constraints: stableList(value.constraints.map(normalizeConstraint)),
22994
- prior: normalizePrior(value.prior),
22995
- ...value.preferences ? { preferences: { ...value.preferences } } : {},
22996
- policyVersion: POLICY_VERSION
22997
- };
22998
- return success(null, null, normalized);
22999
- }
23000
- var requirement = (spec) => ({
23001
- ...spec,
23002
- id: requirementId({
23003
- ruleId: spec.ruleId,
23004
- scope: spec.scope,
23005
- satisfaction: spec.satisfaction,
23006
- before: spec.before,
23007
- dependentAction: spec.dependentAction
23008
- })
23009
- });
23010
- var signalReason = (input, name) => input.assessment.signals[name].reason;
23011
- function resolveRequirements(input) {
23012
- const { signals } = input.assessment;
23013
- const scope = input.intent.scope;
23014
- const requirements = [];
23015
- const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
23016
- const hasUnknownFact = [
23017
- ...input.assessment.facts,
23018
- ...input.assessment.consequences.map(({ fact }) => fact)
23019
- ].some((fact) => fact.basis === "unknown");
23020
- if (hasUnknownSignal || hasUnknownFact)
23021
- requirements.push(requirement({
23022
- ruleId: "consequential-unknown",
23023
- dimension: "investigation",
23024
- scope,
23025
- reason: `Resolve consequential uncertainty before the dependent action: ${[
23026
- ...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
23027
- ...hasUnknownFact ? ["unknown fact"] : []
23028
- ].join(", ")}`,
23029
- satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
23030
- before: "dependent_action",
23031
- dependentAction: null,
23032
- acceptanceAllowed: false
23033
- }));
23034
- if (signals.productChoiceOpen.value === true)
23035
- requirements.push(requirement({
23036
- ruleId: "product-decision",
23037
- dimension: "decisions",
23038
- scope,
23039
- reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
23040
- satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
23041
- before: "dependent_action",
23042
- dependentAction: null,
23043
- acceptanceAllowed: false
23044
- }));
23045
- const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
23046
- if (signals.behaviorChange.value === true)
23047
- requirements.push(requirement({
23048
- ruleId: "behavioral-verification",
23049
- dimension: "testing",
23050
- scope,
23051
- reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
23052
- satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
23053
- before: "close",
23054
- dependentAction: null,
23055
- acceptanceAllowed: false
23056
- }), requirement({
23057
- ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
23058
- dimension: "review",
23059
- scope,
23060
- 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.",
23061
- 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.",
23062
- before: "close",
23063
- dependentAction: null,
23064
- acceptanceAllowed: !independentBehaviorReview
23065
- }));
23066
- else if (signals.mechanicalLowRisk.value === true)
23067
- requirements.push(requirement({
23068
- ruleId: "mechanical-existing-checks",
23069
- dimension: "verification",
23070
- scope,
23071
- reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
23072
- satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
23073
- before: "close",
23074
- dependentAction: null,
23075
- acceptanceAllowed: true
23076
- }), requirement({
23077
- ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
23078
- dimension: "review",
23079
- scope,
23080
- 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.",
23081
- 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.",
23082
- before: "close",
23083
- dependentAction: null,
23084
- acceptanceAllowed: !input.preferences?.thorough
23085
- }));
23086
- if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
23087
- requirements.push(requirement({
23088
- ruleId: "pre-pr-cleanup",
23089
- dimension: "verification",
23090
- scope,
23091
- reason: "Implementation output must be deslopped before it is delivered.",
23092
- satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
23093
- before: "dependent_action",
23094
- dependentAction: "hosting.pull_request",
23095
- acceptanceAllowed: true
23096
- }));
23097
- if (signals.durableAgreementNeeded.value === true)
23098
- requirements.push(requirement({
23099
- ruleId: "durable-spec",
23100
- dimension: "artifacts",
23101
- scope,
23102
- reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
23103
- satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
23104
- before: "write",
23105
- dependentAction: null,
23106
- acceptanceAllowed: true
23107
- }));
23108
- if (signals.coordinationPlanNeeded.value === true)
23109
- requirements.push(requirement({
23110
- ruleId: "coordination-plan",
23111
- dimension: "continuity",
23112
- scope,
23113
- reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
23114
- satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
23115
- before: "dependent_action",
23116
- dependentAction: null,
23117
- acceptanceAllowed: true
23118
- }));
23119
- if (signals.helperUseful.value === true && !input.preferences?.fast)
23120
- requirements.push(requirement({
23121
- ruleId: "helper-usefulness",
23122
- dimension: "delegation",
23123
- scope,
23124
- reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
23125
- satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
23126
- before: "dependent_action",
23127
- dependentAction: null,
23128
- acceptanceAllowed: true
23129
- }));
23130
- for (const constraint of input.constraints)
23131
- for (const obligation of constraint.requires) {
23132
- const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
23133
- requirements.push(requirement({
23134
- ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
23135
- kind: obligation.kind,
23136
- scope: normalizedScope(obligation.scope),
23137
- refs: normalizedRefList(obligation.refs),
23138
- method: obligation.method,
23139
- before: obligation.before,
23140
- dependentAction: obligation.dependentAction
23141
- })}`,
23142
- dimension,
23143
- scope: obligation.scope,
23144
- reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
23145
- satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
23146
- before: obligation.before,
23147
- dependentAction: obligation.dependentAction,
23148
- acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
23149
- }));
23150
- }
23151
- const seen = new Set;
23152
- return requirements.filter((item) => {
23153
- if (seen.has(item.id))
23154
- return false;
23155
- seen.add(item.id);
23156
- return true;
23157
- });
23158
- }
23159
- function resolvePolicy(input) {
23160
- const normalized = normalizeResolverInput(input);
23161
- if (!normalized.ok)
23162
- return normalized;
23163
- const requirements = resolveRequirements(normalized.data);
23164
23615
  const policy = {
23165
23616
  policyVersion: POLICY_VERSION,
23166
- inputDigest: sha256(canonicalJson(normalized.data)),
23167
- requirements
23617
+ inputDigest: sha256(canonicalJson({
23618
+ judgment: input.judgment,
23619
+ scope: input.intent.scope,
23620
+ constraints: input.constraints,
23621
+ verification: input.verification ?? "self",
23622
+ policyVersion: POLICY_VERSION
23623
+ })),
23624
+ requirements: deriveRequirements(input.judgment, input.intent.scope, input.constraints, input.verification)
23168
23625
  };
23169
23626
  const parsed = policySchema.safeParse(policy);
23170
23627
  if (!parsed.success)
23171
- return failure2("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
23628
+ return failure2("invalid_input", "resolved policy is invalid", {
23629
+ fields: parsed.error.issues.map((issue) => ({
23630
+ path: issue.path.join("."),
23631
+ reason: issue.message
23632
+ }))
23633
+ });
23172
23634
  return success(null, null, parsed.data);
23173
23635
  }
23636
+ var compareCodeUnits3 = (left, right) => left < right ? -1 : left > right ? 1 : 0;
23174
23637
  function diffPolicy(previous, next, reason, now) {
23175
23638
  if (!policySchema.safeParse(previous).success && previous !== null)
23176
23639
  throw new TypeError("previous policy is invalid");
@@ -23198,7 +23661,222 @@ function diffPolicy(previous, next, reason, now) {
23198
23661
  return parsed.data;
23199
23662
  }
23200
23663
 
23664
+ // packages/workit-core/src/core/policy/judgment.ts
23665
+ var isObject6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23666
+ var squash = (key) => key.toLowerCase().replace(/[_-]/g, "");
23667
+ var ALIASES = {
23668
+ riskTier: ["risktier", "risk", "risklevel", "tier"],
23669
+ behaviorChange: ["behaviorchange", "behavior", "behaviour", "behaviourchange"],
23670
+ productChoiceOpen: ["productchoiceopen", "openproductchoice", "productchoice", "openchoice"],
23671
+ needsPlan: ["needsplan", "plan", "needsspec", "spec"],
23672
+ note: ["note", "notes", "reason", "why"],
23673
+ refs: ["refs", "ref", "references"]
23674
+ };
23675
+ var LEGACY_KEYS = new Set(["assessment", "facts", "signals", "consequences", "verification"]);
23676
+ var RISK = {
23677
+ trivial: "trivial",
23678
+ low: "trivial",
23679
+ minor: "trivial",
23680
+ mechanical: "trivial",
23681
+ none: "trivial",
23682
+ normal: "normal",
23683
+ medium: "normal",
23684
+ med: "normal",
23685
+ moderate: "normal",
23686
+ high: "high",
23687
+ critical: "high",
23688
+ major: "high"
23689
+ };
23690
+ var YES = new Set(["true", "yes", "y", "1", "on"]);
23691
+ var NO = new Set(["false", "no", "n", "0", "off", "none"]);
23692
+ var flag = (value, unknownAs) => {
23693
+ if (typeof value === "boolean")
23694
+ return { ok: true, value };
23695
+ if (typeof value === "number" && (value === 0 || value === 1))
23696
+ return { ok: true, value: value === 1 };
23697
+ if (typeof value !== "string")
23698
+ return { ok: false };
23699
+ const text = value.trim().toLowerCase();
23700
+ if (YES.has(text))
23701
+ return { ok: true, value: true };
23702
+ if (NO.has(text))
23703
+ return { ok: true, value: false };
23704
+ if (text === "unknown" || text === "maybe")
23705
+ return { ok: true, value: unknownAs };
23706
+ return { ok: false };
23707
+ };
23708
+ var fromLegacy = (raw, previous) => {
23709
+ const source = isObject6(raw.assessment) ? raw.assessment : raw;
23710
+ const signals = isObject6(source.signals) ? source.signals : {};
23711
+ const value = (name) => isObject6(signals[name]) ? signals[name].value : undefined;
23712
+ const known = (name, fallback) => typeof value(name) === "boolean" ? value(name) : fallback;
23713
+ const behaviorChange = known("behaviorChange", previous?.behaviorChange ?? false);
23714
+ const consequences = Array.isArray(source.consequences) ? source.consequences : [];
23715
+ const freshReview = value("behaviorChange") === true && consequences.some((item) => isObject6(item) && ["security", "data", "public_contract", "operations"].includes(String(item.area)));
23716
+ return {
23717
+ riskTier: freshReview ? "high" : value("mechanicalLowRisk") === true && !behaviorChange ? "trivial" : "normal",
23718
+ behaviorChange,
23719
+ productChoiceOpen: known("productChoiceOpen", previous?.productChoiceOpen ?? false),
23720
+ needsPlan: known("durableAgreementNeeded", previous?.needsPlan ?? false) || value("coordinationPlanNeeded") === true
23721
+ };
23722
+ };
23723
+ var DEFAULT = {
23724
+ riskTier: "normal",
23725
+ behaviorChange: false,
23726
+ productChoiceOpen: false,
23727
+ needsPlan: false,
23728
+ note: null,
23729
+ refs: []
23730
+ };
23731
+ function normalizeJudgment(raw, previous) {
23732
+ const input = isObject6(raw) ? raw : {};
23733
+ const legacy = Object.keys(input).some((key) => LEGACY_KEYS.has(key));
23734
+ const base = { ...DEFAULT, ...previous, refs: [...previous?.refs ?? []] };
23735
+ const judgment = legacy ? { ...base, ...fromLegacy(input, previous) } : { ...base };
23736
+ const ignored = [];
23737
+ const fields = [];
23738
+ for (const [key, value] of Object.entries(input)) {
23739
+ if (LEGACY_KEYS.has(key))
23740
+ continue;
23741
+ const name = Object.keys(ALIASES).find((field) => ALIASES[field].includes(squash(key)));
23742
+ if (!name) {
23743
+ ignored.push(key);
23744
+ continue;
23745
+ }
23746
+ if (name === "riskTier") {
23747
+ const tier = typeof value === "string" ? RISK[value.trim().toLowerCase()] : undefined;
23748
+ if (tier)
23749
+ judgment.riskTier = tier;
23750
+ else
23751
+ fields.push({ path: key, reason: "expected trivial|normal|high (or low|medium)" });
23752
+ } else if (name === "note") {
23753
+ judgment.note = typeof value === "string" && value.trim() ? value.trim() : null;
23754
+ } else if (name === "refs") {
23755
+ const items = Array.isArray(value) ? value : [value];
23756
+ judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
23757
+ } else {
23758
+ if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
23759
+ judgment.needsPlan = true;
23760
+ judgment.refs.push(refOf(value.trim()));
23761
+ continue;
23762
+ }
23763
+ const parsed = flag(value, name === "behaviorChange");
23764
+ if (parsed.ok)
23765
+ judgment[name] = parsed.value;
23766
+ else
23767
+ fields.push({ path: key, reason: "expected true|false (yes|no accepted)" });
23768
+ }
23769
+ }
23770
+ if (fields.length)
23771
+ return failure2("invalid_input", "judgment values are invalid", { fields });
23772
+ const seen = new Set;
23773
+ judgment.refs = judgment.refs.filter((ref) => {
23774
+ const key = JSON.stringify(ref);
23775
+ return seen.has(key) ? false : (seen.add(key), true);
23776
+ });
23777
+ const parsed = judgmentSchema.safeParse(judgment);
23778
+ if (!parsed.success)
23779
+ return failure2("invalid_input", "judgment is invalid", {
23780
+ fields: parsed.error.issues.map((issue) => ({
23781
+ path: issue.path.join("."),
23782
+ reason: issue.message
23783
+ }))
23784
+ });
23785
+ return success(null, null, { judgment: parsed.data, ignored, legacy });
23786
+ }
23787
+
23788
+ // packages/workit-core/src/autonomy.ts
23789
+ import os3 from "node:os";
23790
+ import path15 from "node:path";
23791
+ var accountHome = () => {
23792
+ try {
23793
+ return os3.userInfo().homedir;
23794
+ } catch {
23795
+ return os3.homedir();
23796
+ }
23797
+ };
23798
+ var grantsHome = { resolve: accountHome };
23799
+ var grantsDir = () => path15.join(grantsHome.resolve(), ".config", "workit");
23800
+ var grantsOverride = () => {
23801
+ const configured = path15.resolve(resolveConfigDir());
23802
+ 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`;
23803
+ };
23804
+ var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
23805
+ var DEFAULT_GRANTS = Object.freeze({
23806
+ push: true,
23807
+ pr: true,
23808
+ merge: false,
23809
+ release: false,
23810
+ rerun: true
23811
+ });
23812
+ var DEFAULT_ENDPOINT = "commit";
23813
+ var DEFAULT_VERIFICATION = "self";
23814
+ var verificationOf = (entry) => entry?.verification === "independent" ? "independent" : "self";
23815
+ var endpointOf = (entry) => entry?.defaultEndpoint === "pr" ? "pr" : "commit";
23816
+ var isGrantValue = (value) => value === true || value === false || value === "verified";
23817
+ var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
23818
+ var fromAutoApprove = (value) => {
23819
+ if (value === true)
23820
+ return { push: true, pr: true, merge: "verified" };
23821
+ if (!Array.isArray(value))
23822
+ return null;
23823
+ const grants = {};
23824
+ for (const item of value)
23825
+ if (item === "push" || item === "pr")
23826
+ grants[item] = true;
23827
+ else if (item === "merge")
23828
+ grants.merge = "verified";
23829
+ return Object.keys(grants).length > 0 ? grants : null;
23830
+ };
23831
+ function configuredGrants(entry) {
23832
+ const record = entry ?? {};
23833
+ const raw = record.autonomy;
23834
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
23835
+ const grants = {};
23836
+ for (const kind of GRANT_KINDS) {
23837
+ const value = raw[kind];
23838
+ if (isGrantValue(value))
23839
+ grants[kind] = normalized(kind, value);
23840
+ }
23841
+ return { grants, source: "autonomy" };
23842
+ }
23843
+ const legacy = fromAutoApprove(record.autoApprove);
23844
+ return legacy ? { grants: legacy, source: "autoApprove" } : { grants: {}, source: "default" };
23845
+ }
23846
+ function resolveAutonomy(cwd) {
23847
+ const override = grantsOverride();
23848
+ if (override)
23849
+ return {
23850
+ workspace: null,
23851
+ grants: { ...DEFAULT_GRANTS },
23852
+ configured: [],
23853
+ source: "default",
23854
+ accountConfigured: false,
23855
+ defaultEndpoint: DEFAULT_ENDPOINT,
23856
+ verification: DEFAULT_VERIFICATION,
23857
+ note: override
23858
+ };
23859
+ const workspace = resolveWorkspaceFrom(cwd, grantsDir(), process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined);
23860
+ const { grants, source } = configuredGrants(workspace);
23861
+ return {
23862
+ workspace: workspace?.name ?? null,
23863
+ grants: { ...DEFAULT_GRANTS, ...grants },
23864
+ configured: GRANT_KINDS.filter((kind) => grants[kind] !== undefined),
23865
+ source,
23866
+ accountConfigured: Boolean(workspace?.vcs?.account),
23867
+ defaultEndpoint: endpointOf(workspace),
23868
+ verification: verificationOf(workspace)
23869
+ };
23870
+ }
23871
+
23201
23872
  // packages/workit-core/src/core/task-engine.ts
23873
+ var workspaceVerification = (root) => {
23874
+ try {
23875
+ return resolveAutonomy(root).verification;
23876
+ } catch {
23877
+ return "self";
23878
+ }
23879
+ };
23202
23880
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
23203
23881
  kind,
23204
23882
  host: context.caller.host,
@@ -23391,22 +24069,7 @@ var mapRef = (ref, ids) => {
23391
24069
  return ref;
23392
24070
  return { ...ref, id: ids.get(ref.id) ?? ref.id };
23393
24071
  };
23394
- var portableRefs = (refs) => refs.filter((ref) => ref.kind === "file" || ref.kind === "record");
23395
24072
  var portableRef = (ref) => ref && (ref.kind === "file" || ref.kind === "record") ? ref : null;
23396
- var portableFact = (fact) => {
23397
- const refs = portableRefs(fact.refs);
23398
- return fact.basis === "observed" && refs.length === 0 ? { ...fact, basis: "unknown", refs } : { ...fact, refs };
23399
- };
23400
- var portableSignal = (signal) => {
23401
- const refs = portableRefs(signal.refs);
23402
- return signal.basis === "observed" && refs.length === 0 ? {
23403
- ...signal,
23404
- value: "unknown",
23405
- basis: "unknown",
23406
- reason: "portable supporting reference was removed",
23407
- refs
23408
- } : { ...signal, refs };
23409
- };
23410
24073
  var importedProvenance = (context) => ({
23411
24074
  kind: "imported",
23412
24075
  host: context.caller.host,
@@ -23418,18 +24081,6 @@ var portableTask = (task) => {
23418
24081
  if (typeof stripped !== "object" || stripped === null || Array.isArray(stripped))
23419
24082
  throw new TypeError("portable task rewrite produced a non-record");
23420
24083
  const clone = structuredClone(stripped);
23421
- const demote = (facts, signals, consequences) => {
23422
- for (const fact of facts)
23423
- Object.assign(fact, portableFact(fact));
23424
- for (const name of Object.keys(signals)) {
23425
- const signal = signals[name];
23426
- Object.assign(signal, portableSignal(signal));
23427
- }
23428
- for (const consequence of consequences)
23429
- Object.assign(consequence.fact, portableFact(consequence.fact));
23430
- };
23431
- for (const entry of clone.assessments)
23432
- demote(entry.data.facts, entry.data.signals, entry.data.consequences);
23433
24084
  clone.candidates = [];
23434
24085
  return clone;
23435
24086
  };
@@ -23446,6 +24097,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
23446
24097
  for (const entry of [
23447
24098
  source.intent,
23448
24099
  ...source.assessments,
24100
+ ...source.judgments ?? [],
23449
24101
  ...source.evidence,
23450
24102
  ...source.decisions,
23451
24103
  ...source.findings,
@@ -23469,9 +24121,16 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
23469
24121
  id: fresh(entry.id),
23470
24122
  recordedAt: timestamp,
23471
24123
  provenance: imported,
23472
- data: rewriteRecordRefs(data, assessmentSchema, remap)
24124
+ data: rewriteRecordRefs(data, legacyAssessmentSchema, remap)
23473
24125
  };
23474
24126
  });
24127
+ const judgments = source.judgments?.map((entry) => ({
24128
+ ...entry,
24129
+ id: fresh(entry.id),
24130
+ recordedAt: timestamp,
24131
+ provenance: imported,
24132
+ data: rewriteRecordRefs(entry.data, judgmentSchema, remap)
24133
+ }));
23475
24134
  const evidence = source.evidence.map((entry) => ({
23476
24135
  ...entry,
23477
24136
  id: fresh(entry.id),
@@ -23539,6 +24198,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
23539
24198
  status: "paused",
23540
24199
  closure: null,
23541
24200
  assessments,
24201
+ ...judgments ? { judgments } : {},
23542
24202
  evidence,
23543
24203
  decisions: decisions.map((entry) => {
23544
24204
  const data = rewriteRecordRefs(entry.data, decisionSchema, remap);
@@ -23608,7 +24268,8 @@ class WorkitCore {
23608
24268
  retryOmittedRevisions(request, run) {
23609
24269
  return retryFilledRevisions(filledRevisions(request), run);
23610
24270
  }
23611
- implicitTask(family, request) {
24271
+ implicitTask(family, raw) {
24272
+ const request = normalizeOperationInput(family, raw, this.context.caller.host);
23612
24273
  if (typeof request !== "object" || request === null || Array.isArray(request))
23613
24274
  return success(null, null, request);
23614
24275
  const value = request;
@@ -23862,7 +24523,11 @@ class WorkitCore {
23862
24523
  if ((this.context.workerId ?? null) !== null)
23863
24524
  return failure2("permission_denied", "helpers cannot change task requirements");
23864
24525
  this.fillRevisions(input, task.data);
23865
- const resolved = this.resolve(task.data, input.assessment);
24526
+ const judged = normalizeJudgment(input.judgment, latestJudgment(task.data));
24527
+ if (!judged.ok)
24528
+ return judged;
24529
+ const judgment = judged.data.judgment;
24530
+ const resolved = this.resolve(task.data, judgment);
23866
24531
  if (!resolved.ok)
23867
24532
  return resolved;
23868
24533
  if (input.action === "preview")
@@ -23873,16 +24538,17 @@ class WorkitCore {
23873
24538
  if (current.status === "closed")
23874
24539
  return failure2("invalid_transition", "closed task cannot be assessed");
23875
24540
  const nextChange = diffPolicy(current.policy, resolved.data, "policy reassessed", mutation.now);
23876
- const assessment = {
24541
+ const entry = {
23877
24542
  id: newId(),
23878
24543
  recordedAt: mutation.now,
23879
24544
  provenance: provenance(this.context),
23880
- data: input.assessment
24545
+ data: judgment
23881
24546
  };
23882
24547
  const next = {
23883
24548
  ...current,
23884
24549
  constraints: this.context.constraints,
23885
- assessments: [...current.assessments, assessment],
24550
+ judgments: [...current.judgments ?? [], entry],
24551
+ critical: [...new Set([...current.critical ?? [], "judgments"])],
23886
24552
  policy: resolved.data,
23887
24553
  policyChanges: nextChange ? [...current.policyChanges, nextChange] : current.policyChanges
23888
24554
  };
@@ -24084,9 +24750,14 @@ class WorkitCore {
24084
24750
  return workspace;
24085
24751
  if (!workspace.data)
24086
24752
  return failure2("not_found", "workspace not found");
24087
- if (input.binding.taskId !== task.data.id || input.binding.workspaceId !== workspace.data.id)
24753
+ const binding = {
24754
+ ...input.binding,
24755
+ taskId: input.binding.taskId ?? task.data.id,
24756
+ workspaceId: input.binding.workspaceId ?? workspace.data.id
24757
+ };
24758
+ if (binding.taskId !== task.data.id || binding.workspaceId !== workspace.data.id)
24088
24759
  return failure2("invalid_input", "decision task or workspace binding is invalid");
24089
- const content = verifyDecisionContentAtRoot(this.store.root, input.binding);
24760
+ const content = verifyDecisionContentAtRoot(this.store.root, binding);
24090
24761
  if (!content.ok)
24091
24762
  return content;
24092
24763
  const knownRequirements = new Set(task.data.policy?.requirements.map((item) => item.id) ?? []);
@@ -24094,7 +24765,7 @@ class WorkitCore {
24094
24765
  return failure2("invalid_input", "decision references an unknown requirement");
24095
24766
  const base = {
24096
24767
  purpose: input.purpose,
24097
- binding: input.binding,
24768
+ binding,
24098
24769
  response: input.response,
24099
24770
  requirementIds: input.requirementIds,
24100
24771
  revoked: null
@@ -24453,16 +25124,12 @@ class WorkitCore {
24453
25124
  now: trustedNow(this.context)
24454
25125
  });
24455
25126
  }
24456
- resolve(task, assessment) {
25127
+ resolve(task, judgment) {
24457
25128
  return resolvePolicy({
24458
25129
  intent: task.intent.data,
24459
- assessment,
25130
+ judgment,
24460
25131
  constraints: this.context.constraints,
24461
- prior: {
24462
- decisions: task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data),
24463
- findings: task.findings.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data),
24464
- requirements: task.origin || task.policy === null ? [] : task.policy.requirements
24465
- }
25132
+ verification: workspaceVerification(this.context.root)
24466
25133
  });
24467
25134
  }
24468
25135
  summary(task) {
@@ -24788,9 +25455,9 @@ class WorkitCore {
24788
25455
  }
24789
25456
  }
24790
25457
  // packages/workit-core/src/core/repo-context.ts
24791
- import { spawnSync as spawnSync7 } from "node:child_process";
24792
- import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
24793
- import path13 from "node:path";
25458
+ import { spawnSync as spawnSync8 } from "node:child_process";
25459
+ import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
25460
+ import path16 from "node:path";
24794
25461
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
24795
25462
  try {
24796
25463
  gitRevisionParts(value);
@@ -24817,7 +25484,7 @@ var invalidRange = (cwd) => ({
24817
25484
  cwd
24818
25485
  });
24819
25486
  var runGit = (cwd, args) => {
24820
- const result = spawnSync7("git", args, { cwd, encoding: "utf8" });
25487
+ const result = spawnSync8("git", args, { cwd, encoding: "utf8" });
24821
25488
  return {
24822
25489
  stdout: (result.stdout ?? "").trimEnd(),
24823
25490
  stderr: (result.stderr ?? "").trimEnd(),
@@ -24828,11 +25495,11 @@ var repoRoot = (cwd) => {
24828
25495
  const r = runGit(cwd, ["rev-parse", "--show-toplevel"]);
24829
25496
  return r.exitCode === 0 && r.stdout ? r.stdout : cwd;
24830
25497
  };
24831
- var currentBranch = (cwd) => {
25498
+ var currentBranch2 = (cwd) => {
24832
25499
  const r = runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
24833
25500
  return r.exitCode === 0 && r.stdout ? r.stdout : "unknown";
24834
25501
  };
24835
- var defaultBase = (cwd) => {
25502
+ var defaultBase2 = (cwd) => {
24836
25503
  for (const ref of [
24837
25504
  "origin/main",
24838
25505
  "main",
@@ -24846,11 +25513,11 @@ var defaultBase = (cwd) => {
24846
25513
  }
24847
25514
  return "HEAD~1";
24848
25515
  };
24849
- var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${defaultBase(cwd)}...HEAD`;
25516
+ var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${defaultBase2(cwd)}...HEAD`;
24850
25517
  var isProtectedBranch = (branch) => ["main", "master", "develop", "prod", "production"].includes(branch);
24851
25518
  var isPrBranch = (branch) => branch.startsWith("feature/") || branch.startsWith("bugfix/");
24852
25519
  function resolvePrBranchContext(cwd) {
24853
- const branch = currentBranch(cwd);
25520
+ const branch = currentBranch2(cwd);
24854
25521
  if (isProtectedBranch(branch)) {
24855
25522
  return {
24856
25523
  ok: false,
@@ -24924,7 +25591,7 @@ var findPrTemplate = (cwd) => {
24924
25591
  ]) {
24925
25592
  const parts = rel.split("/");
24926
25593
  const base = parts[parts.length - 1];
24927
- const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
25594
+ const dir = parts.length > 1 ? path16.join(cwd, ...parts.slice(0, -1)) : cwd;
24928
25595
  const actual = probeCaseInsensitive(dir, base);
24929
25596
  if (actual)
24930
25597
  return [...parts.slice(0, -1), actual].join("/");
@@ -24955,8 +25622,8 @@ var diffStatForRange = (cwd, range) => {
24955
25622
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
24956
25623
  };
24957
25624
  var packageScripts = (cwd, keys) => {
24958
- const pkgPath = path13.join(cwd, "package.json");
24959
- if (!existsSync5(pkgPath))
25625
+ const pkgPath = path16.join(cwd, "package.json");
25626
+ if (!existsSync6(pkgPath))
24960
25627
  return [];
24961
25628
  try {
24962
25629
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
@@ -24981,9 +25648,9 @@ var documentationFiles = (cwd) => {
24981
25648
  if (entry.isDirectory()) {
24982
25649
  if (excluded.has(entry.name))
24983
25650
  continue;
24984
- walk(path13.join(dir, entry.name), depth + 1);
25651
+ walk(path16.join(dir, entry.name), depth + 1);
24985
25652
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
24986
- matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
25653
+ matches.push("./" + path16.relative(cwd, path16.join(dir, entry.name)).replaceAll("\\", "/"));
24987
25654
  }
24988
25655
  }
24989
25656
  };
@@ -24991,7 +25658,7 @@ var documentationFiles = (cwd) => {
24991
25658
  return matches.toSorted().slice(0, 200);
24992
25659
  };
24993
25660
  var readTrimmed2 = (file, maxLines) => {
24994
- if (!existsSync5(file))
25661
+ if (!existsSync6(file))
24995
25662
  return "";
24996
25663
  const lines = readFileSync5(file, "utf8").split(`
24997
25664
  `);
@@ -24999,7 +25666,7 @@ var readTrimmed2 = (file, maxLines) => {
24999
25666
  `);
25000
25667
  };
25001
25668
  function prReadyContext(root, range) {
25002
- const cwd = path13.resolve(repoRoot(root));
25669
+ const cwd = path16.resolve(repoRoot(root));
25003
25670
  if (range !== undefined && !isResolvableContextRange(cwd, range))
25004
25671
  return invalidRange(cwd);
25005
25672
  let stdout = "";
@@ -25015,7 +25682,7 @@ function prReadyContext(root, range) {
25015
25682
  stdout += printSection("Repository");
25016
25683
  stdout += `root: ${cwd}
25017
25684
  `;
25018
- stdout += `branch: ${currentBranch(cwd)}
25685
+ stdout += `branch: ${currentBranch2(cwd)}
25019
25686
  `;
25020
25687
  stdout += `range: ${range}
25021
25688
  `;
@@ -25057,7 +25724,7 @@ ${status.stderr}` : "");
25057
25724
  stdout += `template_path: ${template}
25058
25725
 
25059
25726
  `;
25060
- stdout += readTrimmed2(path13.join(cwd, template), 220);
25727
+ stdout += readTrimmed2(path16.join(cwd, template), 220);
25061
25728
  } else {
25062
25729
  stdout += `template_path: none
25063
25730
 
@@ -25067,7 +25734,7 @@ ${status.stderr}` : "");
25067
25734
  stdout += `
25068
25735
  `;
25069
25736
  stdout += printSection("Recent Validation Signals");
25070
- if (existsSync5(path13.join(cwd, "package.json"))) {
25737
+ if (existsSync6(path16.join(cwd, "package.json"))) {
25071
25738
  stdout += `package.json detected. Common scripts:
25072
25739
  `;
25073
25740
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -25076,7 +25743,7 @@ ${status.stderr}` : "");
25076
25743
  `) + `
25077
25744
  `;
25078
25745
  }
25079
- if (existsSync5(path13.join(cwd, "Cargo.toml")) || existsSync5(path13.join(cwd, "src-tauri/Cargo.toml"))) {
25746
+ if (existsSync6(path16.join(cwd, "Cargo.toml")) || existsSync6(path16.join(cwd, "src-tauri/Cargo.toml"))) {
25080
25747
  stdout += `Rust project detected.
25081
25748
  `;
25082
25749
  }
@@ -25109,7 +25776,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
25109
25776
  - Apply with the host-native editor, preserving the repository changelog conventions.
25110
25777
  - If Unreleased already has duplicate category headings, normalize_only first.`;
25111
25778
  function changelogContext(root, range) {
25112
- const cwd = path13.resolve(repoRoot(root));
25779
+ const cwd = path16.resolve(repoRoot(root));
25113
25780
  if (range !== undefined && !isResolvableContextRange(cwd, range))
25114
25781
  return invalidRange(cwd);
25115
25782
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -25119,7 +25786,7 @@ function changelogContext(root, range) {
25119
25786
  stdout += printSection("Repository");
25120
25787
  stdout += `root: ${cwd}
25121
25788
  `;
25122
- stdout += `branch: ${currentBranch(cwd)}
25789
+ stdout += `branch: ${currentBranch2(cwd)}
25123
25790
  `;
25124
25791
  stdout += `range: ${resolvedRange}
25125
25792
  `;
@@ -25127,8 +25794,8 @@ function changelogContext(root, range) {
25127
25794
  stdout += CHANGELOG_RULES + `
25128
25795
  `;
25129
25796
  stdout += printSection("Existing CHANGELOG.md");
25130
- const changelogPath = path13.join(cwd, "CHANGELOG.md");
25131
- if (existsSync5(changelogPath)) {
25797
+ const changelogPath = path16.join(cwd, "CHANGELOG.md");
25798
+ if (existsSync6(changelogPath)) {
25132
25799
  stdout += readTrimmed2(changelogPath, 260) + `
25133
25800
  `;
25134
25801
  } else {
@@ -25147,7 +25814,7 @@ function changelogContext(root, range) {
25147
25814
  return { stdout, stderr: "", exitCode: 0, cwd };
25148
25815
  }
25149
25816
  function docsRefreshContext(root, range) {
25150
- const cwd = path13.resolve(repoRoot(root));
25817
+ const cwd = path16.resolve(repoRoot(root));
25151
25818
  if (range !== undefined && !isResolvableContextRange(cwd, range))
25152
25819
  return invalidRange(cwd);
25153
25820
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -25157,7 +25824,7 @@ function docsRefreshContext(root, range) {
25157
25824
  stdout += printSection("Repository");
25158
25825
  stdout += `root: ${cwd}
25159
25826
  `;
25160
- stdout += `branch: ${currentBranch(cwd)}
25827
+ stdout += `branch: ${currentBranch2(cwd)}
25161
25828
  `;
25162
25829
  stdout += `range: ${resolvedRange}
25163
25830
  `;
@@ -25169,8 +25836,8 @@ function docsRefreshContext(root, range) {
25169
25836
  `) + `
25170
25837
  `;
25171
25838
  stdout += printSection("README Preview");
25172
- const readme = path13.join(cwd, "README.md");
25173
- if (existsSync5(readme)) {
25839
+ const readme = path16.join(cwd, "README.md");
25840
+ if (existsSync6(readme)) {
25174
25841
  stdout += readTrimmed2(readme, 220) + `
25175
25842
  `;
25176
25843
  } else {
@@ -25178,8 +25845,8 @@ function docsRefreshContext(root, range) {
25178
25845
  `;
25179
25846
  }
25180
25847
  stdout += printSection("Package Scripts");
25181
- const pkgPath = path13.join(cwd, "package.json");
25182
- if (existsSync5(pkgPath)) {
25848
+ const pkgPath = path16.join(cwd, "package.json");
25849
+ if (existsSync6(pkgPath)) {
25183
25850
  try {
25184
25851
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
25185
25852
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
@@ -25192,7 +25859,7 @@ function docsRefreshContext(root, range) {
25192
25859
  return { stdout, stderr: "", exitCode: 0, cwd };
25193
25860
  }
25194
25861
  function releaseNotesContext(root, rangeOrTag) {
25195
- const cwd = path13.resolve(repoRoot(root));
25862
+ const cwd = path16.resolve(repoRoot(root));
25196
25863
  if (!rangeOrTag) {
25197
25864
  return { stdout: "", stderr: `ERROR: release tag or range required
25198
25865
  `, exitCode: 1, cwd };
@@ -25206,7 +25873,7 @@ function releaseNotesContext(root, rangeOrTag) {
25206
25873
  stdout += printSection("Repository");
25207
25874
  stdout += `root: ${cwd}
25208
25875
  `;
25209
- stdout += `branch: ${currentBranch(cwd)}
25876
+ stdout += `branch: ${currentBranch2(cwd)}
25210
25877
  `;
25211
25878
  stdout += `requested: ${rangeOrTag}
25212
25879
  `;
@@ -25230,7 +25897,7 @@ function releaseNotesContext(root, rangeOrTag) {
25230
25897
  `;
25231
25898
  stdout += printSection("Existing Release Files");
25232
25899
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
25233
- if (existsSync5(path13.join(cwd, rel)))
25900
+ if (existsSync6(path16.join(cwd, rel)))
25234
25901
  stdout += rel + `
25235
25902
  `;
25236
25903
  }
@@ -25258,12 +25925,12 @@ function releaseNotesContext(root, rangeOrTag) {
25258
25925
  }
25259
25926
 
25260
25927
  // packages/workit-core/src/core/tracker-issues.ts
25261
- import { spawnSync as spawnSync9 } from "node:child_process";
25928
+ import { spawnSync as spawnSync10 } from "node:child_process";
25262
25929
 
25263
25930
  // packages/workit-core/src/core/pr-create.ts
25264
- import fs12 from "node:fs";
25265
- import path14 from "node:path";
25266
- import { spawnSync as spawnSync8 } from "node:child_process";
25931
+ import fs13 from "node:fs";
25932
+ import path17 from "node:path";
25933
+ import { spawnSync as spawnSync9 } from "node:child_process";
25267
25934
  // packages/workit-core/src/forge/pr-body.ts
25268
25935
  function parseGhIssue(value) {
25269
25936
  const m = /issues\/(\d+)/.exec(value);
@@ -25273,13 +25940,13 @@ function parseGhIssue(value) {
25273
25940
  // packages/workit-core/src/core/pr-create.ts
25274
25941
  function whichOnPath(tool) {
25275
25942
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
25276
- for (const dir of (process.env.PATH ?? "").split(path14.delimiter)) {
25943
+ for (const dir of (process.env.PATH ?? "").split(path17.delimiter)) {
25277
25944
  if (!dir)
25278
25945
  continue;
25279
25946
  for (const name of names) {
25280
- const candidate = path14.join(dir, name);
25947
+ const candidate = path17.join(dir, name);
25281
25948
  try {
25282
- fs12.accessSync(candidate, fs12.constants.X_OK);
25949
+ fs13.accessSync(candidate, fs13.constants.X_OK);
25283
25950
  return candidate;
25284
25951
  } catch {}
25285
25952
  }
@@ -25288,7 +25955,7 @@ function whichOnPath(tool) {
25288
25955
  }
25289
25956
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
25290
25957
  var pushRemote = (cwd) => {
25291
- const result = spawnSync8("git", ["remote", "get-url", "--push", "--all", "origin"], {
25958
+ const result = spawnSync9("git", ["remote", "get-url", "--push", "--all", "origin"], {
25292
25959
  cwd,
25293
25960
  encoding: "utf8"
25294
25961
  });
@@ -25319,7 +25986,7 @@ var defaultCli = (bin, available) => {
25319
25986
  if (!available)
25320
25987
  return null;
25321
25988
  return (args, root) => {
25322
- const out = spawnSync9(bin, args, {
25989
+ const out = spawnSync10(bin, args, {
25323
25990
  cwd: root,
25324
25991
  encoding: "utf8",
25325
25992
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -25397,8 +26064,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
25397
26064
  };
25398
26065
 
25399
26066
  // packages/workit-core/src/core/youtrack.ts
25400
- import fs13 from "node:fs";
25401
- import path15 from "node:path";
26067
+ import fs14 from "node:fs";
26068
+ import path18 from "node:path";
25402
26069
 
25403
26070
  // packages/workit-core/src/core/templates.ts
25404
26071
  var repoRoot2 = assetRoot();
@@ -25411,22 +26078,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
25411
26078
  // packages/workit-core/src/core/youtrack.ts
25412
26079
  var ISSUE_RE = /^[A-Z]+-\d+$/;
25413
26080
  var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
25414
- var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path15.join(configDir(), "youtrack.json");
25415
- var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path15.join(resolveConfigDir(), "youtrack.json");
26081
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(configDir(), "youtrack.json");
26082
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(resolveConfigDir(), "youtrack.json");
25416
26083
  var youTrackTokenModeOk = (p) => {
25417
26084
  if (process.platform === "win32")
25418
26085
  return true;
25419
- const mode = fs13.statSync(p).mode & 511;
26086
+ const mode = fs14.statSync(p).mode & 511;
25420
26087
  return mode === 384;
25421
26088
  };
25422
26089
  function readYouTrackConfig(required) {
25423
26090
  const cfgPath = youTrackConfigPath();
25424
- if (!fs13.existsSync(cfgPath)) {
26091
+ if (!fs14.existsSync(cfgPath)) {
25425
26092
  return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
25426
26093
  }
25427
26094
  let parsed;
25428
26095
  try {
25429
- parsed = JSON.parse(fs13.readFileSync(cfgPath, "utf8"));
26096
+ parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
25430
26097
  } catch {
25431
26098
  return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
25432
26099
  }
@@ -25437,11 +26104,11 @@ function readYouTrackConfig(required) {
25437
26104
  }
25438
26105
  var readYouTrackContextConfig = () => {
25439
26106
  const cfgPath = youTrackReadOnlyConfigPath();
25440
- if (!fs13.existsSync(cfgPath))
26107
+ if (!fs14.existsSync(cfgPath))
25441
26108
  return { data: {} };
25442
26109
  let parsed;
25443
26110
  try {
25444
- parsed = JSON.parse(fs13.readFileSync(cfgPath, "utf8"));
26111
+ parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
25445
26112
  } catch {
25446
26113
  return { error: "YouTrack configuration is unavailable" };
25447
26114
  }
@@ -25480,12 +26147,12 @@ var youTrackToken = () => {
25480
26147
  if ("error" in loaded)
25481
26148
  return loaded;
25482
26149
  const tokenFile = String(loaded.config.tokenFile ?? "");
25483
- const tokenPath = tokenFile ? path15.isAbsolute(tokenFile) ? path15.resolve(tokenFile) : path15.resolve(process.cwd(), tokenFile) : "";
25484
- if (!tokenPath || !fs13.existsSync(tokenPath))
26150
+ const tokenPath = tokenFile ? path18.isAbsolute(tokenFile) ? path18.resolve(tokenFile) : path18.resolve(process.cwd(), tokenFile) : "";
26151
+ if (!tokenPath || !fs14.existsSync(tokenPath))
25485
26152
  return { error: "missing youtrack.token" };
25486
26153
  if (!youTrackTokenModeOk(tokenPath))
25487
26154
  return { error: "youtrack.token mode must be 0600" };
25488
- const token = fs13.readFileSync(tokenPath, "utf8").trim();
26155
+ const token = fs14.readFileSync(tokenPath, "utf8").trim();
25489
26156
  if (!token)
25490
26157
  return { error: "empty token file" };
25491
26158
  if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
@@ -25550,9 +26217,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
25550
26217
  } catch {
25551
26218
  continue;
25552
26219
  }
25553
- if (!fs13.existsSync(full))
26220
+ if (!fs14.existsSync(full))
25554
26221
  continue;
25555
- const text = fs13.readFileSync(full, "utf8");
26222
+ const text = fs14.readFileSync(full, "utf8");
25556
26223
  const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
25557
26224
  if (m)
25558
26225
  return m[1];
@@ -25822,22 +26489,22 @@ function resolveGitRevision(root, value) {
25822
26489
  }
25823
26490
  function resolveInside(root, candidate) {
25824
26491
  const base = realpathSync5(root);
25825
- const target = path16.resolve(base, candidate);
25826
- if (target !== base && !target.startsWith(base + path16.sep)) {
26492
+ const target = path19.resolve(base, candidate);
26493
+ if (target !== base && !target.startsWith(base + path19.sep)) {
25827
26494
  throw new Error("path must stay inside repository root");
25828
26495
  }
25829
26496
  let ancestor = target;
25830
- while (!existsSync6(ancestor))
25831
- ancestor = path16.dirname(ancestor);
26497
+ while (!existsSync7(ancestor))
26498
+ ancestor = path19.dirname(ancestor);
25832
26499
  const canonicalAncestor = realpathSync5(ancestor);
25833
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path16.sep)) {
26500
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path19.sep)) {
25834
26501
  throw new Error("path must stay inside repository root");
25835
26502
  }
25836
26503
  return target;
25837
26504
  }
25838
26505
  function run2(root, executable, args, env = {}) {
25839
26506
  const cwd = realpathSync5(root);
25840
- const result = spawnSync10(executable, args, {
26507
+ const result = spawnSync11(executable, args, {
25841
26508
  cwd,
25842
26509
  encoding: "utf8",
25843
26510
  env: { ...process.env, ...env }
@@ -25851,7 +26518,7 @@ function run2(root, executable, args, env = {}) {
25851
26518
  }
25852
26519
 
25853
26520
  // packages/workit-core/src/core/logger.ts
25854
- import os3 from "node:os";
26521
+ import os4 from "node:os";
25855
26522
  var REDACTED = "[REDACTED]";
25856
26523
  var SECRET_VALUE = /\b(?:Bearer|Basic|Digest|Token)\s+\S+/gi;
25857
26524
  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;
@@ -25894,7 +26561,7 @@ var SENSITIVE_WORDS = new Set([
25894
26561
  ]);
25895
26562
  var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
25896
26563
  var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25897
- var homePattern = new RegExp(`${escapeRegExp(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
26564
+ var homePattern = new RegExp(`${escapeRegExp(os4.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
25898
26565
  var applyPatterns = (value) => {
25899
26566
  let out = value;
25900
26567
  out = out.replace(SECRET_VALUE, REDACTED);
@@ -25945,13 +26612,13 @@ var rootVariants = (workspaceRoot) => {
25945
26612
  if (!workspaceRoot)
25946
26613
  return [];
25947
26614
  const roots = new Set;
25948
- for (const candidate of [workspaceRoot, path17.resolve(workspaceRoot)]) {
25949
- if (path17.isAbsolute(candidate) && candidate !== path17.parse(candidate).root)
26615
+ for (const candidate of [workspaceRoot, path20.resolve(workspaceRoot)]) {
26616
+ if (path20.isAbsolute(candidate) && candidate !== path20.parse(candidate).root)
25950
26617
  roots.add(candidate);
25951
26618
  }
25952
26619
  try {
25953
26620
  const canonical = realpathSync6(workspaceRoot);
25954
- if (canonical !== path17.parse(canonical).root)
26621
+ if (canonical !== path20.parse(canonical).root)
25955
26622
  roots.add(canonical);
25956
26623
  } catch {}
25957
26624
  return [...roots].toSorted((left, right) => right.length - left.length);
@@ -25973,39 +26640,20 @@ function assertMcpHost(host) {
25973
26640
  throw new Error("MCP host must be cursor, codex_cli, or codex_desktop");
25974
26641
  }
25975
26642
  }
25976
- var operationDescription = (family) => `Workit ${family} operations. Inputs are validated by the shared Workit contract.`;
25977
- var READ_ONLY_ACTIONS = new Set(["list", "inspect", "preview", "explain", "export"]);
25978
- var requiresCallerIdentity = (input) => typeof input !== "object" || input === null || !READ_ONLY_ACTIONS.has(String(input.action));
25979
- var toolInputSchema = (family) => {
25980
- const schema = boundedOperationJsonSchema(family);
25981
- return { type: "object", ...schema };
25982
- };
25983
- var schemaBranches = (schema) => Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : [schema];
25984
- var readOnlyBranches = (family) => schemaBranches(boundedOperationJsonSchema(family)).filter((branch) => {
25985
- const action = branch.properties?.action?.const;
25986
- return typeof action === "string" && READ_ONLY_ACTIONS.has(action);
25987
- });
25988
- var advertisedToolSchemas = (attested) => {
25989
- if (attested)
25990
- return OPERATION_FAMILIES.map((family) => ({
26643
+ var READ_ONLY_ACTIONS2 = new Set(["list", "inspect", "preview", "explain", "export"]);
26644
+ var requiresCallerIdentity = (input) => typeof input !== "object" || input === null || !READ_ONLY_ACTIONS2.has(String(input.action));
26645
+ var advertisedToolSchemas = (attested) => OPERATION_FAMILIES.flatMap((family) => {
26646
+ const schema = flatOperationJsonSchema(family, { readOnly: !attested });
26647
+ if (!schema)
26648
+ return [];
26649
+ return [
26650
+ {
25991
26651
  name: `workit_${family}`,
25992
- description: operationDescription(family),
25993
- inputSchema: toolInputSchema(family)
25994
- }));
25995
- return OPERATION_FAMILIES.flatMap((family) => {
25996
- const branches = readOnlyBranches(family);
25997
- if (!branches.length)
25998
- return [];
25999
- const schema = boundedOperationJsonSchema(family);
26000
- return [
26001
- {
26002
- name: `workit_${family}`,
26003
- description: `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
26004
- inputSchema: { type: "object", ...schema, oneOf: branches }
26005
- }
26006
- ];
26007
- });
26008
- };
26652
+ description: attested ? operationDescription(family) : `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
26653
+ inputSchema: schema
26654
+ }
26655
+ ];
26656
+ });
26009
26657
  var sanitizeFailure = (result, workspaceRoot) => {
26010
26658
  if (result.ok)
26011
26659
  return result;
@@ -26052,12 +26700,12 @@ var thrownResult = (tool, error, workspaceRoot) => {
26052
26700
  };
26053
26701
  function createMcpServer(host, contextProvider) {
26054
26702
  assertMcpHost(host);
26055
- const here = path17.dirname(fileURLToPath2(import.meta.url));
26703
+ const here = path20.dirname(fileURLToPath2(import.meta.url));
26056
26704
  const sourceMarker = markSourcesLoaded([
26057
- path17.join(here, "server.ts"),
26058
- path17.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
26059
- path17.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
26060
- ].filter((file) => existsSync7(file)));
26705
+ path20.join(here, "server.ts"),
26706
+ path20.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
26707
+ path20.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
26708
+ ].filter((file) => existsSync8(file)));
26061
26709
  let staleWarned = false;
26062
26710
  const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
26063
26711
  server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -26181,7 +26829,7 @@ function createMcpServer(host, contextProvider) {
26181
26829
  details: { operation: family }
26182
26830
  }, workspaceRoot);
26183
26831
  }
26184
- const parsed = parseOperation(family, request.params.arguments);
26832
+ const parsed = parseAdvertisedOperation(family, request.params.arguments, host);
26185
26833
  if (!parsed.ok)
26186
26834
  return resultForClient(parsed, workspaceRoot);
26187
26835
  if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
@@ -26236,8 +26884,8 @@ var main = async () => {
26236
26884
  process.exitCode = 2;
26237
26885
  }
26238
26886
  };
26239
- var entryPath = process.argv[1] ? path18.resolve(process.argv[1]) : "";
26240
- if (entryPath === path18.resolve(fileURLToPath3(import.meta.url)))
26887
+ var entryPath = process.argv[1] ? path21.resolve(process.argv[1]) : "";
26888
+ if (entryPath === path21.resolve(fileURLToPath3(import.meta.url)))
26241
26889
  await main();
26242
26890
  // packages/workit-core/src/hooks/descriptor.ts
26243
26891
  var support = (descriptor, axis) => {
@@ -26266,12 +26914,34 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
26266
26914
  // packages/workit-core/src/hooks/context.ts
26267
26915
  var signals = new Map;
26268
26916
  var cache = new Map;
26917
+ // packages/workit-core/src/hooks/write-gate.ts
26918
+ var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
26919
+ var PREFIXES = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
26920
+ var cache2 = new Map;
26921
+ // packages/workit-core/src/hooks/hosts/fields.ts
26922
+ var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
26923
+ var WRITE_TOOLS = new Set([
26924
+ "write",
26925
+ "edit",
26926
+ "multiedit",
26927
+ "notebookedit",
26928
+ "applypatch",
26929
+ "patch",
26930
+ "delete",
26931
+ "remove",
26932
+ "rename",
26933
+ "mkdir",
26934
+ "mv",
26935
+ "cp",
26936
+ "touch",
26937
+ "strreplace",
26938
+ "searchreplace",
26939
+ "editfile",
26940
+ "writefile"
26941
+ ]);
26269
26942
  // packages/workit-core/src/hooks/handle.ts
26270
26943
  var offered = new Set;
26271
26944
  var CLAUDE_JUDGES = new Set(["workit:verifier", "workit:reviewer"]);
26272
- // packages/workit-core/src/hooks/hosts/fields.ts
26273
- var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
26274
-
26275
26945
  // packages/workit-core/src/hooks/hosts/claude-code.ts
26276
26946
  var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
26277
26947
  var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
@@ -26295,6 +26965,7 @@ var CURSOR_DESCRIPTOR = {
26295
26965
  "context.turn": none,
26296
26966
  "shell.pre": { support: "native", native: "beforeShellExecution" },
26297
26967
  "tool.pre": { support: "native", native: "preToolUse" },
26968
+ "write.pre": { support: "partial", native: "preToolUse" },
26298
26969
  "shell.post": undocumented,
26299
26970
  "subagent.start": { support: "native", native: "subagentStart" },
26300
26971
  "subagent.stop": { support: "partial", native: "subagentStop" },