@brainervirus/workit-mcp 6.0.1 → 7.0.0

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