@wrongstack/requirement-intake 0.299.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/LICENSE +21 -0
- package/README.md +237 -0
- package/dist/authorization.d.ts +48 -0
- package/dist/constants.d.ts +84 -0
- package/dist/errors.d.ts +43 -0
- package/dist/events.d.ts +18 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1805 -0
- package/dist/lifecycle.d.ts +21 -0
- package/dist/logger.d.ts +34 -0
- package/dist/metrics.d.ts +30 -0
- package/dist/questions.d.ts +13 -0
- package/dist/service.d.ts +72 -0
- package/dist/store.d.ts +81 -0
- package/dist/suggestions.d.ts +71 -0
- package/dist/types.d.ts +223 -0
- package/dist/validation.d.ts +209 -0
- package/package.json +46 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirements Intake — deterministic lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Status transitions are enforced by application logic. The LLM never
|
|
5
|
+
* changes status directly; it may only *suggest* information that moves a
|
|
6
|
+
* draft into `collecting_information` through the service.
|
|
7
|
+
*/
|
|
8
|
+
import { type IntakeStatus } from './constants.js';
|
|
9
|
+
export declare const ALLOWED_TRANSITIONS: Readonly<Record<IntakeStatus, readonly IntakeStatus[]>>;
|
|
10
|
+
/**
|
|
11
|
+
* Statuses whose content may still be edited. Submitted, cancelled, and
|
|
12
|
+
* archived records are locked (except for lifecycle operations).
|
|
13
|
+
*/
|
|
14
|
+
export declare const MUTABLE_STATUSES: readonly IntakeStatus[];
|
|
15
|
+
export declare function canTransition(from: IntakeStatus, to: IntakeStatus): boolean;
|
|
16
|
+
/** Throw `IntakeStateTransitionError` when the transition is not allowed. */
|
|
17
|
+
export declare function assertTransition(from: IntakeStatus, to: IntakeStatus): void;
|
|
18
|
+
export declare function isMutableStatus(status: IntakeStatus): boolean;
|
|
19
|
+
export declare function isTerminalStatus(status: IntakeStatus): boolean;
|
|
20
|
+
export declare function isKnownStatus(value: string): value is IntakeStatus;
|
|
21
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirements Intake — structured logging.
|
|
3
|
+
*
|
|
4
|
+
* The service logs identifiers and safe metadata only. Request content,
|
|
5
|
+
* answers, and attachments are NEVER passed to the logger, so no secret or
|
|
6
|
+
* sensitive requirement content can leak into logs.
|
|
7
|
+
*/
|
|
8
|
+
export interface IntakeLogFields {
|
|
9
|
+
[key: string]: string | number | boolean | undefined;
|
|
10
|
+
}
|
|
11
|
+
export interface IntakeLogger {
|
|
12
|
+
info(scope: string, message: string, fields?: IntakeLogFields): void;
|
|
13
|
+
warn(scope: string, message: string, fields?: IntakeLogFields): void;
|
|
14
|
+
error(scope: string, message: string, fields?: IntakeLogFields): void;
|
|
15
|
+
}
|
|
16
|
+
/** Default logger — silent. Hosts may wire their own structured logger. */
|
|
17
|
+
export declare class NoopIntakeLogger implements IntakeLogger {
|
|
18
|
+
info(_scope: string, _message: string, _fields?: IntakeLogFields): void;
|
|
19
|
+
warn(_scope: string, _message: string, _fields?: IntakeLogFields): void;
|
|
20
|
+
error(_scope: string, _message: string, _fields?: IntakeLogFields): void;
|
|
21
|
+
}
|
|
22
|
+
/** In-memory logger for tests. */
|
|
23
|
+
export declare class InMemoryIntakeLogger implements IntakeLogger {
|
|
24
|
+
readonly entries: Array<{
|
|
25
|
+
level: 'info' | 'warn' | 'error';
|
|
26
|
+
scope: string;
|
|
27
|
+
message: string;
|
|
28
|
+
fields?: IntakeLogFields | undefined;
|
|
29
|
+
}>;
|
|
30
|
+
info(scope: string, message: string, fields?: IntakeLogFields): void;
|
|
31
|
+
warn(scope: string, message: string, fields?: IntakeLogFields): void;
|
|
32
|
+
error(scope: string, message: string, fields?: IntakeLogFields): void;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirements Intake — metrics.
|
|
3
|
+
*
|
|
4
|
+
* Lightweight counters/timers for the observability surface. The service
|
|
5
|
+
* never records request content; only identifiers and outcome counts.
|
|
6
|
+
*/
|
|
7
|
+
export declare const INTAKE_COUNTERS: readonly ['intake.created', 'intake.submitted', 'intake.cancelled', 'intake.archived', 'intake.duplicate_create', 'intake.duplicate_submit', 'intake.validation_failure', 'intake.suggestions.requested', 'intake.suggestions.succeeded', 'intake.suggestions.failed', 'intake.unauthorized_attempt'];
|
|
8
|
+
export type IntakeCounter = (typeof INTAKE_COUNTERS)[number];
|
|
9
|
+
export declare const INTAKE_TIMERS: readonly ['intake.time_to_submit'];
|
|
10
|
+
export type IntakeTimer = (typeof INTAKE_TIMERS)[number];
|
|
11
|
+
export interface IntakeMetrics {
|
|
12
|
+
increment(counter: IntakeCounter, by?: number, labels?: Record<string, string>): void;
|
|
13
|
+
recordDuration(timer: IntakeTimer, milliseconds: number): void;
|
|
14
|
+
}
|
|
15
|
+
/** Default metrics — silent. */
|
|
16
|
+
export declare class NoopIntakeMetrics implements IntakeMetrics {
|
|
17
|
+
increment(_counter: IntakeCounter, _by?: number, _labels?: Record<string, string>): void;
|
|
18
|
+
recordDuration(_timer: IntakeTimer, _milliseconds: number): void;
|
|
19
|
+
}
|
|
20
|
+
/** In-memory metrics for tests and lightweight hosts. */
|
|
21
|
+
export declare class InMemoryIntakeMetrics implements IntakeMetrics {
|
|
22
|
+
readonly counters: Map<string, number>;
|
|
23
|
+
readonly durations: Map<string, number[]>;
|
|
24
|
+
increment(counter: IntakeCounter, by?: number, _labels?: Record<string, string>): void;
|
|
25
|
+
recordDuration(timer: IntakeTimer, milliseconds: number): void;
|
|
26
|
+
count(counter: IntakeCounter): number;
|
|
27
|
+
/** Sum of recorded durations for a timer, or undefined when none recorded. */
|
|
28
|
+
durationSum(timer: IntakeTimer): number | undefined;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type IntakeQuestionTemplate } from './constants.js';
|
|
2
|
+
import type { CreateIntakeInput, IntakeQuestion, RequirementIntakeRecord } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build the initial question set for a new record. Skipped questions are
|
|
5
|
+
* retained with status `skipped` so the record documents why they were never
|
|
6
|
+
* asked; `pendingQuestions()` only surfaces unanswered ones.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildInitialQuestions(input: CreateIntakeInput, catalog?: readonly IntakeQuestionTemplate[]): IntakeQuestion[];
|
|
9
|
+
/** Questions still awaiting an answer on a record. */
|
|
10
|
+
export declare function pendingQuestions(record: RequirementIntakeRecord): IntakeQuestion[];
|
|
11
|
+
/** Merge a question template into a record's question list (no duplicates). */
|
|
12
|
+
export declare function upsertQuestion(record: RequirementIntakeRecord, template: IntakeQuestionTemplate): boolean;
|
|
13
|
+
//# sourceMappingURL=questions.d.ts.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type IntakeQuestionTemplate, type IntakeStatus } from './constants.js';
|
|
2
|
+
import type { IntakeAuthorizer } from './authorization.js';
|
|
3
|
+
import { IntakeEventEmitter } from './events.js';
|
|
4
|
+
import { type IntakeLogger } from './logger.js';
|
|
5
|
+
import { type IntakeMetrics } from './metrics.js';
|
|
6
|
+
import { type LlmSuggestionGenerator } from './suggestions.js';
|
|
7
|
+
import { type RequirementIntakeStore } from './store.js';
|
|
8
|
+
import type { AddAnswerInput, AttachResourceInput, CreateIntakeInput, IntakeContext, IntakeEvent, IntakeQuestion, LlmSuggestionProposal, RequirementIntakeRecord, UpdateIntakeInput } from './types.js';
|
|
9
|
+
export interface RequirementIntakeServiceOptions {
|
|
10
|
+
store: RequirementIntakeStore;
|
|
11
|
+
authorizer: IntakeAuthorizer;
|
|
12
|
+
/** Optional LLM adapter. `generateSuggestions` fails until one is wired. */
|
|
13
|
+
generator?: LlmSuggestionGenerator | undefined;
|
|
14
|
+
emitter?: IntakeEventEmitter | undefined;
|
|
15
|
+
logger?: IntakeLogger | undefined;
|
|
16
|
+
metrics?: IntakeMetrics | undefined;
|
|
17
|
+
/** Default question catalog override. */
|
|
18
|
+
questions?: readonly IntakeQuestionTemplate[] | undefined;
|
|
19
|
+
}
|
|
20
|
+
export interface IntakeCreateResult {
|
|
21
|
+
record: RequirementIntakeRecord;
|
|
22
|
+
created: boolean;
|
|
23
|
+
idempotent: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface IntakeSubmitResult {
|
|
26
|
+
record: RequirementIntakeRecord;
|
|
27
|
+
idempotent: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface IntakeListFilter {
|
|
30
|
+
statuses?: readonly IntakeStatus[] | undefined;
|
|
31
|
+
}
|
|
32
|
+
export declare class RequirementIntakeService {
|
|
33
|
+
private readonly store;
|
|
34
|
+
private readonly authorizer;
|
|
35
|
+
private readonly generator;
|
|
36
|
+
private readonly emitter;
|
|
37
|
+
private readonly logger;
|
|
38
|
+
private readonly metrics;
|
|
39
|
+
private readonly catalog;
|
|
40
|
+
constructor(options: RequirementIntakeServiceOptions);
|
|
41
|
+
/** Subscribe to domain events. Returns a disposer. */
|
|
42
|
+
subscribe(listener: (event: IntakeEvent) => void): () => void;
|
|
43
|
+
createIntake(input: CreateIntakeInput, ctx: IntakeContext): Promise<IntakeCreateResult>;
|
|
44
|
+
getIntake(id: string, ctx: IntakeContext): Promise<RequirementIntakeRecord | null>;
|
|
45
|
+
listIntakes(projectId: string, ctx: IntakeContext, filter?: IntakeListFilter | undefined): Promise<RequirementIntakeRecord[]>;
|
|
46
|
+
pendingQuestions(id: string, ctx: IntakeContext): Promise<IntakeQuestion[]>;
|
|
47
|
+
updateIntake(id: string, patch: UpdateIntakeInput, ctx: IntakeContext, expectedVersion?: number | undefined): Promise<RequirementIntakeRecord>;
|
|
48
|
+
addAnswer(id: string, input: AddAnswerInput, ctx: IntakeContext, expectedVersion?: number | undefined): Promise<RequirementIntakeRecord>;
|
|
49
|
+
updateAnswer(id: string, answerId: string, patch: {
|
|
50
|
+
answer: string;
|
|
51
|
+
}, ctx: IntakeContext, expectedVersion?: number | undefined): Promise<RequirementIntakeRecord>;
|
|
52
|
+
attachResource(id: string, input: AttachResourceInput, ctx: IntakeContext, expectedVersion?: number | undefined): Promise<RequirementIntakeRecord>;
|
|
53
|
+
generateSuggestions(id: string, ctx: IntakeContext, focus?: string[] | undefined): Promise<LlmSuggestionProposal[]>;
|
|
54
|
+
acceptSuggestion(id: string, proposalId: string, ctx: IntakeContext, expectedVersion?: number | undefined): Promise<RequirementIntakeRecord>;
|
|
55
|
+
rejectSuggestion(id: string, proposalId: string, ctx: IntakeContext, expectedVersion?: number | undefined): Promise<RequirementIntakeRecord>;
|
|
56
|
+
submitIntake(id: string, ctx: IntakeContext, expectedVersion?: number | undefined): Promise<IntakeSubmitResult>;
|
|
57
|
+
cancelIntake(id: string, ctx: IntakeContext, reason?: string | undefined, expectedVersion?: number | undefined): Promise<RequirementIntakeRecord>;
|
|
58
|
+
archiveIntake(id: string, ctx: IntakeContext, expectedVersion?: number | undefined): Promise<RequirementIntakeRecord>;
|
|
59
|
+
private buildNewRecord;
|
|
60
|
+
private requireRecord;
|
|
61
|
+
private authorize;
|
|
62
|
+
private assertMutable;
|
|
63
|
+
private assertAnswerField;
|
|
64
|
+
private assertSubmitReady;
|
|
65
|
+
private findSuggestion;
|
|
66
|
+
private applyProposal;
|
|
67
|
+
private updateMeta;
|
|
68
|
+
private afterMutation;
|
|
69
|
+
private emit;
|
|
70
|
+
private guardValidation;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=service.d.ts.map
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type IntakeStatus } from './constants.js';
|
|
2
|
+
import type { RequirementIntakeRecord } from './types.js';
|
|
3
|
+
export interface IntakeIndexEntry {
|
|
4
|
+
id: string;
|
|
5
|
+
projectId: string;
|
|
6
|
+
title: string;
|
|
7
|
+
status: IntakeStatus;
|
|
8
|
+
requestType: RequirementIntakeRecord['requestType'];
|
|
9
|
+
priority: RequirementIntakeRecord['priority'];
|
|
10
|
+
requestedBy: string;
|
|
11
|
+
updatedAt: number;
|
|
12
|
+
createdAt: number;
|
|
13
|
+
}
|
|
14
|
+
export interface RequirementIntakeStoreOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Directory where intake records are stored. Defaults to
|
|
17
|
+
* `resolveWstackPaths({ projectRoot: process.cwd() }).projectRequirementIntakes`
|
|
18
|
+
* (`~/.wrongstack/projects/<slug>/requirement-intakes`).
|
|
19
|
+
*/
|
|
20
|
+
baseDir?: string | undefined;
|
|
21
|
+
/** Cap on retained idempotency-key entries; oldest are pruned. Default 10_000. */
|
|
22
|
+
maxIdempotencyEntries?: number | undefined;
|
|
23
|
+
/** Timeout for acquiring the per-record write lock, in ms. Default 15_000. */
|
|
24
|
+
lockTimeoutMs?: number | undefined;
|
|
25
|
+
}
|
|
26
|
+
export interface StoreUpdateOptions {
|
|
27
|
+
/** Enforce optimistic concurrency. Undefined = unconditional write. */
|
|
28
|
+
expectedVersion?: number | undefined;
|
|
29
|
+
actorId?: string | undefined;
|
|
30
|
+
actorType?: string | undefined;
|
|
31
|
+
action?: string | undefined;
|
|
32
|
+
fields?: string[] | undefined;
|
|
33
|
+
from?: IntakeStatus | undefined;
|
|
34
|
+
to?: IntakeStatus | undefined;
|
|
35
|
+
}
|
|
36
|
+
export interface StoreCreateResult {
|
|
37
|
+
record: RequirementIntakeRecord;
|
|
38
|
+
created: boolean;
|
|
39
|
+
idempotent: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare class RequirementIntakeStore {
|
|
42
|
+
private readonly baseDir;
|
|
43
|
+
private readonly indexPath;
|
|
44
|
+
private readonly idempotencyPath;
|
|
45
|
+
private readonly maxIdempotencyEntries;
|
|
46
|
+
private readonly lockTimeoutMs;
|
|
47
|
+
constructor(options: RequirementIntakeStoreOptions);
|
|
48
|
+
get directory(): string;
|
|
49
|
+
private recordPath;
|
|
50
|
+
load(id: string): Promise<RequirementIntakeRecord | null>;
|
|
51
|
+
exists(id: string): Promise<boolean>;
|
|
52
|
+
/** List full records for a project, newest-updated first. */
|
|
53
|
+
list(projectId?: string | undefined, filter?: {
|
|
54
|
+
statuses?: readonly IntakeStatus[] | undefined;
|
|
55
|
+
} | undefined): Promise<RequirementIntakeRecord[]>;
|
|
56
|
+
/** Cheap listing via the index — no record file reads. */
|
|
57
|
+
listIndex(projectId?: string | undefined, filter?: {
|
|
58
|
+
statuses?: readonly IntakeStatus[] | undefined;
|
|
59
|
+
} | undefined): Promise<IntakeIndexEntry[]>;
|
|
60
|
+
findByIdempotencyKey(key: string): Promise<RequirementIntakeRecord | null>;
|
|
61
|
+
/**
|
|
62
|
+
* Persist a new record. When `idempotencyKey` is given, a second create
|
|
63
|
+
* with the same key returns the existing record instead of duplicating it.
|
|
64
|
+
*/
|
|
65
|
+
create(record: RequirementIntakeRecord, idempotencyKey?: string): Promise<StoreCreateResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Read-modify-write with optimistic concurrency. `mutate` receives a
|
|
68
|
+
* mutable copy; the store bumps `version`, refreshes `updatedAt`, appends
|
|
69
|
+
* the history entry, and persists atomically under the record lock.
|
|
70
|
+
*/
|
|
71
|
+
update(id: string, options: StoreUpdateOptions, mutate: (record: RequirementIntakeRecord) => void | Promise<void>): Promise<RequirementIntakeRecord>;
|
|
72
|
+
private writeRecord;
|
|
73
|
+
private appendHistory;
|
|
74
|
+
private readIndex;
|
|
75
|
+
private updateIndexFor;
|
|
76
|
+
private readIdempotency;
|
|
77
|
+
private pruneIdempotency;
|
|
78
|
+
}
|
|
79
|
+
/** Generate a store-compatible record id (`reqi_<ulid>`). */
|
|
80
|
+
export declare function newIntakeId(): string;
|
|
81
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirements Intake — LLM suggestion pipeline.
|
|
3
|
+
*
|
|
4
|
+
* LLM output is always treated as a *proposal*:
|
|
5
|
+
* 1. returned in structured form,
|
|
6
|
+
* 2. validated against a zod schema (malformed output is rejected),
|
|
7
|
+
* 3. stored separately with `source: 'llm'`,
|
|
8
|
+
* 4. never allowed to overwrite the original request,
|
|
9
|
+
* 5. accept/reject is an explicit user decision,
|
|
10
|
+
* 6. never controls persistence, authorization, or workflow state directly.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { type IntakePriority, type RequestType } from './constants.js';
|
|
14
|
+
import type { IntakeQuestionTemplateInput, LlmSuggestionProposal, RequirementIntakeRecord } from './types.js';
|
|
15
|
+
/** What the module hands to the host's LLM adapter. */
|
|
16
|
+
export interface LlmSuggestionRequest {
|
|
17
|
+
record: RequirementIntakeRecord;
|
|
18
|
+
/** Optional focus hint, e.g. ['title', 'questions']. */
|
|
19
|
+
focus?: string[] | undefined;
|
|
20
|
+
}
|
|
21
|
+
/** Structured output contract for the LLM adapter. */
|
|
22
|
+
export interface LlmSuggestionOutput {
|
|
23
|
+
suggested_title?: string | undefined;
|
|
24
|
+
normalized_summary?: string | undefined;
|
|
25
|
+
suggested_request_type?: string | undefined;
|
|
26
|
+
suggested_priority?: string | undefined;
|
|
27
|
+
extracted_constraints?: string[] | undefined;
|
|
28
|
+
extracted_target_users?: string[] | undefined;
|
|
29
|
+
suggested_outcome?: string | undefined;
|
|
30
|
+
suggested_questions?: IntakeQuestionTemplateInput[] | undefined;
|
|
31
|
+
}
|
|
32
|
+
/** Host-provided LLM adapter. Must return structured output. */
|
|
33
|
+
export interface LlmSuggestionGenerator {
|
|
34
|
+
generate(request: LlmSuggestionRequest): Promise<LlmSuggestionOutput>;
|
|
35
|
+
}
|
|
36
|
+
/** Schema for raw (untrusted) LLM output. */
|
|
37
|
+
export declare const llmSuggestionOutputSchema: z.ZodObject<{
|
|
38
|
+
suggested_title: z.ZodOptional<z.ZodString>;
|
|
39
|
+
normalized_summary: z.ZodOptional<z.ZodString>;
|
|
40
|
+
suggested_request_type: z.ZodOptional<z.ZodString>;
|
|
41
|
+
suggested_priority: z.ZodOptional<z.ZodString>;
|
|
42
|
+
extracted_constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
43
|
+
extracted_target_users: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
44
|
+
suggested_outcome: z.ZodOptional<z.ZodString>;
|
|
45
|
+
suggested_questions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
46
|
+
field: z.ZodString;
|
|
47
|
+
question: z.ZodString;
|
|
48
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
49
|
+
}, z.core.$strip>>>;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
/** Validated + normalized LLM output ready for proposal conversion. */
|
|
52
|
+
export interface NormalizedLlmSuggestion {
|
|
53
|
+
suggestedTitle?: string | undefined;
|
|
54
|
+
normalizedSummary?: string | undefined;
|
|
55
|
+
suggestedRequestType?: RequestType | undefined;
|
|
56
|
+
suggestedPriority?: IntakePriority | undefined;
|
|
57
|
+
extractedConstraints: string[];
|
|
58
|
+
extractedTargetUsers: string[];
|
|
59
|
+
suggestedOutcome?: string | undefined;
|
|
60
|
+
suggestedQuestions: IntakeQuestionTemplateInput[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Validate raw LLM output. Throws `IntakeSuggestionError` when the shape is
|
|
64
|
+
* malformed; unknown request-type/priority values are normalized, not kept.
|
|
65
|
+
*/
|
|
66
|
+
export declare function validateLlmSuggestionOutput(raw: unknown): NormalizedLlmSuggestion;
|
|
67
|
+
/** Convert validated LLM output into pending proposals. */
|
|
68
|
+
export declare function toProposals(suggestion: NormalizedLlmSuggestion): LlmSuggestionProposal[];
|
|
69
|
+
/** Ensure a raw value is a non-blank string within a length bound. */
|
|
70
|
+
export declare function assertSuggestionString(value: unknown, label: string, max: number): string;
|
|
71
|
+
//# sourceMappingURL=suggestions.d.ts.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirements Intake — domain types.
|
|
3
|
+
*
|
|
4
|
+
* The record model preserves the exact original request verbatim
|
|
5
|
+
* (`originalRequest`) and keeps every derived, normalized, or machine-
|
|
6
|
+
* generated value separate, source-annotated, and user-editable.
|
|
7
|
+
*/
|
|
8
|
+
import type { IntakeAttachmentKind, IntakeField, IntakeFieldSource, IntakePriority, IntakeQuestionStatus, IntakeStatus, RelatedResourceKind, RequestType, SuggestionKind, SuggestionStatus } from './constants.js';
|
|
9
|
+
/** Actor identity used for authorization and audit. */
|
|
10
|
+
export interface IntakeActor {
|
|
11
|
+
/** Stable actor id (user id, agent id, or automation id). */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Actor class. Automation/LLM identities should get minimal permissions. */
|
|
14
|
+
type: 'user' | 'agent' | 'system' | 'automation';
|
|
15
|
+
}
|
|
16
|
+
/** Per-operation security context. `projectId` is the authorization scope. */
|
|
17
|
+
export interface IntakeContext extends IntakeActor {
|
|
18
|
+
/** The project the operation targets. Cross-project access must be denied. */
|
|
19
|
+
projectId: string;
|
|
20
|
+
}
|
|
21
|
+
/** A supporting file/document/link attached to an intake record. */
|
|
22
|
+
export interface IntakeAttachment {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
kind: IntakeAttachmentKind;
|
|
26
|
+
/** Host-resolved path for file-like attachments. */
|
|
27
|
+
path?: string | undefined;
|
|
28
|
+
/** http(s) URL for link-kind attachments. */
|
|
29
|
+
url?: string | undefined;
|
|
30
|
+
sizeBytes?: number | undefined;
|
|
31
|
+
mimeType?: string | undefined;
|
|
32
|
+
source: IntakeFieldSource;
|
|
33
|
+
addedBy?: string | undefined;
|
|
34
|
+
addedAt: number;
|
|
35
|
+
}
|
|
36
|
+
/** A reference to an existing resource (spec, issue, PR, doc, URL, …). */
|
|
37
|
+
export interface RelatedResource {
|
|
38
|
+
id: string;
|
|
39
|
+
kind: RelatedResourceKind;
|
|
40
|
+
/** Resource id, URL, or path. */
|
|
41
|
+
reference: string;
|
|
42
|
+
title?: string | undefined;
|
|
43
|
+
source: IntakeFieldSource;
|
|
44
|
+
addedBy?: string | undefined;
|
|
45
|
+
addedAt: number;
|
|
46
|
+
}
|
|
47
|
+
/** A structured question/answer pair collected during intake. */
|
|
48
|
+
export interface IntakeAnswer {
|
|
49
|
+
id: string;
|
|
50
|
+
/** The intake field this answer fills (see constants.INTAKE_FIELDS). */
|
|
51
|
+
field: string;
|
|
52
|
+
/** Snapshot of the question text at ask time. */
|
|
53
|
+
question: string;
|
|
54
|
+
answer: string;
|
|
55
|
+
source: IntakeFieldSource;
|
|
56
|
+
answeredBy?: string | undefined;
|
|
57
|
+
answeredAt: number;
|
|
58
|
+
}
|
|
59
|
+
/** A question asked (or offered) during intake. */
|
|
60
|
+
export interface IntakeQuestion {
|
|
61
|
+
id: string;
|
|
62
|
+
field: string;
|
|
63
|
+
question: string;
|
|
64
|
+
answer?: string | undefined;
|
|
65
|
+
required: boolean;
|
|
66
|
+
status: IntakeQuestionStatus;
|
|
67
|
+
/** Catalog order — stable across re-derivations. */
|
|
68
|
+
order: number;
|
|
69
|
+
}
|
|
70
|
+
/** One append-only entry of the record's change history. */
|
|
71
|
+
export interface ChangeHistoryEntry {
|
|
72
|
+
at: number;
|
|
73
|
+
actor?: string | undefined;
|
|
74
|
+
actorType?: string | undefined;
|
|
75
|
+
action: string;
|
|
76
|
+
/** Fields whose values changed in this entry. */
|
|
77
|
+
fields?: string[] | undefined;
|
|
78
|
+
from?: IntakeStatus | undefined;
|
|
79
|
+
to?: IntakeStatus | undefined;
|
|
80
|
+
}
|
|
81
|
+
/** An LLM-generated proposal. Never authoritative until accepted by a user. */
|
|
82
|
+
export interface LlmSuggestionProposal {
|
|
83
|
+
id: string;
|
|
84
|
+
kind: SuggestionKind;
|
|
85
|
+
/** Target record field for field-backed kinds (title, request_type, …). */
|
|
86
|
+
field?: string | undefined;
|
|
87
|
+
value: unknown;
|
|
88
|
+
rationale?: string | undefined;
|
|
89
|
+
status: SuggestionStatus;
|
|
90
|
+
createdAt: number;
|
|
91
|
+
resolvedAt?: number | undefined;
|
|
92
|
+
}
|
|
93
|
+
/** The persistent requirement intake record. */
|
|
94
|
+
export interface RequirementIntakeRecord {
|
|
95
|
+
/** `reqi_<ulid>` — stable, generated by the module. */
|
|
96
|
+
id: string;
|
|
97
|
+
/** `proj_<ulid>` project identifier (see core project-identity). */
|
|
98
|
+
projectId: string;
|
|
99
|
+
title: string;
|
|
100
|
+
/** Exact original user request. Immutable after creation. */
|
|
101
|
+
originalRequest: string;
|
|
102
|
+
normalizedSummary: string;
|
|
103
|
+
requestType: RequestType;
|
|
104
|
+
status: IntakeStatus;
|
|
105
|
+
priority: IntakePriority;
|
|
106
|
+
requestedBy: string;
|
|
107
|
+
businessGoal?: string | undefined;
|
|
108
|
+
targetUsers: string[];
|
|
109
|
+
expectedOutcome?: string | undefined;
|
|
110
|
+
scopeNotes?: string | undefined;
|
|
111
|
+
constraints: string[];
|
|
112
|
+
providedContext: string[];
|
|
113
|
+
attachments: IntakeAttachment[];
|
|
114
|
+
relatedResources: RelatedResource[];
|
|
115
|
+
answers: IntakeAnswer[];
|
|
116
|
+
questions: IntakeQuestion[];
|
|
117
|
+
llmSuggestions: LlmSuggestionProposal[];
|
|
118
|
+
metadata: Record<string, unknown>;
|
|
119
|
+
/** Source annotation per record field ('user' | 'llm' | 'deterministic'). */
|
|
120
|
+
fieldSources: Partial<Record<IntakeField, IntakeFieldSource>>;
|
|
121
|
+
/** Raw idempotency key used at creation (kept for diagnostics). */
|
|
122
|
+
idempotencyKey?: string | undefined;
|
|
123
|
+
/** Optimistic-concurrency counter; every write increments it. */
|
|
124
|
+
version: number;
|
|
125
|
+
history: ChangeHistoryEntry[];
|
|
126
|
+
createdAt: number;
|
|
127
|
+
updatedAt: number;
|
|
128
|
+
submittedAt?: number | undefined;
|
|
129
|
+
submittedBy?: string | undefined;
|
|
130
|
+
submittedByType?: string | undefined;
|
|
131
|
+
cancelledAt?: number | undefined;
|
|
132
|
+
cancelledReason?: string | undefined;
|
|
133
|
+
archivedAt?: number | undefined;
|
|
134
|
+
}
|
|
135
|
+
/** Payload for creating an intake record. */
|
|
136
|
+
export interface CreateIntakeInput {
|
|
137
|
+
projectId: string;
|
|
138
|
+
/** Exact original request. Required, non-blank, ≤ MAX_REQUEST_LENGTH. */
|
|
139
|
+
originalRequest: string;
|
|
140
|
+
title?: string | undefined;
|
|
141
|
+
/** Any string is accepted and normalized; unknown values become 'other'. */
|
|
142
|
+
requestType?: string | undefined;
|
|
143
|
+
priority?: IntakePriority | undefined;
|
|
144
|
+
requestedBy: string;
|
|
145
|
+
businessGoal?: string | undefined;
|
|
146
|
+
targetUsers?: string[] | undefined;
|
|
147
|
+
expectedOutcome?: string | undefined;
|
|
148
|
+
scopeNotes?: string | undefined;
|
|
149
|
+
constraints?: string[] | undefined;
|
|
150
|
+
providedContext?: string[] | undefined;
|
|
151
|
+
attachments?: IntakeAttachmentInput[] | undefined;
|
|
152
|
+
relatedResources?: RelatedResourceInput[] | undefined;
|
|
153
|
+
metadata?: Record<string, unknown> | undefined;
|
|
154
|
+
/** Idempotent-create key: same key + project returns the existing record. */
|
|
155
|
+
idempotencyKey?: string | undefined;
|
|
156
|
+
/**
|
|
157
|
+
* Fields already answerable from existing project data. Their questions
|
|
158
|
+
* are skipped instead of asked.
|
|
159
|
+
*/
|
|
160
|
+
knownFields?: string[] | undefined;
|
|
161
|
+
/** Question catalog override. Defaults to DEFAULT_INTAKE_QUESTIONS. */
|
|
162
|
+
questions?: IntakeQuestionTemplateInput[] | undefined;
|
|
163
|
+
}
|
|
164
|
+
export interface IntakeAttachmentInput {
|
|
165
|
+
name: string;
|
|
166
|
+
kind: IntakeAttachmentKind;
|
|
167
|
+
path?: string | undefined;
|
|
168
|
+
url?: string | undefined;
|
|
169
|
+
sizeBytes?: number | undefined;
|
|
170
|
+
mimeType?: string | undefined;
|
|
171
|
+
}
|
|
172
|
+
export interface RelatedResourceInput {
|
|
173
|
+
kind: RelatedResourceKind;
|
|
174
|
+
reference: string;
|
|
175
|
+
title?: string | undefined;
|
|
176
|
+
}
|
|
177
|
+
export interface IntakeQuestionTemplateInput {
|
|
178
|
+
field: string;
|
|
179
|
+
question: string;
|
|
180
|
+
required?: boolean | undefined;
|
|
181
|
+
}
|
|
182
|
+
/** Payload for updating a draft intake record. */
|
|
183
|
+
export interface UpdateIntakeInput {
|
|
184
|
+
title?: string | undefined;
|
|
185
|
+
requestType?: string | undefined;
|
|
186
|
+
priority?: IntakePriority | undefined;
|
|
187
|
+
businessGoal?: string | undefined;
|
|
188
|
+
targetUsers?: string[] | undefined;
|
|
189
|
+
expectedOutcome?: string | undefined;
|
|
190
|
+
scopeNotes?: string | undefined;
|
|
191
|
+
constraints?: string[] | undefined;
|
|
192
|
+
providedContext?: string[] | undefined;
|
|
193
|
+
metadata?: Record<string, unknown> | undefined;
|
|
194
|
+
}
|
|
195
|
+
/** Payload for answering an intake question. */
|
|
196
|
+
export interface AddAnswerInput {
|
|
197
|
+
field: string;
|
|
198
|
+
answer: string;
|
|
199
|
+
question?: string | undefined;
|
|
200
|
+
}
|
|
201
|
+
/** Payload for attaching a supporting resource. */
|
|
202
|
+
export interface AttachResourceInput {
|
|
203
|
+
attachment?: IntakeAttachmentInput | undefined;
|
|
204
|
+
relatedResource?: RelatedResourceInput | undefined;
|
|
205
|
+
}
|
|
206
|
+
/** Domain events published by the service. */
|
|
207
|
+
export declare const INTAKE_EVENT_NAMES: readonly ['RequirementIntakeCreated', 'RequirementIntakeUpdated', 'RequirementIntakeInformationRequested', 'RequirementIntakeSubmitted', 'RequirementIntakeCancelled', 'RequirementIntakeArchived'];
|
|
208
|
+
export type IntakeEventName = (typeof INTAKE_EVENT_NAMES)[number];
|
|
209
|
+
/**
|
|
210
|
+
* Event payload. Carries identifiers and safe metadata only — never a copy
|
|
211
|
+
* of the user request.
|
|
212
|
+
*/
|
|
213
|
+
export interface IntakeEvent {
|
|
214
|
+
event: IntakeEventName;
|
|
215
|
+
intakeId: string;
|
|
216
|
+
projectId: string;
|
|
217
|
+
actorId?: string | undefined;
|
|
218
|
+
actorType?: string | undefined;
|
|
219
|
+
previousStatus?: IntakeStatus | undefined;
|
|
220
|
+
status: IntakeStatus;
|
|
221
|
+
timestamp: string;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=types.d.ts.map
|