@wrongstack/requirement-intake 0.306.4 → 0.307.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +345 -267
- package/dist/service-helpers.d.ts +19 -0
- package/dist/service.d.ts +15 -0
- package/dist/types.d.ts +11 -0
- package/dist/vibe.d.ts +44 -0
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -18,4 +18,5 @@ export { IntakeEventEmitter } from './events.js';
|
|
|
18
18
|
export { RequirementIntakeStore, newIntakeId, type RequirementIntakeStoreOptions, type IntakeIndexEntry, type StoreUpdateOptions, type StoreCreateResult, } from './store.js';
|
|
19
19
|
export { llmSuggestionOutputSchema, validateLlmSuggestionOutput, toProposals, assertSuggestionString, type LlmSuggestionRequest, type LlmSuggestionOutput, type LlmSuggestionGenerator, type NormalizedLlmSuggestion, } from './suggestions.js';
|
|
20
20
|
export { RequirementIntakeService, type RequirementIntakeServiceOptions, type IntakeCreateResult, type IntakeSubmitResult, type IntakeListFilter, } from './service.js';
|
|
21
|
+
export { VIBE_TAG_REGEX, hasVibeTag, stripVibeTag, deriveVibeState, type VibeProtocolStage, type VibeProtocolState, } from './vibe.js';
|
|
21
22
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -878,8 +878,45 @@ function assertSuggestionString(value, label, max) {
|
|
|
878
878
|
return value;
|
|
879
879
|
}
|
|
880
880
|
|
|
881
|
-
// src/service.ts
|
|
881
|
+
// src/service-helpers.ts
|
|
882
882
|
import { ulid as ulid4 } from "@wrongstack/core/utils";
|
|
883
|
+
|
|
884
|
+
// src/vibe.ts
|
|
885
|
+
var VIBE_TAG_REGEX = /\[VIBE\]/i;
|
|
886
|
+
function hasVibeTag(text) {
|
|
887
|
+
if (!text) return false;
|
|
888
|
+
return VIBE_TAG_REGEX.test(text);
|
|
889
|
+
}
|
|
890
|
+
function stripVibeTag(text) {
|
|
891
|
+
return text.replace(VIBE_TAG_REGEX, "").replace(/[ \t]{2,}/g, " ").trim();
|
|
892
|
+
}
|
|
893
|
+
function deriveVibeState(rawText, existingState, now = Date.now()) {
|
|
894
|
+
const containsTag = hasVibeTag(rawText);
|
|
895
|
+
if (existingState?.isVibeMode) {
|
|
896
|
+
return {
|
|
897
|
+
...existingState,
|
|
898
|
+
isVibeMode: true
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
if (containsTag) {
|
|
902
|
+
return {
|
|
903
|
+
isVibeMode: true,
|
|
904
|
+
detectedAt: now,
|
|
905
|
+
stage: "synthesizer"
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
return void 0;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// src/service-helpers.ts
|
|
912
|
+
function appendItems(target, raw) {
|
|
913
|
+
const items = raw.split(/[\n,;]+/).map((item) => item.trim()).filter((item) => item.length > 0);
|
|
914
|
+
for (const item of items) {
|
|
915
|
+
if (!target.includes(item) && target.length < MAX_ARRAY_ITEMS) {
|
|
916
|
+
target.push(item);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
883
920
|
var ANSWER_FIELD_MAPPING = {
|
|
884
921
|
business_goal: {
|
|
885
922
|
set: (record, value) => {
|
|
@@ -931,11 +968,294 @@ var ANSWER_FIELD_MAPPING = {
|
|
|
931
968
|
}
|
|
932
969
|
}
|
|
933
970
|
};
|
|
934
|
-
function
|
|
935
|
-
const
|
|
936
|
-
const
|
|
937
|
-
|
|
971
|
+
function buildNewIntakeRecord(input, ctx, now, catalog) {
|
|
972
|
+
const titleProvided = input.title !== void 0 && input.title.trim().length > 0;
|
|
973
|
+
const title = titleProvided ? input.title.trim() : deterministicTitle(input.originalRequest);
|
|
974
|
+
const requestType = normalizeRequestType(input.requestType);
|
|
975
|
+
const idempotencyKey = input.idempotencyKey?.trim();
|
|
976
|
+
const attachments = (input.attachments ?? []).map((attachment) => ({
|
|
977
|
+
id: `${ATTACHMENT_ID_PREFIX}${ulid4()}`,
|
|
978
|
+
name: attachment.name,
|
|
979
|
+
kind: attachment.kind,
|
|
980
|
+
path: attachment.path,
|
|
981
|
+
url: attachment.url,
|
|
982
|
+
sizeBytes: attachment.sizeBytes,
|
|
983
|
+
mimeType: attachment.mimeType,
|
|
984
|
+
source: "user",
|
|
985
|
+
addedBy: ctx.id,
|
|
986
|
+
addedAt: now
|
|
987
|
+
}));
|
|
988
|
+
const relatedResources = (input.relatedResources ?? []).map((resource) => ({
|
|
989
|
+
id: `${RELATED_RESOURCE_ID_PREFIX}${ulid4()}`,
|
|
990
|
+
kind: resource.kind,
|
|
991
|
+
reference: resource.reference,
|
|
992
|
+
title: resource.title,
|
|
993
|
+
source: "user",
|
|
994
|
+
addedBy: ctx.id,
|
|
995
|
+
addedAt: now
|
|
996
|
+
}));
|
|
997
|
+
const vibeState = deriveVibeState(
|
|
998
|
+
input.originalRequest,
|
|
999
|
+
input.vibeProtocol ?? (input.isVibeMode ? { isVibeMode: true, detectedAt: now, stage: "synthesizer" } : void 0),
|
|
1000
|
+
now
|
|
1001
|
+
);
|
|
1002
|
+
return {
|
|
1003
|
+
id: newIntakeId(),
|
|
1004
|
+
projectId: input.projectId,
|
|
1005
|
+
title,
|
|
1006
|
+
originalRequest: input.originalRequest,
|
|
1007
|
+
normalizedSummary: deterministicSummary(input.originalRequest),
|
|
1008
|
+
requestType,
|
|
1009
|
+
status: "draft",
|
|
1010
|
+
priority: input.priority ?? "unspecified",
|
|
1011
|
+
requestedBy: input.requestedBy,
|
|
1012
|
+
...vibeState ? { isVibeMode: true, vibeProtocol: vibeState } : {},
|
|
1013
|
+
...input.businessGoal !== void 0 ? { businessGoal: input.businessGoal } : {},
|
|
1014
|
+
targetUsers: [...input.targetUsers ?? []],
|
|
1015
|
+
...input.expectedOutcome !== void 0 ? { expectedOutcome: input.expectedOutcome } : {},
|
|
1016
|
+
...input.scopeNotes !== void 0 ? { scopeNotes: input.scopeNotes } : {},
|
|
1017
|
+
constraints: [...input.constraints ?? []],
|
|
1018
|
+
providedContext: [...input.providedContext ?? []],
|
|
1019
|
+
attachments,
|
|
1020
|
+
relatedResources,
|
|
1021
|
+
answers: [],
|
|
1022
|
+
questions: buildInitialQuestions(input, catalog),
|
|
1023
|
+
llmSuggestions: [],
|
|
1024
|
+
metadata: { ...input.metadata ?? {} },
|
|
1025
|
+
fieldSources: {
|
|
1026
|
+
...titleProvided ? { title: "user" } : { title: "deterministic" },
|
|
1027
|
+
normalized_summary: "deterministic",
|
|
1028
|
+
request_type: input.requestType !== void 0 ? "user" : "deterministic",
|
|
1029
|
+
priority: input.priority !== void 0 ? "user" : "deterministic",
|
|
1030
|
+
...input.businessGoal !== void 0 ? { business_goal: "user" } : {},
|
|
1031
|
+
...input.targetUsers !== void 0 ? { target_users: "user" } : {},
|
|
1032
|
+
...input.expectedOutcome !== void 0 ? { expected_outcome: "user" } : {},
|
|
1033
|
+
...input.scopeNotes !== void 0 ? { scope_notes: "user" } : {},
|
|
1034
|
+
...input.constraints !== void 0 ? { constraints: "user" } : {},
|
|
1035
|
+
...input.providedContext !== void 0 ? { provided_context: "user" } : {},
|
|
1036
|
+
...attachments.length > 0 ? { attachments: "user" } : {},
|
|
1037
|
+
...relatedResources.length > 0 ? { related_resources: "user" } : {}
|
|
1038
|
+
},
|
|
1039
|
+
...idempotencyKey !== void 0 && idempotencyKey.length > 0 ? { idempotencyKey } : {},
|
|
1040
|
+
version: 1,
|
|
1041
|
+
history: [{ at: now, actor: ctx.id, actorType: ctx.type, action: "created" }],
|
|
1042
|
+
createdAt: now,
|
|
1043
|
+
updatedAt: now
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
function assertIntakeSubmitReady(record) {
|
|
1047
|
+
const issues = [];
|
|
1048
|
+
if (record.originalRequest.trim().length === 0) {
|
|
1049
|
+
issues.push({ field: "originalRequest", message: "original request must not be empty" });
|
|
1050
|
+
}
|
|
1051
|
+
if (record.title.trim().length === 0) {
|
|
1052
|
+
issues.push({ field: "title", message: "title must not be empty" });
|
|
1053
|
+
}
|
|
1054
|
+
if (record.requestedBy.trim().length === 0) {
|
|
1055
|
+
issues.push({ field: "requestedBy", message: "requester must not be empty" });
|
|
1056
|
+
}
|
|
1057
|
+
if (record.projectId.trim().length === 0) {
|
|
1058
|
+
issues.push({ field: "projectId", message: "project must not be empty" });
|
|
1059
|
+
}
|
|
1060
|
+
if (issues.length > 0) {
|
|
1061
|
+
throw new IntakeValidationError(issues, "Requirement intake is not ready for submission");
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function findSuggestionProposal(record, proposalId) {
|
|
1065
|
+
const proposal2 = record.llmSuggestions.find((candidate) => candidate.id === proposalId);
|
|
1066
|
+
if (!proposal2) {
|
|
1067
|
+
throw new IntakeValidationError([
|
|
1068
|
+
{ field: "suggestionId", message: `suggestion not found: ${proposalId}` }
|
|
1069
|
+
]);
|
|
1070
|
+
}
|
|
1071
|
+
return proposal2;
|
|
1072
|
+
}
|
|
1073
|
+
function applySuggestionProposal(record, proposal2) {
|
|
1074
|
+
switch (proposal2.kind) {
|
|
1075
|
+
case "title": {
|
|
1076
|
+
const value = assertSuggestionString(proposal2.value, "title", MAX_TITLE_LENGTH);
|
|
1077
|
+
record.title = value;
|
|
1078
|
+
record.fieldSources.title = "llm";
|
|
1079
|
+
break;
|
|
1080
|
+
}
|
|
1081
|
+
case "summary": {
|
|
1082
|
+
const value = assertSuggestionString(
|
|
1083
|
+
proposal2.value,
|
|
1084
|
+
"normalized_summary",
|
|
1085
|
+
MAX_SUMMARY_LENGTH
|
|
1086
|
+
);
|
|
1087
|
+
record.normalizedSummary = value;
|
|
1088
|
+
record.fieldSources.normalized_summary = "llm";
|
|
1089
|
+
break;
|
|
1090
|
+
}
|
|
1091
|
+
case "request_type": {
|
|
1092
|
+
const value = normalizeRequestType(proposal2.value);
|
|
1093
|
+
record.requestType = value;
|
|
1094
|
+
record.fieldSources.request_type = "llm";
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
1097
|
+
case "priority": {
|
|
1098
|
+
const value = String(proposal2.value).trim().toLowerCase();
|
|
1099
|
+
if (INTAKE_PRIORITIES.includes(value)) {
|
|
1100
|
+
record.priority = value;
|
|
1101
|
+
record.fieldSources.priority = "llm";
|
|
1102
|
+
}
|
|
1103
|
+
break;
|
|
1104
|
+
}
|
|
1105
|
+
case "constraint": {
|
|
1106
|
+
const value = assertSuggestionString(proposal2.value, "constraint", MAX_STRING_FIELD_LENGTH);
|
|
1107
|
+
appendItems(record.constraints, value);
|
|
1108
|
+
record.fieldSources.constraints = "llm";
|
|
1109
|
+
break;
|
|
1110
|
+
}
|
|
1111
|
+
case "target_user": {
|
|
1112
|
+
const value = assertSuggestionString(
|
|
1113
|
+
proposal2.value,
|
|
1114
|
+
"target_user",
|
|
1115
|
+
MAX_STRING_FIELD_LENGTH
|
|
1116
|
+
);
|
|
1117
|
+
appendItems(record.targetUsers, value);
|
|
1118
|
+
record.fieldSources.target_users = "llm";
|
|
1119
|
+
break;
|
|
1120
|
+
}
|
|
1121
|
+
case "outcome": {
|
|
1122
|
+
const value = assertSuggestionString(proposal2.value, "outcome", MAX_STRING_FIELD_LENGTH);
|
|
1123
|
+
record.expectedOutcome = value;
|
|
1124
|
+
record.fieldSources.expected_outcome = "llm";
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
case "question": {
|
|
1128
|
+
const template = proposal2.value;
|
|
1129
|
+
if (typeof template === "object" && template !== null && typeof template.field === "string" && typeof template.question === "string") {
|
|
1130
|
+
upsertQuestion(record, {
|
|
1131
|
+
field: template.field,
|
|
1132
|
+
question: template.question,
|
|
1133
|
+
required: template.required
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
function applyOptionalString(record, field, value) {
|
|
1141
|
+
if (value === void 0) return;
|
|
1142
|
+
const trimmed = value.trim();
|
|
1143
|
+
if (trimmed.length === 0) {
|
|
1144
|
+
delete record[field];
|
|
1145
|
+
} else {
|
|
1146
|
+
record[field] = trimmed;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
function markUserSources(record, changedKeys) {
|
|
1150
|
+
const mapping = {
|
|
1151
|
+
title: "title",
|
|
1152
|
+
requestType: "request_type",
|
|
1153
|
+
priority: "priority",
|
|
1154
|
+
businessGoal: "business_goal",
|
|
1155
|
+
targetUsers: "target_users",
|
|
1156
|
+
expectedOutcome: "expected_outcome",
|
|
1157
|
+
scopeNotes: "scope_notes",
|
|
1158
|
+
constraints: "constraints",
|
|
1159
|
+
providedContext: "provided_context"
|
|
1160
|
+
};
|
|
1161
|
+
for (const key of changedKeys) {
|
|
1162
|
+
const sourceField = mapping[key];
|
|
1163
|
+
if (sourceField) {
|
|
1164
|
+
record.fieldSources[sourceField] = "user";
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function markQuestionAnswered(record, field, value) {
|
|
1169
|
+
const question = record.questions.find((candidate) => candidate.field === field);
|
|
1170
|
+
if (question && question.status === "unanswered") {
|
|
1171
|
+
question.status = "answered";
|
|
1172
|
+
question.answer = value;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
function applyAnswerToRecord(record, validated, actorId, now) {
|
|
1176
|
+
const matchingQuestion = record.questions.find((candidate) => candidate.field === validated.field);
|
|
1177
|
+
const answer = {
|
|
1178
|
+
id: `${ANSWER_ID_PREFIX}${ulid4()}`,
|
|
1179
|
+
field: validated.field,
|
|
1180
|
+
question: validated.question ?? matchingQuestion?.question ?? validated.field,
|
|
1181
|
+
answer: validated.answer,
|
|
1182
|
+
answeredBy: actorId,
|
|
1183
|
+
answeredAt: now,
|
|
1184
|
+
source: "user"
|
|
1185
|
+
};
|
|
1186
|
+
record.answers.push(answer);
|
|
1187
|
+
markQuestionAnswered(record, validated.field, validated.answer);
|
|
1188
|
+
const mapping = ANSWER_FIELD_MAPPING[validated.field];
|
|
1189
|
+
if (mapping) {
|
|
1190
|
+
mapping.set(record, validated.answer);
|
|
1191
|
+
const sourceKey = validated.field;
|
|
1192
|
+
record.fieldSources[sourceKey] = "user";
|
|
1193
|
+
}
|
|
1194
|
+
return answer;
|
|
1195
|
+
}
|
|
1196
|
+
function applyAnswerUpdateToRecord(record, answerId, newAnswer) {
|
|
1197
|
+
const answer = record.answers.find((candidate) => candidate.id === answerId);
|
|
1198
|
+
if (!answer) {
|
|
1199
|
+
throw new IntakeValidationError([
|
|
1200
|
+
{ field: "answerId", message: `answer not found: ${answerId}` }
|
|
1201
|
+
]);
|
|
1202
|
+
}
|
|
1203
|
+
answer.answer = newAnswer;
|
|
1204
|
+
answer.answeredAt = Date.now();
|
|
1205
|
+
const question = record.questions.find((candidate) => candidate.field === answer.field);
|
|
1206
|
+
if (question) {
|
|
1207
|
+
question.answer = newAnswer;
|
|
1208
|
+
question.status = "answered";
|
|
1209
|
+
}
|
|
938
1210
|
}
|
|
1211
|
+
function applyAttachmentToRecord(record, validated, actorId, now) {
|
|
1212
|
+
if (record.attachments.length >= MAX_ATTACHMENTS) {
|
|
1213
|
+
throw new IntakeValidationError([
|
|
1214
|
+
{ field: "attachments", message: `maximum of ${MAX_ATTACHMENTS} attachments reached` }
|
|
1215
|
+
]);
|
|
1216
|
+
}
|
|
1217
|
+
const attachment = {
|
|
1218
|
+
id: `${ATTACHMENT_ID_PREFIX}${ulid4()}`,
|
|
1219
|
+
name: validated.attachment.name,
|
|
1220
|
+
kind: validated.attachment.kind,
|
|
1221
|
+
path: validated.attachment?.path,
|
|
1222
|
+
url: validated.attachment?.url,
|
|
1223
|
+
sizeBytes: validated.attachment?.sizeBytes,
|
|
1224
|
+
mimeType: validated.attachment?.mimeType,
|
|
1225
|
+
source: "user",
|
|
1226
|
+
addedBy: actorId,
|
|
1227
|
+
addedAt: now
|
|
1228
|
+
};
|
|
1229
|
+
record.attachments.push(attachment);
|
|
1230
|
+
record.fieldSources.attachments = "user";
|
|
1231
|
+
markQuestionAnswered(record, "attachments", attachment.name);
|
|
1232
|
+
return attachment;
|
|
1233
|
+
}
|
|
1234
|
+
function applyRelatedResourceToRecord(record, validated, actorId, now) {
|
|
1235
|
+
if (record.relatedResources.length >= MAX_RELATED_RESOURCES) {
|
|
1236
|
+
throw new IntakeValidationError([
|
|
1237
|
+
{
|
|
1238
|
+
field: "relatedResources",
|
|
1239
|
+
message: `maximum of ${MAX_RELATED_RESOURCES} related resources reached`
|
|
1240
|
+
}
|
|
1241
|
+
]);
|
|
1242
|
+
}
|
|
1243
|
+
const resource = {
|
|
1244
|
+
id: `${RELATED_RESOURCE_ID_PREFIX}${ulid4()}`,
|
|
1245
|
+
kind: validated.relatedResource.kind,
|
|
1246
|
+
reference: validated.relatedResource.reference,
|
|
1247
|
+
title: validated.relatedResource?.title,
|
|
1248
|
+
source: "user",
|
|
1249
|
+
addedBy: actorId,
|
|
1250
|
+
addedAt: now
|
|
1251
|
+
};
|
|
1252
|
+
record.relatedResources.push(resource);
|
|
1253
|
+
record.fieldSources.related_resources = "user";
|
|
1254
|
+
markQuestionAnswered(record, "related_resources", resource.reference);
|
|
1255
|
+
return resource;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// src/service.ts
|
|
939
1259
|
var RequirementIntakeService = class {
|
|
940
1260
|
store;
|
|
941
1261
|
authorizer;
|
|
@@ -1057,6 +1377,8 @@ var RequirementIntakeService = class {
|
|
|
1057
1377
|
if (validated.providedContext !== void 0)
|
|
1058
1378
|
next.providedContext = [...validated.providedContext];
|
|
1059
1379
|
if (validated.metadata !== void 0) next.metadata = validated.metadata;
|
|
1380
|
+
if (validated.isVibeMode !== void 0) next.isVibeMode = validated.isVibeMode;
|
|
1381
|
+
if (validated.vibeProtocol !== void 0) next.vibeProtocol = validated.vibeProtocol;
|
|
1060
1382
|
markUserSources(next, changedKeys);
|
|
1061
1383
|
}).then((updated) => {
|
|
1062
1384
|
this.afterMutation(updated, ctx, "RequirementIntakeUpdated");
|
|
@@ -1072,25 +1394,7 @@ var RequirementIntakeService = class {
|
|
|
1072
1394
|
id,
|
|
1073
1395
|
this.updateMeta(ctx, "answer_added", [validated.field], expectedVersion),
|
|
1074
1396
|
(next) => {
|
|
1075
|
-
|
|
1076
|
-
const answer = {
|
|
1077
|
-
id: `${ANSWER_ID_PREFIX}${ulid4()}`,
|
|
1078
|
-
field: validated.field,
|
|
1079
|
-
question: validated.question ?? question?.question ?? validated.field,
|
|
1080
|
-
answer: validated.answer,
|
|
1081
|
-
source: "user",
|
|
1082
|
-
answeredBy: ctx.id,
|
|
1083
|
-
answeredAt: Date.now()
|
|
1084
|
-
};
|
|
1085
|
-
next.answers.push(answer);
|
|
1086
|
-
if (question && question.status === "unanswered") {
|
|
1087
|
-
question.status = "answered";
|
|
1088
|
-
question.answer = validated.answer;
|
|
1089
|
-
}
|
|
1090
|
-
ANSWER_FIELD_MAPPING[validated.field]?.set(next, validated.answer);
|
|
1091
|
-
if (INTAKE_FIELDS.includes(validated.field)) {
|
|
1092
|
-
next.fieldSources[validated.field] = "user";
|
|
1093
|
-
}
|
|
1397
|
+
applyAnswerToRecord(next, validated, ctx.id, Date.now());
|
|
1094
1398
|
}
|
|
1095
1399
|
).then((updated) => {
|
|
1096
1400
|
this.afterMutation(updated, ctx, "RequirementIntakeUpdated");
|
|
@@ -1107,19 +1411,7 @@ var RequirementIntakeService = class {
|
|
|
1107
1411
|
})
|
|
1108
1412
|
);
|
|
1109
1413
|
return this.store.update(id, this.updateMeta(ctx, "answer_updated", [answerId], expectedVersion), (next) => {
|
|
1110
|
-
|
|
1111
|
-
if (!answer) {
|
|
1112
|
-
throw new IntakeValidationError([
|
|
1113
|
-
{ field: "answerId", message: `answer not found: ${answerId}` }
|
|
1114
|
-
]);
|
|
1115
|
-
}
|
|
1116
|
-
answer.answer = validated.answer;
|
|
1117
|
-
answer.answeredAt = Date.now();
|
|
1118
|
-
const question = next.questions.find((candidate) => candidate.field === answer.field);
|
|
1119
|
-
if (question) {
|
|
1120
|
-
question.answer = validated.answer;
|
|
1121
|
-
question.status = "answered";
|
|
1122
|
-
}
|
|
1414
|
+
applyAnswerUpdateToRecord(next, answerId, validated.answer);
|
|
1123
1415
|
}).then((updated) => {
|
|
1124
1416
|
this.afterMutation(updated, ctx, "RequirementIntakeUpdated");
|
|
1125
1417
|
return updated;
|
|
@@ -1131,60 +1423,22 @@ var RequirementIntakeService = class {
|
|
|
1131
1423
|
const validated = this.guardValidation(() => validateAttachResourceInput(input));
|
|
1132
1424
|
const now = Date.now();
|
|
1133
1425
|
if (validated.attachment !== void 0) {
|
|
1134
|
-
if (record.attachments.length >= MAX_ATTACHMENTS) {
|
|
1135
|
-
throw new IntakeValidationError([
|
|
1136
|
-
{ field: "attachments", message: `maximum of ${MAX_ATTACHMENTS} attachments reached` }
|
|
1137
|
-
]);
|
|
1138
|
-
}
|
|
1139
1426
|
return this.store.update(
|
|
1140
1427
|
id,
|
|
1141
1428
|
this.updateMeta(ctx, "attachment_added", ["attachments"], expectedVersion),
|
|
1142
1429
|
(next) => {
|
|
1143
|
-
|
|
1144
|
-
id: `${ATTACHMENT_ID_PREFIX}${ulid4()}`,
|
|
1145
|
-
name: validated.attachment.name,
|
|
1146
|
-
kind: validated.attachment.kind,
|
|
1147
|
-
path: validated.attachment?.path,
|
|
1148
|
-
url: validated.attachment?.url,
|
|
1149
|
-
sizeBytes: validated.attachment?.sizeBytes,
|
|
1150
|
-
mimeType: validated.attachment?.mimeType,
|
|
1151
|
-
source: "user",
|
|
1152
|
-
addedBy: ctx.id,
|
|
1153
|
-
addedAt: now
|
|
1154
|
-
};
|
|
1155
|
-
next.attachments.push(attachment);
|
|
1156
|
-
next.fieldSources.attachments = "user";
|
|
1157
|
-
markQuestionAnswered(next, "attachments", attachment.name);
|
|
1430
|
+
applyAttachmentToRecord(next, validated, ctx.id, now);
|
|
1158
1431
|
}
|
|
1159
1432
|
).then((updated) => {
|
|
1160
1433
|
this.afterMutation(updated, ctx, "RequirementIntakeUpdated");
|
|
1161
1434
|
return updated;
|
|
1162
1435
|
});
|
|
1163
1436
|
}
|
|
1164
|
-
if (record.relatedResources.length >= MAX_RELATED_RESOURCES) {
|
|
1165
|
-
throw new IntakeValidationError([
|
|
1166
|
-
{
|
|
1167
|
-
field: "relatedResources",
|
|
1168
|
-
message: `maximum of ${MAX_RELATED_RESOURCES} related resources reached`
|
|
1169
|
-
}
|
|
1170
|
-
]);
|
|
1171
|
-
}
|
|
1172
1437
|
return this.store.update(
|
|
1173
1438
|
id,
|
|
1174
1439
|
this.updateMeta(ctx, "related_resource_added", ["related_resources"], expectedVersion),
|
|
1175
1440
|
(next) => {
|
|
1176
|
-
|
|
1177
|
-
id: `${RELATED_RESOURCE_ID_PREFIX}${ulid4()}`,
|
|
1178
|
-
kind: validated.relatedResource.kind,
|
|
1179
|
-
reference: validated.relatedResource.reference,
|
|
1180
|
-
title: validated.relatedResource?.title,
|
|
1181
|
-
source: "user",
|
|
1182
|
-
addedBy: ctx.id,
|
|
1183
|
-
addedAt: now
|
|
1184
|
-
};
|
|
1185
|
-
next.relatedResources.push(resource);
|
|
1186
|
-
next.fieldSources.related_resources = "user";
|
|
1187
|
-
markQuestionAnswered(next, "related_resources", resource.reference);
|
|
1441
|
+
applyRelatedResourceToRecord(next, validated, ctx.id, now);
|
|
1188
1442
|
}
|
|
1189
1443
|
).then((updated) => {
|
|
1190
1444
|
this.afterMutation(updated, ctx, "RequirementIntakeUpdated");
|
|
@@ -1452,73 +1706,7 @@ var RequirementIntakeService = class {
|
|
|
1452
1706
|
// Internals
|
|
1453
1707
|
// -------------------------------------------------------------------------
|
|
1454
1708
|
buildNewRecord(input, ctx, now) {
|
|
1455
|
-
|
|
1456
|
-
const title = titleProvided ? input.title.trim() : deterministicTitle(input.originalRequest);
|
|
1457
|
-
const requestType = normalizeRequestType(input.requestType);
|
|
1458
|
-
const idempotencyKey = input.idempotencyKey?.trim();
|
|
1459
|
-
const attachments = (input.attachments ?? []).map((attachment) => ({
|
|
1460
|
-
id: `${ATTACHMENT_ID_PREFIX}${ulid4()}`,
|
|
1461
|
-
name: attachment.name,
|
|
1462
|
-
kind: attachment.kind,
|
|
1463
|
-
path: attachment.path,
|
|
1464
|
-
url: attachment.url,
|
|
1465
|
-
sizeBytes: attachment.sizeBytes,
|
|
1466
|
-
mimeType: attachment.mimeType,
|
|
1467
|
-
source: "user",
|
|
1468
|
-
addedBy: ctx.id,
|
|
1469
|
-
addedAt: now
|
|
1470
|
-
}));
|
|
1471
|
-
const relatedResources = (input.relatedResources ?? []).map((resource) => ({
|
|
1472
|
-
id: `${RELATED_RESOURCE_ID_PREFIX}${ulid4()}`,
|
|
1473
|
-
kind: resource.kind,
|
|
1474
|
-
reference: resource.reference,
|
|
1475
|
-
title: resource.title,
|
|
1476
|
-
source: "user",
|
|
1477
|
-
addedBy: ctx.id,
|
|
1478
|
-
addedAt: now
|
|
1479
|
-
}));
|
|
1480
|
-
return {
|
|
1481
|
-
id: newIntakeId(),
|
|
1482
|
-
projectId: input.projectId,
|
|
1483
|
-
title,
|
|
1484
|
-
originalRequest: input.originalRequest,
|
|
1485
|
-
normalizedSummary: deterministicSummary(input.originalRequest),
|
|
1486
|
-
requestType,
|
|
1487
|
-
status: "draft",
|
|
1488
|
-
priority: input.priority ?? "unspecified",
|
|
1489
|
-
requestedBy: input.requestedBy,
|
|
1490
|
-
...input.businessGoal !== void 0 ? { businessGoal: input.businessGoal } : {},
|
|
1491
|
-
targetUsers: [...input.targetUsers ?? []],
|
|
1492
|
-
...input.expectedOutcome !== void 0 ? { expectedOutcome: input.expectedOutcome } : {},
|
|
1493
|
-
...input.scopeNotes !== void 0 ? { scopeNotes: input.scopeNotes } : {},
|
|
1494
|
-
constraints: [...input.constraints ?? []],
|
|
1495
|
-
providedContext: [...input.providedContext ?? []],
|
|
1496
|
-
attachments,
|
|
1497
|
-
relatedResources,
|
|
1498
|
-
answers: [],
|
|
1499
|
-
questions: buildInitialQuestions(input, this.catalog),
|
|
1500
|
-
llmSuggestions: [],
|
|
1501
|
-
metadata: { ...input.metadata ?? {} },
|
|
1502
|
-
fieldSources: {
|
|
1503
|
-
...titleProvided ? { title: "user" } : { title: "deterministic" },
|
|
1504
|
-
normalized_summary: "deterministic",
|
|
1505
|
-
request_type: input.requestType !== void 0 ? "user" : "deterministic",
|
|
1506
|
-
priority: input.priority !== void 0 ? "user" : "deterministic",
|
|
1507
|
-
...input.businessGoal !== void 0 ? { business_goal: "user" } : {},
|
|
1508
|
-
...input.targetUsers !== void 0 ? { target_users: "user" } : {},
|
|
1509
|
-
...input.expectedOutcome !== void 0 ? { expected_outcome: "user" } : {},
|
|
1510
|
-
...input.scopeNotes !== void 0 ? { scope_notes: "user" } : {},
|
|
1511
|
-
...input.constraints !== void 0 ? { constraints: "user" } : {},
|
|
1512
|
-
...input.providedContext !== void 0 ? { provided_context: "user" } : {},
|
|
1513
|
-
...attachments.length > 0 ? { attachments: "user" } : {},
|
|
1514
|
-
...relatedResources.length > 0 ? { related_resources: "user" } : {}
|
|
1515
|
-
},
|
|
1516
|
-
...idempotencyKey !== void 0 && idempotencyKey.length > 0 ? { idempotencyKey } : {},
|
|
1517
|
-
version: 1,
|
|
1518
|
-
history: [{ at: now, actor: ctx.id, actorType: ctx.type, action: "created" }],
|
|
1519
|
-
createdAt: now,
|
|
1520
|
-
updatedAt: now
|
|
1521
|
-
};
|
|
1709
|
+
return buildNewIntakeRecord(input, ctx, now, this.catalog);
|
|
1522
1710
|
}
|
|
1523
1711
|
async requireRecord(id, ctx, operation) {
|
|
1524
1712
|
const record = await this.store.load(id);
|
|
@@ -1547,99 +1735,20 @@ var RequirementIntakeService = class {
|
|
|
1547
1735
|
}
|
|
1548
1736
|
}
|
|
1549
1737
|
assertSubmitReady(record) {
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
if (record.requestedBy.trim().length === 0) {
|
|
1558
|
-
issues.push({ field: "requestedBy", message: "requester must not be empty" });
|
|
1559
|
-
}
|
|
1560
|
-
if (record.projectId.trim().length === 0) {
|
|
1561
|
-
issues.push({ field: "projectId", message: "project must not be empty" });
|
|
1562
|
-
}
|
|
1563
|
-
if (issues.length > 0) {
|
|
1564
|
-
this.metrics.increment("intake.validation_failure");
|
|
1565
|
-
throw new IntakeValidationError(issues, "Requirement intake is not ready for submission");
|
|
1738
|
+
try {
|
|
1739
|
+
assertIntakeSubmitReady(record);
|
|
1740
|
+
} catch (error) {
|
|
1741
|
+
if (error instanceof IntakeValidationError) {
|
|
1742
|
+
this.metrics.increment("intake.validation_failure");
|
|
1743
|
+
}
|
|
1744
|
+
throw error;
|
|
1566
1745
|
}
|
|
1567
1746
|
}
|
|
1568
1747
|
findSuggestion(record, proposalId) {
|
|
1569
|
-
|
|
1570
|
-
if (!proposal2) {
|
|
1571
|
-
throw new IntakeValidationError([
|
|
1572
|
-
{ field: "suggestionId", message: `suggestion not found: ${proposalId}` }
|
|
1573
|
-
]);
|
|
1574
|
-
}
|
|
1575
|
-
return proposal2;
|
|
1748
|
+
return findSuggestionProposal(record, proposalId);
|
|
1576
1749
|
}
|
|
1577
1750
|
applyProposal(record, proposal2) {
|
|
1578
|
-
|
|
1579
|
-
case "title": {
|
|
1580
|
-
const value = assertSuggestionString(proposal2.value, "title", MAX_TITLE_LENGTH);
|
|
1581
|
-
record.title = value;
|
|
1582
|
-
record.fieldSources.title = "llm";
|
|
1583
|
-
break;
|
|
1584
|
-
}
|
|
1585
|
-
case "summary": {
|
|
1586
|
-
const value = assertSuggestionString(
|
|
1587
|
-
proposal2.value,
|
|
1588
|
-
"normalized_summary",
|
|
1589
|
-
MAX_SUMMARY_LENGTH
|
|
1590
|
-
);
|
|
1591
|
-
record.normalizedSummary = value;
|
|
1592
|
-
record.fieldSources.normalized_summary = "llm";
|
|
1593
|
-
break;
|
|
1594
|
-
}
|
|
1595
|
-
case "request_type": {
|
|
1596
|
-
const value = normalizeRequestType(proposal2.value);
|
|
1597
|
-
record.requestType = value;
|
|
1598
|
-
record.fieldSources.request_type = "llm";
|
|
1599
|
-
break;
|
|
1600
|
-
}
|
|
1601
|
-
case "priority": {
|
|
1602
|
-
const value = String(proposal2.value).trim().toLowerCase();
|
|
1603
|
-
if (INTAKE_PRIORITIES.includes(value)) {
|
|
1604
|
-
record.priority = value;
|
|
1605
|
-
record.fieldSources.priority = "llm";
|
|
1606
|
-
}
|
|
1607
|
-
break;
|
|
1608
|
-
}
|
|
1609
|
-
case "constraint": {
|
|
1610
|
-
const value = assertSuggestionString(proposal2.value, "constraint", MAX_STRING_FIELD_LENGTH);
|
|
1611
|
-
appendItems(record.constraints, value);
|
|
1612
|
-
record.fieldSources.constraints = "llm";
|
|
1613
|
-
break;
|
|
1614
|
-
}
|
|
1615
|
-
case "target_user": {
|
|
1616
|
-
const value = assertSuggestionString(
|
|
1617
|
-
proposal2.value,
|
|
1618
|
-
"target_user",
|
|
1619
|
-
MAX_STRING_FIELD_LENGTH
|
|
1620
|
-
);
|
|
1621
|
-
appendItems(record.targetUsers, value);
|
|
1622
|
-
record.fieldSources.target_users = "llm";
|
|
1623
|
-
break;
|
|
1624
|
-
}
|
|
1625
|
-
case "outcome": {
|
|
1626
|
-
const value = assertSuggestionString(proposal2.value, "outcome", MAX_STRING_FIELD_LENGTH);
|
|
1627
|
-
record.expectedOutcome = value;
|
|
1628
|
-
record.fieldSources.expected_outcome = "llm";
|
|
1629
|
-
break;
|
|
1630
|
-
}
|
|
1631
|
-
case "question": {
|
|
1632
|
-
const template = proposal2.value;
|
|
1633
|
-
if (typeof template === "object" && template !== null && typeof template.field === "string" && typeof template.question === "string") {
|
|
1634
|
-
upsertQuestion(record, {
|
|
1635
|
-
field: template.field,
|
|
1636
|
-
question: template.question,
|
|
1637
|
-
required: template.required
|
|
1638
|
-
});
|
|
1639
|
-
}
|
|
1640
|
-
break;
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1751
|
+
applySuggestionProposal(record, proposal2);
|
|
1643
1752
|
}
|
|
1644
1753
|
updateMeta(ctx, action, fields, expectedVersion) {
|
|
1645
1754
|
return {
|
|
@@ -1678,41 +1787,6 @@ var RequirementIntakeService = class {
|
|
|
1678
1787
|
}
|
|
1679
1788
|
}
|
|
1680
1789
|
};
|
|
1681
|
-
function applyOptionalString(record, field, value) {
|
|
1682
|
-
if (value === void 0) return;
|
|
1683
|
-
const trimmed = value.trim();
|
|
1684
|
-
if (trimmed.length === 0) {
|
|
1685
|
-
delete record[field];
|
|
1686
|
-
} else {
|
|
1687
|
-
record[field] = trimmed;
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
function markUserSources(record, changedKeys) {
|
|
1691
|
-
const mapping = {
|
|
1692
|
-
title: "title",
|
|
1693
|
-
requestType: "request_type",
|
|
1694
|
-
priority: "priority",
|
|
1695
|
-
businessGoal: "business_goal",
|
|
1696
|
-
targetUsers: "target_users",
|
|
1697
|
-
expectedOutcome: "expected_outcome",
|
|
1698
|
-
scopeNotes: "scope_notes",
|
|
1699
|
-
constraints: "constraints",
|
|
1700
|
-
providedContext: "provided_context"
|
|
1701
|
-
};
|
|
1702
|
-
for (const key of changedKeys) {
|
|
1703
|
-
const sourceField = mapping[key];
|
|
1704
|
-
if (sourceField) {
|
|
1705
|
-
record.fieldSources[sourceField] = "user";
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
function markQuestionAnswered(record, field, value) {
|
|
1710
|
-
const question = record.questions.find((candidate) => candidate.field === field);
|
|
1711
|
-
if (question && question.status === "unanswered") {
|
|
1712
|
-
question.status = "answered";
|
|
1713
|
-
question.answer = value;
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
1790
|
export {
|
|
1717
1791
|
ALLOWED_TRANSITIONS,
|
|
1718
1792
|
AllowAllIntakeAuthorizer,
|
|
@@ -1765,6 +1839,7 @@ export {
|
|
|
1765
1839
|
RequirementIntakeStore,
|
|
1766
1840
|
SUGGESTION_KINDS,
|
|
1767
1841
|
SUGGESTION_STATUSES,
|
|
1842
|
+
VIBE_TAG_REGEX,
|
|
1768
1843
|
answerInputSchema,
|
|
1769
1844
|
assertSuggestionString,
|
|
1770
1845
|
assertTransition,
|
|
@@ -1773,8 +1848,10 @@ export {
|
|
|
1773
1848
|
buildInitialQuestions,
|
|
1774
1849
|
canTransition,
|
|
1775
1850
|
createIntakeSchema,
|
|
1851
|
+
deriveVibeState,
|
|
1776
1852
|
deterministicSummary,
|
|
1777
1853
|
deterministicTitle,
|
|
1854
|
+
hasVibeTag,
|
|
1778
1855
|
isBlank,
|
|
1779
1856
|
isKnownStatus,
|
|
1780
1857
|
isMutableStatus,
|
|
@@ -1789,6 +1866,7 @@ export {
|
|
|
1789
1866
|
questionTemplateInputSchema,
|
|
1790
1867
|
relatedResourceInputSchema,
|
|
1791
1868
|
requestTypeSchema,
|
|
1869
|
+
stripVibeTag,
|
|
1792
1870
|
toProposals,
|
|
1793
1871
|
updateIntakeSchema,
|
|
1794
1872
|
upsertQuestion,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type IntakeQuestionTemplate } from './constants.js';
|
|
2
|
+
import type { AddAnswerInput, AttachResourceInput, CreateIntakeInput, IntakeAnswer, IntakeAttachment, IntakeContext, LlmSuggestionProposal, RelatedResource, RequirementIntakeRecord } from './types.js';
|
|
3
|
+
export declare function appendItems(target: string[], raw: string): void;
|
|
4
|
+
/** Answer fields that also update a record property. */
|
|
5
|
+
export declare const ANSWER_FIELD_MAPPING: Readonly<Record<string, {
|
|
6
|
+
set: (record: RequirementIntakeRecord, value: string) => void;
|
|
7
|
+
}>>;
|
|
8
|
+
export declare function buildNewIntakeRecord(input: CreateIntakeInput, ctx: IntakeContext, now: number, catalog: readonly IntakeQuestionTemplate[]): RequirementIntakeRecord;
|
|
9
|
+
export declare function assertIntakeSubmitReady(record: RequirementIntakeRecord): void;
|
|
10
|
+
export declare function findSuggestionProposal(record: RequirementIntakeRecord, proposalId: string): LlmSuggestionProposal;
|
|
11
|
+
export declare function applySuggestionProposal(record: RequirementIntakeRecord, proposal: LlmSuggestionProposal): void;
|
|
12
|
+
export declare function applyOptionalString(record: RequirementIntakeRecord, field: 'businessGoal' | 'expectedOutcome' | 'scopeNotes', value: string | undefined): void;
|
|
13
|
+
export declare function markUserSources(record: RequirementIntakeRecord, changedKeys: string[]): void;
|
|
14
|
+
export declare function markQuestionAnswered(record: RequirementIntakeRecord, field: string, value: string): void;
|
|
15
|
+
export declare function applyAnswerToRecord(record: RequirementIntakeRecord, validated: AddAnswerInput, actorId: string, now: number): IntakeAnswer;
|
|
16
|
+
export declare function applyAnswerUpdateToRecord(record: RequirementIntakeRecord, answerId: string, newAnswer: string): void;
|
|
17
|
+
export declare function applyAttachmentToRecord(record: RequirementIntakeRecord, validated: AttachResourceInput, actorId: string, now: number): IntakeAttachment;
|
|
18
|
+
export declare function applyRelatedResourceToRecord(record: RequirementIntakeRecord, validated: AttachResourceInput, actorId: string, now: number): RelatedResource;
|
|
19
|
+
//# sourceMappingURL=service-helpers.d.ts.map
|
package/dist/service.d.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirements Intake — service.
|
|
3
|
+
*
|
|
4
|
+
* The single entry point for all intake operations. Enforces:
|
|
5
|
+
* - deterministic validation of every input (untrusted text stays data),
|
|
6
|
+
* - authorization on every operation (fail closed),
|
|
7
|
+
* - lifecycle transitions via application logic only,
|
|
8
|
+
* - optimistic concurrency (expectedVersion),
|
|
9
|
+
* - idempotent create (idempotency key) and submit,
|
|
10
|
+
* - source tracking (user / llm / deterministic),
|
|
11
|
+
* - domain events + structured logs + metrics with safe fields only.
|
|
12
|
+
*
|
|
13
|
+
* The original request is immutable after creation: no API path can change
|
|
14
|
+
* `originalRequest`, and LLM suggestions can never write to it.
|
|
15
|
+
*/
|
|
1
16
|
import { type IntakeQuestionTemplate, type IntakeStatus } from './constants.js';
|
|
2
17
|
import type { IntakeAuthorizer } from './authorization.js';
|
|
3
18
|
import { IntakeEventEmitter } from './events.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* generated value separate, source-annotated, and user-editable.
|
|
7
7
|
*/
|
|
8
8
|
import type { IntakeAttachmentKind, IntakeField, IntakeFieldSource, IntakePriority, IntakeQuestionStatus, IntakeStatus, RelatedResourceKind, RequestType, SuggestionKind, SuggestionStatus } from './constants.js';
|
|
9
|
+
import type { VibeProtocolState } from './vibe.js';
|
|
9
10
|
/** Actor identity used for authorization and audit. */
|
|
10
11
|
export interface IntakeActor {
|
|
11
12
|
/** Stable actor id (user id, agent id, or automation id). */
|
|
@@ -116,6 +117,10 @@ export interface RequirementIntakeRecord {
|
|
|
116
117
|
questions: IntakeQuestion[];
|
|
117
118
|
llmSuggestions: LlmSuggestionProposal[];
|
|
118
119
|
metadata: Record<string, unknown>;
|
|
120
|
+
/** True when the request is processed under the Three-Stage VIBE Verification Protocol. */
|
|
121
|
+
isVibeMode?: boolean | undefined;
|
|
122
|
+
/** Persisted state of the VIBE protocol pipeline (Spec-Synthesizer -> Coder -> Auditor). */
|
|
123
|
+
vibeProtocol?: VibeProtocolState | undefined;
|
|
119
124
|
/** Source annotation per record field ('user' | 'llm' | 'deterministic'). */
|
|
120
125
|
fieldSources: Partial<Record<IntakeField, IntakeFieldSource>>;
|
|
121
126
|
/** Raw idempotency key used at creation (kept for diagnostics). */
|
|
@@ -151,6 +156,10 @@ export interface CreateIntakeInput {
|
|
|
151
156
|
attachments?: IntakeAttachmentInput[] | undefined;
|
|
152
157
|
relatedResources?: RelatedResourceInput[] | undefined;
|
|
153
158
|
metadata?: Record<string, unknown> | undefined;
|
|
159
|
+
/** Explicit override for Vibe Mode; if omitted, automatically detected from originalRequest. */
|
|
160
|
+
isVibeMode?: boolean | undefined;
|
|
161
|
+
/** Optional initial vibe state if resuming an existing cycle. */
|
|
162
|
+
vibeProtocol?: VibeProtocolState | undefined;
|
|
154
163
|
/** Idempotent-create key: same key + project returns the existing record. */
|
|
155
164
|
idempotencyKey?: string | undefined;
|
|
156
165
|
/**
|
|
@@ -191,6 +200,8 @@ export interface UpdateIntakeInput {
|
|
|
191
200
|
constraints?: string[] | undefined;
|
|
192
201
|
providedContext?: string[] | undefined;
|
|
193
202
|
metadata?: Record<string, unknown> | undefined;
|
|
203
|
+
isVibeMode?: boolean | undefined;
|
|
204
|
+
vibeProtocol?: VibeProtocolState | undefined;
|
|
194
205
|
}
|
|
195
206
|
/** Payload for answering an intake question. */
|
|
196
207
|
export interface AddAnswerInput {
|
package/dist/vibe.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirements Intake — VIBE Three-Stage Verification Protocol.
|
|
3
|
+
*
|
|
4
|
+
* Provides detection, tag parsing, and persistent state management for requests
|
|
5
|
+
* marked with the `[VIBE]` protocol trigger anywhere in the prompt text.
|
|
6
|
+
*
|
|
7
|
+
* When `[VIBE]` is detected, it locks the task into the Three-Stage
|
|
8
|
+
* Verification Protocol (Spec-Synthesizer -> Coder -> Auditor) and ensures
|
|
9
|
+
* the state is preserved across iterative refining and updates.
|
|
10
|
+
*/
|
|
11
|
+
export declare const VIBE_TAG_REGEX: RegExp;
|
|
12
|
+
export type VibeProtocolStage = 'synthesizer' | 'coder' | 'auditor' | 'passed';
|
|
13
|
+
export interface VibeProtocolState {
|
|
14
|
+
/** True whenever [VIBE] was present in the prompt or preserved across refinements. */
|
|
15
|
+
isVibeMode: boolean;
|
|
16
|
+
detectedAt: number;
|
|
17
|
+
/** Active stage in the verification protocol. */
|
|
18
|
+
stage: VibeProtocolStage;
|
|
19
|
+
/** Structured output from Spec-Synthesizer. */
|
|
20
|
+
synthesizedSpec?: string | undefined;
|
|
21
|
+
/** Generated implementation contract or code diff. */
|
|
22
|
+
coderContract?: string | undefined;
|
|
23
|
+
/** Independent verification decision from Auditor. */
|
|
24
|
+
auditVerdict?: 'PASS' | 'REJECT' | undefined;
|
|
25
|
+
/** Detailed audit check results or revision requests. */
|
|
26
|
+
auditNotes?: string[] | undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Checks if the given text contains the `[VIBE]` tag (case-insensitive)
|
|
30
|
+
* anywhere in the string.
|
|
31
|
+
*/
|
|
32
|
+
export declare function hasVibeTag(text: string | undefined | null): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Safely strips the `[VIBE]` tag from the text for normalized display,
|
|
35
|
+
* preserving all other content and trimming extraneous whitespace.
|
|
36
|
+
*/
|
|
37
|
+
export declare function stripVibeTag(text: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Derives or preserves Vibe Protocol state for an intake record.
|
|
40
|
+
* If the record was already in Vibe mode, it remains in Vibe mode across
|
|
41
|
+
* refinements even if subsequent messages omit the tag.
|
|
42
|
+
*/
|
|
43
|
+
export declare function deriveVibeState(rawText: string, existingState?: VibeProtocolState | undefined, now?: number): VibeProtocolState | undefined;
|
|
44
|
+
//# sourceMappingURL=vibe.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/requirement-intake",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.307.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "WrongStack Requirements Intake — collect, preserve, validate, normalize, and submit unstructured software development requests as structured intake records. Upstream of spec-driven development: it never plans, specifies, or implements.",
|
|
6
6
|
"repository": {
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"zod": "4.4.3",
|
|
31
|
-
"@wrongstack/core": "0.
|
|
31
|
+
"@wrongstack/core": "0.307.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^26.
|
|
34
|
+
"@types/node": "^26.2.0",
|
|
35
35
|
"typescript": "^7.0.2"
|
|
36
36
|
},
|
|
37
37
|
"publishConfig": {
|