@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
|
-
|
|
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)
|
|
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
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/sdd",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
31
|
-
"@wrongstack/kanban": "0.
|
|
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",
|