@exaudeus/workrail 3.74.3 → 3.76.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 (32) hide show
  1. package/dist/console-ui/assets/index-DFZjlsUM.js +28 -0
  2. package/dist/console-ui/index.html +1 -1
  3. package/dist/coordinators/adaptive-pipeline.d.ts +8 -0
  4. package/dist/coordinators/context-assembly.d.ts +4 -0
  5. package/dist/coordinators/context-assembly.js +156 -0
  6. package/dist/coordinators/modes/full-pipeline.d.ts +1 -1
  7. package/dist/coordinators/modes/full-pipeline.js +140 -27
  8. package/dist/coordinators/modes/implement-shared.d.ts +3 -2
  9. package/dist/coordinators/modes/implement-shared.js +16 -6
  10. package/dist/coordinators/modes/implement.js +49 -3
  11. package/dist/coordinators/pipeline-run-context.d.ts +1811 -0
  12. package/dist/coordinators/pipeline-run-context.js +114 -0
  13. package/dist/infrastructure/storage/schema-validating-workflow-storage.js +25 -2
  14. package/dist/manifest.json +54 -30
  15. package/dist/trigger/coordinator-deps.js +131 -0
  16. package/dist/v2/durable-core/domain/artifact-contract-validator.js +99 -0
  17. package/dist/v2/durable-core/schemas/artifacts/discovery-handoff.d.ts +39 -0
  18. package/dist/v2/durable-core/schemas/artifacts/discovery-handoff.js +10 -1
  19. package/dist/v2/durable-core/schemas/artifacts/index.d.ts +2 -1
  20. package/dist/v2/durable-core/schemas/artifacts/index.js +12 -1
  21. package/dist/v2/durable-core/schemas/artifacts/phase-handoff.d.ts +89 -0
  22. package/dist/v2/durable-core/schemas/artifacts/phase-handoff.js +56 -0
  23. package/docs/authoring-v2.md +12 -0
  24. package/docs/ideas/backlog.md +409 -1
  25. package/package.json +1 -1
  26. package/workflows/coding-task-workflow-agentic.json +9 -6
  27. package/workflows/mr-review-workflow.agentic.v2.json +2 -2
  28. package/workflows/routines/tension-driven-design.json +12 -12
  29. package/workflows/workflow-for-workflows.json +5 -11
  30. package/workflows/wr.discovery.json +20 -17
  31. package/workflows/wr.shaping.json +7 -4
  32. package/dist/console-ui/assets/index-ByqIsoyt.js +0 -28
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>WorkRail Console</title>
7
- <script type="module" crossorigin src="/console/assets/index-ByqIsoyt.js"></script>
7
+ <script type="module" crossorigin src="/console/assets/index-DFZjlsUM.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/console/assets/index-DHrKiMCf.css">
9
9
  </head>
10
10
  <body>
@@ -1,4 +1,6 @@
1
+ import type { Result } from 'neverthrow';
1
2
  import type { CoordinatorDeps } from './pr-review.js';
3
+ import type { PipelineRunContext, PhaseRecord } from './pipeline-run-context.js';
2
4
  export declare const DISCOVERY_TIMEOUT_MS: number;
3
5
  export declare const SHAPING_TIMEOUT_MS: number;
4
6
  export declare const CODING_TIMEOUT_MS: number;
@@ -33,6 +35,12 @@ export interface AdaptiveCoordinatorDeps extends CoordinatorDeps {
33
35
  pollForPR(branchPattern: string, timeoutMs: number): Promise<string | null>;
34
36
  postToOutbox(message: string, metadata: Readonly<Record<string, unknown>>): Promise<void>;
35
37
  pollOutboxAck(requestId: string, timeoutMs: number): Promise<'acked' | 'timeout'>;
38
+ generateRunId(): string;
39
+ readActiveRunId(workspace: string): Promise<Result<string | null, string>>;
40
+ readPipelineContext(workspace: string, runId: string): Promise<Result<PipelineRunContext | null, string>>;
41
+ createPipelineContext(workspace: string, runId: string, goal: string, pipelineMode: PipelineRunContext['pipelineMode']): Promise<Result<void, string>>;
42
+ markPipelineRunComplete(workspace: string, runId: string): Promise<Result<void, string>>;
43
+ writePhaseRecord(workspace: string, runId: string, entry: PhaseRecord): Promise<Result<void, string>>;
36
44
  }
37
45
  export interface ModeExecutors {
38
46
  readonly runQuickReview: (deps: AdaptiveCoordinatorDeps, opts: AdaptivePipelineOpts, prNumbers: readonly number[], coordinatorStartMs: number) => Promise<PipelineOutcome>;
@@ -0,0 +1,4 @@
1
+ import type { ZodType, ZodTypeDef } from 'zod';
2
+ import type { PhaseHandoffArtifact } from '../v2/durable-core/schemas/artifacts/index.js';
3
+ export declare function extractPhaseArtifact<T>(artifacts: readonly unknown[], schema: ZodType<T, ZodTypeDef, unknown>, kindPredicate: (a: unknown) => boolean): T | null;
4
+ export declare function buildContextSummary(priorArtifacts: readonly PhaseHandoffArtifact[], targetPhase: 'shaping' | 'coding' | 'review' | 'fix'): string;
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractPhaseArtifact = extractPhaseArtifact;
4
+ exports.buildContextSummary = buildContextSummary;
5
+ const MAX_CONTEXT_BYTES = 8192;
6
+ function extractPhaseArtifact(artifacts, schema, kindPredicate) {
7
+ const candidates = artifacts.filter(kindPredicate);
8
+ if (candidates.length === 0)
9
+ return null;
10
+ const result = schema.safeParse(candidates[0]);
11
+ return result.success ? result.data : null;
12
+ }
13
+ function buildContextSummary(priorArtifacts, targetPhase) {
14
+ const discovery = priorArtifacts.find((a) => a.kind === 'wr.discovery_handoff') ?? null;
15
+ const shaping = priorArtifacts.find((a) => a.kind === 'wr.shaping_handoff') ?? null;
16
+ const coding = priorArtifacts.find((a) => a.kind === 'wr.coding_handoff') ?? null;
17
+ const sections = [];
18
+ switch (targetPhase) {
19
+ case 'shaping': {
20
+ if (discovery) {
21
+ if (discovery.implementationConstraints?.length) {
22
+ sections.push({ priority: 1, content: renderList('Implementation Constraints', discovery.implementationConstraints) });
23
+ }
24
+ sections.push({ priority: 1, content: `**Selected Direction:** ${discovery.selectedDirection}` });
25
+ if (discovery.keyInvariants.length) {
26
+ sections.push({ priority: 2, content: renderList('Key Invariants', discovery.keyInvariants) });
27
+ }
28
+ if (discovery.rejectedDirections?.length) {
29
+ sections.push({ priority: 2, content: renderRejectedDirections(discovery.rejectedDirections) });
30
+ }
31
+ if (discovery.keyCodebaseLocations?.length) {
32
+ sections.push({ priority: 3, content: renderCodebaseLocations(discovery.keyCodebaseLocations) });
33
+ }
34
+ }
35
+ break;
36
+ }
37
+ case 'coding': {
38
+ if (discovery) {
39
+ if (discovery.implementationConstraints?.length) {
40
+ sections.push({ priority: 1, content: renderList('Implementation Constraints', discovery.implementationConstraints) });
41
+ }
42
+ if (discovery.keyInvariants.length) {
43
+ sections.push({ priority: 2, content: renderList('Key Invariants', discovery.keyInvariants) });
44
+ }
45
+ if (discovery.keyCodebaseLocations?.length) {
46
+ sections.push({ priority: 3, content: renderCodebaseLocations(discovery.keyCodebaseLocations) });
47
+ }
48
+ }
49
+ if (shaping) {
50
+ sections.push({ priority: 1, content: `**Selected Shape:** ${shaping.selectedShape}\n**Appetite:** ${shaping.appetite}` });
51
+ if (shaping.keyConstraints.length) {
52
+ sections.push({ priority: 1, content: renderList('Key Constraints', shaping.keyConstraints) });
53
+ }
54
+ if (shaping.outOfScope.length) {
55
+ sections.push({ priority: 1, content: renderList('Out of Scope', shaping.outOfScope) });
56
+ }
57
+ if (shaping.rabbitHoles.length) {
58
+ sections.push({ priority: 2, content: renderList('Rabbit Holes', shaping.rabbitHoles) });
59
+ }
60
+ }
61
+ break;
62
+ }
63
+ case 'review': {
64
+ if (discovery) {
65
+ if (discovery.implementationConstraints?.length) {
66
+ sections.push({ priority: 1, content: renderList('Implementation Constraints', discovery.implementationConstraints) });
67
+ }
68
+ if (discovery.keyInvariants.length) {
69
+ sections.push({ priority: 2, content: renderList('Key Invariants', discovery.keyInvariants) });
70
+ }
71
+ }
72
+ if (shaping) {
73
+ if (shaping.validationChecklist.length) {
74
+ sections.push({ priority: 1, content: renderList('Validation Checklist (check each explicitly)', shaping.validationChecklist) });
75
+ }
76
+ if (shaping.keyConstraints.length) {
77
+ sections.push({ priority: 1, content: renderList('Key Constraints', shaping.keyConstraints) });
78
+ }
79
+ if (shaping.outOfScope.length) {
80
+ sections.push({ priority: 1, content: renderList('Out of Scope', shaping.outOfScope) });
81
+ }
82
+ }
83
+ if (coding) {
84
+ if (coding.keyDecisions.length) {
85
+ sections.push({ priority: 1, content: renderList('Coding Decisions (WHY)', coding.keyDecisions) });
86
+ }
87
+ if (coding.correctedAssumptions?.length) {
88
+ sections.push({ priority: 1, content: renderCorrectedAssumptions(coding.correctedAssumptions) });
89
+ }
90
+ if (coding.knownLimitations.length) {
91
+ sections.push({ priority: 2, content: renderList('Known Limitations', coding.knownLimitations) });
92
+ }
93
+ if (coding.filesChanged.length) {
94
+ sections.push({ priority: 3, content: renderList('Files Changed', coding.filesChanged) });
95
+ }
96
+ }
97
+ break;
98
+ }
99
+ case 'fix': {
100
+ if (shaping) {
101
+ if (shaping.validationChecklist.length) {
102
+ sections.push({ priority: 1, content: renderList('Validation Checklist', shaping.validationChecklist) });
103
+ }
104
+ if (shaping.keyConstraints.length) {
105
+ sections.push({ priority: 1, content: renderList('Key Constraints', shaping.keyConstraints) });
106
+ }
107
+ if (shaping.outOfScope.length) {
108
+ sections.push({ priority: 1, content: renderList('Out of Scope', shaping.outOfScope) });
109
+ }
110
+ }
111
+ if (coding) {
112
+ if (coding.keyDecisions.length) {
113
+ sections.push({ priority: 1, content: renderList('Coding Decisions (WHY)', coding.keyDecisions) });
114
+ }
115
+ if (coding.correctedAssumptions?.length) {
116
+ sections.push({ priority: 1, content: renderCorrectedAssumptions(coding.correctedAssumptions) });
117
+ }
118
+ if (coding.knownLimitations.length) {
119
+ sections.push({ priority: 2, content: renderList('Known Limitations', coding.knownLimitations) });
120
+ }
121
+ if (coding.filesChanged.length) {
122
+ sections.push({ priority: 3, content: renderList('Files Changed', coding.filesChanged) });
123
+ }
124
+ }
125
+ break;
126
+ }
127
+ }
128
+ if (sections.length === 0)
129
+ return '';
130
+ return buildBudgetedOutput(sections);
131
+ }
132
+ function renderList(heading, items) {
133
+ return `**${heading}:**\n${items.map((i) => `- ${i}`).join('\n')}`;
134
+ }
135
+ function renderRejectedDirections(directions) {
136
+ return `**Rejected Directions:**\n${directions.map((d) => `- ${d.direction} -- ${d.reason}`).join('\n')}`;
137
+ }
138
+ function renderCodebaseLocations(locations) {
139
+ return `**Key Codebase Locations:**\n${locations.map((l) => `- \`${l.path}\` -- ${l.relevance}`).join('\n')}`;
140
+ }
141
+ function renderCorrectedAssumptions(corrections) {
142
+ return `**Corrected Assumptions (prior phase was wrong about these):**\n${corrections.map((c) => `- Assumed: ${c.assumed}\n Actual: ${c.actual}`).join('\n')}`;
143
+ }
144
+ function buildBudgetedOutput(sections) {
145
+ const ordered = [...sections].sort((a, b) => a.priority - b.priority);
146
+ const included = [];
147
+ let bytesUsed = 0;
148
+ for (const section of ordered) {
149
+ const sectionBytes = Buffer.byteLength(section.content + '\n\n', 'utf8');
150
+ if (bytesUsed + sectionBytes <= MAX_CONTEXT_BYTES) {
151
+ included.push(section.content);
152
+ bytesUsed += sectionBytes;
153
+ }
154
+ }
155
+ return included.join('\n\n');
156
+ }
@@ -1,4 +1,4 @@
1
1
  import type { AdaptiveCoordinatorDeps, AdaptivePipelineOpts, PipelineOutcome } from '../adaptive-pipeline.js';
2
- import { type DiscoveryHandoffArtifactV1 } from '../../v2/durable-core/schemas/artifacts/discovery-handoff.js';
2
+ import { type DiscoveryHandoffArtifactV1 } from '../../v2/durable-core/schemas/artifacts/index.js';
3
3
  export declare function renderHandoff(artifact: DiscoveryHandoffArtifactV1): string;
4
4
  export declare function runFullPipeline(deps: AdaptiveCoordinatorDeps, opts: AdaptivePipelineOpts, coordinatorStartMs: number): Promise<PipelineOutcome>;
@@ -3,12 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderHandoff = renderHandoff;
4
4
  exports.runFullPipeline = runFullPipeline;
5
5
  const adaptive_pipeline_js_1 = require("../adaptive-pipeline.js");
6
- const discovery_handoff_js_1 = require("../../v2/durable-core/schemas/artifacts/discovery-handoff.js");
6
+ const index_js_1 = require("../../v2/durable-core/schemas/artifacts/index.js");
7
+ const context_assembly_js_1 = require("../context-assembly.js");
8
+ const pipeline_run_context_js_1 = require("../pipeline-run-context.js");
7
9
  const implement_shared_js_1 = require("./implement-shared.js");
8
10
  const implement_js_1 = require("./implement.js");
9
11
  const PR_POLL_TIMEOUT_MS = 5 * 60 * 1000;
10
12
  const UX_GATE_ACK_TIMEOUT_MS = 24 * 60 * 60 * 1000;
11
13
  const MIN_NOTES_LENGTH_FOR_FALLBACK = 50;
14
+ function extractPriorArtifactsFromContext(ctx) {
15
+ const artifacts = [];
16
+ if (ctx.phases.discovery?.result.kind === 'full')
17
+ artifacts.push(ctx.phases.discovery.result.artifact);
18
+ if (ctx.phases.shaping?.result.kind === 'full')
19
+ artifacts.push(ctx.phases.shaping.result.artifact);
20
+ if (ctx.phases.coding?.result.kind === 'full')
21
+ artifacts.push(ctx.phases.coding.result.artifact);
22
+ return artifacts;
23
+ }
12
24
  function renderHandoff(artifact) {
13
25
  const lines = [
14
26
  `## Discovery Handoff`,
@@ -30,9 +42,9 @@ function renderHandoff(artifact) {
30
42
  function readDiscoveryHandoffArtifact(artifacts, sessionHandle, stderrFn) {
31
43
  const handlePrefix = sessionHandle.slice(0, 16);
32
44
  for (const raw of artifacts) {
33
- if (!(0, discovery_handoff_js_1.isDiscoveryHandoffArtifact)(raw))
45
+ if (!(0, index_js_1.isDiscoveryHandoffArtifact)(raw))
34
46
  continue;
35
- const result = discovery_handoff_js_1.DiscoveryHandoffArtifactV1Schema.safeParse(raw);
47
+ const result = index_js_1.DiscoveryHandoffArtifactV1Schema.safeParse(raw);
36
48
  if (!result.success) {
37
49
  const issues = result.error.issues
38
50
  .map((i) => `${i.path.join('.')}: ${i.message}`)
@@ -46,13 +58,37 @@ function readDiscoveryHandoffArtifact(artifacts, sessionHandle, stderrFn) {
46
58
  }
47
59
  async function runFullPipeline(deps, opts, coordinatorStartMs) {
48
60
  deps.stderr(`[full-pipeline] Starting FULL pipeline for workspace=${opts.workspace}`);
61
+ const activeRunResult = await deps.readActiveRunId(opts.workspace);
62
+ const priorRunId = activeRunResult.isOk() ? activeRunResult.value : null;
63
+ const runId = priorRunId ?? deps.generateRunId();
64
+ let initialPriorArtifacts = [];
65
+ if (priorRunId) {
66
+ const existingCtx = await deps.readPipelineContext(opts.workspace, priorRunId);
67
+ if (existingCtx.isOk() && existingCtx.value !== null) {
68
+ initialPriorArtifacts = extractPriorArtifactsFromContext(existingCtx.value);
69
+ }
70
+ }
71
+ deps.stderr(priorRunId
72
+ ? `[full-pipeline] Resuming prior run ${priorRunId} with ${initialPriorArtifacts.length} artifact(s)`
73
+ : `[full-pipeline] Starting new run ${runId}`);
74
+ if (!priorRunId) {
75
+ const initResult = await deps.createPipelineContext(opts.workspace, runId, opts.goal, 'FULL');
76
+ if (initResult.isErr()) {
77
+ deps.stderr(`[full-pipeline] FATAL: failed to initialize PipelineRunContext: ${initResult.error}`);
78
+ return { kind: 'escalated', escalationReason: { phase: 'init', reason: `PipelineRunContext initialization failed: ${initResult.error}` } };
79
+ }
80
+ }
49
81
  const pitchPath = opts.workspace + '/.workrail/current-pitch.md';
50
82
  const archiveDir = opts.workspace + '/.workrail/used-pitches';
51
83
  const archiveTimestamp = deps.nowIso().replace(/[:.]/g, '-');
52
84
  const archivePath = archiveDir + '/pitch-' + archiveTimestamp + '.md';
53
85
  let outcome;
54
86
  try {
55
- outcome = await runFullPipelineCore(deps, opts, coordinatorStartMs);
87
+ outcome = await runFullPipelineCore(deps, opts, coordinatorStartMs, runId, initialPriorArtifacts);
88
+ const markResult = await deps.markPipelineRunComplete(opts.workspace, runId);
89
+ if (markResult.isErr()) {
90
+ deps.stderr(`[WARN full-pipeline] markPipelineRunComplete failed -- next run may resume this one: ${markResult.error}`);
91
+ }
56
92
  }
57
93
  finally {
58
94
  try {
@@ -66,7 +102,8 @@ async function runFullPipeline(deps, opts, coordinatorStartMs) {
66
102
  }
67
103
  return outcome;
68
104
  }
69
- async function runFullPipelineCore(deps, opts, coordinatorStartMs) {
105
+ async function runFullPipelineCore(deps, opts, coordinatorStartMs, runId, initialPriorArtifacts) {
106
+ let priorArtifacts = initialPriorArtifacts;
70
107
  const discoveryCutoff = (0, adaptive_pipeline_js_1.checkSpawnCutoff)(coordinatorStartMs, deps.now(), 'discovery');
71
108
  if (discoveryCutoff)
72
109
  return discoveryCutoff;
@@ -107,31 +144,37 @@ async function runFullPipelineCore(deps, opts, coordinatorStartMs) {
107
144
  deps.stderr(`[coordinator] getAgentResult failed: ${msg}`);
108
145
  return {
109
146
  kind: 'escalated',
110
- escalationReason: { phase: 'review', reason: `getAgentResult threw: ${msg}` },
147
+ escalationReason: { phase: 'discovery', reason: `getAgentResult threw: ${msg}` },
111
148
  };
112
149
  }
113
- const handoffArtifact = readDiscoveryHandoffArtifact(discoveryAgentResult.artifacts, discoveryHandle, deps.stderr);
114
- let shapingContext;
115
- if (handoffArtifact !== null) {
116
- deps.stderr(`[full-pipeline] Discovery handoff artifact found -- injecting structured context`);
117
- shapingContext = {
118
- selectedDirection: handoffArtifact.selectedDirection,
119
- designDocPath: handoffArtifact.designDocPath,
120
- assembledContextSummary: renderHandoff(handoffArtifact),
121
- };
150
+ const discoveryArtifact = (0, context_assembly_js_1.extractPhaseArtifact)(discoveryAgentResult.artifacts, index_js_1.DiscoveryHandoffArtifactV1Schema, index_js_1.isDiscoveryHandoffArtifact);
151
+ const discoveryPhaseResult = (0, pipeline_run_context_js_1.buildPhaseResult)(discoveryArtifact, discoveryAgentResult.recapMarkdown);
152
+ priorArtifacts = discoveryArtifact !== null ? [...priorArtifacts, discoveryArtifact] : priorArtifacts;
153
+ const discoveryWriteResult = await deps.writePhaseRecord(opts.workspace, runId, {
154
+ phase: 'discovery',
155
+ record: { completedAt: deps.nowIso(), sessionHandle: discoveryHandle, result: discoveryPhaseResult },
156
+ });
157
+ if (discoveryWriteResult.isErr()) {
158
+ deps.stderr(`[full-pipeline] FATAL: failed to persist discovery phase record: ${discoveryWriteResult.error}`);
159
+ return { kind: 'escalated', escalationReason: { phase: 'discovery', reason: `context persistence failed: ${discoveryWriteResult.error}` } };
122
160
  }
123
- else {
124
- const notes = discoveryAgentResult.recapMarkdown;
125
- if (notes !== null && notes.trim().length > MIN_NOTES_LENGTH_FOR_FALLBACK) {
126
- deps.stderr(`[full-pipeline] No handoff artifact -- using lastStepNotes as context (length=${notes.trim().length})`);
127
- shapingContext = { assembledContextSummary: notes.trim() };
128
- }
129
- else {
130
- const reason = notes === null ? 'null' : `length=${notes.trim().length} <= ${MIN_NOTES_LENGTH_FOR_FALLBACK}`;
131
- deps.stderr(`[full-pipeline] No handoff artifact and notes too short (${reason}) -- proceeding without context`);
132
- shapingContext = {};
133
- }
161
+ deps.stderr(`[full-pipeline] Discovery phase result: ${discoveryPhaseResult.kind}`);
162
+ if (discoveryPhaseResult.kind === 'fallback') {
163
+ return {
164
+ kind: 'escalated',
165
+ escalationReason: {
166
+ phase: 'discovery',
167
+ reason: 'discovery session produced no usable output (no artifact and no meaningful notes). Starting shaping blind would produce low-quality work. Fix the discovery session and resume.',
168
+ },
169
+ };
134
170
  }
171
+ const shapingContextSummary = (0, context_assembly_js_1.buildContextSummary)(priorArtifacts, 'shaping');
172
+ const partialWarning = discoveryPhaseResult.kind === 'partial'
173
+ ? '\n\n**Note:** Discovery phase produced partial output only (no structured artifact). Context above is from session notes and may be incomplete.'
174
+ : '';
175
+ const shapingContext = (shapingContextSummary || partialWarning)
176
+ ? { assembledContextSummary: (shapingContextSummary + partialWarning).trim() }
177
+ : {};
135
178
  const shapingCutoff = (0, adaptive_pipeline_js_1.checkSpawnCutoff)(coordinatorStartMs, deps.now(), 'shaping');
136
179
  if (shapingCutoff)
137
180
  return shapingCutoff;
@@ -163,6 +206,36 @@ async function runFullPipelineCore(deps, opts, coordinatorStartMs) {
163
206
  };
164
207
  }
165
208
  deps.stderr(`[full-pipeline] Shaping session completed`);
209
+ let shapingAgentResult;
210
+ try {
211
+ shapingAgentResult = await deps.getAgentResult(shapingHandle);
212
+ }
213
+ catch (e) {
214
+ const msg = e instanceof Error ? e.message : String(e);
215
+ deps.stderr(`[coordinator] getAgentResult (shaping) failed: ${msg}`);
216
+ shapingAgentResult = { recapMarkdown: null, artifacts: [] };
217
+ }
218
+ const shapingArtifact = (0, context_assembly_js_1.extractPhaseArtifact)(shapingAgentResult.artifacts, index_js_1.ShapingHandoffArtifactV1Schema, index_js_1.isShapingHandoffArtifact);
219
+ const shapingPhaseResult = (0, pipeline_run_context_js_1.buildPhaseResult)(shapingArtifact, shapingAgentResult.recapMarkdown);
220
+ priorArtifacts = shapingArtifact !== null ? [...priorArtifacts, shapingArtifact] : priorArtifacts;
221
+ const shapingWriteResult = await deps.writePhaseRecord(opts.workspace, runId, {
222
+ phase: 'shaping',
223
+ record: { completedAt: deps.nowIso(), sessionHandle: shapingHandle, result: shapingPhaseResult },
224
+ });
225
+ if (shapingWriteResult.isErr()) {
226
+ deps.stderr(`[full-pipeline] FATAL: failed to persist shaping phase record: ${shapingWriteResult.error}`);
227
+ return { kind: 'escalated', escalationReason: { phase: 'shaping', reason: `context persistence failed: ${shapingWriteResult.error}` } };
228
+ }
229
+ deps.stderr(`[full-pipeline] Shaping phase result: ${shapingPhaseResult.kind}`);
230
+ if (shapingPhaseResult.kind === 'fallback') {
231
+ return {
232
+ kind: 'escalated',
233
+ escalationReason: {
234
+ phase: 'shaping',
235
+ reason: 'shaping session produced no usable output (no artifact and no meaningful notes). Starting coding blind would produce low-quality work. Fix the shaping session and resume.',
236
+ },
237
+ };
238
+ }
166
239
  if ((0, implement_js_1.touchesUI)(opts.goal)) {
167
240
  deps.stderr(`[full-pipeline] UX signals detected -- dispatching wr.ui-ux-design`);
168
241
  const uxCutoff = (0, adaptive_pipeline_js_1.checkSpawnCutoff)(coordinatorStartMs, deps.now(), 'ux-gate');
@@ -231,8 +304,18 @@ async function runFullPipelineCore(deps, opts, coordinatorStartMs) {
231
304
  if (codingCutoff)
232
305
  return codingCutoff;
233
306
  deps.stderr(`[full-pipeline] Spawning wr.coding-task`);
307
+ const codingContextSummary = (0, context_assembly_js_1.buildContextSummary)(priorArtifacts, 'coding');
308
+ const shapingPartialWarning = shapingPhaseResult.kind === 'partial'
309
+ ? '\n\n**Note:** Shaping phase produced partial output only (no structured artifact). Context above is from session notes and may be incomplete.'
310
+ : '';
311
+ const discoveryPartialWarning = discoveryPhaseResult.kind === 'partial'
312
+ ? '\n\n**Note:** Discovery phase produced partial output only (no structured artifact). Some upstream context may be missing.'
313
+ : '';
314
+ const codingWarnings = discoveryPartialWarning + shapingPartialWarning;
315
+ const codingFullContext = (codingContextSummary + codingWarnings).trim();
234
316
  const codingSpawnResult = await deps.spawnSession('wr.coding-task', opts.goal, opts.workspace, {
235
317
  pitchPath: opts.workspace + '/.workrail/current-pitch.md',
318
+ ...(codingFullContext ? { assembledContextSummary: codingFullContext } : {}),
236
319
  }, { maxSessionMinutes: Math.ceil(adaptive_pipeline_js_1.CODING_TIMEOUT_MS / 60000) });
237
320
  if (codingSpawnResult.kind === 'err') {
238
321
  return {
@@ -260,6 +343,36 @@ async function runFullPipelineCore(deps, opts, coordinatorStartMs) {
260
343
  };
261
344
  }
262
345
  deps.stderr(`[full-pipeline] Coding session completed`);
346
+ let codingAgentResult;
347
+ try {
348
+ codingAgentResult = await deps.getAgentResult(codingHandle);
349
+ }
350
+ catch (e) {
351
+ const msg = e instanceof Error ? e.message : String(e);
352
+ deps.stderr(`[coordinator] getAgentResult (coding) failed: ${msg}`);
353
+ codingAgentResult = { recapMarkdown: null, artifacts: [] };
354
+ }
355
+ const codingArtifact = (0, context_assembly_js_1.extractPhaseArtifact)(codingAgentResult.artifacts, index_js_1.CodingHandoffArtifactV1Schema, index_js_1.isCodingHandoffArtifact);
356
+ const codingPhaseResult = (0, pipeline_run_context_js_1.buildPhaseResult)(codingArtifact, codingAgentResult.recapMarkdown);
357
+ priorArtifacts = codingArtifact !== null ? [...priorArtifacts, codingArtifact] : priorArtifacts;
358
+ const codingWriteResult = await deps.writePhaseRecord(opts.workspace, runId, {
359
+ phase: 'coding',
360
+ record: { completedAt: deps.nowIso(), sessionHandle: codingHandle, result: codingPhaseResult },
361
+ });
362
+ if (codingWriteResult.isErr()) {
363
+ deps.stderr(`[full-pipeline] FATAL: failed to persist coding phase record: ${codingWriteResult.error}`);
364
+ return { kind: 'escalated', escalationReason: { phase: 'coding', reason: `context persistence failed: ${codingWriteResult.error}` } };
365
+ }
366
+ deps.stderr(`[full-pipeline] Coding phase result: ${codingPhaseResult.kind}`);
367
+ if (codingPhaseResult.kind === 'fallback') {
368
+ return {
369
+ kind: 'escalated',
370
+ escalationReason: {
371
+ phase: 'coding',
372
+ reason: 'coding session produced no usable output (no artifact and no meaningful notes). Starting review blind would miss design-level issues. Fix the coding session and resume.',
373
+ },
374
+ };
375
+ }
263
376
  const branchPattern = `worktrain/${codingHandle.slice(0, 16)}`;
264
377
  deps.stderr(`[full-pipeline] Polling for PR on branch pattern: ${branchPattern}`);
265
378
  let prUrl;
@@ -284,5 +397,5 @@ async function runFullPipelineCore(deps, opts, coordinatorStartMs) {
284
397
  };
285
398
  }
286
399
  deps.stderr(`[full-pipeline] PR detected: ${prUrl}`);
287
- return (0, implement_shared_js_1.runReviewAndVerdictCycle)(deps, opts, prUrl, coordinatorStartMs, 0);
400
+ return (0, implement_shared_js_1.runReviewAndVerdictCycle)(deps, opts, prUrl, coordinatorStartMs, 0, runId, priorArtifacts);
288
401
  }
@@ -1,5 +1,6 @@
1
1
  import type { AdaptiveCoordinatorDeps, AdaptivePipelineOpts, PipelineOutcome } from '../adaptive-pipeline.js';
2
2
  import type { ReviewVerdictArtifactV1 } from '../../v2/durable-core/schemas/artifacts/review-verdict.js';
3
+ import type { PhaseHandoffArtifact } from '../../v2/durable-core/schemas/artifacts/index.js';
3
4
  export declare const MAX_FIX_ITERATIONS = 2;
4
- export declare function runReviewAndVerdictCycle(deps: AdaptiveCoordinatorDeps, opts: AdaptivePipelineOpts, prUrl: string, coordinatorStartMs: number, iteration: number): Promise<PipelineOutcome>;
5
- export declare function runAuditChain(deps: AdaptiveCoordinatorDeps, opts: AdaptivePipelineOpts, prUrl: string, coordinatorStartMs: number, severity: 'blocking' | 'unknown', findings?: ReviewVerdictArtifactV1['findings']): Promise<PipelineOutcome>;
5
+ export declare function runReviewAndVerdictCycle(deps: AdaptiveCoordinatorDeps, opts: AdaptivePipelineOpts, prUrl: string, coordinatorStartMs: number, iteration: number, runId?: string, priorArtifacts?: readonly PhaseHandoffArtifact[]): Promise<PipelineOutcome>;
6
+ export declare function runAuditChain(deps: AdaptiveCoordinatorDeps, opts: AdaptivePipelineOpts, prUrl: string, coordinatorStartMs: number, severity: 'blocking' | 'unknown', findings?: ReviewVerdictArtifactV1['findings'], priorArtifacts?: readonly PhaseHandoffArtifact[]): Promise<PipelineOutcome>;
@@ -6,8 +6,9 @@ exports.runAuditChain = runAuditChain;
6
6
  const adaptive_pipeline_js_1 = require("../adaptive-pipeline.js");
7
7
  const pr_review_js_1 = require("../pr-review.js");
8
8
  const review_verdict_js_1 = require("../../v2/durable-core/schemas/artifacts/review-verdict.js");
9
+ const context_assembly_js_1 = require("../context-assembly.js");
9
10
  exports.MAX_FIX_ITERATIONS = 2;
10
- async function runReviewAndVerdictCycle(deps, opts, prUrl, coordinatorStartMs, iteration) {
11
+ async function runReviewAndVerdictCycle(deps, opts, prUrl, coordinatorStartMs, iteration, runId = '', priorArtifacts = []) {
11
12
  const cutoffCheck = (0, adaptive_pipeline_js_1.checkSpawnCutoff)(coordinatorStartMs, deps.now(), 'review');
12
13
  if (cutoffCheck)
13
14
  return cutoffCheck;
@@ -15,7 +16,11 @@ async function runReviewAndVerdictCycle(deps, opts, prUrl, coordinatorStartMs, i
15
16
  ? `Review PR for merge: ${prUrl}`
16
17
  : `Re-review PR after fixes (iteration ${iteration}): ${prUrl}`;
17
18
  deps.stderr(`[review-cycle] Spawning review session (iteration=${iteration}): ${reviewGoal.slice(0, 80)}`);
18
- const reviewSpawnResult = await deps.spawnSession('wr.mr-review', reviewGoal, opts.workspace, { prUrl });
19
+ const reviewContextSummary = (0, context_assembly_js_1.buildContextSummary)(priorArtifacts, 'review');
20
+ const reviewSpawnResult = await deps.spawnSession('wr.mr-review', reviewGoal, opts.workspace, {
21
+ prUrl,
22
+ ...(reviewContextSummary ? { assembledContextSummary: reviewContextSummary } : {}),
23
+ });
19
24
  if (reviewSpawnResult.kind === 'err') {
20
25
  return {
21
26
  kind: 'escalated',
@@ -86,7 +91,12 @@ async function runReviewAndVerdictCycle(deps, opts, prUrl, coordinatorStartMs, i
86
91
  if (fixCutoff)
87
92
  return fixCutoff;
88
93
  const fixGoal = `Fix review findings: ${findings.findingSummaries.slice(0, 3).join('; ')}`;
89
- const fixSpawnResult = await deps.spawnSession('wr.coding-task', fixGoal, opts.workspace, { prUrl, findings: findings.findingSummaries });
94
+ const fixContextSummary = (0, context_assembly_js_1.buildContextSummary)(priorArtifacts, 'fix');
95
+ const fixSpawnResult = await deps.spawnSession('wr.coding-task', fixGoal, opts.workspace, {
96
+ prUrl,
97
+ findings: findings.findingSummaries,
98
+ ...(fixContextSummary ? { assembledContextSummary: fixContextSummary } : {}),
99
+ });
90
100
  if (fixSpawnResult.kind === 'err') {
91
101
  return {
92
102
  kind: 'escalated',
@@ -110,15 +120,15 @@ async function runReviewAndVerdictCycle(deps, opts, prUrl, coordinatorStartMs, i
110
120
  };
111
121
  }
112
122
  deps.stderr(`[review-cycle] Fix iteration ${iteration + 1} complete -- re-reviewing`);
113
- return runReviewAndVerdictCycle(deps, opts, prUrl, coordinatorStartMs, iteration + 1);
123
+ return runReviewAndVerdictCycle(deps, opts, prUrl, coordinatorStartMs, iteration + 1, runId, priorArtifacts);
114
124
  }
115
125
  case 'blocking':
116
126
  case 'unknown': {
117
- return runAuditChain(deps, opts, prUrl, coordinatorStartMs, findings.severity, rawVerdict?.findings);
127
+ return runAuditChain(deps, opts, prUrl, coordinatorStartMs, findings.severity, rawVerdict?.findings, priorArtifacts);
118
128
  }
119
129
  }
120
130
  }
121
- async function runAuditChain(deps, opts, prUrl, coordinatorStartMs, severity, findings) {
131
+ async function runAuditChain(deps, opts, prUrl, coordinatorStartMs, severity, findings, priorArtifacts = []) {
122
132
  deps.stderr(`[audit-chain] ${severity.toUpperCase()} finding -- running audit chain`);
123
133
  const auditCutoff = (0, adaptive_pipeline_js_1.checkSpawnCutoff)(coordinatorStartMs, deps.now(), 'audit');
124
134
  if (auditCutoff)
@@ -2,8 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.touchesUI = touchesUI;
4
4
  exports.runImplementPipeline = runImplementPipeline;
5
+ const neverthrow_1 = require("neverthrow");
5
6
  const adaptive_pipeline_js_1 = require("../adaptive-pipeline.js");
6
7
  const implement_shared_js_1 = require("./implement-shared.js");
8
+ const context_assembly_js_1 = require("../context-assembly.js");
9
+ const pipeline_run_context_js_1 = require("../pipeline-run-context.js");
10
+ const index_js_1 = require("../../v2/durable-core/schemas/artifacts/index.js");
7
11
  const PR_POLL_TIMEOUT_MS = 5 * 60 * 1000;
8
12
  const UI_KEYWORDS = [
9
13
  'ui', 'screen', 'view', 'layout', 'component', 'design', 'ux', 'frontend',
@@ -17,9 +21,23 @@ async function runImplementPipeline(deps, opts, pitchPath, coordinatorStartMs) {
17
21
  const archiveDir = opts.workspace + '/.workrail/used-pitches';
18
22
  const archiveTimestamp = deps.nowIso().replace(/[:.]/g, '-');
19
23
  const archivePath = archiveDir + '/pitch-' + archiveTimestamp + '.md';
24
+ const activeRunResult = await deps.readActiveRunId(opts.workspace);
25
+ const priorRunId = activeRunResult.isOk() ? activeRunResult.value : null;
26
+ const runId = priorRunId ?? deps.generateRunId();
27
+ const initResult = priorRunId
28
+ ? (0, neverthrow_1.ok)(undefined)
29
+ : await deps.createPipelineContext(opts.workspace, runId, opts.goal, 'IMPLEMENT');
30
+ if (initResult.isErr()) {
31
+ deps.stderr(`[implement] FATAL: failed to initialize PipelineRunContext: ${initResult.error}`);
32
+ return { kind: 'escalated', escalationReason: { phase: 'init', reason: `PipelineRunContext initialization failed: ${initResult.error}` } };
33
+ }
20
34
  let outcome;
21
35
  try {
22
- outcome = await runImplementCore(deps, opts, pitchPath, coordinatorStartMs);
36
+ outcome = await runImplementCore(deps, opts, pitchPath, coordinatorStartMs, runId);
37
+ const markResult = await deps.markPipelineRunComplete(opts.workspace, runId);
38
+ if (markResult.isErr()) {
39
+ deps.stderr(`[WARN implement] markPipelineRunComplete failed -- next run may resume this one: ${markResult.error}`);
40
+ }
23
41
  }
24
42
  finally {
25
43
  try {
@@ -33,7 +51,8 @@ async function runImplementPipeline(deps, opts, pitchPath, coordinatorStartMs) {
33
51
  }
34
52
  return outcome;
35
53
  }
36
- async function runImplementCore(deps, opts, pitchPath, coordinatorStartMs) {
54
+ async function runImplementCore(deps, opts, pitchPath, coordinatorStartMs, runId) {
55
+ const priorArtifacts = [];
37
56
  if (touchesUI(opts.goal)) {
38
57
  deps.stderr(`[implement] UX signals detected in goal, dispatching wr.ui-ux-design`);
39
58
  const cutoffCheck = (0, adaptive_pipeline_js_1.checkSpawnCutoff)(coordinatorStartMs, deps.now(), 'ux-gate');
@@ -97,6 +116,33 @@ async function runImplementCore(deps, opts, pitchPath, coordinatorStartMs) {
97
116
  };
98
117
  }
99
118
  deps.stderr(`[implement] Coding session completed (${Math.round((codingResult.durationMs ?? 0) / 1000)}s)`);
119
+ let codingAgentResult;
120
+ try {
121
+ codingAgentResult = await deps.getAgentResult(codingHandle);
122
+ }
123
+ catch {
124
+ codingAgentResult = { recapMarkdown: null, artifacts: [] };
125
+ }
126
+ const codingArtifact = (0, context_assembly_js_1.extractPhaseArtifact)(codingAgentResult.artifacts, index_js_1.CodingHandoffArtifactV1Schema, index_js_1.isCodingHandoffArtifact);
127
+ const codingPhaseResult = (0, pipeline_run_context_js_1.buildPhaseResult)(codingArtifact, codingAgentResult.recapMarkdown);
128
+ const updatedPriorArtifacts = codingArtifact !== null ? [...priorArtifacts, codingArtifact] : priorArtifacts;
129
+ const codingWriteResult = await deps.writePhaseRecord(opts.workspace, runId, {
130
+ phase: 'coding',
131
+ record: { completedAt: deps.nowIso(), sessionHandle: codingHandle, result: codingPhaseResult },
132
+ });
133
+ if (codingWriteResult.isErr()) {
134
+ deps.stderr(`[implement] FATAL: failed to persist coding phase record: ${codingWriteResult.error}`);
135
+ return { kind: 'escalated', escalationReason: { phase: 'coding', reason: `context persistence failed: ${codingWriteResult.error}` } };
136
+ }
137
+ if (codingPhaseResult.kind === 'fallback') {
138
+ return {
139
+ kind: 'escalated',
140
+ escalationReason: {
141
+ phase: 'coding',
142
+ reason: 'coding session produced no usable output (no artifact and no meaningful notes). Starting review blind would miss design-level issues. Fix the coding session and resume.',
143
+ },
144
+ };
145
+ }
100
146
  const branchPattern = `worktrain/${codingHandle.slice(0, 16)}`;
101
147
  deps.stderr(`[implement] Polling for PR on branch pattern: ${branchPattern}`);
102
148
  let prUrl;
@@ -121,5 +167,5 @@ async function runImplementCore(deps, opts, pitchPath, coordinatorStartMs) {
121
167
  };
122
168
  }
123
169
  deps.stderr(`[implement] PR detected: ${prUrl}`);
124
- return (0, implement_shared_js_1.runReviewAndVerdictCycle)(deps, opts, prUrl, coordinatorStartMs, 0);
170
+ return (0, implement_shared_js_1.runReviewAndVerdictCycle)(deps, opts, prUrl, coordinatorStartMs, 0, runId, updatedPriorArtifacts);
125
171
  }