@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ECOSTACK TECHNOLOGY OÜ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# @wrongstack/requirement-intake
|
|
2
|
+
|
|
3
|
+
Requirements Intake — collect, preserve, validate, normalize, and submit
|
|
4
|
+
unstructured software development requests as structured intake records.
|
|
5
|
+
|
|
6
|
+
This module is **upstream of spec-driven development**. It only collects and
|
|
7
|
+
preserves the initial request and its supporting information. It does not
|
|
8
|
+
plan tasks, generate executable specifications, resolve contradictions,
|
|
9
|
+
produce architecture, or generate code — those concerns belong to separate
|
|
10
|
+
modules (`@wrongstack/sdd` and friends).
|
|
11
|
+
|
|
12
|
+
## Design goals
|
|
13
|
+
|
|
14
|
+
1. **The original request is sacred.** The exact user input is stored verbatim
|
|
15
|
+
in `originalRequest` and is immutable after creation. No update path, no
|
|
16
|
+
LLM suggestion, and no normalization step can overwrite it.
|
|
17
|
+
2. **Every derived value is separable and source-annotated.** Normalized,
|
|
18
|
+
summarized, categorized, or LLM-generated content is stored separately and
|
|
19
|
+
tagged with `fieldSources` / `source` (`user`, `llm`, `deterministic`).
|
|
20
|
+
3. **LLM output is always a proposal.** It is validated against a schema,
|
|
21
|
+
stored with `source: 'llm'`, and only applied after an explicit user
|
|
22
|
+
`acceptSuggestion`. It never controls persistence, authorization, or
|
|
23
|
+
lifecycle state.
|
|
24
|
+
4. **Deterministic validation and lifecycle.** Enums are authoritative;
|
|
25
|
+
unknown request types map to `other`/`unspecified`; status transitions are
|
|
26
|
+
enforced by application logic.
|
|
27
|
+
5. **Safety first.** Authorization is enforced on every operation (fail
|
|
28
|
+
closed), concurrent writes use optimistic concurrency + file locks,
|
|
29
|
+
create/submit are idempotent, and no sensitive request content ever
|
|
30
|
+
reaches logs, metrics, or events.
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import {
|
|
36
|
+
RequirementIntakeStore,
|
|
37
|
+
RequirementIntakeService,
|
|
38
|
+
AllowAllIntakeAuthorizer,
|
|
39
|
+
} from '@wrongstack/requirement-intake';
|
|
40
|
+
|
|
41
|
+
const store = new RequirementIntakeStore({ baseDir: '.wrongstack/requirement-intakes' });
|
|
42
|
+
const service = new RequirementIntakeService({
|
|
43
|
+
store,
|
|
44
|
+
authorizer: new AllowAllIntakeAuthorizer(), // wire your own policy in production
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const ctx = { id: 'user-42', type: 'user', projectId: 'proj_01ABCDEF123456789' };
|
|
48
|
+
|
|
49
|
+
const { record } = await service.createIntake(
|
|
50
|
+
{
|
|
51
|
+
projectId: ctx.projectId,
|
|
52
|
+
originalRequest: 'Add email-based password reset so users can recover access.',
|
|
53
|
+
requestedBy: ctx.id,
|
|
54
|
+
},
|
|
55
|
+
ctx,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
await service.addAnswer(record.id, { field: 'business_goal', answer: 'Reduce support tickets' }, ctx);
|
|
59
|
+
const { record: submitted } = await service.submitIntake(record.id, ctx);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Operations
|
|
63
|
+
|
|
64
|
+
| Operation | Method |
|
|
65
|
+
|---|---|
|
|
66
|
+
| Create intake | `createIntake(input, ctx)` |
|
|
67
|
+
| Update draft | `updateIntake(id, patch, ctx, expectedVersion?)` |
|
|
68
|
+
| Get intake | `getIntake(id, ctx)` |
|
|
69
|
+
| Add answer | `addAnswer(id, { field, answer }, ctx)` |
|
|
70
|
+
| Update answer | `updateAnswer(id, answerId, { answer }, ctx)` |
|
|
71
|
+
| Attach resources | `attachResource(id, { attachment \| relatedResource }, ctx)` |
|
|
72
|
+
| LLM suggestions | `generateSuggestions(id, ctx, focus?)` |
|
|
73
|
+
| Accept suggestion | `acceptSuggestion(id, proposalId, ctx)` |
|
|
74
|
+
| Reject suggestion | `rejectSuggestion(id, proposalId, ctx)` |
|
|
75
|
+
| Submit | `submitIntake(id, ctx)` |
|
|
76
|
+
| Cancel | `cancelIntake(id, ctx, reason?)` |
|
|
77
|
+
| Archive | `archiveIntake(id, ctx)` |
|
|
78
|
+
| List project intakes | `listIntakes(projectId, ctx, filter?)` |
|
|
79
|
+
| Pending questions | `pendingQuestions(id, ctx)` |
|
|
80
|
+
|
|
81
|
+
Every mutation accepts an optional `expectedVersion` for optimistic
|
|
82
|
+
concurrency; a mismatch throws `IntakeConflictError`.
|
|
83
|
+
|
|
84
|
+
## Data model
|
|
85
|
+
|
|
86
|
+
The record adapts the spec's snake_case JSON to the codebase's camelCase
|
|
87
|
+
convention:
|
|
88
|
+
|
|
89
|
+
| Concept (spec) | Field |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `id` | `id` — `reqi_<ulid>` |
|
|
92
|
+
| `project_id` | `projectId` |
|
|
93
|
+
| `original_request` | `originalRequest` — immutable |
|
|
94
|
+
| `normalized_summary` | `normalizedSummary` |
|
|
95
|
+
| `request_type` | `requestType` |
|
|
96
|
+
| `requested_by` | `requestedBy` |
|
|
97
|
+
| `business_goal` | `businessGoal` |
|
|
98
|
+
| `target_users` | `targetUsers` |
|
|
99
|
+
| `expected_outcome` | `expectedOutcome` |
|
|
100
|
+
| `scope_notes` | `scopeNotes` |
|
|
101
|
+
| `provided_context` | `providedContext` |
|
|
102
|
+
| `related_resources` | `relatedResources` |
|
|
103
|
+
| `created_at` / `updated_at` | `createdAt` / `updatedAt` (epoch ms) |
|
|
104
|
+
|
|
105
|
+
Plus: `status`, `priority`, `constraints`, `attachments`, `answers`,
|
|
106
|
+
`questions`, `llmSuggestions`, `metadata`, `fieldSources`, `version`,
|
|
107
|
+
`history`, submission/cancellation/archival stamps, and the create
|
|
108
|
+
`idempotencyKey`.
|
|
109
|
+
|
|
110
|
+
Request types: `feature`, `bug_fix`, `refactor`, `performance`, `security`,
|
|
111
|
+
`ui_change`, `api_change`, `infrastructure`, `migration`, `testing`,
|
|
112
|
+
`documentation`, `maintenance`, `other`, `unspecified`.
|
|
113
|
+
|
|
114
|
+
Lifecycle: `draft → collecting_information → submitted`, `draft → cancelled`,
|
|
115
|
+
`collecting_information → cancelled`, `submitted/cancelled → archived`.
|
|
116
|
+
Invalid transitions throw `IntakeStateTransitionError`. Duplicate submission
|
|
117
|
+
is idempotent (returns the submitted record).
|
|
118
|
+
|
|
119
|
+
## LLM suggestions
|
|
120
|
+
|
|
121
|
+
Wire an adapter that implements `LlmSuggestionGenerator` and returns
|
|
122
|
+
structured output:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const service = new RequirementIntakeService({
|
|
126
|
+
store,
|
|
127
|
+
authorizer,
|
|
128
|
+
generator: {
|
|
129
|
+
async generate({ record, focus }) {
|
|
130
|
+
// call your LLM; return structured JSON only
|
|
131
|
+
return {
|
|
132
|
+
suggested_title: 'Add email-based password reset',
|
|
133
|
+
normalized_summary: 'Allow users to reset forgotten passwords through an email link.',
|
|
134
|
+
suggested_request_type: 'feature',
|
|
135
|
+
extracted_constraints: ['Rate-limit reset emails'],
|
|
136
|
+
suggested_questions: [{ field: 'target_users', question: 'Which users?' }],
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Output is validated with zod (`llmSuggestionOutputSchema`); malformed output
|
|
144
|
+
throws `IntakeSuggestionError` and is never persisted. Generating suggestions
|
|
145
|
+
moves a `draft` to `collecting_information` (application logic, not the LLM).
|
|
146
|
+
|
|
147
|
+
## Authorization
|
|
148
|
+
|
|
149
|
+
Pass an `IntakeAuthorizer` to the service. The module fails closed: without a
|
|
150
|
+
permissive authorizer, every operation throws `IntakeAuthorizationError`.
|
|
151
|
+
|
|
152
|
+
- `AllowAllIntakeAuthorizer` — embedded/single-user hosts.
|
|
153
|
+
- `DenyAllIntakeAuthorizer` — fail-closed default.
|
|
154
|
+
- `ProjectMembershipIntakeAuthorizer` — membership-based policy with optional
|
|
155
|
+
owner-only operations and built-in cross-project denial.
|
|
156
|
+
|
|
157
|
+
The service always verifies `record.projectId === ctx.projectId` through the
|
|
158
|
+
authorizer and rejects `listIntakes` for a project different from the
|
|
159
|
+
context's project.
|
|
160
|
+
|
|
161
|
+
## Persistence & concurrency
|
|
162
|
+
|
|
163
|
+
File-backed JSON store (mirrors `SpecStore` conventions):
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
baseDir/<id>.json — one record per file
|
|
167
|
+
baseDir/_index.json — listing index
|
|
168
|
+
baseDir/_idempotency.json — create-idempotency key map
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- Every write goes through `atomicWrite` (temp + rename) under a per-file
|
|
172
|
+
exclusive lock (`withFileLock`), serializing concurrent writers within and
|
|
173
|
+
across processes.
|
|
174
|
+
- `version` is a per-write mutation counter; passing a stale `expectedVersion`
|
|
175
|
+
throws `IntakeConflictError` instead of silently overwriting.
|
|
176
|
+
- Create is idempotent via `idempotencyKey` (hashed in `_idempotency.json`).
|
|
177
|
+
- Change history is appended per write (`history`, capped at 200 entries).
|
|
178
|
+
- Records are never hard-deleted; `archived` is the soft-delete path.
|
|
179
|
+
|
|
180
|
+
Default location: `~/.wrongstack/projects/<slug>/requirement-intakes`
|
|
181
|
+
(`resolveWstackPaths(...).projectRequirementIntakes` — the `baseDir` option is
|
|
182
|
+
optional and falls back to this when omitted).
|
|
183
|
+
|
|
184
|
+
## Integrations
|
|
185
|
+
|
|
186
|
+
- **REST** — the WebUI server exposes the intake API under
|
|
187
|
+
`/api/projects/:projectId/requirement-intakes` (create/list) and
|
|
188
|
+
`/api/requirement-intakes/:intakeId` (get/patch/answers/suggestions/
|
|
189
|
+
submit/cancel/archive), token-gated like every other `/api` route. The
|
|
190
|
+
service is constructed per project in `startHttpServer` with an
|
|
191
|
+
`AllowAllIntakeAuthorizer` (the HTTP token gate is the authorization
|
|
192
|
+
boundary); hosts may inject their own via `intakeService`.
|
|
193
|
+
- **CLI** — `/intake [text]` creates and submits an intake record from the
|
|
194
|
+
given text or the most recent session prompt (see `docs/slash/intake.md`).
|
|
195
|
+
- **MCP** — `@wrongstack/requirement-intake-mcp` provides
|
|
196
|
+
`wstack-requirement-intake-mcp`, a project-scoped MCP server with
|
|
197
|
+
`requirement_intake_list` (read tier) and `requirement_intake_submit`
|
|
198
|
+
(writable tier) tools, mirroring the kanban-mcp pattern.
|
|
199
|
+
- **SDD** — `startInterviewFromIntake(driver, record)` /
|
|
200
|
+
`intakeToInterviewKickoff(record)` in `@wrongstack/sdd` seed a spec-builder
|
|
201
|
+
interview from a submitted intake record, using the original request as the
|
|
202
|
+
interview intent and the collected facts as project context.
|
|
203
|
+
|
|
204
|
+
## Observability
|
|
205
|
+
|
|
206
|
+
- **Events** — `RequirementIntakeCreated`, `RequirementIntakeUpdated`,
|
|
207
|
+
`RequirementIntakeInformationRequested`, `RequirementIntakeSubmitted`,
|
|
208
|
+
`RequirementIntakeCancelled`, `RequirementIntakeArchived`. Payloads carry
|
|
209
|
+
identifiers and safe metadata only — never request content.
|
|
210
|
+
- **Metrics** (`IntakeMetrics`) — created/submitted/cancelled/archived counts,
|
|
211
|
+
duplicate create/submit, validation failures, suggestion
|
|
212
|
+
requested/succeeded/failed, unauthorized attempts, and
|
|
213
|
+
`intake.time_to_submit` duration.
|
|
214
|
+
- **Logging** (`IntakeLogger`) — structured, scope-tagged, identifiers only.
|
|
215
|
+
|
|
216
|
+
All three are injectable; defaults are silent. Request content, answers, and
|
|
217
|
+
metadata values are never passed to the logger, metrics, or events.
|
|
218
|
+
|
|
219
|
+
## Security
|
|
220
|
+
|
|
221
|
+
- Prompt-injection text is ordinary data: preserved verbatim, never executed.
|
|
222
|
+
- Oversized input, blank fields, malformed metadata, and unknown enums are
|
|
223
|
+
rejected/normalized deterministically.
|
|
224
|
+
- Cross-project access is denied; automation identities receive only what the
|
|
225
|
+
authorizer grants.
|
|
226
|
+
- Sensitive content does not leak into logs/events (covered by tests).
|
|
227
|
+
|
|
228
|
+
## Development
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
pnpm --filter @wrongstack/requirement-intake typecheck
|
|
232
|
+
pnpm --filter @wrongstack/requirement-intake test
|
|
233
|
+
pnpm --filter @wrongstack/requirement-intake build
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Tests: 147 unit/integration/security tests in `tests/` (run under both the
|
|
237
|
+
package config and the workspace root config).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirements Intake — authorization.
|
|
3
|
+
*
|
|
4
|
+
* The module enforces authorization through an injected `IntakeAuthorizer`.
|
|
5
|
+
* Hosts wire their own policy (session user, project membership, agent
|
|
6
|
+
* permissions). The service awaits `isAllowed` on every operation and throws
|
|
7
|
+
* `IntakeAuthorizationError` on denial, so a misconfigured host fails closed
|
|
8
|
+
* unless it explicitly installs an allow-all policy.
|
|
9
|
+
*/
|
|
10
|
+
import type { IntakeContext, RequirementIntakeRecord } from './types.js';
|
|
11
|
+
export declare const INTAKE_OPERATIONS: readonly ['create', 'read', 'list', 'update', 'answer', 'attach', 'suggest', 'accept_suggestion', 'reject_suggestion', 'submit', 'cancel', 'archive'];
|
|
12
|
+
export type IntakeOperation = (typeof INTAKE_OPERATIONS)[number];
|
|
13
|
+
export interface IntakeAuthorizer {
|
|
14
|
+
/**
|
|
15
|
+
* Decide whether `actor` may perform `operation` in `ctx.projectId`,
|
|
16
|
+
* optionally against the target record. May return a Promise for
|
|
17
|
+
* async resolvers. Implementations must deny cross-project access:
|
|
18
|
+
* the record's `projectId` must equal `ctx.projectId`.
|
|
19
|
+
*/
|
|
20
|
+
isAllowed(operation: IntakeOperation, ctx: IntakeContext, record?: RequirementIntakeRecord | undefined): boolean | Promise<boolean>;
|
|
21
|
+
}
|
|
22
|
+
/** Permissive policy — every operation is allowed. For embedded/single-user hosts. */
|
|
23
|
+
export declare class AllowAllIntakeAuthorizer implements IntakeAuthorizer {
|
|
24
|
+
isAllowed(_operation: IntakeOperation, _ctx: IntakeContext, _record?: RequirementIntakeRecord | undefined): boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Fail-closed policy — every operation is denied. Default safety net. */
|
|
27
|
+
export declare class DenyAllIntakeAuthorizer implements IntakeAuthorizer {
|
|
28
|
+
isAllowed(_operation: IntakeOperation, _ctx: IntakeContext, _record?: RequirementIntakeRecord | undefined): boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface ProjectMembershipIntakeAuthorizerOptions {
|
|
31
|
+
/** Resolve which projects an actor belongs to. */
|
|
32
|
+
projectsOf: (actorId: string, actorType: string) => Promise<ReadonlySet<string>> | ReadonlySet<string>;
|
|
33
|
+
/** Operations that additionally require the actor to own the record. */
|
|
34
|
+
ownerOnlyOperations?: ReadonlySet<IntakeOperation> | undefined;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Membership-based policy: an actor may operate on a project only when the
|
|
38
|
+
* `projectsOf` resolver lists it. Cross-project access is always denied.
|
|
39
|
+
* `ownerOnlyOperations` (e.g. `submit`) can additionally require
|
|
40
|
+
* `record.requestedBy === actor.id`.
|
|
41
|
+
*/
|
|
42
|
+
export declare class ProjectMembershipIntakeAuthorizer implements IntakeAuthorizer {
|
|
43
|
+
private readonly projectsOf;
|
|
44
|
+
private readonly ownerOnly;
|
|
45
|
+
constructor(options: ProjectMembershipIntakeAuthorizerOptions);
|
|
46
|
+
isAllowed(operation: IntakeOperation, ctx: IntakeContext, record?: RequirementIntakeRecord | undefined): Promise<boolean>;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=authorization.d.ts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirements Intake — domain constants.
|
|
3
|
+
*
|
|
4
|
+
* Deterministic enums and size limits. The authoritative request-type and
|
|
5
|
+
* status values live here; unknown values are normalized (see validation.ts)
|
|
6
|
+
* and never persisted as uncontrolled strings.
|
|
7
|
+
*/
|
|
8
|
+
export declare const INTAKE_ID_PREFIX = "reqi_";
|
|
9
|
+
export declare const ANSWER_ID_PREFIX = "ans_";
|
|
10
|
+
export declare const ATTACHMENT_ID_PREFIX = "attach_";
|
|
11
|
+
export declare const RELATED_RESOURCE_ID_PREFIX = "relres_";
|
|
12
|
+
export declare const QUESTION_ID_PREFIX = "q_";
|
|
13
|
+
export declare const SUGGESTION_ID_PREFIX = "sug_";
|
|
14
|
+
/**
|
|
15
|
+
* Supported request types. `unspecified` is the neutral default; `other`
|
|
16
|
+
* absorbs any unknown/unsupported value supplied by callers or the LLM.
|
|
17
|
+
*/
|
|
18
|
+
export declare const REQUEST_TYPES: readonly ['feature', 'bug_fix', 'refactor', 'performance', 'security', 'ui_change', 'api_change', 'infrastructure', 'migration', 'testing', 'documentation', 'maintenance', 'other', 'unspecified'];
|
|
19
|
+
export type RequestType = (typeof REQUEST_TYPES)[number];
|
|
20
|
+
/** Deterministic lifecycle. Only application logic may change status. */
|
|
21
|
+
export declare const INTAKE_STATUSES: readonly ['draft', 'collecting_information', 'submitted', 'cancelled', 'archived'];
|
|
22
|
+
export type IntakeStatus = (typeof INTAKE_STATUSES)[number];
|
|
23
|
+
export declare const INTAKE_PRIORITIES: readonly ['unspecified', 'low', 'medium', 'high', 'critical'];
|
|
24
|
+
export type IntakePriority = (typeof INTAKE_PRIORITIES)[number];
|
|
25
|
+
/** Who produced a stored value. Distinguishes user vs machine content. */
|
|
26
|
+
export declare const INTAKE_FIELD_SOURCES: readonly ['user', 'llm', 'deterministic', 'system'];
|
|
27
|
+
export type IntakeFieldSource = (typeof INTAKE_FIELD_SOURCES)[number];
|
|
28
|
+
/** Record fields that carry a source annotation. */
|
|
29
|
+
export declare const INTAKE_FIELDS: readonly ['title', 'normalized_summary', 'request_type', 'priority', 'business_goal', 'target_users', 'expected_outcome', 'scope_notes', 'constraints', 'provided_context', 'attachments', 'related_resources'];
|
|
30
|
+
export type IntakeField = (typeof INTAKE_FIELDS)[number];
|
|
31
|
+
export declare const INTAKE_ATTACHMENT_KINDS: readonly ['file', 'image', 'document', 'link', 'other'];
|
|
32
|
+
export type IntakeAttachmentKind = (typeof INTAKE_ATTACHMENT_KINDS)[number];
|
|
33
|
+
export declare const RELATED_RESOURCE_KINDS: readonly ['spec', 'issue', 'pr', 'doc', 'url', 'other'];
|
|
34
|
+
export type RelatedResourceKind = (typeof RELATED_RESOURCE_KINDS)[number];
|
|
35
|
+
export declare const INTAKE_QUESTION_STATUSES: readonly ['unanswered', 'answered', 'skipped'];
|
|
36
|
+
export type IntakeQuestionStatus = (typeof INTAKE_QUESTION_STATUSES)[number];
|
|
37
|
+
export declare const SUGGESTION_STATUSES: readonly ['pending', 'accepted', 'rejected'];
|
|
38
|
+
export type SuggestionStatus = (typeof SUGGESTION_STATUSES)[number];
|
|
39
|
+
export declare const SUGGESTION_KINDS: readonly ['title', 'summary', 'request_type', 'priority', 'question', 'constraint', 'target_user', 'outcome'];
|
|
40
|
+
export type SuggestionKind = (typeof SUGGESTION_KINDS)[number];
|
|
41
|
+
/** Maximum length of the original request, in characters. */
|
|
42
|
+
export declare const MAX_REQUEST_LENGTH = 100000;
|
|
43
|
+
/** Maximum length of a title, in characters. */
|
|
44
|
+
export declare const MAX_TITLE_LENGTH = 200;
|
|
45
|
+
/** Maximum length of the normalized summary, in characters. */
|
|
46
|
+
export declare const MAX_SUMMARY_LENGTH = 2000;
|
|
47
|
+
/** Maximum length of any free-form string field (goal, outcome, scope notes, …). */
|
|
48
|
+
export declare const MAX_STRING_FIELD_LENGTH = 5000;
|
|
49
|
+
/** Maximum number of items in a list field (target users, constraints, …). */
|
|
50
|
+
export declare const MAX_ARRAY_ITEMS = 50;
|
|
51
|
+
/** Maximum number of attachments on one intake record. */
|
|
52
|
+
export declare const MAX_ATTACHMENTS = 20;
|
|
53
|
+
/** Maximum number of related resources on one intake record. */
|
|
54
|
+
export declare const MAX_RELATED_RESOURCES = 50;
|
|
55
|
+
/** Maximum number of top-level metadata keys. */
|
|
56
|
+
export declare const MAX_METADATA_ENTRIES = 50;
|
|
57
|
+
/** Maximum serialized metadata size, in bytes. */
|
|
58
|
+
export declare const MAX_METADATA_BYTES: number;
|
|
59
|
+
/** Maximum length of an intake answer, in characters. */
|
|
60
|
+
export declare const MAX_ANSWER_LENGTH = 50000;
|
|
61
|
+
/** Maximum length of an idempotency key, in characters. */
|
|
62
|
+
export declare const MAX_IDEMPOTENCY_KEY_LENGTH = 128;
|
|
63
|
+
/** Maximum length of an attachment/related-resource reference. */
|
|
64
|
+
export declare const MAX_REFERENCE_LENGTH = 500;
|
|
65
|
+
/** Maximum length of a question text. */
|
|
66
|
+
export declare const MAX_QUESTION_LENGTH = 500;
|
|
67
|
+
/** Maximum number of change-history entries retained per record. */
|
|
68
|
+
export declare const MAX_HISTORY_ENTRIES = 200;
|
|
69
|
+
/** Maximum number of pending LLM suggestions retained per record. */
|
|
70
|
+
export declare const MAX_SUGGESTIONS = 100;
|
|
71
|
+
/** Deterministic fallback summary length when no LLM summary is available. */
|
|
72
|
+
export declare const DETERMINISTIC_SUMMARY_LENGTH = 240;
|
|
73
|
+
/**
|
|
74
|
+
* Default intake question catalog. Hosts may override via create options.
|
|
75
|
+
* `field` values map to record properties through the answer pipeline
|
|
76
|
+
* (see service.ts) or stay as structured answers for later modules.
|
|
77
|
+
*/
|
|
78
|
+
export interface IntakeQuestionTemplate {
|
|
79
|
+
field: string;
|
|
80
|
+
question: string;
|
|
81
|
+
required: boolean;
|
|
82
|
+
}
|
|
83
|
+
export declare const DEFAULT_INTAKE_QUESTIONS: readonly IntakeQuestionTemplate[];
|
|
84
|
+
//# sourceMappingURL=constants.d.ts.map
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirements Intake — typed errors.
|
|
3
|
+
*
|
|
4
|
+
* Every failure path throws an `IntakeError` subclass carrying a stable
|
|
5
|
+
* `code` so hosts can map them to HTTP statuses, CLI messages, or events.
|
|
6
|
+
*/
|
|
7
|
+
export type IntakeErrorCode = 'INTAKE_VALIDATION_ERROR' | 'INTAKE_NOT_FOUND' | 'INTAKE_INVALID_TRANSITION' | 'INTAKE_STATUS_LOCKED' | 'INTAKE_CONFLICT' | 'INTAKE_UNAUTHORIZED' | 'INTAKE_SUGGESTION_ERROR';
|
|
8
|
+
export declare class IntakeError extends Error {
|
|
9
|
+
readonly code: IntakeErrorCode;
|
|
10
|
+
constructor(code: IntakeErrorCode, message: string, options?: ErrorOptions);
|
|
11
|
+
}
|
|
12
|
+
/** One field-level validation problem. */
|
|
13
|
+
export interface IntakeValidationIssue {
|
|
14
|
+
/** Path of the offending field, e.g. `originalRequest` or `metadata.foo`. */
|
|
15
|
+
field: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class IntakeValidationError extends IntakeError {
|
|
19
|
+
readonly issues: IntakeValidationIssue[];
|
|
20
|
+
constructor(issues: IntakeValidationIssue[], message?: string);
|
|
21
|
+
}
|
|
22
|
+
export declare class IntakeNotFoundError extends IntakeError {
|
|
23
|
+
constructor(id: string);
|
|
24
|
+
}
|
|
25
|
+
export declare class IntakeStateTransitionError extends IntakeError {
|
|
26
|
+
constructor(from: string, to: string);
|
|
27
|
+
}
|
|
28
|
+
/** Raised when mutating a record whose status locks its content. */
|
|
29
|
+
export declare class IntakeStatusLockedError extends IntakeError {
|
|
30
|
+
constructor(id: string, status: string, action: string);
|
|
31
|
+
}
|
|
32
|
+
/** Raised on optimistic-concurrency version mismatch. */
|
|
33
|
+
export declare class IntakeConflictError extends IntakeError {
|
|
34
|
+
constructor(id: string, expectedVersion: number, actualVersion: number);
|
|
35
|
+
}
|
|
36
|
+
export declare class IntakeAuthorizationError extends IntakeError {
|
|
37
|
+
constructor(operation: string, actorId: string, projectId: string);
|
|
38
|
+
}
|
|
39
|
+
/** Raised when the LLM suggestion pipeline fails or returns malformed output. */
|
|
40
|
+
export declare class IntakeSuggestionError extends IntakeError {
|
|
41
|
+
constructor(message: string, options?: ErrorOptions);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=errors.d.ts.map
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirements Intake — domain event emitter.
|
|
3
|
+
*
|
|
4
|
+
* Events carry identifiers and safe metadata only — never a copy of the
|
|
5
|
+
* user request. Pattern mirrors the Kanban board event emitter.
|
|
6
|
+
*/
|
|
7
|
+
import type { IntakeEvent, IntakeEventName } from './types.js';
|
|
8
|
+
type Listener = (event: IntakeEvent) => void;
|
|
9
|
+
export declare class IntakeEventEmitter {
|
|
10
|
+
private readonly listeners;
|
|
11
|
+
/** Publish an event. Listener errors are swallowed so one bad listener cannot break others. */
|
|
12
|
+
emit(event: IntakeEventName, data: Omit<IntakeEvent, 'event' | 'timestamp'>): void;
|
|
13
|
+
/** Subscribe; returns a disposer. Past the cap, returns a no-op disposer. */
|
|
14
|
+
subscribe(listener: Listener): () => void;
|
|
15
|
+
get listenerCount(): number;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=events.d.ts.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Requirements Intake — public surface.
|
|
3
|
+
*
|
|
4
|
+
* Collect, preserve, validate, normalize, and submit unstructured software
|
|
5
|
+
* development requests as structured intake records. Upstream of spec-driven
|
|
6
|
+
* development; this module never plans, specifies, or implements.
|
|
7
|
+
*/
|
|
8
|
+
export { INTAKE_ID_PREFIX, REQUEST_TYPES, INTAKE_STATUSES, INTAKE_PRIORITIES, INTAKE_FIELD_SOURCES, INTAKE_FIELDS, INTAKE_ATTACHMENT_KINDS, RELATED_RESOURCE_KINDS, INTAKE_QUESTION_STATUSES, SUGGESTION_STATUSES, SUGGESTION_KINDS, MAX_REQUEST_LENGTH, MAX_TITLE_LENGTH, MAX_SUMMARY_LENGTH, MAX_STRING_FIELD_LENGTH, MAX_ARRAY_ITEMS, MAX_ATTACHMENTS, MAX_RELATED_RESOURCES, MAX_METADATA_ENTRIES, MAX_METADATA_BYTES, MAX_ANSWER_LENGTH, MAX_IDEMPOTENCY_KEY_LENGTH, MAX_REFERENCE_LENGTH, MAX_QUESTION_LENGTH, MAX_HISTORY_ENTRIES, MAX_SUGGESTIONS, DEFAULT_INTAKE_QUESTIONS, type RequestType, type IntakeStatus, type IntakePriority, type IntakeFieldSource, type IntakeField, type IntakeAttachmentKind, type RelatedResourceKind, type IntakeQuestionStatus, type SuggestionStatus, type SuggestionKind, type IntakeQuestionTemplate, } from './constants.js';
|
|
9
|
+
export { INTAKE_EVENT_NAMES, type IntakeActor, type IntakeContext, type IntakeAttachment, type RelatedResource, type IntakeAnswer, type IntakeQuestion, type ChangeHistoryEntry, type LlmSuggestionProposal, type RequirementIntakeRecord, type CreateIntakeInput, type IntakeAttachmentInput, type RelatedResourceInput, type IntakeQuestionTemplateInput, type UpdateIntakeInput, type AddAnswerInput, type AttachResourceInput, type IntakeEvent, type IntakeEventName, } from './types.js';
|
|
10
|
+
export { IntakeError, IntakeValidationError, IntakeNotFoundError, IntakeStateTransitionError, IntakeStatusLockedError, IntakeConflictError, IntakeAuthorizationError, IntakeSuggestionError, type IntakeErrorCode, type IntakeValidationIssue, } from './errors.js';
|
|
11
|
+
export { requestTypeSchema, prioritySchema, metadataSchema, attachmentInputSchema, relatedResourceInputSchema, questionTemplateInputSchema, createIntakeSchema, updateIntakeSchema, answerInputSchema, attachResourceInputSchema, normalizeRequestType, isBlank, parseWithIssues, validateCreateInput, validateUpdateInput, validateAnswerInput, validateAttachResourceInput, validateAttachmentInput, validateRelatedResourceInput, validateQuestionTemplateInput, validateFieldSource, deterministicSummary, deterministicTitle, } from './validation.js';
|
|
12
|
+
export { ALLOWED_TRANSITIONS, MUTABLE_STATUSES, canTransition, assertTransition, isMutableStatus, isTerminalStatus, isKnownStatus, } from './lifecycle.js';
|
|
13
|
+
export { buildInitialQuestions, pendingQuestions, upsertQuestion } from './questions.js';
|
|
14
|
+
export { INTAKE_OPERATIONS, AllowAllIntakeAuthorizer, DenyAllIntakeAuthorizer, ProjectMembershipIntakeAuthorizer, type IntakeAuthorizer, type IntakeOperation, type ProjectMembershipIntakeAuthorizerOptions, } from './authorization.js';
|
|
15
|
+
export { NoopIntakeLogger, InMemoryIntakeLogger, type IntakeLogger, type IntakeLogFields, } from './logger.js';
|
|
16
|
+
export { INTAKE_COUNTERS, INTAKE_TIMERS, NoopIntakeMetrics, InMemoryIntakeMetrics, type IntakeCounter, type IntakeTimer, type IntakeMetrics, } from './metrics.js';
|
|
17
|
+
export { IntakeEventEmitter } from './events.js';
|
|
18
|
+
export { RequirementIntakeStore, newIntakeId, type RequirementIntakeStoreOptions, type IntakeIndexEntry, type StoreUpdateOptions, type StoreCreateResult, } from './store.js';
|
|
19
|
+
export { llmSuggestionOutputSchema, validateLlmSuggestionOutput, toProposals, assertSuggestionString, type LlmSuggestionRequest, type LlmSuggestionOutput, type LlmSuggestionGenerator, type NormalizedLlmSuggestion, } from './suggestions.js';
|
|
20
|
+
export { RequirementIntakeService, type RequirementIntakeServiceOptions, type IntakeCreateResult, type IntakeSubmitResult, type IntakeListFilter, } from './service.js';
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|