@wrongstack/sdd 0.298.3 → 0.300.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;
@@ -3112,6 +3128,7 @@ var SddParallelRun = class {
3112
3128
  async cancelTask(taskId) {
3113
3129
  const node = this.opts.tracker.getNode(taskId);
3114
3130
  if (!node) return false;
3131
+ if (node.status === "completed") return false;
3115
3132
  this.cancelledTasks.add(taskId);
3116
3133
  this.opts.tracker.patchMetadata(taskId, { cancelled: true });
3117
3134
  this.opts.tracker.updateNodeStatus(taskId, "failed", "cancelled by user");
@@ -3175,6 +3192,7 @@ var SddParallelRun = class {
3175
3192
  */
3176
3193
  async run() {
3177
3194
  this.stopRequested = false;
3195
+ this.fatalError = void 0;
3178
3196
  this.restoreRetryMap();
3179
3197
  const startTime = Date.now();
3180
3198
  this.round = 0;
@@ -3265,14 +3283,18 @@ var SddParallelRun = class {
3265
3283
  this.opts.onProgress?.(this.buildProgress());
3266
3284
  }
3267
3285
  }
3268
- if (this.stopRequested) await this.teardown();
3286
+ if (this.stopRequested) {
3287
+ await Promise.allSettled(running.values());
3288
+ await this.teardown();
3289
+ }
3269
3290
  const finalProgress = this.opts.tracker.getProgress();
3270
3291
  this.emit("sdd.run.finished", {
3271
3292
  runId: this.runId,
3272
3293
  deadlocked,
3273
3294
  completed: finalProgress.completed,
3274
3295
  failed: finalProgress.failed,
3275
- stopped: this.stopRequested
3296
+ stopped: this.stopRequested,
3297
+ ...this.fatalError ? { fatalError: this.fatalError } : {}
3276
3298
  });
3277
3299
  return {
3278
3300
  totalWaves: this.round,
@@ -3281,6 +3303,7 @@ var SddParallelRun = class {
3281
3303
  totalDurationMs: Date.now() - startTime,
3282
3304
  deadlocked,
3283
3305
  stopRequested: this.stopRequested,
3306
+ ...this.fatalError ? { fatalError: this.fatalError } : {},
3284
3307
  finalProgress
3285
3308
  };
3286
3309
  }
@@ -3390,6 +3413,17 @@ var SddParallelRun = class {
3390
3413
  }
3391
3414
  }
3392
3415
  }
3416
+ /**
3417
+ * Hard-stop the run after an unrecoverable error: the base branch is in a
3418
+ * state no retry can fix (e.g. a known-invalid merge that could not be rolled
3419
+ * back), so continuing would contaminate every task forked after this point.
3420
+ * The reason is surfaced on `run()`'s result and the `sdd.run.finished` event.
3421
+ */
3422
+ abortRun(reason) {
3423
+ this.fatalError = reason;
3424
+ this.stopRequested = true;
3425
+ this.coordinator?.stopAll();
3426
+ }
3393
3427
  // -------------------------------------------------------------------
3394
3428
  // Internal
3395
3429
  // -------------------------------------------------------------------
@@ -3592,8 +3626,16 @@ var SddParallelRun = class {
3592
3626
  regressed = `verification error after conflict resolution: ${String(err)}`;
3593
3627
  }
3594
3628
  if (regressed) {
3595
- await wt.revertBaseTo(handle, baseSha).catch(() => {
3596
- });
3629
+ const rolledBack = await wt.revertBaseTo(handle, baseSha).catch(() => false);
3630
+ if (!rolledBack) {
3631
+ this.abortRun(
3632
+ `cannot roll back invalid merge of "${task.title}" (${task.id}): ${regressed}`
3633
+ );
3634
+ await wt.release(handle, { keep: true }).catch(() => {
3635
+ });
3636
+ this.forgetWorktree(task.id, { keepBranchLabel: true });
3637
+ return { ok: false, conflictFiles: [], reason: regressed, fatal: true };
3638
+ }
3597
3639
  await wt.release(handle, { keep: false }).catch(() => {
3598
3640
  });
3599
3641
  this.forgetWorktree(task.id, { keepBranchLabel: true });
@@ -4226,6 +4268,32 @@ async function gatherProjectContext(projectRoot) {
4226
4268
  return parts.join("\n");
4227
4269
  }
4228
4270
 
4271
+ // src/intake-kickoff.ts
4272
+ function deriveTitle(value) {
4273
+ const firstLine = value.split("\n").map((line) => line.trim()).find(Boolean);
4274
+ if (!firstLine) return "New SDD Project";
4275
+ const sentence = firstLine.split(/(?<=[.!?])\s/)[0] ?? firstLine;
4276
+ return sentence.length <= 64 ? sentence : `${sentence.slice(0, 63).trimEnd()}\u2026`;
4277
+ }
4278
+ function intakeToInterviewKickoff(record) {
4279
+ const contextLines = [];
4280
+ if (record.businessGoal) contextLines.push(`Business goal: ${record.businessGoal}`);
4281
+ if (record.expectedOutcome) contextLines.push(`Expected outcome: ${record.expectedOutcome}`);
4282
+ if (record.scopeNotes) contextLines.push(`Scope notes: ${record.scopeNotes}`);
4283
+ for (const targetUser of record.targetUsers) contextLines.push(`Target user: ${targetUser}`);
4284
+ for (const constraint of record.constraints) contextLines.push(`Constraint: ${constraint}`);
4285
+ for (const context of record.providedContext) contextLines.push(`Context: ${context}`);
4286
+ return {
4287
+ title: deriveTitle(record.title),
4288
+ intent: record.originalRequest,
4289
+ projectContext: contextLines.length > 0 ? contextLines.join("\n") : ""
4290
+ };
4291
+ }
4292
+ function startInterviewFromIntake(driver, record) {
4293
+ const kickoff = intakeToInterviewKickoff(record);
4294
+ return driver.start(kickoff.title, kickoff.intent);
4295
+ }
4296
+
4229
4297
  // src/spec-templates.ts
4230
4298
  var SPEC_TEMPLATES = [
4231
4299
  {
@@ -5530,6 +5598,7 @@ export {
5530
5598
  gatherProjectContext,
5531
5599
  getTemplate,
5532
5600
  hasConflictMarkers,
5601
+ intakeToInterviewKickoff,
5533
5602
  isAISpecSession,
5534
5603
  isExplanatoryText,
5535
5604
  listTemplates,
@@ -5548,6 +5617,7 @@ export {
5548
5617
  rollbackSddRunFromDisk,
5549
5618
  shortIdMap,
5550
5619
  splitGraphNode,
5620
+ startInterviewFromIntake,
5551
5621
  startSddRun,
5552
5622
  templateToMarkdown,
5553
5623
  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.300.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.300.0",
31
+ "@wrongstack/kanban": "0.300.0",
32
+ "@wrongstack/requirement-intake": "0.300.0"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@types/node": "^26.1.2",