@brainervirus/workit-mcp 6.0.1 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 spawnSync12 } 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,677 @@ 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)
21263
+ function mergeBase(cwd, a, b) {
21264
+ if (!safeArg(a) || !safeArg(b))
21225
21265
  return null;
21226
- if (value.includes("\\") || value.split("/").includes(".."))
21266
+ return line(git2(cwd, ["merge-base", a, b]));
21267
+ }
21268
+ function patchId(cwd, base, head) {
21269
+ if (!safeArg(base) || !safeArg(head))
21227
21270
  return null;
21228
- const normalized = path9.posix.normalize(value);
21229
- if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
21271
+ const diff = git2(cwd, [
21272
+ "diff",
21273
+ "-U3",
21274
+ "--diff-algorithm=myers",
21275
+ "--indent-heuristic",
21276
+ "--no-color",
21277
+ "--no-ext-diff",
21278
+ "--no-textconv",
21279
+ "--no-renames",
21280
+ "--full-index",
21281
+ "--src-prefix=a/",
21282
+ "--dst-prefix=b/",
21283
+ `${base}...${head}`,
21284
+ "--"
21285
+ ]);
21286
+ if (!diff.ok || !diff.stdout.trim())
21230
21287
  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))
21288
+ const id = git2(cwd, ["patch-id", "--stable"], { input: diff.stdout });
21289
+ return line(id)?.split(/\s+/u)[0] ?? null;
21290
+ }
21291
+ function remoteNames(cwd) {
21292
+ return (git2(cwd, ["remote"]).stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
21293
+ }
21294
+ function resolveRef(cwd, ref) {
21295
+ if (!safeArg(ref))
21238
21296
  return null;
21297
+ return line(git2(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
21298
+ }
21299
+ var config2 = (cwd, key) => line(git2(cwd, ["config", "--get", key]));
21300
+ function currentBranch(cwd) {
21301
+ return line(git2(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
21302
+ }
21303
+ function pushRemoteName(cwd, branch) {
21304
+ const name = branch ?? currentBranch(cwd);
21305
+ const remotes = remoteNames(cwd);
21306
+ const candidates = [
21307
+ name ? config2(cwd, `branch.${name}.pushRemote`) : null,
21308
+ config2(cwd, "remote.pushDefault"),
21309
+ name ? config2(cwd, `branch.${name}.remote`) : null
21310
+ ];
21311
+ for (const candidate of candidates)
21312
+ if (candidate && candidate !== "." && remotes.includes(candidate))
21313
+ return candidate;
21314
+ if (remotes.includes("origin"))
21315
+ return "origin";
21316
+ return remotes.length === 1 ? remotes[0] : null;
21317
+ }
21318
+
21319
+ // packages/workit-core/src/core/policy/derive.ts
21320
+ import { existsSync as existsSync3 } from "node:fs";
21321
+ import path10 from "node:path";
21322
+
21323
+ // packages/workit-core/src/ledger.ts
21324
+ import { spawnSync as spawnSync5 } from "node:child_process";
21325
+ import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
21326
+ import fs10 from "node:fs";
21327
+ import path9 from "node:path";
21328
+ var LEDGER_VERSION = 1;
21329
+ var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
21330
+ var FAILING = new Set(["failed", "blocked"]);
21331
+ var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
21332
+ var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
21333
+ var err = (code, error, unblock) => ({
21334
+ ok: false,
21335
+ code,
21336
+ error,
21337
+ ...unblock ? { unblock } : {}
21338
+ });
21339
+ var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
21340
+ const run = spawnSync5("git", args, {
21341
+ cwd,
21342
+ encoding: "utf8",
21343
+ timeout: timeoutMs,
21344
+ killSignal: "SIGKILL",
21345
+ windowsHide: true,
21346
+ maxBuffer: 256 * 1024 * 1024,
21347
+ env: {
21348
+ ...process.env,
21349
+ GIT_TERMINAL_PROMPT: "0",
21350
+ GIT_OPTIONAL_LOCKS: "0",
21351
+ LC_ALL: "C",
21352
+ LANGUAGE: "C"
21353
+ }
21354
+ });
21239
21355
  return {
21240
- description: scope.description,
21241
- paths: [...new Set(normalizedPaths)],
21242
- exclusions: [...new Set(normalizedExclusions)]
21356
+ ok: run.status === 0,
21357
+ stdout: run.stdout ?? "",
21358
+ stderr: run.error ? String(run.error.message) : run.stderr ?? ""
21243
21359
  };
21244
21360
  };
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);
21361
+ var git3 = (cwd, args) => {
21362
+ const run = gitRun2(cwd, args);
21363
+ const value = run.ok ? run.stdout.trim() : "";
21364
+ return value ? value : null;
21252
21365
  };
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;
21366
+ var safeRef = (value) => value.length > 0 && !value.startsWith("-");
21367
+ function resolveCommit(cwd, ref) {
21368
+ if (!safeRef(ref))
21369
+ return null;
21370
+ return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
21371
+ }
21372
+ function branchHead(cwd, branch) {
21373
+ if (!safeRef(branch))
21374
+ return null;
21375
+ return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
21376
+ }
21377
+ function defaultBase(cwd) {
21378
+ const originHead = git3(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
21379
+ if (originHead && resolveCommit(cwd, originHead))
21380
+ return originHead;
21381
+ for (const candidate of ["origin/main", "origin/master", "main", "master"])
21382
+ if (resolveCommit(cwd, candidate))
21383
+ return candidate;
21384
+ return null;
21385
+ }
21386
+ function diffHash(cwd, base, head) {
21387
+ if (!safeRef(base) || !safeRef(head))
21388
+ return null;
21389
+ const diff = gitRun2(cwd, [
21390
+ "diff",
21391
+ "-U3",
21392
+ "--diff-algorithm=myers",
21393
+ "--indent-heuristic",
21394
+ "--no-color",
21395
+ "--no-ext-diff",
21396
+ "--no-textconv",
21397
+ "--no-renames",
21398
+ "--full-index",
21399
+ "--binary",
21400
+ "--src-prefix=a/",
21401
+ "--dst-prefix=b/",
21402
+ `${base}...${head}`,
21403
+ "--"
21404
+ ], GIT_TIMEOUTS.worktree);
21405
+ if (!diff.ok || !diff.stdout)
21406
+ return null;
21407
+ const hash = createHash6("sha256");
21408
+ for (const line of diff.stdout.split(`
21409
+ `)) {
21410
+ if (line.startsWith("index "))
21411
+ continue;
21412
+ hash.update(line.startsWith("@@ ") ? "@@" : line);
21413
+ hash.update(`
21414
+ `);
21260
21415
  }
21261
- return left.length - right.length;
21416
+ return `sha256:${hash.digest("hex")}`;
21417
+ }
21418
+ function storeRoot(cwd) {
21419
+ const location = resolveStore(path9.resolve(cwd));
21420
+ if (location instanceof Error)
21421
+ 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");
21422
+ return { ok: true, value: { root: location.dir, shared: location.shared } };
21423
+ }
21424
+ function ledgerPath(cwd) {
21425
+ const root = storeRoot(cwd);
21426
+ return root.ok ? { ok: true, value: path9.join(root.value.root, "ledger", "ledger.jsonl") } : root;
21427
+ }
21428
+ var NETWORK_FS_TYPES = new Set([
21429
+ 26985,
21430
+ 20859,
21431
+ 4266872130,
21432
+ 4283649346,
21433
+ 1702057286,
21434
+ 1397113167,
21435
+ 12805120,
21436
+ 16914839,
21437
+ 1196443219,
21438
+ 198183888
21439
+ ]);
21440
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21441
+ var str2 = (value) => typeof value === "string" ? value : null;
21442
+ var normalizeActor = (value) => {
21443
+ const actor = isRecord2(value) ? value : {};
21444
+ return {
21445
+ host: str2(actor.host) ?? "unknown",
21446
+ session: str2(actor.session),
21447
+ agentId: str2(actor.agentId)
21448
+ };
21262
21449
  };
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: [],
21272
- uncertain: !/not a git repository/i.test(stderr),
21273
- git: !/not a git repository/i.test(stderr)
21274
- };
21450
+ function supersedeAllowed(by, target) {
21451
+ if (by.type !== target.type)
21452
+ return `a ${by.type} row cannot supersede a ${target.type} row`;
21453
+ if (!by.actor.session || by.actor.session !== target.actor.session)
21454
+ return "only the session that wrote a row can supersede it";
21455
+ if (by.type === "verdict" && by.self === true && target.self !== true)
21456
+ return "a self verdict cannot replace an independent verdict";
21457
+ return null;
21458
+ }
21459
+ function readLedger(cwd) {
21460
+ const file = ledgerPath(cwd);
21461
+ if (!file.ok)
21462
+ return file;
21463
+ let text = "";
21464
+ try {
21465
+ text = fs10.readFileSync(file.value, "utf8");
21466
+ } catch (error) {
21467
+ if (error.code === "ENOENT")
21468
+ return { ok: true, value: { path: file.value, rows: [], skipped: 0 } };
21469
+ return err("unavailable", `cannot read ${file.value}: ${error.message}`);
21470
+ }
21471
+ const rows = [];
21472
+ let skipped = 0;
21473
+ for (const raw of text.split(`
21474
+ `)) {
21475
+ const trimmed = raw.trim();
21476
+ if (!trimmed)
21477
+ continue;
21478
+ let parsed;
21479
+ try {
21480
+ parsed = JSON.parse(trimmed);
21481
+ } catch {
21482
+ skipped += 1;
21483
+ continue;
21484
+ }
21485
+ if (!isRecord2(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
21486
+ skipped += 1;
21487
+ continue;
21488
+ }
21489
+ const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
21490
+ rows.push({
21491
+ ...parsed,
21492
+ v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
21493
+ id: parsed.id,
21494
+ at: parsed.at,
21495
+ type: parsed.type,
21496
+ actor: normalizeActor(parsed.actor),
21497
+ branch: str2(parsed.branch),
21498
+ head: str2(parsed.head),
21499
+ base: str2(parsed.base),
21500
+ baseSha: str2(parsed.baseSha),
21501
+ patchId: str2(parsed.patchId),
21502
+ diffHash: str2(parsed.diffHash),
21503
+ tree: str2(parsed.tree),
21504
+ dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
21505
+ ...pr === undefined ? {} : { pr },
21506
+ ...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
21507
+ seq: rows.length + 1,
21508
+ superseded: false,
21509
+ supersedeIgnored: false
21510
+ });
21511
+ }
21512
+ const byId = new Map;
21513
+ for (const row of rows) {
21514
+ const target = row.supersedes ? byId.get(row.supersedes) : undefined;
21515
+ if (target && supersedeAllowed(row, target) === null)
21516
+ target.superseded = true;
21517
+ else if (row.supersedes)
21518
+ row.supersedeIgnored = true;
21519
+ if (!byId.has(row.id))
21520
+ byId.set(row.id, row);
21521
+ }
21522
+ return { ok: true, value: { path: file.value, rows, skipped } };
21523
+ }
21524
+ function verdictBasis(verdict, current) {
21525
+ if (verdict.head && verdict.head === current.head)
21526
+ return "fresh";
21527
+ if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
21528
+ return "carried";
21529
+ return "stale";
21530
+ }
21531
+ var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded;
21532
+ function trailerSessions(cwd, base, head) {
21533
+ if (!base || !head || !safeRef(base) || !safeRef(head))
21534
+ return [];
21535
+ const out = git3(cwd, [
21536
+ "log",
21537
+ "--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
21538
+ `${base}..${head}`
21539
+ ]);
21540
+ return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
21541
+ }
21542
+ function authorSessions(cwd, rows, branch, range = { base: null, head: null }) {
21543
+ const sessions = new Set;
21544
+ for (const row of rows) {
21545
+ if (row.branch !== branch || row.type !== "commit.recorded" && row.type !== "task.opened")
21546
+ continue;
21547
+ const session = str2(row.session) ?? row.actor.session;
21548
+ if (session)
21549
+ sessions.add(session);
21550
+ }
21551
+ for (const session of trailerSessions(cwd, range.base, range.head))
21552
+ sessions.add(session);
21553
+ return sessions;
21554
+ }
21555
+ var independenceReasons = (row, authors) => {
21556
+ const reasons = [];
21557
+ if (!row.actor.session)
21558
+ reasons.push("no_session");
21559
+ else if (authors.has(row.actor.session))
21560
+ reasons.push("author_session");
21561
+ if (row.self === true && !reasons.includes("no_session"))
21562
+ reasons.push("self");
21563
+ return reasons;
21564
+ };
21565
+ function effectiveOf(rows, authors) {
21566
+ let effective = null;
21567
+ for (const row of rows) {
21568
+ if (!effective) {
21569
+ effective = row;
21570
+ continue;
21571
+ }
21572
+ const rowIndependent = independenceReasons(row, authors).length === 0;
21573
+ const effIndependent = independenceReasons(effective, authors).length === 0;
21574
+ if (effIndependent && !rowIndependent)
21575
+ continue;
21576
+ if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session)
21577
+ continue;
21578
+ effective = row;
21579
+ }
21580
+ return effective;
21581
+ }
21582
+ function restackCarries(rows, branch, from, to) {
21583
+ if (!from || !to)
21584
+ return false;
21585
+ const edges = new Map;
21586
+ for (const row of rows)
21587
+ if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
21588
+ edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
21589
+ const seen = new Set([from]);
21590
+ const queue = [from];
21591
+ while (queue.length) {
21592
+ const next = queue.shift();
21593
+ if (next === to)
21594
+ return true;
21595
+ for (const target of edges.get(next) ?? [])
21596
+ if (!seen.has(target)) {
21597
+ seen.add(target);
21598
+ queue.push(target);
21599
+ }
21600
+ }
21601
+ return false;
21602
+ }
21603
+ function checkVerdicts(cwd, branch, rows) {
21604
+ const head = branchHead(cwd, branch);
21605
+ const fallbackBase = defaultBase(cwd);
21606
+ const keyCache = new Map;
21607
+ const keyFor = (base) => {
21608
+ if (!base || !head)
21609
+ return { patchId: null, diffHash: null };
21610
+ let key = keyCache.get(base);
21611
+ if (!key) {
21612
+ key = { patchId: patchId(cwd, base, head), diffHash: diffHash(cwd, base, head) };
21613
+ keyCache.set(base, key);
21614
+ }
21615
+ return key;
21616
+ };
21617
+ const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head });
21618
+ const byKind = new Map;
21619
+ for (const row of rows)
21620
+ if (row.branch === branch && isLiveVerdict(row)) {
21621
+ const kind = typeof row.kind === "string" ? row.kind : "review";
21622
+ byKind.set(kind, [...byKind.get(kind) ?? [], row]);
21623
+ }
21624
+ const verdicts = [];
21625
+ for (const [kind, list] of byKind) {
21626
+ const row = effectiveOf(list, authors);
21627
+ if (!row)
21628
+ continue;
21629
+ let basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
21630
+ if (basis === "stale" && restackCarries(rows, branch, row.head, head))
21631
+ basis = "carried";
21632
+ const current = basis !== "stale";
21633
+ const independence = independenceReasons(row, authors);
21634
+ const reasons = [
21635
+ ...current ? [] : ["stale"],
21636
+ ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
21637
+ ...independence
21638
+ ];
21639
+ verdicts.push({
21640
+ kind,
21641
+ basis,
21642
+ current,
21643
+ independent: independence.length === 0,
21644
+ accepted: reasons.length === 0,
21645
+ reasons,
21646
+ verdict: row
21647
+ });
21648
+ }
21649
+ verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
21650
+ const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
21651
+ const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
21652
+ const newestAccepted = verdicts.findLast((entry) => entry.accepted) ?? null;
21653
+ const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
21654
+ accepted: false,
21655
+ verdict: failing?.verdict ?? null,
21656
+ reasons: failing ? ["failing_verdict"] : newestCurrent?.reasons ?? ["no_verdict"]
21657
+ };
21658
+ const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
21659
+ return {
21660
+ branch,
21661
+ head,
21662
+ review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
21663
+ selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
21664
+ base: newestCurrent?.verdict.base ?? fallbackBase,
21665
+ patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
21666
+ current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
21667
+ accepted,
21668
+ verdicts,
21669
+ authors: [...authors].toSorted()
21670
+ };
21671
+ }
21672
+ var OBSERVED_ONLY = new Set(["check", ...PR_ROW_TYPES]);
21673
+
21674
+ // packages/workit-core/src/core/policy/derive.ts
21675
+ var RULES = {
21676
+ test: "check:test",
21677
+ self: "verdict:self",
21678
+ nonAuthor: "verdict:non-author",
21679
+ verified: "verdict:verified",
21680
+ product: "decision:product",
21681
+ plan: "plan"
21682
+ };
21683
+ var derived2 = (spec) => ({
21684
+ ...spec,
21685
+ id: requirementId({
21686
+ ruleId: spec.ruleId,
21687
+ scope: spec.scope,
21688
+ satisfaction: spec.satisfaction,
21689
+ before: spec.before,
21690
+ dependentAction: spec.dependentAction
21691
+ })
21692
+ });
21693
+ var sorted = (values) => [...new Set(values)].toSorted();
21694
+ var normalizedScope = (scope) => ({
21695
+ description: scope.description,
21696
+ paths: sorted(scope.paths),
21697
+ exclusions: sorted(scope.exclusions)
21698
+ });
21699
+ function deriveRequirements(judgment, scope, constraints, verification = "self") {
21700
+ const out = [];
21701
+ const base = { scope, dependentAction: null };
21702
+ if (judgment.behaviorChange)
21703
+ out.push(derived2({
21704
+ ...base,
21705
+ ruleId: RULES.test,
21706
+ dimension: "testing",
21707
+ reason: "Behavior changes: the configured test check must pass on the final tree.",
21708
+ satisfaction: "Run `workit check test` on the final tree.",
21709
+ before: "close",
21710
+ acceptanceAllowed: false
21711
+ }));
21712
+ if (judgment.behaviorChange && judgment.riskTier === "normal")
21713
+ out.push(verification === "independent" ? derived2({
21714
+ ...base,
21715
+ ruleId: RULES.nonAuthor,
21716
+ dimension: "review",
21717
+ reason: "This workspace requires independent verification: a session that did not author the change records the verdict.",
21718
+ satisfaction: "An independent session records a passing verdict: `workit ledger verdict verified --how <what it exercised>`.",
21719
+ before: "close",
21720
+ acceptanceAllowed: false
21721
+ }) : derived2({
21722
+ ...base,
21723
+ ruleId: RULES.self,
21724
+ dimension: "review",
21725
+ reason: "A behavior change needs a recorded verdict; the author's own is allowed and is shown as self-reviewed, never verified.",
21726
+ 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).",
21727
+ before: "close",
21728
+ acceptanceAllowed: false
21729
+ }));
21730
+ if (judgment.riskTier === "high")
21731
+ out.push(derived2({
21732
+ ...base,
21733
+ ruleId: RULES.verified,
21734
+ dimension: "review",
21735
+ reason: "High risk: an independent verifier must exercise the live surface.",
21736
+ satisfaction: "An independent verifier runs the real surface and records `workit ledger verdict verified --kind live --how <what it exercised>`.",
21737
+ before: "close",
21738
+ acceptanceAllowed: false
21739
+ }));
21740
+ if (judgment.productChoiceOpen)
21741
+ out.push(derived2({
21742
+ ...base,
21743
+ ruleId: RULES.product,
21744
+ dimension: "decisions",
21745
+ reason: "A product or preference choice is open; the user decides it before code is written.",
21746
+ satisfaction: 'Ask the user (with your recommendation), then record the answer: `workit ledger decision "<choice>" --why "<reason>"`.',
21747
+ before: "write",
21748
+ acceptanceAllowed: false
21749
+ }));
21750
+ if (judgment.needsPlan || judgment.riskTier === "high")
21751
+ out.push(derived2({
21752
+ ...base,
21753
+ ruleId: RULES.plan,
21754
+ dimension: "artifacts",
21755
+ reason: judgment.needsPlan ? "The work needs a written plan or spec before implementation." : "High-risk work needs a written plan before implementation.",
21756
+ 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>`.",
21757
+ before: "write",
21758
+ acceptanceAllowed: true
21759
+ }));
21760
+ for (const constraint of constraints)
21761
+ for (const obligation of constraint.requires)
21762
+ out.push(derived2({
21763
+ ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
21764
+ kind: obligation.kind,
21765
+ scope: normalizedScope(obligation.scope),
21766
+ refs: obligation.refs.map((ref) => JSON.stringify(ref)).toSorted(),
21767
+ method: obligation.method,
21768
+ before: obligation.before,
21769
+ dependentAction: obligation.dependentAction
21770
+ })}`,
21771
+ dimension: obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge"),
21772
+ scope: obligation.scope,
21773
+ reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
21774
+ satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
21775
+ before: obligation.before,
21776
+ dependentAction: obligation.dependentAction,
21777
+ acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
21778
+ }));
21779
+ const seen = new Set;
21780
+ return out.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true));
21781
+ }
21782
+ var latestJudgment = (task) => task.judgments?.at(-1)?.data ?? null;
21783
+ var ledgerView = (root) => {
21784
+ let branch;
21785
+ let rows;
21786
+ return {
21787
+ branch: () => branch === undefined ? branch = currentBranch(root) : branch,
21788
+ rows: () => {
21789
+ if (rows === undefined) {
21790
+ const read = readLedger(root);
21791
+ rows = read.ok ? read.value.rows : [];
21792
+ }
21793
+ return rows;
21794
+ }
21795
+ };
21796
+ };
21797
+ var productDecision = (task, ledger) => {
21798
+ const decisions = task.decisions.filter((entry) => entry.data.revoked === null && (entry.data.response === "approved" || entry.data.response === "stated") && entry.data.purpose !== "limitation");
21799
+ if (decisions.length)
21800
+ return {
21801
+ met: true,
21802
+ reason: "the user's decision is recorded on the task",
21803
+ decisionIds: decisions.map((entry) => entry.id)
21804
+ };
21805
+ const since = Date.parse(task.createdAt);
21806
+ const branch = ledger.branch();
21807
+ const row = ledger.rows().findLast((item) => item.type === "decision" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
21808
+ return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
21809
+ met: false,
21810
+ reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or re-judge productChoiceOpen=false if it is not open)',
21811
+ decisionIds: []
21812
+ };
21813
+ };
21814
+ var planRecorded = (task, root) => {
21815
+ const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync3(path10.resolve(root, ref.path)));
21816
+ if (cited)
21817
+ return {
21818
+ met: true,
21819
+ reason: `plan recorded: ${cited.kind === "file" ? cited.path : cited.kind === "external" ? cited.url : ""}`,
21820
+ decisionIds: []
21821
+ };
21822
+ const artifact = task.evidence.some((entry) => entry.data.kind === "artifact" && entry.data.result === "passed");
21823
+ return artifact ? { met: true, reason: "a passing artifact records the plan", decisionIds: [] } : {
21824
+ met: false,
21825
+ reason: "no plan recorded: write it (docs/ and Markdown writes stay allowed), then `workit policy assess --ref <path>`",
21826
+ decisionIds: []
21827
+ };
21828
+ };
21829
+ var verdict = (requirement, root, ledger) => {
21830
+ const branch = ledger.branch();
21831
+ if (!branch)
21832
+ return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
21833
+ const check = checkVerdicts(root, branch, ledger.rows());
21834
+ const failing = check.accepted.reasons.includes("failing_verdict");
21835
+ const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
21836
+ 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)));
21837
+ if (met)
21838
+ return {
21839
+ met: true,
21840
+ reason: `${met.accepted ? "accepted independent" : "self-reviewed (author's own)"} ${String(met.verdict.result)} verdict ${met.verdict.id}`,
21841
+ decisionIds: []
21842
+ };
21843
+ 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, " ");
21844
+ 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>\``;
21845
+ return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
21846
+ };
21847
+ function derivedRuleStatus(task, requirement, root, ledger) {
21848
+ switch (requirement.ruleId) {
21849
+ case RULES.product:
21850
+ return productDecision(task, ledger);
21851
+ case RULES.plan:
21852
+ return planRecorded(task, root);
21853
+ case RULES.self:
21854
+ case RULES.nonAuthor:
21855
+ case RULES.verified:
21856
+ return verdict(requirement, root, ledger);
21857
+ default:
21858
+ return null;
21859
+ }
21860
+ }
21861
+
21862
+ // packages/workit-core/src/core/task-evaluation.ts
21863
+ var digestBytes = (value) => createHash7("sha256").update(value).digest("hex");
21864
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path11.sep}`);
21865
+ var relative = (root, candidate) => path11.relative(root, candidate).split(path11.sep).join("/") || ".";
21866
+ var canonicalPath = (value) => {
21867
+ if (typeof value !== "string" || !value)
21868
+ return null;
21869
+ if (value.includes("\\") || value.split("/").includes(".."))
21870
+ return null;
21871
+ const normalized = path11.posix.normalize(value);
21872
+ if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
21873
+ return null;
21874
+ return normalized === "." ? "." : normalized.replace(/\/$/, "");
21875
+ };
21876
+ var canonicalScope = (scope) => {
21877
+ const paths = scope.paths.length ? scope.paths : ["."];
21878
+ const normalizedPaths = paths.map(canonicalPath);
21879
+ const normalizedExclusions = scope.exclusions.map(canonicalPath);
21880
+ if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
21881
+ return null;
21882
+ return {
21883
+ description: scope.description,
21884
+ paths: [...new Set(normalizedPaths)],
21885
+ exclusions: [...new Set(normalizedExclusions)]
21886
+ };
21887
+ };
21888
+ var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
21889
+ var scopeMatches = (value, scope) => {
21890
+ const normalized = canonicalScope(scope);
21891
+ if (!normalized)
21892
+ return false;
21893
+ const paths = normalized.paths;
21894
+ return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
21895
+ };
21896
+ var pathRelevant = (value, scope) => scopeMatches(value, scope);
21897
+ var compareCodeUnits2 = (left, right) => {
21898
+ const length = Math.min(left.length, right.length);
21899
+ for (let index = 0;index < length; index += 1) {
21900
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
21901
+ if (difference !== 0)
21902
+ return difference;
21903
+ }
21904
+ return left.length - right.length;
21905
+ };
21906
+ var gitPaths = (root) => {
21907
+ const result = spawnSync6("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
21908
+ cwd: root,
21909
+ encoding: "buffer"
21910
+ });
21911
+ if (result.status !== 0) {
21912
+ const stderr = result.stderr?.toString("utf8") ?? "";
21913
+ return {
21914
+ paths: [],
21915
+ uncertain: !/not a git repository/i.test(stderr),
21916
+ git: !/not a git repository/i.test(stderr)
21917
+ };
21275
21918
  }
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" });
21919
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")) : [];
21920
+ const stagedDeleted = spawnSync6("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
21278
21921
  if (stagedDeleted.status !== 0) {
21279
21922
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
21280
21923
  if (!/not a git repository/i.test(stderr))
21281
21924
  return { paths, uncertain: true, git: true };
21282
21925
  } else if (stagedDeleted.stdout) {
21283
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")));
21926
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")));
21284
21927
  }
21285
21928
  return { paths: [...new Set(paths)], uncertain: false, git: true };
21286
21929
  };
21287
21930
  var walk = (root, directory, output) => {
21288
21931
  let names;
21289
21932
  try {
21290
- names = fs10.readdirSync(directory);
21933
+ names = fs11.readdirSync(directory);
21291
21934
  } catch {
21292
21935
  return true;
21293
21936
  }
@@ -21295,10 +21938,10 @@ var walk = (root, directory, output) => {
21295
21938
  for (const name of names) {
21296
21939
  if (name === ".git" || name === ".workit")
21297
21940
  continue;
21298
- const target = path9.join(directory, name);
21941
+ const target = path11.join(directory, name);
21299
21942
  let stat;
21300
21943
  try {
21301
- stat = fs10.lstatSync(target);
21944
+ stat = fs11.lstatSync(target);
21302
21945
  } catch {
21303
21946
  uncertain = true;
21304
21947
  continue;
@@ -21313,14 +21956,14 @@ var walk = (root, directory, output) => {
21313
21956
  var scopeRoots = (root, scope) => {
21314
21957
  const roots = [];
21315
21958
  for (const item of scope.paths.length ? scope.paths : ["."]) {
21316
- const target = path9.resolve(root, item);
21959
+ const target = path11.resolve(root, item);
21317
21960
  if (!inside(root, target))
21318
21961
  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
21962
  let ancestor = target;
21320
- while (!fs10.existsSync(ancestor) && ancestor !== root)
21321
- ancestor = path9.dirname(ancestor);
21963
+ while (!fs11.existsSync(ancestor) && ancestor !== root)
21964
+ ancestor = path11.dirname(ancestor);
21322
21965
  try {
21323
- if (!inside(root, fs10.realpathSync(ancestor)))
21966
+ if (!inside(root, fs11.realpathSync(ancestor)))
21324
21967
  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
21968
  } catch {
21326
21969
  return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
@@ -21332,7 +21975,7 @@ var scopeRoots = (root, scope) => {
21332
21975
  function captureCandidate(root, scope, environment = []) {
21333
21976
  let checkout;
21334
21977
  try {
21335
- checkout = fs10.realpathSync(root);
21978
+ checkout = fs11.realpathSync(root);
21336
21979
  } catch {
21337
21980
  return failure2("invalid_input", "candidate root does not exist");
21338
21981
  }
@@ -21350,11 +21993,11 @@ function captureCandidate(root, scope, environment = []) {
21350
21993
  if (item === ".git" || item === ".workit")
21351
21994
  continue;
21352
21995
  names.add(item);
21353
- if (!fs10.existsSync(target))
21996
+ if (!fs11.existsSync(target))
21354
21997
  continue;
21355
21998
  let stat;
21356
21999
  try {
21357
- stat = fs10.lstatSync(target);
22000
+ stat = fs11.lstatSync(target);
21358
22001
  } catch {
21359
22002
  uncertain = true;
21360
22003
  continue;
@@ -21370,17 +22013,17 @@ function captureCandidate(root, scope, environment = []) {
21370
22013
  for (const item of [...names].toSorted()) {
21371
22014
  if (!scopeMatches(item, normalizedScope))
21372
22015
  continue;
21373
- const target = path9.join(checkout, item);
22016
+ const target = path11.join(checkout, item);
21374
22017
  try {
21375
- const stat = fs10.lstatSync(target);
22018
+ const stat = fs11.lstatSync(target);
21376
22019
  if (stat.isSymbolicLink()) {
21377
- const link = fs10.readlinkSync(target);
22020
+ const link = fs11.readlinkSync(target);
21378
22021
  files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
21379
22022
  } else if (stat.isFile()) {
21380
22023
  files.push({
21381
22024
  path: item,
21382
22025
  kind: "file",
21383
- digest: digestBytes(fs10.readFileSync(target)),
22026
+ digest: digestBytes(fs11.readFileSync(target)),
21384
22027
  executable: (stat.mode & 73) !== 0
21385
22028
  });
21386
22029
  } else if (!stat.isDirectory()) {
@@ -21405,7 +22048,7 @@ function captureCandidate(root, scope, environment = []) {
21405
22048
  const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
21406
22049
  if (values.some(({ value }) => value === null))
21407
22050
  completeness = "uncertain";
21408
- const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
22051
+ const headResult = spawnSync6("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
21409
22052
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
21410
22053
  completeness = "uncertain";
21411
22054
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -21545,7 +22188,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
21545
22188
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
21546
22189
  let root;
21547
22190
  try {
21548
- root = fs10.realpathSync(checkoutRoot);
22191
+ root = fs11.realpathSync(checkoutRoot);
21549
22192
  } catch {
21550
22193
  return failure2("permission_denied", "checkout root is unavailable");
21551
22194
  }
@@ -21554,23 +22197,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
21554
22197
  continue;
21555
22198
  if (!reference.digest)
21556
22199
  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}`))
22200
+ const target = path11.resolve(root, reference.path);
22201
+ if (target !== root && !target.startsWith(`${root}${path11.sep}`))
21559
22202
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
21560
22203
  fields: [
21561
22204
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
21562
22205
  ]
21563
22206
  });
21564
22207
  try {
21565
- const below = path9.relative(root, target);
22208
+ const below = path11.relative(root, target);
21566
22209
  let current = root;
21567
- for (const segment of below.split(path9.sep)) {
22210
+ for (const segment of below.split(path11.sep)) {
21568
22211
  if (!segment)
21569
22212
  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}`))
22213
+ current = path11.join(current, segment);
22214
+ const stat = fs11.lstatSync(current);
22215
+ const real = fs11.realpathSync(current);
22216
+ if (real !== root && !real.startsWith(`${root}${path11.sep}`))
21574
22217
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
21575
22218
  fields: [
21576
22219
  {
@@ -21586,7 +22229,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
21586
22229
  if (current !== target && !stat.isDirectory())
21587
22230
  return failure2("invalid_input", "document reference ancestor is not a directory");
21588
22231
  }
21589
- if (digestBytes(fs10.readFileSync(target)) !== reference.digest)
22232
+ if (digestBytes(fs11.readFileSync(target)) !== reference.digest)
21590
22233
  return failure2("permission_denied", "decision document bytes have changed");
21591
22234
  } catch {
21592
22235
  return failure2("invalid_input", "decision document is unavailable");
@@ -21671,7 +22314,35 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
21671
22314
  decisionIds: [],
21672
22315
  reason: "stored policy version is unsupported; reassessment is required"
21673
22316
  }));
22317
+ const ledger = ledgerView(checkoutRoot);
21674
22318
  return (task.policy?.requirements ?? []).map((requirement) => {
22319
+ const derived = derivedRuleStatus(task, requirement, checkoutRoot, ledger);
22320
+ if (derived) {
22321
+ if (derived.met)
22322
+ return {
22323
+ requirementId: requirement.id,
22324
+ status: "satisfied",
22325
+ evidenceIds: [],
22326
+ decisionIds: derived.decisionIds,
22327
+ reason: derived.reason
22328
+ };
22329
+ const waived = requirement.acceptanceAllowed ? applicableDecision(task, workspace, requirement, checkoutRoot) : [];
22330
+ if (waived.length)
22331
+ return {
22332
+ requirementId: requirement.id,
22333
+ status: "accepted_limitation",
22334
+ evidenceIds: [],
22335
+ decisionIds: waived.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
22336
+ reason: "an applicable approved limitation permits the missing evidence"
22337
+ };
22338
+ return {
22339
+ requirementId: requirement.id,
22340
+ status: "unsatisfied",
22341
+ evidenceIds: [],
22342
+ decisionIds: [],
22343
+ reason: derived.reason
22344
+ };
22345
+ }
21675
22346
  const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
21676
22347
  if (gate?.satisfied.length)
21677
22348
  return {
@@ -22086,14 +22757,14 @@ var gitContext = (workspaceRoot, paths = []) => {
22086
22757
  import {
22087
22758
  copyFileSync as copyFileSync2,
22088
22759
  cpSync as cpSync2,
22089
- existsSync as existsSync4,
22760
+ existsSync as existsSync5,
22090
22761
  mkdirSync as mkdirSync2,
22091
22762
  readFileSync as readFileSync3,
22092
22763
  readdirSync as readdirSync3,
22093
22764
  writeFileSync as writeFileSync2
22094
22765
  } from "node:fs";
22095
22766
  import os2 from "node:os";
22096
- import path10 from "node:path";
22767
+ import path12 from "node:path";
22097
22768
 
22098
22769
  // packages/workit-core/src/core/boundary.ts
22099
22770
  import { statSync as statSync2 } from "node:fs";
@@ -22152,7 +22823,7 @@ var mergePreset = (preset, input = {}, current = {
22152
22823
  protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
22153
22824
  };
22154
22825
  };
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");
22826
+ 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
22827
  var migratedDir = null;
22157
22828
  var migrationFailed = false;
22158
22829
  var ensureConfigDir = (dir = resolveConfigDir()) => {
@@ -22162,12 +22833,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
22162
22833
  migratedDir = dir;
22163
22834
  return dir;
22164
22835
  }
22165
- const legacy = path10.join(process.env.XDG_CONFIG_HOME || path10.join(os2.homedir(), ".config"), "workflow-toolkit");
22166
- if (!existsSync4(legacy)) {
22836
+ const legacy = path12.join(process.env.XDG_CONFIG_HOME || path12.join(os2.homedir(), ".config"), "workflow-toolkit");
22837
+ if (!existsSync5(legacy)) {
22167
22838
  migratedDir = dir;
22168
22839
  return dir;
22169
22840
  }
22170
- if (!migrationFailed && existsSync4(dir)) {
22841
+ if (!migrationFailed && existsSync5(dir)) {
22171
22842
  migratedDir = dir;
22172
22843
  return dir;
22173
22844
  }
@@ -22175,9 +22846,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
22175
22846
  mkdirSync2(dir, { recursive: true });
22176
22847
  diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
22177
22848
  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))
22849
+ const src = path12.join(legacy, entry.name);
22850
+ const dest = path12.join(dir, entry.name);
22851
+ if (existsSync5(dest))
22181
22852
  continue;
22182
22853
  try {
22183
22854
  if (entry.isDirectory())
@@ -22248,7 +22919,7 @@ var parseConfigResult = (raw, file) => {
22248
22919
  };
22249
22920
  };
22250
22921
  var readConfigTyped = (dir) => {
22251
- const file = path10.join(dir ?? configDir(), "config.json");
22922
+ const file = path12.join(dir ?? configDir(), "config.json");
22252
22923
  return parseConfigResult(readSafe(file), file);
22253
22924
  };
22254
22925
  var readConfig = () => {
@@ -22257,6 +22928,8 @@ var readConfig = () => {
22257
22928
  throw new Error(result.error);
22258
22929
  return result.config ?? DEFAULTS;
22259
22930
  };
22931
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path12.join(configDir(), "vcs.json");
22932
+ var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
22260
22933
  var resolveBranchPolicy = (config, workspace) => {
22261
22934
  const wp = workspace?.branchPolicy ?? {};
22262
22935
  const preset = Object.hasOwn(PRESETS, wp.preset) ? wp.preset : config.branchPolicy?.preset ?? "gitflow";
@@ -22264,7 +22937,7 @@ var resolveBranchPolicy = (config, workspace) => {
22264
22937
  allowed: wp.allowed,
22265
22938
  protectedNames: wp.protected
22266
22939
  }, config);
22267
- const allowed = merged.allowed.map((p) => new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"));
22940
+ const allowed = merged.allowed.map(branchGlobPattern);
22268
22941
  return {
22269
22942
  preset,
22270
22943
  allowed,
@@ -22281,20 +22954,11 @@ var COMMIT_PRESETS = [
22281
22954
  "custom",
22282
22955
  "auto"
22283
22956
  ];
22284
- var resolveCommitPolicy = (config, workspace) => {
22285
- const wp = workspace?.commitPolicy ?? {};
22286
- if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
22287
- return {
22288
- preset: wp.preset,
22289
- ...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
22290
- };
22291
- }
22292
- return config.commitPolicy ?? { preset: "conventional" };
22293
- };
22294
22957
 
22295
22958
  // packages/workit-core/src/core/workspaces.ts
22959
+ import { createHash as createHash8 } from "node:crypto";
22296
22960
  import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
22297
- import path11 from "node:path";
22961
+ import path13 from "node:path";
22298
22962
  var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
22299
22963
  var branchPreset = custom((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
22300
22964
  var commitPreset = custom((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
@@ -22328,26 +22992,31 @@ var versionSourceSchema = discriminatedUnion("kind", [
22328
22992
  object2({ kind: literal("manual") }).strict()
22329
22993
  ]);
22330
22994
  var releaseTrackSchema = object2({
22331
- strategy: branchPreset,
22995
+ strategy: branchPreset.optional(),
22332
22996
  productionBranch: nonBlank,
22333
22997
  integrationBranch: nonBlank,
22334
- naming: object2({ feature: nonBlank, release: nonBlank, hotfix: nonBlank }).strict(),
22335
- baseBranch: nonBlank,
22336
- mergeBackBranches: array(nonBlank),
22337
- pullRequestTarget: nonBlank,
22338
- tagNamespace: string2(),
22339
- versionSource: versionSourceSchema,
22340
- requiredChecks: array(nonBlank)
22341
- }).strict().superRefine((track, context) => {
22998
+ naming: object2({
22999
+ feature: nonBlank.optional(),
23000
+ release: nonBlank.optional(),
23001
+ hotfix: nonBlank.optional()
23002
+ }).passthrough().optional(),
23003
+ baseBranch: nonBlank.optional(),
23004
+ mergeBackBranches: array(nonBlank).optional(),
23005
+ pullRequestTarget: nonBlank.optional(),
23006
+ tagNamespace: string2().optional(),
23007
+ versionSource: versionSourceSchema.optional(),
23008
+ requiredChecks: array(nonBlank).optional(),
23009
+ critical: array(nonBlank).optional()
23010
+ }).passthrough().superRefine((track, context) => {
22342
23011
  for (const [field, values] of [
22343
- ["mergeBackBranches", track.mergeBackBranches],
22344
- ["requiredChecks", track.requiredChecks]
23012
+ ["mergeBackBranches", track.mergeBackBranches ?? []],
23013
+ ["requiredChecks", track.requiredChecks ?? []]
22345
23014
  ]) {
22346
23015
  if (new Set(values).size !== values.length) {
22347
23016
  context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
22348
23017
  }
22349
23018
  }
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(".."))) {
23019
+ 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
23020
  context.addIssue({
22352
23021
  code: "custom",
22353
23022
  path: ["versionSource", "path"],
@@ -22366,6 +23035,7 @@ var workspaceConfigSchema = object2({
22366
23035
  }).passthrough().optional(),
22367
23036
  autoApprove: union([boolean2(), array(nonBlank)]).optional(),
22368
23037
  defaultEndpoint: _enum(["commit", "pr"]).optional(),
23038
+ verification: _enum(["self", "independent"]).optional(),
22369
23039
  autonomy: object2(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
22370
23040
  kind,
22371
23041
  union([boolean2(), literal("verified")]).optional()
@@ -22406,6 +23076,72 @@ var workspacesFileSchema = object2({ workspaces: array(workspaceConfigSchema).op
22406
23076
  names.add(workspace.name);
22407
23077
  }
22408
23078
  });
23079
+ var workspacesRevision = (contents) => createHash8("sha256").update(contents ?? "<missing>").digest("hex");
23080
+ var validateWorkspacesDocument = (parsed, file) => {
23081
+ if (!isConfigObject(parsed)) {
23082
+ return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
23083
+ }
23084
+ const validated = workspacesFileSchema.safeParse(parsed, { reportInput: true });
23085
+ if (!validated.success) {
23086
+ const detail = validated.error.issues.map((issue) => {
23087
+ const field = String(issue.path[issue.path.length - 1] ?? "");
23088
+ const attempted = ["provider", "preset", "strategy"].includes(field) && typeof issue.input === "string" ? ` (${JSON.stringify(issue.input)})` : "";
23089
+ return `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}${attempted}`;
23090
+ }).join("; ");
23091
+ return {
23092
+ status: "invalid",
23093
+ path: file,
23094
+ entries: [],
23095
+ error: `${file} has invalid workspace configuration: ${detail}`
23096
+ };
23097
+ }
23098
+ for (const [index, workspace] of (validated.data.workspaces ?? []).entries()) {
23099
+ const glob = validateWorkspaceGlob(workspace.glob);
23100
+ if (!glob.ok) {
23101
+ return {
23102
+ status: "invalid",
23103
+ path: file,
23104
+ entries: [],
23105
+ error: `${file} workspaces.${index}.glob: ${glob.error}`
23106
+ };
23107
+ }
23108
+ }
23109
+ return {
23110
+ status: "valid",
23111
+ path: file,
23112
+ entries: validated.data.workspaces ?? [],
23113
+ document: parsed
23114
+ };
23115
+ };
23116
+ var readWorkspacesResult = (dir = configDir()) => {
23117
+ const file = path13.join(dir, "workspaces.json");
23118
+ let raw;
23119
+ try {
23120
+ raw = readFileSync4(file, "utf8");
23121
+ } catch (error) {
23122
+ if (error.code === "ENOENT")
23123
+ return { status: "missing", path: file, entries: [], revision: workspacesRevision(null) };
23124
+ return {
23125
+ status: "malformed",
23126
+ path: file,
23127
+ entries: [],
23128
+ error: `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
23129
+ };
23130
+ }
23131
+ let parsed;
23132
+ try {
23133
+ parsed = JSON.parse(raw);
23134
+ } catch {
23135
+ return {
23136
+ status: "malformed",
23137
+ path: file,
23138
+ entries: [],
23139
+ revision: workspacesRevision(raw),
23140
+ error: `${file} is not valid JSON`
23141
+ };
23142
+ }
23143
+ return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
23144
+ };
22409
23145
  var globToRegExp = (glob) => {
22410
23146
  let out = "";
22411
23147
  for (let i = 0;i < glob.length; i++) {
@@ -22503,6 +23239,30 @@ var selectWorkspaceMatch = (matches, cwd, workspaceName) => {
22503
23239
  }
22504
23240
  return mostSpecific[0];
22505
23241
  };
23242
+ var resolveWorkspaceFromEntries = (cwd, entries, workspaceName) => {
23243
+ const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
23244
+ const matches = [];
23245
+ for (const ws of entries) {
23246
+ const glob = ws.glob.replaceAll("\\", "/");
23247
+ const canonical = canonicalGlob(glob);
23248
+ let matched = false;
23249
+ for (const target of targets) {
23250
+ if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
23251
+ matched = true;
23252
+ break;
23253
+ }
23254
+ }
23255
+ if (matched)
23256
+ matches.push(ws);
23257
+ }
23258
+ return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
23259
+ };
23260
+ var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
23261
+ const result = readWorkspacesResult(dir);
23262
+ if (result.status === "malformed" || result.status === "invalid")
23263
+ throw new Error(result.error);
23264
+ return resolveWorkspaceFromEntries(cwd, result.entries, workspaceName);
23265
+ };
22506
23266
  var runtimeWorkspacePolicySchema = (kind) => {
22507
23267
  const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
22508
23268
  const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object2({
@@ -22524,8 +23284,8 @@ var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd,
22524
23284
  var runtimeWorkspaceIndexSchema = object2({
22525
23285
  workspaces: array(object2({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
22526
23286
  }).passthrough();
22527
- var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
22528
- const file = path11.join(configDir(), "workspaces.json");
23287
+ var matchRuntimeWorkspace = (cwd) => {
23288
+ const file = path13.join(configDir(), "workspaces.json");
22529
23289
  let raw;
22530
23290
  try {
22531
23291
  raw = readFileSync4(file, "utf8");
@@ -22559,8 +23319,17 @@ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
22559
23319
  });
22560
23320
  const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
22561
23321
  const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
22562
- if (!selected)
23322
+ return selected ? { file, selected } : null;
23323
+ };
23324
+ var resolveRuntimeReleaseTracks = (cwd) => {
23325
+ const matched = matchRuntimeWorkspace(cwd);
23326
+ return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
23327
+ };
23328
+ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
23329
+ const matched = matchRuntimeWorkspace(cwd);
23330
+ if (!matched)
22563
23331
  return null;
23332
+ const { file, selected } = matched;
22564
23333
  const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
22565
23334
  if (!policy.success) {
22566
23335
  const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
@@ -22586,15 +23355,410 @@ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
22586
23355
  source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
22587
23356
  };
22588
23357
  };
23358
+ function workspaceDefaultTarget(cwd, globalDefault) {
23359
+ const ws = resolveRuntimeWorkspaceVcs(cwd);
23360
+ const wp = ws?.branchPolicy ?? {};
23361
+ const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
23362
+ const selected = resolveRuntimeWorkspacePolicy(cwd, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined);
23363
+ const policyDefault = resolveBranchPolicy(readConfig(), selected.policy ? { branchPolicy: selected.policy } : null).defaultTargetBranch;
23364
+ return String(ws?.vcs?.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : globalDefault ?? policyDefault) ?? "develop");
23365
+ }
22589
23366
 
22590
23367
  // 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");
23368
+ import fs12 from "node:fs";
23369
+ import path14 from "node:path";
23370
+ import { spawnSync as spawnSync8 } from "node:child_process";
23371
+
23372
+ // packages/workit-core/src/core/release-tracks.ts
23373
+ import { createHash as createHash9 } from "node:crypto";
23374
+ import { spawnSync as spawnSync7 } from "node:child_process";
23375
+ var KNOWN_FIELDS = new Set([
23376
+ "strategy",
23377
+ "productionBranch",
23378
+ "integrationBranch",
23379
+ "naming",
23380
+ "baseBranch",
23381
+ "mergeBackBranches",
23382
+ "pullRequestTarget",
23383
+ "tagNamespace",
23384
+ "versionSource",
23385
+ "requiredChecks",
23386
+ "critical"
23387
+ ]);
23388
+ var BRANCH_FIELDS = ["productionBranch", "integrationBranch", "baseBranch", "pullRequestTarget"];
23389
+ var PLACEHOLDER = /\{[^}]*\}/gu;
23390
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23391
+ var branchName = (value) => typeof value === "string" && value.trim() && !value.trim().startsWith("-") ? value.trim() : null;
23392
+ var namedBranches = (entry) => [
23393
+ ...BRANCH_FIELDS.map((field) => branchName(entry[field])),
23394
+ ...Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : []
23395
+ ].filter((name) => name !== null);
23396
+ var literalOf = (template) => template.replace(PLACEHOLDER, "").replace(/[*/]/gu, "").trim();
23397
+ function readReleaseTracks(raw) {
23398
+ const out = { tracks: [], issues: [], error: null, protectedBranches: [] };
23399
+ if (raw === undefined || raw === null)
23400
+ return out;
23401
+ if (!isRecord3(raw)) {
23402
+ out.issues.push("releaseTracks is not an object; ignored");
23403
+ return out;
23404
+ }
23405
+ const fatal = [];
23406
+ const protectedNames = new Set;
23407
+ for (const [name, entry] of Object.entries(raw).toSorted(([a], [b]) => a < b ? -1 : 1)) {
23408
+ const label = `release track ${JSON.stringify(name)}`;
23409
+ if (!isRecord3(entry)) {
23410
+ out.issues.push(`${label} is not an object; ignored`);
23411
+ continue;
23412
+ }
23413
+ for (const branch of namedBranches(entry))
23414
+ protectedNames.add(branch);
23415
+ if (!name.trim()) {
23416
+ out.issues.push("a release track with a blank name was ignored");
23417
+ continue;
23418
+ }
23419
+ let critical = [];
23420
+ if (Array.isArray(entry.critical))
23421
+ critical = entry.critical.filter((item) => typeof item === "string");
23422
+ else if (entry.critical !== undefined)
23423
+ out.issues.push(`${label} critical: must be a list of field names; ignored`);
23424
+ const isCritical = (field) => critical.some((item) => field === item || field.startsWith(`${item}.`));
23425
+ const problem = (field, message) => {
23426
+ if (isCritical(field))
23427
+ fatal.push(`${label} ${field}: ${message}`);
23428
+ else
23429
+ out.issues.push(`${label} ${field}: ${message}`);
23430
+ };
23431
+ for (const key of Object.keys(entry))
23432
+ if (!KNOWN_FIELDS.has(key))
23433
+ problem(key, "unknown field, ignored");
23434
+ const productionBranch = branchName(entry.productionBranch);
23435
+ const integrationBranch = branchName(entry.integrationBranch);
23436
+ if (!productionBranch)
23437
+ problem("productionBranch", "missing or not a branch name");
23438
+ if (!integrationBranch)
23439
+ problem("integrationBranch", "missing or not a branch name");
23440
+ const optionalBranch = (field) => {
23441
+ if (entry[field] === undefined)
23442
+ return null;
23443
+ const value = branchName(entry[field]);
23444
+ if (!value)
23445
+ problem(field, "not a branch name; using the integration branch");
23446
+ return value;
23447
+ };
23448
+ const baseBranch = optionalBranch("baseBranch");
23449
+ const pullRequestTarget = optionalBranch("pullRequestTarget");
23450
+ let mergeBackBranches = [];
23451
+ if (entry.mergeBackBranches !== undefined) {
23452
+ const list = Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : [null];
23453
+ if (list.some((item) => item === null))
23454
+ problem("mergeBackBranches", "must be a list of branch names; ignored");
23455
+ else
23456
+ mergeBackBranches = [...new Set(list)];
23457
+ }
23458
+ const naming = {};
23459
+ if (entry.naming !== undefined) {
23460
+ if (!isRecord3(entry.naming))
23461
+ problem("naming", "not an object; ignored");
23462
+ else
23463
+ for (const kind of ["feature", "release", "hotfix"]) {
23464
+ const template = entry.naming[kind];
23465
+ if (template === undefined)
23466
+ continue;
23467
+ if (typeof template !== "string" || !template.trim())
23468
+ problem(`naming.${kind}`, "not a template string; ignored");
23469
+ else if (!literalOf(template))
23470
+ problem(`naming.${kind}`, "needs literal text besides placeholders; ignored");
23471
+ else
23472
+ naming[kind] = template.trim();
23473
+ }
23474
+ }
23475
+ if (!productionBranch || !integrationBranch) {
23476
+ out.issues.push(`${label} was skipped: it needs a productionBranch and integrationBranch`);
23477
+ continue;
23478
+ }
23479
+ out.tracks.push({
23480
+ name,
23481
+ productionBranch,
23482
+ integrationBranch,
23483
+ baseBranch: baseBranch ?? integrationBranch,
23484
+ pullRequestTarget: pullRequestTarget ?? integrationBranch,
23485
+ mergeBackBranches,
23486
+ naming
23487
+ });
23488
+ }
23489
+ out.protectedBranches = [...protectedNames];
23490
+ if (fatal.length)
23491
+ out.error = `workspace releaseTracks cannot be read by this Workit (${fatal.join("; ")}); upgrade Workit or fix the track`;
23492
+ return out;
23493
+ }
23494
+ var IMPLICIT = "";
23495
+ var ownedBy = (tracks, branch) => tracks.filter((track) => [track.integrationBranch, track.productionBranch, track.baseBranch].includes(branch));
23496
+ var shortRef = (ref, remotes) => {
23497
+ const bare = ref.replace(/^refs\/heads\//u, "").replace(/^refs\/remotes\/[^/]+\//u, "");
23498
+ const remote = remotes.find((name) => bare.startsWith(`${name}/`));
23499
+ return remote ? bare.slice(remote.length + 1) : bare;
23500
+ };
23501
+ var MAX_RECORDED_HOPS = 10;
23502
+ function resolveReleaseTrack(input) {
23503
+ const tracks = [...input.tracks];
23504
+ const issues = [...input.issues ?? []];
23505
+ const requested = input.requested?.trim();
23506
+ if (tracks.length === 0) {
23507
+ if (requested)
23508
+ return {
23509
+ status: "invalid",
23510
+ error: `no release tracks configured for this workspace; --track ${requested} cannot apply`,
23511
+ issues
23512
+ };
23513
+ return { status: "none", issues };
23514
+ }
23515
+ const names = tracks.map((track) => track.name).join(", ");
23516
+ let envProblem = null;
23517
+ const done = (track, source, detail, warnings = [], ambiguous = null) => ({
23518
+ status: "resolved",
23519
+ track: track && track.name !== IMPLICIT ? track : null,
23520
+ source,
23521
+ detail,
23522
+ warnings: [...issues, ...envProblem ? [envProblem] : [], ...warnings],
23523
+ tracks,
23524
+ blocking: envProblem ?? ambiguous
23525
+ });
23526
+ if (requested) {
23527
+ const named = tracks.find((track) => track.name === requested);
23528
+ if (!named)
23529
+ return {
23530
+ status: "invalid",
23531
+ error: `no release track ${JSON.stringify(requested)}; configured: ${names}`,
23532
+ issues
23533
+ };
23534
+ return done(named, "flag", `track ${named.name} was requested explicitly`);
23535
+ }
23536
+ const owned = (branch) => branch !== null && tracks.some((track) => [
23537
+ track.integrationBranch,
23538
+ track.productionBranch,
23539
+ track.baseBranch,
23540
+ track.pullRequestTarget
23541
+ ].includes(branch));
23542
+ const implicit = input.defaultBranch && !owned(input.defaultBranch) ? {
23543
+ name: IMPLICIT,
23544
+ productionBranch: input.defaultBranch,
23545
+ integrationBranch: input.defaultBranch,
23546
+ baseBranch: input.defaultBranch,
23547
+ pullRequestTarget: input.defaultBranch,
23548
+ mergeBackBranches: [],
23549
+ naming: {}
23550
+ } : null;
23551
+ const candidates = implicit ? [...tracks, implicit] : tracks;
23552
+ const label = (track) => track.name === IMPLICIT ? `the workspace default line (${track.integrationBranch})` : `track ${track.name}`;
23553
+ const fallback = (why) => {
23554
+ const byDefault = input.defaultBranch ? tracks.filter((track) => track.integrationBranch === input.defaultBranch || track.pullRequestTarget === input.defaultBranch) : [];
23555
+ const track = byDefault.length === 1 ? byDefault[0] : null;
23556
+ const target = track ? `track ${track.name}` : `default target ${input.defaultBranch ?? "(none)"}`;
23557
+ const ambiguous = candidates.length >= 2 ? `can't tell which release line ${input.branch ?? "HEAD"} is on (${why}); pass --track <name> (${names}) or --base <branch>` : null;
23558
+ return done(track, "default", `${why}; using the workspace ${target}`, [
23559
+ `release track not determined: ${why}; using the workspace ${target} (pass --track <name> to choose)`
23560
+ ], ambiguous);
23561
+ };
23562
+ const branch = input.branch;
23563
+ if (branch) {
23564
+ const owners = ownedBy(candidates, branch);
23565
+ if (owners.length === 1)
23566
+ return done(owners[0], "branch", `${branch} is a branch of ${label(owners[0])}`);
23567
+ if (owners.length > 1)
23568
+ return fallback(`${branch} belongs to more than one track (${owners.map((track) => track.name).join(", ")})`);
23569
+ }
23570
+ const env = input.envRequested?.trim();
23571
+ if (env) {
23572
+ const named = tracks.find((track) => track.name === env);
23573
+ if (named)
23574
+ return done(named, "env", `WORKFLOW_RELEASE_TRACK selects track ${named.name}`);
23575
+ envProblem = `WORKFLOW_RELEASE_TRACK=${env} names no release track of this workspace (${names}); ignored`;
23576
+ }
23577
+ if (!branch)
23578
+ return fallback("HEAD is detached");
23579
+ if (tracks.length === 1 && !implicit)
23580
+ return done(tracks[0], "only-track", `${tracks[0].name} is the only track`);
23581
+ let remotes = null;
23582
+ const short = (ref) => shortRef(ref, remotes ??= input.probe.remotes());
23583
+ const ownerOf = (ref) => {
23584
+ const owner = ownedBy(candidates, short(ref));
23585
+ return owner.length === 1 ? owner[0] : null;
23586
+ };
23587
+ const followRecorded = (start) => {
23588
+ let cursor = start;
23589
+ const seen = new Set([start]);
23590
+ for (let hop = 0;hop < MAX_RECORDED_HOPS; hop += 1) {
23591
+ const recorded = input.probe.recordedBase(cursor);
23592
+ if (!recorded)
23593
+ return null;
23594
+ const base = short(recorded);
23595
+ const owner = ownerOf(base);
23596
+ if (owner)
23597
+ return owner;
23598
+ if (ownedBy(candidates, base).length > 1 || seen.has(base))
23599
+ return null;
23600
+ seen.add(base);
23601
+ cursor = base;
23602
+ }
23603
+ return null;
23604
+ };
23605
+ const recordedOwner = followRecorded(branch);
23606
+ if (recordedOwner)
23607
+ return done(recordedOwner, "recorded-base", `${branch} was created on ${label(recordedOwner)}'s line (recorded base)`);
23608
+ for (const source of ["reflog", "checkout"]) {
23609
+ const ref = source === "reflog" ? input.probe.createdFrom(branch) : input.probe.checkedOutFrom(branch);
23610
+ if (!ref)
23611
+ continue;
23612
+ const owner = ownerOf(ref) ?? followRecorded(short(ref));
23613
+ if (owner)
23614
+ return done(owner, source, `${branch} was created from ${short(ref)} (${source === "reflog" ? "branch reflog" : "HEAD reflog"})`);
23615
+ }
23616
+ const forks = [];
23617
+ for (const track of candidates)
23618
+ for (const ref of new Set([track.integrationBranch, track.baseBranch])) {
23619
+ const sha = input.probe.mergeBase(branch, ref);
23620
+ if (sha)
23621
+ forks.push({ track, ref, sha });
23622
+ }
23623
+ if (forks.length === 0)
23624
+ return fallback(`no track branch could be compared with ${branch} (fetch the track branches)`);
23625
+ const ancestry = new Map;
23626
+ const descends = (older, newer) => {
23627
+ if (older === newer)
23628
+ return false;
23629
+ const key = `${older}>${newer}`;
23630
+ if (!ancestry.has(key))
23631
+ ancestry.set(key, input.probe.isAncestor(older, newer));
23632
+ return ancestry.get(key);
23633
+ };
23634
+ const winners = forks.filter((fork) => forks.every((other) => other.track === fork.track || descends(other.sha, fork.sha)));
23635
+ const winningTracks = [...new Set(winners.map((fork) => fork.track))];
23636
+ if (winningTracks.length === 1 && forks.some((fork) => fork.track !== winningTracks[0])) {
23637
+ const win = winners[0];
23638
+ return done(win.track, "ancestry", `${branch} forked from ${win.ref} at ${win.sha.slice(0, 12)}, after every other line's fork point`);
23639
+ }
23640
+ if (winningTracks.length === 1)
23641
+ return fallback(`only ${label(winningTracks[0])} could be compared with ${branch}`);
23642
+ const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
23643
+ return fallback(`${branch} has the same or unrelated fork points with ${[...new Set(newest.map((fork) => `${fork.ref} (${fork.track.name === IMPLICIT ? "workspace default" : fork.track.name})`))].join(" and ")}`);
23644
+ }
23645
+ var git4 = (cwd, args) => {
23646
+ const run = spawnSync7("git", args, {
23647
+ cwd,
23648
+ encoding: "utf8",
23649
+ timeout: 20000,
23650
+ windowsHide: true,
23651
+ stdio: ["ignore", "pipe", "ignore"]
23652
+ });
23653
+ return run.status === 0 ? (run.stdout ?? "").trim() : null;
23654
+ };
23655
+ var recordedBaseKey = (branch) => `branch.${branch}.workitBase`;
23656
+ var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
23657
+ var reflog = (cwd, ref) => (git4(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
23658
+ `).map((line) => {
23659
+ const [sha = "", selector = "", ...subject] = line.split("\t");
23660
+ return {
23661
+ sha,
23662
+ time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
23663
+ subject: subject.join("\t")
23664
+ };
23665
+ }).filter((entry) => entry.sha && Number.isFinite(entry.time));
23666
+ function gitTrackProbe(cwd) {
23667
+ let remote;
23668
+ let remotes;
23669
+ const tip = (branch) => {
23670
+ remote ??= pushRemoteName(cwd) ?? "origin";
23671
+ return resolveRef(cwd, `refs/remotes/${remote}/${branch}`) ?? resolveRef(cwd, `refs/heads/${branch}`);
23672
+ };
23673
+ return {
23674
+ remotes: () => remotes ??= remoteNames(cwd),
23675
+ recordedBase: (branch) => git4(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
23676
+ createdFrom: (branch) => {
23677
+ const log = git4(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
23678
+ const oldest = log?.split(`
23679
+ `).findLast(Boolean) ?? "";
23680
+ const match = /^branch: Created from (\S+)$/u.exec(oldest);
23681
+ return match && match[1] !== "HEAD" ? match[1] : null;
23682
+ },
23683
+ checkedOutFrom: (branch) => {
23684
+ const own = reflog(cwd, `refs/heads/${branch}`);
23685
+ const created = own.at(-1);
23686
+ if (!created || created.subject !== "branch: Created from HEAD")
23687
+ return null;
23688
+ const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
23689
+ const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
23690
+ return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
23691
+ },
23692
+ mergeBase: (branch, trackBranch) => {
23693
+ const base = tip(trackBranch);
23694
+ return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
23695
+ },
23696
+ isAncestor: (ancestor, descendant) => git4(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
23697
+ };
23698
+ }
23699
+ function workspaceReleaseTracks(cwd) {
23700
+ const raw = resolveRuntimeReleaseTracks(cwd);
23701
+ return { workspace: raw?.workspace ?? null, ...readReleaseTracks(raw?.releaseTracks) };
23702
+ }
23703
+ var memo3 = new Map;
23704
+ var MEMO_LIMIT = 64;
23705
+ function resolveTrackFor(cwd, options) {
23706
+ const read = workspaceReleaseTracks(cwd);
23707
+ if (read.error)
23708
+ return { status: "invalid", error: read.error, issues: read.issues };
23709
+ if (read.tracks.length === 0 && !options.track?.trim())
23710
+ return { status: "none", issues: read.issues };
23711
+ const branch = options.branch === undefined ? currentBranch(cwd) : options.branch;
23712
+ const envRequested = process.env.WORKFLOW_RELEASE_TRACK?.trim() || null;
23713
+ const input = {
23714
+ tracks: read.tracks,
23715
+ requested: options.track ?? null,
23716
+ envRequested,
23717
+ branch,
23718
+ defaultBranch: options.defaultBranch,
23719
+ issues: read.issues
23720
+ };
23721
+ let key = null;
23722
+ if (!options.probe) {
23723
+ const refs = git4(cwd, [
23724
+ "for-each-ref",
23725
+ "--format=%(refname) %(objectname)",
23726
+ "refs/heads",
23727
+ "refs/remotes"
23728
+ ]) ?? "";
23729
+ const head = git4(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
23730
+ const own = branch ? [
23731
+ git4(cwd, [
23732
+ "log",
23733
+ "-g",
23734
+ "-n",
23735
+ "1",
23736
+ "--date=unix",
23737
+ "--format=%H %gd %gs",
23738
+ `refs/heads/${branch}`,
23739
+ "--"
23740
+ ]) ?? "",
23741
+ git4(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
23742
+ ] : [];
23743
+ key = createHash9("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
23744
+ const hit = memo3.get(key);
23745
+ if (hit)
23746
+ return hit;
23747
+ }
23748
+ const resolution = resolveReleaseTrack({ ...input, probe: options.probe ?? gitTrackProbe(cwd) });
23749
+ if (key) {
23750
+ if (memo3.size >= MEMO_LIMIT)
23751
+ memo3.delete(memo3.keys().next().value);
23752
+ memo3.set(key, resolution);
23753
+ }
23754
+ return resolution;
23755
+ }
23756
+
23757
+ // packages/workit-core/src/core/vcs-config.ts
23758
+ var workspacesPath = () => path14.join(configDir(), "workspaces.json");
22595
23759
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
22596
23760
  var remoteProvider = (cwd) => {
22597
- const r = spawnSync6("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
23761
+ const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
22598
23762
  if (r.status !== 0)
22599
23763
  return null;
22600
23764
  const url = (r.stdout ?? "").trim();
@@ -22631,7 +23795,7 @@ var remoteEndpoint = (remote) => {
22631
23795
  var configuredSshEndpoint = (host) => {
22632
23796
  if (host.startsWith("-"))
22633
23797
  return null;
22634
- const resolved = spawnSync6("ssh", ["-G", host], {
23798
+ const resolved = spawnSync8("ssh", ["-G", host], {
22635
23799
  encoding: "utf8",
22636
23800
  timeout: 5000,
22637
23801
  maxBuffer: 1024 * 1024,
@@ -22681,7 +23845,7 @@ var readVcsConfig = () => {
22681
23845
  const file = vcsConfigPath();
22682
23846
  let raw;
22683
23847
  try {
22684
- raw = fs11.readFileSync(file, "utf8");
23848
+ raw = fs12.readFileSync(file, "utf8");
22685
23849
  } catch {
22686
23850
  return { status: "missing", path: file, config: {} };
22687
23851
  }
@@ -22696,7 +23860,16 @@ var readVcsConfig = () => {
22696
23860
  }
22697
23861
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
22698
23862
  };
22699
- function vcsConfig(mode, cwd) {
23863
+ var trackSummary = (resolution) => resolution.status === "resolved" ? {
23864
+ name: resolution.track?.name ?? null,
23865
+ source: resolution.source,
23866
+ detail: resolution.detail,
23867
+ warnings: resolution.warnings,
23868
+ track: resolution.track,
23869
+ tracks: resolution.tracks,
23870
+ blocking: resolution.blocking
23871
+ } : null;
23872
+ function vcsConfig(mode, cwd, options = {}) {
22700
23873
  const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
22701
23874
  const wsVcs = ws?.vcs ?? {};
22702
23875
  const wsYt = ws?.youtrack ?? {};
@@ -22706,10 +23879,7 @@ function vcsConfig(mode, cwd) {
22706
23879
  const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
22707
23880
  const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
22708
23881
  const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
22709
- const wp = ws?.branchPolicy ?? {};
22710
- const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
22711
- const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
22712
- const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
23882
+ const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
22713
23883
  const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
22714
23884
  const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
22715
23885
  let issuesProvider = null;
@@ -22721,11 +23891,24 @@ function vcsConfig(mode, cwd) {
22721
23891
  if (mode === "resolve") {
22722
23892
  if (cfgStatus === "malformed")
22723
23893
  return { ok: false, error: cfgError, configPath: cfgPath };
23894
+ const trackResolution = resolveTrackFor(root, {
23895
+ track: options.track,
23896
+ ...options.branch !== undefined ? { branch: options.branch } : {},
23897
+ defaultBranch: workspaceDefault
23898
+ });
23899
+ if (trackResolution.status === "invalid")
23900
+ return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
23901
+ const releaseTrack = trackSummary(trackResolution);
23902
+ const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
23903
+ const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
22724
23904
  return {
22725
23905
  ok: true,
22726
23906
  workspace_name: ws?.name ?? null,
22727
23907
  provider,
22728
23908
  defaultTargetBranch: defaultTarget,
23909
+ workspaceDefaultTargetBranch: workspaceDefault,
23910
+ baseBranch,
23911
+ releaseTrack,
22729
23912
  link_issues: linkIssues,
22730
23913
  youtrack_base_url: youtrackBaseUrl,
22731
23914
  issues_provider: issuesProvider,
@@ -22747,9 +23930,9 @@ function vcsConfig(mode, cwd) {
22747
23930
  const prov = cfg[provider] ?? {};
22748
23931
  const out = {
22749
23932
  ok: true,
22750
- configPath: path12.resolve(cfgPath),
23933
+ configPath: path14.resolve(cfgPath),
22751
23934
  provider,
22752
- defaultTargetBranch: defaultTarget,
23935
+ defaultTargetBranch: workspaceDefault,
22753
23936
  pr: cfg.pr ?? {},
22754
23937
  workspace_name: ws?.name ?? null,
22755
23938
  link_issues: linkIssues,
@@ -22785,7 +23968,7 @@ function mergedPrStyle(limit = 6, cwd) {
22785
23968
  `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
22786
23969
  descriptionPreview: desc.slice(0, 600)
22787
23970
  });
22788
- const remoteResult = spawnSync6("git", ["remote", "get-url", "--push", "--all", "origin"], {
23971
+ const remoteResult = spawnSync8("git", ["remote", "get-url", "--push", "--all", "origin"], {
22789
23972
  cwd,
22790
23973
  encoding: "utf8"
22791
23974
  });
@@ -22801,7 +23984,7 @@ function mergedPrStyle(limit = 6, cwd) {
22801
23984
  if (!hostingApiHostMatches(remote, host))
22802
23985
  return { ok: false, error: "PR style target does not match the provider host" };
22803
23986
  if (provider === "gitlab") {
22804
- const run = (args) => spawnSync6("glab", ["api", ...args], {
23987
+ const run = (args) => spawnSync8("glab", ["api", ...args], {
22805
23988
  cwd,
22806
23989
  encoding: "utf8",
22807
23990
  env
@@ -22821,7 +24004,7 @@ function mergedPrStyle(limit = 6, cwd) {
22821
24004
  });
22822
24005
  }
22823
24006
  } else if (provider === "github") {
22824
- const r = spawnSync6("gh", [
24007
+ const r = spawnSync8("gh", [
22825
24008
  "pr",
22826
24009
  "list",
22827
24010
  "--repo",
@@ -22852,323 +24035,38 @@ function mergedPrStyle(limit = 6, cwd) {
22852
24035
  examples
22853
24036
  };
22854
24037
  }
22855
-
22856
- // packages/workit-core/src/core/branch.ts
22857
- function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
22858
- const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
22859
- const config = readConfig();
22860
- return kind === "branch" ? {
22861
- branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
22862
- provenance: { branchPolicy: selected.source }
22863
- } : {
22864
- commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
22865
- provenance: { commitPolicy: selected.source }
22866
- };
22867
- }
22868
- var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
22869
24038
  // packages/workit-core/src/core/task-engine.ts
22870
24039
  import { realpathSync as realpathSync4 } from "node:fs";
22871
24040
 
22872
24041
  // 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);
24042
+ function resolvePolicy(input) {
24043
+ const ids = input.constraints.map((constraint) => constraint.id);
22980
24044
  if (new Set(ids).size !== ids.length)
22981
24045
  return failure2("invalid_input", "constraint IDs must be unique", {
22982
24046
  fields: [{ path: "constraints", reason: "duplicate constraint id" }]
22983
24047
  });
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
24048
  const policy = {
23163
24049
  policyVersion: POLICY_VERSION,
23164
- inputDigest: sha256(canonicalJson(normalized.data)),
23165
- requirements
24050
+ inputDigest: sha256(canonicalJson({
24051
+ judgment: input.judgment,
24052
+ scope: input.intent.scope,
24053
+ constraints: input.constraints,
24054
+ verification: input.verification ?? "self",
24055
+ policyVersion: POLICY_VERSION
24056
+ })),
24057
+ requirements: deriveRequirements(input.judgment, input.intent.scope, input.constraints, input.verification)
23166
24058
  };
23167
24059
  const parsed = policySchema.safeParse(policy);
23168
24060
  if (!parsed.success)
23169
- return failure2("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
24061
+ return failure2("invalid_input", "resolved policy is invalid", {
24062
+ fields: parsed.error.issues.map((issue) => ({
24063
+ path: issue.path.join("."),
24064
+ reason: issue.message
24065
+ }))
24066
+ });
23170
24067
  return success(null, null, parsed.data);
23171
24068
  }
24069
+ var compareCodeUnits3 = (left, right) => left < right ? -1 : left > right ? 1 : 0;
23172
24070
  function diffPolicy(previous, next, reason, now) {
23173
24071
  if (!policySchema.safeParse(previous).success && previous !== null)
23174
24072
  throw new TypeError("previous policy is invalid");
@@ -23196,7 +24094,222 @@ function diffPolicy(previous, next, reason, now) {
23196
24094
  return parsed.data;
23197
24095
  }
23198
24096
 
24097
+ // packages/workit-core/src/core/policy/judgment.ts
24098
+ var isObject6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
24099
+ var squash = (key) => key.toLowerCase().replace(/[_-]/g, "");
24100
+ var ALIASES = {
24101
+ riskTier: ["risktier", "risk", "risklevel", "tier"],
24102
+ behaviorChange: ["behaviorchange", "behavior", "behaviour", "behaviourchange"],
24103
+ productChoiceOpen: ["productchoiceopen", "openproductchoice", "productchoice", "openchoice"],
24104
+ needsPlan: ["needsplan", "plan", "needsspec", "spec"],
24105
+ note: ["note", "notes", "reason", "why"],
24106
+ refs: ["refs", "ref", "references"]
24107
+ };
24108
+ var LEGACY_KEYS = new Set(["assessment", "facts", "signals", "consequences", "verification"]);
24109
+ var RISK = {
24110
+ trivial: "trivial",
24111
+ low: "trivial",
24112
+ minor: "trivial",
24113
+ mechanical: "trivial",
24114
+ none: "trivial",
24115
+ normal: "normal",
24116
+ medium: "normal",
24117
+ med: "normal",
24118
+ moderate: "normal",
24119
+ high: "high",
24120
+ critical: "high",
24121
+ major: "high"
24122
+ };
24123
+ var YES = new Set(["true", "yes", "y", "1", "on"]);
24124
+ var NO = new Set(["false", "no", "n", "0", "off", "none"]);
24125
+ var flag = (value, unknownAs) => {
24126
+ if (typeof value === "boolean")
24127
+ return { ok: true, value };
24128
+ if (typeof value === "number" && (value === 0 || value === 1))
24129
+ return { ok: true, value: value === 1 };
24130
+ if (typeof value !== "string")
24131
+ return { ok: false };
24132
+ const text = value.trim().toLowerCase();
24133
+ if (YES.has(text))
24134
+ return { ok: true, value: true };
24135
+ if (NO.has(text))
24136
+ return { ok: true, value: false };
24137
+ if (text === "unknown" || text === "maybe")
24138
+ return { ok: true, value: unknownAs };
24139
+ return { ok: false };
24140
+ };
24141
+ var fromLegacy = (raw, previous) => {
24142
+ const source = isObject6(raw.assessment) ? raw.assessment : raw;
24143
+ const signals = isObject6(source.signals) ? source.signals : {};
24144
+ const value = (name) => isObject6(signals[name]) ? signals[name].value : undefined;
24145
+ const known = (name, fallback) => typeof value(name) === "boolean" ? value(name) : fallback;
24146
+ const behaviorChange = known("behaviorChange", previous?.behaviorChange ?? false);
24147
+ const consequences = Array.isArray(source.consequences) ? source.consequences : [];
24148
+ const freshReview = value("behaviorChange") === true && consequences.some((item) => isObject6(item) && ["security", "data", "public_contract", "operations"].includes(String(item.area)));
24149
+ return {
24150
+ riskTier: freshReview ? "high" : value("mechanicalLowRisk") === true && !behaviorChange ? "trivial" : "normal",
24151
+ behaviorChange,
24152
+ productChoiceOpen: known("productChoiceOpen", previous?.productChoiceOpen ?? false),
24153
+ needsPlan: known("durableAgreementNeeded", previous?.needsPlan ?? false) || value("coordinationPlanNeeded") === true
24154
+ };
24155
+ };
24156
+ var DEFAULT = {
24157
+ riskTier: "normal",
24158
+ behaviorChange: false,
24159
+ productChoiceOpen: false,
24160
+ needsPlan: false,
24161
+ note: null,
24162
+ refs: []
24163
+ };
24164
+ function normalizeJudgment(raw, previous) {
24165
+ const input = isObject6(raw) ? raw : {};
24166
+ const legacy = Object.keys(input).some((key) => LEGACY_KEYS.has(key));
24167
+ const base = { ...DEFAULT, ...previous, refs: [...previous?.refs ?? []] };
24168
+ const judgment = legacy ? { ...base, ...fromLegacy(input, previous) } : { ...base };
24169
+ const ignored = [];
24170
+ const fields = [];
24171
+ for (const [key, value] of Object.entries(input)) {
24172
+ if (LEGACY_KEYS.has(key))
24173
+ continue;
24174
+ const name = Object.keys(ALIASES).find((field) => ALIASES[field].includes(squash(key)));
24175
+ if (!name) {
24176
+ ignored.push(key);
24177
+ continue;
24178
+ }
24179
+ if (name === "riskTier") {
24180
+ const tier = typeof value === "string" ? RISK[value.trim().toLowerCase()] : undefined;
24181
+ if (tier)
24182
+ judgment.riskTier = tier;
24183
+ else
24184
+ fields.push({ path: key, reason: "expected trivial|normal|high (or low|medium)" });
24185
+ } else if (name === "note") {
24186
+ judgment.note = typeof value === "string" && value.trim() ? value.trim() : null;
24187
+ } else if (name === "refs") {
24188
+ const items = Array.isArray(value) ? value : [value];
24189
+ judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
24190
+ } else {
24191
+ if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
24192
+ judgment.needsPlan = true;
24193
+ judgment.refs.push(refOf(value.trim()));
24194
+ continue;
24195
+ }
24196
+ const parsed = flag(value, name === "behaviorChange");
24197
+ if (parsed.ok)
24198
+ judgment[name] = parsed.value;
24199
+ else
24200
+ fields.push({ path: key, reason: "expected true|false (yes|no accepted)" });
24201
+ }
24202
+ }
24203
+ if (fields.length)
24204
+ return failure2("invalid_input", "judgment values are invalid", { fields });
24205
+ const seen = new Set;
24206
+ judgment.refs = judgment.refs.filter((ref) => {
24207
+ const key = JSON.stringify(ref);
24208
+ return seen.has(key) ? false : (seen.add(key), true);
24209
+ });
24210
+ const parsed = judgmentSchema.safeParse(judgment);
24211
+ if (!parsed.success)
24212
+ return failure2("invalid_input", "judgment is invalid", {
24213
+ fields: parsed.error.issues.map((issue) => ({
24214
+ path: issue.path.join("."),
24215
+ reason: issue.message
24216
+ }))
24217
+ });
24218
+ return success(null, null, { judgment: parsed.data, ignored, legacy });
24219
+ }
24220
+
24221
+ // packages/workit-core/src/autonomy.ts
24222
+ import os3 from "node:os";
24223
+ import path15 from "node:path";
24224
+ var accountHome = () => {
24225
+ try {
24226
+ return os3.userInfo().homedir;
24227
+ } catch {
24228
+ return os3.homedir();
24229
+ }
24230
+ };
24231
+ var grantsHome = { resolve: accountHome };
24232
+ var grantsDir = () => path15.join(grantsHome.resolve(), ".config", "workit");
24233
+ var grantsOverride = () => {
24234
+ const configured = path15.resolve(resolveConfigDir());
24235
+ 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`;
24236
+ };
24237
+ var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
24238
+ var DEFAULT_GRANTS = Object.freeze({
24239
+ push: true,
24240
+ pr: true,
24241
+ merge: false,
24242
+ release: false,
24243
+ rerun: true
24244
+ });
24245
+ var DEFAULT_ENDPOINT = "commit";
24246
+ var DEFAULT_VERIFICATION = "self";
24247
+ var verificationOf = (entry) => entry?.verification === "independent" ? "independent" : "self";
24248
+ var endpointOf = (entry) => entry?.defaultEndpoint === "pr" ? "pr" : "commit";
24249
+ var isGrantValue = (value) => value === true || value === false || value === "verified";
24250
+ var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
24251
+ var fromAutoApprove = (value) => {
24252
+ if (value === true)
24253
+ return { push: true, pr: true, merge: "verified" };
24254
+ if (!Array.isArray(value))
24255
+ return null;
24256
+ const grants = {};
24257
+ for (const item of value)
24258
+ if (item === "push" || item === "pr")
24259
+ grants[item] = true;
24260
+ else if (item === "merge")
24261
+ grants.merge = "verified";
24262
+ return Object.keys(grants).length > 0 ? grants : null;
24263
+ };
24264
+ function configuredGrants(entry) {
24265
+ const record = entry ?? {};
24266
+ const raw = record.autonomy;
24267
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
24268
+ const grants = {};
24269
+ for (const kind of GRANT_KINDS) {
24270
+ const value = raw[kind];
24271
+ if (isGrantValue(value))
24272
+ grants[kind] = normalized(kind, value);
24273
+ }
24274
+ return { grants, source: "autonomy" };
24275
+ }
24276
+ const legacy = fromAutoApprove(record.autoApprove);
24277
+ return legacy ? { grants: legacy, source: "autoApprove" } : { grants: {}, source: "default" };
24278
+ }
24279
+ function resolveAutonomy(cwd) {
24280
+ const override = grantsOverride();
24281
+ if (override)
24282
+ return {
24283
+ workspace: null,
24284
+ grants: { ...DEFAULT_GRANTS },
24285
+ configured: [],
24286
+ source: "default",
24287
+ accountConfigured: false,
24288
+ defaultEndpoint: DEFAULT_ENDPOINT,
24289
+ verification: DEFAULT_VERIFICATION,
24290
+ note: override
24291
+ };
24292
+ const workspace = resolveWorkspaceFrom(cwd, grantsDir(), process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined);
24293
+ const { grants, source } = configuredGrants(workspace);
24294
+ return {
24295
+ workspace: workspace?.name ?? null,
24296
+ grants: { ...DEFAULT_GRANTS, ...grants },
24297
+ configured: GRANT_KINDS.filter((kind) => grants[kind] !== undefined),
24298
+ source,
24299
+ accountConfigured: Boolean(workspace?.vcs?.account),
24300
+ defaultEndpoint: endpointOf(workspace),
24301
+ verification: verificationOf(workspace)
24302
+ };
24303
+ }
24304
+
23199
24305
  // packages/workit-core/src/core/task-engine.ts
24306
+ var workspaceVerification = (root) => {
24307
+ try {
24308
+ return resolveAutonomy(root).verification;
24309
+ } catch {
24310
+ return "self";
24311
+ }
24312
+ };
23200
24313
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
23201
24314
  kind,
23202
24315
  host: context.caller.host,
@@ -23389,22 +24502,7 @@ var mapRef = (ref, ids) => {
23389
24502
  return ref;
23390
24503
  return { ...ref, id: ids.get(ref.id) ?? ref.id };
23391
24504
  };
23392
- var portableRefs = (refs) => refs.filter((ref) => ref.kind === "file" || ref.kind === "record");
23393
24505
  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
24506
  var importedProvenance = (context) => ({
23409
24507
  kind: "imported",
23410
24508
  host: context.caller.host,
@@ -23416,18 +24514,6 @@ var portableTask = (task) => {
23416
24514
  if (typeof stripped !== "object" || stripped === null || Array.isArray(stripped))
23417
24515
  throw new TypeError("portable task rewrite produced a non-record");
23418
24516
  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
24517
  clone.candidates = [];
23432
24518
  return clone;
23433
24519
  };
@@ -23444,6 +24530,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
23444
24530
  for (const entry of [
23445
24531
  source.intent,
23446
24532
  ...source.assessments,
24533
+ ...source.judgments ?? [],
23447
24534
  ...source.evidence,
23448
24535
  ...source.decisions,
23449
24536
  ...source.findings,
@@ -23467,9 +24554,16 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
23467
24554
  id: fresh(entry.id),
23468
24555
  recordedAt: timestamp,
23469
24556
  provenance: imported,
23470
- data: rewriteRecordRefs(data, assessmentSchema, remap)
24557
+ data: rewriteRecordRefs(data, legacyAssessmentSchema, remap)
23471
24558
  };
23472
24559
  });
24560
+ const judgments = source.judgments?.map((entry) => ({
24561
+ ...entry,
24562
+ id: fresh(entry.id),
24563
+ recordedAt: timestamp,
24564
+ provenance: imported,
24565
+ data: rewriteRecordRefs(entry.data, judgmentSchema, remap)
24566
+ }));
23473
24567
  const evidence = source.evidence.map((entry) => ({
23474
24568
  ...entry,
23475
24569
  id: fresh(entry.id),
@@ -23537,6 +24631,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
23537
24631
  status: "paused",
23538
24632
  closure: null,
23539
24633
  assessments,
24634
+ ...judgments ? { judgments } : {},
23540
24635
  evidence,
23541
24636
  decisions: decisions.map((entry) => {
23542
24637
  const data = rewriteRecordRefs(entry.data, decisionSchema, remap);
@@ -23606,7 +24701,8 @@ class WorkitCore {
23606
24701
  retryOmittedRevisions(request, run) {
23607
24702
  return retryFilledRevisions(filledRevisions(request), run);
23608
24703
  }
23609
- implicitTask(family, request) {
24704
+ implicitTask(family, raw) {
24705
+ const request = normalizeOperationInput(family, raw, this.context.caller.host);
23610
24706
  if (typeof request !== "object" || request === null || Array.isArray(request))
23611
24707
  return success(null, null, request);
23612
24708
  const value = request;
@@ -23860,7 +24956,11 @@ class WorkitCore {
23860
24956
  if ((this.context.workerId ?? null) !== null)
23861
24957
  return failure2("permission_denied", "helpers cannot change task requirements");
23862
24958
  this.fillRevisions(input, task.data);
23863
- const resolved = this.resolve(task.data, input.assessment);
24959
+ const judged = normalizeJudgment(input.judgment, latestJudgment(task.data));
24960
+ if (!judged.ok)
24961
+ return judged;
24962
+ const judgment = judged.data.judgment;
24963
+ const resolved = this.resolve(task.data, judgment);
23864
24964
  if (!resolved.ok)
23865
24965
  return resolved;
23866
24966
  if (input.action === "preview")
@@ -23871,16 +24971,17 @@ class WorkitCore {
23871
24971
  if (current.status === "closed")
23872
24972
  return failure2("invalid_transition", "closed task cannot be assessed");
23873
24973
  const nextChange = diffPolicy(current.policy, resolved.data, "policy reassessed", mutation.now);
23874
- const assessment = {
24974
+ const entry = {
23875
24975
  id: newId(),
23876
24976
  recordedAt: mutation.now,
23877
24977
  provenance: provenance(this.context),
23878
- data: input.assessment
24978
+ data: judgment
23879
24979
  };
23880
24980
  const next = {
23881
24981
  ...current,
23882
24982
  constraints: this.context.constraints,
23883
- assessments: [...current.assessments, assessment],
24983
+ judgments: [...current.judgments ?? [], entry],
24984
+ critical: [...new Set([...current.critical ?? [], "judgments"])],
23884
24985
  policy: resolved.data,
23885
24986
  policyChanges: nextChange ? [...current.policyChanges, nextChange] : current.policyChanges
23886
24987
  };
@@ -24082,9 +25183,14 @@ class WorkitCore {
24082
25183
  return workspace;
24083
25184
  if (!workspace.data)
24084
25185
  return failure2("not_found", "workspace not found");
24085
- if (input.binding.taskId !== task.data.id || input.binding.workspaceId !== workspace.data.id)
25186
+ const binding = {
25187
+ ...input.binding,
25188
+ taskId: input.binding.taskId ?? task.data.id,
25189
+ workspaceId: input.binding.workspaceId ?? workspace.data.id
25190
+ };
25191
+ if (binding.taskId !== task.data.id || binding.workspaceId !== workspace.data.id)
24086
25192
  return failure2("invalid_input", "decision task or workspace binding is invalid");
24087
- const content = verifyDecisionContentAtRoot(this.store.root, input.binding);
25193
+ const content = verifyDecisionContentAtRoot(this.store.root, binding);
24088
25194
  if (!content.ok)
24089
25195
  return content;
24090
25196
  const knownRequirements = new Set(task.data.policy?.requirements.map((item) => item.id) ?? []);
@@ -24092,7 +25198,7 @@ class WorkitCore {
24092
25198
  return failure2("invalid_input", "decision references an unknown requirement");
24093
25199
  const base = {
24094
25200
  purpose: input.purpose,
24095
- binding: input.binding,
25201
+ binding,
24096
25202
  response: input.response,
24097
25203
  requirementIds: input.requirementIds,
24098
25204
  revoked: null
@@ -24451,16 +25557,12 @@ class WorkitCore {
24451
25557
  now: trustedNow(this.context)
24452
25558
  });
24453
25559
  }
24454
- resolve(task, assessment) {
25560
+ resolve(task, judgment) {
24455
25561
  return resolvePolicy({
24456
25562
  intent: task.intent.data,
24457
- assessment,
25563
+ judgment,
24458
25564
  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
- }
25565
+ verification: workspaceVerification(this.context.root)
24464
25566
  });
24465
25567
  }
24466
25568
  summary(task) {
@@ -24786,9 +25888,9 @@ class WorkitCore {
24786
25888
  }
24787
25889
  }
24788
25890
  // 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";
25891
+ import { spawnSync as spawnSync9 } from "node:child_process";
25892
+ import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
25893
+ import path16 from "node:path";
24792
25894
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
24793
25895
  try {
24794
25896
  gitRevisionParts(value);
@@ -24815,7 +25917,7 @@ var invalidRange = (cwd) => ({
24815
25917
  cwd
24816
25918
  });
24817
25919
  var runGit = (cwd, args) => {
24818
- const result = spawnSync7("git", args, { cwd, encoding: "utf8" });
25920
+ const result = spawnSync9("git", args, { cwd, encoding: "utf8" });
24819
25921
  return {
24820
25922
  stdout: (result.stdout ?? "").trimEnd(),
24821
25923
  stderr: (result.stderr ?? "").trimEnd(),
@@ -24826,11 +25928,11 @@ var repoRoot = (cwd) => {
24826
25928
  const r = runGit(cwd, ["rev-parse", "--show-toplevel"]);
24827
25929
  return r.exitCode === 0 && r.stdout ? r.stdout : cwd;
24828
25930
  };
24829
- var currentBranch = (cwd) => {
25931
+ var currentBranch2 = (cwd) => {
24830
25932
  const r = runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
24831
25933
  return r.exitCode === 0 && r.stdout ? r.stdout : "unknown";
24832
25934
  };
24833
- var defaultBase = (cwd) => {
25935
+ var defaultBase2 = (cwd) => {
24834
25936
  for (const ref of [
24835
25937
  "origin/main",
24836
25938
  "main",
@@ -24844,11 +25946,11 @@ var defaultBase = (cwd) => {
24844
25946
  }
24845
25947
  return "HEAD~1";
24846
25948
  };
24847
- var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${defaultBase(cwd)}...HEAD`;
25949
+ var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${defaultBase2(cwd)}...HEAD`;
24848
25950
  var isProtectedBranch = (branch) => ["main", "master", "develop", "prod", "production"].includes(branch);
24849
25951
  var isPrBranch = (branch) => branch.startsWith("feature/") || branch.startsWith("bugfix/");
24850
25952
  function resolvePrBranchContext(cwd) {
24851
- const branch = currentBranch(cwd);
25953
+ const branch = currentBranch2(cwd);
24852
25954
  if (isProtectedBranch(branch)) {
24853
25955
  return {
24854
25956
  ok: false,
@@ -24922,7 +26024,7 @@ var findPrTemplate = (cwd) => {
24922
26024
  ]) {
24923
26025
  const parts = rel.split("/");
24924
26026
  const base = parts[parts.length - 1];
24925
- const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
26027
+ const dir = parts.length > 1 ? path16.join(cwd, ...parts.slice(0, -1)) : cwd;
24926
26028
  const actual = probeCaseInsensitive(dir, base);
24927
26029
  if (actual)
24928
26030
  return [...parts.slice(0, -1), actual].join("/");
@@ -24953,8 +26055,8 @@ var diffStatForRange = (cwd, range) => {
24953
26055
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
24954
26056
  };
24955
26057
  var packageScripts = (cwd, keys) => {
24956
- const pkgPath = path13.join(cwd, "package.json");
24957
- if (!existsSync5(pkgPath))
26058
+ const pkgPath = path16.join(cwd, "package.json");
26059
+ if (!existsSync6(pkgPath))
24958
26060
  return [];
24959
26061
  try {
24960
26062
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
@@ -24979,9 +26081,9 @@ var documentationFiles = (cwd) => {
24979
26081
  if (entry.isDirectory()) {
24980
26082
  if (excluded.has(entry.name))
24981
26083
  continue;
24982
- walk(path13.join(dir, entry.name), depth + 1);
26084
+ walk(path16.join(dir, entry.name), depth + 1);
24983
26085
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
24984
- matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
26086
+ matches.push("./" + path16.relative(cwd, path16.join(dir, entry.name)).replaceAll("\\", "/"));
24985
26087
  }
24986
26088
  }
24987
26089
  };
@@ -24989,7 +26091,7 @@ var documentationFiles = (cwd) => {
24989
26091
  return matches.toSorted().slice(0, 200);
24990
26092
  };
24991
26093
  var readTrimmed2 = (file, maxLines) => {
24992
- if (!existsSync5(file))
26094
+ if (!existsSync6(file))
24993
26095
  return "";
24994
26096
  const lines = readFileSync5(file, "utf8").split(`
24995
26097
  `);
@@ -24997,7 +26099,7 @@ var readTrimmed2 = (file, maxLines) => {
24997
26099
  `);
24998
26100
  };
24999
26101
  function prReadyContext(root, range) {
25000
- const cwd = path13.resolve(repoRoot(root));
26102
+ const cwd = path16.resolve(repoRoot(root));
25001
26103
  if (range !== undefined && !isResolvableContextRange(cwd, range))
25002
26104
  return invalidRange(cwd);
25003
26105
  let stdout = "";
@@ -25013,7 +26115,7 @@ function prReadyContext(root, range) {
25013
26115
  stdout += printSection("Repository");
25014
26116
  stdout += `root: ${cwd}
25015
26117
  `;
25016
- stdout += `branch: ${currentBranch(cwd)}
26118
+ stdout += `branch: ${currentBranch2(cwd)}
25017
26119
  `;
25018
26120
  stdout += `range: ${range}
25019
26121
  `;
@@ -25055,7 +26157,7 @@ ${status.stderr}` : "");
25055
26157
  stdout += `template_path: ${template}
25056
26158
 
25057
26159
  `;
25058
- stdout += readTrimmed2(path13.join(cwd, template), 220);
26160
+ stdout += readTrimmed2(path16.join(cwd, template), 220);
25059
26161
  } else {
25060
26162
  stdout += `template_path: none
25061
26163
 
@@ -25065,7 +26167,7 @@ ${status.stderr}` : "");
25065
26167
  stdout += `
25066
26168
  `;
25067
26169
  stdout += printSection("Recent Validation Signals");
25068
- if (existsSync5(path13.join(cwd, "package.json"))) {
26170
+ if (existsSync6(path16.join(cwd, "package.json"))) {
25069
26171
  stdout += `package.json detected. Common scripts:
25070
26172
  `;
25071
26173
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -25074,7 +26176,7 @@ ${status.stderr}` : "");
25074
26176
  `) + `
25075
26177
  `;
25076
26178
  }
25077
- if (existsSync5(path13.join(cwd, "Cargo.toml")) || existsSync5(path13.join(cwd, "src-tauri/Cargo.toml"))) {
26179
+ if (existsSync6(path16.join(cwd, "Cargo.toml")) || existsSync6(path16.join(cwd, "src-tauri/Cargo.toml"))) {
25078
26180
  stdout += `Rust project detected.
25079
26181
  `;
25080
26182
  }
@@ -25107,7 +26209,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
25107
26209
  - Apply with the host-native editor, preserving the repository changelog conventions.
25108
26210
  - If Unreleased already has duplicate category headings, normalize_only first.`;
25109
26211
  function changelogContext(root, range) {
25110
- const cwd = path13.resolve(repoRoot(root));
26212
+ const cwd = path16.resolve(repoRoot(root));
25111
26213
  if (range !== undefined && !isResolvableContextRange(cwd, range))
25112
26214
  return invalidRange(cwd);
25113
26215
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -25117,7 +26219,7 @@ function changelogContext(root, range) {
25117
26219
  stdout += printSection("Repository");
25118
26220
  stdout += `root: ${cwd}
25119
26221
  `;
25120
- stdout += `branch: ${currentBranch(cwd)}
26222
+ stdout += `branch: ${currentBranch2(cwd)}
25121
26223
  `;
25122
26224
  stdout += `range: ${resolvedRange}
25123
26225
  `;
@@ -25125,8 +26227,8 @@ function changelogContext(root, range) {
25125
26227
  stdout += CHANGELOG_RULES + `
25126
26228
  `;
25127
26229
  stdout += printSection("Existing CHANGELOG.md");
25128
- const changelogPath = path13.join(cwd, "CHANGELOG.md");
25129
- if (existsSync5(changelogPath)) {
26230
+ const changelogPath = path16.join(cwd, "CHANGELOG.md");
26231
+ if (existsSync6(changelogPath)) {
25130
26232
  stdout += readTrimmed2(changelogPath, 260) + `
25131
26233
  `;
25132
26234
  } else {
@@ -25145,7 +26247,7 @@ function changelogContext(root, range) {
25145
26247
  return { stdout, stderr: "", exitCode: 0, cwd };
25146
26248
  }
25147
26249
  function docsRefreshContext(root, range) {
25148
- const cwd = path13.resolve(repoRoot(root));
26250
+ const cwd = path16.resolve(repoRoot(root));
25149
26251
  if (range !== undefined && !isResolvableContextRange(cwd, range))
25150
26252
  return invalidRange(cwd);
25151
26253
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -25155,7 +26257,7 @@ function docsRefreshContext(root, range) {
25155
26257
  stdout += printSection("Repository");
25156
26258
  stdout += `root: ${cwd}
25157
26259
  `;
25158
- stdout += `branch: ${currentBranch(cwd)}
26260
+ stdout += `branch: ${currentBranch2(cwd)}
25159
26261
  `;
25160
26262
  stdout += `range: ${resolvedRange}
25161
26263
  `;
@@ -25167,8 +26269,8 @@ function docsRefreshContext(root, range) {
25167
26269
  `) + `
25168
26270
  `;
25169
26271
  stdout += printSection("README Preview");
25170
- const readme = path13.join(cwd, "README.md");
25171
- if (existsSync5(readme)) {
26272
+ const readme = path16.join(cwd, "README.md");
26273
+ if (existsSync6(readme)) {
25172
26274
  stdout += readTrimmed2(readme, 220) + `
25173
26275
  `;
25174
26276
  } else {
@@ -25176,8 +26278,8 @@ function docsRefreshContext(root, range) {
25176
26278
  `;
25177
26279
  }
25178
26280
  stdout += printSection("Package Scripts");
25179
- const pkgPath = path13.join(cwd, "package.json");
25180
- if (existsSync5(pkgPath)) {
26281
+ const pkgPath = path16.join(cwd, "package.json");
26282
+ if (existsSync6(pkgPath)) {
25181
26283
  try {
25182
26284
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
25183
26285
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
@@ -25190,7 +26292,7 @@ function docsRefreshContext(root, range) {
25190
26292
  return { stdout, stderr: "", exitCode: 0, cwd };
25191
26293
  }
25192
26294
  function releaseNotesContext(root, rangeOrTag) {
25193
- const cwd = path13.resolve(repoRoot(root));
26295
+ const cwd = path16.resolve(repoRoot(root));
25194
26296
  if (!rangeOrTag) {
25195
26297
  return { stdout: "", stderr: `ERROR: release tag or range required
25196
26298
  `, exitCode: 1, cwd };
@@ -25204,7 +26306,7 @@ function releaseNotesContext(root, rangeOrTag) {
25204
26306
  stdout += printSection("Repository");
25205
26307
  stdout += `root: ${cwd}
25206
26308
  `;
25207
- stdout += `branch: ${currentBranch(cwd)}
26309
+ stdout += `branch: ${currentBranch2(cwd)}
25208
26310
  `;
25209
26311
  stdout += `requested: ${rangeOrTag}
25210
26312
  `;
@@ -25228,7 +26330,7 @@ function releaseNotesContext(root, rangeOrTag) {
25228
26330
  `;
25229
26331
  stdout += printSection("Existing Release Files");
25230
26332
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
25231
- if (existsSync5(path13.join(cwd, rel)))
26333
+ if (existsSync6(path16.join(cwd, rel)))
25232
26334
  stdout += rel + `
25233
26335
  `;
25234
26336
  }
@@ -25256,12 +26358,12 @@ function releaseNotesContext(root, rangeOrTag) {
25256
26358
  }
25257
26359
 
25258
26360
  // packages/workit-core/src/core/tracker-issues.ts
25259
- import { spawnSync as spawnSync9 } from "node:child_process";
26361
+ import { spawnSync as spawnSync11 } from "node:child_process";
25260
26362
 
25261
26363
  // 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";
26364
+ import fs13 from "node:fs";
26365
+ import path17 from "node:path";
26366
+ import { spawnSync as spawnSync10 } from "node:child_process";
25265
26367
  // packages/workit-core/src/forge/pr-body.ts
25266
26368
  function parseGhIssue(value) {
25267
26369
  const m = /issues\/(\d+)/.exec(value);
@@ -25271,13 +26373,13 @@ function parseGhIssue(value) {
25271
26373
  // packages/workit-core/src/core/pr-create.ts
25272
26374
  function whichOnPath(tool) {
25273
26375
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
25274
- for (const dir of (process.env.PATH ?? "").split(path14.delimiter)) {
26376
+ for (const dir of (process.env.PATH ?? "").split(path17.delimiter)) {
25275
26377
  if (!dir)
25276
26378
  continue;
25277
26379
  for (const name of names) {
25278
- const candidate = path14.join(dir, name);
26380
+ const candidate = path17.join(dir, name);
25279
26381
  try {
25280
- fs12.accessSync(candidate, fs12.constants.X_OK);
26382
+ fs13.accessSync(candidate, fs13.constants.X_OK);
25281
26383
  return candidate;
25282
26384
  } catch {}
25283
26385
  }
@@ -25286,7 +26388,7 @@ function whichOnPath(tool) {
25286
26388
  }
25287
26389
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
25288
26390
  var pushRemote = (cwd) => {
25289
- const result = spawnSync8("git", ["remote", "get-url", "--push", "--all", "origin"], {
26391
+ const result = spawnSync10("git", ["remote", "get-url", "--push", "--all", "origin"], {
25290
26392
  cwd,
25291
26393
  encoding: "utf8"
25292
26394
  });
@@ -25317,7 +26419,7 @@ var defaultCli = (bin, available) => {
25317
26419
  if (!available)
25318
26420
  return null;
25319
26421
  return (args, root) => {
25320
- const out = spawnSync9(bin, args, {
26422
+ const out = spawnSync11(bin, args, {
25321
26423
  cwd: root,
25322
26424
  encoding: "utf8",
25323
26425
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -25395,8 +26497,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
25395
26497
  };
25396
26498
 
25397
26499
  // packages/workit-core/src/core/youtrack.ts
25398
- import fs13 from "node:fs";
25399
- import path15 from "node:path";
26500
+ import fs14 from "node:fs";
26501
+ import path18 from "node:path";
25400
26502
 
25401
26503
  // packages/workit-core/src/core/templates.ts
25402
26504
  var repoRoot2 = assetRoot();
@@ -25409,22 +26511,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
25409
26511
  // packages/workit-core/src/core/youtrack.ts
25410
26512
  var ISSUE_RE = /^[A-Z]+-\d+$/;
25411
26513
  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");
26514
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(configDir(), "youtrack.json");
26515
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(resolveConfigDir(), "youtrack.json");
25414
26516
  var youTrackTokenModeOk = (p) => {
25415
26517
  if (process.platform === "win32")
25416
26518
  return true;
25417
- const mode = fs13.statSync(p).mode & 511;
26519
+ const mode = fs14.statSync(p).mode & 511;
25418
26520
  return mode === 384;
25419
26521
  };
25420
26522
  function readYouTrackConfig(required) {
25421
26523
  const cfgPath = youTrackConfigPath();
25422
- if (!fs13.existsSync(cfgPath)) {
26524
+ if (!fs14.existsSync(cfgPath)) {
25423
26525
  return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
25424
26526
  }
25425
26527
  let parsed;
25426
26528
  try {
25427
- parsed = JSON.parse(fs13.readFileSync(cfgPath, "utf8"));
26529
+ parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
25428
26530
  } catch {
25429
26531
  return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
25430
26532
  }
@@ -25435,11 +26537,11 @@ function readYouTrackConfig(required) {
25435
26537
  }
25436
26538
  var readYouTrackContextConfig = () => {
25437
26539
  const cfgPath = youTrackReadOnlyConfigPath();
25438
- if (!fs13.existsSync(cfgPath))
26540
+ if (!fs14.existsSync(cfgPath))
25439
26541
  return { data: {} };
25440
26542
  let parsed;
25441
26543
  try {
25442
- parsed = JSON.parse(fs13.readFileSync(cfgPath, "utf8"));
26544
+ parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
25443
26545
  } catch {
25444
26546
  return { error: "YouTrack configuration is unavailable" };
25445
26547
  }
@@ -25478,12 +26580,12 @@ var youTrackToken = () => {
25478
26580
  if ("error" in loaded)
25479
26581
  return loaded;
25480
26582
  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))
26583
+ const tokenPath = tokenFile ? path18.isAbsolute(tokenFile) ? path18.resolve(tokenFile) : path18.resolve(process.cwd(), tokenFile) : "";
26584
+ if (!tokenPath || !fs14.existsSync(tokenPath))
25483
26585
  return { error: "missing youtrack.token" };
25484
26586
  if (!youTrackTokenModeOk(tokenPath))
25485
26587
  return { error: "youtrack.token mode must be 0600" };
25486
- const token = fs13.readFileSync(tokenPath, "utf8").trim();
26588
+ const token = fs14.readFileSync(tokenPath, "utf8").trim();
25487
26589
  if (!token)
25488
26590
  return { error: "empty token file" };
25489
26591
  if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
@@ -25548,9 +26650,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
25548
26650
  } catch {
25549
26651
  continue;
25550
26652
  }
25551
- if (!fs13.existsSync(full))
26653
+ if (!fs14.existsSync(full))
25552
26654
  continue;
25553
- const text = fs13.readFileSync(full, "utf8");
26655
+ const text = fs14.readFileSync(full, "utf8");
25554
26656
  const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
25555
26657
  if (m)
25556
26658
  return m[1];
@@ -25820,22 +26922,22 @@ function resolveGitRevision(root, value) {
25820
26922
  }
25821
26923
  function resolveInside(root, candidate) {
25822
26924
  const base = realpathSync5(root);
25823
- const target = path16.resolve(base, candidate);
25824
- if (target !== base && !target.startsWith(base + path16.sep)) {
26925
+ const target = path19.resolve(base, candidate);
26926
+ if (target !== base && !target.startsWith(base + path19.sep)) {
25825
26927
  throw new Error("path must stay inside repository root");
25826
26928
  }
25827
26929
  let ancestor = target;
25828
- while (!existsSync6(ancestor))
25829
- ancestor = path16.dirname(ancestor);
26930
+ while (!existsSync7(ancestor))
26931
+ ancestor = path19.dirname(ancestor);
25830
26932
  const canonicalAncestor = realpathSync5(ancestor);
25831
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path16.sep)) {
26933
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path19.sep)) {
25832
26934
  throw new Error("path must stay inside repository root");
25833
26935
  }
25834
26936
  return target;
25835
26937
  }
25836
26938
  function run2(root, executable, args, env = {}) {
25837
26939
  const cwd = realpathSync5(root);
25838
- const result = spawnSync10(executable, args, {
26940
+ const result = spawnSync12(executable, args, {
25839
26941
  cwd,
25840
26942
  encoding: "utf8",
25841
26943
  env: { ...process.env, ...env }
@@ -25849,7 +26951,7 @@ function run2(root, executable, args, env = {}) {
25849
26951
  }
25850
26952
 
25851
26953
  // packages/workit-core/src/core/logger.ts
25852
- import os3 from "node:os";
26954
+ import os4 from "node:os";
25853
26955
  var REDACTED = "[REDACTED]";
25854
26956
  var SECRET_VALUE = /\b(?:Bearer|Basic|Digest|Token)\s+\S+/gi;
25855
26957
  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;
@@ -25891,8 +26993,8 @@ var SENSITIVE_WORDS = new Set([
25891
26993
  "query"
25892
26994
  ]);
25893
26995
  var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
25894
- var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25895
- var homePattern = new RegExp(`${escapeRegExp(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
26996
+ var escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26997
+ var homePattern = new RegExp(`${escapeRegExp2(os4.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
25896
26998
  var applyPatterns = (value) => {
25897
26999
  let out = value;
25898
27000
  out = out.replace(SECRET_VALUE, REDACTED);
@@ -25943,13 +27045,13 @@ var rootVariants = (workspaceRoot) => {
25943
27045
  if (!workspaceRoot)
25944
27046
  return [];
25945
27047
  const roots = new Set;
25946
- for (const candidate of [workspaceRoot, path17.resolve(workspaceRoot)]) {
25947
- if (path17.isAbsolute(candidate) && candidate !== path17.parse(candidate).root)
27048
+ for (const candidate of [workspaceRoot, path20.resolve(workspaceRoot)]) {
27049
+ if (path20.isAbsolute(candidate) && candidate !== path20.parse(candidate).root)
25948
27050
  roots.add(candidate);
25949
27051
  }
25950
27052
  try {
25951
27053
  const canonical = realpathSync6(workspaceRoot);
25952
- if (canonical !== path17.parse(canonical).root)
27054
+ if (canonical !== path20.parse(canonical).root)
25953
27055
  roots.add(canonical);
25954
27056
  } catch {}
25955
27057
  return [...roots].toSorted((left, right) => right.length - left.length);
@@ -25971,39 +27073,20 @@ function assertMcpHost(host) {
25971
27073
  throw new Error("MCP host must be cursor, codex_cli, or codex_desktop");
25972
27074
  }
25973
27075
  }
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) => ({
27076
+ var READ_ONLY_ACTIONS2 = new Set(["list", "inspect", "preview", "explain", "export"]);
27077
+ var requiresCallerIdentity = (input) => typeof input !== "object" || input === null || !READ_ONLY_ACTIONS2.has(String(input.action));
27078
+ var advertisedToolSchemas = (attested) => OPERATION_FAMILIES.flatMap((family) => {
27079
+ const schema = flatOperationJsonSchema(family, { readOnly: !attested });
27080
+ if (!schema)
27081
+ return [];
27082
+ return [
27083
+ {
25989
27084
  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
- };
27085
+ description: attested ? operationDescription(family) : `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
27086
+ inputSchema: schema
27087
+ }
27088
+ ];
27089
+ });
26007
27090
  var sanitizeFailure = (result, workspaceRoot) => {
26008
27091
  if (result.ok)
26009
27092
  return result;
@@ -26050,12 +27133,12 @@ var thrownResult = (tool, error, workspaceRoot) => {
26050
27133
  };
26051
27134
  function createMcpServer(host, contextProvider) {
26052
27135
  assertMcpHost(host);
26053
- const here = path17.dirname(fileURLToPath2(import.meta.url));
27136
+ const here = path20.dirname(fileURLToPath2(import.meta.url));
26054
27137
  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)));
27138
+ path20.join(here, "server.ts"),
27139
+ path20.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
27140
+ path20.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
27141
+ ].filter((file) => existsSync8(file)));
26059
27142
  let staleWarned = false;
26060
27143
  const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
26061
27144
  server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -26179,7 +27262,7 @@ function createMcpServer(host, contextProvider) {
26179
27262
  details: { operation: family }
26180
27263
  }, workspaceRoot);
26181
27264
  }
26182
- const parsed = parseOperation(family, request.params.arguments);
27265
+ const parsed = parseAdvertisedOperation(family, request.params.arguments, host);
26183
27266
  if (!parsed.ok)
26184
27267
  return resultForClient(parsed, workspaceRoot);
26185
27268
  if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
@@ -26234,8 +27317,8 @@ var main = async () => {
26234
27317
  process.exitCode = 2;
26235
27318
  }
26236
27319
  };
26237
- var entryPath = process.argv[1] ? path18.resolve(process.argv[1]) : "";
26238
- if (entryPath === path18.resolve(fileURLToPath3(import.meta.url)))
27320
+ var entryPath = process.argv[1] ? path21.resolve(process.argv[1]) : "";
27321
+ if (entryPath === path21.resolve(fileURLToPath3(import.meta.url)))
26239
27322
  await main();
26240
27323
  export {
26241
27324
  McpCapabilityUnavailableError,