@wrongstack/sdd 0.306.4 → 0.307.1

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,6 @@ 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, isIdentifierLikeExclusion, type VibeScopeBoundaries, type VibeSpecSynthesizerResult, type VibeCoderContract, type VibeAuditCheck, type VibeAuditVerdict, type VibeVerificationReport, } from './vibe-protocol.js';
33
+ export { installVibeProtocol, VIBE_PROTOCOL_META_KEY } from './vibe-protocol-wiring.js';
32
34
  //# 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,239 @@ 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 isIdentifierLikeExclusion(excluded) {
5787
+ const trimmed = excluded.trim();
5788
+ if (trimmed.length < 2) return false;
5789
+ if (/^['"`].+['"`]$/.test(trimmed)) return true;
5790
+ return !/\s/.test(trimmed);
5791
+ }
5792
+ function synthesizeVibeSpec(rawPrompt, projectContext = "") {
5793
+ const cleanPrompt = stripVibeTag(rawPrompt);
5794
+ const lines = cleanPrompt.split(/[\n;]+/).map((line) => line.trim()).filter(Boolean);
5795
+ const coreIntent = lines[0] ?? "Implement requested feature according to project standards";
5796
+ const sensibleDefaults = [
5797
+ "Apply sensible defaults for unstated edge cases without breaking existing API contracts",
5798
+ "Follow project conventions and strict type safety"
5799
+ ];
5800
+ if (/admin|role|auth|yetki/i.test(cleanPrompt)) {
5801
+ sensibleDefaults.push("Enforce role-based permission checks before executing privileged actions");
5802
+ }
5803
+ if (/sil|delete|remove|uçur/i.test(cleanPrompt)) {
5804
+ sensibleDefaults.push("Confirm deletion intent and handle non-existent resource gracefully");
5805
+ }
5806
+ if (/sepet|cart|ekle|add|art/i.test(cleanPrompt)) {
5807
+ sensibleDefaults.push("Ensure idempotency and optimistic UI updates for cart/counter operations");
5808
+ }
5809
+ const acceptanceCriteria = lines.map(
5810
+ (line, idx) => `Given user interaction, should correctly handle step ${idx + 1}: ${line}`
5811
+ );
5812
+ if (acceptanceCriteria.length === 0) {
5813
+ acceptanceCriteria.push("Given standard input, action executes and updates state successfully");
5814
+ }
5815
+ const scopeBoundaries = {
5816
+ included: [coreIntent],
5817
+ excluded: ["Unrelated UI refactors", "Unrequested third-party dependency additions"]
5818
+ };
5819
+ const formattedSpecMarkdown = [
5820
+ "## \u{1F30A} VIBE Synthesized Specification",
5821
+ `**Core Intent:** ${coreIntent}`,
5822
+ "",
5823
+ "### \u{1F9E9} Sensible Defaults & Assumptions",
5824
+ ...sensibleDefaults.map((item) => `- ${item}`),
5825
+ "",
5826
+ "### \u{1F4CB} Acceptance Criteria",
5827
+ ...acceptanceCriteria.map((ac) => `- [ ] ${ac}`),
5828
+ "",
5829
+ "### \u{1F6A7} Scope Boundaries",
5830
+ "**Included:**",
5831
+ ...scopeBoundaries.included.map((item) => ` - ${item}`),
5832
+ "**Excluded:**",
5833
+ ...scopeBoundaries.excluded.map((item) => ` - ${item}`),
5834
+ ...projectContext ? ["", "### \u{1F4C1} Context", projectContext] : []
5835
+ ].join("\n");
5836
+ return {
5837
+ coreIntent,
5838
+ sensibleDefaults,
5839
+ acceptanceCriteria,
5840
+ scopeBoundaries,
5841
+ formattedSpecMarkdown
5842
+ };
5843
+ }
5844
+ function buildCoderContract(spec, targetFiles = []) {
5845
+ const instructions = [
5846
+ `Implement core intent: ${spec.coreIntent}`,
5847
+ ...spec.sensibleDefaults.map((d) => `Respect default: ${d}`),
5848
+ ...spec.acceptanceCriteria.map((ac) => `Satisfy acceptance criterion: ${ac}`),
5849
+ ...spec.scopeBoundaries.excluded.map((exc) => `DO NOT introduce: ${exc}`)
5850
+ ];
5851
+ return {
5852
+ specSummary: spec.coreIntent,
5853
+ instructions,
5854
+ targetFiles,
5855
+ acceptanceChecklist: spec.acceptanceCriteria
5856
+ };
5857
+ }
5858
+ function auditVibeExecution(input) {
5859
+ const { spec, coderOutput } = input;
5860
+ const checks = [];
5861
+ const reworkDirectives = [];
5862
+ const hasCoderOutput = coderOutput.trim().length > 0;
5863
+ checks.push({
5864
+ id: "intent-fidelity",
5865
+ name: "Intent Fidelity (Did coder address the synthesized spec?)",
5866
+ passed: hasCoderOutput,
5867
+ details: hasCoderOutput ? "Coder output produced against synthesized spec" : "No code changes provided by coder"
5868
+ });
5869
+ if (!hasCoderOutput) {
5870
+ reworkDirectives.push("Coder must generate implementation matching the spec.");
5871
+ }
5872
+ let scopeClean = true;
5873
+ for (const excluded of spec.scopeBoundaries.excluded) {
5874
+ if (!isIdentifierLikeExclusion(excluded)) continue;
5875
+ if (coderOutput.toLowerCase().includes(excluded.toLowerCase())) {
5876
+ scopeClean = false;
5877
+ checks.push({
5878
+ id: "scope-bleed",
5879
+ name: `Scope Violation Check (${excluded})`,
5880
+ passed: false,
5881
+ details: `Coder output appears to touch excluded scope: ${excluded}`
5882
+ });
5883
+ reworkDirectives.push(`Remove changes related to excluded scope: ${excluded}`);
5884
+ }
5885
+ }
5886
+ if (scopeClean) {
5887
+ checks.push({
5888
+ id: "scope-fidelity",
5889
+ name: "Scope Boundary Check",
5890
+ passed: true,
5891
+ details: "Implementation stays within specified boundaries without scope bleed"
5892
+ });
5893
+ }
5894
+ checks.push({
5895
+ id: "acceptance-criteria",
5896
+ name: "Acceptance Criteria Alignment",
5897
+ passed: hasCoderOutput,
5898
+ details: hasCoderOutput ? `Coder produced output against a contract of ${spec.acceptanceCriteria.length} acceptance criteria (not independently executed)` : "No coder output to align with acceptance criteria"
5899
+ });
5900
+ const allPassed = checks.every((c) => c.passed);
5901
+ const score = Math.round(checks.filter((c) => c.passed).length / checks.length * 100);
5902
+ return {
5903
+ verdict: allPassed ? "PASS" : "REJECT",
5904
+ score,
5905
+ checks,
5906
+ ...reworkDirectives.length > 0 ? { reworkDirectives } : {},
5907
+ summary: allPassed ? "Auditor approved: Code aligns with synthesized spec, no hallucinations or scope violations detected." : `Auditor rejected: ${reworkDirectives.join("; ")}`
5908
+ };
5909
+ }
5910
+ function formatVibeReport(rawPrompt, spec, coder, audit) {
5911
+ return [
5912
+ "### \u{1F30A} [VIBE Protocol: Spec-Synthesizer]",
5913
+ ...rawPrompt.trim().length > 0 ? [`- **\u{1F5E3}\uFE0F Raw Prompt:** ${rawPrompt.trim()}`] : [],
5914
+ `- **\u{1F3AF} Core Intent:** ${spec.coreIntent}`,
5915
+ "- **\u{1F9E9} Sensible Defaults:**",
5916
+ ...spec.sensibleDefaults.map((d) => ` \u2022 ${d}`),
5917
+ "- **\u{1F4CB} Acceptance Criteria:**",
5918
+ ...spec.acceptanceCriteria.map((ac) => ` - [ ] ${ac}`),
5919
+ "- _Auditor did not independently execute these criteria._",
5920
+ "",
5921
+ "---",
5922
+ "",
5923
+ "### \u{1F4BB} [Coder Contract]",
5924
+ `- **Target Files:** ${coder.targetFiles.length > 0 ? coder.targetFiles.join(", ") : "Inferred from project context"}`,
5925
+ `- **Directives:** ${coder.instructions.length} generated guidelines`,
5926
+ "",
5927
+ "---",
5928
+ "",
5929
+ "### \u{1F6E1}\uFE0F [Auditor Verdict]",
5930
+ `- **Verdict:** ${audit.verdict === "PASS" ? "\u2705 PASS" : "\u274C REJECT"} (Score: ${audit.score}%)`,
5931
+ `- **Summary:** ${audit.summary}`,
5932
+ ...audit.checks.map((c) => ` - ${c.passed ? "\u2705" : "\u274C"} **${c.name}**: ${c.details}`),
5933
+ ...audit.reworkDirectives && audit.reworkDirectives.length > 0 ? ["", "**\u{1F527} Rework Instructions:**", ...audit.reworkDirectives.map((r) => ` - ${r}`)] : []
5934
+ ].join("\n");
5935
+ }
5936
+
5937
+ // src/vibe-protocol-wiring.ts
5938
+ import { hasVibeTag } from "@wrongstack/requirement-intake";
5939
+ var VIBE_PROTOCOL_META_KEY = "vibeProtocol";
5940
+ function formatCoderInput(spec, coder) {
5941
+ return [
5942
+ "[vibe_protocol]",
5943
+ "The user explicitly requested the VIBE three-stage verification protocol.",
5944
+ "Implement the request using the synthesized specification and coder contract below.",
5945
+ "Do not merely describe the contract: perform the requested work, verify it, and report the result.",
5946
+ "",
5947
+ spec.formattedSpecMarkdown,
5948
+ "",
5949
+ "## Coder Contract",
5950
+ ...coder.instructions.map((instruction) => `- ${instruction}`),
5951
+ "[/vibe_protocol]"
5952
+ ].join("\n");
5953
+ }
5954
+ function installVibeProtocol(pipelines) {
5955
+ let active;
5956
+ const userInput = {
5957
+ name: "VibeProtocolInput",
5958
+ owner: "sdd",
5959
+ async handler(payload, next) {
5960
+ active = void 0;
5961
+ delete payload.ctx.meta[VIBE_PROTOCOL_META_KEY];
5962
+ if (!hasVibeTag(payload.text)) return next(payload);
5963
+ const rawPrompt = payload.text;
5964
+ const spec = synthesizeVibeSpec(rawPrompt);
5965
+ const coder = buildCoderContract(spec);
5966
+ const contractBlock = { type: "text", text: formatCoderInput(spec, coder) };
5967
+ payload.content = [...payload.content, contractBlock];
5968
+ payload.text = `${payload.text}
5969
+
5970
+ ${contractBlock.text}`;
5971
+ active = { rawPrompt, spec, coder, ctx: payload.ctx };
5972
+ payload.ctx.meta[VIBE_PROTOCOL_META_KEY] = {
5973
+ isVibeMode: true,
5974
+ stage: "coder",
5975
+ synthesizer: spec,
5976
+ coder
5977
+ };
5978
+ return next(payload);
5979
+ }
5980
+ };
5981
+ const response = {
5982
+ name: "VibeProtocolAuditor",
5983
+ owner: "sdd",
5984
+ async handler(value, next) {
5985
+ const run = active;
5986
+ if (!run || value.content.some((block) => block.type === "tool_use")) return next(value);
5987
+ const coderOutput = value.content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
5988
+ if (!coderOutput) return next(value);
5989
+ const audit = auditVibeExecution({
5990
+ rawPrompt: run.rawPrompt,
5991
+ spec: run.spec,
5992
+ coderOutput
5993
+ });
5994
+ const markdown = formatVibeReport(run.rawPrompt, run.spec, run.coder, audit);
5995
+ const report = {
5996
+ isVibeMode: true,
5997
+ stage: audit.verdict === "PASS" ? "passed" : "auditor",
5998
+ synthesizer: run.spec,
5999
+ coder: run.coder,
6000
+ audit,
6001
+ markdown
6002
+ };
6003
+ active = void 0;
6004
+ run.ctx.meta[VIBE_PROTOCOL_META_KEY] = report;
6005
+ return next({
6006
+ ...value,
6007
+ content: [...value.content, { type: "text", text: `
6008
+
6009
+ ${markdown}` }]
6010
+ });
6011
+ }
6012
+ };
6013
+ pipelines.userInput.use(userInput);
6014
+ pipelines.response.use(response);
6015
+ }
5710
6016
  export {
5711
6017
  AISpecBuilder,
5712
6018
  AutoExecutor,
@@ -5727,6 +6033,7 @@ export {
5727
6033
  TaskGenerator,
5728
6034
  TaskGraphStore,
5729
6035
  TaskTracker3 as TaskTracker,
6036
+ VIBE_PROTOCOL_META_KEY,
5730
6037
  analyzeCriticalPath,
5731
6038
  applySddLifecycle,
5732
6039
  assertSpecTaskGraphCoverage,
@@ -5734,8 +6041,10 @@ export {
5734
6041
  assertTaskGraphRequirementCoverage,
5735
6042
  assessGeneratedTaskAtomicity,
5736
6043
  assessTaskNodeAtomicity,
6044
+ auditVibeExecution,
5737
6045
  buildBoardSnapshot,
5738
6046
  buildBoardTasks,
6047
+ buildCoderContract,
5739
6048
  cleanupSddWorktrees,
5740
6049
  cleanupStaleSddWorktrees,
5741
6050
  cleanupStaleWorktrees,
@@ -5745,12 +6054,15 @@ export {
5745
6054
  destroySddProject,
5746
6055
  evaluateTaskGraphRequirementCoverage,
5747
6056
  extractVerificationCommand,
6057
+ formatVibeReport,
5748
6058
  gatherProjectContext,
5749
6059
  getTemplate,
5750
6060
  hasConflictMarkers,
6061
+ installVibeProtocol,
5751
6062
  intakeToInterviewKickoff,
5752
6063
  isAISpecSession,
5753
6064
  isExplanatoryText,
6065
+ isIdentifierLikeExclusion,
5754
6066
  listTemplates,
5755
6067
  makeAcceptanceCriteriaVerifier,
5756
6068
  makeCommandVerifier,
@@ -5769,6 +6081,7 @@ export {
5769
6081
  splitGraphNode,
5770
6082
  startInterviewFromIntake,
5771
6083
  startSddRun,
6084
+ synthesizeVibeSpec,
5772
6085
  templateToMarkdown,
5773
6086
  tokenizeCommand
5774
6087
  };
@@ -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,10 @@
1
+ import type { AgentPipelines } from '@wrongstack/core/agent';
2
+ export declare const VIBE_PROTOCOL_META_KEY = "vibeProtocol";
3
+ /**
4
+ * Installs the VIBE protocol on Agent pipelines. CLI/TUI (lifecycle-plugins)
5
+ * and standalone WebUI (createAgentServices) both call this so a tagged user
6
+ * turn receives the synthesized spec and coder contract before the model runs;
7
+ * the first final text response is then audited and receives the report.
8
+ */
9
+ export declare function installVibeProtocol(pipelines: AgentPipelines): void;
10
+ //# sourceMappingURL=vibe-protocol-wiring.d.ts.map
@@ -0,0 +1,80 @@
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
+ * Policy sentences are not code artifacts. Only identifier-like exclusions
57
+ * (packages, files, symbols) are safe to substring-match in coder output.
58
+ */
59
+ export declare function isIdentifierLikeExclusion(excluded: string): boolean;
60
+ /**
61
+ * 1. Spec-Synthesizer: Transforms unstructured, chaotic vibe prompt into a formal spec.
62
+ */
63
+ export declare function synthesizeVibeSpec(rawPrompt: string, projectContext?: string): VibeSpecSynthesizerResult;
64
+ /**
65
+ * 2. Coder Stage: Generates coder contract strictly from the synthesized spec.
66
+ */
67
+ export declare function buildCoderContract(spec: VibeSpecSynthesizerResult, targetFiles?: string[]): VibeCoderContract;
68
+ /**
69
+ * 3. Auditor Stage: Cross-checks raw vibe vs synthesized spec vs coder output.
70
+ */
71
+ export declare function auditVibeExecution(input: {
72
+ rawPrompt?: string;
73
+ spec: VibeSpecSynthesizerResult;
74
+ coderOutput: string;
75
+ }): VibeAuditVerdict;
76
+ /**
77
+ * Formats a comprehensive 3-stage Markdown report.
78
+ */
79
+ export declare function formatVibeReport(rawPrompt: string, spec: VibeSpecSynthesizerResult, coder: VibeCoderContract, audit: VibeAuditVerdict): string;
80
+ //# 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.1",
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/requirement-intake": "0.307.1",
31
+ "@wrongstack/kanban": "0.307.1",
32
+ "@wrongstack/core": "0.307.1"
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": {