@alex.botez/elaborate 1.0.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 +114 -0
- package/dist/durable/index.d.ts +10 -0
- package/dist/durable/index.d.ts.map +1 -0
- package/dist/durable/index.js +9 -0
- package/dist/durable/index.js.map +1 -0
- package/dist/durable/workflow.d.ts +165 -0
- package/dist/durable/workflow.d.ts.map +1 -0
- package/dist/durable/workflow.js +224 -0
- package/dist/durable/workflow.js.map +1 -0
- package/dist/interview/classification.d.ts +16 -0
- package/dist/interview/classification.d.ts.map +1 -0
- package/dist/interview/classification.js +25 -0
- package/dist/interview/classification.js.map +1 -0
- package/dist/interview/composition.d.ts +12 -0
- package/dist/interview/composition.d.ts.map +1 -0
- package/dist/interview/composition.js +19 -0
- package/dist/interview/composition.js.map +1 -0
- package/dist/interview/describe.d.ts +41 -0
- package/dist/interview/describe.d.ts.map +1 -0
- package/dist/interview/describe.js +80 -0
- package/dist/interview/describe.js.map +1 -0
- package/dist/interview/deviation.d.ts +91 -0
- package/dist/interview/deviation.d.ts.map +1 -0
- package/dist/interview/deviation.js +137 -0
- package/dist/interview/deviation.js.map +1 -0
- package/dist/interview/extraction.d.ts +32 -0
- package/dist/interview/extraction.d.ts.map +1 -0
- package/dist/interview/extraction.js +102 -0
- package/dist/interview/extraction.js.map +1 -0
- package/dist/interview/index.d.ts +25 -0
- package/dist/interview/index.d.ts.map +1 -0
- package/dist/interview/index.js +22 -0
- package/dist/interview/index.js.map +1 -0
- package/dist/interview/macros.d.ts +91 -0
- package/dist/interview/macros.d.ts.map +1 -0
- package/dist/interview/macros.js +138 -0
- package/dist/interview/macros.js.map +1 -0
- package/dist/interview/preambles.d.ts +18 -0
- package/dist/interview/preambles.d.ts.map +1 -0
- package/dist/interview/preambles.js +93 -0
- package/dist/interview/preambles.js.map +1 -0
- package/dist/interview/progress.d.ts +31 -0
- package/dist/interview/progress.d.ts.map +1 -0
- package/dist/interview/progress.js +51 -0
- package/dist/interview/progress.js.map +1 -0
- package/dist/interview/prompt.d.ts +20 -0
- package/dist/interview/prompt.d.ts.map +1 -0
- package/dist/interview/prompt.js +58 -0
- package/dist/interview/prompt.js.map +1 -0
- package/dist/phases/aggregate/assumptions.d.ts +14 -0
- package/dist/phases/aggregate/assumptions.d.ts.map +1 -0
- package/dist/phases/aggregate/assumptions.js +33 -0
- package/dist/phases/aggregate/assumptions.js.map +1 -0
- package/dist/phases/aggregate/findings.d.ts +10 -0
- package/dist/phases/aggregate/findings.d.ts.map +1 -0
- package/dist/phases/aggregate/findings.js +20 -0
- package/dist/phases/aggregate/findings.js.map +1 -0
- package/dist/phases/aggregate/goals.d.ts +28 -0
- package/dist/phases/aggregate/goals.d.ts.map +1 -0
- package/dist/phases/aggregate/goals.js +74 -0
- package/dist/phases/aggregate/goals.js.map +1 -0
- package/dist/phases/aggregate/index.d.ts +128 -0
- package/dist/phases/aggregate/index.d.ts.map +1 -0
- package/dist/phases/aggregate/index.js +165 -0
- package/dist/phases/aggregate/index.js.map +1 -0
- package/dist/phases/aggregate/pam.d.ts +19 -0
- package/dist/phases/aggregate/pam.d.ts.map +1 -0
- package/dist/phases/aggregate/pam.js +37 -0
- package/dist/phases/aggregate/pam.js.map +1 -0
- package/dist/phases/aggregate/scope.d.ts +24 -0
- package/dist/phases/aggregate/scope.d.ts.map +1 -0
- package/dist/phases/aggregate/scope.js +67 -0
- package/dist/phases/aggregate/scope.js.map +1 -0
- package/dist/phases/aggregate/shared.d.ts +16 -0
- package/dist/phases/aggregate/shared.d.ts.map +1 -0
- package/dist/phases/aggregate/shared.js +26 -0
- package/dist/phases/aggregate/shared.js.map +1 -0
- package/dist/phases/aggregate/stakeholders.d.ts +27 -0
- package/dist/phases/aggregate/stakeholders.d.ts.map +1 -0
- package/dist/phases/aggregate/stakeholders.js +90 -0
- package/dist/phases/aggregate/stakeholders.js.map +1 -0
- package/dist/phases/aggregate/waitingRoom.d.ts +16 -0
- package/dist/phases/aggregate/waitingRoom.d.ts.map +1 -0
- package/dist/phases/aggregate/waitingRoom.js +32 -0
- package/dist/phases/aggregate/waitingRoom.js.map +1 -0
- package/dist/phases/assumptions.d.ts +19 -0
- package/dist/phases/assumptions.d.ts.map +1 -0
- package/dist/phases/assumptions.js +224 -0
- package/dist/phases/assumptions.js.map +1 -0
- package/dist/phases/configuration.d.ts +8 -0
- package/dist/phases/configuration.d.ts.map +1 -0
- package/dist/phases/configuration.js +14 -0
- package/dist/phases/configuration.js.map +1 -0
- package/dist/phases/goals.d.ts +15 -0
- package/dist/phases/goals.d.ts.map +1 -0
- package/dist/phases/goals.js +297 -0
- package/dist/phases/goals.js.map +1 -0
- package/dist/phases/index.d.ts +21 -0
- package/dist/phases/index.d.ts.map +1 -0
- package/dist/phases/index.js +56 -0
- package/dist/phases/index.js.map +1 -0
- package/dist/phases/opening.d.ts +12 -0
- package/dist/phases/opening.d.ts.map +1 -0
- package/dist/phases/opening.js +99 -0
- package/dist/phases/opening.js.map +1 -0
- package/dist/phases/purpose.d.ts +17 -0
- package/dist/phases/purpose.d.ts.map +1 -0
- package/dist/phases/purpose.js +207 -0
- package/dist/phases/purpose.js.map +1 -0
- package/dist/phases/schema.d.ts +861 -0
- package/dist/phases/schema.d.ts.map +1 -0
- package/dist/phases/schema.js +157 -0
- package/dist/phases/schema.js.map +1 -0
- package/dist/phases/scope.d.ts +18 -0
- package/dist/phases/scope.d.ts.map +1 -0
- package/dist/phases/scope.js +440 -0
- package/dist/phases/scope.js.map +1 -0
- package/dist/phases/session/archive.d.ts +19 -0
- package/dist/phases/session/archive.d.ts.map +1 -0
- package/dist/phases/session/archive.js +49 -0
- package/dist/phases/session/archive.js.map +1 -0
- package/dist/phases/session/file.d.ts +31 -0
- package/dist/phases/session/file.d.ts.map +1 -0
- package/dist/phases/session/file.js +113 -0
- package/dist/phases/session/file.js.map +1 -0
- package/dist/phases/session/index.d.ts +8 -0
- package/dist/phases/session/index.d.ts.map +1 -0
- package/dist/phases/session/index.js +8 -0
- package/dist/phases/session/index.js.map +1 -0
- package/dist/phases/session/persistence.d.ts +30 -0
- package/dist/phases/session/persistence.d.ts.map +1 -0
- package/dist/phases/session/persistence.js +91 -0
- package/dist/phases/session/persistence.js.map +1 -0
- package/dist/phases/shared.d.ts +64 -0
- package/dist/phases/shared.d.ts.map +1 -0
- package/dist/phases/shared.js +100 -0
- package/dist/phases/shared.js.map +1 -0
- package/dist/phases/stakeholders.d.ts +17 -0
- package/dist/phases/stakeholders.d.ts.map +1 -0
- package/dist/phases/stakeholders.js +370 -0
- package/dist/phases/stakeholders.js.map +1 -0
- package/dist/phases/validation.d.ts +13 -0
- package/dist/phases/validation.d.ts.map +1 -0
- package/dist/phases/validation.js +130 -0
- package/dist/phases/validation.js.map +1 -0
- package/dist/skill/adapter.d.ts +16 -0
- package/dist/skill/adapter.d.ts.map +1 -0
- package/dist/skill/adapter.js +278 -0
- package/dist/skill/adapter.js.map +1 -0
- package/dist/skill/index.d.ts +12 -0
- package/dist/skill/index.d.ts.map +1 -0
- package/dist/skill/index.js +11 -0
- package/dist/skill/index.js.map +1 -0
- package/dist/skill/log.d.ts +18 -0
- package/dist/skill/log.d.ts.map +1 -0
- package/dist/skill/log.js +53 -0
- package/dist/skill/log.js.map +1 -0
- package/package.json +83 -0
- package/skills/elaborate/SKILL.md +97 -0
- package/skills/elaborate/scripts/elaborate.cjs +589 -0
- package/skills/project-brief/SKILL.md +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexandru Botez
|
|
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,114 @@
|
|
|
1
|
+
# Elaborate
|
|
2
|
+
|
|
3
|
+
An agent skill that runs a Socratic interview inside your coding agent — turning vague ideas into structured intent before you write a line of code.
|
|
4
|
+
|
|
5
|
+
Works with Claude Code, Cursor, GitHub Copilot, and any agent that supports the [agentskills.io](https://agentskills.io) standard.
|
|
6
|
+
|
|
7
|
+
[](https://github.com/alexbot/elaborate/actions/workflows/ci.yml)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## The Problem
|
|
13
|
+
|
|
14
|
+
Most projects start with a gap between "I have an idea" and "I know what to build." People either jump straight to code or hand an AI a one-line prompt and hope for a spec. Both skip the hard part: figuring out what success looks like, who cares, where the boundaries are, and which assumptions will bite you later.
|
|
15
|
+
|
|
16
|
+
Traditional methods for this exist — but they depend on trained facilitators and structured workshops. Solo developers, founders, and small teams skip them entirely. The result: projects start with vague goals and discover critical gaps mid-build.
|
|
17
|
+
|
|
18
|
+
## What Elaborate Does
|
|
19
|
+
|
|
20
|
+
Elaborate runs a structured interview — 25-32 questions across seven phases — that moves you from a vague idea to a scoped project definition.
|
|
21
|
+
|
|
22
|
+
| Phase | What it covers |
|
|
23
|
+
|-------|---------------|
|
|
24
|
+
| **Opening** | Context and framing — greenfield or existing project? |
|
|
25
|
+
| **Purpose** | What problem are you solving? What does success look like? |
|
|
26
|
+
| **Goals** | Concrete, measurable outcomes with rationale |
|
|
27
|
+
| **Stakeholders** | Who cares, what they need, where interests conflict |
|
|
28
|
+
| **Scope** | What's in, what's out, why the boundary sits there |
|
|
29
|
+
| **Assumptions** | What you're taking for granted that could invalidate everything |
|
|
30
|
+
| **Validation** | Review and confirm — or revise before you build |
|
|
31
|
+
|
|
32
|
+
Every goal, stakeholder, scope decision, and assumption in the output traces back to the conversation turn that produced it.
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
Requires **Node.js 18+**.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx skills add alexbot/elaborate
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
To install for a specific agent:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx skills add alexbot/elaborate -a claude-code # Claude Code only
|
|
46
|
+
npx skills add alexbot/elaborate -a cursor # Cursor only
|
|
47
|
+
npx skills add alexbot/elaborate -a github-copilot # GitHub Copilot only
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
/elaborate
|
|
54
|
+
# or just say it naturally:
|
|
55
|
+
"I'd like to elaborate my latest idea about a neighborhood recycling app"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Elaborate responds to both the slash command and natural language that signals intent to elaborate an idea.
|
|
59
|
+
|
|
60
|
+
> **Note on model quality:** The interview process completes reliably on any model, but the depth of insight depends on model capability. Stronger models extract more nuance and produce richer artifacts. Smaller models may miss subtlety or generate shallow extractions.
|
|
61
|
+
|
|
62
|
+
## Example: Saving a Library Branch
|
|
63
|
+
|
|
64
|
+
A volunteer wants to save their neighborhood library from closure. The input:
|
|
65
|
+
|
|
66
|
+
> *"The city's talking about closing it because foot traffic has dropped. I don't want to see that happen — the library's been part of this neighborhood for a long time. I want to figure out how to bring people back."*
|
|
67
|
+
|
|
68
|
+
After 28 questions, the respondent had reversed their own premise: "I've been working backwards — I never stopped to ask whether the library is actually what the neighborhood needs." What started as "bring people back" became a research phase that could confirm or kill the original idea — and the respondent was the one who got there, not the tool.
|
|
69
|
+
|
|
70
|
+
[Read the full brief →](docs/examples/library.brief.md)
|
|
71
|
+
|
|
72
|
+
See also: [Elaborate interviews itself](docs/examples/elaborate.brief.md) (the tool pointed at its own premise) and [selling cookies at the farmers market](docs/examples/cookies.brief.md) (a $500 hobby that might be a business). More examples across domains — from [federal spending oversight](docs/examples/federalspending.brief.md) to [elderly independent living](docs/examples/alfred.brief.md) — in [docs/examples/](docs/examples/).
|
|
73
|
+
|
|
74
|
+
### Output
|
|
75
|
+
|
|
76
|
+
The interview produces a session file (`.elaborate/session.yaml`) — a structured YAML artifact with every goal, stakeholder, scope item, and assumption traced to the conversation turn that produced it. This is machine-consumable: designed as input for spec-driven development tools, code generators, or any pipeline that needs structured intent.
|
|
77
|
+
|
|
78
|
+
If you want something presentable — for a business plan, a stakeholder pitch, or a project kickoff — run `/project-brief` on the session file to generate a readable markdown brief. The skill prompt is in [`skills/project-brief/SKILL.md`](skills/project-brief/SKILL.md).
|
|
79
|
+
|
|
80
|
+
All examples above are generated by the automated test suite using a simulated respondent (an LLM playing the person with the idea). The briefs are generated from those sessions using the project-brief skill. You can reproduce both by running the scenario harness yourself.
|
|
81
|
+
|
|
82
|
+
## How It Works
|
|
83
|
+
|
|
84
|
+
The interview is complex — 7 phases, state persistence, extraction cycles, deviation handling. Rather than relying on the model to follow a long prompt correctly, Elaborate splits the work: a compiled Node.js script handles all process decisions (what to ask, when to transition, how to store artifacts) while the model handles only semantic work (understanding what you said, extracting meaning, composing follow-up questions). The model can't skip phases or lose track — the script drives.
|
|
85
|
+
|
|
86
|
+
The interview techniques draw on qualitative research traditions:
|
|
87
|
+
|
|
88
|
+
- **Kvale & Patton** — semi-structured interview design
|
|
89
|
+
- **Miller & Rollnick** — motivational interviewing (surfacing ambivalence without pushing)
|
|
90
|
+
- **Reynolds & Gutman** — means-end laddering (climbing from features to underlying values)
|
|
91
|
+
- **KAOS** — goal decomposition (structuring what emerges into testable hierarchies)
|
|
92
|
+
|
|
93
|
+
The AI asks questions, flags ambiguity, and structures what you say. It never fills gaps on its own — you decide everything.
|
|
94
|
+
|
|
95
|
+
See [docs/decisions/](docs/decisions/) for the full architecture story.
|
|
96
|
+
|
|
97
|
+
## Development
|
|
98
|
+
|
|
99
|
+
Requires **Node 22+**.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm install
|
|
103
|
+
npm run build # TypeScript compilation
|
|
104
|
+
npm run build:skill # esbuild bundle → dist/skill/
|
|
105
|
+
npx vitest run # Run tests
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Contributing
|
|
109
|
+
|
|
110
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, architecture, and how to pick up work.
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable workflow framework — public barrel.
|
|
3
|
+
*
|
|
4
|
+
* Memoized coroutine execution with suspend/resume. See workflow.ts for the
|
|
5
|
+
* full implementation; this file is the public surface for cross-layer and
|
|
6
|
+
* external consumers.
|
|
7
|
+
*/
|
|
8
|
+
export { Suspend, NonDeterminismError, DuplicateCallIdError, WorkflowContext, execute, } from "./workflow.js";
|
|
9
|
+
export type { StateEntry, WorkflowStatus, WorkflowState, StatePersistence, InferRequest, InferStep, PromptRequest, PromptStep, Prompt, Resolver, Workflow, FidelityDetail, FidelityResult, ExecutionResult, } from "./workflow.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/durable/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,OAAO,EACP,mBAAmB,EACnB,oBAAoB,EACpB,eAAe,EACf,OAAO,GACR,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,UAAU,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,aAAa,EACb,UAAU,EACV,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,cAAc,EACd,eAAe,GAChB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable workflow framework — public barrel.
|
|
3
|
+
*
|
|
4
|
+
* Memoized coroutine execution with suspend/resume. See workflow.ts for the
|
|
5
|
+
* full implementation; this file is the public surface for cross-layer and
|
|
6
|
+
* external consumers.
|
|
7
|
+
*/
|
|
8
|
+
export { Suspend, NonDeterminismError, DuplicateCallIdError, WorkflowContext, execute, } from "./workflow.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/durable/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,OAAO,EACP,mBAAmB,EACnB,oBAAoB,EACpB,eAAe,EACf,OAAO,GACR,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable workflow framework — memoized coroutine execution with suspend/resume.
|
|
3
|
+
*
|
|
4
|
+
* Workflows call infer (LLM) and prompt (user) through a context object. All calls
|
|
5
|
+
* are logged to an ordered state array; on re-execution, recorded values replay
|
|
6
|
+
* and only new calls hit the resolver. Any call can suspend via Suspend throw,
|
|
7
|
+
* which persists the suspension point and propagates to the caller.
|
|
8
|
+
*/
|
|
9
|
+
/** Thrown by a resolver (or call fn) to signal that a call cannot be fulfilled now. */
|
|
10
|
+
export declare class Suspend {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly value?: unknown | undefined;
|
|
13
|
+
constructor(id: string, value?: unknown | undefined);
|
|
14
|
+
}
|
|
15
|
+
/** Thrown when a replayed call id doesn't match the recorded sequence. */
|
|
16
|
+
export declare class NonDeterminismError extends Error {
|
|
17
|
+
constructor(position: number, expected: string, got: string);
|
|
18
|
+
}
|
|
19
|
+
/** Thrown when the same call id is used more than once in a single execution. */
|
|
20
|
+
export declare class DuplicateCallIdError extends Error {
|
|
21
|
+
constructor(id: string);
|
|
22
|
+
}
|
|
23
|
+
/** A single entry in the state log — either completed (has value) or suspended. */
|
|
24
|
+
export type StateEntry = {
|
|
25
|
+
id: string;
|
|
26
|
+
value: unknown;
|
|
27
|
+
} | {
|
|
28
|
+
id: string;
|
|
29
|
+
suspended: true;
|
|
30
|
+
};
|
|
31
|
+
/** Lifecycle status of a workflow execution. */
|
|
32
|
+
export type WorkflowStatus = "running" | "suspended" | "completed" | "failed";
|
|
33
|
+
/** Serializable snapshot of a workflow's progress — status plus ordered call log. */
|
|
34
|
+
export interface WorkflowState {
|
|
35
|
+
status: WorkflowStatus;
|
|
36
|
+
entries: StateEntry[];
|
|
37
|
+
}
|
|
38
|
+
/** Storage adapter for persisting workflow state between executions. */
|
|
39
|
+
export interface StatePersistence {
|
|
40
|
+
load(): WorkflowState | null;
|
|
41
|
+
save(state: WorkflowState): void;
|
|
42
|
+
/** Create initial empty state with "running" status. */
|
|
43
|
+
initialize(): void;
|
|
44
|
+
/** Transition workflow to a new status. */
|
|
45
|
+
setStatus(status: WorkflowStatus): void;
|
|
46
|
+
}
|
|
47
|
+
/** Payload for an infer call — semantic processing by the resolver (typically LLM). */
|
|
48
|
+
export interface InferRequest {
|
|
49
|
+
message: string;
|
|
50
|
+
schema?: Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
/** Phantom brand — carries result type without runtime presence. */
|
|
53
|
+
declare const INFER_STEP_RESULT: unique symbol;
|
|
54
|
+
/** Pre-built infer request with embedded id and phantom return type. */
|
|
55
|
+
export interface InferStep<T = unknown> extends InferRequest {
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly [INFER_STEP_RESULT]?: T;
|
|
58
|
+
}
|
|
59
|
+
/** Payload for a prompt call — user-facing message with optional suggested answers. */
|
|
60
|
+
export interface PromptRequest {
|
|
61
|
+
message: string;
|
|
62
|
+
suggestions?: string[];
|
|
63
|
+
}
|
|
64
|
+
/** Pre-built prompt request with embedded id — mirrors InferStep for prompt calls. */
|
|
65
|
+
export interface PromptStep extends PromptRequest {
|
|
66
|
+
readonly id: string;
|
|
67
|
+
}
|
|
68
|
+
/** A prompt emitted by the framework — either an infer or prompt request tagged with its call id. */
|
|
69
|
+
export type Prompt = {
|
|
70
|
+
id: string;
|
|
71
|
+
type: "infer";
|
|
72
|
+
request: InferRequest;
|
|
73
|
+
} | {
|
|
74
|
+
id: string;
|
|
75
|
+
type: "prompt";
|
|
76
|
+
request: PromptRequest;
|
|
77
|
+
};
|
|
78
|
+
/** Callback that fulfills prompts — the single adapter interface for all non-compute calls. */
|
|
79
|
+
export type Resolver = (prompt: Prompt) => Promise<unknown>;
|
|
80
|
+
/** Per-extraction mismatch detail — which keys were expected but missing. */
|
|
81
|
+
export interface FidelityDetail {
|
|
82
|
+
id: string;
|
|
83
|
+
expectedKeys: string[];
|
|
84
|
+
actualKeys: string[];
|
|
85
|
+
missingKeys: string[];
|
|
86
|
+
reason?: "non-object" | "missing-keys";
|
|
87
|
+
}
|
|
88
|
+
/** Accumulated fidelity result across all schema-bearing infer calls. */
|
|
89
|
+
export interface FidelityResult {
|
|
90
|
+
checked: number;
|
|
91
|
+
mismatched: number;
|
|
92
|
+
details: FidelityDetail[];
|
|
93
|
+
}
|
|
94
|
+
/** Result of a workflow execution — extensible container for execution-level concerns. */
|
|
95
|
+
export interface ExecutionResult {
|
|
96
|
+
fidelity: FidelityResult;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Execution context passed to workflow functions.
|
|
100
|
+
*
|
|
101
|
+
* Provides three call primitives (call, infer, prompt) that memoize results
|
|
102
|
+
* in an ordered state log and replay on re-execution.
|
|
103
|
+
*/
|
|
104
|
+
export declare class WorkflowContext {
|
|
105
|
+
private persistence;
|
|
106
|
+
private resolver;
|
|
107
|
+
private cursor;
|
|
108
|
+
private seen;
|
|
109
|
+
private state;
|
|
110
|
+
private fidelity;
|
|
111
|
+
private lastPrompt;
|
|
112
|
+
constructor(persistence: StatePersistence, resolver: Resolver);
|
|
113
|
+
/**
|
|
114
|
+
* Deterministic compute — runs fn, memoizes, replays from log on re-execution.
|
|
115
|
+
* If fn throws Suspend, a suspended entry is written before propagating.
|
|
116
|
+
*/
|
|
117
|
+
call<T>(id: string, fn: () => T | Promise<T>): Promise<T>;
|
|
118
|
+
/** ID of the most recent prompt() call, or null before any prompt. */
|
|
119
|
+
get lastPromptId(): string | null;
|
|
120
|
+
get currentSource(): {
|
|
121
|
+
promptId: string;
|
|
122
|
+
} | undefined;
|
|
123
|
+
/** Accumulated fidelity result for all schema-bearing infer calls in this execution. */
|
|
124
|
+
fidelityResult(): FidelityResult;
|
|
125
|
+
/** Non-deterministic semantic processing — delegates to resolver, memoized. */
|
|
126
|
+
infer<T>(step: InferStep<T>): Promise<T>;
|
|
127
|
+
infer<T>(id: string, request: InferRequest): Promise<T>;
|
|
128
|
+
/** Check result keys against schema keys; accumulate fidelity counters. */
|
|
129
|
+
private checkFidelity;
|
|
130
|
+
/** Human interaction — delegates to resolver, memoized. May cause suspension. */
|
|
131
|
+
prompt(step: PromptStep): Promise<string>;
|
|
132
|
+
prompt(id: string, request: PromptRequest): Promise<string>;
|
|
133
|
+
}
|
|
134
|
+
/** A workflow function — receives a context, performs calls, returns when complete. */
|
|
135
|
+
export type Workflow = (ctx: WorkflowContext) => Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Run (or resume) a workflow. Replays memoized calls, then continues execution.
|
|
138
|
+
* Returns execution result on completion. Throws Suspend on suspension (already
|
|
139
|
+
* persisted by the context). Throws other errors on failure.
|
|
140
|
+
*
|
|
141
|
+
* If status is already `completed`, returns `{ fidelity: EMPTY_FIDELITY }`
|
|
142
|
+
* without re-executing. Callers that need to distinguish fresh completion
|
|
143
|
+
* from a no-op should check `persistence.status()` before calling.
|
|
144
|
+
*
|
|
145
|
+
* Suspension is a throw, not a return. A `Suspend` instance carries the id of
|
|
146
|
+
* the suspending call and the pending `Prompt` payload as its `value` field.
|
|
147
|
+
* Callers must handle both paths:
|
|
148
|
+
*
|
|
149
|
+
* try {
|
|
150
|
+
* const result = await execute(persistence, workflow, resolver);
|
|
151
|
+
* // Workflow completed — `result.fidelity` has the extraction stats.
|
|
152
|
+
* } catch (e) {
|
|
153
|
+
* if (e instanceof Suspend) {
|
|
154
|
+
* // Workflow paused on a prompt — `e.value` is the `Prompt` payload,
|
|
155
|
+
* // `e.id` is the suspended call's id. Persist-and-return so the
|
|
156
|
+
* // next invocation resumes from this point.
|
|
157
|
+
* } else {
|
|
158
|
+
* // Real failure — persistence has already been flagged "failed".
|
|
159
|
+
* throw e;
|
|
160
|
+
* }
|
|
161
|
+
* }
|
|
162
|
+
*/
|
|
163
|
+
export declare function execute(persistence: StatePersistence, workflow: Workflow, resolver: Resolver): Promise<ExecutionResult>;
|
|
164
|
+
export {};
|
|
165
|
+
//# sourceMappingURL=workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/durable/workflow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable workflow framework — memoized coroutine execution with suspend/resume.
|
|
3
|
+
*
|
|
4
|
+
* Workflows call infer (LLM) and prompt (user) through a context object. All calls
|
|
5
|
+
* are logged to an ordered state array; on re-execution, recorded values replay
|
|
6
|
+
* and only new calls hit the resolver. Any call can suspend via Suspend throw,
|
|
7
|
+
* which persists the suspension point and propagates to the caller.
|
|
8
|
+
*/
|
|
9
|
+
// Errors
|
|
10
|
+
/** Thrown by a resolver (or call fn) to signal that a call cannot be fulfilled now. */
|
|
11
|
+
export class Suspend {
|
|
12
|
+
id;
|
|
13
|
+
value;
|
|
14
|
+
constructor(id, value) {
|
|
15
|
+
this.id = id;
|
|
16
|
+
this.value = value;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Thrown when a replayed call id doesn't match the recorded sequence. */
|
|
20
|
+
export class NonDeterminismError extends Error {
|
|
21
|
+
constructor(position, expected, got) {
|
|
22
|
+
super(`Non-determinism at position ${position}: expected "${expected}", got "${got}"`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Thrown when the same call id is used more than once in a single execution. */
|
|
26
|
+
export class DuplicateCallIdError extends Error {
|
|
27
|
+
constructor(id) {
|
|
28
|
+
super(`Duplicate call id: "${id}"`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Context
|
|
32
|
+
/**
|
|
33
|
+
* Execution context passed to workflow functions.
|
|
34
|
+
*
|
|
35
|
+
* Provides three call primitives (call, infer, prompt) that memoize results
|
|
36
|
+
* in an ordered state log and replay on re-execution.
|
|
37
|
+
*/
|
|
38
|
+
export class WorkflowContext {
|
|
39
|
+
persistence;
|
|
40
|
+
resolver;
|
|
41
|
+
cursor = 0;
|
|
42
|
+
seen = new Set();
|
|
43
|
+
state;
|
|
44
|
+
fidelity = { checked: 0, mismatched: 0, details: [] };
|
|
45
|
+
lastPrompt = null;
|
|
46
|
+
constructor(persistence, resolver) {
|
|
47
|
+
this.persistence = persistence;
|
|
48
|
+
this.resolver = resolver;
|
|
49
|
+
const state = persistence.load();
|
|
50
|
+
if (!state)
|
|
51
|
+
throw new Error("WorkflowContext requires initialized persistence; call persistence.initialize() first");
|
|
52
|
+
this.state = state;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Deterministic compute — runs fn, memoizes, replays from log on re-execution.
|
|
56
|
+
* If fn throws Suspend, a suspended entry is written before propagating.
|
|
57
|
+
*/
|
|
58
|
+
async call(id, fn) {
|
|
59
|
+
// Replay path: entry exists in state log
|
|
60
|
+
if (this.cursor < this.state.entries.length) {
|
|
61
|
+
const entry = this.state.entries[this.cursor];
|
|
62
|
+
if (entry.id !== id)
|
|
63
|
+
throw new NonDeterminismError(this.cursor, entry.id, id);
|
|
64
|
+
this.seen.add(entry.id);
|
|
65
|
+
this.cursor++;
|
|
66
|
+
// Suspended entry — try to fulfill
|
|
67
|
+
if ("suspended" in entry) {
|
|
68
|
+
try {
|
|
69
|
+
const result = await fn();
|
|
70
|
+
this.state.entries[this.cursor - 1] = { id, value: result };
|
|
71
|
+
this.persistence.save(this.state);
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
if (e instanceof Suspend) {
|
|
76
|
+
this.state.status = "suspended";
|
|
77
|
+
this.persistence.save(this.state);
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Completed entry — return cached value
|
|
84
|
+
return entry.value;
|
|
85
|
+
}
|
|
86
|
+
// First-hit path: new call not yet in state log
|
|
87
|
+
if (this.seen.has(id))
|
|
88
|
+
throw new DuplicateCallIdError(id);
|
|
89
|
+
this.seen.add(id);
|
|
90
|
+
try {
|
|
91
|
+
const result = await fn();
|
|
92
|
+
this.state.entries.push({ id, value: result });
|
|
93
|
+
this.cursor++;
|
|
94
|
+
this.persistence.save(this.state);
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
if (e instanceof Suspend) {
|
|
99
|
+
this.state.entries.push({ id, suspended: true });
|
|
100
|
+
this.state.status = "suspended";
|
|
101
|
+
this.cursor++;
|
|
102
|
+
this.persistence.save(this.state);
|
|
103
|
+
throw e;
|
|
104
|
+
}
|
|
105
|
+
throw e;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** ID of the most recent prompt() call, or null before any prompt. */
|
|
109
|
+
get lastPromptId() { return this.lastPrompt; }
|
|
110
|
+
get currentSource() {
|
|
111
|
+
return this.lastPrompt ? { promptId: this.lastPrompt } : undefined;
|
|
112
|
+
}
|
|
113
|
+
/** Accumulated fidelity result for all schema-bearing infer calls in this execution. */
|
|
114
|
+
fidelityResult() {
|
|
115
|
+
return this.fidelity;
|
|
116
|
+
}
|
|
117
|
+
async infer(first, second) {
|
|
118
|
+
const id = typeof first === "string" ? first : first.id;
|
|
119
|
+
const request = typeof first === "string"
|
|
120
|
+
? second
|
|
121
|
+
: { message: first.message, schema: first.schema };
|
|
122
|
+
const prompt = { id, type: "infer", request };
|
|
123
|
+
const result = await this.call(id, () => this.resolver(prompt));
|
|
124
|
+
if (request.schema && (result == null || typeof result !== "object")) {
|
|
125
|
+
const expectedKeys = Object.keys(request.schema);
|
|
126
|
+
this.fidelity.checked++;
|
|
127
|
+
this.fidelity.mismatched++;
|
|
128
|
+
this.fidelity.details.push({
|
|
129
|
+
id, expectedKeys, actualKeys: [], missingKeys: expectedKeys, reason: "non-object",
|
|
130
|
+
});
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
if (request.schema && result != null && typeof result === "object") {
|
|
134
|
+
this.checkFidelity(id, request.schema, result);
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
/** Check result keys against schema keys; accumulate fidelity counters. */
|
|
139
|
+
checkFidelity(id, schema, result) {
|
|
140
|
+
const expectedKeys = Object.keys(schema);
|
|
141
|
+
const actualKeys = Object.keys(result);
|
|
142
|
+
const missingKeys = expectedKeys.filter((k) => !(k in result));
|
|
143
|
+
this.fidelity.checked++;
|
|
144
|
+
if (missingKeys.length > 0) {
|
|
145
|
+
this.fidelity.mismatched++;
|
|
146
|
+
this.fidelity.details.push({ id, expectedKeys, actualKeys, missingKeys, reason: "missing-keys" });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async prompt(first, second) {
|
|
150
|
+
const id = typeof first === "string" ? first : first.id;
|
|
151
|
+
const request = typeof first === "string"
|
|
152
|
+
? second
|
|
153
|
+
: { message: first.message, ...(first.suggestions ? { suggestions: first.suggestions } : {}) };
|
|
154
|
+
request.message = normalizeMessage(request.message);
|
|
155
|
+
this.lastPrompt = id;
|
|
156
|
+
const p = { id, type: "prompt", request };
|
|
157
|
+
const result = await this.call(id, () => this.resolver(p));
|
|
158
|
+
if (typeof result !== "string")
|
|
159
|
+
return "";
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function normalizeMessage(text) {
|
|
164
|
+
return text
|
|
165
|
+
.split("\n")
|
|
166
|
+
.map((line) => line.trim())
|
|
167
|
+
.join("\n")
|
|
168
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
169
|
+
.trim();
|
|
170
|
+
}
|
|
171
|
+
// Execute
|
|
172
|
+
const EMPTY_FIDELITY = { checked: 0, mismatched: 0, details: [] };
|
|
173
|
+
/**
|
|
174
|
+
* Run (or resume) a workflow. Replays memoized calls, then continues execution.
|
|
175
|
+
* Returns execution result on completion. Throws Suspend on suspension (already
|
|
176
|
+
* persisted by the context). Throws other errors on failure.
|
|
177
|
+
*
|
|
178
|
+
* If status is already `completed`, returns `{ fidelity: EMPTY_FIDELITY }`
|
|
179
|
+
* without re-executing. Callers that need to distinguish fresh completion
|
|
180
|
+
* from a no-op should check `persistence.status()` before calling.
|
|
181
|
+
*
|
|
182
|
+
* Suspension is a throw, not a return. A `Suspend` instance carries the id of
|
|
183
|
+
* the suspending call and the pending `Prompt` payload as its `value` field.
|
|
184
|
+
* Callers must handle both paths:
|
|
185
|
+
*
|
|
186
|
+
* try {
|
|
187
|
+
* const result = await execute(persistence, workflow, resolver);
|
|
188
|
+
* // Workflow completed — `result.fidelity` has the extraction stats.
|
|
189
|
+
* } catch (e) {
|
|
190
|
+
* if (e instanceof Suspend) {
|
|
191
|
+
* // Workflow paused on a prompt — `e.value` is the `Prompt` payload,
|
|
192
|
+
* // `e.id` is the suspended call's id. Persist-and-return so the
|
|
193
|
+
* // next invocation resumes from this point.
|
|
194
|
+
* } else {
|
|
195
|
+
* // Real failure — persistence has already been flagged "failed".
|
|
196
|
+
* throw e;
|
|
197
|
+
* }
|
|
198
|
+
* }
|
|
199
|
+
*/
|
|
200
|
+
export async function execute(persistence, workflow, resolver) {
|
|
201
|
+
const state = persistence.load();
|
|
202
|
+
if (!state) {
|
|
203
|
+
persistence.initialize();
|
|
204
|
+
}
|
|
205
|
+
else if (state.status === "completed") {
|
|
206
|
+
return { fidelity: EMPTY_FIDELITY };
|
|
207
|
+
}
|
|
208
|
+
else if (state.status !== "running") {
|
|
209
|
+
persistence.setStatus("running");
|
|
210
|
+
}
|
|
211
|
+
const ctx = new WorkflowContext(persistence, resolver);
|
|
212
|
+
try {
|
|
213
|
+
await workflow(ctx);
|
|
214
|
+
persistence.setStatus("completed");
|
|
215
|
+
return { fidelity: ctx.fidelityResult() };
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
if (e instanceof Suspend)
|
|
219
|
+
throw e;
|
|
220
|
+
persistence.setStatus("failed");
|
|
221
|
+
throw e;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=workflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow.js","sourceRoot":"","sources":["../../src/durable/workflow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review classification — LLM call that classifies the respondent's reaction
|
|
3
|
+
* to a presented item list (confirmed / removed / new items). Schema shape is
|
|
4
|
+
* phase-specific and passed by the caller.
|
|
5
|
+
*
|
|
6
|
+
* The base confirmation schema (`ConfirmClassifySchema` with `targetId`) and
|
|
7
|
+
* `ctx.confirm` live in `phases/shared.ts` because the classification takes
|
|
8
|
+
* the RE `Phase` and `Artifacts` types.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
declare module "../durable/workflow.js" {
|
|
12
|
+
interface WorkflowContext {
|
|
13
|
+
review<S extends z.ZodObject<any>>(id: string, response: string, itemRef: string, artifactsContext: string, schema: S, ri?: number): Promise<z.infer<S>>;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=classification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classification.d.ts","sourceRoot":"","sources":["../../src/interview/classification.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,QAAQ,wBAAwB,CAAC;IACtC,UAAU,eAAe;QACvB,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;KAC1J;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review classification — LLM call that classifies the respondent's reaction
|
|
3
|
+
* to a presented item list (confirmed / removed / new items). Schema shape is
|
|
4
|
+
* phase-specific and passed by the caller.
|
|
5
|
+
*
|
|
6
|
+
* The base confirmation schema (`ConfirmClassifySchema` with `targetId`) and
|
|
7
|
+
* `ctx.confirm` live in `phases/shared.ts` because the classification takes
|
|
8
|
+
* the RE `Phase` and `Artifacts` types.
|
|
9
|
+
*/
|
|
10
|
+
import { WorkflowContext } from "../durable/index.js";
|
|
11
|
+
/** Classify the respondent's reaction to a presented item list (confirmed/removed/new items). */
|
|
12
|
+
WorkflowContext.prototype.review = async function (id, response, itemRef, artifactsContext, schema, ri) {
|
|
13
|
+
const fullId = ri !== undefined ? `${id}-r${ri}` : id;
|
|
14
|
+
return this.extract({
|
|
15
|
+
id: fullId,
|
|
16
|
+
response,
|
|
17
|
+
artifactsContext,
|
|
18
|
+
schema,
|
|
19
|
+
guidance: `The respondent reacted to the presented list.\n${itemRef ? `Item IDs: ${itemRef}` : "No items existed yet."}`,
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
if (typeof WorkflowContext.prototype.review !== "function") {
|
|
23
|
+
throw new Error("interview/classification.ts self-check failed: WorkflowContext.prototype.review is not a function.");
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=classification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classification.js","sourceRoot":"","sources":["../../src/interview/classification.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAQtD,iGAAiG;AACjG,eAAe,CAAC,SAAS,CAAC,MAAM,GAAG,KAAK,WAAW,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAG;IACrG,MAAM,MAAM,GAAG,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,OAAO,IAAI,CAAC,OAAO,CAAC;QAClB,EAAE,EAAE,MAAM;QACV,QAAQ;QACR,gBAAgB;QAChB,MAAM;QACN,QAAQ,EAAE,kDAAkD,OAAO,CAAC,CAAC,CAAC,aAAa,OAAO,EAAE,CAAC,CAAC,CAAC,uBAAuB,EAAE;KACzH,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,IAAI,OAAQ,eAAe,CAAC,SAAgD,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;IACnG,MAAM,IAAI,KAAK,CAAC,oGAAoG,CAAC,CAAC;AACxH,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Question composition — LLM call that produces an interview question with
|
|
3
|
+
* optional suggested answers, preceded by the composition quality preamble.
|
|
4
|
+
*/
|
|
5
|
+
import type { ComposeParams } from "./describe.js";
|
|
6
|
+
import type { QuestionContext } from "./prompt.js";
|
|
7
|
+
declare module "../durable/workflow.js" {
|
|
8
|
+
interface WorkflowContext {
|
|
9
|
+
compose(step: ComposeParams): Promise<QuestionContext>;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=composition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../../src/interview/composition.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,QAAQ,wBAAwB,CAAC;IACtC,UAAU,eAAe;QACvB,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;KACxD;CACF"}
|