@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 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
- if (!this.builder.getSession().spec) {
2513
- const spec = this.builder.tryParseSpecFromOutput(text);
2514
- if (spec) {
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
- if (this.builder.getPhase() === "implementation") {
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
- const wt = this.opts.worktrees;
3737
- if (!wt) return { ok: true };
3738
- const handle = this.taskWorktrees.get(task.id);
3739
- if (!handle) return { ok: true };
3740
- try {
3741
- await wt.commitAll(handle, `sdd(${task.title}): ${task.id}`);
3742
- const baseShaBefore = await wt.baseHead(handle);
3743
- const baseSha = this.opts.conflictResolver ? baseShaBefore : null;
3744
- const res = await wt.merge(handle, {
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
- const wt = this.opts.worktrees;
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
- const wt = this.opts.worktrees;
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.taskWorktrees.delete(taskId);
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
@@ -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.306.4",
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.306.4",
31
- "@wrongstack/core": "0.306.4",
32
- "@wrongstack/requirement-intake": "0.306.4"
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.1.2",
35
+ "@types/node": "^26.2.0",
36
36
  "typescript": "^7.0.2"
37
37
  },
38
38
  "publishConfig": {