@brainervirus/workit-cursor 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/.cursor-plugin/plugin.json +1 -1
- package/README.md +3 -0
- package/dist/cursor-session-start.js +7981 -6546
- package/dist/mcp-server.js +2097 -991
- package/dist/workit-hook.js +8019 -6576
- package/package.json +3 -3
- package/skills/workit-deslop/SKILL.md +1 -1
- package/skills/workit-implement/SKILL.md +9 -4
- package/skills/workit-review/SKILL.md +2 -0
- package/skills/workit-shape/SKILL.md +1 -1
- package/skills/workit-ship/SKILL.md +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -6622,11 +6622,11 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
6622
6622
|
|
|
6623
6623
|
// packages/workit-mcp/src/index.ts
|
|
6624
6624
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
6625
|
-
import
|
|
6625
|
+
import path21 from "node:path";
|
|
6626
6626
|
|
|
6627
6627
|
// packages/workit-mcp/src/server.ts
|
|
6628
|
-
import { existsSync as
|
|
6629
|
-
import
|
|
6628
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, realpathSync as realpathSync6 } from "node:fs";
|
|
6629
|
+
import path20 from "node:path";
|
|
6630
6630
|
|
|
6631
6631
|
// node_modules/zod/v4/core/util.js
|
|
6632
6632
|
function getEnumValues(entries) {
|
|
@@ -12850,12 +12850,6 @@ var numberProcessor = (schema, ctx, _json, params) => {
|
|
|
12850
12850
|
var booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
12851
12851
|
json.type = "boolean";
|
|
12852
12852
|
};
|
|
12853
|
-
var bigintProcessor = (schema, ctx, json, params) => {
|
|
12854
|
-
handleUnrepresentable(schema, ctx, json, params, "BigInt cannot be represented in JSON Schema");
|
|
12855
|
-
};
|
|
12856
|
-
var symbolProcessor = (schema, ctx, json, params) => {
|
|
12857
|
-
handleUnrepresentable(schema, ctx, json, params, "Symbols cannot be represented in JSON Schema");
|
|
12858
|
-
};
|
|
12859
12853
|
var nullProcessor = (_schema, ctx, json, _params) => {
|
|
12860
12854
|
if (ctx.target === "openapi-3.0") {
|
|
12861
12855
|
json.type = "string";
|
|
@@ -12865,20 +12859,10 @@ var nullProcessor = (_schema, ctx, json, _params) => {
|
|
|
12865
12859
|
json.type = "null";
|
|
12866
12860
|
}
|
|
12867
12861
|
};
|
|
12868
|
-
var undefinedProcessor = (schema, ctx, json, params) => {
|
|
12869
|
-
handleUnrepresentable(schema, ctx, json, params, "Undefined cannot be represented in JSON Schema");
|
|
12870
|
-
};
|
|
12871
|
-
var voidProcessor = (schema, ctx, json, params) => {
|
|
12872
|
-
handleUnrepresentable(schema, ctx, json, params, "Void cannot be represented in JSON Schema");
|
|
12873
|
-
};
|
|
12874
12862
|
var neverProcessor = (_schema, _ctx, json, _params) => {
|
|
12875
12863
|
json.not = {};
|
|
12876
12864
|
};
|
|
12877
|
-
var anyProcessor = (_schema, _ctx, _json, _params) => {};
|
|
12878
12865
|
var unknownProcessor = (_schema, _ctx, _json, _params) => {};
|
|
12879
|
-
var dateProcessor = (schema, ctx, json, params) => {
|
|
12880
|
-
handleUnrepresentable(schema, ctx, json, params, "Date cannot be represented in JSON Schema");
|
|
12881
|
-
};
|
|
12882
12866
|
var enumProcessor = (schema, _ctx, json, _params) => {
|
|
12883
12867
|
const def = schema._zod.def;
|
|
12884
12868
|
const values = getEnumValues(def.entries);
|
|
@@ -12931,54 +12915,12 @@ var literalProcessor = (schema, ctx, json, params) => {
|
|
|
12931
12915
|
json.enum = vals;
|
|
12932
12916
|
}
|
|
12933
12917
|
};
|
|
12934
|
-
var nanProcessor = (schema, ctx, json, params) => {
|
|
12935
|
-
handleUnrepresentable(schema, ctx, json, params, "NaN cannot be represented in JSON Schema");
|
|
12936
|
-
};
|
|
12937
|
-
var templateLiteralProcessor = (schema, _ctx, json, _params) => {
|
|
12938
|
-
const _json = json;
|
|
12939
|
-
const pattern = schema._zod.pattern;
|
|
12940
|
-
if (!pattern)
|
|
12941
|
-
throw new Error("Pattern not found in template literal");
|
|
12942
|
-
_json.type = "string";
|
|
12943
|
-
_json.pattern = pattern.source;
|
|
12944
|
-
};
|
|
12945
|
-
var fileProcessor = (schema, _ctx, json, _params) => {
|
|
12946
|
-
const _json = json;
|
|
12947
|
-
_json.type = "string";
|
|
12948
|
-
_json.format = "binary";
|
|
12949
|
-
_json.contentEncoding = "binary";
|
|
12950
|
-
const { minimum, maximum, mime } = aggregateChecks(schema);
|
|
12951
|
-
if (minimum !== undefined)
|
|
12952
|
-
_json.minLength = minimum;
|
|
12953
|
-
if (maximum !== undefined)
|
|
12954
|
-
_json.maxLength = maximum;
|
|
12955
|
-
if (!mime)
|
|
12956
|
-
return;
|
|
12957
|
-
if (mime.length === 0)
|
|
12958
|
-
_json.not = {};
|
|
12959
|
-
else if (mime.length === 1)
|
|
12960
|
-
_json.contentMediaType = mime[0];
|
|
12961
|
-
else
|
|
12962
|
-
_json.anyOf = mime.map((m) => ({ contentMediaType: m }));
|
|
12963
|
-
};
|
|
12964
|
-
var successProcessor = (_schema, _ctx, json, _params) => {
|
|
12965
|
-
json.type = "boolean";
|
|
12966
|
-
};
|
|
12967
12918
|
var customProcessor = (schema, ctx, json, params) => {
|
|
12968
12919
|
handleUnrepresentable(schema, ctx, json, params, "Custom types cannot be represented in JSON Schema");
|
|
12969
12920
|
};
|
|
12970
|
-
var functionProcessor = (schema, ctx, json, params) => {
|
|
12971
|
-
handleUnrepresentable(schema, ctx, json, params, "Function types cannot be represented in JSON Schema");
|
|
12972
|
-
};
|
|
12973
12921
|
var transformProcessor = (schema, ctx, json, params) => {
|
|
12974
12922
|
handleUnrepresentable(schema, ctx, json, params, "Transforms cannot be represented in JSON Schema");
|
|
12975
12923
|
};
|
|
12976
|
-
var mapProcessor = (schema, ctx, json, params) => {
|
|
12977
|
-
handleUnrepresentable(schema, ctx, json, params, "Map cannot be represented in JSON Schema");
|
|
12978
|
-
};
|
|
12979
|
-
var setProcessor = (schema, ctx, json, params) => {
|
|
12980
|
-
handleUnrepresentable(schema, ctx, json, params, "Set cannot be represented in JSON Schema");
|
|
12981
|
-
};
|
|
12982
12924
|
var arrayProcessor = (schema, ctx, _json, params) => {
|
|
12983
12925
|
const json = _json;
|
|
12984
12926
|
const def = schema._zod.def;
|
|
@@ -13072,70 +13014,6 @@ var intersectionProcessor = (schema, ctx, json, params) => {
|
|
|
13072
13014
|
json.allOf = allOf;
|
|
13073
13015
|
ctx.intersections.push(allOf);
|
|
13074
13016
|
};
|
|
13075
|
-
var tupleProcessor = (schema, ctx, _json, params) => {
|
|
13076
|
-
const json = _json;
|
|
13077
|
-
const def = schema._zod.def;
|
|
13078
|
-
json.type = "array";
|
|
13079
|
-
const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
|
|
13080
|
-
const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
|
|
13081
|
-
const prefixItems = def.items.map((x, i) => processSchema(x, ctx, {
|
|
13082
|
-
...params,
|
|
13083
|
-
path: [...params.path, prefixPath, i]
|
|
13084
|
-
}));
|
|
13085
|
-
const rest = def.rest ? processSchema(def.rest, ctx, {
|
|
13086
|
-
...params,
|
|
13087
|
-
path: [...params.path, restPath, ...ctx.target === "openapi-3.0" ? [def.items.length] : []]
|
|
13088
|
-
}) : null;
|
|
13089
|
-
let minItems = def.items.length;
|
|
13090
|
-
while (minItems > 0) {
|
|
13091
|
-
const item = def.items[minItems - 1];
|
|
13092
|
-
const optional = ctx.io === "input" ? inputOptin(item) !== undefined : item._zod.optout === "optional";
|
|
13093
|
-
if (!optional)
|
|
13094
|
-
break;
|
|
13095
|
-
minItems--;
|
|
13096
|
-
}
|
|
13097
|
-
const maxItems = def.items.length;
|
|
13098
|
-
const isClosed = !def.rest;
|
|
13099
|
-
if (ctx.target === "draft-2020-12") {
|
|
13100
|
-
json.prefixItems = prefixItems;
|
|
13101
|
-
if (isClosed) {
|
|
13102
|
-
json.items = false;
|
|
13103
|
-
} else if (rest) {
|
|
13104
|
-
json.items = rest;
|
|
13105
|
-
}
|
|
13106
|
-
if (minItems > 0)
|
|
13107
|
-
json.minItems = minItems;
|
|
13108
|
-
if (isClosed)
|
|
13109
|
-
json.maxItems = maxItems;
|
|
13110
|
-
} else if (ctx.target === "openapi-3.0") {
|
|
13111
|
-
json.items = {
|
|
13112
|
-
anyOf: prefixItems
|
|
13113
|
-
};
|
|
13114
|
-
if (rest) {
|
|
13115
|
-
json.items.anyOf.push(rest);
|
|
13116
|
-
}
|
|
13117
|
-
if (minItems > 0)
|
|
13118
|
-
json.minItems = minItems;
|
|
13119
|
-
if (isClosed)
|
|
13120
|
-
json.maxItems = maxItems;
|
|
13121
|
-
} else {
|
|
13122
|
-
json.items = prefixItems;
|
|
13123
|
-
if (isClosed) {
|
|
13124
|
-
json.additionalItems = false;
|
|
13125
|
-
} else if (rest) {
|
|
13126
|
-
json.additionalItems = rest;
|
|
13127
|
-
}
|
|
13128
|
-
if (minItems > 0)
|
|
13129
|
-
json.minItems = minItems;
|
|
13130
|
-
if (isClosed)
|
|
13131
|
-
json.maxItems = maxItems;
|
|
13132
|
-
}
|
|
13133
|
-
const { minimum, maximum } = aggregateChecks(schema);
|
|
13134
|
-
if (typeof minimum === "number")
|
|
13135
|
-
json.minItems = minimum;
|
|
13136
|
-
if (typeof maximum === "number")
|
|
13137
|
-
json.maxItems = maximum;
|
|
13138
|
-
};
|
|
13139
13017
|
function stringifyKeyNames(bySchema, json, visited) {
|
|
13140
13018
|
if (json.$ref) {
|
|
13141
13019
|
if (visited.has(json))
|
|
@@ -13322,99 +13200,12 @@ var readonlyProcessor = (schema, ctx, json, params) => {
|
|
|
13322
13200
|
seen.ref = def.innerType;
|
|
13323
13201
|
json.readOnly = true;
|
|
13324
13202
|
};
|
|
13325
|
-
var promiseProcessor = (schema, ctx, _json, params) => {
|
|
13326
|
-
const def = schema._zod.def;
|
|
13327
|
-
processSchema(def.innerType, ctx, params);
|
|
13328
|
-
const seen = ctx.seen.get(schema);
|
|
13329
|
-
seen.ref = def.innerType;
|
|
13330
|
-
};
|
|
13331
13203
|
var optionalProcessor = (schema, ctx, _json, params) => {
|
|
13332
13204
|
const def = schema._zod.def;
|
|
13333
13205
|
processSchema(def.innerType, ctx, params);
|
|
13334
13206
|
const seen = ctx.seen.get(schema);
|
|
13335
13207
|
seen.ref = def.innerType;
|
|
13336
13208
|
};
|
|
13337
|
-
var lazyProcessor = (schema, ctx, _json, params) => {
|
|
13338
|
-
const innerType = schema._zod.innerType;
|
|
13339
|
-
processSchema(innerType, ctx, params);
|
|
13340
|
-
const seen = ctx.seen.get(schema);
|
|
13341
|
-
seen.ref = innerType;
|
|
13342
|
-
};
|
|
13343
|
-
var allProcessors = {
|
|
13344
|
-
string: stringProcessor,
|
|
13345
|
-
number: numberProcessor,
|
|
13346
|
-
boolean: booleanProcessor,
|
|
13347
|
-
bigint: bigintProcessor,
|
|
13348
|
-
symbol: symbolProcessor,
|
|
13349
|
-
null: nullProcessor,
|
|
13350
|
-
undefined: undefinedProcessor,
|
|
13351
|
-
void: voidProcessor,
|
|
13352
|
-
never: neverProcessor,
|
|
13353
|
-
any: anyProcessor,
|
|
13354
|
-
unknown: unknownProcessor,
|
|
13355
|
-
date: dateProcessor,
|
|
13356
|
-
enum: enumProcessor,
|
|
13357
|
-
literal: literalProcessor,
|
|
13358
|
-
nan: nanProcessor,
|
|
13359
|
-
template_literal: templateLiteralProcessor,
|
|
13360
|
-
file: fileProcessor,
|
|
13361
|
-
success: successProcessor,
|
|
13362
|
-
custom: customProcessor,
|
|
13363
|
-
function: functionProcessor,
|
|
13364
|
-
transform: transformProcessor,
|
|
13365
|
-
map: mapProcessor,
|
|
13366
|
-
set: setProcessor,
|
|
13367
|
-
array: arrayProcessor,
|
|
13368
|
-
object: objectProcessor,
|
|
13369
|
-
union: unionProcessor,
|
|
13370
|
-
intersection: intersectionProcessor,
|
|
13371
|
-
tuple: tupleProcessor,
|
|
13372
|
-
record: recordProcessor,
|
|
13373
|
-
nullable: nullableProcessor,
|
|
13374
|
-
nonoptional: nonoptionalProcessor,
|
|
13375
|
-
default: defaultProcessor,
|
|
13376
|
-
prefault: prefaultProcessor,
|
|
13377
|
-
catch: catchProcessor,
|
|
13378
|
-
pipe: pipeProcessor,
|
|
13379
|
-
readonly: readonlyProcessor,
|
|
13380
|
-
promise: promiseProcessor,
|
|
13381
|
-
optional: optionalProcessor,
|
|
13382
|
-
lazy: lazyProcessor
|
|
13383
|
-
};
|
|
13384
|
-
function toJSONSchema(input, params) {
|
|
13385
|
-
if ("_idmap" in input) {
|
|
13386
|
-
const registry = input;
|
|
13387
|
-
const ctx = initializeContext({ ...params, processors: allProcessors });
|
|
13388
|
-
const defs = {};
|
|
13389
|
-
for (const entry of registry._idmap.entries()) {
|
|
13390
|
-
const [_, schema] = entry;
|
|
13391
|
-
processSchema(schema, ctx);
|
|
13392
|
-
}
|
|
13393
|
-
const schemas = {};
|
|
13394
|
-
const external = {
|
|
13395
|
-
registry,
|
|
13396
|
-
uri: params?.uri,
|
|
13397
|
-
defs
|
|
13398
|
-
};
|
|
13399
|
-
ctx.external = external;
|
|
13400
|
-
for (const entry of registry._idmap.entries()) {
|
|
13401
|
-
const [key, schema] = entry;
|
|
13402
|
-
extractDefs(ctx, schema);
|
|
13403
|
-
assignProp(schemas, key, finalize(ctx, schema));
|
|
13404
|
-
}
|
|
13405
|
-
if (Object.keys(defs).length > 0) {
|
|
13406
|
-
const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions";
|
|
13407
|
-
schemas.__shared = {
|
|
13408
|
-
[defsSegment]: defs
|
|
13409
|
-
};
|
|
13410
|
-
}
|
|
13411
|
-
return { schemas };
|
|
13412
|
-
}
|
|
13413
|
-
const ctx = initializeContext({ ...params, processors: allProcessors });
|
|
13414
|
-
processSchema(input, ctx);
|
|
13415
|
-
extractDefs(ctx, input);
|
|
13416
|
-
return finalize(ctx, input);
|
|
13417
|
-
}
|
|
13418
13209
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
|
|
13419
13210
|
function isZ4Schema(s) {
|
|
13420
13211
|
const schema = s;
|
|
@@ -16785,9 +16576,9 @@ class StdioServerTransport {
|
|
|
16785
16576
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
16786
16577
|
|
|
16787
16578
|
// packages/workit-core/src/core.ts
|
|
16788
|
-
import { spawnSync as
|
|
16789
|
-
import { existsSync as
|
|
16790
|
-
import
|
|
16579
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
16580
|
+
import { existsSync as existsSync7, realpathSync as realpathSync5 } from "node:fs";
|
|
16581
|
+
import path19 from "node:path";
|
|
16791
16582
|
|
|
16792
16583
|
// packages/workit-core/src/core/task-contract.ts
|
|
16793
16584
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -16988,76 +16779,16 @@ var provenanceSchema = object2({
|
|
|
16988
16779
|
workerId: nullableId
|
|
16989
16780
|
}).strict();
|
|
16990
16781
|
var entrySchema = (data) => object2({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict();
|
|
16991
|
-
var
|
|
16992
|
-
|
|
16993
|
-
|
|
16994
|
-
|
|
16995
|
-
|
|
16996
|
-
|
|
16997
|
-
|
|
16998
|
-
code: "custom",
|
|
16999
|
-
input: ctx.value,
|
|
17000
|
-
message: "observed facts require supporting references",
|
|
17001
|
-
path: ["refs"]
|
|
17002
|
-
});
|
|
17003
|
-
});
|
|
17004
|
-
var signalSchema = object2({
|
|
17005
|
-
value: union([boolean2(), literal("unknown")]),
|
|
17006
|
-
basis: _enum(["observed", "inferred", "unknown"]),
|
|
17007
|
-
reason: text,
|
|
16782
|
+
var RISK_TIERS = ["trivial", "normal", "high"];
|
|
16783
|
+
var judgmentSchema = object2({
|
|
16784
|
+
riskTier: _enum(RISK_TIERS),
|
|
16785
|
+
behaviorChange: boolean2(),
|
|
16786
|
+
productChoiceOpen: boolean2(),
|
|
16787
|
+
needsPlan: boolean2(),
|
|
16788
|
+
note: text.nullable(),
|
|
17008
16789
|
refs: array(refSchema)
|
|
17009
|
-
}).strict().check((ctx) => {
|
|
17010
|
-
const signal = ctx.value;
|
|
17011
|
-
const valid = signal.value === "unknown" ? signal.basis === "unknown" : signal.basis === "observed" || signal.basis === "inferred";
|
|
17012
|
-
if (!valid)
|
|
17013
|
-
ctx.issues.push({
|
|
17014
|
-
code: "custom",
|
|
17015
|
-
input: signal,
|
|
17016
|
-
message: "signal value and basis disagree",
|
|
17017
|
-
path: []
|
|
17018
|
-
});
|
|
17019
|
-
if (signal.basis === "observed" && signal.refs.length === 0)
|
|
17020
|
-
ctx.issues.push({
|
|
17021
|
-
code: "custom",
|
|
17022
|
-
input: signal,
|
|
17023
|
-
message: "observed signals require supporting references",
|
|
17024
|
-
path: ["refs"]
|
|
17025
|
-
});
|
|
17026
|
-
});
|
|
17027
|
-
var signalSet = object2({
|
|
17028
|
-
approachUnknown: signalSchema,
|
|
17029
|
-
productChoiceOpen: signalSchema,
|
|
17030
|
-
behaviorChange: signalSchema,
|
|
17031
|
-
mechanicalLowRisk: signalSchema,
|
|
17032
|
-
durableAgreementNeeded: signalSchema,
|
|
17033
|
-
coordinationPlanNeeded: signalSchema,
|
|
17034
|
-
helperUseful: signalSchema,
|
|
17035
|
-
testFirstPractical: signalSchema
|
|
17036
|
-
}).strict();
|
|
17037
|
-
var consequenceSchema = object2({
|
|
17038
|
-
area: _enum([
|
|
17039
|
-
"behavior",
|
|
17040
|
-
"public_contract",
|
|
17041
|
-
"data",
|
|
17042
|
-
"security",
|
|
17043
|
-
"operations",
|
|
17044
|
-
"reversibility",
|
|
17045
|
-
"consumers"
|
|
17046
|
-
]),
|
|
17047
|
-
fact: factSchema
|
|
17048
|
-
}).strict();
|
|
17049
|
-
var verificationSchema = object2({
|
|
17050
|
-
claim: text,
|
|
17051
|
-
scope: scopeSchema,
|
|
17052
|
-
availableChecks: array(refSchema),
|
|
17053
|
-
gaps: array(text)
|
|
17054
|
-
}).strict();
|
|
17055
|
-
var assessmentSchema = object2({
|
|
17056
|
-
facts: array(factSchema),
|
|
17057
|
-
signals: signalSet,
|
|
17058
|
-
consequences: array(consequenceSchema),
|
|
17059
|
-
verification: array(verificationSchema)
|
|
17060
16790
|
}).strict();
|
|
16791
|
+
var legacyAssessmentSchema = looseObject({});
|
|
17061
16792
|
var constraintSchema = object2({
|
|
17062
16793
|
id: nonEmpty,
|
|
17063
16794
|
kind: _enum(["host", "project", "user"]),
|
|
@@ -17294,7 +17025,8 @@ var taskRecordSchema = object2({
|
|
|
17294
17025
|
progress: progressSchema,
|
|
17295
17026
|
pauseReason: text.nullable().optional(),
|
|
17296
17027
|
runtime: runtimeSchema.optional(),
|
|
17297
|
-
assessments: array(entrySchema(
|
|
17028
|
+
assessments: array(entrySchema(legacyAssessmentSchema)),
|
|
17029
|
+
judgments: array(entrySchema(judgmentSchema)).optional(),
|
|
17298
17030
|
policy: policySchema.nullable(),
|
|
17299
17031
|
policyChanges: array(policyChangeSchema),
|
|
17300
17032
|
candidates: array(candidateSchema),
|
|
@@ -17470,14 +17202,15 @@ var taskOperations = {
|
|
|
17470
17202
|
decisionIds: array(id).optional()
|
|
17471
17203
|
})
|
|
17472
17204
|
};
|
|
17205
|
+
var rawJudgment = record(string2(), unknown());
|
|
17473
17206
|
var policyOperations = {
|
|
17474
17207
|
assess: operation({
|
|
17475
17208
|
action: literal("assess"),
|
|
17476
17209
|
...taskId,
|
|
17477
17210
|
expectedRevision: revision.optional(),
|
|
17478
|
-
|
|
17211
|
+
judgment: rawJudgment
|
|
17479
17212
|
}),
|
|
17480
|
-
preview: operation({ action: literal("preview"), ...taskId,
|
|
17213
|
+
preview: operation({ action: literal("preview"), ...taskId, judgment: rawJudgment }),
|
|
17481
17214
|
explain: operation({ action: literal("explain"), ...taskId })
|
|
17482
17215
|
};
|
|
17483
17216
|
var evidenceOperations = {
|
|
@@ -17516,7 +17249,10 @@ var decisionOperations = {
|
|
|
17516
17249
|
...taskId,
|
|
17517
17250
|
expectedRevision: revision.optional(),
|
|
17518
17251
|
purpose: decisionSchema.shape.purpose,
|
|
17519
|
-
binding: decisionSchema.shape.binding
|
|
17252
|
+
binding: decisionSchema.shape.binding.extend({
|
|
17253
|
+
taskId: id.optional(),
|
|
17254
|
+
workspaceId: id.optional()
|
|
17255
|
+
}),
|
|
17520
17256
|
response: decisionSchema.shape.response,
|
|
17521
17257
|
requirementIds: array(digest)
|
|
17522
17258
|
}),
|
|
@@ -17570,6 +17306,15 @@ var operationSchemas = {
|
|
|
17570
17306
|
worker: discriminatedUnion("action", Object.values(workerOperations)),
|
|
17571
17307
|
state: discriminatedUnion("action", Object.values(stateOperations))
|
|
17572
17308
|
};
|
|
17309
|
+
var OPERATION_ACTIONS = {
|
|
17310
|
+
task: Object.keys(taskOperations),
|
|
17311
|
+
policy: Object.keys(policyOperations),
|
|
17312
|
+
evidence: Object.keys(evidenceOperations),
|
|
17313
|
+
finding: Object.keys(findingOperations),
|
|
17314
|
+
decision: Object.keys(decisionOperations),
|
|
17315
|
+
worker: Object.keys(workerOperations),
|
|
17316
|
+
state: Object.keys(stateOperations)
|
|
17317
|
+
};
|
|
17573
17318
|
var withImplicitTask = (options) => Object.values(options).map((option) => ("taskId" in option.shape) ? option.extend({ taskId: id.optional() }) : option);
|
|
17574
17319
|
var advertised = (options) => discriminatedUnion("action", withImplicitTask(options));
|
|
17575
17320
|
var advertisedOperationSchemas = {
|
|
@@ -17603,103 +17348,6 @@ function parseOperation(family, input) {
|
|
|
17603
17348
|
}));
|
|
17604
17349
|
return failure2("invalid_input", "operation input is invalid", { fields });
|
|
17605
17350
|
}
|
|
17606
|
-
function operationJsonSchema(family) {
|
|
17607
|
-
return toJSONSchema(advertisedOperationSchemas[family], { target: "draft-2020-12" });
|
|
17608
|
-
}
|
|
17609
|
-
var OPERATION_SCHEMA_DEPTH = 1;
|
|
17610
|
-
var schemaShapeDescription = (value, depth = 0) => {
|
|
17611
|
-
if (!value || typeof value !== "object")
|
|
17612
|
-
return "value";
|
|
17613
|
-
const schema = value;
|
|
17614
|
-
if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
|
|
17615
|
-
const options = Array.isArray(schema.oneOf) ? schema.oneOf : schema.anyOf;
|
|
17616
|
-
return `(${options.map((option) => schemaShapeDescription(option, depth)).join("|")})`;
|
|
17617
|
-
}
|
|
17618
|
-
if (schema.const !== undefined)
|
|
17619
|
-
return `=${JSON.stringify(schema.const)}`;
|
|
17620
|
-
if (Array.isArray(schema.enum))
|
|
17621
|
-
return `[${schema.enum.map(String).join("|")}]`;
|
|
17622
|
-
const type = Array.isArray(schema.type) ? schema.type.join("/") : schema.type;
|
|
17623
|
-
if (type === "array")
|
|
17624
|
-
return `arr<${schemaShapeDescription(schema.items, depth)}>`;
|
|
17625
|
-
if (type === "object" && schema.properties && typeof schema.properties === "object") {
|
|
17626
|
-
if (depth >= 2)
|
|
17627
|
-
return "obj";
|
|
17628
|
-
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
17629
|
-
const properties = schema.properties;
|
|
17630
|
-
const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child, depth + 1)}`);
|
|
17631
|
-
return `obj{${fields.join(",")}}`;
|
|
17632
|
-
}
|
|
17633
|
-
if (typeof type !== "string")
|
|
17634
|
-
return "?";
|
|
17635
|
-
return type.split("/").map((part) => ({ string: "str", boolean: "bool", number: "num", null: "null" })[part] ?? part).join("|");
|
|
17636
|
-
};
|
|
17637
|
-
var canonicalFieldsDescription = (value) => {
|
|
17638
|
-
if (Array.isArray(value))
|
|
17639
|
-
return `Canonical object fields: ${value.join(", ")}. Workit validates the complete nested value.`;
|
|
17640
|
-
const isZodType = (schema) => ("_zod" in schema) && typeof schema._zod === "object";
|
|
17641
|
-
const schema = isZodType(value) ? toJSONSchema(value, { target: "draft-2020-12" }) : value;
|
|
17642
|
-
if (!schema.properties || typeof schema.properties !== "object") {
|
|
17643
|
-
const variants = Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : null;
|
|
17644
|
-
return variants ? `Canonical shapes: ${variants.map((variant) => schemaShapeDescription(variant)).join(" | ")}. Full nested value is validated.` : "Canonical nested value; Workit validates the complete value.";
|
|
17645
|
-
}
|
|
17646
|
-
const properties = schema.properties;
|
|
17647
|
-
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
17648
|
-
const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child)}`);
|
|
17649
|
-
return `Fields (! required): ${fields.join(", ")}. Full nested value is validated.`;
|
|
17650
|
-
};
|
|
17651
|
-
function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, stringifiedObjects = false) {
|
|
17652
|
-
const tolerate = (projected) => {
|
|
17653
|
-
if (!stringifiedObjects)
|
|
17654
|
-
return projected;
|
|
17655
|
-
if (projected.properties && typeof projected.properties === "object")
|
|
17656
|
-
return { ...projected, type: ["object", "string"] };
|
|
17657
|
-
if (projected.items)
|
|
17658
|
-
return { ...projected, type: ["array", "string"] };
|
|
17659
|
-
if ("const" in projected && (typeof projected.const === "number" || typeof projected.const === "boolean"))
|
|
17660
|
-
return { anyOf: [projected, { const: String(projected.const) }] };
|
|
17661
|
-
if (projected.type === "object")
|
|
17662
|
-
return { ...projected, type: ["object", "string"] };
|
|
17663
|
-
if (projected.type === "array")
|
|
17664
|
-
return { ...projected, type: ["array", "string"] };
|
|
17665
|
-
return projected;
|
|
17666
|
-
};
|
|
17667
|
-
const collapse = (node, currentDepth) => {
|
|
17668
|
-
if (Array.isArray(node))
|
|
17669
|
-
return node.map((item) => collapse(item, currentDepth));
|
|
17670
|
-
if (typeof node !== "object" || node === null)
|
|
17671
|
-
return node;
|
|
17672
|
-
const record = node;
|
|
17673
|
-
if (record.properties && typeof record.properties === "object") {
|
|
17674
|
-
const properties = record.properties;
|
|
17675
|
-
const fields = Object.keys(properties);
|
|
17676
|
-
if (currentDepth >= maxDepth || fields.length === 0) {
|
|
17677
|
-
const description = stringifiedObjects ? `${canonicalFieldsDescription(record)} A JSON-encoded string is also accepted.` : canonicalFieldsDescription(record);
|
|
17678
|
-
return tolerate({ type: "object", description });
|
|
17679
|
-
}
|
|
17680
|
-
return tolerate({
|
|
17681
|
-
...record,
|
|
17682
|
-
properties: Object.fromEntries(Object.entries(record.properties).map(([key, child]) => [
|
|
17683
|
-
key,
|
|
17684
|
-
collapse(child, currentDepth + 1)
|
|
17685
|
-
]))
|
|
17686
|
-
});
|
|
17687
|
-
}
|
|
17688
|
-
if (record.items)
|
|
17689
|
-
return tolerate({ ...record, items: collapse(record.items, currentDepth) });
|
|
17690
|
-
const composed = ["anyOf", "oneOf", "allOf"].filter((key) => Array.isArray(record[key]));
|
|
17691
|
-
if (composed.length > 0) {
|
|
17692
|
-
const projected = { ...record };
|
|
17693
|
-
for (const key of composed)
|
|
17694
|
-
projected[key] = record[key].map((item) => collapse(item, currentDepth));
|
|
17695
|
-
return projected;
|
|
17696
|
-
}
|
|
17697
|
-
if ("const" in record)
|
|
17698
|
-
return tolerate({ ...record });
|
|
17699
|
-
return node;
|
|
17700
|
-
};
|
|
17701
|
-
return collapse(operationJsonSchema(family), 0);
|
|
17702
|
-
}
|
|
17703
17351
|
function canonical(value, seen) {
|
|
17704
17352
|
if (value === null || typeof value === "boolean" || typeof value === "string") {
|
|
17705
17353
|
if (typeof value === "string" && invalidUnicode(value))
|
|
@@ -17788,6 +17436,402 @@ function requirementId(input) {
|
|
|
17788
17436
|
}
|
|
17789
17437
|
});
|
|
17790
17438
|
}
|
|
17439
|
+
// packages/workit-core/src/core/operation-input.ts
|
|
17440
|
+
var str = (d, values) => ({
|
|
17441
|
+
type: "string",
|
|
17442
|
+
...values ? { enum: values } : {},
|
|
17443
|
+
...d ? { d } : {}
|
|
17444
|
+
});
|
|
17445
|
+
var list = (d) => ({ type: "array", ...d ? { d } : {} });
|
|
17446
|
+
var bool = (d) => ({ type: "boolean", ...d ? { d } : {} });
|
|
17447
|
+
var int2 = (d) => ({ type: "integer", ...d ? { d } : {} });
|
|
17448
|
+
var scopeFields = {
|
|
17449
|
+
paths: list("Scope paths (default: whole checkout)."),
|
|
17450
|
+
exclusions: list(),
|
|
17451
|
+
scope: str("Scope description.")
|
|
17452
|
+
};
|
|
17453
|
+
var refs = list("Paths or URLs.");
|
|
17454
|
+
var FLAT = {
|
|
17455
|
+
task: {
|
|
17456
|
+
fields: {
|
|
17457
|
+
objective: str(),
|
|
17458
|
+
...scopeFields,
|
|
17459
|
+
refs,
|
|
17460
|
+
reason: str(),
|
|
17461
|
+
summary: str(),
|
|
17462
|
+
nextAction: str(),
|
|
17463
|
+
blockers: list(),
|
|
17464
|
+
outcome: str(undefined, ["verified", "accepted_limitations", "stopped"]),
|
|
17465
|
+
decisionIds: list(),
|
|
17466
|
+
status: str(undefined, ["open", "closed", "all"]),
|
|
17467
|
+
query: str(),
|
|
17468
|
+
limit: int2(),
|
|
17469
|
+
view: str(undefined, ["summary", "full"])
|
|
17470
|
+
}
|
|
17471
|
+
},
|
|
17472
|
+
policy: {
|
|
17473
|
+
fields: {
|
|
17474
|
+
riskTier: str("low=trivial, medium=normal.", ["trivial", "normal", "high", "low", "medium"]),
|
|
17475
|
+
behaviorChange: bool("Observable behavior changes."),
|
|
17476
|
+
productChoiceOpen: bool("A product/preference choice is still open."),
|
|
17477
|
+
needsPlan: bool("Needs a written plan or spec first."),
|
|
17478
|
+
note: str(),
|
|
17479
|
+
refs: list("Plan/spec paths or URLs.")
|
|
17480
|
+
}
|
|
17481
|
+
},
|
|
17482
|
+
evidence: {
|
|
17483
|
+
fields: {
|
|
17484
|
+
kind: str(undefined, ["check", "review", "investigation", "artifact"]),
|
|
17485
|
+
claim: str(),
|
|
17486
|
+
result: str(undefined, ["passed", "failed", "missing", "skipped"]),
|
|
17487
|
+
summary: str(),
|
|
17488
|
+
exitCode: int2(),
|
|
17489
|
+
refs,
|
|
17490
|
+
requirementIds: list(),
|
|
17491
|
+
candidateId: str(),
|
|
17492
|
+
beforeCandidateId: str(),
|
|
17493
|
+
reviewSession: str("Reviewer session handle (review evidence).")
|
|
17494
|
+
}
|
|
17495
|
+
},
|
|
17496
|
+
finding: {
|
|
17497
|
+
fields: {
|
|
17498
|
+
claim: str(),
|
|
17499
|
+
consequence: str(),
|
|
17500
|
+
...scopeFields,
|
|
17501
|
+
candidateId: str(),
|
|
17502
|
+
refs,
|
|
17503
|
+
findingId: str(),
|
|
17504
|
+
disposition: str(undefined, ["open", "fixed", "dismissed", "deferred"]),
|
|
17505
|
+
reason: str(),
|
|
17506
|
+
evidenceIds: list(),
|
|
17507
|
+
decisionIds: list()
|
|
17508
|
+
}
|
|
17509
|
+
},
|
|
17510
|
+
decision: {
|
|
17511
|
+
fields: {
|
|
17512
|
+
purpose: str(undefined, ["design", "action", "limitation", "preference"]),
|
|
17513
|
+
response: str(undefined, ["approved", "rejected", "stated"]),
|
|
17514
|
+
presented: str("What the user was asked."),
|
|
17515
|
+
choice: str("The user's stated choice (response=stated)."),
|
|
17516
|
+
...scopeFields,
|
|
17517
|
+
refs,
|
|
17518
|
+
requirementIds: list(),
|
|
17519
|
+
decisionId: str(),
|
|
17520
|
+
reason: str()
|
|
17521
|
+
}
|
|
17522
|
+
},
|
|
17523
|
+
worker: {
|
|
17524
|
+
fields: {
|
|
17525
|
+
role: str(undefined, ["investigator", "reviewer", "implementer"]),
|
|
17526
|
+
objective: str(),
|
|
17527
|
+
...scopeFields,
|
|
17528
|
+
decisionIds: list(),
|
|
17529
|
+
requirementIds: list(),
|
|
17530
|
+
candidateId: str(),
|
|
17531
|
+
stoppingCondition: str(),
|
|
17532
|
+
workerId: str(),
|
|
17533
|
+
outcome: str(undefined, ["completed", "failed", "cancelled"]),
|
|
17534
|
+
summary: str(),
|
|
17535
|
+
evidenceIds: list(),
|
|
17536
|
+
findingIds: list(),
|
|
17537
|
+
reason: str()
|
|
17538
|
+
}
|
|
17539
|
+
},
|
|
17540
|
+
state: {
|
|
17541
|
+
fields: { bundle: { type: "object", d: "An export bundle (object or JSON string)." } }
|
|
17542
|
+
}
|
|
17543
|
+
};
|
|
17544
|
+
var COMMON = {
|
|
17545
|
+
taskId: str("Omit for this branch's task."),
|
|
17546
|
+
expectedRevision: str(),
|
|
17547
|
+
expectedWorkspaceRevision: str()
|
|
17548
|
+
};
|
|
17549
|
+
var DESCRIPTIONS = {
|
|
17550
|
+
task: "Workit task lifecycle for this branch's task (optional continuity).",
|
|
17551
|
+
policy: "Judge the work in four flat calls (riskTier, behaviorChange, productChoiceOpen, needsPlan); Workit derives the checks, verdicts, decisions and plan it needs.",
|
|
17552
|
+
evidence: "Record a note-level evidence entry; only `workit check` runs satisfy check gates.",
|
|
17553
|
+
finding: "Record or resolve a review finding.",
|
|
17554
|
+
decision: "Record or revoke a user decision (never an authorization).",
|
|
17555
|
+
worker: "Assign, report or cancel a bounded helper.",
|
|
17556
|
+
state: "Export or import a task handoff bundle."
|
|
17557
|
+
};
|
|
17558
|
+
var operationDescription = (family) => `${DESCRIPTIONS[family]} Flat fields; omit taskId and revisions.`;
|
|
17559
|
+
var READ_TASK_FIELDS = new Set(["status", "query", "limit", "view"]);
|
|
17560
|
+
var READ_ONLY_ACTIONS = new Set(["list", "inspect", "preview", "explain", "export"]);
|
|
17561
|
+
function flatOperationJsonSchema(family, options = {}) {
|
|
17562
|
+
const spec = FLAT[family];
|
|
17563
|
+
const actions = options.readOnly ? OPERATION_ACTIONS[family].filter((action) => READ_ONLY_ACTIONS.has(action)) : [...OPERATION_ACTIONS[family]];
|
|
17564
|
+
if (!actions.length)
|
|
17565
|
+
return null;
|
|
17566
|
+
const property = (field) => {
|
|
17567
|
+
const base = field.type === "array" ? { type: "array", items: { type: "string" } } : field.type === "object" ? { type: "object" } : { type: field.type };
|
|
17568
|
+
const tolerant = options.stringTolerant && field.type !== "string" ? { type: [field.type, "string"] } : {};
|
|
17569
|
+
return {
|
|
17570
|
+
...base,
|
|
17571
|
+
...tolerant,
|
|
17572
|
+
...field.enum ? { enum: field.enum } : {},
|
|
17573
|
+
...field.d ? { description: field.d } : {}
|
|
17574
|
+
};
|
|
17575
|
+
};
|
|
17576
|
+
const properties = { action: { type: "string", enum: actions } };
|
|
17577
|
+
const readField = (name) => name === "taskId" || (family === "task" ? READ_TASK_FIELDS.has(name) : family === "policy");
|
|
17578
|
+
for (const [name, field] of Object.entries({ ...COMMON, ...spec.fields }))
|
|
17579
|
+
if (options.readOnly ? readField(name) : !(family === "state" && name === "expectedRevision"))
|
|
17580
|
+
properties[name] = property(field);
|
|
17581
|
+
return {
|
|
17582
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
17583
|
+
type: "object",
|
|
17584
|
+
properties,
|
|
17585
|
+
required: ["action"]
|
|
17586
|
+
};
|
|
17587
|
+
}
|
|
17588
|
+
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17589
|
+
var decoded = (value) => {
|
|
17590
|
+
if (typeof value !== "string")
|
|
17591
|
+
return value;
|
|
17592
|
+
const trimmed = value.trim();
|
|
17593
|
+
if (!/^[[{]/.test(trimmed) && !/^(true|false|null|-?\d+)$/.test(trimmed))
|
|
17594
|
+
return value;
|
|
17595
|
+
try {
|
|
17596
|
+
return JSON.parse(trimmed);
|
|
17597
|
+
} catch {
|
|
17598
|
+
return value;
|
|
17599
|
+
}
|
|
17600
|
+
};
|
|
17601
|
+
var strings = (value) => {
|
|
17602
|
+
const plain = decoded(value);
|
|
17603
|
+
if (typeof plain === "string")
|
|
17604
|
+
return plain.trim() ? [plain] : [];
|
|
17605
|
+
return plain;
|
|
17606
|
+
};
|
|
17607
|
+
var refOf = (value) => {
|
|
17608
|
+
if (typeof value !== "string")
|
|
17609
|
+
return value;
|
|
17610
|
+
return /^https?:\/\//i.test(value) ? { kind: "external", url: value } : { kind: "file", path: value.replace(/^\.\//, ""), digest: null };
|
|
17611
|
+
};
|
|
17612
|
+
var refList = (value) => {
|
|
17613
|
+
const items = strings(value);
|
|
17614
|
+
return Array.isArray(items) ? items.map(refOf) : items;
|
|
17615
|
+
};
|
|
17616
|
+
var take = (flat, keys, map = decoded) => {
|
|
17617
|
+
const out = {};
|
|
17618
|
+
for (const key of keys)
|
|
17619
|
+
if (key in flat) {
|
|
17620
|
+
out[key] = map(flat[key]);
|
|
17621
|
+
delete flat[key];
|
|
17622
|
+
}
|
|
17623
|
+
return out;
|
|
17624
|
+
};
|
|
17625
|
+
var nest = (request, key, flat, defaults = {}) => {
|
|
17626
|
+
const nested = isObject2(decoded(request[key])) ? decoded(request[key]) : null;
|
|
17627
|
+
if (!nested && !Object.keys(flat).length)
|
|
17628
|
+
return;
|
|
17629
|
+
request[key] = { ...defaults, ...flat, ...nested };
|
|
17630
|
+
};
|
|
17631
|
+
var scopeOf = (flat, description) => {
|
|
17632
|
+
const scope = decoded(flat.scope);
|
|
17633
|
+
const has = "paths" in flat || "exclusions" in flat || typeof scope === "string";
|
|
17634
|
+
const out = isObject2(scope) ? scope : has ? {
|
|
17635
|
+
description: typeof scope === "string" ? scope : String(description ?? ""),
|
|
17636
|
+
paths: strings(flat.paths ?? ["."]),
|
|
17637
|
+
exclusions: strings(flat.exclusions ?? [])
|
|
17638
|
+
} : null;
|
|
17639
|
+
delete flat.scope;
|
|
17640
|
+
delete flat.paths;
|
|
17641
|
+
delete flat.exclusions;
|
|
17642
|
+
return out;
|
|
17643
|
+
};
|
|
17644
|
+
var defaultScope = (description) => ({
|
|
17645
|
+
description: typeof description === "string" ? description : "",
|
|
17646
|
+
paths: ["."],
|
|
17647
|
+
exclusions: []
|
|
17648
|
+
});
|
|
17649
|
+
var LISTS = [
|
|
17650
|
+
"decisionIds",
|
|
17651
|
+
"evidenceIds",
|
|
17652
|
+
"findingIds",
|
|
17653
|
+
"requirementIds",
|
|
17654
|
+
"refs",
|
|
17655
|
+
"blockers"
|
|
17656
|
+
];
|
|
17657
|
+
var ENVELOPE = new Set([
|
|
17658
|
+
"schemaVersion",
|
|
17659
|
+
"action",
|
|
17660
|
+
"taskId",
|
|
17661
|
+
"expectedRevision",
|
|
17662
|
+
"expectedWorkspaceRevision",
|
|
17663
|
+
"judgment"
|
|
17664
|
+
]);
|
|
17665
|
+
function normalizeOperationInput(family, input, host) {
|
|
17666
|
+
if (!isObject2(input))
|
|
17667
|
+
return input;
|
|
17668
|
+
const request = { ...input };
|
|
17669
|
+
if (!("schemaVersion" in request))
|
|
17670
|
+
request.schemaVersion = 1;
|
|
17671
|
+
else
|
|
17672
|
+
request.schemaVersion = decoded(request.schemaVersion);
|
|
17673
|
+
for (const key of ["limit", "exitCode"])
|
|
17674
|
+
if (key in request)
|
|
17675
|
+
request[key] = decoded(request[key]);
|
|
17676
|
+
const action = request.action;
|
|
17677
|
+
const flat = request;
|
|
17678
|
+
switch (family) {
|
|
17679
|
+
case "task": {
|
|
17680
|
+
if (action === "start" || action === "revise") {
|
|
17681
|
+
const intent = isObject2(decoded(request.intent)) ? decoded(request.intent) : {};
|
|
17682
|
+
const objective = flat.objective ?? intent.objective;
|
|
17683
|
+
const scope = scopeOf(flat, objective);
|
|
17684
|
+
const fields = take(flat, ["objective"]);
|
|
17685
|
+
const authorityRefs = "refs" in flat ? refList(flat.refs) : undefined;
|
|
17686
|
+
delete flat.refs;
|
|
17687
|
+
nest(request, "intent", {
|
|
17688
|
+
...fields,
|
|
17689
|
+
...scope ? { scope } : {},
|
|
17690
|
+
...authorityRefs ? { authorityRefs } : {}
|
|
17691
|
+
}, { scope: defaultScope(objective), authorityRefs: [] });
|
|
17692
|
+
} else if (action === "progress") {
|
|
17693
|
+
const fields = take(flat, ["summary", "nextAction"]);
|
|
17694
|
+
const blockers = "blockers" in flat ? strings(flat.blockers) : undefined;
|
|
17695
|
+
delete flat.blockers;
|
|
17696
|
+
nest(request, "progress", {
|
|
17697
|
+
...fields,
|
|
17698
|
+
...Array.isArray(blockers) ? {
|
|
17699
|
+
blockers: blockers.map((item) => typeof item === "string" ? { reason: item, dependentAction: "", refs: [] } : item)
|
|
17700
|
+
} : {}
|
|
17701
|
+
}, { nextAction: null, blockers: [] });
|
|
17702
|
+
} else if (action === "close" && "decisionIds" in flat)
|
|
17703
|
+
flat.decisionIds = strings(flat.decisionIds);
|
|
17704
|
+
break;
|
|
17705
|
+
}
|
|
17706
|
+
case "policy": {
|
|
17707
|
+
if (action === "assess" || action === "preview") {
|
|
17708
|
+
const judgment = isObject2(decoded(request.judgment)) ? { ...decoded(request.judgment) } : {};
|
|
17709
|
+
for (const key of Object.keys(flat))
|
|
17710
|
+
if (!ENVELOPE.has(key)) {
|
|
17711
|
+
if (!(key in judgment))
|
|
17712
|
+
judgment[key] = decoded(flat[key]);
|
|
17713
|
+
delete flat[key];
|
|
17714
|
+
}
|
|
17715
|
+
request.judgment = judgment;
|
|
17716
|
+
}
|
|
17717
|
+
break;
|
|
17718
|
+
}
|
|
17719
|
+
case "evidence": {
|
|
17720
|
+
if (action === "record") {
|
|
17721
|
+
const review = "reviewSession" in flat ? decoded(flat.reviewSession) : undefined;
|
|
17722
|
+
delete flat.reviewSession;
|
|
17723
|
+
const fields = take(flat, [
|
|
17724
|
+
"kind",
|
|
17725
|
+
"claim",
|
|
17726
|
+
"result",
|
|
17727
|
+
"summary",
|
|
17728
|
+
"exitCode",
|
|
17729
|
+
"candidateId",
|
|
17730
|
+
"beforeCandidateId",
|
|
17731
|
+
"reviewContext"
|
|
17732
|
+
]);
|
|
17733
|
+
if ("refs" in flat)
|
|
17734
|
+
fields.refs = refList(flat.refs);
|
|
17735
|
+
if ("requirementIds" in flat)
|
|
17736
|
+
fields.requirementIds = strings(flat.requirementIds);
|
|
17737
|
+
delete flat.refs;
|
|
17738
|
+
delete flat.requirementIds;
|
|
17739
|
+
if (typeof review === "string" && review)
|
|
17740
|
+
fields.reviewContext = { kind: "host", host, handle: review };
|
|
17741
|
+
nest(request, "evidence", fields, {
|
|
17742
|
+
summary: "",
|
|
17743
|
+
refs: [],
|
|
17744
|
+
requirementIds: [],
|
|
17745
|
+
exitCode: null,
|
|
17746
|
+
reviewContext: null
|
|
17747
|
+
});
|
|
17748
|
+
}
|
|
17749
|
+
break;
|
|
17750
|
+
}
|
|
17751
|
+
case "finding": {
|
|
17752
|
+
if (action === "record") {
|
|
17753
|
+
const scope = scopeOf(flat, flat.claim);
|
|
17754
|
+
if (scope)
|
|
17755
|
+
request.scope = scope;
|
|
17756
|
+
else if (!("scope" in request))
|
|
17757
|
+
request.scope = defaultScope(request.claim);
|
|
17758
|
+
request.refs = "refs" in flat ? refList(flat.refs) : [];
|
|
17759
|
+
}
|
|
17760
|
+
break;
|
|
17761
|
+
}
|
|
17762
|
+
case "decision": {
|
|
17763
|
+
if (action === "record") {
|
|
17764
|
+
const binding = isObject2(decoded(request.binding)) ? decoded(request.binding) : {};
|
|
17765
|
+
const presented = flat.presented ?? binding.presented;
|
|
17766
|
+
const scope = scopeOf(flat, presented);
|
|
17767
|
+
const fields = take(flat, ["presented"]);
|
|
17768
|
+
const choice = flat.choice;
|
|
17769
|
+
delete flat.choice;
|
|
17770
|
+
const contentRefs = "refs" in flat ? refList(flat.refs) : undefined;
|
|
17771
|
+
delete flat.refs;
|
|
17772
|
+
nest(request, "binding", {
|
|
17773
|
+
...fields,
|
|
17774
|
+
...scope ? { scope } : {},
|
|
17775
|
+
...contentRefs ? { contentRefs } : {},
|
|
17776
|
+
...typeof choice === "string" ? { statedChoice: { ref: "choice", text: choice } } : {}
|
|
17777
|
+
}, { scope: defaultScope(presented), contentRefs: [] });
|
|
17778
|
+
}
|
|
17779
|
+
break;
|
|
17780
|
+
}
|
|
17781
|
+
case "worker": {
|
|
17782
|
+
if (action === "assign") {
|
|
17783
|
+
const objective = flat.objective;
|
|
17784
|
+
const scope = scopeOf(flat, objective);
|
|
17785
|
+
const fields = take(flat, ["role", "objective", "candidateId", "stoppingCondition"]);
|
|
17786
|
+
for (const key of ["decisionIds", "requirementIds"])
|
|
17787
|
+
if (key in flat) {
|
|
17788
|
+
fields[key] = strings(flat[key]);
|
|
17789
|
+
delete flat[key];
|
|
17790
|
+
}
|
|
17791
|
+
nest(request, "assignment", { ...fields, ...scope ? { scope } : {} }, { scope: defaultScope(objective), decisionIds: [], requirementIds: [] });
|
|
17792
|
+
} else if (action === "report") {
|
|
17793
|
+
const fields = take(flat, ["outcome", "summary"]);
|
|
17794
|
+
for (const key of ["evidenceIds", "findingIds"])
|
|
17795
|
+
if (key in flat) {
|
|
17796
|
+
fields[key] = strings(flat[key]);
|
|
17797
|
+
delete flat[key];
|
|
17798
|
+
}
|
|
17799
|
+
nest(request, "report", fields, { evidenceIds: [], findingIds: [] });
|
|
17800
|
+
}
|
|
17801
|
+
break;
|
|
17802
|
+
}
|
|
17803
|
+
case "state": {
|
|
17804
|
+
if ("bundle" in request)
|
|
17805
|
+
request.bundle = decoded(request.bundle);
|
|
17806
|
+
break;
|
|
17807
|
+
}
|
|
17808
|
+
}
|
|
17809
|
+
const defaults = {
|
|
17810
|
+
finding: { resolve: ["evidenceIds", "decisionIds"] },
|
|
17811
|
+
decision: { record: ["requirementIds"] }
|
|
17812
|
+
};
|
|
17813
|
+
for (const key of defaults[family]?.[String(action)] ?? [])
|
|
17814
|
+
if (!(key in request))
|
|
17815
|
+
request[key] = [];
|
|
17816
|
+
for (const key of LISTS)
|
|
17817
|
+
if (key in request && typeof request[key] === "string")
|
|
17818
|
+
request[key] = strings(request[key]);
|
|
17819
|
+
return request;
|
|
17820
|
+
}
|
|
17821
|
+
function parseAdvertisedOperation(family, input, host) {
|
|
17822
|
+
const request = normalizeOperationInput(family, input, host);
|
|
17823
|
+
if (isObject2(request) && request.schemaVersion !== SCHEMA_VERSION)
|
|
17824
|
+
return failure2("unsupported_version", "unsupported schema version", { operation: family });
|
|
17825
|
+
const parsed = advertisedOperationSchemas[family].safeParse(request);
|
|
17826
|
+
if (parsed.success)
|
|
17827
|
+
return success(null, null, request);
|
|
17828
|
+
return failure2("invalid_input", "operation input is invalid", {
|
|
17829
|
+
fields: parsed.error.issues.map((issue) => ({
|
|
17830
|
+
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
17831
|
+
reason: issue.message
|
|
17832
|
+
}))
|
|
17833
|
+
});
|
|
17834
|
+
}
|
|
17791
17835
|
// packages/workit-core/src/core/task-store.ts
|
|
17792
17836
|
import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
|
|
17793
17837
|
import * as fs7 from "node:fs";
|
|
@@ -18842,7 +18886,7 @@ class LogDamage extends Error {
|
|
|
18842
18886
|
this.upgrade = upgrade;
|
|
18843
18887
|
}
|
|
18844
18888
|
}
|
|
18845
|
-
var
|
|
18889
|
+
var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18846
18890
|
var parseEvent = (line, file, offset) => {
|
|
18847
18891
|
let value;
|
|
18848
18892
|
try {
|
|
@@ -18850,11 +18894,11 @@ var parseEvent = (line, file, offset) => {
|
|
|
18850
18894
|
} catch {
|
|
18851
18895
|
throw new LogDamage(`${file}: unreadable event at byte ${offset}`);
|
|
18852
18896
|
}
|
|
18853
|
-
if (!
|
|
18897
|
+
if (!isObject3(value) || typeof value.v !== "number")
|
|
18854
18898
|
throw new LogDamage(`${file}: malformed event at byte ${offset}`);
|
|
18855
18899
|
if (value.v > EVENT_VERSION)
|
|
18856
18900
|
throw new LogDamage(`${file}: event format v${value.v} was written by a newer Workit; upgrade Workit to read this task`, true);
|
|
18857
|
-
if (typeof value.seq !== "number" || !Number.isInteger(value.seq) || typeof value.type !== "string" || typeof value.id !== "string" || typeof value.task !== "string" || !
|
|
18901
|
+
if (typeof value.seq !== "number" || !Number.isInteger(value.seq) || typeof value.type !== "string" || typeof value.id !== "string" || typeof value.task !== "string" || !isObject3(value.data))
|
|
18858
18902
|
throw new LogDamage(`${file}: malformed event at byte ${offset}`);
|
|
18859
18903
|
return value;
|
|
18860
18904
|
};
|
|
@@ -19125,13 +19169,13 @@ var KNOWN_TYPES = new Set([
|
|
|
19125
19169
|
"decision.updated",
|
|
19126
19170
|
"worker.updated"
|
|
19127
19171
|
]);
|
|
19128
|
-
var
|
|
19129
|
-
var isKey = (value) =>
|
|
19172
|
+
var isObject4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19173
|
+
var isKey = (value) => isObject4(value) && typeof value.key === "string" && (value.kind === "branch" || value.kind === "detached" || value.kind === "dir") && (value.branch === null || typeof value.branch === "string");
|
|
19130
19174
|
var BLOB_REF = "$blob";
|
|
19131
|
-
var blobRef = (value) =>
|
|
19175
|
+
var blobRef = (value) => isObject4(value) && Object.keys(value).length === 1 && typeof value[BLOB_REF] === "string" && /^[0-9a-f]{64}$/.test(value[BLOB_REF]) ? value[BLOB_REF] : null;
|
|
19132
19176
|
var blobDigest = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
19133
19177
|
var toBlob = (value, blobs) => {
|
|
19134
|
-
if (!
|
|
19178
|
+
if (!isObject4(value))
|
|
19135
19179
|
return value;
|
|
19136
19180
|
const digest = blobDigest(value);
|
|
19137
19181
|
blobs.write(digest, value);
|
|
@@ -19155,7 +19199,7 @@ var decode3 = (value, blobs) => {
|
|
|
19155
19199
|
return blobs.read(digest);
|
|
19156
19200
|
if (Array.isArray(value))
|
|
19157
19201
|
return value.map((item) => decode3(item, blobs));
|
|
19158
|
-
if (
|
|
19202
|
+
if (isObject4(value)) {
|
|
19159
19203
|
const out = {};
|
|
19160
19204
|
for (const [key, item] of Object.entries(value))
|
|
19161
19205
|
out[key] = decode3(item, blobs);
|
|
@@ -19174,7 +19218,7 @@ function reduce(state, event, blobs) {
|
|
|
19174
19218
|
}
|
|
19175
19219
|
let next = state;
|
|
19176
19220
|
if ("record" in data) {
|
|
19177
|
-
if (!
|
|
19221
|
+
if (!isObject4(data.record))
|
|
19178
19222
|
throw new LogDamage(`event ${event.seq} carries a malformed record`);
|
|
19179
19223
|
next = {
|
|
19180
19224
|
record: decode3(data.record, blobs),
|
|
@@ -19196,7 +19240,7 @@ function reduce(state, event, blobs) {
|
|
|
19196
19240
|
}
|
|
19197
19241
|
if ("key" in data)
|
|
19198
19242
|
next.key = isKey(data.key) ? data.key : null;
|
|
19199
|
-
if ("legacy" in data &&
|
|
19243
|
+
if ("legacy" in data && isObject4(data.legacy))
|
|
19200
19244
|
next.legacy = {
|
|
19201
19245
|
path: String(data.legacy.path ?? ""),
|
|
19202
19246
|
digest: String(data.legacy.digest ?? "")
|
|
@@ -19252,8 +19296,8 @@ var AUTO_COMPACT_BYTES = 2 * 1024 * 1024;
|
|
|
19252
19296
|
var TOMBSTONE = Symbol("tombstone");
|
|
19253
19297
|
var STALE_TEMPORARY_MS = 60 * 60000;
|
|
19254
19298
|
var INDEX_VERSION = 3;
|
|
19255
|
-
var
|
|
19256
|
-
var hostSession = (value) =>
|
|
19299
|
+
var isObject5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19300
|
+
var hostSession = (value) => isObject5(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
|
|
19257
19301
|
var indexEntry = (task, key, legacy, file) => {
|
|
19258
19302
|
const sessions = [];
|
|
19259
19303
|
const intent = hostSession(task.intent.provenance.session);
|
|
@@ -20066,7 +20110,7 @@ class TaskStore {
|
|
|
20066
20110
|
readSnapshot(taskId) {
|
|
20067
20111
|
try {
|
|
20068
20112
|
const value = JSON.parse(fs7.readFileSync(this.snapshotPath(taskId), "utf8"));
|
|
20069
|
-
if (!
|
|
20113
|
+
if (!isObject5(value) || value.v !== 1 || value.task !== taskId || typeof value.seq !== "number" || typeof value.lastId !== "string" || typeof value.lastStart !== "number" || typeof value.end !== "number" || !isObject5(value.record))
|
|
20070
20114
|
return null;
|
|
20071
20115
|
return value;
|
|
20072
20116
|
} catch {
|
|
@@ -20412,11 +20456,11 @@ class TaskStore {
|
|
|
20412
20456
|
readIndex() {
|
|
20413
20457
|
try {
|
|
20414
20458
|
const value = JSON.parse(fs7.readFileSync(this.paths().index, "utf8"));
|
|
20415
|
-
if (!
|
|
20459
|
+
if (!isObject5(value) || value.version !== INDEX_VERSION || !isObject5(value.tasks))
|
|
20416
20460
|
return {};
|
|
20417
20461
|
const tasks = {};
|
|
20418
20462
|
for (const [id, entry] of Object.entries(value.tasks))
|
|
20419
|
-
if (
|
|
20463
|
+
if (isObject5(entry) && entry.id === id && typeof entry.file === "string" && typeof entry.status === "string" && typeof entry.updatedAt === "string" && typeof entry.workspaceId === "string" && Array.isArray(entry.sessions) && isObject5(entry.progress) && isObject5(entry.source))
|
|
20420
20464
|
tasks[id] = entry;
|
|
20421
20465
|
return tasks;
|
|
20422
20466
|
} catch {
|
|
@@ -20460,12 +20504,12 @@ class TaskStore {
|
|
|
20460
20504
|
return this.parseValue(value, schema, label);
|
|
20461
20505
|
}
|
|
20462
20506
|
parseValue(value, schema, label) {
|
|
20463
|
-
if (
|
|
20507
|
+
if (isObject5(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
20464
20508
|
return failure2("unsupported_version", `unsupported ${label} schema version`);
|
|
20465
20509
|
const parsed = parseStoredRecord(schema, value);
|
|
20466
20510
|
if (parsed.success)
|
|
20467
20511
|
return success(null, null, parsed.data);
|
|
20468
|
-
const writerVersion =
|
|
20512
|
+
const writerVersion = isObject5(value) && isObject5(value.runtime) ? value.runtime.updatedWith : null;
|
|
20469
20513
|
if (parsed.critical.length > 0)
|
|
20470
20514
|
return failure2("recovery_required", `${label} record requires fields this Workit cannot read (${parsed.critical.join(", ")}); upgrade Workit before mutating this checkout`);
|
|
20471
20515
|
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
@@ -20667,7 +20711,7 @@ class TaskStore {
|
|
|
20667
20711
|
}
|
|
20668
20712
|
try {
|
|
20669
20713
|
const value = JSON.parse(bytes);
|
|
20670
|
-
if (
|
|
20714
|
+
if (isObject5(value) && isObject5(value.store) && value.store.format === "workit-store")
|
|
20671
20715
|
return TOMBSTONE;
|
|
20672
20716
|
} catch {}
|
|
20673
20717
|
return { bytes };
|
|
@@ -20712,7 +20756,7 @@ class TaskStore {
|
|
|
20712
20756
|
isStub(file) {
|
|
20713
20757
|
try {
|
|
20714
20758
|
const value = JSON.parse(fs7.readFileSync(file, "utf8"));
|
|
20715
|
-
return
|
|
20759
|
+
return isObject5(value) && isObject5(value.store) && value.store.format === "workit-store";
|
|
20716
20760
|
} catch {
|
|
20717
20761
|
return false;
|
|
20718
20762
|
}
|
|
@@ -20883,10 +20927,10 @@ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
|
20883
20927
|
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
20884
20928
|
|
|
20885
20929
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
20886
|
-
import { createHash as
|
|
20887
|
-
import * as
|
|
20888
|
-
import { spawnSync as
|
|
20889
|
-
import
|
|
20930
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
20931
|
+
import * as fs11 from "node:fs";
|
|
20932
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
20933
|
+
import path11 from "node:path";
|
|
20890
20934
|
|
|
20891
20935
|
// packages/workit-core/src/check-config.ts
|
|
20892
20936
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
@@ -21126,6 +21170,7 @@ var line = (run) => {
|
|
|
21126
21170
|
const value = run.stdout.trim();
|
|
21127
21171
|
return value ? value : null;
|
|
21128
21172
|
};
|
|
21173
|
+
var safeArg = (value) => value.length > 0 && !value.startsWith("-");
|
|
21129
21174
|
function headSha(cwd) {
|
|
21130
21175
|
return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
|
|
21131
21176
|
}
|
|
@@ -21217,79 +21262,677 @@ function worktreeSignal(cwd, options = {}) {
|
|
|
21217
21262
|
}
|
|
21218
21263
|
return `sig:${hash.digest("hex")}`;
|
|
21219
21264
|
}
|
|
21220
|
-
|
|
21221
|
-
|
|
21222
|
-
var digestBytes = (value) => createHash6("sha256").update(value).digest("hex");
|
|
21223
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path9.sep}`);
|
|
21224
|
-
var relative = (root, candidate) => path9.relative(root, candidate).split(path9.sep).join("/") || ".";
|
|
21225
|
-
var canonicalPath = (value) => {
|
|
21226
|
-
if (typeof value !== "string" || !value)
|
|
21265
|
+
function mergeBase(cwd, a, b) {
|
|
21266
|
+
if (!safeArg(a) || !safeArg(b))
|
|
21227
21267
|
return null;
|
|
21228
|
-
|
|
21268
|
+
return line(git2(cwd, ["merge-base", a, b]));
|
|
21269
|
+
}
|
|
21270
|
+
function patchId(cwd, base, head) {
|
|
21271
|
+
if (!safeArg(base) || !safeArg(head))
|
|
21229
21272
|
return null;
|
|
21230
|
-
const
|
|
21231
|
-
|
|
21273
|
+
const diff = git2(cwd, [
|
|
21274
|
+
"diff",
|
|
21275
|
+
"-U3",
|
|
21276
|
+
"--diff-algorithm=myers",
|
|
21277
|
+
"--indent-heuristic",
|
|
21278
|
+
"--no-color",
|
|
21279
|
+
"--no-ext-diff",
|
|
21280
|
+
"--no-textconv",
|
|
21281
|
+
"--no-renames",
|
|
21282
|
+
"--full-index",
|
|
21283
|
+
"--src-prefix=a/",
|
|
21284
|
+
"--dst-prefix=b/",
|
|
21285
|
+
`${base}...${head}`,
|
|
21286
|
+
"--"
|
|
21287
|
+
]);
|
|
21288
|
+
if (!diff.ok || !diff.stdout.trim())
|
|
21232
21289
|
return null;
|
|
21233
|
-
|
|
21234
|
-
|
|
21235
|
-
|
|
21236
|
-
|
|
21237
|
-
|
|
21238
|
-
|
|
21239
|
-
|
|
21290
|
+
const id = git2(cwd, ["patch-id", "--stable"], { input: diff.stdout });
|
|
21291
|
+
return line(id)?.split(/\s+/u)[0] ?? null;
|
|
21292
|
+
}
|
|
21293
|
+
function remoteNames(cwd) {
|
|
21294
|
+
return (git2(cwd, ["remote"]).stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
|
|
21295
|
+
}
|
|
21296
|
+
function resolveRef(cwd, ref) {
|
|
21297
|
+
if (!safeArg(ref))
|
|
21240
21298
|
return null;
|
|
21299
|
+
return line(git2(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
21300
|
+
}
|
|
21301
|
+
var config2 = (cwd, key) => line(git2(cwd, ["config", "--get", key]));
|
|
21302
|
+
function currentBranch(cwd) {
|
|
21303
|
+
return line(git2(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
|
|
21304
|
+
}
|
|
21305
|
+
function pushRemoteName(cwd, branch) {
|
|
21306
|
+
const name = branch ?? currentBranch(cwd);
|
|
21307
|
+
const remotes = remoteNames(cwd);
|
|
21308
|
+
const candidates = [
|
|
21309
|
+
name ? config2(cwd, `branch.${name}.pushRemote`) : null,
|
|
21310
|
+
config2(cwd, "remote.pushDefault"),
|
|
21311
|
+
name ? config2(cwd, `branch.${name}.remote`) : null
|
|
21312
|
+
];
|
|
21313
|
+
for (const candidate of candidates)
|
|
21314
|
+
if (candidate && candidate !== "." && remotes.includes(candidate))
|
|
21315
|
+
return candidate;
|
|
21316
|
+
if (remotes.includes("origin"))
|
|
21317
|
+
return "origin";
|
|
21318
|
+
return remotes.length === 1 ? remotes[0] : null;
|
|
21319
|
+
}
|
|
21320
|
+
|
|
21321
|
+
// packages/workit-core/src/core/policy/derive.ts
|
|
21322
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
21323
|
+
import path10 from "node:path";
|
|
21324
|
+
|
|
21325
|
+
// packages/workit-core/src/ledger.ts
|
|
21326
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
21327
|
+
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
21328
|
+
import fs10 from "node:fs";
|
|
21329
|
+
import path9 from "node:path";
|
|
21330
|
+
var LEDGER_VERSION = 1;
|
|
21331
|
+
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
21332
|
+
var FAILING = new Set(["failed", "blocked"]);
|
|
21333
|
+
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
21334
|
+
var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
|
|
21335
|
+
var err = (code, error, unblock) => ({
|
|
21336
|
+
ok: false,
|
|
21337
|
+
code,
|
|
21338
|
+
error,
|
|
21339
|
+
...unblock ? { unblock } : {}
|
|
21340
|
+
});
|
|
21341
|
+
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
21342
|
+
const run = spawnSync5("git", args, {
|
|
21343
|
+
cwd,
|
|
21344
|
+
encoding: "utf8",
|
|
21345
|
+
timeout: timeoutMs,
|
|
21346
|
+
killSignal: "SIGKILL",
|
|
21347
|
+
windowsHide: true,
|
|
21348
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
21349
|
+
env: {
|
|
21350
|
+
...process.env,
|
|
21351
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
21352
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
21353
|
+
LC_ALL: "C",
|
|
21354
|
+
LANGUAGE: "C"
|
|
21355
|
+
}
|
|
21356
|
+
});
|
|
21241
21357
|
return {
|
|
21242
|
-
|
|
21243
|
-
|
|
21244
|
-
|
|
21358
|
+
ok: run.status === 0,
|
|
21359
|
+
stdout: run.stdout ?? "",
|
|
21360
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
21245
21361
|
};
|
|
21246
21362
|
};
|
|
21247
|
-
var
|
|
21248
|
-
|
|
21249
|
-
const
|
|
21250
|
-
|
|
21251
|
-
return false;
|
|
21252
|
-
const paths = normalized.paths;
|
|
21253
|
-
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
21363
|
+
var git3 = (cwd, args) => {
|
|
21364
|
+
const run = gitRun2(cwd, args);
|
|
21365
|
+
const value = run.ok ? run.stdout.trim() : "";
|
|
21366
|
+
return value ? value : null;
|
|
21254
21367
|
};
|
|
21255
|
-
var
|
|
21256
|
-
|
|
21257
|
-
|
|
21258
|
-
|
|
21259
|
-
|
|
21260
|
-
|
|
21261
|
-
|
|
21368
|
+
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
21369
|
+
function resolveCommit(cwd, ref) {
|
|
21370
|
+
if (!safeRef(ref))
|
|
21371
|
+
return null;
|
|
21372
|
+
return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
21373
|
+
}
|
|
21374
|
+
function branchHead(cwd, branch) {
|
|
21375
|
+
if (!safeRef(branch))
|
|
21376
|
+
return null;
|
|
21377
|
+
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
21378
|
+
}
|
|
21379
|
+
function defaultBase(cwd) {
|
|
21380
|
+
const originHead = git3(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
|
|
21381
|
+
if (originHead && resolveCommit(cwd, originHead))
|
|
21382
|
+
return originHead;
|
|
21383
|
+
for (const candidate of ["origin/main", "origin/master", "main", "master"])
|
|
21384
|
+
if (resolveCommit(cwd, candidate))
|
|
21385
|
+
return candidate;
|
|
21386
|
+
return null;
|
|
21387
|
+
}
|
|
21388
|
+
function diffHash(cwd, base, head) {
|
|
21389
|
+
if (!safeRef(base) || !safeRef(head))
|
|
21390
|
+
return null;
|
|
21391
|
+
const diff = gitRun2(cwd, [
|
|
21392
|
+
"diff",
|
|
21393
|
+
"-U3",
|
|
21394
|
+
"--diff-algorithm=myers",
|
|
21395
|
+
"--indent-heuristic",
|
|
21396
|
+
"--no-color",
|
|
21397
|
+
"--no-ext-diff",
|
|
21398
|
+
"--no-textconv",
|
|
21399
|
+
"--no-renames",
|
|
21400
|
+
"--full-index",
|
|
21401
|
+
"--binary",
|
|
21402
|
+
"--src-prefix=a/",
|
|
21403
|
+
"--dst-prefix=b/",
|
|
21404
|
+
`${base}...${head}`,
|
|
21405
|
+
"--"
|
|
21406
|
+
], GIT_TIMEOUTS.worktree);
|
|
21407
|
+
if (!diff.ok || !diff.stdout)
|
|
21408
|
+
return null;
|
|
21409
|
+
const hash = createHash6("sha256");
|
|
21410
|
+
for (const line of diff.stdout.split(`
|
|
21411
|
+
`)) {
|
|
21412
|
+
if (line.startsWith("index "))
|
|
21413
|
+
continue;
|
|
21414
|
+
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
21415
|
+
hash.update(`
|
|
21416
|
+
`);
|
|
21262
21417
|
}
|
|
21263
|
-
return
|
|
21418
|
+
return `sha256:${hash.digest("hex")}`;
|
|
21419
|
+
}
|
|
21420
|
+
function storeRoot(cwd) {
|
|
21421
|
+
const location = resolveStore(path9.resolve(cwd));
|
|
21422
|
+
if (location instanceof Error)
|
|
21423
|
+
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");
|
|
21424
|
+
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
21425
|
+
}
|
|
21426
|
+
function ledgerPath(cwd) {
|
|
21427
|
+
const root = storeRoot(cwd);
|
|
21428
|
+
return root.ok ? { ok: true, value: path9.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
21429
|
+
}
|
|
21430
|
+
var NETWORK_FS_TYPES = new Set([
|
|
21431
|
+
26985,
|
|
21432
|
+
20859,
|
|
21433
|
+
4266872130,
|
|
21434
|
+
4283649346,
|
|
21435
|
+
1702057286,
|
|
21436
|
+
1397113167,
|
|
21437
|
+
12805120,
|
|
21438
|
+
16914839,
|
|
21439
|
+
1196443219,
|
|
21440
|
+
198183888
|
|
21441
|
+
]);
|
|
21442
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21443
|
+
var str2 = (value) => typeof value === "string" ? value : null;
|
|
21444
|
+
var normalizeActor = (value) => {
|
|
21445
|
+
const actor = isRecord2(value) ? value : {};
|
|
21446
|
+
return {
|
|
21447
|
+
host: str2(actor.host) ?? "unknown",
|
|
21448
|
+
session: str2(actor.session),
|
|
21449
|
+
agentId: str2(actor.agentId)
|
|
21450
|
+
};
|
|
21264
21451
|
};
|
|
21265
|
-
|
|
21266
|
-
|
|
21267
|
-
|
|
21268
|
-
|
|
21269
|
-
|
|
21270
|
-
if (
|
|
21271
|
-
|
|
21272
|
-
|
|
21273
|
-
|
|
21274
|
-
|
|
21275
|
-
|
|
21276
|
-
|
|
21452
|
+
function supersedeAllowed(by, target) {
|
|
21453
|
+
if (by.type !== target.type)
|
|
21454
|
+
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
21455
|
+
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
21456
|
+
return "only the session that wrote a row can supersede it";
|
|
21457
|
+
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
21458
|
+
return "a self verdict cannot replace an independent verdict";
|
|
21459
|
+
return null;
|
|
21460
|
+
}
|
|
21461
|
+
function readLedger(cwd) {
|
|
21462
|
+
const file = ledgerPath(cwd);
|
|
21463
|
+
if (!file.ok)
|
|
21464
|
+
return file;
|
|
21465
|
+
let text = "";
|
|
21466
|
+
try {
|
|
21467
|
+
text = fs10.readFileSync(file.value, "utf8");
|
|
21468
|
+
} catch (error) {
|
|
21469
|
+
if (error.code === "ENOENT")
|
|
21470
|
+
return { ok: true, value: { path: file.value, rows: [], skipped: 0 } };
|
|
21471
|
+
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
21472
|
+
}
|
|
21473
|
+
const rows = [];
|
|
21474
|
+
let skipped = 0;
|
|
21475
|
+
for (const raw of text.split(`
|
|
21476
|
+
`)) {
|
|
21477
|
+
const trimmed = raw.trim();
|
|
21478
|
+
if (!trimmed)
|
|
21479
|
+
continue;
|
|
21480
|
+
let parsed;
|
|
21481
|
+
try {
|
|
21482
|
+
parsed = JSON.parse(trimmed);
|
|
21483
|
+
} catch {
|
|
21484
|
+
skipped += 1;
|
|
21485
|
+
continue;
|
|
21486
|
+
}
|
|
21487
|
+
if (!isRecord2(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
|
|
21488
|
+
skipped += 1;
|
|
21489
|
+
continue;
|
|
21490
|
+
}
|
|
21491
|
+
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
21492
|
+
rows.push({
|
|
21493
|
+
...parsed,
|
|
21494
|
+
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
21495
|
+
id: parsed.id,
|
|
21496
|
+
at: parsed.at,
|
|
21497
|
+
type: parsed.type,
|
|
21498
|
+
actor: normalizeActor(parsed.actor),
|
|
21499
|
+
branch: str2(parsed.branch),
|
|
21500
|
+
head: str2(parsed.head),
|
|
21501
|
+
base: str2(parsed.base),
|
|
21502
|
+
baseSha: str2(parsed.baseSha),
|
|
21503
|
+
patchId: str2(parsed.patchId),
|
|
21504
|
+
diffHash: str2(parsed.diffHash),
|
|
21505
|
+
tree: str2(parsed.tree),
|
|
21506
|
+
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
21507
|
+
...pr === undefined ? {} : { pr },
|
|
21508
|
+
...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
21509
|
+
seq: rows.length + 1,
|
|
21510
|
+
superseded: false,
|
|
21511
|
+
supersedeIgnored: false
|
|
21512
|
+
});
|
|
21513
|
+
}
|
|
21514
|
+
const byId = new Map;
|
|
21515
|
+
for (const row of rows) {
|
|
21516
|
+
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
21517
|
+
if (target && supersedeAllowed(row, target) === null)
|
|
21518
|
+
target.superseded = true;
|
|
21519
|
+
else if (row.supersedes)
|
|
21520
|
+
row.supersedeIgnored = true;
|
|
21521
|
+
if (!byId.has(row.id))
|
|
21522
|
+
byId.set(row.id, row);
|
|
21523
|
+
}
|
|
21524
|
+
return { ok: true, value: { path: file.value, rows, skipped } };
|
|
21525
|
+
}
|
|
21526
|
+
function verdictBasis(verdict, current) {
|
|
21527
|
+
if (verdict.head && verdict.head === current.head)
|
|
21528
|
+
return "fresh";
|
|
21529
|
+
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
21530
|
+
return "carried";
|
|
21531
|
+
return "stale";
|
|
21532
|
+
}
|
|
21533
|
+
var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded;
|
|
21534
|
+
function trailerSessions(cwd, base, head) {
|
|
21535
|
+
if (!base || !head || !safeRef(base) || !safeRef(head))
|
|
21536
|
+
return [];
|
|
21537
|
+
const out = git3(cwd, [
|
|
21538
|
+
"log",
|
|
21539
|
+
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
21540
|
+
`${base}..${head}`
|
|
21541
|
+
]);
|
|
21542
|
+
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
21543
|
+
}
|
|
21544
|
+
function authorSessions(cwd, rows, branch, range = { base: null, head: null }) {
|
|
21545
|
+
const sessions = new Set;
|
|
21546
|
+
for (const row of rows) {
|
|
21547
|
+
if (row.branch !== branch || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
21548
|
+
continue;
|
|
21549
|
+
const session = str2(row.session) ?? row.actor.session;
|
|
21550
|
+
if (session)
|
|
21551
|
+
sessions.add(session);
|
|
21552
|
+
}
|
|
21553
|
+
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
21554
|
+
sessions.add(session);
|
|
21555
|
+
return sessions;
|
|
21556
|
+
}
|
|
21557
|
+
var independenceReasons = (row, authors) => {
|
|
21558
|
+
const reasons = [];
|
|
21559
|
+
if (!row.actor.session)
|
|
21560
|
+
reasons.push("no_session");
|
|
21561
|
+
else if (authors.has(row.actor.session))
|
|
21562
|
+
reasons.push("author_session");
|
|
21563
|
+
if (row.self === true && !reasons.includes("no_session"))
|
|
21564
|
+
reasons.push("self");
|
|
21565
|
+
return reasons;
|
|
21566
|
+
};
|
|
21567
|
+
function effectiveOf(rows, authors) {
|
|
21568
|
+
let effective = null;
|
|
21569
|
+
for (const row of rows) {
|
|
21570
|
+
if (!effective) {
|
|
21571
|
+
effective = row;
|
|
21572
|
+
continue;
|
|
21573
|
+
}
|
|
21574
|
+
const rowIndependent = independenceReasons(row, authors).length === 0;
|
|
21575
|
+
const effIndependent = independenceReasons(effective, authors).length === 0;
|
|
21576
|
+
if (effIndependent && !rowIndependent)
|
|
21577
|
+
continue;
|
|
21578
|
+
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session)
|
|
21579
|
+
continue;
|
|
21580
|
+
effective = row;
|
|
21581
|
+
}
|
|
21582
|
+
return effective;
|
|
21583
|
+
}
|
|
21584
|
+
function restackCarries(rows, branch, from, to) {
|
|
21585
|
+
if (!from || !to)
|
|
21586
|
+
return false;
|
|
21587
|
+
const edges = new Map;
|
|
21588
|
+
for (const row of rows)
|
|
21589
|
+
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
21590
|
+
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
21591
|
+
const seen = new Set([from]);
|
|
21592
|
+
const queue = [from];
|
|
21593
|
+
while (queue.length) {
|
|
21594
|
+
const next = queue.shift();
|
|
21595
|
+
if (next === to)
|
|
21596
|
+
return true;
|
|
21597
|
+
for (const target of edges.get(next) ?? [])
|
|
21598
|
+
if (!seen.has(target)) {
|
|
21599
|
+
seen.add(target);
|
|
21600
|
+
queue.push(target);
|
|
21601
|
+
}
|
|
21602
|
+
}
|
|
21603
|
+
return false;
|
|
21604
|
+
}
|
|
21605
|
+
function checkVerdicts(cwd, branch, rows) {
|
|
21606
|
+
const head = branchHead(cwd, branch);
|
|
21607
|
+
const fallbackBase = defaultBase(cwd);
|
|
21608
|
+
const keyCache = new Map;
|
|
21609
|
+
const keyFor = (base) => {
|
|
21610
|
+
if (!base || !head)
|
|
21611
|
+
return { patchId: null, diffHash: null };
|
|
21612
|
+
let key = keyCache.get(base);
|
|
21613
|
+
if (!key) {
|
|
21614
|
+
key = { patchId: patchId(cwd, base, head), diffHash: diffHash(cwd, base, head) };
|
|
21615
|
+
keyCache.set(base, key);
|
|
21616
|
+
}
|
|
21617
|
+
return key;
|
|
21618
|
+
};
|
|
21619
|
+
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head });
|
|
21620
|
+
const byKind = new Map;
|
|
21621
|
+
for (const row of rows)
|
|
21622
|
+
if (row.branch === branch && isLiveVerdict(row)) {
|
|
21623
|
+
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
21624
|
+
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
21625
|
+
}
|
|
21626
|
+
const verdicts = [];
|
|
21627
|
+
for (const [kind, list] of byKind) {
|
|
21628
|
+
const row = effectiveOf(list, authors);
|
|
21629
|
+
if (!row)
|
|
21630
|
+
continue;
|
|
21631
|
+
let basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
21632
|
+
if (basis === "stale" && restackCarries(rows, branch, row.head, head))
|
|
21633
|
+
basis = "carried";
|
|
21634
|
+
const current = basis !== "stale";
|
|
21635
|
+
const independence = independenceReasons(row, authors);
|
|
21636
|
+
const reasons = [
|
|
21637
|
+
...current ? [] : ["stale"],
|
|
21638
|
+
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
21639
|
+
...independence
|
|
21640
|
+
];
|
|
21641
|
+
verdicts.push({
|
|
21642
|
+
kind,
|
|
21643
|
+
basis,
|
|
21644
|
+
current,
|
|
21645
|
+
independent: independence.length === 0,
|
|
21646
|
+
accepted: reasons.length === 0,
|
|
21647
|
+
reasons,
|
|
21648
|
+
verdict: row
|
|
21649
|
+
});
|
|
21650
|
+
}
|
|
21651
|
+
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
21652
|
+
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
21653
|
+
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
21654
|
+
const newestAccepted = verdicts.findLast((entry) => entry.accepted) ?? null;
|
|
21655
|
+
const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
21656
|
+
accepted: false,
|
|
21657
|
+
verdict: failing?.verdict ?? null,
|
|
21658
|
+
reasons: failing ? ["failing_verdict"] : newestCurrent?.reasons ?? ["no_verdict"]
|
|
21659
|
+
};
|
|
21660
|
+
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
21661
|
+
return {
|
|
21662
|
+
branch,
|
|
21663
|
+
head,
|
|
21664
|
+
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
21665
|
+
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
21666
|
+
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
21667
|
+
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
21668
|
+
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
21669
|
+
accepted,
|
|
21670
|
+
verdicts,
|
|
21671
|
+
authors: [...authors].toSorted()
|
|
21672
|
+
};
|
|
21673
|
+
}
|
|
21674
|
+
var OBSERVED_ONLY = new Set(["check", ...PR_ROW_TYPES]);
|
|
21675
|
+
|
|
21676
|
+
// packages/workit-core/src/core/policy/derive.ts
|
|
21677
|
+
var RULES = {
|
|
21678
|
+
test: "check:test",
|
|
21679
|
+
self: "verdict:self",
|
|
21680
|
+
nonAuthor: "verdict:non-author",
|
|
21681
|
+
verified: "verdict:verified",
|
|
21682
|
+
product: "decision:product",
|
|
21683
|
+
plan: "plan"
|
|
21684
|
+
};
|
|
21685
|
+
var derived2 = (spec) => ({
|
|
21686
|
+
...spec,
|
|
21687
|
+
id: requirementId({
|
|
21688
|
+
ruleId: spec.ruleId,
|
|
21689
|
+
scope: spec.scope,
|
|
21690
|
+
satisfaction: spec.satisfaction,
|
|
21691
|
+
before: spec.before,
|
|
21692
|
+
dependentAction: spec.dependentAction
|
|
21693
|
+
})
|
|
21694
|
+
});
|
|
21695
|
+
var sorted = (values) => [...new Set(values)].toSorted();
|
|
21696
|
+
var normalizedScope = (scope) => ({
|
|
21697
|
+
description: scope.description,
|
|
21698
|
+
paths: sorted(scope.paths),
|
|
21699
|
+
exclusions: sorted(scope.exclusions)
|
|
21700
|
+
});
|
|
21701
|
+
function deriveRequirements(judgment, scope, constraints, verification = "self") {
|
|
21702
|
+
const out = [];
|
|
21703
|
+
const base = { scope, dependentAction: null };
|
|
21704
|
+
if (judgment.behaviorChange)
|
|
21705
|
+
out.push(derived2({
|
|
21706
|
+
...base,
|
|
21707
|
+
ruleId: RULES.test,
|
|
21708
|
+
dimension: "testing",
|
|
21709
|
+
reason: "Behavior changes: the configured test check must pass on the final tree.",
|
|
21710
|
+
satisfaction: "Run `workit check test` on the final tree.",
|
|
21711
|
+
before: "close",
|
|
21712
|
+
acceptanceAllowed: false
|
|
21713
|
+
}));
|
|
21714
|
+
if (judgment.behaviorChange && judgment.riskTier === "normal")
|
|
21715
|
+
out.push(verification === "independent" ? derived2({
|
|
21716
|
+
...base,
|
|
21717
|
+
ruleId: RULES.nonAuthor,
|
|
21718
|
+
dimension: "review",
|
|
21719
|
+
reason: "This workspace requires independent verification: a session that did not author the change records the verdict.",
|
|
21720
|
+
satisfaction: "An independent session records a passing verdict: `workit ledger verdict verified --how <what it exercised>`.",
|
|
21721
|
+
before: "close",
|
|
21722
|
+
acceptanceAllowed: false
|
|
21723
|
+
}) : derived2({
|
|
21724
|
+
...base,
|
|
21725
|
+
ruleId: RULES.self,
|
|
21726
|
+
dimension: "review",
|
|
21727
|
+
reason: "A behavior change needs a recorded verdict; the author's own is allowed and is shown as self-reviewed, never verified.",
|
|
21728
|
+
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).",
|
|
21729
|
+
before: "close",
|
|
21730
|
+
acceptanceAllowed: false
|
|
21731
|
+
}));
|
|
21732
|
+
if (judgment.riskTier === "high")
|
|
21733
|
+
out.push(derived2({
|
|
21734
|
+
...base,
|
|
21735
|
+
ruleId: RULES.verified,
|
|
21736
|
+
dimension: "review",
|
|
21737
|
+
reason: "High risk: an independent verifier must exercise the live surface.",
|
|
21738
|
+
satisfaction: "An independent verifier runs the real surface and records `workit ledger verdict verified --kind live --how <what it exercised>`.",
|
|
21739
|
+
before: "close",
|
|
21740
|
+
acceptanceAllowed: false
|
|
21741
|
+
}));
|
|
21742
|
+
if (judgment.productChoiceOpen)
|
|
21743
|
+
out.push(derived2({
|
|
21744
|
+
...base,
|
|
21745
|
+
ruleId: RULES.product,
|
|
21746
|
+
dimension: "decisions",
|
|
21747
|
+
reason: "A product or preference choice is open; the user decides it before code is written.",
|
|
21748
|
+
satisfaction: 'Ask the user (with your recommendation), then record the answer: `workit ledger decision "<choice>" --why "<reason>"`.',
|
|
21749
|
+
before: "write",
|
|
21750
|
+
acceptanceAllowed: false
|
|
21751
|
+
}));
|
|
21752
|
+
if (judgment.needsPlan || judgment.riskTier === "high")
|
|
21753
|
+
out.push(derived2({
|
|
21754
|
+
...base,
|
|
21755
|
+
ruleId: RULES.plan,
|
|
21756
|
+
dimension: "artifacts",
|
|
21757
|
+
reason: judgment.needsPlan ? "The work needs a written plan or spec before implementation." : "High-risk work needs a written plan before implementation.",
|
|
21758
|
+
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>`.",
|
|
21759
|
+
before: "write",
|
|
21760
|
+
acceptanceAllowed: true
|
|
21761
|
+
}));
|
|
21762
|
+
for (const constraint of constraints)
|
|
21763
|
+
for (const obligation of constraint.requires)
|
|
21764
|
+
out.push(derived2({
|
|
21765
|
+
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
21766
|
+
kind: obligation.kind,
|
|
21767
|
+
scope: normalizedScope(obligation.scope),
|
|
21768
|
+
refs: obligation.refs.map((ref) => JSON.stringify(ref)).toSorted(),
|
|
21769
|
+
method: obligation.method,
|
|
21770
|
+
before: obligation.before,
|
|
21771
|
+
dependentAction: obligation.dependentAction
|
|
21772
|
+
})}`,
|
|
21773
|
+
dimension: obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge"),
|
|
21774
|
+
scope: obligation.scope,
|
|
21775
|
+
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
21776
|
+
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
21777
|
+
before: obligation.before,
|
|
21778
|
+
dependentAction: obligation.dependentAction,
|
|
21779
|
+
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
21780
|
+
}));
|
|
21781
|
+
const seen = new Set;
|
|
21782
|
+
return out.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true));
|
|
21783
|
+
}
|
|
21784
|
+
var latestJudgment = (task) => task.judgments?.at(-1)?.data ?? null;
|
|
21785
|
+
var ledgerView = (root) => {
|
|
21786
|
+
let branch;
|
|
21787
|
+
let rows;
|
|
21788
|
+
return {
|
|
21789
|
+
branch: () => branch === undefined ? branch = currentBranch(root) : branch,
|
|
21790
|
+
rows: () => {
|
|
21791
|
+
if (rows === undefined) {
|
|
21792
|
+
const read = readLedger(root);
|
|
21793
|
+
rows = read.ok ? read.value.rows : [];
|
|
21794
|
+
}
|
|
21795
|
+
return rows;
|
|
21796
|
+
}
|
|
21797
|
+
};
|
|
21798
|
+
};
|
|
21799
|
+
var productDecision = (task, ledger) => {
|
|
21800
|
+
const decisions = task.decisions.filter((entry) => entry.data.revoked === null && (entry.data.response === "approved" || entry.data.response === "stated") && entry.data.purpose !== "limitation");
|
|
21801
|
+
if (decisions.length)
|
|
21802
|
+
return {
|
|
21803
|
+
met: true,
|
|
21804
|
+
reason: "the user's decision is recorded on the task",
|
|
21805
|
+
decisionIds: decisions.map((entry) => entry.id)
|
|
21806
|
+
};
|
|
21807
|
+
const since = Date.parse(task.createdAt);
|
|
21808
|
+
const branch = ledger.branch();
|
|
21809
|
+
const row = ledger.rows().findLast((item) => item.type === "decision" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
|
|
21810
|
+
return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
|
|
21811
|
+
met: false,
|
|
21812
|
+
reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or re-judge productChoiceOpen=false if it is not open)',
|
|
21813
|
+
decisionIds: []
|
|
21814
|
+
};
|
|
21815
|
+
};
|
|
21816
|
+
var planRecorded = (task, root) => {
|
|
21817
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync3(path10.resolve(root, ref.path)));
|
|
21818
|
+
if (cited)
|
|
21819
|
+
return {
|
|
21820
|
+
met: true,
|
|
21821
|
+
reason: `plan recorded: ${cited.kind === "file" ? cited.path : cited.kind === "external" ? cited.url : ""}`,
|
|
21822
|
+
decisionIds: []
|
|
21823
|
+
};
|
|
21824
|
+
const artifact = task.evidence.some((entry) => entry.data.kind === "artifact" && entry.data.result === "passed");
|
|
21825
|
+
return artifact ? { met: true, reason: "a passing artifact records the plan", decisionIds: [] } : {
|
|
21826
|
+
met: false,
|
|
21827
|
+
reason: "no plan recorded: write it (docs/ and Markdown writes stay allowed), then `workit policy assess --ref <path>`",
|
|
21828
|
+
decisionIds: []
|
|
21829
|
+
};
|
|
21830
|
+
};
|
|
21831
|
+
var verdict = (requirement, root, ledger) => {
|
|
21832
|
+
const branch = ledger.branch();
|
|
21833
|
+
if (!branch)
|
|
21834
|
+
return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
|
|
21835
|
+
const check = checkVerdicts(root, branch, ledger.rows());
|
|
21836
|
+
const failing = check.accepted.reasons.includes("failing_verdict");
|
|
21837
|
+
const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
|
|
21838
|
+
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)));
|
|
21839
|
+
if (met)
|
|
21840
|
+
return {
|
|
21841
|
+
met: true,
|
|
21842
|
+
reason: `${met.accepted ? "accepted independent" : "self-reviewed (author's own)"} ${String(met.verdict.result)} verdict ${met.verdict.id}`,
|
|
21843
|
+
decisionIds: []
|
|
21844
|
+
};
|
|
21845
|
+
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, " ");
|
|
21846
|
+
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>\``;
|
|
21847
|
+
return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
|
|
21848
|
+
};
|
|
21849
|
+
function derivedRuleStatus(task, requirement, root, ledger) {
|
|
21850
|
+
switch (requirement.ruleId) {
|
|
21851
|
+
case RULES.product:
|
|
21852
|
+
return productDecision(task, ledger);
|
|
21853
|
+
case RULES.plan:
|
|
21854
|
+
return planRecorded(task, root);
|
|
21855
|
+
case RULES.self:
|
|
21856
|
+
case RULES.nonAuthor:
|
|
21857
|
+
case RULES.verified:
|
|
21858
|
+
return verdict(requirement, root, ledger);
|
|
21859
|
+
default:
|
|
21860
|
+
return null;
|
|
21277
21861
|
}
|
|
21278
|
-
|
|
21279
|
-
|
|
21862
|
+
}
|
|
21863
|
+
|
|
21864
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
21865
|
+
var digestBytes = (value) => createHash7("sha256").update(value).digest("hex");
|
|
21866
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path11.sep}`);
|
|
21867
|
+
var relative = (root, candidate) => path11.relative(root, candidate).split(path11.sep).join("/") || ".";
|
|
21868
|
+
var canonicalPath = (value) => {
|
|
21869
|
+
if (typeof value !== "string" || !value)
|
|
21870
|
+
return null;
|
|
21871
|
+
if (value.includes("\\") || value.split("/").includes(".."))
|
|
21872
|
+
return null;
|
|
21873
|
+
const normalized = path11.posix.normalize(value);
|
|
21874
|
+
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
21875
|
+
return null;
|
|
21876
|
+
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
21877
|
+
};
|
|
21878
|
+
var canonicalScope = (scope) => {
|
|
21879
|
+
const paths = scope.paths.length ? scope.paths : ["."];
|
|
21880
|
+
const normalizedPaths = paths.map(canonicalPath);
|
|
21881
|
+
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
21882
|
+
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
21883
|
+
return null;
|
|
21884
|
+
return {
|
|
21885
|
+
description: scope.description,
|
|
21886
|
+
paths: [...new Set(normalizedPaths)],
|
|
21887
|
+
exclusions: [...new Set(normalizedExclusions)]
|
|
21888
|
+
};
|
|
21889
|
+
};
|
|
21890
|
+
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
21891
|
+
var scopeMatches = (value, scope) => {
|
|
21892
|
+
const normalized = canonicalScope(scope);
|
|
21893
|
+
if (!normalized)
|
|
21894
|
+
return false;
|
|
21895
|
+
const paths = normalized.paths;
|
|
21896
|
+
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
21897
|
+
};
|
|
21898
|
+
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
21899
|
+
var compareCodeUnits2 = (left, right) => {
|
|
21900
|
+
const length = Math.min(left.length, right.length);
|
|
21901
|
+
for (let index = 0;index < length; index += 1) {
|
|
21902
|
+
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
21903
|
+
if (difference !== 0)
|
|
21904
|
+
return difference;
|
|
21905
|
+
}
|
|
21906
|
+
return left.length - right.length;
|
|
21907
|
+
};
|
|
21908
|
+
var gitPaths = (root) => {
|
|
21909
|
+
const result = spawnSync6("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
21910
|
+
cwd: root,
|
|
21911
|
+
encoding: "buffer"
|
|
21912
|
+
});
|
|
21913
|
+
if (result.status !== 0) {
|
|
21914
|
+
const stderr = result.stderr?.toString("utf8") ?? "";
|
|
21915
|
+
return {
|
|
21916
|
+
paths: [],
|
|
21917
|
+
uncertain: !/not a git repository/i.test(stderr),
|
|
21918
|
+
git: !/not a git repository/i.test(stderr)
|
|
21919
|
+
};
|
|
21920
|
+
}
|
|
21921
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")) : [];
|
|
21922
|
+
const stagedDeleted = spawnSync6("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
21280
21923
|
if (stagedDeleted.status !== 0) {
|
|
21281
21924
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
21282
21925
|
if (!/not a git repository/i.test(stderr))
|
|
21283
21926
|
return { paths, uncertain: true, git: true };
|
|
21284
21927
|
} else if (stagedDeleted.stdout) {
|
|
21285
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
21928
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")));
|
|
21286
21929
|
}
|
|
21287
21930
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
21288
21931
|
};
|
|
21289
21932
|
var walk = (root, directory, output) => {
|
|
21290
21933
|
let names;
|
|
21291
21934
|
try {
|
|
21292
|
-
names =
|
|
21935
|
+
names = fs11.readdirSync(directory);
|
|
21293
21936
|
} catch {
|
|
21294
21937
|
return true;
|
|
21295
21938
|
}
|
|
@@ -21297,10 +21940,10 @@ var walk = (root, directory, output) => {
|
|
|
21297
21940
|
for (const name of names) {
|
|
21298
21941
|
if (name === ".git" || name === ".workit")
|
|
21299
21942
|
continue;
|
|
21300
|
-
const target =
|
|
21943
|
+
const target = path11.join(directory, name);
|
|
21301
21944
|
let stat;
|
|
21302
21945
|
try {
|
|
21303
|
-
stat =
|
|
21946
|
+
stat = fs11.lstatSync(target);
|
|
21304
21947
|
} catch {
|
|
21305
21948
|
uncertain = true;
|
|
21306
21949
|
continue;
|
|
@@ -21315,14 +21958,14 @@ var walk = (root, directory, output) => {
|
|
|
21315
21958
|
var scopeRoots = (root, scope) => {
|
|
21316
21959
|
const roots = [];
|
|
21317
21960
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
21318
|
-
const target =
|
|
21961
|
+
const target = path11.resolve(root, item);
|
|
21319
21962
|
if (!inside(root, target))
|
|
21320
21963
|
return failure2("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
21321
21964
|
let ancestor = target;
|
|
21322
|
-
while (!
|
|
21323
|
-
ancestor =
|
|
21965
|
+
while (!fs11.existsSync(ancestor) && ancestor !== root)
|
|
21966
|
+
ancestor = path11.dirname(ancestor);
|
|
21324
21967
|
try {
|
|
21325
|
-
if (!inside(root,
|
|
21968
|
+
if (!inside(root, fs11.realpathSync(ancestor)))
|
|
21326
21969
|
return failure2("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
21327
21970
|
} catch {
|
|
21328
21971
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -21334,7 +21977,7 @@ var scopeRoots = (root, scope) => {
|
|
|
21334
21977
|
function captureCandidate(root, scope, environment = []) {
|
|
21335
21978
|
let checkout;
|
|
21336
21979
|
try {
|
|
21337
|
-
checkout =
|
|
21980
|
+
checkout = fs11.realpathSync(root);
|
|
21338
21981
|
} catch {
|
|
21339
21982
|
return failure2("invalid_input", "candidate root does not exist");
|
|
21340
21983
|
}
|
|
@@ -21352,11 +21995,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
21352
21995
|
if (item === ".git" || item === ".workit")
|
|
21353
21996
|
continue;
|
|
21354
21997
|
names.add(item);
|
|
21355
|
-
if (!
|
|
21998
|
+
if (!fs11.existsSync(target))
|
|
21356
21999
|
continue;
|
|
21357
22000
|
let stat;
|
|
21358
22001
|
try {
|
|
21359
|
-
stat =
|
|
22002
|
+
stat = fs11.lstatSync(target);
|
|
21360
22003
|
} catch {
|
|
21361
22004
|
uncertain = true;
|
|
21362
22005
|
continue;
|
|
@@ -21372,17 +22015,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
21372
22015
|
for (const item of [...names].toSorted()) {
|
|
21373
22016
|
if (!scopeMatches(item, normalizedScope))
|
|
21374
22017
|
continue;
|
|
21375
|
-
const target =
|
|
22018
|
+
const target = path11.join(checkout, item);
|
|
21376
22019
|
try {
|
|
21377
|
-
const stat =
|
|
22020
|
+
const stat = fs11.lstatSync(target);
|
|
21378
22021
|
if (stat.isSymbolicLink()) {
|
|
21379
|
-
const link =
|
|
22022
|
+
const link = fs11.readlinkSync(target);
|
|
21380
22023
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
21381
22024
|
} else if (stat.isFile()) {
|
|
21382
22025
|
files.push({
|
|
21383
22026
|
path: item,
|
|
21384
22027
|
kind: "file",
|
|
21385
|
-
digest: digestBytes(
|
|
22028
|
+
digest: digestBytes(fs11.readFileSync(target)),
|
|
21386
22029
|
executable: (stat.mode & 73) !== 0
|
|
21387
22030
|
});
|
|
21388
22031
|
} else if (!stat.isDirectory()) {
|
|
@@ -21407,7 +22050,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
21407
22050
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
21408
22051
|
if (values.some(({ value }) => value === null))
|
|
21409
22052
|
completeness = "uncertain";
|
|
21410
|
-
const headResult =
|
|
22053
|
+
const headResult = spawnSync6("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
21411
22054
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
21412
22055
|
completeness = "uncertain";
|
|
21413
22056
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -21547,7 +22190,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
21547
22190
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
21548
22191
|
let root;
|
|
21549
22192
|
try {
|
|
21550
|
-
root =
|
|
22193
|
+
root = fs11.realpathSync(checkoutRoot);
|
|
21551
22194
|
} catch {
|
|
21552
22195
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
21553
22196
|
}
|
|
@@ -21556,23 +22199,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
21556
22199
|
continue;
|
|
21557
22200
|
if (!reference.digest)
|
|
21558
22201
|
return failure2("invalid_input", "document references require a byte digest");
|
|
21559
|
-
const target =
|
|
21560
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
22202
|
+
const target = path11.resolve(root, reference.path);
|
|
22203
|
+
if (target !== root && !target.startsWith(`${root}${path11.sep}`))
|
|
21561
22204
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
21562
22205
|
fields: [
|
|
21563
22206
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
21564
22207
|
]
|
|
21565
22208
|
});
|
|
21566
22209
|
try {
|
|
21567
|
-
const below =
|
|
22210
|
+
const below = path11.relative(root, target);
|
|
21568
22211
|
let current = root;
|
|
21569
|
-
for (const segment of below.split(
|
|
22212
|
+
for (const segment of below.split(path11.sep)) {
|
|
21570
22213
|
if (!segment)
|
|
21571
22214
|
continue;
|
|
21572
|
-
current =
|
|
21573
|
-
const stat =
|
|
21574
|
-
const real =
|
|
21575
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
22215
|
+
current = path11.join(current, segment);
|
|
22216
|
+
const stat = fs11.lstatSync(current);
|
|
22217
|
+
const real = fs11.realpathSync(current);
|
|
22218
|
+
if (real !== root && !real.startsWith(`${root}${path11.sep}`))
|
|
21576
22219
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
21577
22220
|
fields: [
|
|
21578
22221
|
{
|
|
@@ -21588,7 +22231,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
21588
22231
|
if (current !== target && !stat.isDirectory())
|
|
21589
22232
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
21590
22233
|
}
|
|
21591
|
-
if (digestBytes(
|
|
22234
|
+
if (digestBytes(fs11.readFileSync(target)) !== reference.digest)
|
|
21592
22235
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
21593
22236
|
} catch {
|
|
21594
22237
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -21673,7 +22316,35 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
21673
22316
|
decisionIds: [],
|
|
21674
22317
|
reason: "stored policy version is unsupported; reassessment is required"
|
|
21675
22318
|
}));
|
|
22319
|
+
const ledger = ledgerView(checkoutRoot);
|
|
21676
22320
|
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
22321
|
+
const derived = derivedRuleStatus(task, requirement, checkoutRoot, ledger);
|
|
22322
|
+
if (derived) {
|
|
22323
|
+
if (derived.met)
|
|
22324
|
+
return {
|
|
22325
|
+
requirementId: requirement.id,
|
|
22326
|
+
status: "satisfied",
|
|
22327
|
+
evidenceIds: [],
|
|
22328
|
+
decisionIds: derived.decisionIds,
|
|
22329
|
+
reason: derived.reason
|
|
22330
|
+
};
|
|
22331
|
+
const waived = requirement.acceptanceAllowed ? applicableDecision(task, workspace, requirement, checkoutRoot) : [];
|
|
22332
|
+
if (waived.length)
|
|
22333
|
+
return {
|
|
22334
|
+
requirementId: requirement.id,
|
|
22335
|
+
status: "accepted_limitation",
|
|
22336
|
+
evidenceIds: [],
|
|
22337
|
+
decisionIds: waived.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
|
|
22338
|
+
reason: "an applicable approved limitation permits the missing evidence"
|
|
22339
|
+
};
|
|
22340
|
+
return {
|
|
22341
|
+
requirementId: requirement.id,
|
|
22342
|
+
status: "unsatisfied",
|
|
22343
|
+
evidenceIds: [],
|
|
22344
|
+
decisionIds: [],
|
|
22345
|
+
reason: derived.reason
|
|
22346
|
+
};
|
|
22347
|
+
}
|
|
21677
22348
|
const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
|
|
21678
22349
|
if (gate?.satisfied.length)
|
|
21679
22350
|
return {
|
|
@@ -22088,14 +22759,14 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
22088
22759
|
import {
|
|
22089
22760
|
copyFileSync as copyFileSync2,
|
|
22090
22761
|
cpSync as cpSync2,
|
|
22091
|
-
existsSync as
|
|
22762
|
+
existsSync as existsSync5,
|
|
22092
22763
|
mkdirSync as mkdirSync2,
|
|
22093
22764
|
readFileSync as readFileSync3,
|
|
22094
22765
|
readdirSync as readdirSync3,
|
|
22095
22766
|
writeFileSync as writeFileSync2
|
|
22096
22767
|
} from "node:fs";
|
|
22097
22768
|
import os2 from "node:os";
|
|
22098
|
-
import
|
|
22769
|
+
import path12 from "node:path";
|
|
22099
22770
|
|
|
22100
22771
|
// packages/workit-core/src/core/boundary.ts
|
|
22101
22772
|
import { statSync as statSync2 } from "node:fs";
|
|
@@ -22154,7 +22825,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
22154
22825
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
22155
22826
|
};
|
|
22156
22827
|
};
|
|
22157
|
-
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
22828
|
+
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path12.join(process.env.XDG_CONFIG_HOME || path12.join(os2.homedir(), ".config"), "workit");
|
|
22158
22829
|
var migratedDir = null;
|
|
22159
22830
|
var migrationFailed = false;
|
|
22160
22831
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -22164,12 +22835,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
22164
22835
|
migratedDir = dir;
|
|
22165
22836
|
return dir;
|
|
22166
22837
|
}
|
|
22167
|
-
const legacy =
|
|
22168
|
-
if (!
|
|
22838
|
+
const legacy = path12.join(process.env.XDG_CONFIG_HOME || path12.join(os2.homedir(), ".config"), "workflow-toolkit");
|
|
22839
|
+
if (!existsSync5(legacy)) {
|
|
22169
22840
|
migratedDir = dir;
|
|
22170
22841
|
return dir;
|
|
22171
22842
|
}
|
|
22172
|
-
if (!migrationFailed &&
|
|
22843
|
+
if (!migrationFailed && existsSync5(dir)) {
|
|
22173
22844
|
migratedDir = dir;
|
|
22174
22845
|
return dir;
|
|
22175
22846
|
}
|
|
@@ -22177,9 +22848,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
22177
22848
|
mkdirSync2(dir, { recursive: true });
|
|
22178
22849
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
22179
22850
|
for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
|
|
22180
|
-
const src =
|
|
22181
|
-
const dest =
|
|
22182
|
-
if (
|
|
22851
|
+
const src = path12.join(legacy, entry.name);
|
|
22852
|
+
const dest = path12.join(dir, entry.name);
|
|
22853
|
+
if (existsSync5(dest))
|
|
22183
22854
|
continue;
|
|
22184
22855
|
try {
|
|
22185
22856
|
if (entry.isDirectory())
|
|
@@ -22250,7 +22921,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
22250
22921
|
};
|
|
22251
22922
|
};
|
|
22252
22923
|
var readConfigTyped = (dir) => {
|
|
22253
|
-
const file =
|
|
22924
|
+
const file = path12.join(dir ?? configDir(), "config.json");
|
|
22254
22925
|
return parseConfigResult(readSafe(file), file);
|
|
22255
22926
|
};
|
|
22256
22927
|
var readConfig = () => {
|
|
@@ -22259,6 +22930,8 @@ var readConfig = () => {
|
|
|
22259
22930
|
throw new Error(result.error);
|
|
22260
22931
|
return result.config ?? DEFAULTS;
|
|
22261
22932
|
};
|
|
22933
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path12.join(configDir(), "vcs.json");
|
|
22934
|
+
var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
|
|
22262
22935
|
var resolveBranchPolicy = (config, workspace) => {
|
|
22263
22936
|
const wp = workspace?.branchPolicy ?? {};
|
|
22264
22937
|
const preset = Object.hasOwn(PRESETS, wp.preset) ? wp.preset : config.branchPolicy?.preset ?? "gitflow";
|
|
@@ -22266,7 +22939,7 @@ var resolveBranchPolicy = (config, workspace) => {
|
|
|
22266
22939
|
allowed: wp.allowed,
|
|
22267
22940
|
protectedNames: wp.protected
|
|
22268
22941
|
}, config);
|
|
22269
|
-
const allowed = merged.allowed.map(
|
|
22942
|
+
const allowed = merged.allowed.map(branchGlobPattern);
|
|
22270
22943
|
return {
|
|
22271
22944
|
preset,
|
|
22272
22945
|
allowed,
|
|
@@ -22283,20 +22956,11 @@ var COMMIT_PRESETS = [
|
|
|
22283
22956
|
"custom",
|
|
22284
22957
|
"auto"
|
|
22285
22958
|
];
|
|
22286
|
-
var resolveCommitPolicy = (config, workspace) => {
|
|
22287
|
-
const wp = workspace?.commitPolicy ?? {};
|
|
22288
|
-
if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
|
|
22289
|
-
return {
|
|
22290
|
-
preset: wp.preset,
|
|
22291
|
-
...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
|
|
22292
|
-
};
|
|
22293
|
-
}
|
|
22294
|
-
return config.commitPolicy ?? { preset: "conventional" };
|
|
22295
|
-
};
|
|
22296
22959
|
|
|
22297
22960
|
// packages/workit-core/src/core/workspaces.ts
|
|
22961
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
22298
22962
|
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
22299
|
-
import
|
|
22963
|
+
import path13 from "node:path";
|
|
22300
22964
|
var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
|
|
22301
22965
|
var branchPreset = custom((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
|
|
22302
22966
|
var commitPreset = custom((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
|
|
@@ -22330,26 +22994,31 @@ var versionSourceSchema = discriminatedUnion("kind", [
|
|
|
22330
22994
|
object2({ kind: literal("manual") }).strict()
|
|
22331
22995
|
]);
|
|
22332
22996
|
var releaseTrackSchema = object2({
|
|
22333
|
-
strategy: branchPreset,
|
|
22997
|
+
strategy: branchPreset.optional(),
|
|
22334
22998
|
productionBranch: nonBlank,
|
|
22335
22999
|
integrationBranch: nonBlank,
|
|
22336
|
-
naming: object2({
|
|
22337
|
-
|
|
22338
|
-
|
|
22339
|
-
|
|
22340
|
-
|
|
22341
|
-
|
|
22342
|
-
|
|
22343
|
-
|
|
23000
|
+
naming: object2({
|
|
23001
|
+
feature: nonBlank.optional(),
|
|
23002
|
+
release: nonBlank.optional(),
|
|
23003
|
+
hotfix: nonBlank.optional()
|
|
23004
|
+
}).passthrough().optional(),
|
|
23005
|
+
baseBranch: nonBlank.optional(),
|
|
23006
|
+
mergeBackBranches: array(nonBlank).optional(),
|
|
23007
|
+
pullRequestTarget: nonBlank.optional(),
|
|
23008
|
+
tagNamespace: string2().optional(),
|
|
23009
|
+
versionSource: versionSourceSchema.optional(),
|
|
23010
|
+
requiredChecks: array(nonBlank).optional(),
|
|
23011
|
+
critical: array(nonBlank).optional()
|
|
23012
|
+
}).passthrough().superRefine((track, context) => {
|
|
22344
23013
|
for (const [field, values] of [
|
|
22345
|
-
["mergeBackBranches", track.mergeBackBranches],
|
|
22346
|
-
["requiredChecks", track.requiredChecks]
|
|
23014
|
+
["mergeBackBranches", track.mergeBackBranches ?? []],
|
|
23015
|
+
["requiredChecks", track.requiredChecks ?? []]
|
|
22347
23016
|
]) {
|
|
22348
23017
|
if (new Set(values).size !== values.length) {
|
|
22349
23018
|
context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
|
|
22350
23019
|
}
|
|
22351
23020
|
}
|
|
22352
|
-
if (track.versionSource
|
|
23021
|
+
if (track.versionSource?.kind === "package-json" && (path13.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
|
|
22353
23022
|
context.addIssue({
|
|
22354
23023
|
code: "custom",
|
|
22355
23024
|
path: ["versionSource", "path"],
|
|
@@ -22368,6 +23037,7 @@ var workspaceConfigSchema = object2({
|
|
|
22368
23037
|
}).passthrough().optional(),
|
|
22369
23038
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
22370
23039
|
defaultEndpoint: _enum(["commit", "pr"]).optional(),
|
|
23040
|
+
verification: _enum(["self", "independent"]).optional(),
|
|
22371
23041
|
autonomy: object2(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
22372
23042
|
kind,
|
|
22373
23043
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -22408,6 +23078,72 @@ var workspacesFileSchema = object2({ workspaces: array(workspaceConfigSchema).op
|
|
|
22408
23078
|
names.add(workspace.name);
|
|
22409
23079
|
}
|
|
22410
23080
|
});
|
|
23081
|
+
var workspacesRevision = (contents) => createHash8("sha256").update(contents ?? "<missing>").digest("hex");
|
|
23082
|
+
var validateWorkspacesDocument = (parsed, file) => {
|
|
23083
|
+
if (!isConfigObject(parsed)) {
|
|
23084
|
+
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
23085
|
+
}
|
|
23086
|
+
const validated = workspacesFileSchema.safeParse(parsed, { reportInput: true });
|
|
23087
|
+
if (!validated.success) {
|
|
23088
|
+
const detail = validated.error.issues.map((issue) => {
|
|
23089
|
+
const field = String(issue.path[issue.path.length - 1] ?? "");
|
|
23090
|
+
const attempted = ["provider", "preset", "strategy"].includes(field) && typeof issue.input === "string" ? ` (${JSON.stringify(issue.input)})` : "";
|
|
23091
|
+
return `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}${attempted}`;
|
|
23092
|
+
}).join("; ");
|
|
23093
|
+
return {
|
|
23094
|
+
status: "invalid",
|
|
23095
|
+
path: file,
|
|
23096
|
+
entries: [],
|
|
23097
|
+
error: `${file} has invalid workspace configuration: ${detail}`
|
|
23098
|
+
};
|
|
23099
|
+
}
|
|
23100
|
+
for (const [index, workspace] of (validated.data.workspaces ?? []).entries()) {
|
|
23101
|
+
const glob = validateWorkspaceGlob(workspace.glob);
|
|
23102
|
+
if (!glob.ok) {
|
|
23103
|
+
return {
|
|
23104
|
+
status: "invalid",
|
|
23105
|
+
path: file,
|
|
23106
|
+
entries: [],
|
|
23107
|
+
error: `${file} workspaces.${index}.glob: ${glob.error}`
|
|
23108
|
+
};
|
|
23109
|
+
}
|
|
23110
|
+
}
|
|
23111
|
+
return {
|
|
23112
|
+
status: "valid",
|
|
23113
|
+
path: file,
|
|
23114
|
+
entries: validated.data.workspaces ?? [],
|
|
23115
|
+
document: parsed
|
|
23116
|
+
};
|
|
23117
|
+
};
|
|
23118
|
+
var readWorkspacesResult = (dir = configDir()) => {
|
|
23119
|
+
const file = path13.join(dir, "workspaces.json");
|
|
23120
|
+
let raw;
|
|
23121
|
+
try {
|
|
23122
|
+
raw = readFileSync4(file, "utf8");
|
|
23123
|
+
} catch (error) {
|
|
23124
|
+
if (error.code === "ENOENT")
|
|
23125
|
+
return { status: "missing", path: file, entries: [], revision: workspacesRevision(null) };
|
|
23126
|
+
return {
|
|
23127
|
+
status: "malformed",
|
|
23128
|
+
path: file,
|
|
23129
|
+
entries: [],
|
|
23130
|
+
error: `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
|
|
23131
|
+
};
|
|
23132
|
+
}
|
|
23133
|
+
let parsed;
|
|
23134
|
+
try {
|
|
23135
|
+
parsed = JSON.parse(raw);
|
|
23136
|
+
} catch {
|
|
23137
|
+
return {
|
|
23138
|
+
status: "malformed",
|
|
23139
|
+
path: file,
|
|
23140
|
+
entries: [],
|
|
23141
|
+
revision: workspacesRevision(raw),
|
|
23142
|
+
error: `${file} is not valid JSON`
|
|
23143
|
+
};
|
|
23144
|
+
}
|
|
23145
|
+
return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
|
|
23146
|
+
};
|
|
22411
23147
|
var globToRegExp = (glob) => {
|
|
22412
23148
|
let out = "";
|
|
22413
23149
|
for (let i = 0;i < glob.length; i++) {
|
|
@@ -22505,6 +23241,30 @@ var selectWorkspaceMatch = (matches, cwd, workspaceName) => {
|
|
|
22505
23241
|
}
|
|
22506
23242
|
return mostSpecific[0];
|
|
22507
23243
|
};
|
|
23244
|
+
var resolveWorkspaceFromEntries = (cwd, entries, workspaceName) => {
|
|
23245
|
+
const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
|
|
23246
|
+
const matches = [];
|
|
23247
|
+
for (const ws of entries) {
|
|
23248
|
+
const glob = ws.glob.replaceAll("\\", "/");
|
|
23249
|
+
const canonical = canonicalGlob(glob);
|
|
23250
|
+
let matched = false;
|
|
23251
|
+
for (const target of targets) {
|
|
23252
|
+
if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
|
|
23253
|
+
matched = true;
|
|
23254
|
+
break;
|
|
23255
|
+
}
|
|
23256
|
+
}
|
|
23257
|
+
if (matched)
|
|
23258
|
+
matches.push(ws);
|
|
23259
|
+
}
|
|
23260
|
+
return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
|
|
23261
|
+
};
|
|
23262
|
+
var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
|
|
23263
|
+
const result = readWorkspacesResult(dir);
|
|
23264
|
+
if (result.status === "malformed" || result.status === "invalid")
|
|
23265
|
+
throw new Error(result.error);
|
|
23266
|
+
return resolveWorkspaceFromEntries(cwd, result.entries, workspaceName);
|
|
23267
|
+
};
|
|
22508
23268
|
var runtimeWorkspacePolicySchema = (kind) => {
|
|
22509
23269
|
const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
|
|
22510
23270
|
const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object2({
|
|
@@ -22526,8 +23286,8 @@ var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd,
|
|
|
22526
23286
|
var runtimeWorkspaceIndexSchema = object2({
|
|
22527
23287
|
workspaces: array(object2({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
22528
23288
|
}).passthrough();
|
|
22529
|
-
var
|
|
22530
|
-
const file =
|
|
23289
|
+
var matchRuntimeWorkspace = (cwd) => {
|
|
23290
|
+
const file = path13.join(configDir(), "workspaces.json");
|
|
22531
23291
|
let raw;
|
|
22532
23292
|
try {
|
|
22533
23293
|
raw = readFileSync4(file, "utf8");
|
|
@@ -22561,8 +23321,17 @@ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
|
22561
23321
|
});
|
|
22562
23322
|
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
22563
23323
|
const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
|
|
22564
|
-
|
|
23324
|
+
return selected ? { file, selected } : null;
|
|
23325
|
+
};
|
|
23326
|
+
var resolveRuntimeReleaseTracks = (cwd) => {
|
|
23327
|
+
const matched = matchRuntimeWorkspace(cwd);
|
|
23328
|
+
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
23329
|
+
};
|
|
23330
|
+
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
23331
|
+
const matched = matchRuntimeWorkspace(cwd);
|
|
23332
|
+
if (!matched)
|
|
22565
23333
|
return null;
|
|
23334
|
+
const { file, selected } = matched;
|
|
22566
23335
|
const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
|
|
22567
23336
|
if (!policy.success) {
|
|
22568
23337
|
const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
|
|
@@ -22588,15 +23357,410 @@ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
|
|
|
22588
23357
|
source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
|
|
22589
23358
|
};
|
|
22590
23359
|
};
|
|
23360
|
+
function workspaceDefaultTarget(cwd, globalDefault) {
|
|
23361
|
+
const ws = resolveRuntimeWorkspaceVcs(cwd);
|
|
23362
|
+
const wp = ws?.branchPolicy ?? {};
|
|
23363
|
+
const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
|
|
23364
|
+
const selected = resolveRuntimeWorkspacePolicy(cwd, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
23365
|
+
const policyDefault = resolveBranchPolicy(readConfig(), selected.policy ? { branchPolicy: selected.policy } : null).defaultTargetBranch;
|
|
23366
|
+
return String(ws?.vcs?.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : globalDefault ?? policyDefault) ?? "develop");
|
|
23367
|
+
}
|
|
22591
23368
|
|
|
22592
23369
|
// packages/workit-core/src/core/vcs-config.ts
|
|
22593
|
-
import
|
|
22594
|
-
import
|
|
22595
|
-
import { spawnSync as
|
|
22596
|
-
|
|
23370
|
+
import fs12 from "node:fs";
|
|
23371
|
+
import path14 from "node:path";
|
|
23372
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
23373
|
+
|
|
23374
|
+
// packages/workit-core/src/core/release-tracks.ts
|
|
23375
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
23376
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
23377
|
+
var KNOWN_FIELDS = new Set([
|
|
23378
|
+
"strategy",
|
|
23379
|
+
"productionBranch",
|
|
23380
|
+
"integrationBranch",
|
|
23381
|
+
"naming",
|
|
23382
|
+
"baseBranch",
|
|
23383
|
+
"mergeBackBranches",
|
|
23384
|
+
"pullRequestTarget",
|
|
23385
|
+
"tagNamespace",
|
|
23386
|
+
"versionSource",
|
|
23387
|
+
"requiredChecks",
|
|
23388
|
+
"critical"
|
|
23389
|
+
]);
|
|
23390
|
+
var BRANCH_FIELDS = ["productionBranch", "integrationBranch", "baseBranch", "pullRequestTarget"];
|
|
23391
|
+
var PLACEHOLDER = /\{[^}]*\}/gu;
|
|
23392
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23393
|
+
var branchName = (value) => typeof value === "string" && value.trim() && !value.trim().startsWith("-") ? value.trim() : null;
|
|
23394
|
+
var namedBranches = (entry) => [
|
|
23395
|
+
...BRANCH_FIELDS.map((field) => branchName(entry[field])),
|
|
23396
|
+
...Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : []
|
|
23397
|
+
].filter((name) => name !== null);
|
|
23398
|
+
var literalOf = (template) => template.replace(PLACEHOLDER, "").replace(/[*/]/gu, "").trim();
|
|
23399
|
+
function readReleaseTracks(raw) {
|
|
23400
|
+
const out = { tracks: [], issues: [], error: null, protectedBranches: [] };
|
|
23401
|
+
if (raw === undefined || raw === null)
|
|
23402
|
+
return out;
|
|
23403
|
+
if (!isRecord3(raw)) {
|
|
23404
|
+
out.issues.push("releaseTracks is not an object; ignored");
|
|
23405
|
+
return out;
|
|
23406
|
+
}
|
|
23407
|
+
const fatal = [];
|
|
23408
|
+
const protectedNames = new Set;
|
|
23409
|
+
for (const [name, entry] of Object.entries(raw).toSorted(([a], [b]) => a < b ? -1 : 1)) {
|
|
23410
|
+
const label = `release track ${JSON.stringify(name)}`;
|
|
23411
|
+
if (!isRecord3(entry)) {
|
|
23412
|
+
out.issues.push(`${label} is not an object; ignored`);
|
|
23413
|
+
continue;
|
|
23414
|
+
}
|
|
23415
|
+
for (const branch of namedBranches(entry))
|
|
23416
|
+
protectedNames.add(branch);
|
|
23417
|
+
if (!name.trim()) {
|
|
23418
|
+
out.issues.push("a release track with a blank name was ignored");
|
|
23419
|
+
continue;
|
|
23420
|
+
}
|
|
23421
|
+
let critical = [];
|
|
23422
|
+
if (Array.isArray(entry.critical))
|
|
23423
|
+
critical = entry.critical.filter((item) => typeof item === "string");
|
|
23424
|
+
else if (entry.critical !== undefined)
|
|
23425
|
+
out.issues.push(`${label} critical: must be a list of field names; ignored`);
|
|
23426
|
+
const isCritical = (field) => critical.some((item) => field === item || field.startsWith(`${item}.`));
|
|
23427
|
+
const problem = (field, message) => {
|
|
23428
|
+
if (isCritical(field))
|
|
23429
|
+
fatal.push(`${label} ${field}: ${message}`);
|
|
23430
|
+
else
|
|
23431
|
+
out.issues.push(`${label} ${field}: ${message}`);
|
|
23432
|
+
};
|
|
23433
|
+
for (const key of Object.keys(entry))
|
|
23434
|
+
if (!KNOWN_FIELDS.has(key))
|
|
23435
|
+
problem(key, "unknown field, ignored");
|
|
23436
|
+
const productionBranch = branchName(entry.productionBranch);
|
|
23437
|
+
const integrationBranch = branchName(entry.integrationBranch);
|
|
23438
|
+
if (!productionBranch)
|
|
23439
|
+
problem("productionBranch", "missing or not a branch name");
|
|
23440
|
+
if (!integrationBranch)
|
|
23441
|
+
problem("integrationBranch", "missing or not a branch name");
|
|
23442
|
+
const optionalBranch = (field) => {
|
|
23443
|
+
if (entry[field] === undefined)
|
|
23444
|
+
return null;
|
|
23445
|
+
const value = branchName(entry[field]);
|
|
23446
|
+
if (!value)
|
|
23447
|
+
problem(field, "not a branch name; using the integration branch");
|
|
23448
|
+
return value;
|
|
23449
|
+
};
|
|
23450
|
+
const baseBranch = optionalBranch("baseBranch");
|
|
23451
|
+
const pullRequestTarget = optionalBranch("pullRequestTarget");
|
|
23452
|
+
let mergeBackBranches = [];
|
|
23453
|
+
if (entry.mergeBackBranches !== undefined) {
|
|
23454
|
+
const list = Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : [null];
|
|
23455
|
+
if (list.some((item) => item === null))
|
|
23456
|
+
problem("mergeBackBranches", "must be a list of branch names; ignored");
|
|
23457
|
+
else
|
|
23458
|
+
mergeBackBranches = [...new Set(list)];
|
|
23459
|
+
}
|
|
23460
|
+
const naming = {};
|
|
23461
|
+
if (entry.naming !== undefined) {
|
|
23462
|
+
if (!isRecord3(entry.naming))
|
|
23463
|
+
problem("naming", "not an object; ignored");
|
|
23464
|
+
else
|
|
23465
|
+
for (const kind of ["feature", "release", "hotfix"]) {
|
|
23466
|
+
const template = entry.naming[kind];
|
|
23467
|
+
if (template === undefined)
|
|
23468
|
+
continue;
|
|
23469
|
+
if (typeof template !== "string" || !template.trim())
|
|
23470
|
+
problem(`naming.${kind}`, "not a template string; ignored");
|
|
23471
|
+
else if (!literalOf(template))
|
|
23472
|
+
problem(`naming.${kind}`, "needs literal text besides placeholders; ignored");
|
|
23473
|
+
else
|
|
23474
|
+
naming[kind] = template.trim();
|
|
23475
|
+
}
|
|
23476
|
+
}
|
|
23477
|
+
if (!productionBranch || !integrationBranch) {
|
|
23478
|
+
out.issues.push(`${label} was skipped: it needs a productionBranch and integrationBranch`);
|
|
23479
|
+
continue;
|
|
23480
|
+
}
|
|
23481
|
+
out.tracks.push({
|
|
23482
|
+
name,
|
|
23483
|
+
productionBranch,
|
|
23484
|
+
integrationBranch,
|
|
23485
|
+
baseBranch: baseBranch ?? integrationBranch,
|
|
23486
|
+
pullRequestTarget: pullRequestTarget ?? integrationBranch,
|
|
23487
|
+
mergeBackBranches,
|
|
23488
|
+
naming
|
|
23489
|
+
});
|
|
23490
|
+
}
|
|
23491
|
+
out.protectedBranches = [...protectedNames];
|
|
23492
|
+
if (fatal.length)
|
|
23493
|
+
out.error = `workspace releaseTracks cannot be read by this Workit (${fatal.join("; ")}); upgrade Workit or fix the track`;
|
|
23494
|
+
return out;
|
|
23495
|
+
}
|
|
23496
|
+
var IMPLICIT = "";
|
|
23497
|
+
var ownedBy = (tracks, branch) => tracks.filter((track) => [track.integrationBranch, track.productionBranch, track.baseBranch].includes(branch));
|
|
23498
|
+
var shortRef = (ref, remotes) => {
|
|
23499
|
+
const bare = ref.replace(/^refs\/heads\//u, "").replace(/^refs\/remotes\/[^/]+\//u, "");
|
|
23500
|
+
const remote = remotes.find((name) => bare.startsWith(`${name}/`));
|
|
23501
|
+
return remote ? bare.slice(remote.length + 1) : bare;
|
|
23502
|
+
};
|
|
23503
|
+
var MAX_RECORDED_HOPS = 10;
|
|
23504
|
+
function resolveReleaseTrack(input) {
|
|
23505
|
+
const tracks = [...input.tracks];
|
|
23506
|
+
const issues = [...input.issues ?? []];
|
|
23507
|
+
const requested = input.requested?.trim();
|
|
23508
|
+
if (tracks.length === 0) {
|
|
23509
|
+
if (requested)
|
|
23510
|
+
return {
|
|
23511
|
+
status: "invalid",
|
|
23512
|
+
error: `no release tracks configured for this workspace; --track ${requested} cannot apply`,
|
|
23513
|
+
issues
|
|
23514
|
+
};
|
|
23515
|
+
return { status: "none", issues };
|
|
23516
|
+
}
|
|
23517
|
+
const names = tracks.map((track) => track.name).join(", ");
|
|
23518
|
+
let envProblem = null;
|
|
23519
|
+
const done = (track, source, detail, warnings = [], ambiguous = null) => ({
|
|
23520
|
+
status: "resolved",
|
|
23521
|
+
track: track && track.name !== IMPLICIT ? track : null,
|
|
23522
|
+
source,
|
|
23523
|
+
detail,
|
|
23524
|
+
warnings: [...issues, ...envProblem ? [envProblem] : [], ...warnings],
|
|
23525
|
+
tracks,
|
|
23526
|
+
blocking: envProblem ?? ambiguous
|
|
23527
|
+
});
|
|
23528
|
+
if (requested) {
|
|
23529
|
+
const named = tracks.find((track) => track.name === requested);
|
|
23530
|
+
if (!named)
|
|
23531
|
+
return {
|
|
23532
|
+
status: "invalid",
|
|
23533
|
+
error: `no release track ${JSON.stringify(requested)}; configured: ${names}`,
|
|
23534
|
+
issues
|
|
23535
|
+
};
|
|
23536
|
+
return done(named, "flag", `track ${named.name} was requested explicitly`);
|
|
23537
|
+
}
|
|
23538
|
+
const owned = (branch) => branch !== null && tracks.some((track) => [
|
|
23539
|
+
track.integrationBranch,
|
|
23540
|
+
track.productionBranch,
|
|
23541
|
+
track.baseBranch,
|
|
23542
|
+
track.pullRequestTarget
|
|
23543
|
+
].includes(branch));
|
|
23544
|
+
const implicit = input.defaultBranch && !owned(input.defaultBranch) ? {
|
|
23545
|
+
name: IMPLICIT,
|
|
23546
|
+
productionBranch: input.defaultBranch,
|
|
23547
|
+
integrationBranch: input.defaultBranch,
|
|
23548
|
+
baseBranch: input.defaultBranch,
|
|
23549
|
+
pullRequestTarget: input.defaultBranch,
|
|
23550
|
+
mergeBackBranches: [],
|
|
23551
|
+
naming: {}
|
|
23552
|
+
} : null;
|
|
23553
|
+
const candidates = implicit ? [...tracks, implicit] : tracks;
|
|
23554
|
+
const label = (track) => track.name === IMPLICIT ? `the workspace default line (${track.integrationBranch})` : `track ${track.name}`;
|
|
23555
|
+
const fallback = (why) => {
|
|
23556
|
+
const byDefault = input.defaultBranch ? tracks.filter((track) => track.integrationBranch === input.defaultBranch || track.pullRequestTarget === input.defaultBranch) : [];
|
|
23557
|
+
const track = byDefault.length === 1 ? byDefault[0] : null;
|
|
23558
|
+
const target = track ? `track ${track.name}` : `default target ${input.defaultBranch ?? "(none)"}`;
|
|
23559
|
+
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;
|
|
23560
|
+
return done(track, "default", `${why}; using the workspace ${target}`, [
|
|
23561
|
+
`release track not determined: ${why}; using the workspace ${target} (pass --track <name> to choose)`
|
|
23562
|
+
], ambiguous);
|
|
23563
|
+
};
|
|
23564
|
+
const branch = input.branch;
|
|
23565
|
+
if (branch) {
|
|
23566
|
+
const owners = ownedBy(candidates, branch);
|
|
23567
|
+
if (owners.length === 1)
|
|
23568
|
+
return done(owners[0], "branch", `${branch} is a branch of ${label(owners[0])}`);
|
|
23569
|
+
if (owners.length > 1)
|
|
23570
|
+
return fallback(`${branch} belongs to more than one track (${owners.map((track) => track.name).join(", ")})`);
|
|
23571
|
+
}
|
|
23572
|
+
const env = input.envRequested?.trim();
|
|
23573
|
+
if (env) {
|
|
23574
|
+
const named = tracks.find((track) => track.name === env);
|
|
23575
|
+
if (named)
|
|
23576
|
+
return done(named, "env", `WORKFLOW_RELEASE_TRACK selects track ${named.name}`);
|
|
23577
|
+
envProblem = `WORKFLOW_RELEASE_TRACK=${env} names no release track of this workspace (${names}); ignored`;
|
|
23578
|
+
}
|
|
23579
|
+
if (!branch)
|
|
23580
|
+
return fallback("HEAD is detached");
|
|
23581
|
+
if (tracks.length === 1 && !implicit)
|
|
23582
|
+
return done(tracks[0], "only-track", `${tracks[0].name} is the only track`);
|
|
23583
|
+
let remotes = null;
|
|
23584
|
+
const short = (ref) => shortRef(ref, remotes ??= input.probe.remotes());
|
|
23585
|
+
const ownerOf = (ref) => {
|
|
23586
|
+
const owner = ownedBy(candidates, short(ref));
|
|
23587
|
+
return owner.length === 1 ? owner[0] : null;
|
|
23588
|
+
};
|
|
23589
|
+
const followRecorded = (start) => {
|
|
23590
|
+
let cursor = start;
|
|
23591
|
+
const seen = new Set([start]);
|
|
23592
|
+
for (let hop = 0;hop < MAX_RECORDED_HOPS; hop += 1) {
|
|
23593
|
+
const recorded = input.probe.recordedBase(cursor);
|
|
23594
|
+
if (!recorded)
|
|
23595
|
+
return null;
|
|
23596
|
+
const base = short(recorded);
|
|
23597
|
+
const owner = ownerOf(base);
|
|
23598
|
+
if (owner)
|
|
23599
|
+
return owner;
|
|
23600
|
+
if (ownedBy(candidates, base).length > 1 || seen.has(base))
|
|
23601
|
+
return null;
|
|
23602
|
+
seen.add(base);
|
|
23603
|
+
cursor = base;
|
|
23604
|
+
}
|
|
23605
|
+
return null;
|
|
23606
|
+
};
|
|
23607
|
+
const recordedOwner = followRecorded(branch);
|
|
23608
|
+
if (recordedOwner)
|
|
23609
|
+
return done(recordedOwner, "recorded-base", `${branch} was created on ${label(recordedOwner)}'s line (recorded base)`);
|
|
23610
|
+
for (const source of ["reflog", "checkout"]) {
|
|
23611
|
+
const ref = source === "reflog" ? input.probe.createdFrom(branch) : input.probe.checkedOutFrom(branch);
|
|
23612
|
+
if (!ref)
|
|
23613
|
+
continue;
|
|
23614
|
+
const owner = ownerOf(ref) ?? followRecorded(short(ref));
|
|
23615
|
+
if (owner)
|
|
23616
|
+
return done(owner, source, `${branch} was created from ${short(ref)} (${source === "reflog" ? "branch reflog" : "HEAD reflog"})`);
|
|
23617
|
+
}
|
|
23618
|
+
const forks = [];
|
|
23619
|
+
for (const track of candidates)
|
|
23620
|
+
for (const ref of new Set([track.integrationBranch, track.baseBranch])) {
|
|
23621
|
+
const sha = input.probe.mergeBase(branch, ref);
|
|
23622
|
+
if (sha)
|
|
23623
|
+
forks.push({ track, ref, sha });
|
|
23624
|
+
}
|
|
23625
|
+
if (forks.length === 0)
|
|
23626
|
+
return fallback(`no track branch could be compared with ${branch} (fetch the track branches)`);
|
|
23627
|
+
const ancestry = new Map;
|
|
23628
|
+
const descends = (older, newer) => {
|
|
23629
|
+
if (older === newer)
|
|
23630
|
+
return false;
|
|
23631
|
+
const key = `${older}>${newer}`;
|
|
23632
|
+
if (!ancestry.has(key))
|
|
23633
|
+
ancestry.set(key, input.probe.isAncestor(older, newer));
|
|
23634
|
+
return ancestry.get(key);
|
|
23635
|
+
};
|
|
23636
|
+
const winners = forks.filter((fork) => forks.every((other) => other.track === fork.track || descends(other.sha, fork.sha)));
|
|
23637
|
+
const winningTracks = [...new Set(winners.map((fork) => fork.track))];
|
|
23638
|
+
if (winningTracks.length === 1 && forks.some((fork) => fork.track !== winningTracks[0])) {
|
|
23639
|
+
const win = winners[0];
|
|
23640
|
+
return done(win.track, "ancestry", `${branch} forked from ${win.ref} at ${win.sha.slice(0, 12)}, after every other line's fork point`);
|
|
23641
|
+
}
|
|
23642
|
+
if (winningTracks.length === 1)
|
|
23643
|
+
return fallback(`only ${label(winningTracks[0])} could be compared with ${branch}`);
|
|
23644
|
+
const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
|
|
23645
|
+
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 ")}`);
|
|
23646
|
+
}
|
|
23647
|
+
var git4 = (cwd, args) => {
|
|
23648
|
+
const run = spawnSync7("git", args, {
|
|
23649
|
+
cwd,
|
|
23650
|
+
encoding: "utf8",
|
|
23651
|
+
timeout: 20000,
|
|
23652
|
+
windowsHide: true,
|
|
23653
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
23654
|
+
});
|
|
23655
|
+
return run.status === 0 ? (run.stdout ?? "").trim() : null;
|
|
23656
|
+
};
|
|
23657
|
+
var recordedBaseKey = (branch) => `branch.${branch}.workitBase`;
|
|
23658
|
+
var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
23659
|
+
var reflog = (cwd, ref) => (git4(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
|
|
23660
|
+
`).map((line) => {
|
|
23661
|
+
const [sha = "", selector = "", ...subject] = line.split("\t");
|
|
23662
|
+
return {
|
|
23663
|
+
sha,
|
|
23664
|
+
time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
|
|
23665
|
+
subject: subject.join("\t")
|
|
23666
|
+
};
|
|
23667
|
+
}).filter((entry) => entry.sha && Number.isFinite(entry.time));
|
|
23668
|
+
function gitTrackProbe(cwd) {
|
|
23669
|
+
let remote;
|
|
23670
|
+
let remotes;
|
|
23671
|
+
const tip = (branch) => {
|
|
23672
|
+
remote ??= pushRemoteName(cwd) ?? "origin";
|
|
23673
|
+
return resolveRef(cwd, `refs/remotes/${remote}/${branch}`) ?? resolveRef(cwd, `refs/heads/${branch}`);
|
|
23674
|
+
};
|
|
23675
|
+
return {
|
|
23676
|
+
remotes: () => remotes ??= remoteNames(cwd),
|
|
23677
|
+
recordedBase: (branch) => git4(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
|
|
23678
|
+
createdFrom: (branch) => {
|
|
23679
|
+
const log = git4(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
|
|
23680
|
+
const oldest = log?.split(`
|
|
23681
|
+
`).findLast(Boolean) ?? "";
|
|
23682
|
+
const match = /^branch: Created from (\S+)$/u.exec(oldest);
|
|
23683
|
+
return match && match[1] !== "HEAD" ? match[1] : null;
|
|
23684
|
+
},
|
|
23685
|
+
checkedOutFrom: (branch) => {
|
|
23686
|
+
const own = reflog(cwd, `refs/heads/${branch}`);
|
|
23687
|
+
const created = own.at(-1);
|
|
23688
|
+
if (!created || created.subject !== "branch: Created from HEAD")
|
|
23689
|
+
return null;
|
|
23690
|
+
const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
|
|
23691
|
+
const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
|
|
23692
|
+
return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
|
|
23693
|
+
},
|
|
23694
|
+
mergeBase: (branch, trackBranch) => {
|
|
23695
|
+
const base = tip(trackBranch);
|
|
23696
|
+
return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
|
|
23697
|
+
},
|
|
23698
|
+
isAncestor: (ancestor, descendant) => git4(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
|
|
23699
|
+
};
|
|
23700
|
+
}
|
|
23701
|
+
function workspaceReleaseTracks(cwd) {
|
|
23702
|
+
const raw = resolveRuntimeReleaseTracks(cwd);
|
|
23703
|
+
return { workspace: raw?.workspace ?? null, ...readReleaseTracks(raw?.releaseTracks) };
|
|
23704
|
+
}
|
|
23705
|
+
var memo3 = new Map;
|
|
23706
|
+
var MEMO_LIMIT = 64;
|
|
23707
|
+
function resolveTrackFor(cwd, options) {
|
|
23708
|
+
const read = workspaceReleaseTracks(cwd);
|
|
23709
|
+
if (read.error)
|
|
23710
|
+
return { status: "invalid", error: read.error, issues: read.issues };
|
|
23711
|
+
if (read.tracks.length === 0 && !options.track?.trim())
|
|
23712
|
+
return { status: "none", issues: read.issues };
|
|
23713
|
+
const branch = options.branch === undefined ? currentBranch(cwd) : options.branch;
|
|
23714
|
+
const envRequested = process.env.WORKFLOW_RELEASE_TRACK?.trim() || null;
|
|
23715
|
+
const input = {
|
|
23716
|
+
tracks: read.tracks,
|
|
23717
|
+
requested: options.track ?? null,
|
|
23718
|
+
envRequested,
|
|
23719
|
+
branch,
|
|
23720
|
+
defaultBranch: options.defaultBranch,
|
|
23721
|
+
issues: read.issues
|
|
23722
|
+
};
|
|
23723
|
+
let key = null;
|
|
23724
|
+
if (!options.probe) {
|
|
23725
|
+
const refs = git4(cwd, [
|
|
23726
|
+
"for-each-ref",
|
|
23727
|
+
"--format=%(refname) %(objectname)",
|
|
23728
|
+
"refs/heads",
|
|
23729
|
+
"refs/remotes"
|
|
23730
|
+
]) ?? "";
|
|
23731
|
+
const head = git4(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
|
|
23732
|
+
const own = branch ? [
|
|
23733
|
+
git4(cwd, [
|
|
23734
|
+
"log",
|
|
23735
|
+
"-g",
|
|
23736
|
+
"-n",
|
|
23737
|
+
"1",
|
|
23738
|
+
"--date=unix",
|
|
23739
|
+
"--format=%H %gd %gs",
|
|
23740
|
+
`refs/heads/${branch}`,
|
|
23741
|
+
"--"
|
|
23742
|
+
]) ?? "",
|
|
23743
|
+
git4(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
|
|
23744
|
+
] : [];
|
|
23745
|
+
key = createHash9("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
|
|
23746
|
+
const hit = memo3.get(key);
|
|
23747
|
+
if (hit)
|
|
23748
|
+
return hit;
|
|
23749
|
+
}
|
|
23750
|
+
const resolution = resolveReleaseTrack({ ...input, probe: options.probe ?? gitTrackProbe(cwd) });
|
|
23751
|
+
if (key) {
|
|
23752
|
+
if (memo3.size >= MEMO_LIMIT)
|
|
23753
|
+
memo3.delete(memo3.keys().next().value);
|
|
23754
|
+
memo3.set(key, resolution);
|
|
23755
|
+
}
|
|
23756
|
+
return resolution;
|
|
23757
|
+
}
|
|
23758
|
+
|
|
23759
|
+
// packages/workit-core/src/core/vcs-config.ts
|
|
23760
|
+
var workspacesPath = () => path14.join(configDir(), "workspaces.json");
|
|
22597
23761
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
22598
23762
|
var remoteProvider = (cwd) => {
|
|
22599
|
-
const r =
|
|
23763
|
+
const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
22600
23764
|
if (r.status !== 0)
|
|
22601
23765
|
return null;
|
|
22602
23766
|
const url = (r.stdout ?? "").trim();
|
|
@@ -22633,7 +23797,7 @@ var remoteEndpoint = (remote) => {
|
|
|
22633
23797
|
var configuredSshEndpoint = (host) => {
|
|
22634
23798
|
if (host.startsWith("-"))
|
|
22635
23799
|
return null;
|
|
22636
|
-
const resolved =
|
|
23800
|
+
const resolved = spawnSync8("ssh", ["-G", host], {
|
|
22637
23801
|
encoding: "utf8",
|
|
22638
23802
|
timeout: 5000,
|
|
22639
23803
|
maxBuffer: 1024 * 1024,
|
|
@@ -22683,7 +23847,7 @@ var readVcsConfig = () => {
|
|
|
22683
23847
|
const file = vcsConfigPath();
|
|
22684
23848
|
let raw;
|
|
22685
23849
|
try {
|
|
22686
|
-
raw =
|
|
23850
|
+
raw = fs12.readFileSync(file, "utf8");
|
|
22687
23851
|
} catch {
|
|
22688
23852
|
return { status: "missing", path: file, config: {} };
|
|
22689
23853
|
}
|
|
@@ -22698,7 +23862,16 @@ var readVcsConfig = () => {
|
|
|
22698
23862
|
}
|
|
22699
23863
|
return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
|
|
22700
23864
|
};
|
|
22701
|
-
|
|
23865
|
+
var trackSummary = (resolution) => resolution.status === "resolved" ? {
|
|
23866
|
+
name: resolution.track?.name ?? null,
|
|
23867
|
+
source: resolution.source,
|
|
23868
|
+
detail: resolution.detail,
|
|
23869
|
+
warnings: resolution.warnings,
|
|
23870
|
+
track: resolution.track,
|
|
23871
|
+
tracks: resolution.tracks,
|
|
23872
|
+
blocking: resolution.blocking
|
|
23873
|
+
} : null;
|
|
23874
|
+
function vcsConfig(mode, cwd, options = {}) {
|
|
22702
23875
|
const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
|
|
22703
23876
|
const wsVcs = ws?.vcs ?? {};
|
|
22704
23877
|
const wsYt = ws?.youtrack ?? {};
|
|
@@ -22708,10 +23881,7 @@ function vcsConfig(mode, cwd) {
|
|
|
22708
23881
|
const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
|
|
22709
23882
|
const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
|
|
22710
23883
|
const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
|
|
22711
|
-
const
|
|
22712
|
-
const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
|
|
22713
|
-
const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
|
|
22714
|
-
const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
|
|
23884
|
+
const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
|
|
22715
23885
|
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
22716
23886
|
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
22717
23887
|
let issuesProvider = null;
|
|
@@ -22723,11 +23893,24 @@ function vcsConfig(mode, cwd) {
|
|
|
22723
23893
|
if (mode === "resolve") {
|
|
22724
23894
|
if (cfgStatus === "malformed")
|
|
22725
23895
|
return { ok: false, error: cfgError, configPath: cfgPath };
|
|
23896
|
+
const trackResolution = resolveTrackFor(root, {
|
|
23897
|
+
track: options.track,
|
|
23898
|
+
...options.branch !== undefined ? { branch: options.branch } : {},
|
|
23899
|
+
defaultBranch: workspaceDefault
|
|
23900
|
+
});
|
|
23901
|
+
if (trackResolution.status === "invalid")
|
|
23902
|
+
return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
|
|
23903
|
+
const releaseTrack = trackSummary(trackResolution);
|
|
23904
|
+
const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
|
|
23905
|
+
const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
|
|
22726
23906
|
return {
|
|
22727
23907
|
ok: true,
|
|
22728
23908
|
workspace_name: ws?.name ?? null,
|
|
22729
23909
|
provider,
|
|
22730
23910
|
defaultTargetBranch: defaultTarget,
|
|
23911
|
+
workspaceDefaultTargetBranch: workspaceDefault,
|
|
23912
|
+
baseBranch,
|
|
23913
|
+
releaseTrack,
|
|
22731
23914
|
link_issues: linkIssues,
|
|
22732
23915
|
youtrack_base_url: youtrackBaseUrl,
|
|
22733
23916
|
issues_provider: issuesProvider,
|
|
@@ -22749,9 +23932,9 @@ function vcsConfig(mode, cwd) {
|
|
|
22749
23932
|
const prov = cfg[provider] ?? {};
|
|
22750
23933
|
const out = {
|
|
22751
23934
|
ok: true,
|
|
22752
|
-
configPath:
|
|
23935
|
+
configPath: path14.resolve(cfgPath),
|
|
22753
23936
|
provider,
|
|
22754
|
-
defaultTargetBranch:
|
|
23937
|
+
defaultTargetBranch: workspaceDefault,
|
|
22755
23938
|
pr: cfg.pr ?? {},
|
|
22756
23939
|
workspace_name: ws?.name ?? null,
|
|
22757
23940
|
link_issues: linkIssues,
|
|
@@ -22787,7 +23970,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
22787
23970
|
`).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
|
|
22788
23971
|
descriptionPreview: desc.slice(0, 600)
|
|
22789
23972
|
});
|
|
22790
|
-
const remoteResult =
|
|
23973
|
+
const remoteResult = spawnSync8("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
22791
23974
|
cwd,
|
|
22792
23975
|
encoding: "utf8"
|
|
22793
23976
|
});
|
|
@@ -22803,7 +23986,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
22803
23986
|
if (!hostingApiHostMatches(remote, host))
|
|
22804
23987
|
return { ok: false, error: "PR style target does not match the provider host" };
|
|
22805
23988
|
if (provider === "gitlab") {
|
|
22806
|
-
const run = (args) =>
|
|
23989
|
+
const run = (args) => spawnSync8("glab", ["api", ...args], {
|
|
22807
23990
|
cwd,
|
|
22808
23991
|
encoding: "utf8",
|
|
22809
23992
|
env
|
|
@@ -22823,7 +24006,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
22823
24006
|
});
|
|
22824
24007
|
}
|
|
22825
24008
|
} else if (provider === "github") {
|
|
22826
|
-
const r =
|
|
24009
|
+
const r = spawnSync8("gh", [
|
|
22827
24010
|
"pr",
|
|
22828
24011
|
"list",
|
|
22829
24012
|
"--repo",
|
|
@@ -22854,323 +24037,38 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
22854
24037
|
examples
|
|
22855
24038
|
};
|
|
22856
24039
|
}
|
|
22857
|
-
|
|
22858
|
-
// packages/workit-core/src/core/branch.ts
|
|
22859
|
-
function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
22860
|
-
const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
|
|
22861
|
-
const config = readConfig();
|
|
22862
|
-
return kind === "branch" ? {
|
|
22863
|
-
branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
|
|
22864
|
-
provenance: { branchPolicy: selected.source }
|
|
22865
|
-
} : {
|
|
22866
|
-
commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
|
|
22867
|
-
provenance: { commitPolicy: selected.source }
|
|
22868
|
-
};
|
|
22869
|
-
}
|
|
22870
|
-
var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
|
|
22871
24040
|
// packages/workit-core/src/core/task-engine.ts
|
|
22872
24041
|
import { realpathSync as realpathSync4 } from "node:fs";
|
|
22873
24042
|
|
|
22874
24043
|
// packages/workit-core/src/core/policy-resolver.ts
|
|
22875
|
-
|
|
22876
|
-
|
|
22877
|
-
assessment: assessmentSchema,
|
|
22878
|
-
constraints: array(constraintSchema),
|
|
22879
|
-
prior: object2({
|
|
22880
|
-
decisions: array(decisionSchema),
|
|
22881
|
-
findings: array(findingSchema),
|
|
22882
|
-
requirements: array(requirementSchema)
|
|
22883
|
-
}).strict(),
|
|
22884
|
-
preferences: object2({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
|
|
22885
|
-
}).strict();
|
|
22886
|
-
var compareCodeUnits3 = (left, right) => {
|
|
22887
|
-
const length = Math.min(left.length, right.length);
|
|
22888
|
-
for (let index = 0;index < length; index += 1) {
|
|
22889
|
-
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
22890
|
-
if (difference !== 0)
|
|
22891
|
-
return difference;
|
|
22892
|
-
}
|
|
22893
|
-
return left.length - right.length;
|
|
22894
|
-
};
|
|
22895
|
-
var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits3);
|
|
22896
|
-
var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right)));
|
|
22897
|
-
var normalizedScope = (scope) => ({
|
|
22898
|
-
description: scope.description,
|
|
22899
|
-
paths: stableUnique(scope.paths),
|
|
22900
|
-
exclusions: stableUnique(scope.exclusions)
|
|
22901
|
-
});
|
|
22902
|
-
var normalizedRefList = (values) => stableList(values);
|
|
22903
|
-
var normalizeAssessment = (assessment) => ({
|
|
22904
|
-
facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
|
|
22905
|
-
signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
|
|
22906
|
-
name,
|
|
22907
|
-
{ ...signal, refs: normalizedRefList(signal.refs) }
|
|
22908
|
-
])),
|
|
22909
|
-
consequences: stableList(assessment.consequences.map((consequence) => ({
|
|
22910
|
-
...consequence,
|
|
22911
|
-
fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
|
|
22912
|
-
}))),
|
|
22913
|
-
verification: stableList(assessment.verification.map((verification) => ({
|
|
22914
|
-
...verification,
|
|
22915
|
-
scope: normalizedScope(verification.scope),
|
|
22916
|
-
availableChecks: normalizedRefList(verification.availableChecks),
|
|
22917
|
-
gaps: stableUnique(verification.gaps)
|
|
22918
|
-
})))
|
|
22919
|
-
});
|
|
22920
|
-
var normalizeConstraint = (constraint) => ({
|
|
22921
|
-
...constraint,
|
|
22922
|
-
source: constraint.source,
|
|
22923
|
-
requires: stableList(constraint.requires.map((requirement) => ({
|
|
22924
|
-
...requirement,
|
|
22925
|
-
scope: normalizedScope(requirement.scope),
|
|
22926
|
-
refs: normalizedRefList(requirement.refs)
|
|
22927
|
-
})))
|
|
22928
|
-
});
|
|
22929
|
-
var normalizeDecision = (decision) => ({
|
|
22930
|
-
...decision,
|
|
22931
|
-
binding: {
|
|
22932
|
-
...decision.binding,
|
|
22933
|
-
scope: normalizedScope(decision.binding.scope),
|
|
22934
|
-
contentRefs: normalizedRefList(decision.binding.contentRefs)
|
|
22935
|
-
},
|
|
22936
|
-
requirementIds: stableUnique(decision.requirementIds)
|
|
22937
|
-
});
|
|
22938
|
-
var normalizeIntent = (intent) => ({
|
|
22939
|
-
...intent,
|
|
22940
|
-
scope: normalizedScope(intent.scope),
|
|
22941
|
-
authorityRefs: normalizedRefList(intent.authorityRefs)
|
|
22942
|
-
});
|
|
22943
|
-
var normalizePrior = (prior) => ({
|
|
22944
|
-
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
22945
|
-
findings: stableList(prior.findings.map((finding) => ({
|
|
22946
|
-
...finding,
|
|
22947
|
-
scope: normalizedScope(finding.scope),
|
|
22948
|
-
refs: normalizedRefList(finding.refs),
|
|
22949
|
-
resolution: finding.resolution ? {
|
|
22950
|
-
...finding.resolution,
|
|
22951
|
-
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
22952
|
-
decisionIds: stableUnique(finding.resolution.decisionIds)
|
|
22953
|
-
} : null
|
|
22954
|
-
}))),
|
|
22955
|
-
requirements: stableList(prior.requirements.map((requirement) => ({
|
|
22956
|
-
...requirement,
|
|
22957
|
-
scope: normalizedScope(requirement.scope)
|
|
22958
|
-
})))
|
|
22959
|
-
});
|
|
22960
|
-
var invalidFields = (error) => ({
|
|
22961
|
-
fields: error.issues.map((issue) => ({
|
|
22962
|
-
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
22963
|
-
reason: issue.message
|
|
22964
|
-
}))
|
|
22965
|
-
});
|
|
22966
|
-
function normalizeResolverInput(input) {
|
|
22967
|
-
const parsed = resolverInputSchema.safeParse(input);
|
|
22968
|
-
if (!parsed.success)
|
|
22969
|
-
return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
22970
|
-
const value = parsed.data;
|
|
22971
|
-
const signals = value.assessment.signals;
|
|
22972
|
-
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
22973
|
-
return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
22974
|
-
fields: [
|
|
22975
|
-
{
|
|
22976
|
-
path: "assessment.signals",
|
|
22977
|
-
reason: "contradictory behaviorChange and mechanicalLowRisk"
|
|
22978
|
-
}
|
|
22979
|
-
]
|
|
22980
|
-
});
|
|
22981
|
-
const ids = value.constraints.map((constraint) => constraint.id);
|
|
24044
|
+
function resolvePolicy(input) {
|
|
24045
|
+
const ids = input.constraints.map((constraint) => constraint.id);
|
|
22982
24046
|
if (new Set(ids).size !== ids.length)
|
|
22983
24047
|
return failure2("invalid_input", "constraint IDs must be unique", {
|
|
22984
24048
|
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
22985
24049
|
});
|
|
22986
|
-
if (value.preferences?.fast && value.preferences.thorough)
|
|
22987
|
-
return failure2("invalid_input", "fast and thorough preferences contradict", {
|
|
22988
|
-
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
|
|
22989
|
-
});
|
|
22990
|
-
const normalized = {
|
|
22991
|
-
intent: normalizeIntent(value.intent),
|
|
22992
|
-
assessment: normalizeAssessment(value.assessment),
|
|
22993
|
-
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
22994
|
-
prior: normalizePrior(value.prior),
|
|
22995
|
-
...value.preferences ? { preferences: { ...value.preferences } } : {},
|
|
22996
|
-
policyVersion: POLICY_VERSION
|
|
22997
|
-
};
|
|
22998
|
-
return success(null, null, normalized);
|
|
22999
|
-
}
|
|
23000
|
-
var requirement = (spec) => ({
|
|
23001
|
-
...spec,
|
|
23002
|
-
id: requirementId({
|
|
23003
|
-
ruleId: spec.ruleId,
|
|
23004
|
-
scope: spec.scope,
|
|
23005
|
-
satisfaction: spec.satisfaction,
|
|
23006
|
-
before: spec.before,
|
|
23007
|
-
dependentAction: spec.dependentAction
|
|
23008
|
-
})
|
|
23009
|
-
});
|
|
23010
|
-
var signalReason = (input, name) => input.assessment.signals[name].reason;
|
|
23011
|
-
function resolveRequirements(input) {
|
|
23012
|
-
const { signals } = input.assessment;
|
|
23013
|
-
const scope = input.intent.scope;
|
|
23014
|
-
const requirements = [];
|
|
23015
|
-
const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
|
|
23016
|
-
const hasUnknownFact = [
|
|
23017
|
-
...input.assessment.facts,
|
|
23018
|
-
...input.assessment.consequences.map(({ fact }) => fact)
|
|
23019
|
-
].some((fact) => fact.basis === "unknown");
|
|
23020
|
-
if (hasUnknownSignal || hasUnknownFact)
|
|
23021
|
-
requirements.push(requirement({
|
|
23022
|
-
ruleId: "consequential-unknown",
|
|
23023
|
-
dimension: "investigation",
|
|
23024
|
-
scope,
|
|
23025
|
-
reason: `Resolve consequential uncertainty before the dependent action: ${[
|
|
23026
|
-
...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
|
|
23027
|
-
...hasUnknownFact ? ["unknown fact"] : []
|
|
23028
|
-
].join(", ")}`,
|
|
23029
|
-
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
23030
|
-
before: "dependent_action",
|
|
23031
|
-
dependentAction: null,
|
|
23032
|
-
acceptanceAllowed: false
|
|
23033
|
-
}));
|
|
23034
|
-
if (signals.productChoiceOpen.value === true)
|
|
23035
|
-
requirements.push(requirement({
|
|
23036
|
-
ruleId: "product-decision",
|
|
23037
|
-
dimension: "decisions",
|
|
23038
|
-
scope,
|
|
23039
|
-
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
23040
|
-
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
23041
|
-
before: "dependent_action",
|
|
23042
|
-
dependentAction: null,
|
|
23043
|
-
acceptanceAllowed: false
|
|
23044
|
-
}));
|
|
23045
|
-
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
23046
|
-
if (signals.behaviorChange.value === true)
|
|
23047
|
-
requirements.push(requirement({
|
|
23048
|
-
ruleId: "behavioral-verification",
|
|
23049
|
-
dimension: "testing",
|
|
23050
|
-
scope,
|
|
23051
|
-
reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
|
|
23052
|
-
satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
|
|
23053
|
-
before: "close",
|
|
23054
|
-
dependentAction: null,
|
|
23055
|
-
acceptanceAllowed: false
|
|
23056
|
-
}), requirement({
|
|
23057
|
-
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
23058
|
-
dimension: "review",
|
|
23059
|
-
scope,
|
|
23060
|
-
reason: independentBehaviorReview ? "Consequential behavior or the thorough preference requires fresh-context review." : "Bounded behavior changes use self-review unless consequential risks or trusted rules require more.",
|
|
23061
|
-
satisfaction: independentBehaviorReview ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
23062
|
-
before: "close",
|
|
23063
|
-
dependentAction: null,
|
|
23064
|
-
acceptanceAllowed: !independentBehaviorReview
|
|
23065
|
-
}));
|
|
23066
|
-
else if (signals.mechanicalLowRisk.value === true)
|
|
23067
|
-
requirements.push(requirement({
|
|
23068
|
-
ruleId: "mechanical-existing-checks",
|
|
23069
|
-
dimension: "verification",
|
|
23070
|
-
scope,
|
|
23071
|
-
reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
|
|
23072
|
-
satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
|
|
23073
|
-
before: "close",
|
|
23074
|
-
dependentAction: null,
|
|
23075
|
-
acceptanceAllowed: true
|
|
23076
|
-
}), requirement({
|
|
23077
|
-
ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
|
|
23078
|
-
dimension: "review",
|
|
23079
|
-
scope,
|
|
23080
|
-
reason: input.preferences?.thorough ? "The thorough preference upgrades mechanical self-review to fresh-context review." : "Mechanical low-risk work uses self-review rather than mandatory fresh-context review.",
|
|
23081
|
-
satisfaction: input.preferences?.thorough ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
23082
|
-
before: "close",
|
|
23083
|
-
dependentAction: null,
|
|
23084
|
-
acceptanceAllowed: !input.preferences?.thorough
|
|
23085
|
-
}));
|
|
23086
|
-
if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
|
|
23087
|
-
requirements.push(requirement({
|
|
23088
|
-
ruleId: "pre-pr-cleanup",
|
|
23089
|
-
dimension: "verification",
|
|
23090
|
-
scope,
|
|
23091
|
-
reason: "Implementation output must be deslopped before it is delivered.",
|
|
23092
|
-
satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
|
|
23093
|
-
before: "dependent_action",
|
|
23094
|
-
dependentAction: "hosting.pull_request",
|
|
23095
|
-
acceptanceAllowed: true
|
|
23096
|
-
}));
|
|
23097
|
-
if (signals.durableAgreementNeeded.value === true)
|
|
23098
|
-
requirements.push(requirement({
|
|
23099
|
-
ruleId: "durable-spec",
|
|
23100
|
-
dimension: "artifacts",
|
|
23101
|
-
scope,
|
|
23102
|
-
reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
|
|
23103
|
-
satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
|
|
23104
|
-
before: "write",
|
|
23105
|
-
dependentAction: null,
|
|
23106
|
-
acceptanceAllowed: true
|
|
23107
|
-
}));
|
|
23108
|
-
if (signals.coordinationPlanNeeded.value === true)
|
|
23109
|
-
requirements.push(requirement({
|
|
23110
|
-
ruleId: "coordination-plan",
|
|
23111
|
-
dimension: "continuity",
|
|
23112
|
-
scope,
|
|
23113
|
-
reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
|
|
23114
|
-
satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
|
|
23115
|
-
before: "dependent_action",
|
|
23116
|
-
dependentAction: null,
|
|
23117
|
-
acceptanceAllowed: true
|
|
23118
|
-
}));
|
|
23119
|
-
if (signals.helperUseful.value === true && !input.preferences?.fast)
|
|
23120
|
-
requirements.push(requirement({
|
|
23121
|
-
ruleId: "helper-usefulness",
|
|
23122
|
-
dimension: "delegation",
|
|
23123
|
-
scope,
|
|
23124
|
-
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
23125
|
-
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
23126
|
-
before: "dependent_action",
|
|
23127
|
-
dependentAction: null,
|
|
23128
|
-
acceptanceAllowed: true
|
|
23129
|
-
}));
|
|
23130
|
-
for (const constraint of input.constraints)
|
|
23131
|
-
for (const obligation of constraint.requires) {
|
|
23132
|
-
const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
|
|
23133
|
-
requirements.push(requirement({
|
|
23134
|
-
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
23135
|
-
kind: obligation.kind,
|
|
23136
|
-
scope: normalizedScope(obligation.scope),
|
|
23137
|
-
refs: normalizedRefList(obligation.refs),
|
|
23138
|
-
method: obligation.method,
|
|
23139
|
-
before: obligation.before,
|
|
23140
|
-
dependentAction: obligation.dependentAction
|
|
23141
|
-
})}`,
|
|
23142
|
-
dimension,
|
|
23143
|
-
scope: obligation.scope,
|
|
23144
|
-
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
23145
|
-
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
23146
|
-
before: obligation.before,
|
|
23147
|
-
dependentAction: obligation.dependentAction,
|
|
23148
|
-
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
23149
|
-
}));
|
|
23150
|
-
}
|
|
23151
|
-
const seen = new Set;
|
|
23152
|
-
return requirements.filter((item) => {
|
|
23153
|
-
if (seen.has(item.id))
|
|
23154
|
-
return false;
|
|
23155
|
-
seen.add(item.id);
|
|
23156
|
-
return true;
|
|
23157
|
-
});
|
|
23158
|
-
}
|
|
23159
|
-
function resolvePolicy(input) {
|
|
23160
|
-
const normalized = normalizeResolverInput(input);
|
|
23161
|
-
if (!normalized.ok)
|
|
23162
|
-
return normalized;
|
|
23163
|
-
const requirements = resolveRequirements(normalized.data);
|
|
23164
24050
|
const policy = {
|
|
23165
24051
|
policyVersion: POLICY_VERSION,
|
|
23166
|
-
inputDigest: sha256(canonicalJson(
|
|
23167
|
-
|
|
24052
|
+
inputDigest: sha256(canonicalJson({
|
|
24053
|
+
judgment: input.judgment,
|
|
24054
|
+
scope: input.intent.scope,
|
|
24055
|
+
constraints: input.constraints,
|
|
24056
|
+
verification: input.verification ?? "self",
|
|
24057
|
+
policyVersion: POLICY_VERSION
|
|
24058
|
+
})),
|
|
24059
|
+
requirements: deriveRequirements(input.judgment, input.intent.scope, input.constraints, input.verification)
|
|
23168
24060
|
};
|
|
23169
24061
|
const parsed = policySchema.safeParse(policy);
|
|
23170
24062
|
if (!parsed.success)
|
|
23171
|
-
return failure2("invalid_input", "resolved policy is invalid",
|
|
24063
|
+
return failure2("invalid_input", "resolved policy is invalid", {
|
|
24064
|
+
fields: parsed.error.issues.map((issue) => ({
|
|
24065
|
+
path: issue.path.join("."),
|
|
24066
|
+
reason: issue.message
|
|
24067
|
+
}))
|
|
24068
|
+
});
|
|
23172
24069
|
return success(null, null, parsed.data);
|
|
23173
24070
|
}
|
|
24071
|
+
var compareCodeUnits3 = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
23174
24072
|
function diffPolicy(previous, next, reason, now) {
|
|
23175
24073
|
if (!policySchema.safeParse(previous).success && previous !== null)
|
|
23176
24074
|
throw new TypeError("previous policy is invalid");
|
|
@@ -23198,7 +24096,222 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
23198
24096
|
return parsed.data;
|
|
23199
24097
|
}
|
|
23200
24098
|
|
|
24099
|
+
// packages/workit-core/src/core/policy/judgment.ts
|
|
24100
|
+
var isObject6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24101
|
+
var squash = (key) => key.toLowerCase().replace(/[_-]/g, "");
|
|
24102
|
+
var ALIASES = {
|
|
24103
|
+
riskTier: ["risktier", "risk", "risklevel", "tier"],
|
|
24104
|
+
behaviorChange: ["behaviorchange", "behavior", "behaviour", "behaviourchange"],
|
|
24105
|
+
productChoiceOpen: ["productchoiceopen", "openproductchoice", "productchoice", "openchoice"],
|
|
24106
|
+
needsPlan: ["needsplan", "plan", "needsspec", "spec"],
|
|
24107
|
+
note: ["note", "notes", "reason", "why"],
|
|
24108
|
+
refs: ["refs", "ref", "references"]
|
|
24109
|
+
};
|
|
24110
|
+
var LEGACY_KEYS = new Set(["assessment", "facts", "signals", "consequences", "verification"]);
|
|
24111
|
+
var RISK = {
|
|
24112
|
+
trivial: "trivial",
|
|
24113
|
+
low: "trivial",
|
|
24114
|
+
minor: "trivial",
|
|
24115
|
+
mechanical: "trivial",
|
|
24116
|
+
none: "trivial",
|
|
24117
|
+
normal: "normal",
|
|
24118
|
+
medium: "normal",
|
|
24119
|
+
med: "normal",
|
|
24120
|
+
moderate: "normal",
|
|
24121
|
+
high: "high",
|
|
24122
|
+
critical: "high",
|
|
24123
|
+
major: "high"
|
|
24124
|
+
};
|
|
24125
|
+
var YES = new Set(["true", "yes", "y", "1", "on"]);
|
|
24126
|
+
var NO = new Set(["false", "no", "n", "0", "off", "none"]);
|
|
24127
|
+
var flag = (value, unknownAs) => {
|
|
24128
|
+
if (typeof value === "boolean")
|
|
24129
|
+
return { ok: true, value };
|
|
24130
|
+
if (typeof value === "number" && (value === 0 || value === 1))
|
|
24131
|
+
return { ok: true, value: value === 1 };
|
|
24132
|
+
if (typeof value !== "string")
|
|
24133
|
+
return { ok: false };
|
|
24134
|
+
const text = value.trim().toLowerCase();
|
|
24135
|
+
if (YES.has(text))
|
|
24136
|
+
return { ok: true, value: true };
|
|
24137
|
+
if (NO.has(text))
|
|
24138
|
+
return { ok: true, value: false };
|
|
24139
|
+
if (text === "unknown" || text === "maybe")
|
|
24140
|
+
return { ok: true, value: unknownAs };
|
|
24141
|
+
return { ok: false };
|
|
24142
|
+
};
|
|
24143
|
+
var fromLegacy = (raw, previous) => {
|
|
24144
|
+
const source = isObject6(raw.assessment) ? raw.assessment : raw;
|
|
24145
|
+
const signals = isObject6(source.signals) ? source.signals : {};
|
|
24146
|
+
const value = (name) => isObject6(signals[name]) ? signals[name].value : undefined;
|
|
24147
|
+
const known = (name, fallback) => typeof value(name) === "boolean" ? value(name) : fallback;
|
|
24148
|
+
const behaviorChange = known("behaviorChange", previous?.behaviorChange ?? false);
|
|
24149
|
+
const consequences = Array.isArray(source.consequences) ? source.consequences : [];
|
|
24150
|
+
const freshReview = value("behaviorChange") === true && consequences.some((item) => isObject6(item) && ["security", "data", "public_contract", "operations"].includes(String(item.area)));
|
|
24151
|
+
return {
|
|
24152
|
+
riskTier: freshReview ? "high" : value("mechanicalLowRisk") === true && !behaviorChange ? "trivial" : "normal",
|
|
24153
|
+
behaviorChange,
|
|
24154
|
+
productChoiceOpen: known("productChoiceOpen", previous?.productChoiceOpen ?? false),
|
|
24155
|
+
needsPlan: known("durableAgreementNeeded", previous?.needsPlan ?? false) || value("coordinationPlanNeeded") === true
|
|
24156
|
+
};
|
|
24157
|
+
};
|
|
24158
|
+
var DEFAULT = {
|
|
24159
|
+
riskTier: "normal",
|
|
24160
|
+
behaviorChange: false,
|
|
24161
|
+
productChoiceOpen: false,
|
|
24162
|
+
needsPlan: false,
|
|
24163
|
+
note: null,
|
|
24164
|
+
refs: []
|
|
24165
|
+
};
|
|
24166
|
+
function normalizeJudgment(raw, previous) {
|
|
24167
|
+
const input = isObject6(raw) ? raw : {};
|
|
24168
|
+
const legacy = Object.keys(input).some((key) => LEGACY_KEYS.has(key));
|
|
24169
|
+
const base = { ...DEFAULT, ...previous, refs: [...previous?.refs ?? []] };
|
|
24170
|
+
const judgment = legacy ? { ...base, ...fromLegacy(input, previous) } : { ...base };
|
|
24171
|
+
const ignored = [];
|
|
24172
|
+
const fields = [];
|
|
24173
|
+
for (const [key, value] of Object.entries(input)) {
|
|
24174
|
+
if (LEGACY_KEYS.has(key))
|
|
24175
|
+
continue;
|
|
24176
|
+
const name = Object.keys(ALIASES).find((field) => ALIASES[field].includes(squash(key)));
|
|
24177
|
+
if (!name) {
|
|
24178
|
+
ignored.push(key);
|
|
24179
|
+
continue;
|
|
24180
|
+
}
|
|
24181
|
+
if (name === "riskTier") {
|
|
24182
|
+
const tier = typeof value === "string" ? RISK[value.trim().toLowerCase()] : undefined;
|
|
24183
|
+
if (tier)
|
|
24184
|
+
judgment.riskTier = tier;
|
|
24185
|
+
else
|
|
24186
|
+
fields.push({ path: key, reason: "expected trivial|normal|high (or low|medium)" });
|
|
24187
|
+
} else if (name === "note") {
|
|
24188
|
+
judgment.note = typeof value === "string" && value.trim() ? value.trim() : null;
|
|
24189
|
+
} else if (name === "refs") {
|
|
24190
|
+
const items = Array.isArray(value) ? value : [value];
|
|
24191
|
+
judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
|
|
24192
|
+
} else {
|
|
24193
|
+
if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
|
|
24194
|
+
judgment.needsPlan = true;
|
|
24195
|
+
judgment.refs.push(refOf(value.trim()));
|
|
24196
|
+
continue;
|
|
24197
|
+
}
|
|
24198
|
+
const parsed = flag(value, name === "behaviorChange");
|
|
24199
|
+
if (parsed.ok)
|
|
24200
|
+
judgment[name] = parsed.value;
|
|
24201
|
+
else
|
|
24202
|
+
fields.push({ path: key, reason: "expected true|false (yes|no accepted)" });
|
|
24203
|
+
}
|
|
24204
|
+
}
|
|
24205
|
+
if (fields.length)
|
|
24206
|
+
return failure2("invalid_input", "judgment values are invalid", { fields });
|
|
24207
|
+
const seen = new Set;
|
|
24208
|
+
judgment.refs = judgment.refs.filter((ref) => {
|
|
24209
|
+
const key = JSON.stringify(ref);
|
|
24210
|
+
return seen.has(key) ? false : (seen.add(key), true);
|
|
24211
|
+
});
|
|
24212
|
+
const parsed = judgmentSchema.safeParse(judgment);
|
|
24213
|
+
if (!parsed.success)
|
|
24214
|
+
return failure2("invalid_input", "judgment is invalid", {
|
|
24215
|
+
fields: parsed.error.issues.map((issue) => ({
|
|
24216
|
+
path: issue.path.join("."),
|
|
24217
|
+
reason: issue.message
|
|
24218
|
+
}))
|
|
24219
|
+
});
|
|
24220
|
+
return success(null, null, { judgment: parsed.data, ignored, legacy });
|
|
24221
|
+
}
|
|
24222
|
+
|
|
24223
|
+
// packages/workit-core/src/autonomy.ts
|
|
24224
|
+
import os3 from "node:os";
|
|
24225
|
+
import path15 from "node:path";
|
|
24226
|
+
var accountHome = () => {
|
|
24227
|
+
try {
|
|
24228
|
+
return os3.userInfo().homedir;
|
|
24229
|
+
} catch {
|
|
24230
|
+
return os3.homedir();
|
|
24231
|
+
}
|
|
24232
|
+
};
|
|
24233
|
+
var grantsHome = { resolve: accountHome };
|
|
24234
|
+
var grantsDir = () => path15.join(grantsHome.resolve(), ".config", "workit");
|
|
24235
|
+
var grantsOverride = () => {
|
|
24236
|
+
const configured = path15.resolve(resolveConfigDir());
|
|
24237
|
+
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`;
|
|
24238
|
+
};
|
|
24239
|
+
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
24240
|
+
var DEFAULT_GRANTS = Object.freeze({
|
|
24241
|
+
push: true,
|
|
24242
|
+
pr: true,
|
|
24243
|
+
merge: false,
|
|
24244
|
+
release: false,
|
|
24245
|
+
rerun: true
|
|
24246
|
+
});
|
|
24247
|
+
var DEFAULT_ENDPOINT = "commit";
|
|
24248
|
+
var DEFAULT_VERIFICATION = "self";
|
|
24249
|
+
var verificationOf = (entry) => entry?.verification === "independent" ? "independent" : "self";
|
|
24250
|
+
var endpointOf = (entry) => entry?.defaultEndpoint === "pr" ? "pr" : "commit";
|
|
24251
|
+
var isGrantValue = (value) => value === true || value === false || value === "verified";
|
|
24252
|
+
var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
|
|
24253
|
+
var fromAutoApprove = (value) => {
|
|
24254
|
+
if (value === true)
|
|
24255
|
+
return { push: true, pr: true, merge: "verified" };
|
|
24256
|
+
if (!Array.isArray(value))
|
|
24257
|
+
return null;
|
|
24258
|
+
const grants = {};
|
|
24259
|
+
for (const item of value)
|
|
24260
|
+
if (item === "push" || item === "pr")
|
|
24261
|
+
grants[item] = true;
|
|
24262
|
+
else if (item === "merge")
|
|
24263
|
+
grants.merge = "verified";
|
|
24264
|
+
return Object.keys(grants).length > 0 ? grants : null;
|
|
24265
|
+
};
|
|
24266
|
+
function configuredGrants(entry) {
|
|
24267
|
+
const record = entry ?? {};
|
|
24268
|
+
const raw = record.autonomy;
|
|
24269
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
24270
|
+
const grants = {};
|
|
24271
|
+
for (const kind of GRANT_KINDS) {
|
|
24272
|
+
const value = raw[kind];
|
|
24273
|
+
if (isGrantValue(value))
|
|
24274
|
+
grants[kind] = normalized(kind, value);
|
|
24275
|
+
}
|
|
24276
|
+
return { grants, source: "autonomy" };
|
|
24277
|
+
}
|
|
24278
|
+
const legacy = fromAutoApprove(record.autoApprove);
|
|
24279
|
+
return legacy ? { grants: legacy, source: "autoApprove" } : { grants: {}, source: "default" };
|
|
24280
|
+
}
|
|
24281
|
+
function resolveAutonomy(cwd) {
|
|
24282
|
+
const override = grantsOverride();
|
|
24283
|
+
if (override)
|
|
24284
|
+
return {
|
|
24285
|
+
workspace: null,
|
|
24286
|
+
grants: { ...DEFAULT_GRANTS },
|
|
24287
|
+
configured: [],
|
|
24288
|
+
source: "default",
|
|
24289
|
+
accountConfigured: false,
|
|
24290
|
+
defaultEndpoint: DEFAULT_ENDPOINT,
|
|
24291
|
+
verification: DEFAULT_VERIFICATION,
|
|
24292
|
+
note: override
|
|
24293
|
+
};
|
|
24294
|
+
const workspace = resolveWorkspaceFrom(cwd, grantsDir(), process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined);
|
|
24295
|
+
const { grants, source } = configuredGrants(workspace);
|
|
24296
|
+
return {
|
|
24297
|
+
workspace: workspace?.name ?? null,
|
|
24298
|
+
grants: { ...DEFAULT_GRANTS, ...grants },
|
|
24299
|
+
configured: GRANT_KINDS.filter((kind) => grants[kind] !== undefined),
|
|
24300
|
+
source,
|
|
24301
|
+
accountConfigured: Boolean(workspace?.vcs?.account),
|
|
24302
|
+
defaultEndpoint: endpointOf(workspace),
|
|
24303
|
+
verification: verificationOf(workspace)
|
|
24304
|
+
};
|
|
24305
|
+
}
|
|
24306
|
+
|
|
23201
24307
|
// packages/workit-core/src/core/task-engine.ts
|
|
24308
|
+
var workspaceVerification = (root) => {
|
|
24309
|
+
try {
|
|
24310
|
+
return resolveAutonomy(root).verification;
|
|
24311
|
+
} catch {
|
|
24312
|
+
return "self";
|
|
24313
|
+
}
|
|
24314
|
+
};
|
|
23202
24315
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
23203
24316
|
kind,
|
|
23204
24317
|
host: context.caller.host,
|
|
@@ -23391,22 +24504,7 @@ var mapRef = (ref, ids) => {
|
|
|
23391
24504
|
return ref;
|
|
23392
24505
|
return { ...ref, id: ids.get(ref.id) ?? ref.id };
|
|
23393
24506
|
};
|
|
23394
|
-
var portableRefs = (refs) => refs.filter((ref) => ref.kind === "file" || ref.kind === "record");
|
|
23395
24507
|
var portableRef = (ref) => ref && (ref.kind === "file" || ref.kind === "record") ? ref : null;
|
|
23396
|
-
var portableFact = (fact) => {
|
|
23397
|
-
const refs = portableRefs(fact.refs);
|
|
23398
|
-
return fact.basis === "observed" && refs.length === 0 ? { ...fact, basis: "unknown", refs } : { ...fact, refs };
|
|
23399
|
-
};
|
|
23400
|
-
var portableSignal = (signal) => {
|
|
23401
|
-
const refs = portableRefs(signal.refs);
|
|
23402
|
-
return signal.basis === "observed" && refs.length === 0 ? {
|
|
23403
|
-
...signal,
|
|
23404
|
-
value: "unknown",
|
|
23405
|
-
basis: "unknown",
|
|
23406
|
-
reason: "portable supporting reference was removed",
|
|
23407
|
-
refs
|
|
23408
|
-
} : { ...signal, refs };
|
|
23409
|
-
};
|
|
23410
24508
|
var importedProvenance = (context) => ({
|
|
23411
24509
|
kind: "imported",
|
|
23412
24510
|
host: context.caller.host,
|
|
@@ -23418,18 +24516,6 @@ var portableTask = (task) => {
|
|
|
23418
24516
|
if (typeof stripped !== "object" || stripped === null || Array.isArray(stripped))
|
|
23419
24517
|
throw new TypeError("portable task rewrite produced a non-record");
|
|
23420
24518
|
const clone = structuredClone(stripped);
|
|
23421
|
-
const demote = (facts, signals, consequences) => {
|
|
23422
|
-
for (const fact of facts)
|
|
23423
|
-
Object.assign(fact, portableFact(fact));
|
|
23424
|
-
for (const name of Object.keys(signals)) {
|
|
23425
|
-
const signal = signals[name];
|
|
23426
|
-
Object.assign(signal, portableSignal(signal));
|
|
23427
|
-
}
|
|
23428
|
-
for (const consequence of consequences)
|
|
23429
|
-
Object.assign(consequence.fact, portableFact(consequence.fact));
|
|
23430
|
-
};
|
|
23431
|
-
for (const entry of clone.assessments)
|
|
23432
|
-
demote(entry.data.facts, entry.data.signals, entry.data.consequences);
|
|
23433
24519
|
clone.candidates = [];
|
|
23434
24520
|
return clone;
|
|
23435
24521
|
};
|
|
@@ -23446,6 +24532,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
23446
24532
|
for (const entry of [
|
|
23447
24533
|
source.intent,
|
|
23448
24534
|
...source.assessments,
|
|
24535
|
+
...source.judgments ?? [],
|
|
23449
24536
|
...source.evidence,
|
|
23450
24537
|
...source.decisions,
|
|
23451
24538
|
...source.findings,
|
|
@@ -23469,9 +24556,16 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
23469
24556
|
id: fresh(entry.id),
|
|
23470
24557
|
recordedAt: timestamp,
|
|
23471
24558
|
provenance: imported,
|
|
23472
|
-
data: rewriteRecordRefs(data,
|
|
24559
|
+
data: rewriteRecordRefs(data, legacyAssessmentSchema, remap)
|
|
23473
24560
|
};
|
|
23474
24561
|
});
|
|
24562
|
+
const judgments = source.judgments?.map((entry) => ({
|
|
24563
|
+
...entry,
|
|
24564
|
+
id: fresh(entry.id),
|
|
24565
|
+
recordedAt: timestamp,
|
|
24566
|
+
provenance: imported,
|
|
24567
|
+
data: rewriteRecordRefs(entry.data, judgmentSchema, remap)
|
|
24568
|
+
}));
|
|
23475
24569
|
const evidence = source.evidence.map((entry) => ({
|
|
23476
24570
|
...entry,
|
|
23477
24571
|
id: fresh(entry.id),
|
|
@@ -23539,6 +24633,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
23539
24633
|
status: "paused",
|
|
23540
24634
|
closure: null,
|
|
23541
24635
|
assessments,
|
|
24636
|
+
...judgments ? { judgments } : {},
|
|
23542
24637
|
evidence,
|
|
23543
24638
|
decisions: decisions.map((entry) => {
|
|
23544
24639
|
const data = rewriteRecordRefs(entry.data, decisionSchema, remap);
|
|
@@ -23608,7 +24703,8 @@ class WorkitCore {
|
|
|
23608
24703
|
retryOmittedRevisions(request, run) {
|
|
23609
24704
|
return retryFilledRevisions(filledRevisions(request), run);
|
|
23610
24705
|
}
|
|
23611
|
-
implicitTask(family,
|
|
24706
|
+
implicitTask(family, raw) {
|
|
24707
|
+
const request = normalizeOperationInput(family, raw, this.context.caller.host);
|
|
23612
24708
|
if (typeof request !== "object" || request === null || Array.isArray(request))
|
|
23613
24709
|
return success(null, null, request);
|
|
23614
24710
|
const value = request;
|
|
@@ -23862,7 +24958,11 @@ class WorkitCore {
|
|
|
23862
24958
|
if ((this.context.workerId ?? null) !== null)
|
|
23863
24959
|
return failure2("permission_denied", "helpers cannot change task requirements");
|
|
23864
24960
|
this.fillRevisions(input, task.data);
|
|
23865
|
-
const
|
|
24961
|
+
const judged = normalizeJudgment(input.judgment, latestJudgment(task.data));
|
|
24962
|
+
if (!judged.ok)
|
|
24963
|
+
return judged;
|
|
24964
|
+
const judgment = judged.data.judgment;
|
|
24965
|
+
const resolved = this.resolve(task.data, judgment);
|
|
23866
24966
|
if (!resolved.ok)
|
|
23867
24967
|
return resolved;
|
|
23868
24968
|
if (input.action === "preview")
|
|
@@ -23873,16 +24973,17 @@ class WorkitCore {
|
|
|
23873
24973
|
if (current.status === "closed")
|
|
23874
24974
|
return failure2("invalid_transition", "closed task cannot be assessed");
|
|
23875
24975
|
const nextChange = diffPolicy(current.policy, resolved.data, "policy reassessed", mutation.now);
|
|
23876
|
-
const
|
|
24976
|
+
const entry = {
|
|
23877
24977
|
id: newId(),
|
|
23878
24978
|
recordedAt: mutation.now,
|
|
23879
24979
|
provenance: provenance(this.context),
|
|
23880
|
-
data:
|
|
24980
|
+
data: judgment
|
|
23881
24981
|
};
|
|
23882
24982
|
const next = {
|
|
23883
24983
|
...current,
|
|
23884
24984
|
constraints: this.context.constraints,
|
|
23885
|
-
|
|
24985
|
+
judgments: [...current.judgments ?? [], entry],
|
|
24986
|
+
critical: [...new Set([...current.critical ?? [], "judgments"])],
|
|
23886
24987
|
policy: resolved.data,
|
|
23887
24988
|
policyChanges: nextChange ? [...current.policyChanges, nextChange] : current.policyChanges
|
|
23888
24989
|
};
|
|
@@ -24084,9 +25185,14 @@ class WorkitCore {
|
|
|
24084
25185
|
return workspace;
|
|
24085
25186
|
if (!workspace.data)
|
|
24086
25187
|
return failure2("not_found", "workspace not found");
|
|
24087
|
-
|
|
25188
|
+
const binding = {
|
|
25189
|
+
...input.binding,
|
|
25190
|
+
taskId: input.binding.taskId ?? task.data.id,
|
|
25191
|
+
workspaceId: input.binding.workspaceId ?? workspace.data.id
|
|
25192
|
+
};
|
|
25193
|
+
if (binding.taskId !== task.data.id || binding.workspaceId !== workspace.data.id)
|
|
24088
25194
|
return failure2("invalid_input", "decision task or workspace binding is invalid");
|
|
24089
|
-
const content = verifyDecisionContentAtRoot(this.store.root,
|
|
25195
|
+
const content = verifyDecisionContentAtRoot(this.store.root, binding);
|
|
24090
25196
|
if (!content.ok)
|
|
24091
25197
|
return content;
|
|
24092
25198
|
const knownRequirements = new Set(task.data.policy?.requirements.map((item) => item.id) ?? []);
|
|
@@ -24094,7 +25200,7 @@ class WorkitCore {
|
|
|
24094
25200
|
return failure2("invalid_input", "decision references an unknown requirement");
|
|
24095
25201
|
const base = {
|
|
24096
25202
|
purpose: input.purpose,
|
|
24097
|
-
binding
|
|
25203
|
+
binding,
|
|
24098
25204
|
response: input.response,
|
|
24099
25205
|
requirementIds: input.requirementIds,
|
|
24100
25206
|
revoked: null
|
|
@@ -24453,16 +25559,12 @@ class WorkitCore {
|
|
|
24453
25559
|
now: trustedNow(this.context)
|
|
24454
25560
|
});
|
|
24455
25561
|
}
|
|
24456
|
-
resolve(task,
|
|
25562
|
+
resolve(task, judgment) {
|
|
24457
25563
|
return resolvePolicy({
|
|
24458
25564
|
intent: task.intent.data,
|
|
24459
|
-
|
|
25565
|
+
judgment,
|
|
24460
25566
|
constraints: this.context.constraints,
|
|
24461
|
-
|
|
24462
|
-
decisions: task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data),
|
|
24463
|
-
findings: task.findings.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data),
|
|
24464
|
-
requirements: task.origin || task.policy === null ? [] : task.policy.requirements
|
|
24465
|
-
}
|
|
25567
|
+
verification: workspaceVerification(this.context.root)
|
|
24466
25568
|
});
|
|
24467
25569
|
}
|
|
24468
25570
|
summary(task) {
|
|
@@ -24788,9 +25890,9 @@ class WorkitCore {
|
|
|
24788
25890
|
}
|
|
24789
25891
|
}
|
|
24790
25892
|
// packages/workit-core/src/core/repo-context.ts
|
|
24791
|
-
import { spawnSync as
|
|
24792
|
-
import { existsSync as
|
|
24793
|
-
import
|
|
25893
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
25894
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
|
|
25895
|
+
import path16 from "node:path";
|
|
24794
25896
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
24795
25897
|
try {
|
|
24796
25898
|
gitRevisionParts(value);
|
|
@@ -24817,7 +25919,7 @@ var invalidRange = (cwd) => ({
|
|
|
24817
25919
|
cwd
|
|
24818
25920
|
});
|
|
24819
25921
|
var runGit = (cwd, args) => {
|
|
24820
|
-
const result =
|
|
25922
|
+
const result = spawnSync9("git", args, { cwd, encoding: "utf8" });
|
|
24821
25923
|
return {
|
|
24822
25924
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
24823
25925
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -24828,11 +25930,11 @@ var repoRoot = (cwd) => {
|
|
|
24828
25930
|
const r = runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
24829
25931
|
return r.exitCode === 0 && r.stdout ? r.stdout : cwd;
|
|
24830
25932
|
};
|
|
24831
|
-
var
|
|
25933
|
+
var currentBranch2 = (cwd) => {
|
|
24832
25934
|
const r = runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
24833
25935
|
return r.exitCode === 0 && r.stdout ? r.stdout : "unknown";
|
|
24834
25936
|
};
|
|
24835
|
-
var
|
|
25937
|
+
var defaultBase2 = (cwd) => {
|
|
24836
25938
|
for (const ref of [
|
|
24837
25939
|
"origin/main",
|
|
24838
25940
|
"main",
|
|
@@ -24846,11 +25948,11 @@ var defaultBase = (cwd) => {
|
|
|
24846
25948
|
}
|
|
24847
25949
|
return "HEAD~1";
|
|
24848
25950
|
};
|
|
24849
|
-
var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${
|
|
25951
|
+
var rangeArgOrDefault = (arg, cwd) => arg ? arg : `${defaultBase2(cwd)}...HEAD`;
|
|
24850
25952
|
var isProtectedBranch = (branch) => ["main", "master", "develop", "prod", "production"].includes(branch);
|
|
24851
25953
|
var isPrBranch = (branch) => branch.startsWith("feature/") || branch.startsWith("bugfix/");
|
|
24852
25954
|
function resolvePrBranchContext(cwd) {
|
|
24853
|
-
const branch =
|
|
25955
|
+
const branch = currentBranch2(cwd);
|
|
24854
25956
|
if (isProtectedBranch(branch)) {
|
|
24855
25957
|
return {
|
|
24856
25958
|
ok: false,
|
|
@@ -24924,7 +26026,7 @@ var findPrTemplate = (cwd) => {
|
|
|
24924
26026
|
]) {
|
|
24925
26027
|
const parts = rel.split("/");
|
|
24926
26028
|
const base = parts[parts.length - 1];
|
|
24927
|
-
const dir = parts.length > 1 ?
|
|
26029
|
+
const dir = parts.length > 1 ? path16.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
24928
26030
|
const actual = probeCaseInsensitive(dir, base);
|
|
24929
26031
|
if (actual)
|
|
24930
26032
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -24955,8 +26057,8 @@ var diffStatForRange = (cwd, range) => {
|
|
|
24955
26057
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
24956
26058
|
};
|
|
24957
26059
|
var packageScripts = (cwd, keys) => {
|
|
24958
|
-
const pkgPath =
|
|
24959
|
-
if (!
|
|
26060
|
+
const pkgPath = path16.join(cwd, "package.json");
|
|
26061
|
+
if (!existsSync6(pkgPath))
|
|
24960
26062
|
return [];
|
|
24961
26063
|
try {
|
|
24962
26064
|
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
@@ -24981,9 +26083,9 @@ var documentationFiles = (cwd) => {
|
|
|
24981
26083
|
if (entry.isDirectory()) {
|
|
24982
26084
|
if (excluded.has(entry.name))
|
|
24983
26085
|
continue;
|
|
24984
|
-
walk(
|
|
26086
|
+
walk(path16.join(dir, entry.name), depth + 1);
|
|
24985
26087
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
24986
|
-
matches.push("./" +
|
|
26088
|
+
matches.push("./" + path16.relative(cwd, path16.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
24987
26089
|
}
|
|
24988
26090
|
}
|
|
24989
26091
|
};
|
|
@@ -24991,7 +26093,7 @@ var documentationFiles = (cwd) => {
|
|
|
24991
26093
|
return matches.toSorted().slice(0, 200);
|
|
24992
26094
|
};
|
|
24993
26095
|
var readTrimmed2 = (file, maxLines) => {
|
|
24994
|
-
if (!
|
|
26096
|
+
if (!existsSync6(file))
|
|
24995
26097
|
return "";
|
|
24996
26098
|
const lines = readFileSync5(file, "utf8").split(`
|
|
24997
26099
|
`);
|
|
@@ -24999,7 +26101,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
24999
26101
|
`);
|
|
25000
26102
|
};
|
|
25001
26103
|
function prReadyContext(root, range) {
|
|
25002
|
-
const cwd =
|
|
26104
|
+
const cwd = path16.resolve(repoRoot(root));
|
|
25003
26105
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
25004
26106
|
return invalidRange(cwd);
|
|
25005
26107
|
let stdout = "";
|
|
@@ -25015,7 +26117,7 @@ function prReadyContext(root, range) {
|
|
|
25015
26117
|
stdout += printSection("Repository");
|
|
25016
26118
|
stdout += `root: ${cwd}
|
|
25017
26119
|
`;
|
|
25018
|
-
stdout += `branch: ${
|
|
26120
|
+
stdout += `branch: ${currentBranch2(cwd)}
|
|
25019
26121
|
`;
|
|
25020
26122
|
stdout += `range: ${range}
|
|
25021
26123
|
`;
|
|
@@ -25057,7 +26159,7 @@ ${status.stderr}` : "");
|
|
|
25057
26159
|
stdout += `template_path: ${template}
|
|
25058
26160
|
|
|
25059
26161
|
`;
|
|
25060
|
-
stdout += readTrimmed2(
|
|
26162
|
+
stdout += readTrimmed2(path16.join(cwd, template), 220);
|
|
25061
26163
|
} else {
|
|
25062
26164
|
stdout += `template_path: none
|
|
25063
26165
|
|
|
@@ -25067,7 +26169,7 @@ ${status.stderr}` : "");
|
|
|
25067
26169
|
stdout += `
|
|
25068
26170
|
`;
|
|
25069
26171
|
stdout += printSection("Recent Validation Signals");
|
|
25070
|
-
if (
|
|
26172
|
+
if (existsSync6(path16.join(cwd, "package.json"))) {
|
|
25071
26173
|
stdout += `package.json detected. Common scripts:
|
|
25072
26174
|
`;
|
|
25073
26175
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -25076,7 +26178,7 @@ ${status.stderr}` : "");
|
|
|
25076
26178
|
`) + `
|
|
25077
26179
|
`;
|
|
25078
26180
|
}
|
|
25079
|
-
if (
|
|
26181
|
+
if (existsSync6(path16.join(cwd, "Cargo.toml")) || existsSync6(path16.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
25080
26182
|
stdout += `Rust project detected.
|
|
25081
26183
|
`;
|
|
25082
26184
|
}
|
|
@@ -25109,7 +26211,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
25109
26211
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
25110
26212
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
25111
26213
|
function changelogContext(root, range) {
|
|
25112
|
-
const cwd =
|
|
26214
|
+
const cwd = path16.resolve(repoRoot(root));
|
|
25113
26215
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
25114
26216
|
return invalidRange(cwd);
|
|
25115
26217
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -25119,7 +26221,7 @@ function changelogContext(root, range) {
|
|
|
25119
26221
|
stdout += printSection("Repository");
|
|
25120
26222
|
stdout += `root: ${cwd}
|
|
25121
26223
|
`;
|
|
25122
|
-
stdout += `branch: ${
|
|
26224
|
+
stdout += `branch: ${currentBranch2(cwd)}
|
|
25123
26225
|
`;
|
|
25124
26226
|
stdout += `range: ${resolvedRange}
|
|
25125
26227
|
`;
|
|
@@ -25127,8 +26229,8 @@ function changelogContext(root, range) {
|
|
|
25127
26229
|
stdout += CHANGELOG_RULES + `
|
|
25128
26230
|
`;
|
|
25129
26231
|
stdout += printSection("Existing CHANGELOG.md");
|
|
25130
|
-
const changelogPath =
|
|
25131
|
-
if (
|
|
26232
|
+
const changelogPath = path16.join(cwd, "CHANGELOG.md");
|
|
26233
|
+
if (existsSync6(changelogPath)) {
|
|
25132
26234
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
25133
26235
|
`;
|
|
25134
26236
|
} else {
|
|
@@ -25147,7 +26249,7 @@ function changelogContext(root, range) {
|
|
|
25147
26249
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
25148
26250
|
}
|
|
25149
26251
|
function docsRefreshContext(root, range) {
|
|
25150
|
-
const cwd =
|
|
26252
|
+
const cwd = path16.resolve(repoRoot(root));
|
|
25151
26253
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
25152
26254
|
return invalidRange(cwd);
|
|
25153
26255
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -25157,7 +26259,7 @@ function docsRefreshContext(root, range) {
|
|
|
25157
26259
|
stdout += printSection("Repository");
|
|
25158
26260
|
stdout += `root: ${cwd}
|
|
25159
26261
|
`;
|
|
25160
|
-
stdout += `branch: ${
|
|
26262
|
+
stdout += `branch: ${currentBranch2(cwd)}
|
|
25161
26263
|
`;
|
|
25162
26264
|
stdout += `range: ${resolvedRange}
|
|
25163
26265
|
`;
|
|
@@ -25169,8 +26271,8 @@ function docsRefreshContext(root, range) {
|
|
|
25169
26271
|
`) + `
|
|
25170
26272
|
`;
|
|
25171
26273
|
stdout += printSection("README Preview");
|
|
25172
|
-
const readme =
|
|
25173
|
-
if (
|
|
26274
|
+
const readme = path16.join(cwd, "README.md");
|
|
26275
|
+
if (existsSync6(readme)) {
|
|
25174
26276
|
stdout += readTrimmed2(readme, 220) + `
|
|
25175
26277
|
`;
|
|
25176
26278
|
} else {
|
|
@@ -25178,8 +26280,8 @@ function docsRefreshContext(root, range) {
|
|
|
25178
26280
|
`;
|
|
25179
26281
|
}
|
|
25180
26282
|
stdout += printSection("Package Scripts");
|
|
25181
|
-
const pkgPath =
|
|
25182
|
-
if (
|
|
26283
|
+
const pkgPath = path16.join(cwd, "package.json");
|
|
26284
|
+
if (existsSync6(pkgPath)) {
|
|
25183
26285
|
try {
|
|
25184
26286
|
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
25185
26287
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
@@ -25192,7 +26294,7 @@ function docsRefreshContext(root, range) {
|
|
|
25192
26294
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
25193
26295
|
}
|
|
25194
26296
|
function releaseNotesContext(root, rangeOrTag) {
|
|
25195
|
-
const cwd =
|
|
26297
|
+
const cwd = path16.resolve(repoRoot(root));
|
|
25196
26298
|
if (!rangeOrTag) {
|
|
25197
26299
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
25198
26300
|
`, exitCode: 1, cwd };
|
|
@@ -25206,7 +26308,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
25206
26308
|
stdout += printSection("Repository");
|
|
25207
26309
|
stdout += `root: ${cwd}
|
|
25208
26310
|
`;
|
|
25209
|
-
stdout += `branch: ${
|
|
26311
|
+
stdout += `branch: ${currentBranch2(cwd)}
|
|
25210
26312
|
`;
|
|
25211
26313
|
stdout += `requested: ${rangeOrTag}
|
|
25212
26314
|
`;
|
|
@@ -25230,7 +26332,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
25230
26332
|
`;
|
|
25231
26333
|
stdout += printSection("Existing Release Files");
|
|
25232
26334
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
25233
|
-
if (
|
|
26335
|
+
if (existsSync6(path16.join(cwd, rel)))
|
|
25234
26336
|
stdout += rel + `
|
|
25235
26337
|
`;
|
|
25236
26338
|
}
|
|
@@ -25258,12 +26360,12 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
25258
26360
|
}
|
|
25259
26361
|
|
|
25260
26362
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
25261
|
-
import { spawnSync as
|
|
26363
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
25262
26364
|
|
|
25263
26365
|
// packages/workit-core/src/core/pr-create.ts
|
|
25264
|
-
import
|
|
25265
|
-
import
|
|
25266
|
-
import { spawnSync as
|
|
26366
|
+
import fs13 from "node:fs";
|
|
26367
|
+
import path17 from "node:path";
|
|
26368
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
25267
26369
|
// packages/workit-core/src/forge/pr-body.ts
|
|
25268
26370
|
function parseGhIssue(value) {
|
|
25269
26371
|
const m = /issues\/(\d+)/.exec(value);
|
|
@@ -25273,13 +26375,13 @@ function parseGhIssue(value) {
|
|
|
25273
26375
|
// packages/workit-core/src/core/pr-create.ts
|
|
25274
26376
|
function whichOnPath(tool) {
|
|
25275
26377
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
25276
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
26378
|
+
for (const dir of (process.env.PATH ?? "").split(path17.delimiter)) {
|
|
25277
26379
|
if (!dir)
|
|
25278
26380
|
continue;
|
|
25279
26381
|
for (const name of names) {
|
|
25280
|
-
const candidate =
|
|
26382
|
+
const candidate = path17.join(dir, name);
|
|
25281
26383
|
try {
|
|
25282
|
-
|
|
26384
|
+
fs13.accessSync(candidate, fs13.constants.X_OK);
|
|
25283
26385
|
return candidate;
|
|
25284
26386
|
} catch {}
|
|
25285
26387
|
}
|
|
@@ -25288,7 +26390,7 @@ function whichOnPath(tool) {
|
|
|
25288
26390
|
}
|
|
25289
26391
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
25290
26392
|
var pushRemote = (cwd) => {
|
|
25291
|
-
const result =
|
|
26393
|
+
const result = spawnSync10("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
25292
26394
|
cwd,
|
|
25293
26395
|
encoding: "utf8"
|
|
25294
26396
|
});
|
|
@@ -25319,7 +26421,7 @@ var defaultCli = (bin, available) => {
|
|
|
25319
26421
|
if (!available)
|
|
25320
26422
|
return null;
|
|
25321
26423
|
return (args, root) => {
|
|
25322
|
-
const out =
|
|
26424
|
+
const out = spawnSync11(bin, args, {
|
|
25323
26425
|
cwd: root,
|
|
25324
26426
|
encoding: "utf8",
|
|
25325
26427
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -25397,8 +26499,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
25397
26499
|
};
|
|
25398
26500
|
|
|
25399
26501
|
// packages/workit-core/src/core/youtrack.ts
|
|
25400
|
-
import
|
|
25401
|
-
import
|
|
26502
|
+
import fs14 from "node:fs";
|
|
26503
|
+
import path18 from "node:path";
|
|
25402
26504
|
|
|
25403
26505
|
// packages/workit-core/src/core/templates.ts
|
|
25404
26506
|
var repoRoot2 = assetRoot();
|
|
@@ -25411,22 +26513,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
25411
26513
|
// packages/workit-core/src/core/youtrack.ts
|
|
25412
26514
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
25413
26515
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
25414
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
25415
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
26516
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(configDir(), "youtrack.json");
|
|
26517
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path18.join(resolveConfigDir(), "youtrack.json");
|
|
25416
26518
|
var youTrackTokenModeOk = (p) => {
|
|
25417
26519
|
if (process.platform === "win32")
|
|
25418
26520
|
return true;
|
|
25419
|
-
const mode =
|
|
26521
|
+
const mode = fs14.statSync(p).mode & 511;
|
|
25420
26522
|
return mode === 384;
|
|
25421
26523
|
};
|
|
25422
26524
|
function readYouTrackConfig(required) {
|
|
25423
26525
|
const cfgPath = youTrackConfigPath();
|
|
25424
|
-
if (!
|
|
26526
|
+
if (!fs14.existsSync(cfgPath)) {
|
|
25425
26527
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
25426
26528
|
}
|
|
25427
26529
|
let parsed;
|
|
25428
26530
|
try {
|
|
25429
|
-
parsed = JSON.parse(
|
|
26531
|
+
parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
|
|
25430
26532
|
} catch {
|
|
25431
26533
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
25432
26534
|
}
|
|
@@ -25437,11 +26539,11 @@ function readYouTrackConfig(required) {
|
|
|
25437
26539
|
}
|
|
25438
26540
|
var readYouTrackContextConfig = () => {
|
|
25439
26541
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
25440
|
-
if (!
|
|
26542
|
+
if (!fs14.existsSync(cfgPath))
|
|
25441
26543
|
return { data: {} };
|
|
25442
26544
|
let parsed;
|
|
25443
26545
|
try {
|
|
25444
|
-
parsed = JSON.parse(
|
|
26546
|
+
parsed = JSON.parse(fs14.readFileSync(cfgPath, "utf8"));
|
|
25445
26547
|
} catch {
|
|
25446
26548
|
return { error: "YouTrack configuration is unavailable" };
|
|
25447
26549
|
}
|
|
@@ -25480,12 +26582,12 @@ var youTrackToken = () => {
|
|
|
25480
26582
|
if ("error" in loaded)
|
|
25481
26583
|
return loaded;
|
|
25482
26584
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
25483
|
-
const tokenPath = tokenFile ?
|
|
25484
|
-
if (!tokenPath || !
|
|
26585
|
+
const tokenPath = tokenFile ? path18.isAbsolute(tokenFile) ? path18.resolve(tokenFile) : path18.resolve(process.cwd(), tokenFile) : "";
|
|
26586
|
+
if (!tokenPath || !fs14.existsSync(tokenPath))
|
|
25485
26587
|
return { error: "missing youtrack.token" };
|
|
25486
26588
|
if (!youTrackTokenModeOk(tokenPath))
|
|
25487
26589
|
return { error: "youtrack.token mode must be 0600" };
|
|
25488
|
-
const token =
|
|
26590
|
+
const token = fs14.readFileSync(tokenPath, "utf8").trim();
|
|
25489
26591
|
if (!token)
|
|
25490
26592
|
return { error: "empty token file" };
|
|
25491
26593
|
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
@@ -25550,9 +26652,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
25550
26652
|
} catch {
|
|
25551
26653
|
continue;
|
|
25552
26654
|
}
|
|
25553
|
-
if (!
|
|
26655
|
+
if (!fs14.existsSync(full))
|
|
25554
26656
|
continue;
|
|
25555
|
-
const text =
|
|
26657
|
+
const text = fs14.readFileSync(full, "utf8");
|
|
25556
26658
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
25557
26659
|
if (m)
|
|
25558
26660
|
return m[1];
|
|
@@ -25822,22 +26924,22 @@ function resolveGitRevision(root, value) {
|
|
|
25822
26924
|
}
|
|
25823
26925
|
function resolveInside(root, candidate) {
|
|
25824
26926
|
const base = realpathSync5(root);
|
|
25825
|
-
const target =
|
|
25826
|
-
if (target !== base && !target.startsWith(base +
|
|
26927
|
+
const target = path19.resolve(base, candidate);
|
|
26928
|
+
if (target !== base && !target.startsWith(base + path19.sep)) {
|
|
25827
26929
|
throw new Error("path must stay inside repository root");
|
|
25828
26930
|
}
|
|
25829
26931
|
let ancestor = target;
|
|
25830
|
-
while (!
|
|
25831
|
-
ancestor =
|
|
26932
|
+
while (!existsSync7(ancestor))
|
|
26933
|
+
ancestor = path19.dirname(ancestor);
|
|
25832
26934
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
25833
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
26935
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path19.sep)) {
|
|
25834
26936
|
throw new Error("path must stay inside repository root");
|
|
25835
26937
|
}
|
|
25836
26938
|
return target;
|
|
25837
26939
|
}
|
|
25838
26940
|
function run2(root, executable, args, env = {}) {
|
|
25839
26941
|
const cwd = realpathSync5(root);
|
|
25840
|
-
const result =
|
|
26942
|
+
const result = spawnSync12(executable, args, {
|
|
25841
26943
|
cwd,
|
|
25842
26944
|
encoding: "utf8",
|
|
25843
26945
|
env: { ...process.env, ...env }
|
|
@@ -25851,7 +26953,7 @@ function run2(root, executable, args, env = {}) {
|
|
|
25851
26953
|
}
|
|
25852
26954
|
|
|
25853
26955
|
// packages/workit-core/src/core/logger.ts
|
|
25854
|
-
import
|
|
26956
|
+
import os4 from "node:os";
|
|
25855
26957
|
var REDACTED = "[REDACTED]";
|
|
25856
26958
|
var SECRET_VALUE = /\b(?:Bearer|Basic|Digest|Token)\s+\S+/gi;
|
|
25857
26959
|
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;
|
|
@@ -25893,8 +26995,8 @@ var SENSITIVE_WORDS = new Set([
|
|
|
25893
26995
|
"query"
|
|
25894
26996
|
]);
|
|
25895
26997
|
var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
|
|
25896
|
-
var
|
|
25897
|
-
var homePattern = new RegExp(`${
|
|
26998
|
+
var escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
26999
|
+
var homePattern = new RegExp(`${escapeRegExp2(os4.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
25898
27000
|
var applyPatterns = (value) => {
|
|
25899
27001
|
let out = value;
|
|
25900
27002
|
out = out.replace(SECRET_VALUE, REDACTED);
|
|
@@ -25945,13 +27047,13 @@ var rootVariants = (workspaceRoot) => {
|
|
|
25945
27047
|
if (!workspaceRoot)
|
|
25946
27048
|
return [];
|
|
25947
27049
|
const roots = new Set;
|
|
25948
|
-
for (const candidate of [workspaceRoot,
|
|
25949
|
-
if (
|
|
27050
|
+
for (const candidate of [workspaceRoot, path20.resolve(workspaceRoot)]) {
|
|
27051
|
+
if (path20.isAbsolute(candidate) && candidate !== path20.parse(candidate).root)
|
|
25950
27052
|
roots.add(candidate);
|
|
25951
27053
|
}
|
|
25952
27054
|
try {
|
|
25953
27055
|
const canonical = realpathSync6(workspaceRoot);
|
|
25954
|
-
if (canonical !==
|
|
27056
|
+
if (canonical !== path20.parse(canonical).root)
|
|
25955
27057
|
roots.add(canonical);
|
|
25956
27058
|
} catch {}
|
|
25957
27059
|
return [...roots].toSorted((left, right) => right.length - left.length);
|
|
@@ -25973,39 +27075,20 @@ function assertMcpHost(host) {
|
|
|
25973
27075
|
throw new Error("MCP host must be cursor, codex_cli, or codex_desktop");
|
|
25974
27076
|
}
|
|
25975
27077
|
}
|
|
25976
|
-
var
|
|
25977
|
-
var
|
|
25978
|
-
var
|
|
25979
|
-
|
|
25980
|
-
|
|
25981
|
-
|
|
25982
|
-
|
|
25983
|
-
|
|
25984
|
-
var readOnlyBranches = (family) => schemaBranches(boundedOperationJsonSchema(family)).filter((branch) => {
|
|
25985
|
-
const action = branch.properties?.action?.const;
|
|
25986
|
-
return typeof action === "string" && READ_ONLY_ACTIONS.has(action);
|
|
25987
|
-
});
|
|
25988
|
-
var advertisedToolSchemas = (attested) => {
|
|
25989
|
-
if (attested)
|
|
25990
|
-
return OPERATION_FAMILIES.map((family) => ({
|
|
27078
|
+
var READ_ONLY_ACTIONS2 = new Set(["list", "inspect", "preview", "explain", "export"]);
|
|
27079
|
+
var requiresCallerIdentity = (input) => typeof input !== "object" || input === null || !READ_ONLY_ACTIONS2.has(String(input.action));
|
|
27080
|
+
var advertisedToolSchemas = (attested) => OPERATION_FAMILIES.flatMap((family) => {
|
|
27081
|
+
const schema = flatOperationJsonSchema(family, { readOnly: !attested });
|
|
27082
|
+
if (!schema)
|
|
27083
|
+
return [];
|
|
27084
|
+
return [
|
|
27085
|
+
{
|
|
25991
27086
|
name: `workit_${family}`,
|
|
25992
|
-
description: operationDescription(family)
|
|
25993
|
-
inputSchema:
|
|
25994
|
-
}
|
|
25995
|
-
|
|
25996
|
-
|
|
25997
|
-
if (!branches.length)
|
|
25998
|
-
return [];
|
|
25999
|
-
const schema = boundedOperationJsonSchema(family);
|
|
26000
|
-
return [
|
|
26001
|
-
{
|
|
26002
|
-
name: `workit_${family}`,
|
|
26003
|
-
description: `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
|
|
26004
|
-
inputSchema: { type: "object", ...schema, oneOf: branches }
|
|
26005
|
-
}
|
|
26006
|
-
];
|
|
26007
|
-
});
|
|
26008
|
-
};
|
|
27087
|
+
description: attested ? operationDescription(family) : `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
|
|
27088
|
+
inputSchema: schema
|
|
27089
|
+
}
|
|
27090
|
+
];
|
|
27091
|
+
});
|
|
26009
27092
|
var sanitizeFailure = (result, workspaceRoot) => {
|
|
26010
27093
|
if (result.ok)
|
|
26011
27094
|
return result;
|
|
@@ -26052,12 +27135,12 @@ var thrownResult = (tool, error, workspaceRoot) => {
|
|
|
26052
27135
|
};
|
|
26053
27136
|
function createMcpServer(host, contextProvider) {
|
|
26054
27137
|
assertMcpHost(host);
|
|
26055
|
-
const here =
|
|
27138
|
+
const here = path20.dirname(fileURLToPath2(import.meta.url));
|
|
26056
27139
|
const sourceMarker = markSourcesLoaded([
|
|
26057
|
-
|
|
26058
|
-
|
|
26059
|
-
|
|
26060
|
-
].filter((file) =>
|
|
27140
|
+
path20.join(here, "server.ts"),
|
|
27141
|
+
path20.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
|
|
27142
|
+
path20.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
|
|
27143
|
+
].filter((file) => existsSync8(file)));
|
|
26061
27144
|
let staleWarned = false;
|
|
26062
27145
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
26063
27146
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -26181,7 +27264,7 @@ function createMcpServer(host, contextProvider) {
|
|
|
26181
27264
|
details: { operation: family }
|
|
26182
27265
|
}, workspaceRoot);
|
|
26183
27266
|
}
|
|
26184
|
-
const parsed =
|
|
27267
|
+
const parsed = parseAdvertisedOperation(family, request.params.arguments, host);
|
|
26185
27268
|
if (!parsed.ok)
|
|
26186
27269
|
return resultForClient(parsed, workspaceRoot);
|
|
26187
27270
|
if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
|
|
@@ -26236,8 +27319,8 @@ var main = async () => {
|
|
|
26236
27319
|
process.exitCode = 2;
|
|
26237
27320
|
}
|
|
26238
27321
|
};
|
|
26239
|
-
var entryPath = process.argv[1] ?
|
|
26240
|
-
if (entryPath ===
|
|
27322
|
+
var entryPath = process.argv[1] ? path21.resolve(process.argv[1]) : "";
|
|
27323
|
+
if (entryPath === path21.resolve(fileURLToPath3(import.meta.url)))
|
|
26241
27324
|
await main();
|
|
26242
27325
|
// packages/workit-core/src/hooks/descriptor.ts
|
|
26243
27326
|
var support = (descriptor, axis) => {
|
|
@@ -26266,12 +27349,34 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
26266
27349
|
// packages/workit-core/src/hooks/context.ts
|
|
26267
27350
|
var signals = new Map;
|
|
26268
27351
|
var cache = new Map;
|
|
27352
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
27353
|
+
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
27354
|
+
var PREFIXES = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
27355
|
+
var cache2 = new Map;
|
|
27356
|
+
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
27357
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
27358
|
+
var WRITE_TOOLS = new Set([
|
|
27359
|
+
"write",
|
|
27360
|
+
"edit",
|
|
27361
|
+
"multiedit",
|
|
27362
|
+
"notebookedit",
|
|
27363
|
+
"applypatch",
|
|
27364
|
+
"patch",
|
|
27365
|
+
"delete",
|
|
27366
|
+
"remove",
|
|
27367
|
+
"rename",
|
|
27368
|
+
"mkdir",
|
|
27369
|
+
"mv",
|
|
27370
|
+
"cp",
|
|
27371
|
+
"touch",
|
|
27372
|
+
"strreplace",
|
|
27373
|
+
"searchreplace",
|
|
27374
|
+
"editfile",
|
|
27375
|
+
"writefile"
|
|
27376
|
+
]);
|
|
26269
27377
|
// packages/workit-core/src/hooks/handle.ts
|
|
26270
27378
|
var offered = new Set;
|
|
26271
27379
|
var CLAUDE_JUDGES = new Set(["workit:verifier", "workit:reviewer"]);
|
|
26272
|
-
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
26273
|
-
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
26274
|
-
|
|
26275
27380
|
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
26276
27381
|
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
26277
27382
|
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
@@ -26295,6 +27400,7 @@ var CURSOR_DESCRIPTOR = {
|
|
|
26295
27400
|
"context.turn": none,
|
|
26296
27401
|
"shell.pre": { support: "native", native: "beforeShellExecution" },
|
|
26297
27402
|
"tool.pre": { support: "native", native: "preToolUse" },
|
|
27403
|
+
"write.pre": { support: "partial", native: "preToolUse" },
|
|
26298
27404
|
"shell.post": undocumented,
|
|
26299
27405
|
"subagent.start": { support: "native", native: "subagentStart" },
|
|
26300
27406
|
"subagent.stop": { support: "partial", native: "subagentStop" },
|