create-sdd-project 0.17.1 → 0.17.3

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.
@@ -227,7 +227,8 @@ function adaptAgentContentForProjectType(dest, config, replaceInFileFn) {
227
227
  // WORKFLOW_CORE_PROJECT_TYPE_RULES table above so upgrade-generator.js
228
228
  // can apply the same rules in-memory (smart-diff fallback comparison).
229
229
  // pr-template.md + AGENTS.md + base-standards.mdc remain inline because
230
- // they're not workflow-core files (pr-template is v0.17.2 scope).
230
+ // they're not workflow-core files (pr-template is out of scope for v0.17.1
231
+ // see dev/ROADMAP.md "Known follow-ups" item 2).
231
232
  const wfRules = WORKFLOW_CORE_PROJECT_TYPE_RULES[config.projectType];
232
233
  if (wfRules) {
233
234
  for (const dir of toolDirs) {
@@ -243,7 +244,8 @@ function adaptAgentContentForProjectType(dest, config, replaceInFileFn) {
243
244
  [', `ui-ux-designer`', ''],
244
245
  ]);
245
246
  for (const dir of toolDirs) {
246
- // pr-template: remove ui-components from checklist (v0.17.2 scope)
247
+ // pr-template: remove ui-components from checklist (out of scope for
248
+ // v0.17.1 — see dev/ROADMAP.md "Known follow-ups" item 2)
247
249
  replaceInFileFn(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'pr-template.md'), [
248
250
  [' / ui-components.md', ''],
249
251
  ]);
@@ -232,10 +232,32 @@ function generateInit(config) {
232
232
  console.log(' These files were generated from project analysis. Adjust patterns');
233
233
  console.log(' and conventions to match your team\'s actual practices.');
234
234
 
235
- // Test coverage note
236
- if (scan.tests.estimatedCoverage === 'none' || scan.tests.estimatedCoverage === 'low') {
235
+ // Test coverage note — v0.17.3: broaden signal detection across root,
236
+ // backend, and frontend tests (covers E2E-only setups and monorepos
237
+ // with workspace-level tests). Only show the note when NO test signal
238
+ // exists anywhere.
239
+ const hasAnyTestSignal =
240
+ scan.tests.framework !== 'none' ||
241
+ scan.backendTests.framework !== 'none' ||
242
+ scan.frontendTests.framework !== 'none' ||
243
+ scan.tests.e2eFramework !== null ||
244
+ scan.tests.testFiles > 0 ||
245
+ scan.backendTests.testFiles > 0 ||
246
+ scan.frontendTests.testFiles > 0;
247
+ const maxCoverageRank = (c) =>
248
+ c === 'high' ? 3 : c === 'medium' ? 2 : c === 'low' ? 1 : 0;
249
+ const bestCoverage = Math.max(
250
+ maxCoverageRank(scan.tests.estimatedCoverage),
251
+ maxCoverageRank(scan.backendTests.estimatedCoverage),
252
+ maxCoverageRank(scan.frontendTests.estimatedCoverage),
253
+ );
254
+ if (!hasAnyTestSignal || bestCoverage <= 1 /* none or low */) {
237
255
  console.log('');
238
- const fileCount = scan.tests.testFiles;
256
+ const fileCount = Math.max(
257
+ scan.tests.testFiles,
258
+ scan.backendTests.testFiles,
259
+ scan.frontendTests.testFiles,
260
+ );
239
261
  if (fileCount === 0) {
240
262
  console.log(' 📝 No test files detected.');
241
263
  } else {
@@ -458,8 +480,11 @@ function adaptBackendStandards(template, scan) {
458
480
  const db = scan.backend.db;
459
481
  const lang = scan.language === 'typescript' ? 'TypeScript' : 'JavaScript';
460
482
 
461
- const testFramework = scan.tests.framework !== 'none'
462
- ? capitalizeFramework(scan.tests.framework)
483
+ // v0.17.3: consume backendTests instead of tests so that monorepos with
484
+ // workspace-only test frameworks (e.g., fx: vitest in packages/api) emit
485
+ // the correct Testing line. Single-package: backendTests === tests.
486
+ const testFramework = scan.backendTests.framework !== 'none'
487
+ ? capitalizeFramework(scan.backendTests.framework)
463
488
  : 'Not configured';
464
489
 
465
490
  let stackLines = [
@@ -570,9 +595,14 @@ function adaptFrontendStandards(template, scan) {
570
595
  const state = scan.frontend.state ? `, ${scan.frontend.state}` : '';
571
596
  const lang = scan.language === 'typescript' ? 'TypeScript' : 'JavaScript';
572
597
 
598
+ // v0.17.3: consume frontendTests (see backend equivalent above).
599
+ const frontendTestFramework = scan.frontendTests.framework !== 'none'
600
+ ? capitalizeFramework(scan.frontendTests.framework)
601
+ : 'Not configured';
602
+
573
603
  content = content.replace(
574
604
  /## Technology Stack\n\n[\s\S]*?(?=\n## Project Structure)/,
575
- `## Technology Stack\n\n- **Framework**: ${framework}\n- **Language**: ${lang}\n- **Styling**: ${styling}${components ? `\n- **Components**: ${components.slice(2)}` : ''}${state ? `\n- **State Management**: ${state.slice(2)}` : ''}\n- **Testing**: ${scan.tests.framework !== 'none' ? capitalizeFramework(scan.tests.framework) : 'Not configured'}\n\n`
605
+ `## Technology Stack\n\n- **Framework**: ${framework}\n- **Language**: ${lang}\n- **Styling**: ${styling}${components ? `\n- **Components**: ${components.slice(2)}` : ''}${state ? `\n- **State Management**: ${state.slice(2)}` : ''}\n- **Testing**: ${frontendTestFramework}\n\n`
576
606
  );
577
607
 
578
608
  // Update Project Structure
@@ -848,8 +878,29 @@ function configureProductTracker(template, scan) {
848
878
  content = content.replace('| backend | pending', `| ${featureType} | pending`);
849
879
  }
850
880
 
851
- // Add retrofit testing as first feature if coverage is low
852
- if (scan.tests.estimatedCoverage === 'none' || scan.tests.estimatedCoverage === 'low') {
881
+ // Add retrofit testing as first feature if coverage is low — v0.17.3
882
+ // broaden the gate: consider backend and frontend test coverage, not just
883
+ // root-level. Avoids false F001 recommendation on monorepos that have
884
+ // extensive tests in workspaces. Per Gemini round-3 CRITICAL: also gate
885
+ // on the broader "any test signal" disjunction so E2E-only setups
886
+ // (Playwright/Cypress at root, no unit tests anywhere) don't trigger
887
+ // F001 either — symmetric with the console-warning gate above.
888
+ const rankCoverage = (c) =>
889
+ c === 'high' ? 3 : c === 'medium' ? 2 : c === 'low' ? 1 : 0;
890
+ const bestCov = Math.max(
891
+ rankCoverage(scan.tests.estimatedCoverage),
892
+ rankCoverage(scan.backendTests.estimatedCoverage),
893
+ rankCoverage(scan.frontendTests.estimatedCoverage),
894
+ );
895
+ const hasAnyTestSignal =
896
+ scan.tests.framework !== 'none' ||
897
+ scan.backendTests.framework !== 'none' ||
898
+ scan.frontendTests.framework !== 'none' ||
899
+ scan.tests.e2eFramework !== null ||
900
+ scan.tests.testFiles > 0 ||
901
+ scan.backendTests.testFiles > 0 ||
902
+ scan.frontendTests.testFiles > 0;
903
+ if (!hasAnyTestSignal || bestCov <= 1 /* none or low */) {
853
904
  // Use regex to match the F001 placeholder row resiliently (handles column changes)
854
905
  content = content.replace(
855
906
  /\| F001 \|[^\n]*\n/,
@@ -41,8 +41,23 @@ function formatScanSummary(scanResult) {
41
41
  };
42
42
  lines.push(` Architecture: ${patternLabels[scanResult.srcStructure.pattern] || 'Unknown'}`);
43
43
 
44
- if (scanResult.tests.framework !== 'none') {
45
- lines.push(` Tests: ${scanResult.tests.framework} (${scanResult.tests.testFiles} test files)`);
44
+ // v0.17.3: collect distinct non-'none' frameworks across root, backend,
45
+ // and frontend tests. For mixed monorepos (e.g., fx: vitest in api, jest
46
+ // in web), display joined (e.g., "vitest + jest") so the summary reflects
47
+ // what adapt-functions will actually write. Avoids the v1.1 UX issue
48
+ // where OR-precedence picked root-hoisted jest over workspace vitest.
49
+ const uniqueFrameworks = Array.from(new Set([
50
+ scanResult.tests.framework,
51
+ scanResult.backendTests.framework,
52
+ scanResult.frontendTests.framework,
53
+ ].filter((f) => f !== 'none')));
54
+ if (uniqueFrameworks.length > 0) {
55
+ const totalFiles = Math.max(
56
+ scanResult.tests.testFiles,
57
+ scanResult.backendTests.testFiles,
58
+ scanResult.frontendTests.testFiles,
59
+ );
60
+ lines.push(` Tests: ${uniqueFrameworks.join(' + ')} (${totalFiles} test files)`);
46
61
  } else {
47
62
  lines.push(' Tests: None detected');
48
63
  }
package/lib/meta.js CHANGED
@@ -236,9 +236,10 @@ function writeMeta(dest, hashes) {
236
236
  * - 6 workflow-core files (development-workflow SKILL.md + ticket-template.md
237
237
  * + merge-checklist.md, × 2 tools) — filtered by aiTools
238
238
  *
239
- * Out of scope for v0.17.1 (deferred to v0.17.2): bug-workflow/SKILL.md,
240
- * health-check/SKILL.md, pm-orchestrator/SKILL.md, project-memory/SKILL.md,
241
- * and all references/ files except the 3 development-workflow ones above.
239
+ * Out of scope for v0.17.1 (deferred see dev/ROADMAP.md "Known follow-ups"
240
+ * item 2): bug-workflow/SKILL.md, health-check/SKILL.md, pm-orchestrator/SKILL.md,
241
+ * project-memory/SKILL.md, and all references/ files except the 3
242
+ * development-workflow ones above. Did not land in the v0.17.2 scanner hotfix.
242
243
  */
243
244
  function expectedSmartDiffTrackedPaths(aiTools, projectType) {
244
245
  const paths = new Set();
@@ -273,7 +274,7 @@ function expectedSmartDiffTrackedPaths(aiTools, projectType) {
273
274
 
274
275
  // v0.17.1: development-workflow skill core files — filtered by aiTools.
275
276
  // bug-workflow, health-check, pm-orchestrator, project-memory are OUT OF
276
- // SCOPE for v0.17.1 (deferred to v0.17.2).
277
+ // SCOPE for v0.17.1 (deferred see dev/ROADMAP.md "Known follow-ups" item 2).
277
278
  for (const dir of toolDirs) {
278
279
  paths.add(`${dir}/skills/development-workflow/SKILL.md`);
279
280
  paths.add(`${dir}/skills/development-workflow/references/ticket-template.md`);
package/lib/scanner.js CHANGED
@@ -8,18 +8,36 @@ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.n
8
8
  /**
9
9
  * Scan an existing project directory and return detected configuration.
10
10
  *
11
- * v0.17.1: monorepo-aware. If the root `package.json` does not yield a
12
- * backend/frontend detection AND the project is a monorepo with
13
- * `package.json#workspaces`, enumerate workspace `package.json` files in
14
- * declaration order (pattern outer, lexical inner, deduped by normalized
15
- * path) and run `detectBackend` / `detectFrontend` per workspace. The
16
- * FIRST workspace returning `detected: true` wins and its result is merged
11
+ * v0.17.1: monorepo-aware. If the project is a monorepo with
12
+ * `package.json#workspaces` and the root `package.json` does not yield a
13
+ * complete backend/frontend detection, enumerate workspace `package.json`
14
+ * files in declaration order (pattern outer, lexical inner, deduped by
15
+ * normalized path) and run `detectBackend` / `detectFrontend` per workspace.
16
+ * The FIRST workspace returning a framework wins and its result is merged
17
17
  * into `result.backend` / `result.frontend` with a `workspaceSource` field
18
18
  * recording the detected workspace's relative path (for diagnostics).
19
19
  *
20
- * Scanner additive invariant (v0.17.1): for single-package projects, or
21
- * monorepos where root detection already succeeded, the workspace
22
- * enumeration never fires and the result is byte-identical to v0.17.0.
20
+ * v0.17.2: the "complete detection" gate uses `framework` presence, not
21
+ * the older `detected` flag. This is because `detectBackend` has a
22
+ * partial-detection fallback (see `detectBackend` lines ~259-261) that
23
+ * sets `detected: true` when only `db` or `orm` is found — commonly
24
+ * triggered by a ROOT `.env.example` declaring `DATABASE_URL` + `PORT`
25
+ * in a monorepo whose real backend stack lives under `packages/api`.
26
+ * Under the v0.17.1 guard, that partial detection blocked the workspace
27
+ * enumeration and left `backend.framework` null → `adaptBackendStandards`
28
+ * produced generic placeholders → `adaptAgentsMd` fell back to the
29
+ * `(DDD, Express, Prisma)` template literal. Gating on `!framework`
30
+ * fires the enumeration in that case and correctly promotes
31
+ * workspace-level framework/orm info while preserving root-level
32
+ * env-derived fields (`db`, `port`) that the workspace didn't detect.
33
+ *
34
+ * Scanner additive invariant: for single-package projects, `isMonorepo`
35
+ * is false and the enumeration block is skipped entirely — same behavior
36
+ * as v0.17.0 and v0.17.1, byte-identical output. For monorepos, the
37
+ * v0.17.2 gate is strictly looser than v0.17.1's, so it runs the
38
+ * enumeration in a strict superset of cases. Enumeration only adds
39
+ * info (framework/orm from workspace), never removes it. Therefore
40
+ * v0.17.2 scan output ⊇ v0.17.1 scan output for the same input.
23
41
  */
24
42
  function scan(projectDir) {
25
43
  const pkg = readPackageJson(projectDir);
@@ -28,44 +46,187 @@ function scan(projectDir) {
28
46
  const frontend = detectFrontend(projectDir, pkg);
29
47
  const isMonorepo = detectMonorepo(projectDir, pkg);
30
48
 
31
- // v0.17.1 monorepo fallback
32
- if (isMonorepo && (!backend.detected || !frontend.detected)) {
49
+ let language = detectLanguage(projectDir);
50
+ let srcStructure = detectArchitecture(projectDir, pkg);
51
+ const rootTests = detectTests(projectDir, pkg);
52
+ let backendTests = rootTests;
53
+ let frontendTests = rootTests;
54
+
55
+ // v0.17.3: single-pass monorepo enumeration.
56
+ //
57
+ // Combines v0.17.2's framework promotion (runs only when root lacks a
58
+ // framework, gated by `!backend.framework` / `!frontend.framework`) with
59
+ // v0.17.3's auxiliary-field promotion (language, architecture, tests,
60
+ // frontend styling/components/state) into a single loop that always runs
61
+ // in monorepos.
62
+ //
63
+ // Rationale for always-enumerate: `workspaceSource` is only set when
64
+ // v0.17.2 actually promoted a framework from a workspace (root lacked it).
65
+ // If root has the framework hoisted (e.g., `"next": "^14.2.29"` at root in
66
+ // a Next.js monorepo), v0.17.2's loop never runs → `workspaceSource` is
67
+ // null → auxiliary detection has no workspace handle. v0.17.3 solves this
68
+ // by tracking `primaryBackendWs` / `primaryFrontendWs` independently:
69
+ // always populated for monorepos with a detectable backend/frontend
70
+ // workspace, regardless of where the framework came from. See
71
+ // dev/v0.17.3-plan.md D5 for full rationale + round-1/round-2 review trail.
72
+ let primaryBackendWs = null;
73
+ let primaryFrontendWs = null;
74
+ if (isMonorepo) {
33
75
  const workspaces = enumerateWorkspaces(projectDir, pkg);
34
76
  for (const wsRel of workspaces) {
35
77
  const wsAbs = path.join(projectDir, ...wsRel.split('/'));
36
78
  const wsPkg = readPackageJson(wsAbs);
37
- if (!backend.detected) {
79
+
80
+ if (!primaryBackendWs) {
38
81
  const wsBackend = detectBackend(wsAbs, wsPkg);
39
- if (wsBackend.detected) {
40
- Object.assign(backend, wsBackend, { workspaceSource: wsRel });
82
+ if (wsBackend.framework) {
83
+ primaryBackendWs = wsRel;
84
+ // v0.17.2 promotion — only when root lacked framework. Preserves
85
+ // v0.17.2 semantics byte-equivalent.
86
+ if (!backend.framework) {
87
+ for (const field of Object.keys(wsBackend)) {
88
+ if (wsBackend[field] !== null && wsBackend[field] !== undefined) {
89
+ backend[field] = wsBackend[field];
90
+ }
91
+ }
92
+ backend.workspaceSource = wsRel;
93
+ }
41
94
  }
42
95
  }
43
- if (!frontend.detected) {
96
+
97
+ if (!primaryFrontendWs) {
44
98
  const wsFrontend = detectFrontend(wsAbs, wsPkg);
45
- if (wsFrontend.detected) {
46
- Object.assign(frontend, wsFrontend, { workspaceSource: wsRel });
99
+ if (wsFrontend.framework) {
100
+ primaryFrontendWs = wsRel;
101
+ if (!frontend.framework) {
102
+ for (const field of Object.keys(wsFrontend)) {
103
+ if (wsFrontend[field] !== null && wsFrontend[field] !== undefined) {
104
+ frontend[field] = wsFrontend[field];
105
+ }
106
+ }
107
+ frontend.workspaceSource = wsRel;
108
+ }
109
+ }
110
+ }
111
+
112
+ if (primaryBackendWs && primaryFrontendWs) break;
113
+ }
114
+
115
+ // v0.17.3 auxiliary detection — runs whenever a primary workspace was
116
+ // identified, independent of v0.17.2 promotion.
117
+ if (primaryBackendWs) {
118
+ const wsAbs = path.join(projectDir, ...primaryBackendWs.split('/'));
119
+ const wsPkg = readPackageJson(wsAbs);
120
+ // D1: scalar language merge — TypeScript wins over JavaScript, never
121
+ // demote. 'javascript' is detectLanguage's default; if the workspace
122
+ // returns 'typescript', promote.
123
+ if (detectLanguage(wsAbs) === 'typescript') language = 'typescript';
124
+ // D2: architecture per-field merge.
125
+ // - pattern: workspace wins if it detected ANY known pattern (any
126
+ // non-'unknown' value). Strict non-demotion: workspace 'unknown'
127
+ // never demotes a known root pattern. Workspace known-vs-known
128
+ // resolves to workspace because the workspace is the authoritative
129
+ // source for the primary backend's organization (root pattern in
130
+ // monorepos is often a false signal from the listing fallback when
131
+ // no src/ exists).
132
+ // - dirs: workspace wins when it has any dirs (root's empty []
133
+ // carries no info, and root in monorepos lists workspace dirs not
134
+ // source dirs which is misleading).
135
+ // - boolean hasX flags: OR-merge (true wins, never demoted from true).
136
+ const wsArch = detectArchitecture(wsAbs, wsPkg);
137
+ if (wsArch.pattern !== 'unknown') srcStructure.pattern = wsArch.pattern;
138
+ if (wsArch.dirs && wsArch.dirs.length > 0) srcStructure.dirs = wsArch.dirs;
139
+ for (const field of [
140
+ 'hasControllers',
141
+ 'hasRoutes',
142
+ 'hasModels',
143
+ 'hasServices',
144
+ 'hasDomain',
145
+ 'hasMiddleware',
146
+ 'hasFeatures',
147
+ 'hasHandlers',
148
+ ]) {
149
+ if (wsArch[field] === true) srcStructure[field] = true;
150
+ }
151
+ // D3: backend tests per-field merge (preserves root-level E2E).
152
+ backendTests = mergeWorkspaceTests(rootTests, detectTests(wsAbs, wsPkg));
153
+ }
154
+
155
+ if (primaryFrontendWs) {
156
+ const wsAbs = path.join(projectDir, ...primaryFrontendWs.split('/'));
157
+ const wsPkg = readPackageJson(wsAbs);
158
+ // D4 (revised): promote auxiliary frontend fields (styling,
159
+ // components, state) from the primary frontend workspace,
160
+ // independent of whether the framework was promoted at root.
161
+ const wsFrontendAux = detectFrontend(wsAbs, wsPkg);
162
+ for (const field of ['styling', 'components', 'state']) {
163
+ if (wsFrontendAux[field] !== null && wsFrontendAux[field] !== undefined) {
164
+ frontend[field] = wsFrontendAux[field];
47
165
  }
48
166
  }
49
- if (backend.detected && frontend.detected) break;
167
+ frontendTests = mergeWorkspaceTests(rootTests, detectTests(wsAbs, wsPkg));
50
168
  }
169
+
170
+ // D5 observability: export primary workspace identifiers for diagnostics
171
+ // and smoke-test assertions. `workspaceSource` (v0.17.2) remains set only
172
+ // when promotion happened; `primaryWorkspace` (v0.17.3) is set whenever a
173
+ // workspace was used for aux detection.
174
+ if (primaryBackendWs) backend.primaryWorkspace = primaryBackendWs;
175
+ if (primaryFrontendWs) frontend.primaryWorkspace = primaryFrontendWs;
51
176
  }
52
177
 
53
178
  return {
54
179
  projectName: pkg.name || path.basename(projectDir),
55
180
  description: pkg.description || '',
56
- language: detectLanguage(projectDir),
181
+ language,
57
182
  backend,
58
183
  frontend,
59
184
  isMonorepo,
60
185
  rootDirs: listRootDirs(projectDir),
61
- srcStructure: detectArchitecture(projectDir, pkg),
62
- tests: detectTests(projectDir, pkg),
186
+ srcStructure,
187
+ tests: rootTests,
188
+ backendTests,
189
+ frontendTests,
63
190
  existingDocs: detectExistingDocs(projectDir),
64
191
  gitBranch: detectGitBranch(projectDir),
65
192
  hasGit: fs.existsSync(path.join(projectDir, '.git')),
66
193
  };
67
194
  }
68
195
 
196
+ /**
197
+ * v0.17.3: per-field merge of workspace test detection results into a
198
+ * new object based on root-level test detection.
199
+ *
200
+ * Preserves root-level signals the workspace doesn't see:
201
+ * - e2eFramework (Playwright/Cypress typically installed once at root)
202
+ * - framework (if workspace found no unit framework, keep root's default
203
+ * or hoisted value)
204
+ *
205
+ * Promotes workspace signals that override root:
206
+ * - framework: workspace wins if it detected a non-'none' unit framework
207
+ * - hasConfig: logical OR (either source)
208
+ * - testFiles / testDirs / estimatedCoverage: workspace authoritative when
209
+ * it saw any test files. (Note: `countFilesRecursive` walks from
210
+ * projectDir up to depth 6 traversing into packages/*, so rootTests
211
+ * already aggregates workspace counts. A comparison like
212
+ * `wsTests.testFiles > rootTests.testFiles` would be dead code — see
213
+ * dev/v0.17.3-plan.md D3 v1.2 revision.)
214
+ */
215
+ function mergeWorkspaceTests(rootTests, wsTests) {
216
+ const merged = { ...rootTests };
217
+ if (wsTests.framework !== 'none') {
218
+ merged.framework = wsTests.framework;
219
+ merged.hasConfig = wsTests.hasConfig || rootTests.hasConfig;
220
+ }
221
+ if (wsTests.testFiles > 0) {
222
+ merged.testFiles = wsTests.testFiles;
223
+ merged.testDirs = wsTests.testDirs;
224
+ merged.estimatedCoverage = wsTests.estimatedCoverage;
225
+ }
226
+ if (wsTests.e2eFramework) merged.e2eFramework = wsTests.e2eFramework;
227
+ return merged;
228
+ }
229
+
69
230
  /**
70
231
  * v0.17.1: enumerate workspace paths declared in `pkg.workspaces`.
71
232
  *
@@ -76,7 +237,8 @@ function scan(projectDir) {
76
237
  * - Single-wildcard patterns: `"packages/*"` (expand immediate subdirs)
77
238
  *
78
239
  * Does NOT support: `**` recursive patterns, `!exclude` negation, or
79
- * `pnpm-workspace.yaml` — all deferred to v0.17.2.
240
+ * `pnpm-workspace.yaml` — all deferred (see dev/ROADMAP.md "Known follow-ups"
241
+ * item 3). Did not land in the v0.17.2 scanner hotfix.
80
242
  *
81
243
  * Returns a deterministic, deduplicated array of POSIX-style relative
82
244
  * workspace paths. Ordering: outer = declaration order of patterns; inner
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sdd-project",
3
- "version": "0.17.1",
3
+ "version": "0.17.3",
4
4
  "description": "Create a new SDD DevFlow project with AI-assisted development workflow",
5
5
  "bin": {
6
6
  "create-sdd-project": "bin/cli.js"