@wrongstack/requirement-intake 1.0.19 → 1.0.26

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 CHANGED
@@ -17,6 +17,6 @@ export { type IntakeCreateResult, type IntakeListFilter, type IntakeSubmitResult
17
17
  export { type IntakeIndexEntry, newIntakeId, RequirementIntakeStore, type RequirementIntakeStoreOptions, type StoreCreateResult, type StoreUpdateOptions, } from './store.js';
18
18
  export { assertSuggestionString, type LlmSuggestionGenerator, type LlmSuggestionOutput, type LlmSuggestionRequest, llmSuggestionOutputSchema, type NormalizedLlmSuggestion, toProposals, validateLlmSuggestionOutput, } from './suggestions.js';
19
19
  export { type AddAnswerInput, type AttachResourceInput, type ChangeHistoryEntry, type CreateIntakeInput, INTAKE_EVENT_NAMES, type IntakeActor, type IntakeAnswer, type IntakeAttachment, type IntakeAttachmentInput, type IntakeContext, type IntakeEvent, type IntakeEventName, type IntakeQuestion, type IntakeQuestionTemplateInput, type LlmSuggestionProposal, type RelatedResource, type RelatedResourceInput, type RequirementIntakeRecord, type UpdateIntakeInput, } from './types.js';
20
- export { answerInputSchema, attachmentInputSchema, attachResourceInputSchema, createIntakeSchema, deterministicSummary, deterministicTitle, isBlank, metadataSchema, normalizeRequestType, parseWithIssues, prioritySchema, questionTemplateInputSchema, relatedResourceInputSchema, requestTypeSchema, updateIntakeSchema, validateAnswerInput, validateAttachmentInput, validateAttachResourceInput, validateCreateInput, validateFieldSource, validateQuestionTemplateInput, validateRelatedResourceInput, validateUpdateInput, } from './validation.js';
20
+ export { answerInputSchema, attachmentInputSchema, attachResourceInputSchema, createIntakeSchema, deterministicSummary, deterministicTitle, isBlank, metadataSchema, normalizeRequestType, parseWithIssues, prioritySchema, questionTemplateInputSchema, relatedResourceInputSchema, requestTypeSchema, updateIntakeSchema, validateAnswerInput, validateAttachmentInput, validateAttachResourceInput, validateCreateInput, validateFieldSource, validateQuestionTemplateInput, validateRelatedResourceInput, validateUpdateInput, vibeProtocolStateSchema, } from './validation.js';
21
21
  export { deriveVibeState, hasVibeTag, stripVibeTag, VIBE_TAG_REGEX, type VibeProtocolStage, type VibeProtocolState, } from './vibe.js';
22
22
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -413,6 +413,19 @@ var IDEMPOTENCY_PATH = "_idempotency.json";
413
413
  function hashIdempotencyKey(key) {
414
414
  return createHash("sha256").update(key, "utf8").digest("hex");
415
415
  }
416
+ function indexEntryFor(record) {
417
+ return {
418
+ id: record.id,
419
+ projectId: record.projectId,
420
+ title: record.title,
421
+ status: record.status,
422
+ requestType: record.requestType,
423
+ priority: record.priority,
424
+ requestedBy: record.requestedBy,
425
+ updatedAt: record.updatedAt,
426
+ createdAt: record.createdAt
427
+ };
428
+ }
416
429
  var RequirementIntakeStore = class {
417
430
  baseDir;
418
431
  indexPath;
@@ -430,6 +443,14 @@ var RequirementIntakeStore = class {
430
443
  return this.baseDir;
431
444
  }
432
445
  recordPath(id) {
446
+ if (!/^[A-Za-z0-9_-]{1,128}$/.test(id)) {
447
+ throw new IntakeValidationError([
448
+ {
449
+ field: "id",
450
+ message: "record id must contain only letters, numbers, underscores, or hyphens"
451
+ }
452
+ ]);
453
+ }
433
454
  return path.join(this.baseDir, `${id}.json`);
434
455
  }
435
456
  // -------------------------------------------------------------------------
@@ -558,26 +579,37 @@ var RequirementIntakeStore = class {
558
579
  const raw = await fsp.readFile(this.indexPath, "utf8");
559
580
  const parsed = JSON.parse(raw);
560
581
  if (parsed?.version === 1 && Array.isArray(parsed.entries)) return parsed;
561
- } catch {
582
+ } catch (error) {
583
+ if (error.code !== "ENOENT" && !(error instanceof SyntaxError)) {
584
+ throw error;
585
+ }
586
+ }
587
+ let files;
588
+ try {
589
+ files = await fsp.readdir(this.baseDir);
590
+ } catch (error) {
591
+ if (error.code === "ENOENT") return { version: 1, entries: [] };
592
+ throw error;
593
+ }
594
+ const entries = [];
595
+ for (const file of files) {
596
+ if (file === INDEX_PATH || file === IDEMPOTENCY_PATH || !file.endsWith(".json")) continue;
597
+ const id = file.slice(0, -".json".length);
598
+ if (!/^[A-Za-z0-9_-]{1,128}$/.test(id)) continue;
599
+ const record = JSON.parse(
600
+ await fsp.readFile(path.join(this.baseDir, file), "utf8")
601
+ );
602
+ if (record?.id !== id) throw new Error(`Invalid requirement intake record: ${file}`);
603
+ entries.push(indexEntryFor(record));
562
604
  }
563
- return { version: 1, entries: [] };
605
+ return { version: 1, entries };
564
606
  }
565
607
  async updateIndexFor(record) {
566
608
  return withFileLock(
567
609
  this.indexPath,
568
610
  async () => {
569
611
  const index = await this.readIndex();
570
- const entry = {
571
- id: record.id,
572
- projectId: record.projectId,
573
- title: record.title,
574
- status: record.status,
575
- requestType: record.requestType,
576
- priority: record.priority,
577
- requestedBy: record.requestedBy,
578
- updatedAt: record.updatedAt,
579
- createdAt: record.createdAt
580
- };
612
+ const entry = indexEntryFor(record);
581
613
  const position = index.entries.findIndex((candidate) => candidate.id === record.id);
582
614
  if (position >= 0) {
583
615
  index.entries[position] = entry;
@@ -683,6 +715,15 @@ var optionalString = (max) => z.string().max(max).optional();
683
715
  var optionalStringArray = z.array(z.string().trim().min(1).max(MAX_STRING_FIELD_LENGTH)).max(MAX_ARRAY_ITEMS).optional();
684
716
  var optionalAttachmentArray = z.array(attachmentInputSchema).max(MAX_ATTACHMENTS).optional();
685
717
  var optionalRelatedResourceArray = z.array(relatedResourceInputSchema).max(MAX_RELATED_RESOURCES).optional();
718
+ var vibeProtocolStateSchema = z.object({
719
+ isVibeMode: z.literal(true),
720
+ detectedAt: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
721
+ stage: z.enum(["synthesizer", "coder", "auditor", "passed"]),
722
+ synthesizedSpec: z.string().max(MAX_ANSWER_LENGTH).optional(),
723
+ coderContract: z.string().max(MAX_ANSWER_LENGTH).optional(),
724
+ auditVerdict: z.enum(["PASS", "REJECT"]).optional(),
725
+ auditNotes: z.array(z.string().max(MAX_STRING_FIELD_LENGTH)).max(MAX_ARRAY_ITEMS).optional()
726
+ }).strict();
686
727
  var createIntakeSchema = z.object({
687
728
  projectId: idSchema,
688
729
  originalRequest: nonBlankString(MAX_REQUEST_LENGTH, "originalRequest"),
@@ -703,7 +744,7 @@ var createIntakeSchema = z.object({
703
744
  knownFields: z.array(z.string().trim().min(1).max(64)).max(MAX_ARRAY_ITEMS).optional(),
704
745
  questions: z.array(questionTemplateInputSchema).max(50).optional(),
705
746
  isVibeMode: z.boolean().optional(),
706
- vibeProtocol: z.any().optional()
747
+ vibeProtocol: vibeProtocolStateSchema.optional()
707
748
  }).strict();
708
749
  var updateIntakeSchema = z.object({
709
750
  title: optionalString(MAX_TITLE_LENGTH),
@@ -717,7 +758,7 @@ var updateIntakeSchema = z.object({
717
758
  providedContext: optionalStringArray,
718
759
  metadata: metadataSchema.optional(),
719
760
  isVibeMode: z.boolean().optional(),
720
- vibeProtocol: z.any().optional()
761
+ vibeProtocol: vibeProtocolStateSchema.optional()
721
762
  }).strict();
722
763
  var answerInputSchema = z.object({
723
764
  field: nonBlankString(64, "answer field"),
@@ -1203,6 +1244,12 @@ function applyAnswerUpdateToRecord(record, answerId, newAnswer) {
1203
1244
  question.answer = newAnswer;
1204
1245
  question.status = "answered";
1205
1246
  }
1247
+ const mapping = ANSWER_FIELD_MAPPING[answer.field];
1248
+ if (mapping) {
1249
+ mapping.set(record, newAnswer);
1250
+ const sourceKey = answer.field;
1251
+ record.fieldSources[sourceKey] = "user";
1252
+ }
1206
1253
  }
1207
1254
  function applyAttachmentToRecord(record, validated, actorId, now) {
1208
1255
  if (record.attachments.length >= MAX_ATTACHMENTS) {
@@ -1305,6 +1352,23 @@ var RequirementIntakeService = class {
1305
1352
  const now = Date.now();
1306
1353
  const record = this.buildNewRecord(validated, ctx, now);
1307
1354
  const result = await this.store.create(record, validated.idempotencyKey?.trim());
1355
+ if (!result.created) {
1356
+ if (result.record.projectId !== ctx.projectId) {
1357
+ throw new IntakeValidationError([
1358
+ {
1359
+ field: "idempotencyKey",
1360
+ message: "idempotency key already used for a different project"
1361
+ }
1362
+ ]);
1363
+ }
1364
+ this.metrics.increment("intake.duplicate_create");
1365
+ this.logger.info("intake", "intake.duplicate_create", {
1366
+ intakeId: result.record.id,
1367
+ projectId: result.record.projectId,
1368
+ actorId: ctx.id
1369
+ });
1370
+ return result;
1371
+ }
1308
1372
  this.logger.info("intake", "intake.created", {
1309
1373
  intakeId: result.record.id,
1310
1374
  projectId: result.record.projectId,
@@ -1353,6 +1417,7 @@ var RequirementIntakeService = class {
1353
1417
  throw new IntakeValidationError([{ field: "title", message: "title must not be blank" }]);
1354
1418
  }
1355
1419
  return this.store.update(id, this.updateMeta(ctx, "updated", changedKeys, expectedVersion), (next) => {
1420
+ this.assertMutable(next, "updateIntake");
1356
1421
  if (validated.title !== void 0) {
1357
1422
  next.title = validated.title.trim();
1358
1423
  next.fieldSources.title = "user";
@@ -1390,6 +1455,7 @@ var RequirementIntakeService = class {
1390
1455
  id,
1391
1456
  this.updateMeta(ctx, "answer_added", [validated.field], expectedVersion),
1392
1457
  (next) => {
1458
+ this.assertMutable(next, "addAnswer");
1393
1459
  applyAnswerToRecord(next, validated, ctx.id, Date.now());
1394
1460
  }
1395
1461
  ).then((updated) => {
@@ -1407,6 +1473,7 @@ var RequirementIntakeService = class {
1407
1473
  })
1408
1474
  );
1409
1475
  return this.store.update(id, this.updateMeta(ctx, "answer_updated", [answerId], expectedVersion), (next) => {
1476
+ this.assertMutable(next, "updateAnswer");
1410
1477
  applyAnswerUpdateToRecord(next, answerId, validated.answer);
1411
1478
  }).then((updated) => {
1412
1479
  this.afterMutation(updated, ctx, "RequirementIntakeUpdated");
@@ -1423,6 +1490,7 @@ var RequirementIntakeService = class {
1423
1490
  id,
1424
1491
  this.updateMeta(ctx, "attachment_added", ["attachments"], expectedVersion),
1425
1492
  (next) => {
1493
+ this.assertMutable(next, "attachResource");
1426
1494
  applyAttachmentToRecord(next, validated, ctx.id, now);
1427
1495
  }
1428
1496
  ).then((updated) => {
@@ -1434,6 +1502,7 @@ var RequirementIntakeService = class {
1434
1502
  id,
1435
1503
  this.updateMeta(ctx, "related_resource_added", ["related_resources"], expectedVersion),
1436
1504
  (next) => {
1505
+ this.assertMutable(next, "attachResource");
1437
1506
  applyRelatedResourceToRecord(next, validated, ctx.id, now);
1438
1507
  }
1439
1508
  ).then((updated) => {
@@ -1489,6 +1558,7 @@ var RequirementIntakeService = class {
1489
1558
  to: previousStatus === nextStatus ? void 0 : nextStatus
1490
1559
  },
1491
1560
  (next) => {
1561
+ this.assertMutable(next, "generateSuggestions");
1492
1562
  next.llmSuggestions.push(...proposals);
1493
1563
  if (next.llmSuggestions.length > MAX_SUGGESTIONS) {
1494
1564
  next.llmSuggestions = next.llmSuggestions.slice(
@@ -1530,6 +1600,7 @@ var RequirementIntakeService = class {
1530
1600
  id,
1531
1601
  this.updateMeta(ctx, "suggestion_accepted", [proposal2.kind], expectedVersion),
1532
1602
  (next) => {
1603
+ this.assertMutable(next, "acceptSuggestion");
1533
1604
  const target = next.llmSuggestions.find((candidate) => candidate.id === proposalId);
1534
1605
  if (!target) {
1535
1606
  throw new IntakeValidationError([
@@ -1553,6 +1624,7 @@ var RequirementIntakeService = class {
1553
1624
  id,
1554
1625
  this.updateMeta(ctx, "suggestion_rejected", [proposalId], expectedVersion),
1555
1626
  (next) => {
1627
+ this.assertMutable(next, "rejectSuggestion");
1556
1628
  const target = next.llmSuggestions.find((candidate) => candidate.id === proposalId);
1557
1629
  if (!target) {
1558
1630
  throw new IntakeValidationError([
@@ -1884,6 +1956,7 @@ export {
1884
1956
  validateLlmSuggestionOutput,
1885
1957
  validateQuestionTemplateInput,
1886
1958
  validateRelatedResourceInput,
1887
- validateUpdateInput
1959
+ validateUpdateInput,
1960
+ vibeProtocolStateSchema
1888
1961
  };
1889
1962
  //# sourceMappingURL=index.js.map
@@ -75,6 +75,24 @@ export declare const questionTemplateInputSchema: z.ZodObject<{
75
75
  required: z.ZodOptional<z.ZodBoolean>;
76
76
  }, z.core.$strip>;
77
77
  export declare const metadataSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
78
+ /** Strict, bounded representation of resumable VIBE protocol state. */
79
+ export declare const vibeProtocolStateSchema: z.ZodObject<{
80
+ isVibeMode: z.ZodLiteral<true>;
81
+ detectedAt: z.ZodNumber;
82
+ stage: z.ZodEnum<{
83
+ auditor: "auditor";
84
+ coder: "coder";
85
+ passed: "passed";
86
+ synthesizer: "synthesizer";
87
+ }>;
88
+ synthesizedSpec: z.ZodOptional<z.ZodString>;
89
+ coderContract: z.ZodOptional<z.ZodString>;
90
+ auditVerdict: z.ZodOptional<z.ZodEnum<{
91
+ PASS: "PASS";
92
+ REJECT: "REJECT";
93
+ }>>;
94
+ auditNotes: z.ZodOptional<z.ZodArray<z.ZodString>>;
95
+ }, z.core.$strict>;
78
96
  export declare const createIntakeSchema: z.ZodObject<{
79
97
  projectId: z.ZodString;
80
98
  originalRequest: z.ZodString;
@@ -129,7 +147,23 @@ export declare const createIntakeSchema: z.ZodObject<{
129
147
  required: z.ZodOptional<z.ZodBoolean>;
130
148
  }, z.core.$strip>>>;
131
149
  isVibeMode: z.ZodOptional<z.ZodBoolean>;
132
- vibeProtocol: z.ZodOptional<z.ZodAny>;
150
+ vibeProtocol: z.ZodOptional<z.ZodObject<{
151
+ isVibeMode: z.ZodLiteral<true>;
152
+ detectedAt: z.ZodNumber;
153
+ stage: z.ZodEnum<{
154
+ auditor: "auditor";
155
+ coder: "coder";
156
+ passed: "passed";
157
+ synthesizer: "synthesizer";
158
+ }>;
159
+ synthesizedSpec: z.ZodOptional<z.ZodString>;
160
+ coderContract: z.ZodOptional<z.ZodString>;
161
+ auditVerdict: z.ZodOptional<z.ZodEnum<{
162
+ PASS: "PASS";
163
+ REJECT: "REJECT";
164
+ }>>;
165
+ auditNotes: z.ZodOptional<z.ZodArray<z.ZodString>>;
166
+ }, z.core.$strict>>;
133
167
  }, z.core.$strict>;
134
168
  export declare const updateIntakeSchema: z.ZodObject<{
135
169
  title: z.ZodOptional<z.ZodString>;
@@ -149,7 +183,23 @@ export declare const updateIntakeSchema: z.ZodObject<{
149
183
  providedContext: z.ZodOptional<z.ZodArray<z.ZodString>>;
150
184
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
151
185
  isVibeMode: z.ZodOptional<z.ZodBoolean>;
152
- vibeProtocol: z.ZodOptional<z.ZodAny>;
186
+ vibeProtocol: z.ZodOptional<z.ZodObject<{
187
+ isVibeMode: z.ZodLiteral<true>;
188
+ detectedAt: z.ZodNumber;
189
+ stage: z.ZodEnum<{
190
+ auditor: "auditor";
191
+ coder: "coder";
192
+ passed: "passed";
193
+ synthesizer: "synthesizer";
194
+ }>;
195
+ synthesizedSpec: z.ZodOptional<z.ZodString>;
196
+ coderContract: z.ZodOptional<z.ZodString>;
197
+ auditVerdict: z.ZodOptional<z.ZodEnum<{
198
+ PASS: "PASS";
199
+ REJECT: "REJECT";
200
+ }>>;
201
+ auditNotes: z.ZodOptional<z.ZodArray<z.ZodString>>;
202
+ }, z.core.$strict>>;
153
203
  }, z.core.$strict>;
154
204
  export declare const answerInputSchema: z.ZodObject<{
155
205
  field: z.ZodString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/requirement-intake",
3
- "version": "1.0.19",
3
+ "version": "1.0.26",
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": {
@@ -27,7 +27,7 @@
27
27
  "!dist/**/*.map"
28
28
  ],
29
29
  "dependencies": {
30
- "@wrongstack/core": "1.0.19",
30
+ "@wrongstack/core": "1.0.26",
31
31
  "zod": "4.6.2"
32
32
  },
33
33
  "devDependencies": {