@ramplab/generator 0.1.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -0
  3. package/dist/agentSdkRunner.d.ts +64 -0
  4. package/dist/agentSdkRunner.d.ts.map +1 -0
  5. package/dist/agentSdkRunner.js +158 -0
  6. package/dist/agentSdkRunner.js.map +1 -0
  7. package/dist/assembleStage.d.ts +41 -0
  8. package/dist/assembleStage.d.ts.map +1 -0
  9. package/dist/assembleStage.js +33 -0
  10. package/dist/assembleStage.js.map +1 -0
  11. package/dist/authorStage.d.ts +123 -0
  12. package/dist/authorStage.d.ts.map +1 -0
  13. package/dist/authorStage.js +284 -0
  14. package/dist/authorStage.js.map +1 -0
  15. package/dist/cloneRepo.d.ts +72 -0
  16. package/dist/cloneRepo.d.ts.map +1 -0
  17. package/dist/cloneRepo.js +104 -0
  18. package/dist/cloneRepo.js.map +1 -0
  19. package/dist/flagship.d.ts +62 -0
  20. package/dist/flagship.d.ts.map +1 -0
  21. package/dist/flagship.js +216 -0
  22. package/dist/flagship.js.map +1 -0
  23. package/dist/generateLab.d.ts +151 -0
  24. package/dist/generateLab.d.ts.map +1 -0
  25. package/dist/generateLab.js +291 -0
  26. package/dist/generateLab.js.map +1 -0
  27. package/dist/index.d.ts +22 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +21 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/intake.d.ts +31 -0
  32. package/dist/intake.d.ts.map +1 -0
  33. package/dist/intake.js +45 -0
  34. package/dist/intake.js.map +1 -0
  35. package/dist/live.d.ts +2 -0
  36. package/dist/live.d.ts.map +1 -0
  37. package/dist/live.js +213 -0
  38. package/dist/live.js.map +1 -0
  39. package/dist/mapStage.d.ts +84 -0
  40. package/dist/mapStage.d.ts.map +1 -0
  41. package/dist/mapStage.js +241 -0
  42. package/dist/mapStage.js.map +1 -0
  43. package/dist/pass1.d.ts +85 -0
  44. package/dist/pass1.d.ts.map +1 -0
  45. package/dist/pass1.js +81 -0
  46. package/dist/pass1.js.map +1 -0
  47. package/dist/pipeline.d.ts +73 -0
  48. package/dist/pipeline.d.ts.map +1 -0
  49. package/dist/pipeline.js +70 -0
  50. package/dist/pipeline.js.map +1 -0
  51. package/dist/planStage.d.ts +141 -0
  52. package/dist/planStage.d.ts.map +1 -0
  53. package/dist/planStage.js +275 -0
  54. package/dist/planStage.js.map +1 -0
  55. package/dist/progress.d.ts +45 -0
  56. package/dist/progress.d.ts.map +1 -0
  57. package/dist/progress.js +2 -0
  58. package/dist/progress.js.map +1 -0
  59. package/dist/repoCommit.d.ts +41 -0
  60. package/dist/repoCommit.d.ts.map +1 -0
  61. package/dist/repoCommit.js +84 -0
  62. package/dist/repoCommit.js.map +1 -0
  63. package/dist/repoSize.d.ts +23 -0
  64. package/dist/repoSize.d.ts.map +1 -0
  65. package/dist/repoSize.js +74 -0
  66. package/dist/repoSize.js.map +1 -0
  67. package/dist/resolveAnchors.d.ts +142 -0
  68. package/dist/resolveAnchors.d.ts.map +1 -0
  69. package/dist/resolveAnchors.js +242 -0
  70. package/dist/resolveAnchors.js.map +1 -0
  71. package/dist/verifyStage.d.ts +150 -0
  72. package/dist/verifyStage.d.ts.map +1 -0
  73. package/dist/verifyStage.js +461 -0
  74. package/dist/verifyStage.js.map +1 -0
  75. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RampLab
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,23 @@
1
+ # @ramplab/generator
2
+
3
+ The RampLab pipeline: it reads a repository, writes an edition of it, and
4
+ mechanically resolves every code anchor against the source so nothing
5
+ unverifiable survives.
6
+
7
+ ```sh
8
+ npm install @ramplab/generator
9
+ ```
10
+
11
+ Most people want [`@ramplab/cli`](https://www.npmjs.com/package/@ramplab/cli)
12
+ instead, which wraps this with a command line, credential handling and
13
+ progress reporting. Use this directly if you are embedding the press in
14
+ something of your own.
15
+
16
+ `resolveAnchors(spec, repoDir)` is the part worth knowing about even if you
17
+ never press anything: it is pure over (spec, repository directory), checks that
18
+ every anchored file, line range and symbol really exists, and fingerprints the
19
+ region it resolved. Grounding is enforced structurally rather than promised.
20
+
21
+ ## Licence
22
+
23
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,64 @@
1
+ import type { ModelRunner } from './pipeline.js';
2
+ /**
3
+ * Which credential a live run will spend (founder, 2026-07-25). Populating
4
+ * the library used to mean an API key and a metered bill — around $17-21 a
5
+ * pressing at recent Caddy sizes. The SDK's CLI will just as happily use the
6
+ * Claude Code login, which spends session allowance instead of API credit, so
7
+ * both are allowed and the run says out loud which one it took.
8
+ */
9
+ export type AuthMode = 'api-key' | 'claude-code';
10
+ export interface AuthDescription {
11
+ mode: AuthMode;
12
+ /** One line for the run banner. */
13
+ label: string;
14
+ /**
15
+ * True when the run bills the Anthropic API account — i.e. when a reported
16
+ * `costUsd` is real money. False on the Claude Code credential, where the
17
+ * SDK's cost figure is a token-priced estimate nobody is invoiced for.
18
+ */
19
+ billedToApiAccount: boolean;
20
+ }
21
+ /**
22
+ * Report the credential the SDK will resolve, without spending anything. A
23
+ * non-empty `ANTHROPIC_API_KEY` wins because that is the CLI's own precedence;
24
+ * everything else falls through to the Claude Code login (which may still not
25
+ * exist — that surfaces as an SDK auth error on the first stage, not here).
26
+ */
27
+ export declare function describeAuth(env?: NodeJS.ProcessEnv): AuthDescription;
28
+ export interface AgentSdkRunnerOptions {
29
+ /**
30
+ * Cap on agentic turns per stage call — a cost guard, not a tuning knob.
31
+ * @default 50
32
+ */
33
+ maxTurns?: number;
34
+ /**
35
+ * Abort a stage that has gone completely silent for this long. A working
36
+ * session streams messages continuously; one that stops yielding has
37
+ * stalled on a network read and will never resume on its own.
38
+ * @default 15 minutes
39
+ */
40
+ stallTimeoutMs?: number;
41
+ }
42
+ /** 15 minutes: far longer than a healthy turn, far shorter than a hang. */
43
+ export declare const DEFAULT_STALL_TIMEOUT_MS: number;
44
+ export interface StallWatchdog {
45
+ /** (Re)start the countdown — call on every sign of life. */
46
+ arm(): void;
47
+ /** Stop the countdown for good. */
48
+ disarm(): void;
49
+ /** True once the countdown expired and `onStall` fired. */
50
+ get stalled(): boolean;
51
+ }
52
+ /**
53
+ * A dead-man's switch for a streaming session (founder, 2026-07-26).
54
+ *
55
+ * A Caddy eval sat in pass-1 authoring for 5h24m having burned 16 seconds of
56
+ * CPU: the SDK session stopped yielding and `for await` waited forever, with
57
+ * `maxTurns` no help because a turn cap is not a clock. Nothing downstream
58
+ * had a wall-clock guard either, so the whole run hung until it was killed by
59
+ * hand. This watchdog is the missing clock — idle-based rather than total, so
60
+ * a legitimately long stage is never cut off, only a silent one.
61
+ */
62
+ export declare function createStallWatchdog(timeoutMs: number, onStall: () => void): StallWatchdog;
63
+ export declare function createAgentSdkRunner(options?: AgentSdkRunnerOptions): ModelRunner;
64
+ //# sourceMappingURL=agentSdkRunner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentSdkRunner.d.ts","sourceRoot":"","sources":["../src/agentSdkRunner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA+B,MAAM,eAAe,CAAC;AAgC9E;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAC;AAEjD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,QAAQ,CAAC;IACf,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,eAAe,CAgBlF;AAED,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,2EAA2E;AAC3E,eAAO,MAAM,wBAAwB,QAAiB,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,GAAG,IAAI,IAAI,CAAC;IACZ,mCAAmC;IACnC,MAAM,IAAI,IAAI,CAAC;IACf,2DAA2D;IAC3D,IAAI,OAAO,IAAI,OAAO,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,aAAa,CAsBzF;AAWD,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,WAAW,CAqFrF"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The live `ModelRunner`: runs a stage as a Claude Agent SDK session over
3
+ * the repository directory with read-only tools.
4
+ *
5
+ * CI never touches this module's runtime path — tests inject fakes at the
6
+ * `ModelRunner` seam — so the SDK import is lazy: `@ramplab/generator` can
7
+ * be loaded (and everything else tested) without the SDK spawning anything.
8
+ *
9
+ * Auth: the SDK spawns the Claude Code CLI, which resolves credentials the
10
+ * way Claude Code itself does — `ANTHROPIC_API_KEY` first, then
11
+ * `CLAUDE_CODE_OAUTH_TOKEN`, then the stored Claude Code login. Both paths
12
+ * generate identical labs; they differ in who pays. See {@link describeAuth},
13
+ * which the live entry points print so a run always says which one it is on.
14
+ */
15
+ /** The agent may look, not touch: exploration tools only. */
16
+ const READ_ONLY_TOOLS = ['Read', 'Glob', 'Grep'];
17
+ /** Everything with side effects or network reach is denied outright. */
18
+ const DENIED_TOOLS = [
19
+ 'Bash',
20
+ 'Write',
21
+ 'Edit',
22
+ 'NotebookEdit',
23
+ 'WebFetch',
24
+ 'WebSearch',
25
+ 'Task',
26
+ 'TodoWrite',
27
+ ];
28
+ /**
29
+ * Report the credential the SDK will resolve, without spending anything. A
30
+ * non-empty `ANTHROPIC_API_KEY` wins because that is the CLI's own precedence;
31
+ * everything else falls through to the Claude Code login (which may still not
32
+ * exist — that surfaces as an SDK auth error on the first stage, not here).
33
+ */
34
+ export function describeAuth(env = process.env) {
35
+ const apiKey = env['ANTHROPIC_API_KEY'];
36
+ if (apiKey !== undefined && apiKey.length > 0) {
37
+ return {
38
+ mode: 'api-key',
39
+ label: 'ANTHROPIC_API_KEY — billed to the Anthropic API account',
40
+ billedToApiAccount: true,
41
+ };
42
+ }
43
+ return {
44
+ mode: 'claude-code',
45
+ label: 'Claude Code credential (no ANTHROPIC_API_KEY set) — spends session ' +
46
+ 'allowance, not API credit; reported costs are estimates, not charges',
47
+ billedToApiAccount: false,
48
+ };
49
+ }
50
+ /** 15 minutes: far longer than a healthy turn, far shorter than a hang. */
51
+ export const DEFAULT_STALL_TIMEOUT_MS = 15 * 60 * 1000;
52
+ /**
53
+ * A dead-man's switch for a streaming session (founder, 2026-07-26).
54
+ *
55
+ * A Caddy eval sat in pass-1 authoring for 5h24m having burned 16 seconds of
56
+ * CPU: the SDK session stopped yielding and `for await` waited forever, with
57
+ * `maxTurns` no help because a turn cap is not a clock. Nothing downstream
58
+ * had a wall-clock guard either, so the whole run hung until it was killed by
59
+ * hand. This watchdog is the missing clock — idle-based rather than total, so
60
+ * a legitimately long stage is never cut off, only a silent one.
61
+ */
62
+ export function createStallWatchdog(timeoutMs, onStall) {
63
+ let timer;
64
+ let stalled = false;
65
+ return {
66
+ arm() {
67
+ if (stalled)
68
+ return;
69
+ if (timer !== undefined)
70
+ clearTimeout(timer);
71
+ timer = setTimeout(() => {
72
+ stalled = true;
73
+ onStall();
74
+ }, timeoutMs);
75
+ // Never hold the process open on the watchdog's account.
76
+ timer.unref?.();
77
+ },
78
+ disarm() {
79
+ if (timer !== undefined)
80
+ clearTimeout(timer);
81
+ timer = undefined;
82
+ },
83
+ get stalled() {
84
+ return stalled;
85
+ },
86
+ };
87
+ }
88
+ export function createAgentSdkRunner(options = {}) {
89
+ const maxTurns = options.maxTurns ?? 50;
90
+ const stallTimeoutMs = options.stallTimeoutMs ?? DEFAULT_STALL_TIMEOUT_MS;
91
+ return {
92
+ async runStage(request) {
93
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
94
+ // The CLI's dying words. A bare "exited with code 1" cost a day of
95
+ // guessing (2026-07-13); keep the tail and attach it to any failure.
96
+ let stderrTail = '';
97
+ const withStderr = (message) => stderrTail.length > 0 ? `${message}\nCLI stderr (tail):\n${stderrTail.trim()}` : message;
98
+ const abortController = new AbortController();
99
+ const watchdog = createStallWatchdog(stallTimeoutMs, () => abortController.abort());
100
+ const session = query({
101
+ prompt: request.prompt,
102
+ options: {
103
+ model: request.model,
104
+ cwd: request.repoDir,
105
+ allowedTools: READ_ONLY_TOOLS,
106
+ disallowedTools: DENIED_TOOLS,
107
+ permissionMode: 'bypassPermissions',
108
+ // Zero infra assumptions: never load user/project settings.
109
+ settingSources: [],
110
+ maxTurns,
111
+ abortController,
112
+ stderr: (data) => {
113
+ stderrTail = `${stderrTail}${data}`.slice(-2000);
114
+ },
115
+ },
116
+ });
117
+ const stalledError = () => new Error(withStderr(`Agent SDK session for stage "${request.stage}" produced no output for ` +
118
+ `${Math.round(stallTimeoutMs / 60000)} minute(s) and was aborted. The ` +
119
+ `session had stalled, not slowed: raise stallTimeoutMs only if a healthy ` +
120
+ `stage genuinely goes silent that long.`));
121
+ let result;
122
+ try {
123
+ watchdog.arm();
124
+ for await (const message of session) {
125
+ watchdog.arm(); // any message is a sign of life
126
+ const candidate = message;
127
+ if (candidate.type === 'result') {
128
+ result = candidate;
129
+ }
130
+ }
131
+ }
132
+ catch (cause) {
133
+ // An abort we caused reads as a generic cancellation; say what it was.
134
+ if (watchdog.stalled)
135
+ throw stalledError();
136
+ throw new Error(withStderr(cause.message), { cause });
137
+ }
138
+ finally {
139
+ watchdog.disarm();
140
+ }
141
+ // A stall can also surface as a clean end-of-iteration after the abort.
142
+ if (watchdog.stalled)
143
+ throw stalledError();
144
+ if (result === undefined) {
145
+ throw new Error(withStderr(`Agent SDK session for stage "${request.stage}" ended without a result message.`));
146
+ }
147
+ if (result.is_error === true || result.subtype !== 'success') {
148
+ throw new Error(withStderr(`Agent SDK session for stage "${request.stage}" failed` +
149
+ `${result.subtype !== undefined ? ` (${result.subtype})` : ''}.`));
150
+ }
151
+ return {
152
+ output: result.result ?? '',
153
+ ...(result.total_cost_usd !== undefined ? { costUsd: result.total_cost_usd } : {}),
154
+ };
155
+ },
156
+ };
157
+ }
158
+ //# sourceMappingURL=agentSdkRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentSdkRunner.js","sourceRoot":"","sources":["../src/agentSdkRunner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { type LabModule, type LabSpec } from '@ramplab/spec';
2
+ import { type ResolutionPolicy, type ResolutionReport } from './resolveAnchors.js';
3
+ /**
4
+ * The assemble stage (PLAN.md §4): merge the map stage's overview module and
5
+ * the authored modules — in plan order — into one lab spec, revalidate the
6
+ * whole thing against `@ramplab/spec`, and run anchor resolution per policy.
7
+ *
8
+ * Deliberately **mechanical**: no model call. Every creative decision was
9
+ * made upstream (map named the lab, plan ordered the modules, authors wrote
10
+ * the content); assembly only merges, validates, and grounds. Module ids are
11
+ * already stable — `repo-overview` from the map stage, plan ids for authored
12
+ * modules — so overlay entries and learner progress survive regeneration.
13
+ */
14
+ export interface AssembleOptions {
15
+ /** @default 'drop' */
16
+ policy?: ResolutionPolicy;
17
+ /**
18
+ * Id of the authored module that opens the lab (issue #18, iteration 2):
19
+ * concrete-before-abstract applies to the curriculum too, so the flagship
20
+ * trace goes first and the map's overview module follows as the zoom-out.
21
+ * Undefined or unmatched keeps the map's modules first.
22
+ */
23
+ leadModuleId?: string;
24
+ }
25
+ export interface AssembleResult {
26
+ /** The merged, validated, anchor-resolved lab spec. */
27
+ spec: LabSpec;
28
+ /** Full anchor-resolution report over the merged spec. */
29
+ report: ResolutionReport;
30
+ }
31
+ /**
32
+ * Merge `authoredModules` (already in plan order) with the map spec's own
33
+ * modules and revalidate. The module named by `leadModuleId` — the flagship
34
+ * trace — is pulled to the front so the learner meets walked code before the
35
+ * map's architectural overview; the remaining authored modules keep plan
36
+ * order after it. Throws `LabSpecParseError` if the merge violates the spec
37
+ * schema — e.g. a duplicate module id, which upstream stages are built to
38
+ * prevent.
39
+ */
40
+ export declare function assembleLab(mapSpec: LabSpec, authoredModules: readonly LabModule[], repoDir: string, options?: AssembleOptions): AssembleResult;
41
+ //# sourceMappingURL=assembleStage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assembleStage.d.ts","sourceRoot":"","sources":["../src/assembleStage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACtB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,eAAe;IAC9B,sBAAsB;IACtB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,IAAI,EAAE,OAAO,CAAC;IACd,0DAA0D;IAC1D,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,SAAS,SAAS,EAAE,EACrC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,eAAoB,GAC5B,cAAc,CAsBhB"}
@@ -0,0 +1,33 @@
1
+ import { parseLabSpec } from '@ramplab/spec';
2
+ import { resolveAnchors, } from './resolveAnchors.js';
3
+ /**
4
+ * Merge `authoredModules` (already in plan order) with the map spec's own
5
+ * modules and revalidate. The module named by `leadModuleId` — the flagship
6
+ * trace — is pulled to the front so the learner meets walked code before the
7
+ * map's architectural overview; the remaining authored modules keep plan
8
+ * order after it. Throws `LabSpecParseError` if the merge violates the spec
9
+ * schema — e.g. a duplicate module id, which upstream stages are built to
10
+ * prevent.
11
+ */
12
+ export function assembleLab(mapSpec, authoredModules, repoDir, options = {}) {
13
+ const lead = authoredModules.filter((m) => m.id === options.leadModuleId);
14
+ const rest = authoredModules.filter((m) => m.id !== options.leadModuleId);
15
+ const candidate = {
16
+ schemaVersion: mapSpec.schemaVersion,
17
+ id: mapSpec.id,
18
+ title: mapSpec.title,
19
+ ...(mapSpec.repo !== undefined ? { repo: mapSpec.repo } : {}),
20
+ base: {
21
+ modules: [...lead, ...mapSpec.base.modules, ...rest],
22
+ },
23
+ overlay: mapSpec.overlay,
24
+ };
25
+ // Single validation path: a JSON round-trip plus parseLabSpec guarantees
26
+ // the merged spec is exactly what a consumer would load from disk.
27
+ const merged = parseLabSpec(JSON.parse(JSON.stringify(candidate)));
28
+ const { spec, report } = resolveAnchors(merged, repoDir, {
29
+ policy: options.policy ?? 'drop',
30
+ });
31
+ return { spec, report };
32
+ }
33
+ //# sourceMappingURL=assembleStage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assembleStage.js","sourceRoot":"","sources":["../src/assembleStage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAgC,MAAM,eAAe,CAAC;AAC3E,OAAO,EACL,cAAc,GAGf,MAAM,qBAAqB,CAAC;AAiC7B;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CACzB,OAAgB,EAChB,eAAqC,EACrC,OAAe,EACf,UAA2B,EAAE;IAE7B,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAC1E,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAC1E,MAAM,SAAS,GAAG;QAChB,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,IAAI,EAAE;YACJ,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;SACrD;QACD,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC;IAEF,yEAAyE;IACzE,mEAAmE;IACnE,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAEnE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE;QACvD,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,MAAM;KACjC,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,123 @@
1
+ import { type LabModule } from '@ramplab/spec';
2
+ import { type ModelRunner } from './pipeline.js';
3
+ import type { CurriculumPlan, PlannedModule } from './planStage.js';
4
+ /**
5
+ * The author stage (PLAN.md §4, pass 2): fan out one authoring agent per
6
+ * planned module, in parallel under a small concurrency cap. Each agent
7
+ * writes a full module — a mix of anchored widgets (callouts, code
8
+ * walkthroughs, code figures, diagrams, and a closing quiz) — validated
9
+ * against `@ramplab/spec`'s module schema with the same bounded retry the
10
+ * other stages use.
11
+ *
12
+ * Diagrams are budgeted, not left over (founder, 2026-07-25): the Edition v2
13
+ * threads gave every module several prose obligations inside an unchanged
14
+ * 4-to-8 widget budget, and the only widget family described as optional —
15
+ * the diagrams — is what got squeezed out. Live editions pressed on 2026-07-13
16
+ * carried 3-5 system maps; those pressed on 2026-07-14 carried 1-2, with the
17
+ * callout share climbing from 29% to 50% across the same five editions. The
18
+ * range now starts higher, the diagram catalogue sits above code-figure and
19
+ * quiz rather than last, and {@link SHAPE_CONTRACT} states the expectation
20
+ * outright. The escape hatch stays honest: no diagram where the code has no
21
+ * shape, and every node still carries its own anchor.
22
+ *
23
+ * Narrative continuity without serializing the fan-out (issue #18): each
24
+ * author prompt carries its module's **learner-state ledger slice** — what
25
+ * the learner has already seen (`assumes`, plus the titles of earlier
26
+ * modules it may reference), and what this module must add (`teaches`) —
27
+ * plus the **style contract** (concrete before abstract, jargon defined at
28
+ * first use, analogies anchored, mechanism-and-why over structural survey).
29
+ *
30
+ * Failure policy (deliberate, documented): a module that is still
31
+ * schema-invalid after its retries **fails the whole stage** with an error
32
+ * naming the module — there is no silent-skip mode. A planned module is a
33
+ * curriculum commitment; dropping it quietly would ship a lab with a hole
34
+ * the lead never sees. Other in-flight modules finish before the error is
35
+ * raised, so all failures are reported at once.
36
+ */
37
+ /** Default cap on concurrent authoring agents. */
38
+ export declare const DEFAULT_AUTHOR_CONCURRENCY = 4;
39
+ /**
40
+ * Widgets per authored chapter. The floor clears the module's mandatory prose
41
+ * — a quiz, the capability callout, the "when it breaks" warning, the threads
42
+ * the plan assigned — so a diagram has somewhere to go.
43
+ */
44
+ export declare const DEFAULT_WIDGET_RANGE: {
45
+ readonly min: 6;
46
+ readonly max: 10;
47
+ };
48
+ /** Widgets per chapter for repos deep enough to earn a 7-module plan. */
49
+ export declare const DEEP_WIDGET_RANGE: {
50
+ readonly min: 7;
51
+ readonly max: 12;
52
+ };
53
+ export interface AuthorStageOptions {
54
+ /**
55
+ * Widget count guidance interpolated into the prompt. Bigger repos earn
56
+ * deeper chapters, not only more of them. The floor has to clear the
57
+ * module's mandatory prose (a quiz, the capability callout, the "when it
58
+ * breaks" warning, the threads the plan assigned) before a diagram can fit
59
+ * at all — see the module comment. @default 6 to 10
60
+ */
61
+ widgetRange?: {
62
+ min: number;
63
+ max: number;
64
+ };
65
+ model: string;
66
+ plan: CurriculumPlan;
67
+ /**
68
+ * How many times to re-prompt a single module after schema-invalid output.
69
+ * @default 1
70
+ */
71
+ maxRetries?: number;
72
+ /**
73
+ * Max authoring agents in flight at once.
74
+ * @default 4
75
+ */
76
+ concurrency?: number;
77
+ /**
78
+ * Titles of chapters the reader meets BEFORE this plan's modules in the
79
+ * finished lab. Prepended to every module's ledger slice. Needed when a
80
+ * plan covers only part of the book — re-authoring the overview chapter
81
+ * passes the flagship trace's title here, so the overview is told to build
82
+ * on the trace instead of being mistaken for the opening chapter (which is
83
+ * what an empty ledger means).
84
+ */
85
+ precedingTitles?: readonly string[];
86
+ /**
87
+ * Called as soon as each module validates — while other modules are still
88
+ * in flight. This is the fan-out's progress seam: `generateLab` and pass 1
89
+ * forward it as `module-authored` events. Invoked synchronously and not
90
+ * awaited; completion order follows the fan-out, not plan order.
91
+ */
92
+ onModuleAuthored?: (authored: AuthoredModule) => void;
93
+ }
94
+ export interface AuthoredModule {
95
+ /** A valid lab module — not yet anchor-resolved. */
96
+ module: LabModule;
97
+ /** Attempts spent on this module (1 = no retry needed). */
98
+ attempts: number;
99
+ }
100
+ export interface AuthorStageResult {
101
+ /** One authored module per planned module, in plan order. */
102
+ modules: AuthoredModule[];
103
+ /** Summed cost across all modules and attempts, if the runner reports cost. */
104
+ costUsd: number | undefined;
105
+ }
106
+ /** One module's terminal failure inside the author stage. */
107
+ export interface AuthorModuleFailure {
108
+ moduleId: string;
109
+ attempts: number;
110
+ lastFailure: string;
111
+ }
112
+ /** Raised when at least one planned module never produced a valid module. */
113
+ export declare class AuthorStageError extends Error {
114
+ readonly failures: readonly AuthorModuleFailure[];
115
+ constructor(failures: readonly AuthorModuleFailure[]);
116
+ }
117
+ export declare function runAuthorStage(runner: ModelRunner, repoDir: string, options: AuthorStageOptions): Promise<AuthorStageResult>;
118
+ /** Exported for prompt-contract tests; not part of the stage's runtime API. */
119
+ export declare function buildAuthorPrompt(planned: PlannedModule, earlierTitles: readonly string[], range: {
120
+ min: number;
121
+ max: number;
122
+ }): string;
123
+ //# sourceMappingURL=authorStage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorStage.d.ts","sourceRoot":"","sources":["../src/authorStage.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAE7D,OAAO,EAAsB,KAAK,WAAW,EAAsB,MAAM,eAAe,CAAC;AACzF,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,kDAAkD;AAClD,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;CAA+B,CAAC;AAEjE,yEAAyE;AACzE,eAAO,MAAM,iBAAiB;;;CAA+B,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;CACvD;AAED,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,MAAM,EAAE,SAAS,CAAC;IAClB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,6DAA6D;IAC7D,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,+EAA+E;IAC/E,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,6DAA6D;AAC7D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,6EAA6E;AAC7E,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;gBAEtC,QAAQ,EAAE,SAAS,mBAAmB,EAAE;CAerD;AASD,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,iBAAiB,CAAC,CAkC5B;AA4KD,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,aAAa,EACtB,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,KAAK,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAClC,MAAM,CAuDR"}