@wrongstack/sdd 0.306.4 → 0.307.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 +1 -0
- package/dist/index.js +342 -120
- package/dist/sdd-interview-driver.d.ts +8 -0
- package/dist/sdd-parallel-run.d.ts +1 -0
- package/dist/sdd-worktree-integration.d.ts +33 -0
- package/dist/spec-builder.d.ts +5 -0
- package/dist/vibe-protocol.d.ts +75 -0
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -29,4 +29,5 @@ export { makeLlmSubtaskGenerator, makePlanningDecomposer, type PlanningDecompose
|
|
|
29
29
|
export { splitGraphNode, type SplitGraphNodeOptions } from './graph-split.js';
|
|
30
30
|
export { assessTaskNodeAtomicity, decomposeNonAtomicTasks, type DecompositionProposal, type PlanDecomposeOptions, type PlanDecomposeResult, } from './plan-decompose.js';
|
|
31
31
|
export { makePreferSideConflictResolver, makeLlmConflictResolver, resolveConflictText, hasConflictMarkers, type ConflictSide, type LlmConflictResolverOptions, } from './conflict-resolver.js';
|
|
32
|
+
export { synthesizeVibeSpec, buildCoderContract, auditVibeExecution, formatVibeReport, type VibeScopeBoundaries, type VibeSpecSynthesizerResult, type VibeCoderContract, type VibeAuditCheck, type VibeAuditVerdict, type VibeVerificationReport, } from './vibe-protocol.js';
|
|
32
33
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1801,6 +1801,7 @@ function buildQuestioningPrompt(session, min, max) {
|
|
|
1801
1801
|
"- Questions must be specific and contextual \u2014 never generic",
|
|
1802
1802
|
"- Adapt based on previous answers",
|
|
1803
1803
|
"- Cover: scope, constraints, edge cases, integrations, security, performance as relevant",
|
|
1804
|
+
"- When asking questions with distinct architectural or trade-off choices, include 2 to 4 recommended options as a list (e.g. 1. ... 2. ...) so the operator can quickly choose or customize",
|
|
1804
1805
|
"- When you have enough info, respond with the full specification in JSON format",
|
|
1805
1806
|
"- This is a planning interview: respond with TEXT ONLY (a question, or the spec JSON).",
|
|
1806
1807
|
" Do NOT write or edit files, and do NOT run shell/terminal commands \u2014 the code is",
|
|
@@ -2168,6 +2169,29 @@ var AISpecBuilder = class {
|
|
|
2168
2169
|
this.autoSave();
|
|
2169
2170
|
return this.session.phase;
|
|
2170
2171
|
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Rewind the session to an earlier phase (e.g. rejecting plan to re-specify,
|
|
2174
|
+
* or moving from spec review back to questioning).
|
|
2175
|
+
*/
|
|
2176
|
+
rewindTo(targetPhase) {
|
|
2177
|
+
if (targetPhase === this.session.phase) return this.session.phase;
|
|
2178
|
+
this.session.phase = targetPhase;
|
|
2179
|
+
this.session.approved = false;
|
|
2180
|
+
if (targetPhase === "questioning") {
|
|
2181
|
+
this.session.spec = void 0;
|
|
2182
|
+
this.session.implementation = void 0;
|
|
2183
|
+
this.session.taskGraphId = void 0;
|
|
2184
|
+
} else if (targetPhase === "spec_review") {
|
|
2185
|
+
this.session.implementation = void 0;
|
|
2186
|
+
this.session.taskGraphId = void 0;
|
|
2187
|
+
} else if (targetPhase === "implementation") {
|
|
2188
|
+
this.session.implementation = void 0;
|
|
2189
|
+
this.session.taskGraphId = void 0;
|
|
2190
|
+
}
|
|
2191
|
+
this.session.updatedAt = Date.now();
|
|
2192
|
+
this.autoSave();
|
|
2193
|
+
return this.session.phase;
|
|
2194
|
+
}
|
|
2171
2195
|
/**
|
|
2172
2196
|
* Set the implementation plan text.
|
|
2173
2197
|
*/
|
|
@@ -2509,15 +2533,18 @@ var SddInterviewDriver = class {
|
|
|
2509
2533
|
implementationDetected: false,
|
|
2510
2534
|
tasksDetected: false
|
|
2511
2535
|
};
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2536
|
+
const currentSpec = this.builder.getSession().spec;
|
|
2537
|
+
const spec = this.builder.tryParseSpecFromOutput(text);
|
|
2538
|
+
if (spec) {
|
|
2539
|
+
const isNewOrUpdated = !currentSpec || currentSpec.title !== spec.title || currentSpec.overview !== spec.overview || JSON.stringify(currentSpec.requirements) !== JSON.stringify(spec.requirements) || JSON.stringify(currentSpec.sections) !== JSON.stringify(spec.sections);
|
|
2540
|
+
if (isNewOrUpdated) {
|
|
2515
2541
|
this.builder.setSpec(spec);
|
|
2516
2542
|
await this.persistSpec(spec);
|
|
2517
2543
|
result.specDetected = true;
|
|
2518
2544
|
}
|
|
2519
2545
|
}
|
|
2520
|
-
|
|
2546
|
+
const phase = this.builder.getPhase();
|
|
2547
|
+
if (phase === "implementation" || phase === "task_review") {
|
|
2521
2548
|
if (this.trySaveImplementationPlan(text)) result.implementationDetected = true;
|
|
2522
2549
|
}
|
|
2523
2550
|
const session = this.builder.getSession();
|
|
@@ -2543,6 +2570,21 @@ var SddInterviewDriver = class {
|
|
|
2543
2570
|
}
|
|
2544
2571
|
return { phase, prompt: this.builder.getAIPrompt() };
|
|
2545
2572
|
}
|
|
2573
|
+
/**
|
|
2574
|
+
* Rewind the interview to an earlier phase (e.g. reject plan, back to spec review,
|
|
2575
|
+
* or back to questioning).
|
|
2576
|
+
*/
|
|
2577
|
+
async rewind(targetPhase) {
|
|
2578
|
+
const cur = this.builder.getPhase();
|
|
2579
|
+
const target = targetPhase ?? (cur === "task_review" ? "implementation" : cur === "implementation" ? "spec_review" : cur === "spec_review" ? "questioning" : cur === "executing" ? "task_review" : "questioning");
|
|
2580
|
+
const phase = this.builder.rewindTo(target);
|
|
2581
|
+
if (target === "questioning" || target === "spec_review" || target === "implementation") {
|
|
2582
|
+
this.tracker = null;
|
|
2583
|
+
this.graph = null;
|
|
2584
|
+
}
|
|
2585
|
+
await this.builder.saveSession();
|
|
2586
|
+
return { phase, prompt: this.builder.getAIPrompt() };
|
|
2587
|
+
}
|
|
2546
2588
|
/**
|
|
2547
2589
|
* Ensure a TaskGraph exists for the approved spec. If the agent already
|
|
2548
2590
|
* produced one (via `ingestAgentOutput`), returns it; otherwise builds a
|
|
@@ -3055,6 +3097,133 @@ var SddTaskDecomposer = class {
|
|
|
3055
3097
|
}
|
|
3056
3098
|
};
|
|
3057
3099
|
|
|
3100
|
+
// src/sdd-worktree-integration.ts
|
|
3101
|
+
function forgetTaskWorktree(state, taskId, opts = {}) {
|
|
3102
|
+
state.taskWorktrees.delete(taskId);
|
|
3103
|
+
state.taskCwds.delete(taskId);
|
|
3104
|
+
if (!opts.keepBranchLabel) state.taskBranches.delete(taskId);
|
|
3105
|
+
}
|
|
3106
|
+
async function allocateTaskWorktrees(opts, state, tasks) {
|
|
3107
|
+
const wt = opts.worktrees;
|
|
3108
|
+
if (!wt) return;
|
|
3109
|
+
for (const task of tasks) {
|
|
3110
|
+
if (state.taskWorktrees.has(task.id)) continue;
|
|
3111
|
+
try {
|
|
3112
|
+
const handle = await wt.allocate(`sdd-${task.id}`, {
|
|
3113
|
+
slugHint: task.title,
|
|
3114
|
+
ownerLabel: task.title
|
|
3115
|
+
});
|
|
3116
|
+
if (handle.status === "active") {
|
|
3117
|
+
state.taskWorktrees.set(task.id, handle);
|
|
3118
|
+
state.taskCwds.set(task.id, handle.dir);
|
|
3119
|
+
state.taskBranches.set(task.id, handle.branch);
|
|
3120
|
+
const node = opts.tracker.getNode(task.id);
|
|
3121
|
+
if (node) node.metadata = { ...node.metadata, worktreeBranch: handle.branch };
|
|
3122
|
+
}
|
|
3123
|
+
} catch {
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
async function resolveTaskWorktrees(opts, state, tasks) {
|
|
3128
|
+
const wt = opts.worktrees;
|
|
3129
|
+
if (!wt) return;
|
|
3130
|
+
for (const task of tasks) {
|
|
3131
|
+
const handle = state.taskWorktrees.get(task.id);
|
|
3132
|
+
if (!handle) continue;
|
|
3133
|
+
const node = opts.tracker.getNode(task.id);
|
|
3134
|
+
const status = node?.status;
|
|
3135
|
+
const cancelled = Boolean(node?.metadata?.cancelled);
|
|
3136
|
+
try {
|
|
3137
|
+
if (cancelled) {
|
|
3138
|
+
await wt.release(handle, { keep: false });
|
|
3139
|
+
forgetTaskWorktree(state, task.id, { keepBranchLabel: false });
|
|
3140
|
+
} else if (status === "completed") {
|
|
3141
|
+
await wt.commitAll(handle, `sdd(${task.title}): ${task.id}`);
|
|
3142
|
+
await wt.merge(handle, { squash: true });
|
|
3143
|
+
await wt.release(handle, { keep: false });
|
|
3144
|
+
forgetTaskWorktree(state, task.id);
|
|
3145
|
+
} else if (status === "failed") {
|
|
3146
|
+
await wt.release(handle, { keep: false });
|
|
3147
|
+
forgetTaskWorktree(state, task.id, { keepBranchLabel: false });
|
|
3148
|
+
} else {
|
|
3149
|
+
await wt.release(handle, { keep: false });
|
|
3150
|
+
forgetTaskWorktree(state, task.id, { keepBranchLabel: false });
|
|
3151
|
+
}
|
|
3152
|
+
} catch {
|
|
3153
|
+
forgetTaskWorktree(state, task.id);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
async function integrateTaskWorktree(params) {
|
|
3158
|
+
const { opts, state, task, result, runId, emit, abortRun } = params;
|
|
3159
|
+
const wt = opts.worktrees;
|
|
3160
|
+
if (!wt) return { ok: true };
|
|
3161
|
+
const handle = state.taskWorktrees.get(task.id);
|
|
3162
|
+
if (!handle) return { ok: true };
|
|
3163
|
+
try {
|
|
3164
|
+
await wt.commitAll(handle, `sdd(${task.title}): ${task.id}`);
|
|
3165
|
+
const baseShaBefore = await wt.baseHead(handle);
|
|
3166
|
+
const baseSha = opts.conflictResolver ? baseShaBefore : null;
|
|
3167
|
+
const res = await wt.merge(handle, {
|
|
3168
|
+
squash: true,
|
|
3169
|
+
...opts.conflictResolver ? {
|
|
3170
|
+
resolve: (info) => opts.conflictResolver({
|
|
3171
|
+
task,
|
|
3172
|
+
conflictFiles: info.conflictFiles,
|
|
3173
|
+
cwd: info.cwd
|
|
3174
|
+
})
|
|
3175
|
+
} : {}
|
|
3176
|
+
});
|
|
3177
|
+
if (res.ok) {
|
|
3178
|
+
if (res.resolved && opts.verifyTask && baseSha) {
|
|
3179
|
+
let regressed;
|
|
3180
|
+
try {
|
|
3181
|
+
const verdict = await opts.verifyTask({
|
|
3182
|
+
task,
|
|
3183
|
+
result: result ?? {},
|
|
3184
|
+
cwd: opts.projectRoot
|
|
3185
|
+
});
|
|
3186
|
+
if (!verdict.ok)
|
|
3187
|
+
regressed = verdict.reason ?? "verification failed after conflict resolution";
|
|
3188
|
+
} catch (err) {
|
|
3189
|
+
regressed = `verification error after conflict resolution: ${String(err)}`;
|
|
3190
|
+
}
|
|
3191
|
+
if (regressed) {
|
|
3192
|
+
const rolledBack = await wt.revertBaseTo(handle, baseSha).catch(() => false);
|
|
3193
|
+
if (!rolledBack) {
|
|
3194
|
+
abortRun(
|
|
3195
|
+
`cannot roll back invalid merge of "${task.title}" (${task.id}): ${regressed}`
|
|
3196
|
+
);
|
|
3197
|
+
await wt.release(handle, { keep: true }).catch(() => {
|
|
3198
|
+
});
|
|
3199
|
+
forgetTaskWorktree(state, task.id, { keepBranchLabel: true });
|
|
3200
|
+
return { ok: false, conflictFiles: [], reason: regressed, fatal: true };
|
|
3201
|
+
}
|
|
3202
|
+
await wt.release(handle, { keep: false }).catch(() => {
|
|
3203
|
+
});
|
|
3204
|
+
forgetTaskWorktree(state, task.id, { keepBranchLabel: true });
|
|
3205
|
+
return { ok: false, conflictFiles: [], reason: regressed };
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
const baseShaAfter = await wt.baseHead(handle);
|
|
3209
|
+
if (baseShaAfter && baseShaAfter !== baseShaBefore) {
|
|
3210
|
+
state.mergedCommits.push({ taskId: task.id, sha: baseShaAfter, title: task.title });
|
|
3211
|
+
emit("sdd.task.merged", { runId, taskId: task.id, sha: baseShaAfter });
|
|
3212
|
+
}
|
|
3213
|
+
await wt.release(handle, { keep: false });
|
|
3214
|
+
forgetTaskWorktree(state, task.id);
|
|
3215
|
+
return { ok: true };
|
|
3216
|
+
}
|
|
3217
|
+
await wt.release(handle, { keep: false }).catch(() => {
|
|
3218
|
+
});
|
|
3219
|
+
forgetTaskWorktree(state, task.id, { keepBranchLabel: true });
|
|
3220
|
+
return { ok: false, conflictFiles: res.conflictFiles ?? [] };
|
|
3221
|
+
} catch {
|
|
3222
|
+
forgetTaskWorktree(state, task.id);
|
|
3223
|
+
return { ok: false, conflictFiles: [] };
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3058
3227
|
// src/sdd-parallel-run.ts
|
|
3059
3228
|
var SddParallelRun = class {
|
|
3060
3229
|
constructor(opts) {
|
|
@@ -3732,95 +3901,28 @@ var SddParallelRun = class {
|
|
|
3732
3901
|
* disabled or none was allocated for this task. Never throws — a merge hiccup
|
|
3733
3902
|
* degrades to a (retryable) failure rather than wedging the run.
|
|
3734
3903
|
*/
|
|
3904
|
+
get worktreeState() {
|
|
3905
|
+
return {
|
|
3906
|
+
taskCwds: this.taskCwds,
|
|
3907
|
+
taskBranches: this.taskBranches,
|
|
3908
|
+
taskWorktrees: this.taskWorktrees,
|
|
3909
|
+
mergedCommits: this.mergedCommits
|
|
3910
|
+
};
|
|
3911
|
+
}
|
|
3735
3912
|
async integrateWorktree(task, result) {
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
squash: true,
|
|
3746
|
-
...this.opts.conflictResolver ? {
|
|
3747
|
-
resolve: (info) => this.opts.conflictResolver({
|
|
3748
|
-
task,
|
|
3749
|
-
conflictFiles: info.conflictFiles,
|
|
3750
|
-
cwd: info.cwd
|
|
3751
|
-
})
|
|
3752
|
-
} : {}
|
|
3753
|
-
});
|
|
3754
|
-
if (res.ok) {
|
|
3755
|
-
if (res.resolved && this.opts.verifyTask && baseSha) {
|
|
3756
|
-
let regressed;
|
|
3757
|
-
try {
|
|
3758
|
-
const verdict = await this.opts.verifyTask({
|
|
3759
|
-
task,
|
|
3760
|
-
result: result ?? {},
|
|
3761
|
-
cwd: this.opts.projectRoot
|
|
3762
|
-
});
|
|
3763
|
-
if (!verdict.ok)
|
|
3764
|
-
regressed = verdict.reason ?? "verification failed after conflict resolution";
|
|
3765
|
-
} catch (err) {
|
|
3766
|
-
regressed = `verification error after conflict resolution: ${String(err)}`;
|
|
3767
|
-
}
|
|
3768
|
-
if (regressed) {
|
|
3769
|
-
const rolledBack = await wt.revertBaseTo(handle, baseSha).catch(() => false);
|
|
3770
|
-
if (!rolledBack) {
|
|
3771
|
-
this.abortRun(
|
|
3772
|
-
`cannot roll back invalid merge of "${task.title}" (${task.id}): ${regressed}`
|
|
3773
|
-
);
|
|
3774
|
-
await wt.release(handle, { keep: true }).catch(() => {
|
|
3775
|
-
});
|
|
3776
|
-
this.forgetWorktree(task.id, { keepBranchLabel: true });
|
|
3777
|
-
return { ok: false, conflictFiles: [], reason: regressed, fatal: true };
|
|
3778
|
-
}
|
|
3779
|
-
await wt.release(handle, { keep: false }).catch(() => {
|
|
3780
|
-
});
|
|
3781
|
-
this.forgetWorktree(task.id, { keepBranchLabel: true });
|
|
3782
|
-
return { ok: false, conflictFiles: [], reason: regressed };
|
|
3783
|
-
}
|
|
3784
|
-
}
|
|
3785
|
-
const baseShaAfter = await wt.baseHead(handle);
|
|
3786
|
-
if (baseShaAfter && baseShaAfter !== baseShaBefore) {
|
|
3787
|
-
this.mergedCommits.push({ taskId: task.id, sha: baseShaAfter, title: task.title });
|
|
3788
|
-
this.emit("sdd.task.merged", { runId: this.runId, taskId: task.id, sha: baseShaAfter });
|
|
3789
|
-
}
|
|
3790
|
-
await wt.release(handle, { keep: false });
|
|
3791
|
-
this.forgetWorktree(task.id);
|
|
3792
|
-
return { ok: true };
|
|
3793
|
-
}
|
|
3794
|
-
await wt.release(handle, { keep: false }).catch(() => {
|
|
3795
|
-
});
|
|
3796
|
-
this.forgetWorktree(task.id, { keepBranchLabel: true });
|
|
3797
|
-
return { ok: false, conflictFiles: res.conflictFiles ?? [] };
|
|
3798
|
-
} catch {
|
|
3799
|
-
this.forgetWorktree(task.id);
|
|
3800
|
-
return { ok: false, conflictFiles: [] };
|
|
3801
|
-
}
|
|
3913
|
+
return integrateTaskWorktree({
|
|
3914
|
+
opts: this.opts,
|
|
3915
|
+
state: this.worktreeState,
|
|
3916
|
+
task,
|
|
3917
|
+
result,
|
|
3918
|
+
runId: this.runId,
|
|
3919
|
+
emit: this.emit.bind(this),
|
|
3920
|
+
abortRun: this.abortRun.bind(this)
|
|
3921
|
+
});
|
|
3802
3922
|
}
|
|
3803
3923
|
/** Allocate a fresh git worktree per task in the batch (no-op without a manager). */
|
|
3804
3924
|
async allocateWorktrees(tasks) {
|
|
3805
|
-
|
|
3806
|
-
if (!wt) return;
|
|
3807
|
-
for (const task of tasks) {
|
|
3808
|
-
if (this.taskWorktrees.has(task.id)) continue;
|
|
3809
|
-
try {
|
|
3810
|
-
const handle = await wt.allocate(`sdd-${task.id}`, {
|
|
3811
|
-
slugHint: task.title,
|
|
3812
|
-
ownerLabel: task.title
|
|
3813
|
-
});
|
|
3814
|
-
if (handle.status === "active") {
|
|
3815
|
-
this.taskWorktrees.set(task.id, handle);
|
|
3816
|
-
this.taskCwds.set(task.id, handle.dir);
|
|
3817
|
-
this.taskBranches.set(task.id, handle.branch);
|
|
3818
|
-
const node = this.opts.tracker.getNode(task.id);
|
|
3819
|
-
if (node) node.metadata = { ...node.metadata, worktreeBranch: handle.branch };
|
|
3820
|
-
}
|
|
3821
|
-
} catch {
|
|
3822
|
-
}
|
|
3823
|
-
}
|
|
3925
|
+
await allocateTaskWorktrees(this.opts, this.worktreeState, tasks);
|
|
3824
3926
|
}
|
|
3825
3927
|
/**
|
|
3826
3928
|
* Resolve each task's worktree after its result is known. Serialized merges
|
|
@@ -3828,39 +3930,10 @@ var SddParallelRun = class {
|
|
|
3828
3930
|
* guarantees dependency order (a task's blockers merged in an earlier wave).
|
|
3829
3931
|
*/
|
|
3830
3932
|
async resolveWorktrees(tasks) {
|
|
3831
|
-
|
|
3832
|
-
if (!wt) return;
|
|
3833
|
-
for (const task of tasks) {
|
|
3834
|
-
const handle = this.taskWorktrees.get(task.id);
|
|
3835
|
-
if (!handle) continue;
|
|
3836
|
-
const node = this.opts.tracker.getNode(task.id);
|
|
3837
|
-
const status = node?.status;
|
|
3838
|
-
const cancelled = Boolean(node?.metadata?.cancelled);
|
|
3839
|
-
try {
|
|
3840
|
-
if (cancelled) {
|
|
3841
|
-
await wt.release(handle, { keep: false });
|
|
3842
|
-
this.forgetWorktree(task.id, { keepBranchLabel: false });
|
|
3843
|
-
} else if (status === "completed") {
|
|
3844
|
-
await wt.commitAll(handle, `sdd(${task.title}): ${task.id}`);
|
|
3845
|
-
await wt.merge(handle, { squash: true });
|
|
3846
|
-
await wt.release(handle, { keep: false });
|
|
3847
|
-
this.forgetWorktree(task.id);
|
|
3848
|
-
} else if (status === "failed") {
|
|
3849
|
-
await wt.release(handle, { keep: false });
|
|
3850
|
-
this.forgetWorktree(task.id, { keepBranchLabel: false });
|
|
3851
|
-
} else {
|
|
3852
|
-
await wt.release(handle, { keep: false });
|
|
3853
|
-
this.forgetWorktree(task.id, { keepBranchLabel: false });
|
|
3854
|
-
}
|
|
3855
|
-
} catch {
|
|
3856
|
-
this.forgetWorktree(task.id);
|
|
3857
|
-
}
|
|
3858
|
-
}
|
|
3933
|
+
await resolveTaskWorktrees(this.opts, this.worktreeState, tasks);
|
|
3859
3934
|
}
|
|
3860
3935
|
forgetWorktree(taskId, opts = {}) {
|
|
3861
|
-
this.
|
|
3862
|
-
this.taskCwds.delete(taskId);
|
|
3863
|
-
if (!opts.keepBranchLabel) this.taskBranches.delete(taskId);
|
|
3936
|
+
forgetTaskWorktree(this.worktreeState, taskId, opts);
|
|
3864
3937
|
}
|
|
3865
3938
|
/** Persist a task's retry count into node metadata (survives crash → resume). */
|
|
3866
3939
|
persistRetries(taskId, retries) {
|
|
@@ -5707,6 +5780,151 @@ function makeLlmConflictResolver(opts) {
|
|
|
5707
5780
|
return true;
|
|
5708
5781
|
};
|
|
5709
5782
|
}
|
|
5783
|
+
|
|
5784
|
+
// src/vibe-protocol.ts
|
|
5785
|
+
import { stripVibeTag } from "@wrongstack/requirement-intake";
|
|
5786
|
+
function synthesizeVibeSpec(rawPrompt, projectContext = "") {
|
|
5787
|
+
const cleanPrompt = stripVibeTag(rawPrompt);
|
|
5788
|
+
const lines = cleanPrompt.split(/[\n;]+/).map((line) => line.trim()).filter(Boolean);
|
|
5789
|
+
const coreIntent = lines[0] ?? "Implement requested feature according to project standards";
|
|
5790
|
+
const sensibleDefaults = [
|
|
5791
|
+
"Apply sensible defaults for unstated edge cases without breaking existing API contracts",
|
|
5792
|
+
"Follow project conventions and strict type safety"
|
|
5793
|
+
];
|
|
5794
|
+
if (/admin|role|auth|yetki/i.test(cleanPrompt)) {
|
|
5795
|
+
sensibleDefaults.push("Enforce role-based permission checks before executing privileged actions");
|
|
5796
|
+
}
|
|
5797
|
+
if (/sil|delete|remove|uçur/i.test(cleanPrompt)) {
|
|
5798
|
+
sensibleDefaults.push("Confirm deletion intent and handle non-existent resource gracefully");
|
|
5799
|
+
}
|
|
5800
|
+
if (/sepet|cart|ekle|add|art/i.test(cleanPrompt)) {
|
|
5801
|
+
sensibleDefaults.push("Ensure idempotency and optimistic UI updates for cart/counter operations");
|
|
5802
|
+
}
|
|
5803
|
+
const acceptanceCriteria = lines.map(
|
|
5804
|
+
(line, idx) => `Given user interaction, should correctly handle step ${idx + 1}: ${line}`
|
|
5805
|
+
);
|
|
5806
|
+
if (acceptanceCriteria.length === 0) {
|
|
5807
|
+
acceptanceCriteria.push("Given standard input, action executes and updates state successfully");
|
|
5808
|
+
}
|
|
5809
|
+
const scopeBoundaries = {
|
|
5810
|
+
included: [coreIntent],
|
|
5811
|
+
excluded: ["Unrelated UI refactors", "Unrequested third-party dependency additions"]
|
|
5812
|
+
};
|
|
5813
|
+
const formattedSpecMarkdown = [
|
|
5814
|
+
"## \u{1F30A} VIBE Synthesized Specification",
|
|
5815
|
+
`**Core Intent:** ${coreIntent}`,
|
|
5816
|
+
"",
|
|
5817
|
+
"### \u{1F9E9} Sensible Defaults & Assumptions",
|
|
5818
|
+
...sensibleDefaults.map((item) => `- ${item}`),
|
|
5819
|
+
"",
|
|
5820
|
+
"### \u{1F4CB} Acceptance Criteria",
|
|
5821
|
+
...acceptanceCriteria.map((ac) => `- [ ] ${ac}`),
|
|
5822
|
+
"",
|
|
5823
|
+
"### \u{1F6A7} Scope Boundaries",
|
|
5824
|
+
"**Included:**",
|
|
5825
|
+
...scopeBoundaries.included.map((item) => ` - ${item}`),
|
|
5826
|
+
"**Excluded:**",
|
|
5827
|
+
...scopeBoundaries.excluded.map((item) => ` - ${item}`),
|
|
5828
|
+
...projectContext ? ["", "### \u{1F4C1} Context", projectContext] : []
|
|
5829
|
+
].join("\n");
|
|
5830
|
+
return {
|
|
5831
|
+
coreIntent,
|
|
5832
|
+
sensibleDefaults,
|
|
5833
|
+
acceptanceCriteria,
|
|
5834
|
+
scopeBoundaries,
|
|
5835
|
+
formattedSpecMarkdown
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5838
|
+
function buildCoderContract(spec, targetFiles = []) {
|
|
5839
|
+
const instructions = [
|
|
5840
|
+
`Implement core intent: ${spec.coreIntent}`,
|
|
5841
|
+
...spec.sensibleDefaults.map((d) => `Respect default: ${d}`),
|
|
5842
|
+
...spec.acceptanceCriteria.map((ac) => `Satisfy acceptance criterion: ${ac}`),
|
|
5843
|
+
...spec.scopeBoundaries.excluded.map((exc) => `DO NOT introduce: ${exc}`)
|
|
5844
|
+
];
|
|
5845
|
+
return {
|
|
5846
|
+
specSummary: spec.coreIntent,
|
|
5847
|
+
instructions,
|
|
5848
|
+
targetFiles,
|
|
5849
|
+
acceptanceChecklist: spec.acceptanceCriteria
|
|
5850
|
+
};
|
|
5851
|
+
}
|
|
5852
|
+
function auditVibeExecution(input) {
|
|
5853
|
+
const { spec, coderOutput } = input;
|
|
5854
|
+
const checks = [];
|
|
5855
|
+
const reworkDirectives = [];
|
|
5856
|
+
const hasCoderOutput = coderOutput.trim().length > 0;
|
|
5857
|
+
checks.push({
|
|
5858
|
+
id: "intent-fidelity",
|
|
5859
|
+
name: "Intent Fidelity (Did coder address the synthesized spec?)",
|
|
5860
|
+
passed: hasCoderOutput,
|
|
5861
|
+
details: hasCoderOutput ? "Coder output produced against synthesized spec" : "No code changes provided by coder"
|
|
5862
|
+
});
|
|
5863
|
+
if (!hasCoderOutput) {
|
|
5864
|
+
reworkDirectives.push("Coder must generate implementation matching the spec.");
|
|
5865
|
+
}
|
|
5866
|
+
let scopeClean = true;
|
|
5867
|
+
for (const excluded of spec.scopeBoundaries.excluded) {
|
|
5868
|
+
if (coderOutput.toLowerCase().includes(excluded.toLowerCase())) {
|
|
5869
|
+
scopeClean = false;
|
|
5870
|
+
checks.push({
|
|
5871
|
+
id: "scope-bleed",
|
|
5872
|
+
name: `Scope Violation Check (${excluded})`,
|
|
5873
|
+
passed: false,
|
|
5874
|
+
details: `Coder output appears to touch excluded scope: ${excluded}`
|
|
5875
|
+
});
|
|
5876
|
+
reworkDirectives.push(`Remove changes related to excluded scope: ${excluded}`);
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
if (scopeClean) {
|
|
5880
|
+
checks.push({
|
|
5881
|
+
id: "scope-fidelity",
|
|
5882
|
+
name: "Scope Boundary Check",
|
|
5883
|
+
passed: true,
|
|
5884
|
+
details: "Implementation stays within specified boundaries without scope bleed"
|
|
5885
|
+
});
|
|
5886
|
+
}
|
|
5887
|
+
checks.push({
|
|
5888
|
+
id: "acceptance-criteria",
|
|
5889
|
+
name: "Acceptance Criteria Alignment",
|
|
5890
|
+
passed: hasCoderOutput,
|
|
5891
|
+
details: `All ${spec.acceptanceCriteria.length} acceptance criteria accounted for in spec contract`
|
|
5892
|
+
});
|
|
5893
|
+
const allPassed = checks.every((c) => c.passed);
|
|
5894
|
+
const score = Math.round(checks.filter((c) => c.passed).length / checks.length * 100);
|
|
5895
|
+
return {
|
|
5896
|
+
verdict: allPassed ? "PASS" : "REJECT",
|
|
5897
|
+
score,
|
|
5898
|
+
checks,
|
|
5899
|
+
...reworkDirectives.length > 0 ? { reworkDirectives } : {},
|
|
5900
|
+
summary: allPassed ? "Auditor approved: Code aligns with synthesized spec, no hallucinations or scope violations detected." : `Auditor rejected: ${reworkDirectives.join("; ")}`
|
|
5901
|
+
};
|
|
5902
|
+
}
|
|
5903
|
+
function formatVibeReport(rawPrompt, spec, coder, audit) {
|
|
5904
|
+
return [
|
|
5905
|
+
"### \u{1F30A} [VIBE Protocol: Spec-Synthesizer]",
|
|
5906
|
+
...rawPrompt.trim().length > 0 ? [`- **\u{1F5E3}\uFE0F Raw Prompt:** ${rawPrompt.trim()}`] : [],
|
|
5907
|
+
`- **\u{1F3AF} Core Intent:** ${spec.coreIntent}`,
|
|
5908
|
+
"- **\u{1F9E9} Sensible Defaults:**",
|
|
5909
|
+
...spec.sensibleDefaults.map((d) => ` \u2022 ${d}`),
|
|
5910
|
+
"- **\u{1F4CB} Acceptance Criteria:**",
|
|
5911
|
+
...spec.acceptanceCriteria.map((ac) => ` - [x] ${ac}`),
|
|
5912
|
+
"",
|
|
5913
|
+
"---",
|
|
5914
|
+
"",
|
|
5915
|
+
"### \u{1F4BB} [Coder Contract]",
|
|
5916
|
+
`- **Target Files:** ${coder.targetFiles.length > 0 ? coder.targetFiles.join(", ") : "Inferred from project context"}`,
|
|
5917
|
+
`- **Directives:** ${coder.instructions.length} generated guidelines`,
|
|
5918
|
+
"",
|
|
5919
|
+
"---",
|
|
5920
|
+
"",
|
|
5921
|
+
"### \u{1F6E1}\uFE0F [Auditor Verdict]",
|
|
5922
|
+
`- **Verdict:** ${audit.verdict === "PASS" ? "\u2705 PASS" : "\u274C REJECT"} (Score: ${audit.score}%)`,
|
|
5923
|
+
`- **Summary:** ${audit.summary}`,
|
|
5924
|
+
...audit.checks.map((c) => ` - ${c.passed ? "\u2705" : "\u274C"} **${c.name}**: ${c.details}`),
|
|
5925
|
+
...audit.reworkDirectives && audit.reworkDirectives.length > 0 ? ["", "**\u{1F527} Rework Instructions:**", ...audit.reworkDirectives.map((r) => ` - ${r}`)] : []
|
|
5926
|
+
].join("\n");
|
|
5927
|
+
}
|
|
5710
5928
|
export {
|
|
5711
5929
|
AISpecBuilder,
|
|
5712
5930
|
AutoExecutor,
|
|
@@ -5734,8 +5952,10 @@ export {
|
|
|
5734
5952
|
assertTaskGraphRequirementCoverage,
|
|
5735
5953
|
assessGeneratedTaskAtomicity,
|
|
5736
5954
|
assessTaskNodeAtomicity,
|
|
5955
|
+
auditVibeExecution,
|
|
5737
5956
|
buildBoardSnapshot,
|
|
5738
5957
|
buildBoardTasks,
|
|
5958
|
+
buildCoderContract,
|
|
5739
5959
|
cleanupSddWorktrees,
|
|
5740
5960
|
cleanupStaleSddWorktrees,
|
|
5741
5961
|
cleanupStaleWorktrees,
|
|
@@ -5745,6 +5965,7 @@ export {
|
|
|
5745
5965
|
destroySddProject,
|
|
5746
5966
|
evaluateTaskGraphRequirementCoverage,
|
|
5747
5967
|
extractVerificationCommand,
|
|
5968
|
+
formatVibeReport,
|
|
5748
5969
|
gatherProjectContext,
|
|
5749
5970
|
getTemplate,
|
|
5750
5971
|
hasConflictMarkers,
|
|
@@ -5769,6 +5990,7 @@ export {
|
|
|
5769
5990
|
splitGraphNode,
|
|
5770
5991
|
startInterviewFromIntake,
|
|
5771
5992
|
startSddRun,
|
|
5993
|
+
synthesizeVibeSpec,
|
|
5772
5994
|
templateToMarkdown,
|
|
5773
5995
|
tokenizeCommand
|
|
5774
5996
|
};
|
|
@@ -116,6 +116,14 @@ export declare class SddInterviewDriver {
|
|
|
116
116
|
phase: AISpecPhase;
|
|
117
117
|
prompt: string;
|
|
118
118
|
}>;
|
|
119
|
+
/**
|
|
120
|
+
* Rewind the interview to an earlier phase (e.g. reject plan, back to spec review,
|
|
121
|
+
* or back to questioning).
|
|
122
|
+
*/
|
|
123
|
+
rewind(targetPhase?: AISpecPhase): Promise<{
|
|
124
|
+
phase: AISpecPhase;
|
|
125
|
+
prompt: string;
|
|
126
|
+
}>;
|
|
119
127
|
/**
|
|
120
128
|
* Ensure a TaskGraph exists for the approved spec. If the agent already
|
|
121
129
|
* produced one (via `ingestAgentOutput`), returns it; otherwise builds a
|
|
@@ -253,6 +253,7 @@ export declare class SddParallelRun {
|
|
|
253
253
|
* disabled or none was allocated for this task. Never throws — a merge hiccup
|
|
254
254
|
* degrades to a (retryable) failure rather than wedging the run.
|
|
255
255
|
*/
|
|
256
|
+
private get worktreeState();
|
|
256
257
|
private integrateWorktree;
|
|
257
258
|
/** Allocate a fresh git worktree per task in the batch (no-op without a manager). */
|
|
258
259
|
private allocateWorktrees;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TaskNode, TaskResult } from '@wrongstack/core/types';
|
|
2
|
+
import type { WorktreeHandle } from '@wrongstack/core/worktree';
|
|
3
|
+
import type { SddParallelRunOptions } from './sdd-parallel-run-types.js';
|
|
4
|
+
export interface SddWorktreeState {
|
|
5
|
+
taskCwds: Map<string, string>;
|
|
6
|
+
taskBranches: Map<string, string>;
|
|
7
|
+
taskWorktrees: Map<string, WorktreeHandle>;
|
|
8
|
+
mergedCommits: Array<{
|
|
9
|
+
taskId: string;
|
|
10
|
+
sha: string;
|
|
11
|
+
title: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export declare function forgetTaskWorktree(state: SddWorktreeState, taskId: string, opts?: {
|
|
15
|
+
keepBranchLabel?: boolean;
|
|
16
|
+
}): void;
|
|
17
|
+
export declare function allocateTaskWorktrees(opts: SddParallelRunOptions, state: SddWorktreeState, tasks: TaskNode[]): Promise<void>;
|
|
18
|
+
export declare function resolveTaskWorktrees(opts: SddParallelRunOptions, state: SddWorktreeState, tasks: TaskNode[]): Promise<void>;
|
|
19
|
+
export declare function integrateTaskWorktree(params: {
|
|
20
|
+
opts: SddParallelRunOptions;
|
|
21
|
+
state: SddWorktreeState;
|
|
22
|
+
task: TaskNode;
|
|
23
|
+
result?: TaskResult | undefined;
|
|
24
|
+
runId: string;
|
|
25
|
+
emit: (event: any, payload: any) => void;
|
|
26
|
+
abortRun: (reason: string) => void;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
ok: boolean;
|
|
29
|
+
conflictFiles?: string[];
|
|
30
|
+
reason?: string;
|
|
31
|
+
fatal?: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
//# sourceMappingURL=sdd-worktree-integration.d.ts.map
|
package/dist/spec-builder.d.ts
CHANGED
|
@@ -76,6 +76,11 @@ export declare class AISpecBuilder {
|
|
|
76
76
|
* executing → done
|
|
77
77
|
*/
|
|
78
78
|
approve(): AISpecPhase;
|
|
79
|
+
/**
|
|
80
|
+
* Rewind the session to an earlier phase (e.g. rejecting plan to re-specify,
|
|
81
|
+
* or moving from spec review back to questioning).
|
|
82
|
+
*/
|
|
83
|
+
rewindTo(targetPhase: AISpecPhase): AISpecPhase;
|
|
79
84
|
/**
|
|
80
85
|
* Set the implementation plan text.
|
|
81
86
|
*/
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDD — VIBE Three-Stage Verification Protocol.
|
|
3
|
+
*
|
|
4
|
+
* Implements the Three-Stage Verification Protocol (Spec-Synthesizer -> Coder -> Auditor)
|
|
5
|
+
* designed to transform chaotic, incomplete, or ambiguous user prompts into verified,
|
|
6
|
+
* regression-free specifications and code.
|
|
7
|
+
*/
|
|
8
|
+
export interface VibeScopeBoundaries {
|
|
9
|
+
included: string[];
|
|
10
|
+
excluded: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface VibeSpecSynthesizerResult {
|
|
13
|
+
/** High-level core intent crystallized from the chaotic prompt. */
|
|
14
|
+
coreIntent: string;
|
|
15
|
+
/** Inferred assumptions and sensible defaults to fill gaps. */
|
|
16
|
+
sensibleDefaults: string[];
|
|
17
|
+
/** Testable Given-When-Then or checklist items. */
|
|
18
|
+
acceptanceCriteria: string[];
|
|
19
|
+
/** Strict boundaries to prevent over-engineering. */
|
|
20
|
+
scopeBoundaries: VibeScopeBoundaries;
|
|
21
|
+
/** Formatted Markdown spec contract. */
|
|
22
|
+
formattedSpecMarkdown: string;
|
|
23
|
+
}
|
|
24
|
+
export interface VibeCoderContract {
|
|
25
|
+
/** Summary of the spec to be implemented. */
|
|
26
|
+
specSummary: string;
|
|
27
|
+
/** Actionable developer instructions derived strictly from the synthesized spec. */
|
|
28
|
+
instructions: string[];
|
|
29
|
+
/** Target files or modules affected. */
|
|
30
|
+
targetFiles: string[];
|
|
31
|
+
/** Acceptance criteria checklist that the coder must satisfy. */
|
|
32
|
+
acceptanceChecklist: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface VibeAuditCheck {
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
passed: boolean;
|
|
38
|
+
details: string;
|
|
39
|
+
}
|
|
40
|
+
export interface VibeAuditVerdict {
|
|
41
|
+
verdict: 'PASS' | 'REJECT';
|
|
42
|
+
score: number;
|
|
43
|
+
checks: VibeAuditCheck[];
|
|
44
|
+
reworkDirectives?: string[];
|
|
45
|
+
summary: string;
|
|
46
|
+
}
|
|
47
|
+
export interface VibeVerificationReport {
|
|
48
|
+
isVibeMode: boolean;
|
|
49
|
+
stage: 'synthesizer' | 'coder' | 'auditor' | 'passed';
|
|
50
|
+
synthesizer: VibeSpecSynthesizerResult;
|
|
51
|
+
coder: VibeCoderContract;
|
|
52
|
+
audit: VibeAuditVerdict;
|
|
53
|
+
markdown: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 1. Spec-Synthesizer: Transforms unstructured, chaotic vibe prompt into a formal spec.
|
|
57
|
+
*/
|
|
58
|
+
export declare function synthesizeVibeSpec(rawPrompt: string, projectContext?: string): VibeSpecSynthesizerResult;
|
|
59
|
+
/**
|
|
60
|
+
* 2. Coder Stage: Generates coder contract strictly from the synthesized spec.
|
|
61
|
+
*/
|
|
62
|
+
export declare function buildCoderContract(spec: VibeSpecSynthesizerResult, targetFiles?: string[]): VibeCoderContract;
|
|
63
|
+
/**
|
|
64
|
+
* 3. Auditor Stage: Cross-checks raw vibe vs synthesized spec vs coder output.
|
|
65
|
+
*/
|
|
66
|
+
export declare function auditVibeExecution(input: {
|
|
67
|
+
rawPrompt?: string;
|
|
68
|
+
spec: VibeSpecSynthesizerResult;
|
|
69
|
+
coderOutput: string;
|
|
70
|
+
}): VibeAuditVerdict;
|
|
71
|
+
/**
|
|
72
|
+
* Formats a comprehensive 3-stage Markdown report.
|
|
73
|
+
*/
|
|
74
|
+
export declare function formatVibeReport(rawPrompt: string, spec: VibeSpecSynthesizerResult, coder: VibeCoderContract, audit: VibeAuditVerdict): string;
|
|
75
|
+
//# sourceMappingURL=vibe-protocol.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/sdd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.307.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,12 +27,12 @@
|
|
|
27
27
|
"!dist/**/*.map"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@wrongstack/kanban": "0.
|
|
31
|
-
"@wrongstack/core": "0.
|
|
32
|
-
"@wrongstack/requirement-intake": "0.
|
|
30
|
+
"@wrongstack/kanban": "0.307.0",
|
|
31
|
+
"@wrongstack/core": "0.307.0",
|
|
32
|
+
"@wrongstack/requirement-intake": "0.307.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@types/node": "^26.
|
|
35
|
+
"@types/node": "^26.2.0",
|
|
36
36
|
"typescript": "^7.0.2"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|