@picoai/tickets 0.4.0 → 0.5.5
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/.tickets/spec/AGENTS_EXAMPLE.md +6 -0
- package/.tickets/spec/TICKETS.md +31 -7
- package/.tickets/spec/version/20260205-tickets-spec.md +1 -1
- package/.tickets/spec/version/20260311-tickets-spec.md +1 -1
- package/.tickets/spec/version/20260317-2-tickets-spec.md +1 -1
- package/.tickets/spec/version/20260317-3-tickets-spec.md +121 -0
- package/.tickets/spec/version/20260317-4-tickets-spec.md +120 -0
- package/.tickets/spec/version/20260317-tickets-spec.md +1 -1
- package/README.md +15 -2
- package/package.json +1 -1
- package/release-history.json +14 -0
- package/src/cli.js +474 -10
- package/src/lib/constants.js +3 -1
- package/src/lib/projections.js +6 -0
- package/src/lib/validation.js +236 -0
package/src/lib/validation.js
CHANGED
|
@@ -4,6 +4,8 @@ import path from "node:path";
|
|
|
4
4
|
import {
|
|
5
5
|
ASSIGNMENT_MODE_VALUES,
|
|
6
6
|
CLAIM_ACTION_VALUES,
|
|
7
|
+
COMPLETION_ACCEPTANCE_VALUES,
|
|
8
|
+
COMPLETION_VERIFICATION_VALUES,
|
|
7
9
|
PLANNING_NODE_TYPES,
|
|
8
10
|
PRIORITY_VALUES,
|
|
9
11
|
RESOLUTION_VALUES,
|
|
@@ -298,6 +300,124 @@ export function validateTicket(ticketPath, allFields = false) {
|
|
|
298
300
|
}
|
|
299
301
|
}
|
|
300
302
|
|
|
303
|
+
if ("completion" in frontMatter) {
|
|
304
|
+
const completion = frontMatter.completion;
|
|
305
|
+
if (!completion || typeof completion !== "object" || Array.isArray(completion)) {
|
|
306
|
+
issues.push({
|
|
307
|
+
severity: "error",
|
|
308
|
+
code: "COMPLETION_INVALID",
|
|
309
|
+
message: "completion must be mapping",
|
|
310
|
+
ticket_path: ticketPath,
|
|
311
|
+
});
|
|
312
|
+
} else {
|
|
313
|
+
const acceptance = completion.acceptance_criteria;
|
|
314
|
+
const verification = completion.verification;
|
|
315
|
+
const overriddenBy = completion.overridden_by;
|
|
316
|
+
const overrideReason = completion.override_reason;
|
|
317
|
+
const overrideAt = completion.override_at;
|
|
318
|
+
|
|
319
|
+
if (!COMPLETION_ACCEPTANCE_VALUES.includes(acceptance)) {
|
|
320
|
+
issues.push({
|
|
321
|
+
severity: "error",
|
|
322
|
+
code: "COMPLETION_ACCEPTANCE_INVALID",
|
|
323
|
+
message: `completion.acceptance_criteria must be one of ${COMPLETION_ACCEPTANCE_VALUES.join("|")}`,
|
|
324
|
+
ticket_path: ticketPath,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (!COMPLETION_VERIFICATION_VALUES.includes(verification)) {
|
|
329
|
+
issues.push({
|
|
330
|
+
severity: "error",
|
|
331
|
+
code: "COMPLETION_VERIFICATION_INVALID",
|
|
332
|
+
message: `completion.verification must be one of ${COMPLETION_VERIFICATION_VALUES.join("|")}`,
|
|
333
|
+
ticket_path: ticketPath,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (frontMatter.status !== "done") {
|
|
338
|
+
issues.push({
|
|
339
|
+
severity: "error",
|
|
340
|
+
code: "COMPLETION_STATUS_INVALID",
|
|
341
|
+
message: "completion requires status done",
|
|
342
|
+
ticket_path: ticketPath,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const overrideRequired = acceptance !== "met" || verification !== "passed";
|
|
347
|
+
const hasOverrideBy = typeof overriddenBy === "string" && overriddenBy.trim();
|
|
348
|
+
const hasOverrideReason = typeof overrideReason === "string" && overrideReason.trim();
|
|
349
|
+
const hasOverrideAt = overrideAt !== undefined && overrideAt !== null;
|
|
350
|
+
|
|
351
|
+
if (overriddenBy !== undefined && overriddenBy !== null && typeof overriddenBy !== "string") {
|
|
352
|
+
issues.push({
|
|
353
|
+
severity: "error",
|
|
354
|
+
code: "COMPLETION_OVERRIDE_BY_INVALID",
|
|
355
|
+
message: "completion.overridden_by must be string or null",
|
|
356
|
+
ticket_path: ticketPath,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (overrideReason !== undefined && overrideReason !== null && typeof overrideReason !== "string") {
|
|
361
|
+
issues.push({
|
|
362
|
+
severity: "error",
|
|
363
|
+
code: "COMPLETION_OVERRIDE_REASON_INVALID",
|
|
364
|
+
message: "completion.override_reason must be string or null",
|
|
365
|
+
ticket_path: ticketPath,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (hasOverrideAt && !parseIso(overrideAt)) {
|
|
370
|
+
issues.push({
|
|
371
|
+
severity: "error",
|
|
372
|
+
code: "COMPLETION_OVERRIDE_AT_INVALID",
|
|
373
|
+
message: "completion.override_at must be ISO8601 UTC or null",
|
|
374
|
+
ticket_path: ticketPath,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (overrideRequired) {
|
|
379
|
+
if (!hasOverrideBy) {
|
|
380
|
+
issues.push({
|
|
381
|
+
severity: "error",
|
|
382
|
+
code: "COMPLETION_OVERRIDE_BY_MISSING",
|
|
383
|
+
message: "completion.overridden_by required when completion gates are not fully satisfied",
|
|
384
|
+
ticket_path: ticketPath,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
if (!hasOverrideReason) {
|
|
388
|
+
issues.push({
|
|
389
|
+
severity: "error",
|
|
390
|
+
code: "COMPLETION_OVERRIDE_REASON_MISSING",
|
|
391
|
+
message: "completion.override_reason required when completion gates are not fully satisfied",
|
|
392
|
+
ticket_path: ticketPath,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
if (!hasOverrideAt) {
|
|
396
|
+
issues.push({
|
|
397
|
+
severity: "error",
|
|
398
|
+
code: "COMPLETION_OVERRIDE_AT_MISSING",
|
|
399
|
+
message: "completion.override_at required when completion gates are not fully satisfied",
|
|
400
|
+
ticket_path: ticketPath,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
} else if (hasOverrideBy || hasOverrideReason || hasOverrideAt) {
|
|
404
|
+
issues.push({
|
|
405
|
+
severity: "error",
|
|
406
|
+
code: "COMPLETION_OVERRIDE_UNEXPECTED",
|
|
407
|
+
message: "completion override fields are only valid when acceptance criteria or verification are not fully satisfied",
|
|
408
|
+
ticket_path: ticketPath,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
} else if (frontMatter.status === "done") {
|
|
413
|
+
issues.push({
|
|
414
|
+
severity: "error",
|
|
415
|
+
code: "COMPLETION_MISSING",
|
|
416
|
+
message: "completion required when status is done",
|
|
417
|
+
ticket_path: ticketPath,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
301
421
|
if ("agent_limits" in frontMatter) {
|
|
302
422
|
if (!frontMatter.agent_limits || typeof frontMatter.agent_limits !== "object" || Array.isArray(frontMatter.agent_limits)) {
|
|
303
423
|
issues.push({
|
|
@@ -910,6 +1030,122 @@ export function validateRunLog(logPath, machineStrictDefault) {
|
|
|
910
1030
|
log: loc,
|
|
911
1031
|
});
|
|
912
1032
|
}
|
|
1033
|
+
|
|
1034
|
+
if ("completion" in entry) {
|
|
1035
|
+
const completion = entry.completion;
|
|
1036
|
+
if (!completion || typeof completion !== "object" || Array.isArray(completion)) {
|
|
1037
|
+
issues.push({
|
|
1038
|
+
severity: machineEntry ? "error" : "warning",
|
|
1039
|
+
code: "LOG_COMPLETION_INVALID",
|
|
1040
|
+
message: "completion must be mapping",
|
|
1041
|
+
log: loc,
|
|
1042
|
+
});
|
|
1043
|
+
} else {
|
|
1044
|
+
if (eventType !== "status") {
|
|
1045
|
+
issues.push({
|
|
1046
|
+
severity: machineEntry ? "error" : "warning",
|
|
1047
|
+
code: "LOG_COMPLETION_EVENT_INVALID",
|
|
1048
|
+
message: "completion is only valid on status events",
|
|
1049
|
+
log: loc,
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (!COMPLETION_ACCEPTANCE_VALUES.includes(completion.acceptance_criteria)) {
|
|
1054
|
+
issues.push({
|
|
1055
|
+
severity: machineEntry ? "error" : "warning",
|
|
1056
|
+
code: "LOG_COMPLETION_ACCEPTANCE_INVALID",
|
|
1057
|
+
message: `completion.acceptance_criteria must be one of ${COMPLETION_ACCEPTANCE_VALUES.join("|")}`,
|
|
1058
|
+
log: loc,
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (!COMPLETION_VERIFICATION_VALUES.includes(completion.verification)) {
|
|
1063
|
+
issues.push({
|
|
1064
|
+
severity: machineEntry ? "error" : "warning",
|
|
1065
|
+
code: "LOG_COMPLETION_VERIFICATION_INVALID",
|
|
1066
|
+
message: `completion.verification must be one of ${COMPLETION_VERIFICATION_VALUES.join("|")}`,
|
|
1067
|
+
log: loc,
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const overrideRequired =
|
|
1072
|
+
completion.acceptance_criteria !== "met" || completion.verification !== "passed";
|
|
1073
|
+
const hasOverrideBy =
|
|
1074
|
+
typeof completion.overridden_by === "string" && completion.overridden_by.trim();
|
|
1075
|
+
const hasOverrideReason =
|
|
1076
|
+
typeof completion.override_reason === "string" && completion.override_reason.trim();
|
|
1077
|
+
const hasOverrideAt = completion.override_at !== undefined && completion.override_at !== null;
|
|
1078
|
+
|
|
1079
|
+
if (
|
|
1080
|
+
completion.overridden_by !== undefined &&
|
|
1081
|
+
completion.overridden_by !== null &&
|
|
1082
|
+
typeof completion.overridden_by !== "string"
|
|
1083
|
+
) {
|
|
1084
|
+
issues.push({
|
|
1085
|
+
severity: machineEntry ? "error" : "warning",
|
|
1086
|
+
code: "LOG_COMPLETION_OVERRIDE_BY_INVALID",
|
|
1087
|
+
message: "completion.overridden_by must be string or null",
|
|
1088
|
+
log: loc,
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (
|
|
1093
|
+
completion.override_reason !== undefined &&
|
|
1094
|
+
completion.override_reason !== null &&
|
|
1095
|
+
typeof completion.override_reason !== "string"
|
|
1096
|
+
) {
|
|
1097
|
+
issues.push({
|
|
1098
|
+
severity: machineEntry ? "error" : "warning",
|
|
1099
|
+
code: "LOG_COMPLETION_OVERRIDE_REASON_INVALID",
|
|
1100
|
+
message: "completion.override_reason must be string or null",
|
|
1101
|
+
log: loc,
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (hasOverrideAt && !parseIso(completion.override_at)) {
|
|
1106
|
+
issues.push({
|
|
1107
|
+
severity: machineEntry ? "error" : "warning",
|
|
1108
|
+
code: "LOG_COMPLETION_OVERRIDE_AT_INVALID",
|
|
1109
|
+
message: "completion.override_at must be ISO8601 UTC or null",
|
|
1110
|
+
log: loc,
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (overrideRequired) {
|
|
1115
|
+
if (!hasOverrideBy) {
|
|
1116
|
+
issues.push({
|
|
1117
|
+
severity: machineEntry ? "error" : "warning",
|
|
1118
|
+
code: "LOG_COMPLETION_OVERRIDE_BY_MISSING",
|
|
1119
|
+
message: "completion.overridden_by required when completion gates are not fully satisfied",
|
|
1120
|
+
log: loc,
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
if (!hasOverrideReason) {
|
|
1124
|
+
issues.push({
|
|
1125
|
+
severity: machineEntry ? "error" : "warning",
|
|
1126
|
+
code: "LOG_COMPLETION_OVERRIDE_REASON_MISSING",
|
|
1127
|
+
message: "completion.override_reason required when completion gates are not fully satisfied",
|
|
1128
|
+
log: loc,
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
if (!hasOverrideAt) {
|
|
1132
|
+
issues.push({
|
|
1133
|
+
severity: machineEntry ? "error" : "warning",
|
|
1134
|
+
code: "LOG_COMPLETION_OVERRIDE_AT_MISSING",
|
|
1135
|
+
message: "completion.override_at required when completion gates are not fully satisfied",
|
|
1136
|
+
log: loc,
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
} else if (hasOverrideBy || hasOverrideReason || hasOverrideAt) {
|
|
1140
|
+
issues.push({
|
|
1141
|
+
severity: machineEntry ? "error" : "warning",
|
|
1142
|
+
code: "LOG_COMPLETION_OVERRIDE_UNEXPECTED",
|
|
1143
|
+
message: "completion override fields are only valid when acceptance criteria or verification are not fully satisfied",
|
|
1144
|
+
log: loc,
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
913
1149
|
});
|
|
914
1150
|
|
|
915
1151
|
return issues;
|