@wrongstack/sdd 1.0.17 → 1.0.24

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.js CHANGED
@@ -739,9 +739,9 @@ var SpecDrivenDev = class {
739
739
  };
740
740
 
741
741
  // src/spec-store.ts
742
+ import { randomUUID } from "node:crypto";
742
743
  import * as fsp from "node:fs/promises";
743
744
  import * as path from "node:path";
744
- import { randomUUID } from "node:crypto";
745
745
  import { atomicWrite, ensureDir } from "@wrongstack/core/utils";
746
746
  var SpecStore = class {
747
747
  baseDir;
@@ -833,7 +833,7 @@ var SpecStore = class {
833
833
  const dir = path.resolve(this.baseDir);
834
834
  const resolved = path.resolve(dir, `${id}.json`);
835
835
  const rel = path.relative(dir, resolved);
836
- if (rel.startsWith("..") || path.isAbsolute(rel) || rel.includes(path.sep)) {
836
+ if (rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel) || rel.includes(path.sep)) {
837
837
  throw new Error(`Invalid spec id: ${JSON.stringify(id)}`);
838
838
  }
839
839
  return resolved;
@@ -970,7 +970,7 @@ var TaskGraphStore = class {
970
970
  const dir = path2.resolve(this.baseDir);
971
971
  const resolved = path2.resolve(dir, `${id}.json`);
972
972
  const rel = path2.relative(dir, resolved);
973
- if (rel.startsWith("..") || path2.isAbsolute(rel) || rel.includes(path2.sep)) {
973
+ if (rel === ".." || rel.startsWith(`..${path2.sep}`) || path2.isAbsolute(rel) || rel.includes(path2.sep)) {
974
974
  throw new Error(`Invalid task-graph id: ${JSON.stringify(id)}`);
975
975
  }
976
976
  return resolved;
@@ -1358,14 +1358,16 @@ var SddBoardStore = class {
1358
1358
  });
1359
1359
  }
1360
1360
  async removeFromIndex(runId) {
1361
- const current = await this.readIndex();
1362
- const index = {
1363
- version: 1,
1364
- entries: current.entries.filter((entry) => entry.runId !== runId).map((entry) => ({ ...entry }))
1365
- };
1366
- await atomicWrite3(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
1367
- this.cachedIndex = index;
1368
- this.cachedIndexSignature = await this.indexSignature();
1361
+ await withFileLock(this.indexPath, async () => {
1362
+ const current = await this.readIndex();
1363
+ const index = {
1364
+ version: 1,
1365
+ entries: current.entries.filter((entry) => entry.runId !== runId).map((entry) => ({ ...entry }))
1366
+ };
1367
+ await atomicWrite3(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
1368
+ this.cachedIndex = index;
1369
+ this.cachedIndexSignature = await this.indexSignature();
1370
+ });
1369
1371
  }
1370
1372
  async indexSignature() {
1371
1373
  try {
@@ -2011,6 +2013,7 @@ var AISpecBuilder = class {
2011
2013
  store;
2012
2014
  minQuestions;
2013
2015
  maxQuestions;
2016
+ projectContext;
2014
2017
  sessionPath;
2015
2018
  sessionPersistence;
2016
2019
  pendingSessionWrite = Promise.resolve();
@@ -2018,6 +2021,7 @@ var AISpecBuilder = class {
2018
2021
  this.store = opts.store;
2019
2022
  this.minQuestions = opts.minQuestions ?? 2;
2020
2023
  this.maxQuestions = opts.maxQuestions ?? 10;
2024
+ this.projectContext = opts.projectContext ?? "";
2021
2025
  this.sessionPath = opts.sessionPath;
2022
2026
  this.sessionPersistence = opts.sessionPersistence;
2023
2027
  this.session = {
@@ -2025,7 +2029,7 @@ var AISpecBuilder = class {
2025
2029
  phase: "questioning",
2026
2030
  title: "",
2027
2031
  userIntent: "",
2028
- projectContext: opts.projectContext ?? "",
2032
+ projectContext: this.projectContext,
2029
2033
  answers: [],
2030
2034
  questionCount: 0,
2031
2035
  approved: false,
@@ -2105,10 +2109,11 @@ var AISpecBuilder = class {
2105
2109
  void this.saveSession();
2106
2110
  }
2107
2111
  // ── Session Lifecycle ─────────────────────────────────────────────────────
2108
- /** Start a new session with a title and optional intent. */
2109
- startSession(title, intent) {
2112
+ /** Start a new session with a title, optional intent, and per-session context. */
2113
+ startSession(title, intent, projectContext) {
2110
2114
  this.session.title = title;
2111
2115
  this.session.userIntent = intent ?? "";
2116
+ this.session.projectContext = projectContext ?? this.projectContext;
2112
2117
  this.session.phase = "questioning";
2113
2118
  this.session.updatedAt = Date.now();
2114
2119
  this.autoSave();
@@ -2308,6 +2313,7 @@ var AISpecBuilder = class {
2308
2313
  this.session.phase = "questioning";
2309
2314
  this.session.title = "";
2310
2315
  this.session.userIntent = "";
2316
+ this.session.projectContext = this.projectContext;
2311
2317
  this.session.answers = [];
2312
2318
  this.session.questionCount = 0;
2313
2319
  this.session.spec = void 0;
@@ -2500,9 +2506,9 @@ var SddInterviewDriver = class {
2500
2506
  });
2501
2507
  }
2502
2508
  /** Begin a fresh interview. Returns the first AI prompt (a question kickoff). */
2503
- start(title, intent) {
2509
+ start(title, intent, projectContext) {
2504
2510
  this.builder.resetForNewInterview();
2505
- this.builder.startSession(title, intent);
2511
+ this.builder.startSession(title, intent, projectContext);
2506
2512
  this.tracker = null;
2507
2513
  this.graph = null;
2508
2514
  this.resumedFromDisk = false;
@@ -4493,10 +4499,13 @@ function createKanbanSddSessionPersistence(projectRoot, legacySessionPath) {
4493
4499
  await pending;
4494
4500
  },
4495
4501
  async delete() {
4496
- await writeChain;
4497
- await deleteKanbanWorkflowState2(projectRoot, SDD_SESSION_WORKFLOW_ID);
4498
- revision = 0;
4499
- if (legacySessionPath) await fsp5.unlink(legacySessionPath).catch(() => void 0);
4502
+ const pending = writeChain.then(async () => {
4503
+ await deleteKanbanWorkflowState2(projectRoot, SDD_SESSION_WORKFLOW_ID);
4504
+ revision = 0;
4505
+ if (legacySessionPath) await fsp5.unlink(legacySessionPath).catch(() => void 0);
4506
+ });
4507
+ writeChain = pending.catch(() => void 0);
4508
+ await pending;
4500
4509
  }
4501
4510
  };
4502
4511
  }
@@ -4581,7 +4590,7 @@ function intakeToInterviewKickoff(record) {
4581
4590
  }
4582
4591
  function startInterviewFromIntake(driver, record) {
4583
4592
  const kickoff = intakeToInterviewKickoff(record);
4584
- return driver.start(kickoff.title, kickoff.intent);
4593
+ return driver.start(kickoff.title, kickoff.intent, kickoff.projectContext);
4585
4594
  }
4586
4595
 
4587
4596
  // src/spec-templates.ts
@@ -5051,7 +5060,9 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
5051
5060
  }
5052
5061
  function computeParallelGroups(graph, blockedByMap) {
5053
5062
  const groups = [];
5054
- const assigned = /* @__PURE__ */ new Set();
5063
+ const assigned = new Set(
5064
+ Array.from(graph.nodes.values()).filter((n) => n.status === "completed").map((n) => n.id)
5065
+ );
5055
5066
  const nodes = Array.from(graph.nodes.values()).filter((n) => n.status !== "completed");
5056
5067
  const remaining = new Set(nodes.map((n) => n.id));
5057
5068
  while (remaining.size > 0) {
@@ -25,9 +25,9 @@ export interface IntakeInterviewKickoff {
25
25
  export declare function intakeToInterviewKickoff(record: RequirementIntakeRecord): IntakeInterviewKickoff;
26
26
  /**
27
27
  * Start a spec-builder interview from an intake record. Returns the first AI
28
- * prompt (a question kickoff), ready to feed the agent loop. The driver should
29
- * be constructed with `projectContext: kickoff.projectContext` so the
30
- * questioning prompt already knows the collected facts.
28
+ * prompt (a question kickoff), ready to feed the agent loop. Collected intake
29
+ * facts are passed as this interview's project context so the questioning
30
+ * prompt already knows them.
31
31
  */
32
32
  export declare function startInterviewFromIntake(driver: Pick<SddInterviewDriver, 'start'>, record: RequirementIntakeRecord): string;
33
33
  //# sourceMappingURL=intake-kickoff.d.ts.map
@@ -78,7 +78,7 @@ export declare class SddInterviewDriver {
78
78
  private resumedFromDisk;
79
79
  constructor(opts: SddInterviewDriverOptions);
80
80
  /** Begin a fresh interview. Returns the first AI prompt (a question kickoff). */
81
- start(title: string, intent?: string): string;
81
+ start(title: string, intent?: string, projectContext?: string): string;
82
82
  /**
83
83
  * Resume a previously-persisted interview. Re-hydrates the task
84
84
  * graph too when one was already produced. Returns true if a session loaded.
@@ -25,6 +25,7 @@ export declare class AISpecBuilder {
25
25
  private readonly store;
26
26
  private readonly minQuestions;
27
27
  private readonly maxQuestions;
28
+ private readonly projectContext;
28
29
  private readonly sessionPath?;
29
30
  private readonly sessionPersistence?;
30
31
  private pendingSessionWrite;
@@ -37,8 +38,8 @@ export declare class AISpecBuilder {
37
38
  deleteSession(): Promise<void>;
38
39
  /** Auto-save helper. saveSession() already handles best-effort persistence. */
39
40
  private autoSave;
40
- /** Start a new session with a title and optional intent. */
41
- startSession(title: string, intent?: string): void;
41
+ /** Start a new session with a title, optional intent, and per-session context. */
42
+ startSession(title: string, intent?: string, projectContext?: string): void;
42
43
  /** Get current session state (readonly). */
43
44
  getSession(): Readonly<AISpecSession>;
44
45
  /** Get the current phase. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/sdd",
3
- "version": "1.0.17",
3
+ "version": "1.0.24",
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,10 +27,10 @@
27
27
  "!dist/**/*.map"
28
28
  ],
29
29
  "dependencies": {
30
- "@wrongstack/core": "1.0.17",
31
- "@wrongstack/kanban": "1.0.17",
32
- "@wrongstack/primitives": "1.0.17",
33
- "@wrongstack/requirement-intake": "1.0.17"
30
+ "@wrongstack/core": "1.0.24",
31
+ "@wrongstack/kanban": "1.0.24",
32
+ "@wrongstack/primitives": "1.0.24",
33
+ "@wrongstack/requirement-intake": "1.0.24"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^26.5.1",