@wrongstack/sdd 0.298.3 → 0.299.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { TaskGenerator, extractVerificationCommand, assessGeneratedTaskAtomicity
3
3
  export { TaskTracker, DefaultTaskStore, type TaskStore, type TaskTrackerOptions, type TaskTransition, type TaskTrackerChange, type TaskTrackerListener, } from '@wrongstack/core/tasking';
4
4
  export { TaskFlow, SpecDrivenDev, type TaskFlowPhase, type TaskFlowOptions, type TaskFlowExecutionContext, type TaskFlowEventMap, type TaskFlowEventName, type SpecDrivenDevOptions, } from './task-flow.js';
5
5
  export { SpecStore, type SpecStoreOptions, type SpecIndexEntry } from './spec-store.js';
6
- export { TaskGraphStore, type TaskGraphStoreOptions, type TaskGraphIndexEntry } from './task-graph-store.js';
6
+ export { TaskGraphStore, type TaskGraphStoreOptions, type TaskGraphIndexEntry, } from './task-graph-store.js';
7
7
  export { buildBoardTasks, buildBoardSnapshot, shortIdMap, type SddBoardSnapshot, type SddBoardTask, type SddBoardColumn, type SddBoardStatus, type SddTaskDisplayStatus, type SddDeadlockChain, type SddBoardFeedEntry, } from './board-types.js';
8
8
  export { SddBoardStore, type SddBoardStoreOptions, type SddBoardIndexEntry, type SddBoardEvent, } from './sdd-board-store.js';
9
9
  export { SddBoardProjector, type SddBoardPersistence, type SddBoardProjectorOptions, } from './sdd-board-projector.js';
@@ -14,9 +14,10 @@ export { cleanupSddWorktrees, cleanupStaleWorktrees, cleanupStaleSddWorktrees, r
14
14
  export { AISpecBuilder, type AISpecBuilderOptions, type AISpecPhase, type AISpecSession, type AISpecSessionPersistence, type CollectedAnswer, isAISpecSession, } from './spec-builder.js';
15
15
  export { createKanbanSddSessionPersistence } from './kanban-sdd-session.js';
16
16
  export { gatherProjectContext } from './project-context.js';
17
+ export { intakeToInterviewKickoff, startInterviewFromIntake, type IntakeInterviewKickoff, } from './intake-kickoff.js';
17
18
  export { SPEC_TEMPLATES, getTemplate, listTemplates, templateToMarkdown, } from './spec-templates.js';
18
19
  export { renderTaskGraph, renderProgress, renderTaskList, renderSpecAnalysis, } from './task-visualizer.js';
19
- export { analyzeCriticalPath, type CriticalPathAnalysis, type BottleneckTask } from './critical-path.js';
20
+ export { analyzeCriticalPath, type CriticalPathAnalysis, type BottleneckTask, } from './critical-path.js';
20
21
  export { SpecVersioning, type SpecVersion, type SpecDiff } from './spec-versioning.js';
21
22
  export { AutoExecutor, createAutoExecutor, type AutoExecutorOptions, type TaskExecutionContext, type TaskExecutionResult, type ExecutionSummary, } from './auto-executor.js';
22
23
  export { SddTaskDecomposer, type SddTaskDecomposerOptions, type TaskBatch, } from './sdd-task-decomposer.js';
package/dist/index.js CHANGED
@@ -2730,7 +2730,17 @@ async function executeSddTask(params) {
2730
2730
  taskId,
2731
2731
  reason: merged.reason
2732
2732
  });
2733
- await params.applyTaskFailure(taskId, subagentId, merged.reason);
2733
+ if (merged.fatal) {
2734
+ opts.tracker.updateNodeStatus(taskId, "failed", merged.reason);
2735
+ params.emit("sdd.task.failed", {
2736
+ runId: params.runId,
2737
+ taskId,
2738
+ subagentId,
2739
+ error: merged.reason
2740
+ });
2741
+ } else {
2742
+ await params.applyTaskFailure(taskId, subagentId, merged.reason);
2743
+ }
2734
2744
  } else {
2735
2745
  const conflictFiles = merged.conflictFiles ?? [];
2736
2746
  params.emit("sdd.task.conflict", { runId: params.runId, taskId, conflictFiles });
@@ -2976,6 +2986,12 @@ var SddParallelRun = class {
2976
2986
  * snapshot so a post-run rollback can read them off disk.
2977
2987
  */
2978
2988
  mergedCommits = [];
2989
+ /**
2990
+ * Fatal, non-recoverable run error — set together with `stopRequested` when
2991
+ * the run hard-stops (e.g. a known-invalid merge that could not be rolled
2992
+ * back). Surfaced on `run()`'s result so the caller can see WHY it stopped.
2993
+ */
2994
+ fatalError;
2979
2995
  /** Monotonic dispatch counter (unique subagent ids) + dispatch-round counter. */
2980
2996
  dispatchSeq = 0;
2981
2997
  round = 0;
@@ -3175,6 +3191,7 @@ var SddParallelRun = class {
3175
3191
  */
3176
3192
  async run() {
3177
3193
  this.stopRequested = false;
3194
+ this.fatalError = void 0;
3178
3195
  this.restoreRetryMap();
3179
3196
  const startTime = Date.now();
3180
3197
  this.round = 0;
@@ -3265,14 +3282,18 @@ var SddParallelRun = class {
3265
3282
  this.opts.onProgress?.(this.buildProgress());
3266
3283
  }
3267
3284
  }
3268
- if (this.stopRequested) await this.teardown();
3285
+ if (this.stopRequested) {
3286
+ await Promise.allSettled(running.values());
3287
+ await this.teardown();
3288
+ }
3269
3289
  const finalProgress = this.opts.tracker.getProgress();
3270
3290
  this.emit("sdd.run.finished", {
3271
3291
  runId: this.runId,
3272
3292
  deadlocked,
3273
3293
  completed: finalProgress.completed,
3274
3294
  failed: finalProgress.failed,
3275
- stopped: this.stopRequested
3295
+ stopped: this.stopRequested,
3296
+ ...this.fatalError ? { fatalError: this.fatalError } : {}
3276
3297
  });
3277
3298
  return {
3278
3299
  totalWaves: this.round,
@@ -3281,6 +3302,7 @@ var SddParallelRun = class {
3281
3302
  totalDurationMs: Date.now() - startTime,
3282
3303
  deadlocked,
3283
3304
  stopRequested: this.stopRequested,
3305
+ ...this.fatalError ? { fatalError: this.fatalError } : {},
3284
3306
  finalProgress
3285
3307
  };
3286
3308
  }
@@ -3390,6 +3412,17 @@ var SddParallelRun = class {
3390
3412
  }
3391
3413
  }
3392
3414
  }
3415
+ /**
3416
+ * Hard-stop the run after an unrecoverable error: the base branch is in a
3417
+ * state no retry can fix (e.g. a known-invalid merge that could not be rolled
3418
+ * back), so continuing would contaminate every task forked after this point.
3419
+ * The reason is surfaced on `run()`'s result and the `sdd.run.finished` event.
3420
+ */
3421
+ abortRun(reason) {
3422
+ this.fatalError = reason;
3423
+ this.stopRequested = true;
3424
+ this.coordinator?.stopAll();
3425
+ }
3393
3426
  // -------------------------------------------------------------------
3394
3427
  // Internal
3395
3428
  // -------------------------------------------------------------------
@@ -3592,8 +3625,16 @@ var SddParallelRun = class {
3592
3625
  regressed = `verification error after conflict resolution: ${String(err)}`;
3593
3626
  }
3594
3627
  if (regressed) {
3595
- await wt.revertBaseTo(handle, baseSha).catch(() => {
3596
- });
3628
+ const rolledBack = await wt.revertBaseTo(handle, baseSha).catch(() => false);
3629
+ if (!rolledBack) {
3630
+ this.abortRun(
3631
+ `cannot roll back invalid merge of "${task.title}" (${task.id}): ${regressed}`
3632
+ );
3633
+ await wt.release(handle, { keep: true }).catch(() => {
3634
+ });
3635
+ this.forgetWorktree(task.id, { keepBranchLabel: true });
3636
+ return { ok: false, conflictFiles: [], reason: regressed, fatal: true };
3637
+ }
3597
3638
  await wt.release(handle, { keep: false }).catch(() => {
3598
3639
  });
3599
3640
  this.forgetWorktree(task.id, { keepBranchLabel: true });
@@ -4226,6 +4267,32 @@ async function gatherProjectContext(projectRoot) {
4226
4267
  return parts.join("\n");
4227
4268
  }
4228
4269
 
4270
+ // src/intake-kickoff.ts
4271
+ function deriveTitle(value) {
4272
+ const firstLine = value.split("\n").map((line) => line.trim()).find(Boolean);
4273
+ if (!firstLine) return "New SDD Project";
4274
+ const sentence = firstLine.split(/(?<=[.!?])\s/)[0] ?? firstLine;
4275
+ return sentence.length <= 64 ? sentence : `${sentence.slice(0, 63).trimEnd()}\u2026`;
4276
+ }
4277
+ function intakeToInterviewKickoff(record) {
4278
+ const contextLines = [];
4279
+ if (record.businessGoal) contextLines.push(`Business goal: ${record.businessGoal}`);
4280
+ if (record.expectedOutcome) contextLines.push(`Expected outcome: ${record.expectedOutcome}`);
4281
+ if (record.scopeNotes) contextLines.push(`Scope notes: ${record.scopeNotes}`);
4282
+ for (const targetUser of record.targetUsers) contextLines.push(`Target user: ${targetUser}`);
4283
+ for (const constraint of record.constraints) contextLines.push(`Constraint: ${constraint}`);
4284
+ for (const context of record.providedContext) contextLines.push(`Context: ${context}`);
4285
+ return {
4286
+ title: deriveTitle(record.title),
4287
+ intent: record.originalRequest,
4288
+ projectContext: contextLines.length > 0 ? contextLines.join("\n") : ""
4289
+ };
4290
+ }
4291
+ function startInterviewFromIntake(driver, record) {
4292
+ const kickoff = intakeToInterviewKickoff(record);
4293
+ return driver.start(kickoff.title, kickoff.intent);
4294
+ }
4295
+
4229
4296
  // src/spec-templates.ts
4230
4297
  var SPEC_TEMPLATES = [
4231
4298
  {
@@ -5530,6 +5597,7 @@ export {
5530
5597
  gatherProjectContext,
5531
5598
  getTemplate,
5532
5599
  hasConflictMarkers,
5600
+ intakeToInterviewKickoff,
5533
5601
  isAISpecSession,
5534
5602
  isExplanatoryText,
5535
5603
  listTemplates,
@@ -5548,6 +5616,7 @@ export {
5548
5616
  rollbackSddRunFromDisk,
5549
5617
  shortIdMap,
5550
5618
  splitGraphNode,
5619
+ startInterviewFromIntake,
5551
5620
  startSddRun,
5552
5621
  templateToMarkdown,
5553
5622
  tokenizeCommand
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Requirements Intake → SDD interview kickoff.
3
+ *
4
+ * Bridges `@wrongstack/requirement-intake` records into the spec-builder
5
+ * interview. The original request stays the interview's intent verbatim;
6
+ * collected high-level facts (goal, outcome, constraints, target users) are
7
+ * folded into the project context the questioning prompt receives. Nothing
8
+ * here rewrites or summarizes the original request — it only routes it.
9
+ */
10
+ import type { RequirementIntakeRecord } from '@wrongstack/requirement-intake';
11
+ import type { SddInterviewDriver } from './sdd-interview-driver.js';
12
+ export interface IntakeInterviewKickoff {
13
+ /** Short interview title (≤ 64 chars, first sentence of the record title). */
14
+ title: string;
15
+ /** Interview intent — the exact original request, verbatim. */
16
+ intent: string;
17
+ /** Extra project context (goal/outcome/constraints/users) for the prompt. */
18
+ projectContext: string;
19
+ }
20
+ /**
21
+ * Map an intake record to interview kickoff values. `intent` is the exact
22
+ * `originalRequest`; `projectContext` carries the collected high-level fields
23
+ * so the spec interview does not re-ask for already-provided information.
24
+ */
25
+ export declare function intakeToInterviewKickoff(record: RequirementIntakeRecord): IntakeInterviewKickoff;
26
+ /**
27
+ * Start a spec-builder interview from an intake record. Returns the first AI
28
+ * prompt (a question kickoff), ready to feed the agent loop. The driver should
29
+ * be constructed with `projectContext: kickoff.projectContext` so the
30
+ * questioning prompt already knows the collected facts.
31
+ */
32
+ export declare function startInterviewFromIntake(driver: Pick<SddInterviewDriver, 'start'>, record: RequirementIntakeRecord): string;
33
+ //# sourceMappingURL=intake-kickoff.d.ts.map
@@ -195,6 +195,12 @@ export interface RunResult {
195
195
  totalDurationMs: number;
196
196
  deadlocked: boolean;
197
197
  stopRequested: boolean;
198
+ /**
199
+ * Fatal, non-recoverable run error — set when the run hard-stopped
200
+ * (`stopRequested: true`) because no retry could fix the base state
201
+ * (e.g. a known-invalid merge that could not be rolled back).
202
+ */
203
+ fatalError?: string | undefined;
198
204
  finalProgress: TaskProgress;
199
205
  }
200
206
  //# sourceMappingURL=sdd-parallel-run-types.d.ts.map
@@ -74,6 +74,12 @@ export declare class SddParallelRun {
74
74
  * snapshot so a post-run rollback can read them off disk.
75
75
  */
76
76
  private mergedCommits;
77
+ /**
78
+ * Fatal, non-recoverable run error — set together with `stopRequested` when
79
+ * the run hard-stops (e.g. a known-invalid merge that could not be rolled
80
+ * back). Surfaced on `run()`'s result so the caller can see WHY it stopped.
81
+ */
82
+ private fatalError;
77
83
  /** Monotonic dispatch counter (unique subagent ids) + dispatch-round counter. */
78
84
  private dispatchSeq;
79
85
  private round;
@@ -197,6 +203,13 @@ export declare class SddParallelRun {
197
203
  static resetOrphans(tracker: TaskTracker): number;
198
204
  /** Clean teardown after a stop: reset interrupted tasks + release worktrees. */
199
205
  private teardown;
206
+ /**
207
+ * Hard-stop the run after an unrecoverable error: the base branch is in a
208
+ * state no retry can fix (e.g. a known-invalid merge that could not be rolled
209
+ * back), so continuing would contaminate every task forked after this point.
210
+ * The reason is surfaced on `run()`'s result and the `sdd.run.finished` event.
211
+ */
212
+ private abortRun;
200
213
  private buildCoordinator;
201
214
  private defaultFactory;
202
215
  /**
@@ -22,6 +22,7 @@ export declare function executeSddTask(params: {
22
22
  ok: boolean;
23
23
  conflictFiles?: string[];
24
24
  reason?: string;
25
+ fatal?: boolean;
25
26
  }>;
26
27
  applyTaskFailure: (taskId: string, subagentId: string, errMsg: string) => Promise<void>;
27
28
  }): Promise<TaskOutcome>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/sdd",
3
- "version": "0.298.3",
3
+ "version": "0.299.0",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack Spec-Driven Development engine — standalone package extracted from @wrongstack/core. Task graph generation, tracking, execution, lifecycle management, and AI-driven spec building for SDD workflows.",
6
6
  "repository": {
@@ -27,8 +27,9 @@
27
27
  "!dist/**/*.map"
28
28
  ],
29
29
  "dependencies": {
30
- "@wrongstack/core": "0.298.3",
31
- "@wrongstack/kanban": "0.298.3"
30
+ "@wrongstack/core": "0.299.0",
31
+ "@wrongstack/kanban": "0.299.0",
32
+ "@wrongstack/requirement-intake": "0.299.0"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@types/node": "^26.1.2",