@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/README.md +2 -1
- package/dist/index.js +2071 -988
- package/package.json +2 -2
- package/src/server.ts +15 -44
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
|
|
6623
|
+
import path21 from "node:path";
|
|
6624
6624
|
|
|
6625
6625
|
// packages/workit-mcp/src/server.ts
|
|
6626
|
-
import { existsSync as
|
|
6627
|
-
import
|
|
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
|
|
16787
|
-
import { existsSync as
|
|
16788
|
-
import
|
|
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
|
|
16990
|
-
|
|
16991
|
-
|
|
16992
|
-
|
|
16993
|
-
|
|
16994
|
-
|
|
16995
|
-
|
|
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(
|
|
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
|
-
|
|
17209
|
+
judgment: rawJudgment
|
|
17477
17210
|
}),
|
|
17478
|
-
preview: operation({ action: literal("preview"), ...taskId,
|
|
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
|
|
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 (!
|
|
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" || !
|
|
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
|
|
19127
|
-
var isKey = (value) =>
|
|
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) =>
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 &&
|
|
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
|
|
19254
|
-
var hostSession = (value) =>
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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 =
|
|
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 (
|
|
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
|
|
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
|
|
20885
|
-
import * as
|
|
20886
|
-
import { spawnSync as
|
|
20887
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
21229
|
-
|
|
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
|
-
|
|
21232
|
-
|
|
21233
|
-
|
|
21234
|
-
|
|
21235
|
-
|
|
21236
|
-
|
|
21237
|
-
|
|
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
|
-
|
|
21241
|
-
|
|
21242
|
-
|
|
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
|
|
21246
|
-
|
|
21247
|
-
const
|
|
21248
|
-
|
|
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
|
|
21254
|
-
|
|
21255
|
-
|
|
21256
|
-
|
|
21257
|
-
|
|
21258
|
-
|
|
21259
|
-
|
|
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
|
|
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
|
-
|
|
21264
|
-
|
|
21265
|
-
|
|
21266
|
-
|
|
21267
|
-
|
|
21268
|
-
if (
|
|
21269
|
-
|
|
21270
|
-
|
|
21271
|
-
|
|
21272
|
-
|
|
21273
|
-
|
|
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(
|
|
21277
|
-
const stagedDeleted =
|
|
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(
|
|
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 =
|
|
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 =
|
|
21941
|
+
const target = path11.join(directory, name);
|
|
21299
21942
|
let stat;
|
|
21300
21943
|
try {
|
|
21301
|
-
stat =
|
|
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 =
|
|
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 (!
|
|
21321
|
-
ancestor =
|
|
21963
|
+
while (!fs11.existsSync(ancestor) && ancestor !== root)
|
|
21964
|
+
ancestor = path11.dirname(ancestor);
|
|
21322
21965
|
try {
|
|
21323
|
-
if (!inside(root,
|
|
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 =
|
|
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 (!
|
|
21996
|
+
if (!fs11.existsSync(target))
|
|
21354
21997
|
continue;
|
|
21355
21998
|
let stat;
|
|
21356
21999
|
try {
|
|
21357
|
-
stat =
|
|
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 =
|
|
22016
|
+
const target = path11.join(checkout, item);
|
|
21374
22017
|
try {
|
|
21375
|
-
const stat =
|
|
22018
|
+
const stat = fs11.lstatSync(target);
|
|
21376
22019
|
if (stat.isSymbolicLink()) {
|
|
21377
|
-
const link =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
21558
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
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 =
|
|
22208
|
+
const below = path11.relative(root, target);
|
|
21566
22209
|
let current = root;
|
|
21567
|
-
for (const segment of below.split(
|
|
22210
|
+
for (const segment of below.split(path11.sep)) {
|
|
21568
22211
|
if (!segment)
|
|
21569
22212
|
continue;
|
|
21570
|
-
current =
|
|
21571
|
-
const stat =
|
|
21572
|
-
const real =
|
|
21573
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
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(
|
|
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
|
|
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
|
|
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 ??
|
|
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 =
|
|
22166
|
-
if (!
|
|
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 &&
|
|
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 =
|
|
22179
|
-
const dest =
|
|
22180
|
-
if (
|
|
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 =
|
|
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(
|
|
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
|
|
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({
|
|
22335
|
-
|
|
22336
|
-
|
|
22337
|
-
|
|
22338
|
-
|
|
22339
|
-
|
|
22340
|
-
|
|
22341
|
-
|
|
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
|
|
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
|
|
22528
|
-
const file =
|
|
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
|
-
|
|
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
|
|
22592
|
-
import
|
|
22593
|
-
import { spawnSync as
|
|
22594
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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:
|
|
23933
|
+
configPath: path14.resolve(cfgPath),
|
|
22751
23934
|
provider,
|
|
22752
|
-
defaultTargetBranch:
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
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
|
-
|
|
22874
|
-
|
|
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(
|
|
23165
|
-
|
|
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",
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
24974
|
+
const entry = {
|
|
23875
24975
|
id: newId(),
|
|
23876
24976
|
recordedAt: mutation.now,
|
|
23877
24977
|
provenance: provenance(this.context),
|
|
23878
|
-
data:
|
|
24978
|
+
data: judgment
|
|
23879
24979
|
};
|
|
23880
24980
|
const next = {
|
|
23881
24981
|
...current,
|
|
23882
24982
|
constraints: this.context.constraints,
|
|
23883
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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,
|
|
25560
|
+
resolve(task, judgment) {
|
|
24455
25561
|
return resolvePolicy({
|
|
24456
25562
|
intent: task.intent.data,
|
|
24457
|
-
|
|
25563
|
+
judgment,
|
|
24458
25564
|
constraints: this.context.constraints,
|
|
24459
|
-
|
|
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
|
|
24790
|
-
import { existsSync as
|
|
24791
|
-
import
|
|
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 =
|
|
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
|
|
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
|
|
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 : `${
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
24957
|
-
if (!
|
|
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(
|
|
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("./" +
|
|
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 (!
|
|
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 =
|
|
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: ${
|
|
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(
|
|
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 (
|
|
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 (
|
|
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 =
|
|
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: ${
|
|
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 =
|
|
25129
|
-
if (
|
|
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 =
|
|
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: ${
|
|
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 =
|
|
25171
|
-
if (
|
|
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 =
|
|
25180
|
-
if (
|
|
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 =
|
|
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: ${
|
|
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 (
|
|
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
|
|
26361
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
25260
26362
|
|
|
25261
26363
|
// packages/workit-core/src/core/pr-create.ts
|
|
25262
|
-
import
|
|
25263
|
-
import
|
|
25264
|
-
import { spawnSync as
|
|
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(
|
|
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 =
|
|
26380
|
+
const candidate = path17.join(dir, name);
|
|
25279
26381
|
try {
|
|
25280
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
25399
|
-
import
|
|
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 ??
|
|
25413
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
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 =
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
26540
|
+
if (!fs14.existsSync(cfgPath))
|
|
25439
26541
|
return { data: {} };
|
|
25440
26542
|
let parsed;
|
|
25441
26543
|
try {
|
|
25442
|
-
parsed = JSON.parse(
|
|
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 ?
|
|
25482
|
-
if (!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 =
|
|
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 (!
|
|
26653
|
+
if (!fs14.existsSync(full))
|
|
25552
26654
|
continue;
|
|
25553
|
-
const text =
|
|
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 =
|
|
25824
|
-
if (target !== base && !target.startsWith(base +
|
|
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 (!
|
|
25829
|
-
ancestor =
|
|
26930
|
+
while (!existsSync7(ancestor))
|
|
26931
|
+
ancestor = path19.dirname(ancestor);
|
|
25830
26932
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
25831
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
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 =
|
|
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
|
|
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
|
|
25895
|
-
var homePattern = new RegExp(`${
|
|
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,
|
|
25947
|
-
if (
|
|
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 !==
|
|
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
|
|
25975
|
-
var
|
|
25976
|
-
var
|
|
25977
|
-
|
|
25978
|
-
|
|
25979
|
-
|
|
25980
|
-
|
|
25981
|
-
|
|
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:
|
|
25992
|
-
}
|
|
25993
|
-
|
|
25994
|
-
|
|
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 =
|
|
27136
|
+
const here = path20.dirname(fileURLToPath2(import.meta.url));
|
|
26054
27137
|
const sourceMarker = markSourcesLoaded([
|
|
26055
|
-
|
|
26056
|
-
|
|
26057
|
-
|
|
26058
|
-
].filter((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 =
|
|
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] ?
|
|
26238
|
-
if (entryPath ===
|
|
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,
|