@webpresso/agent-kit 0.21.4 → 0.23.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.
Files changed (194) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +93 -66
  4. package/bin/_run.js +143 -1
  5. package/bin/runtime-manifest.json +40 -0
  6. package/catalog/AGENTS.md.tpl +7 -6
  7. package/catalog/agent/commands/plan-refine.md +3 -3
  8. package/catalog/agent/commands/pll.md +2 -0
  9. package/catalog/agent/guides/parallel-execution.md +2 -0
  10. package/catalog/agent/rules/extraction-parity.md +27 -1
  11. package/catalog/agent/rules/public-package-safety.md +24 -1
  12. package/catalog/agent/skills/plan-refine/SKILL.md +5 -4
  13. package/catalog/agent/skills/pll/SKILL.md +1 -0
  14. package/catalog/base-kit/.github/workflows/ci.webpresso.yml.tmpl +33 -0
  15. package/catalog/base-kit/commitlint.config.ts.tmpl +1 -3
  16. package/catalog/base-kit/e2e/fixtures/smoke.html.tmpl +13 -0
  17. package/catalog/base-kit/e2e/smoke.spec.ts.tmpl +13 -0
  18. package/catalog/base-kit/oxlint.config.ts.tmpl +26 -0
  19. package/catalog/base-kit/playwright.config.ts.tmpl +10 -0
  20. package/catalog/base-kit/src/quality-sample.test.ts.tmpl +19 -0
  21. package/catalog/base-kit/src/quality-sample.ts.tmpl +11 -0
  22. package/catalog/base-kit/stryker.config.ts.tmpl +14 -0
  23. package/catalog/base-kit/tsconfig.json.tmpl +9 -0
  24. package/catalog/base-kit/vitest.config.ts.tmpl +10 -0
  25. package/catalog/docs/templates/adr.md +1 -1
  26. package/catalog/docs/templates/blueprint.md +2 -0
  27. package/catalog/docs/templates/blueprint.yaml +16 -15
  28. package/catalog/docs/templates/guide.md +1 -1
  29. package/catalog/docs/templates/postmortem.md +1 -1
  30. package/catalog/docs/templates/research.md +1 -1
  31. package/catalog/docs/templates/runbook.md +1 -1
  32. package/catalog/docs/templates/system.md +12 -3
  33. package/catalog/docs/templates/tech-debt.md +1 -0
  34. package/commands/blueprint.md +10 -12
  35. package/dist/esm/audit/blueprint-db-consistency.d.ts +1 -1
  36. package/dist/esm/audit/blueprint-db-consistency.js +6 -8
  37. package/dist/esm/audit/blueprint-lifecycle-sql.js +10 -3
  38. package/dist/esm/audit/cloudflare-deploy-contract.d.ts +3 -0
  39. package/dist/esm/audit/cloudflare-deploy-contract.js +64 -0
  40. package/dist/esm/audit/no-legacy-cli-bin.d.ts +3 -0
  41. package/dist/esm/audit/no-legacy-cli-bin.js +100 -0
  42. package/dist/esm/audit/package-surface.js +14 -1
  43. package/dist/esm/audit/repo-guardrails.js +40 -13
  44. package/dist/esm/audit/resolve-audit-script.d.ts +24 -0
  45. package/dist/esm/audit/resolve-audit-script.js +27 -0
  46. package/dist/esm/audit/roadmap-links.js +23 -10
  47. package/dist/esm/blueprint/core/schema.d.ts +8 -8
  48. package/dist/esm/blueprint/core/schema.js +2 -2
  49. package/dist/esm/blueprint/db/enums.d.ts +1 -1
  50. package/dist/esm/blueprint/db/ingester.js +18 -10
  51. package/dist/esm/blueprint/index.d.ts +0 -1
  52. package/dist/esm/blueprint/index.js +0 -2
  53. package/dist/esm/blueprint/lifecycle/audit.js +9 -2
  54. package/dist/esm/blueprint/lifecycle/local.js +15 -4
  55. package/dist/esm/blueprint/local.d.ts +0 -3
  56. package/dist/esm/blueprint/local.js +0 -2
  57. package/dist/esm/blueprint/service/BlueprintCreationService.js +16 -8
  58. package/dist/esm/blueprint/service/BlueprintService.js +37 -19
  59. package/dist/esm/blueprint/service/scanner.js +73 -9
  60. package/dist/esm/blueprint/tracked-document/schema.d.ts +2 -2
  61. package/dist/esm/blueprint/utils/document-paths.d.ts +23 -0
  62. package/dist/esm/blueprint/utils/document-paths.js +91 -0
  63. package/dist/esm/blueprint/utils/package-assets.d.ts +11 -0
  64. package/dist/esm/blueprint/utils/package-assets.js +33 -4
  65. package/dist/esm/build/package-manifest.js +7 -0
  66. package/dist/esm/build/release-policy.d.ts +27 -0
  67. package/dist/esm/build/release-policy.js +29 -0
  68. package/dist/esm/build/runtime-targets.d.ts +13 -0
  69. package/dist/esm/build/runtime-targets.js +48 -0
  70. package/dist/esm/build/sync-catalog-doc-templates.d.ts +23 -0
  71. package/dist/esm/build/sync-catalog-doc-templates.js +93 -0
  72. package/dist/esm/cli/auto-update/detect-pm.d.ts +15 -0
  73. package/dist/esm/cli/auto-update/detect-pm.js +24 -9
  74. package/dist/esm/cli/auto-update/skip.js +9 -1
  75. package/dist/esm/cli/bundle/agent-command-inventory.d.ts +120 -0
  76. package/dist/esm/cli/bundle/agent-command-inventory.js +100 -0
  77. package/dist/esm/cli/bundle/index.d.ts +17 -0
  78. package/dist/esm/cli/bundle/index.js +15 -0
  79. package/dist/esm/cli/cli.d.ts +1 -1
  80. package/dist/esm/cli/cli.js +49 -5
  81. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  82. package/dist/esm/cli/commands/audit.js +4 -7
  83. package/dist/esm/cli/commands/blueprint/router.js +16 -10
  84. package/dist/esm/cli/commands/blueprint/template-resolver.js +8 -4
  85. package/dist/esm/cli/commands/hook.d.ts +8 -0
  86. package/dist/esm/cli/commands/hook.js +47 -0
  87. package/dist/esm/cli/commands/init/host-visibility.js +4 -2
  88. package/dist/esm/cli/commands/init/index.js +80 -7
  89. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +12 -0
  90. package/dist/esm/cli/commands/init/scaffold-base-kit.js +142 -7
  91. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/codex-ownership.js +9 -1
  92. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +130 -20
  93. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.d.ts +65 -0
  94. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.js +64 -0
  95. package/dist/esm/cli/commands/package-manager.d.ts +15 -0
  96. package/dist/esm/cli/commands/package-manager.js +42 -0
  97. package/dist/esm/cli/commands/test.d.ts +1 -0
  98. package/dist/esm/cli/commands/test.js +2 -1
  99. package/dist/esm/cli/commands/typecheck.js +10 -19
  100. package/dist/esm/cli/package-scripts.d.ts +12 -0
  101. package/dist/esm/cli/package-scripts.js +59 -0
  102. package/dist/esm/cli/utils.js +3 -22
  103. package/dist/esm/cli/wp-extensions.d.ts +14 -0
  104. package/dist/esm/cli/wp-extensions.js +34 -0
  105. package/dist/esm/config/docs-lint/schemas/common.d.ts +1 -1
  106. package/dist/esm/config/docs-lint/schemas/implementation-plan.d.ts +2 -2
  107. package/dist/esm/config/docs-lint/schemas/parent-roadmap.d.ts +1 -1
  108. package/dist/esm/config/stryker/index.d.ts +85 -0
  109. package/dist/esm/config/stryker/index.js +31 -0
  110. package/dist/esm/e2e/command-builder.js +35 -7
  111. package/dist/esm/e2e/config.d.ts +56 -0
  112. package/dist/esm/e2e/config.js +114 -0
  113. package/dist/esm/e2e/execution.js +8 -0
  114. package/dist/esm/e2e/run-planner.js +2 -0
  115. package/dist/esm/e2e/types.d.ts +3 -0
  116. package/dist/esm/format/index.js +5 -1
  117. package/dist/esm/hooks/guard-switch/index.d.ts +1 -1
  118. package/dist/esm/hooks/guard-switch/index.js +22 -14
  119. package/dist/esm/hooks/post-tool/lint-after-edit.d.ts +1 -0
  120. package/dist/esm/hooks/post-tool/lint-after-edit.js +5 -2
  121. package/dist/esm/hooks/pretool-guard/validators/file-conventions.js +1 -1
  122. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.d.ts +6 -0
  123. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +27 -2
  124. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +2 -1
  125. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +59 -34
  126. package/dist/esm/hooks/pretool-guard/validators/plan-frontmatter.js +3 -3
  127. package/dist/esm/hooks/shared/routing-block.js +18 -4
  128. package/dist/esm/hooks/shared/validators/blueprint.js +3 -0
  129. package/dist/esm/hooks/stop/qa-changed-files.d.ts +1 -0
  130. package/dist/esm/hooks/stop/qa-changed-files.js +5 -2
  131. package/dist/esm/lint/index.js +3 -1
  132. package/dist/esm/mcp/auto-discover.d.ts +2 -0
  133. package/dist/esm/mcp/auto-discover.js +14 -6
  134. package/dist/esm/mcp/blueprint-server.js +379 -80
  135. package/dist/esm/mcp/cli.js +21 -0
  136. package/dist/esm/mcp/runners/test.js +15 -0
  137. package/dist/esm/mcp/server.d.ts +7 -0
  138. package/dist/esm/mcp/server.js +16 -27
  139. package/dist/esm/mcp/tools/_registry.d.ts +3 -0
  140. package/dist/esm/mcp/tools/_registry.js +21 -0
  141. package/dist/esm/mcp/tools/audit.d.ts +1 -0
  142. package/dist/esm/mcp/tools/audit.js +13 -8
  143. package/dist/esm/mcp/tools/typecheck.js +4 -2
  144. package/dist/esm/mutation/affected.d.ts +9 -0
  145. package/dist/esm/mutation/affected.js +36 -0
  146. package/dist/esm/package.json +8 -0
  147. package/dist/esm/runtime/package-version.d.ts +2 -0
  148. package/dist/esm/runtime/package-version.js +43 -0
  149. package/dist/esm/test/command-builder.d.ts +4 -0
  150. package/dist/esm/test/command-builder.js +28 -3
  151. package/dist/esm/test-helpers/hermetic-env.d.ts +25 -0
  152. package/dist/esm/test-helpers/hermetic-env.js +31 -0
  153. package/dist/esm/tool-runtime/index.d.ts +5 -0
  154. package/dist/esm/tool-runtime/index.js +24 -0
  155. package/dist/esm/tool-runtime/resolve-runner.d.ts +16 -0
  156. package/dist/esm/tool-runtime/resolve-runner.js +42 -0
  157. package/dist/esm/typecheck/index.js +4 -2
  158. package/dist/esm/wp-extension/index.d.ts +50 -0
  159. package/dist/esm/wp-extension/index.js +268 -0
  160. package/package.json +75 -46
  161. package/skills/plan-refine/SKILL.md +5 -4
  162. package/skills/pll/SKILL.md +1 -0
  163. package/dist/esm/blueprint/dag/cycle-detector.d.ts +0 -12
  164. package/dist/esm/blueprint/dag/cycle-detector.js +0 -46
  165. package/dist/esm/blueprint/dag/executor.d.ts +0 -140
  166. package/dist/esm/blueprint/dag/executor.js +0 -292
  167. package/dist/esm/blueprint/dag/index.d.ts +0 -20
  168. package/dist/esm/blueprint/dag/index.js +0 -17
  169. package/dist/esm/blueprint/dag/interfaces.d.ts +0 -56
  170. package/dist/esm/blueprint/dag/interfaces.js +0 -13
  171. package/dist/esm/blueprint/dag/local/independence.d.ts +0 -107
  172. package/dist/esm/blueprint/dag/local/independence.js +0 -231
  173. package/dist/esm/blueprint/dag/local/index.d.ts +0 -14
  174. package/dist/esm/blueprint/dag/local/index.js +0 -14
  175. package/dist/esm/blueprint/dag/local/package-graph.d.ts +0 -66
  176. package/dist/esm/blueprint/dag/local/package-graph.js +0 -148
  177. package/dist/esm/blueprint/dag/plan-parser.d.ts +0 -54
  178. package/dist/esm/blueprint/dag/plan-parser.js +0 -236
  179. package/dist/esm/blueprint/dag/task-graph-algorithms.d.ts +0 -13
  180. package/dist/esm/blueprint/dag/task-graph-algorithms.js +0 -236
  181. package/dist/esm/blueprint/dag/task-graph.d.ts +0 -171
  182. package/dist/esm/blueprint/dag/task-graph.js +0 -370
  183. package/dist/esm/blueprint/dag/types.d.ts +0 -17
  184. package/dist/esm/blueprint/dag/types.js +0 -2
  185. package/dist/esm/blueprint/graph/index.d.ts +0 -5
  186. package/dist/esm/blueprint/graph/index.js +0 -5
  187. package/dist/esm/blueprint/graph/mermaid-parser.d.ts +0 -3
  188. package/dist/esm/blueprint/graph/mermaid-parser.js +0 -93
  189. package/dist/esm/blueprint/graph/mermaid-serializer.d.ts +0 -3
  190. package/dist/esm/blueprint/graph/mermaid-serializer.js +0 -20
  191. package/dist/esm/blueprint/graph/schema.d.ts +0 -89
  192. package/dist/esm/blueprint/graph/schema.js +0 -104
  193. package/dist/esm/blueprint/graph/task-graph-adapter.d.ts +0 -6
  194. package/dist/esm/blueprint/graph/task-graph-adapter.js +0 -30
@@ -47,6 +47,7 @@ const SKIP_DIRECTORIES = new Set([
47
47
  '.omx',
48
48
  '.omc',
49
49
  '.stryker-tmp',
50
+ '.webpresso-packed-surface',
50
51
  'node_modules',
51
52
  'dist',
52
53
  'build',
@@ -393,6 +394,7 @@ function stagePackedFiles(root, destinationRoot, candidate, packedFiles) {
393
394
  function runSecretlint(stageRoot, packageRoot) {
394
395
  const rcPath = findSecretlintRc(packageRoot);
395
396
  const outputFile = join(stageRoot, '.secretlint-output.json');
397
+ const toolRoot = findSecretlintToolRoot(packageRoot) ?? findSecretlintToolRoot(process.cwd());
396
398
  const args = ['exec', 'secretlint', '--format', 'json', '--output', outputFile, '--no-gitignore'];
397
399
  if (rcPath) {
398
400
  args.push('--secretlintrc', rcPath);
@@ -403,7 +405,7 @@ function runSecretlint(stageRoot, packageRoot) {
403
405
  args.push(join(stageRoot, '**/*'));
404
406
  try {
405
407
  execFileSync('pnpm', args, {
406
- cwd: packageRoot,
408
+ cwd: toolRoot ?? packageRoot,
407
409
  encoding: 'utf8',
408
410
  stdio: ['ignore', 'pipe', 'pipe'],
409
411
  maxBuffer: 20 * 1024 * 1024,
@@ -421,6 +423,17 @@ function runSecretlint(stageRoot, packageRoot) {
421
423
  rmSync(outputFile, { force: true });
422
424
  }
423
425
  }
426
+ function findSecretlintToolRoot(start) {
427
+ let current = resolve(start);
428
+ while (true) {
429
+ if (existsSync(join(current, 'node_modules', '.bin', 'secretlint')))
430
+ return current;
431
+ const parent = dirname(current);
432
+ if (parent === current)
433
+ return undefined;
434
+ current = parent;
435
+ }
436
+ }
424
437
  function findSecretlintRc(root) {
425
438
  const candidates = [
426
439
  '.secretlintrc.json',
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
2
2
  import { join, relative, resolve, sep } from 'node:path';
3
3
  import matter from 'gray-matter';
4
4
  import { blueprintDerivedHandoffSchema } from '#execution/types';
5
+ import { BLUEPRINT_OVERVIEW_FILENAME, isBlueprintSupportingMarkdownRelativePath, parseBlueprintDocumentRelativePath, } from '#utils/document-paths.js';
5
6
  import { validateLoreTrailers } from './commit-message-lore.js';
6
7
  const DEFAULT_COMMIT_TYPES = [
7
8
  'feat',
@@ -209,33 +210,59 @@ export function auditBlueprintLifecycle(rootDirectory = process.cwd(), options =
209
210
  if (!existsSync(statusRoot))
210
211
  continue;
211
212
  for (const entry of readdirSync(statusRoot, { withFileTypes: true })) {
212
- if (!entry.isDirectory())
213
+ if (!entry.isDirectory() && !entry.isFile())
213
214
  continue;
214
- const overviewPath = join(statusRoot, entry.name, '_overview.md');
215
- checked += 1;
216
- if (!existsSync(overviewPath)) {
217
- violations.push({
218
- file: relativePath(root, overviewPath),
219
- message: 'Missing _overview.md',
220
- });
215
+ const canonicalPath = entry.isDirectory()
216
+ ? join(statusRoot, entry.name, BLUEPRINT_OVERVIEW_FILENAME)
217
+ : join(statusRoot, entry.name);
218
+ const relativeBlueprintPath = relative(blueprintsRoot, canonicalPath);
219
+ const parsedPath = parseBlueprintDocumentRelativePath(relativeBlueprintPath);
220
+ if (entry.isDirectory()) {
221
+ checked += 1;
222
+ if (!existsSync(canonicalPath)) {
223
+ const hasNestedMarkdown = readdirSync(join(statusRoot, entry.name), {
224
+ withFileTypes: true,
225
+ }).some((nested) => nested.isFile() && nested.name.endsWith('.md') && nested.name !== 'README.md');
226
+ if (hasNestedMarkdown) {
227
+ violations.push({
228
+ file: relativePath(root, canonicalPath),
229
+ message: 'Blueprint folders with supporting markdown must include _overview.md',
230
+ });
231
+ }
232
+ else {
233
+ violations.push({
234
+ file: relativePath(root, canonicalPath),
235
+ message: 'Missing _overview.md',
236
+ });
237
+ }
238
+ continue;
239
+ }
240
+ }
241
+ else {
242
+ if (!parsedPath || isBlueprintSupportingMarkdownRelativePath(relativeBlueprintPath)) {
243
+ continue;
244
+ }
245
+ checked += 1;
246
+ }
247
+ if (!parsedPath) {
221
248
  continue;
222
249
  }
223
- const raw = readFileSync(overviewPath, 'utf8');
250
+ const raw = readFileSync(canonicalPath, 'utf8');
224
251
  const frontmatter = matter(raw).data;
225
252
  if (frontmatter.type !== 'blueprint' && frontmatter.type !== 'parent-roadmap') {
226
253
  violations.push({
227
- file: relativePath(root, overviewPath),
228
- message: 'Blueprint overview must use type: blueprint or parent-roadmap',
254
+ file: relativePath(root, canonicalPath),
255
+ message: 'Blueprint markdown must use type: blueprint or parent-roadmap',
229
256
  });
230
257
  }
231
258
  if (frontmatter.status !== status) {
232
259
  violations.push({
233
- file: relativePath(root, overviewPath),
260
+ file: relativePath(root, canonicalPath),
234
261
  message: `Blueprint status must match folder (${status})`,
235
262
  });
236
263
  }
237
264
  violations.push(...validateBlueprintLinkingFrontmatter({
238
- file: relativePath(root, overviewPath),
265
+ file: relativePath(root, canonicalPath),
239
266
  frontmatter,
240
267
  status,
241
268
  }));
@@ -0,0 +1,24 @@
1
+ export interface ResolveAuditScriptDeps {
2
+ /** `import.meta.url` of the calling module (`cli/commands/audit` or `mcp/tools/audit`). */
3
+ moduleUrl: string;
4
+ /** Existence probe; defaults to `fs.existsSync`. Injected in tests to model each layout. */
5
+ exists?: (url: URL) => boolean;
6
+ }
7
+ /**
8
+ * Resolve the absolute path of a Bun audit script (`audit-tph.ts`,
9
+ * `audit-tph-e2e.ts`) living in the sibling `audit/` directory two levels up
10
+ * from the caller.
11
+ *
12
+ * `../../audit/` is correct relative to the caller in BOTH layouts:
13
+ * - dev: `src/{cli,mcp}/.../audit.ts` → `src/audit/`
14
+ * - published: `dist/esm/{cli,mcp}/.../audit.js` → `dist/esm/audit/`
15
+ *
16
+ * The npm tarball ships only `dist/` (never `src/`), so the dev `.ts` source is
17
+ * absent there — fall back to the compiled `.js` sibling the build emits. The
18
+ * previous CLI resolver instead hand-rolled a `<bundle>/src/audit/<name>.ts`
19
+ * path that does not exist in dist, which made `bun` fail with
20
+ * "Module not found"; the MCP twin reached for the unshipped `src/audit/` via
21
+ * `resolvePackageAsset` and failed the same way.
22
+ */
23
+ export declare function resolveAuditScriptPath(name: string, { moduleUrl, exists }: ResolveAuditScriptDeps): string;
24
+ //# sourceMappingURL=resolve-audit-script.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ /**
4
+ * Resolve the absolute path of a Bun audit script (`audit-tph.ts`,
5
+ * `audit-tph-e2e.ts`) living in the sibling `audit/` directory two levels up
6
+ * from the caller.
7
+ *
8
+ * `../../audit/` is correct relative to the caller in BOTH layouts:
9
+ * - dev: `src/{cli,mcp}/.../audit.ts` → `src/audit/`
10
+ * - published: `dist/esm/{cli,mcp}/.../audit.js` → `dist/esm/audit/`
11
+ *
12
+ * The npm tarball ships only `dist/` (never `src/`), so the dev `.ts` source is
13
+ * absent there — fall back to the compiled `.js` sibling the build emits. The
14
+ * previous CLI resolver instead hand-rolled a `<bundle>/src/audit/<name>.ts`
15
+ * path that does not exist in dist, which made `bun` fail with
16
+ * "Module not found"; the MCP twin reached for the unshipped `src/audit/` via
17
+ * `resolvePackageAsset` and failed the same way.
18
+ */
19
+ export function resolveAuditScriptPath(name, { moduleUrl, exists = existsSync }) {
20
+ const sourceUrl = new URL(`../../audit/${name}`, moduleUrl);
21
+ if (exists(sourceUrl)) {
22
+ return fileURLToPath(sourceUrl);
23
+ }
24
+ const compiledUrl = new URL(`../../audit/${name.replace(/\.ts$/, '.js')}`, moduleUrl);
25
+ return fileURLToPath(compiledUrl);
26
+ }
27
+ //# sourceMappingURL=resolve-audit-script.js.map
@@ -1,6 +1,7 @@
1
1
  import matter from 'gray-matter';
2
2
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
3
3
  import { join, relative, resolve, sep } from 'node:path';
4
+ import { BLUEPRINT_OVERVIEW_FILENAME, isBlueprintSupportingMarkdownRelativePath, parseBlueprintDocumentRelativePath, } from '#utils/document-paths.js';
4
5
  const BLUEPRINT_STATUSES = [
5
6
  'draft',
6
7
  'planned',
@@ -11,7 +12,7 @@ const BLUEPRINT_STATUSES = [
11
12
  ];
12
13
  const BLUEPRINT_STATUS_PATTERN = BLUEPRINT_STATUSES.join('|');
13
14
  const ACTIVE_BLUEPRINT_STATUSES = new Set(['draft', 'planned', 'in-progress', 'parked']);
14
- const LOCAL_BLUEPRINT_REFERENCE_PATTERN = new RegExp(String.raw `^(?:blueprints/)?(?:${BLUEPRINT_STATUS_PATTERN})/[A-Za-z0-9._-]+(?:/_overview\.md)?$`);
15
+ const LOCAL_BLUEPRINT_REFERENCE_PATTERN = new RegExp(String.raw `^(?:blueprints/)?(?:${BLUEPRINT_STATUS_PATTERN})/[A-Za-z0-9._-]+(?:\.md|/_overview\.md)?$`);
15
16
  const GITHUB_URL_PATTERN = /https?:\/\/github\.com\//i;
16
17
  const ABSOLUTE_FILE_REFERENCE_PATTERN = /(?:^|[\s(])(?:\/|[A-Za-z]:[\\/]|file:\/\/)/i;
17
18
  const LEGACY_CROSS_REPO_LABEL_PATTERN = /cross-repo:/i;
@@ -112,22 +113,32 @@ function readBlueprintRecords(root, blueprintsRoot) {
112
113
  if (!existsSync(statusRoot))
113
114
  continue;
114
115
  for (const entry of readdirSync(statusRoot, { withFileTypes: true })) {
115
- if (!entry.isDirectory())
116
+ const canonicalPath = entry.isDirectory()
117
+ ? join(statusRoot, entry.name, BLUEPRINT_OVERVIEW_FILENAME)
118
+ : entry.isFile() && entry.name.endsWith('.md')
119
+ ? join(statusRoot, entry.name)
120
+ : null;
121
+ if (!canonicalPath || !existsSync(canonicalPath))
116
122
  continue;
117
- const overviewPath = join(statusRoot, entry.name, '_overview.md');
118
- if (!existsSync(overviewPath))
123
+ const relativeBlueprintPath = relative(blueprintsRoot, canonicalPath);
124
+ if (!parseBlueprintDocumentRelativePath(relativeBlueprintPath) ||
125
+ isBlueprintSupportingMarkdownRelativePath(relativeBlueprintPath)) {
119
126
  continue;
120
- const raw = readFileSync(overviewPath, 'utf8');
127
+ }
128
+ const raw = readFileSync(canonicalPath, 'utf8');
121
129
  const data = matter(raw).data;
122
130
  const type = data.type === 'parent-roadmap' ? 'parent-roadmap' : 'blueprint';
123
131
  const parentRoadmap = typeof data.parent_roadmap === 'string' && data.parent_roadmap.trim()
124
132
  ? data.parent_roadmap.trim()
125
133
  : undefined;
126
- const key = `${status}/${entry.name}`;
134
+ const parsedPath = parseBlueprintDocumentRelativePath(relativeBlueprintPath);
135
+ if (!parsedPath)
136
+ continue;
137
+ const key = `${status}/${parsedPath.slug}`;
127
138
  records.push({
128
- file: relativePath(root, overviewPath),
139
+ file: relativePath(root, canonicalPath),
129
140
  key,
130
- name: entry.name,
141
+ name: parsedPath.slug,
131
142
  ...(parentRoadmap ? { parentRoadmap } : {}),
132
143
  raw,
133
144
  slug: key,
@@ -146,16 +157,18 @@ function indexBlueprints(records) {
146
157
  byKey.set(record.name, record);
147
158
  byKey.set(`blueprints/${record.key}`, record);
148
159
  byKey.set(`blueprints/${record.key}/_overview.md`, record);
160
+ byKey.set(`blueprints/${record.key}.md`, record);
149
161
  byKey.set(`${record.key}/_overview.md`, record);
162
+ byKey.set(`${record.key}.md`, record);
150
163
  }
151
164
  return byKey;
152
165
  }
153
166
  function extractWaveMapChildren(markdown) {
154
167
  const refs = new Set();
155
- const pathPattern = new RegExp(String.raw `(?:blueprints/)?(${BLUEPRINT_STATUS_PATTERN})/([A-Za-z0-9._-]+)(?:/_overview\.md)?`, 'g');
168
+ const pathPattern = new RegExp(String.raw `(?:blueprints/)?(${BLUEPRINT_STATUS_PATTERN})/([A-Za-z0-9._-]+)(?:\.md|/_overview\.md)?`, 'g');
156
169
  for (const match of markdown.matchAll(pathPattern)) {
157
170
  const status = match[1];
158
- const slug = match[2];
171
+ const slug = match[2]?.replace(/\.md$/, '');
159
172
  if (!status || !slug)
160
173
  continue;
161
174
  refs.add(`${status}/${slug}`);
@@ -19,8 +19,8 @@ export declare const planStatusSchema: z.ZodEnum<{
19
19
  completed: "completed";
20
20
  draft: "draft";
21
21
  planned: "planned";
22
- "in-progress": "in-progress";
23
22
  parked: "parked";
23
+ "in-progress": "in-progress";
24
24
  archived: "archived";
25
25
  }>;
26
26
  /**
@@ -30,8 +30,8 @@ export declare const lifecycleBlueprintStatusSchema: z.ZodEnum<{
30
30
  completed: "completed";
31
31
  draft: "draft";
32
32
  planned: "planned";
33
- "in-progress": "in-progress";
34
33
  parked: "parked";
34
+ "in-progress": "in-progress";
35
35
  archived: "archived";
36
36
  }>;
37
37
  /**
@@ -71,8 +71,8 @@ export declare const crossRepoDependencySchema: z.ZodObject<{
71
71
  completed: "completed";
72
72
  draft: "draft";
73
73
  planned: "planned";
74
- "in-progress": "in-progress";
75
74
  parked: "parked";
75
+ "in-progress": "in-progress";
76
76
  archived: "archived";
77
77
  }>>;
78
78
  }, z.core.$strip>;
@@ -82,7 +82,7 @@ export declare const crossRepoDependencySchema: z.ZodObject<{
82
82
  * Required fields:
83
83
  * - type: `blueprint` or `parent-roadmap`
84
84
  * - status: Current plan status
85
- * - complexity: Estimated effort using t-shirt sizing
85
+ * - complexity: Estimated effort using t-shirt sizing (defaults to `M` when omitted for legacy blueprints)
86
86
  *
87
87
  * Optional fields:
88
88
  * - last_updated: Date plan was last modified (YYYY-MM-DD)
@@ -101,17 +101,17 @@ export declare const planFrontmatterSchema: z.ZodObject<{
101
101
  completed: "completed";
102
102
  draft: "draft";
103
103
  planned: "planned";
104
- "in-progress": "in-progress";
105
104
  parked: "parked";
105
+ "in-progress": "in-progress";
106
106
  archived: "archived";
107
107
  }>;
108
- complexity: z.ZodEnum<{
108
+ complexity: z.ZodDefault<z.ZodEnum<{
109
109
  XS: "XS";
110
110
  S: "S";
111
111
  M: "M";
112
112
  L: "L";
113
113
  XL: "XL";
114
- }>;
114
+ }>>;
115
115
  last_updated: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodDate]>>;
116
116
  created: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodDate]>>;
117
117
  progress: z.ZodOptional<z.ZodString>;
@@ -146,8 +146,8 @@ export declare const planFrontmatterSchema: z.ZodObject<{
146
146
  completed: "completed";
147
147
  draft: "draft";
148
148
  planned: "planned";
149
- "in-progress": "in-progress";
150
149
  parked: "parked";
150
+ "in-progress": "in-progress";
151
151
  archived: "archived";
152
152
  }>>;
153
153
  }, z.core.$strip>>>;
@@ -64,7 +64,7 @@ export const crossRepoDependencySchema = z.object({
64
64
  * Required fields:
65
65
  * - type: `blueprint` or `parent-roadmap`
66
66
  * - status: Current plan status
67
- * - complexity: Estimated effort using t-shirt sizing
67
+ * - complexity: Estimated effort using t-shirt sizing (defaults to `M` when omitted for legacy blueprints)
68
68
  *
69
69
  * Optional fields:
70
70
  * - last_updated: Date plan was last modified (YYYY-MM-DD)
@@ -77,7 +77,7 @@ export const planFrontmatterSchema = z.object({
77
77
  title: z.string().optional(),
78
78
  description: z.string().optional(),
79
79
  status: planStatusSchema,
80
- complexity: complexitySchema,
80
+ complexity: complexitySchema.default('M'),
81
81
  last_updated: z.union([z.string(), z.date()]).optional(),
82
82
  created: z.union([z.string(), z.date()]).optional(),
83
83
  progress: z.string().optional(),
@@ -13,8 +13,8 @@ export declare const blueprintStatusSchema: z.ZodEnum<{
13
13
  completed: "completed";
14
14
  draft: "draft";
15
15
  planned: "planned";
16
- "in-progress": "in-progress";
17
16
  parked: "parked";
17
+ "in-progress": "in-progress";
18
18
  archived: "archived";
19
19
  }>;
20
20
  export declare const blueprintComplexitySchema: z.ZodEnum<{
@@ -2,18 +2,24 @@ import { createHash } from 'node:crypto';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { Database } from '#db/sqlite.js';
5
- import { glob } from 'glob';
6
5
  import { parseBlueprintForDb } from './parser/blueprint-db-parser.js';
7
6
  import { parseTechDebtForDb } from './parser/tech-debt-db-parser.js';
8
7
  import { resolvesCrossRepo } from '#cross-repo/resolver.js';
8
+ import { scanBlueprintDirectory } from '#service/scanner.js';
9
9
  import { resolveBlueprintRoot } from '#utils/blueprint-root.js';
10
+ import { parseBlueprintDocumentRelativePath } from '#utils/document-paths.js';
10
11
  import { resolveTechDebtRoot } from '#utils/tech-debt-root.js';
12
+ import { glob } from 'glob';
11
13
  // ---------------------------------------------------------------------------
12
14
  // Helpers
13
15
  // ---------------------------------------------------------------------------
14
- function deriveSlugFromBlueprintPath(filePath) {
15
- // blueprints/<status>/<slug>/_overview.md → slug is the grandparent dir name
16
- return path.basename(path.dirname(filePath));
16
+ function deriveSlugFromBlueprintPath(filePath, blueprintRoot) {
17
+ const relativePath = path.relative(blueprintRoot, filePath);
18
+ const parsed = parseBlueprintDocumentRelativePath(relativePath);
19
+ if (!parsed) {
20
+ throw new Error(`Not a canonical blueprint document: ${filePath}`);
21
+ }
22
+ return parsed.slug;
17
23
  }
18
24
  function deriveSlugFromTechDebtPath(filePath) {
19
25
  // tech-debt/<status>/h-NNN-slug.md → slug is the basename without extension
@@ -47,9 +53,9 @@ function isAllowedCrossOrg(db, sourceOrg, targetOrg) {
47
53
  // ---------------------------------------------------------------------------
48
54
  // Blueprint ingester
49
55
  // ---------------------------------------------------------------------------
50
- function upsertBlueprint(db, filePath, _cwd) {
56
+ function upsertBlueprint(db, filePath, blueprintRoot) {
51
57
  const content = readFileSync(filePath, 'utf8');
52
- const slug = deriveSlugFromBlueprintPath(filePath);
58
+ const slug = deriveSlugFromBlueprintPath(filePath, blueprintRoot);
53
59
  const parsed = parseBlueprintForDb(content, filePath, slug);
54
60
  const now = Date.now();
55
61
  const upsertBp = db.prepare(`INSERT INTO blueprints
@@ -197,18 +203,20 @@ export async function ingestBlueprints(opts) {
197
203
  const errors = [];
198
204
  let ingested = 0;
199
205
  const blueprintRoot = resolveBlueprintRoot(cwd);
200
- const pattern = path.join(blueprintRoot, '**', '_overview.md').replace(/\\/g, '/');
201
- const files = await glob(pattern, { absolute: true, nodir: true });
206
+ const files = scanBlueprintDirectory({
207
+ baseDir: blueprintRoot,
208
+ includeSpecialFolders: true,
209
+ }).map((entry) => entry.path);
202
210
  for (const filePath of files) {
203
211
  try {
204
212
  const content = readFileSync(filePath, 'utf8');
205
- const slug = deriveSlugFromBlueprintPath(filePath);
213
+ const slug = deriveSlugFromBlueprintPath(filePath, blueprintRoot);
206
214
  const newHash = createHash('sha256').update(content).digest('hex');
207
215
  if (!dryRun) {
208
216
  const existing = existingBlueprintHash(db, slug);
209
217
  if (existing === newHash)
210
218
  continue;
211
- upsertBlueprint(db, filePath, cwd);
219
+ upsertBlueprint(db, filePath, blueprintRoot);
212
220
  }
213
221
  ingested++;
214
222
  }
@@ -16,7 +16,6 @@ export { validatePlanTemplate } from './core/validation/template.js';
16
16
  export { type BlueprintDiff, type DiffChange, type DiffFieldChange, formatDiffForDisplay, generateBlueprintDiff, } from './history/diff.js';
17
17
  export { checkAllCheckboxes, checkFirstCheckbox, extractCodeBlocks, extractTaskSection, updateBlockedReason, updateTaskStatus, } from './markdown/helpers.js';
18
18
  export { applyBlueprintLifecycle, type BlueprintLifecycleIntent, type BlueprintLifecycleResult, type LifecycleTaskStatus, } from './lifecycle/engine.js';
19
- export { type GraphEdge, type GraphEdgeType, type GraphLayout, type GraphNode, type GraphNodeType, type NormalizedGraph, parseMermaidToGraph, serializeGraphToMermaid, taskGraphToNormalizedGraph, } from './graph/index.js';
20
19
  export type { BlueprintQueryFilters, BlueprintQueryResult, BlueprintQuerySummary, BlueprintRecord, BlueprintSortField, BlueprintSortOptions, Complexity, SortDirection, TaskStatus, } from './query/types.js';
21
20
  export { isBlueprintStatus, isComplexity, isTaskStatus } from './query/types.js';
22
21
  export { BlueprintNotFoundError } from './utils/errors.js';
@@ -17,8 +17,6 @@ export { formatDiffForDisplay, generateBlueprintDiff, } from './history/diff.js'
17
17
  // Markdown helpers (pure functions)
18
18
  export { checkAllCheckboxes, checkFirstCheckbox, extractCodeBlocks, extractTaskSection, updateBlockedReason, updateTaskStatus, } from './markdown/helpers.js';
19
19
  export { applyBlueprintLifecycle, } from './lifecycle/engine.js';
20
- // Graph model + Mermaid integration
21
- export { parseMermaidToGraph, serializeGraphToMermaid, taskGraphToNormalizedGraph, } from './graph/index.js';
22
20
  export { isBlueprintStatus, isComplexity, isTaskStatus } from './query/types.js';
23
21
  export { BlueprintNotFoundError } from './utils/errors.js';
24
22
  // Evidence Contract (F10) — pin per-kind evidence rules at zod parse time.
@@ -8,11 +8,18 @@ import { readBlueprintExecutionMetadata } from '#execution/metadata';
8
8
  import { BlueprintService } from '#service/BlueprintService';
9
9
  import { scanBlueprintDirectory } from '#service/scanner';
10
10
  import { resolveBlueprintRoot } from '#utils/blueprint-root';
11
+ import { parseBlueprintDocumentRelativePath } from '#utils/document-paths.js';
11
12
  import { relativeBlueprintSlug } from './local.js';
12
13
  function isBlueprintOverview(file) {
13
14
  const normalized = file.replace(/\\/g, '/');
14
- return (normalized.endsWith('/_overview.md') &&
15
- (normalized.includes('webpresso/blueprints/') || normalized.includes('blueprints/')));
15
+ const roots = ['webpresso/blueprints/', 'blueprints/'];
16
+ for (const root of roots) {
17
+ const index = normalized.indexOf(root);
18
+ if (index === -1)
19
+ continue;
20
+ return parseBlueprintDocumentRelativePath(normalized.slice(index + root.length)) !== null;
21
+ }
22
+ return false;
16
23
  }
17
24
  function normalizePath(file) {
18
25
  return file.replace(/\\/g, '/');
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { applyBlueprintLifecycle } from '#lifecycle/engine';
4
4
  import { scanBlueprintDirectory } from '#service/scanner';
5
5
  import { resolveBlueprintRoot } from '#utils/blueprint-root';
6
+ import { getBlueprintDocumentPaths } from '#utils/document-paths.js';
6
7
  const BLUEPRINT_SLUG_SEGMENT_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7
8
  function isStatusSegment(segment) {
8
9
  return (segment === 'draft' ||
@@ -73,10 +74,20 @@ export async function applyBlueprintLifecycleToFile(projectRoot, slug, intent) {
73
74
  const location = await resolveBlueprintFile(projectRoot, slug);
74
75
  const raw = await readFile(location.path, 'utf-8');
75
76
  const mutation = applyBlueprintLifecycle(raw, location.slug, intent);
77
+ const relativeSlug = relativeBlueprintSlug(location.slug);
78
+ const isFlatFile = path.basename(location.path) !== '_overview.md';
76
79
  const sourceDir = path.dirname(location.path);
77
- const targetDir = path.join(baseDir, mutation.targetStatus, relativeBlueprintSlug(location.slug));
78
- const targetPath = path.join(targetDir, '_overview.md');
79
- if (sourceDir !== targetDir) {
80
+ const targetDocumentPaths = getBlueprintDocumentPaths(baseDir, mutation.targetStatus, relativeSlug);
81
+ const targetDir = targetDocumentPaths.directory;
82
+ const targetPath = isFlatFile ? targetDocumentPaths.flat : targetDocumentPaths.folder;
83
+ if (isFlatFile) {
84
+ if (location.path !== targetPath) {
85
+ await mkdir(path.dirname(targetPath), { recursive: true });
86
+ await rename(location.path, targetPath);
87
+ await tryRemoveEmptyParent(sourceDir);
88
+ }
89
+ }
90
+ else if (sourceDir !== targetDir) {
80
91
  await mkdir(path.dirname(targetDir), { recursive: true });
81
92
  await rename(sourceDir, targetDir);
82
93
  await tryRemoveEmptyParent(path.dirname(sourceDir));
@@ -84,7 +95,7 @@ export async function applyBlueprintLifecycleToFile(projectRoot, slug, intent) {
84
95
  await writeFile(targetPath, mutation.markdown, 'utf-8');
85
96
  return {
86
97
  ...mutation,
87
- moved: sourceDir !== targetDir,
98
+ moved: isFlatFile ? location.path !== targetPath : sourceDir !== targetDir,
88
99
  path: targetPath,
89
100
  slug: location.slug,
90
101
  };
@@ -4,9 +4,6 @@
4
4
  * These exports use Node.js APIs (fs, simple-git) and are NOT compatible with Cloudflare Workers.
5
5
  * For Workers-safe functions, use the main 'webpresso/blueprint' entry point.
6
6
  */
7
- export type { FalseDependency, ParallelizeResult, TaskFiles, TaskPairAnalysis, } from './dag/local/independence.js';
8
- export { createMockPackageGraph, IndependenceDetector } from './dag/local/independence.js';
9
- export { createMockFileSystem, PackageGraph, realFileSystem } from './dag/local/package-graph.js';
10
7
  export { type AcceptanceCriteria, type Blueprint, buildRoadmapModel, buildBlueprintProgressBridgeState, type BlueprintStatus, type BlueprintTaskStatus, checkAcceptanceCriteria, checkAllCheckboxes, checkChangelog, checkFirstCheckbox, complexitySchema, type CriteriaResult, extractTaskSection, formatDiffForDisplay, generateBlueprintDiff, isBlueprintStatus, isComplexity, isTaskStatus, lifecycleBlueprintStatusSchema, normalizeOmxTeamTaskSnapshot, type OmxTeamTaskSnapshot, type Phase, parseBlueprint, type PlanComplexity, type PlanFrontmatter, planStatusSchema, projectBlueprintLifecycleFromRuntime, resolveBlueprintProgressBridgePath, type RoadmapModel, type RoadmapNode, type RoadmapRollup, type RoadmapLike, serializeBlueprint, type Task, taskStatusSchema, type TaskStatusValue, updateBlockedReason, updateTaskStatus, type ValidationResult, validateEmbeddedPhases, validatePlanLinks, validatePlanState, validatePlanTemplate, } from './index.js';
11
8
  export { BlueprintCreationService, type BlueprintCreationServiceOptions, type BlueprintDraft, type CreateBlueprintInput, type CreatedBlueprint, } from './service/BlueprintCreationService.js';
12
9
  export { type BlueprintQueryOptions, BlueprintService, type BlueprintSummary, } from './service/BlueprintService.js';
@@ -4,8 +4,6 @@
4
4
  * These exports use Node.js APIs (fs, simple-git) and are NOT compatible with Cloudflare Workers.
5
5
  * For Workers-safe functions, use the main 'webpresso/blueprint' entry point.
6
6
  */
7
- export { createMockPackageGraph, IndependenceDetector } from './dag/local/independence.js';
8
- export { createMockFileSystem, PackageGraph, realFileSystem } from './dag/local/package-graph.js';
9
7
  // Workers-safe exports for convenience (explicit re-export to avoid wildcard)
10
8
  export { buildRoadmapModel, buildBlueprintProgressBridgeState, checkAcceptanceCriteria, checkAllCheckboxes, checkChangelog, checkFirstCheckbox, complexitySchema, extractTaskSection, formatDiffForDisplay, generateBlueprintDiff, isBlueprintStatus, isComplexity, isTaskStatus, lifecycleBlueprintStatusSchema, normalizeOmxTeamTaskSnapshot, parseBlueprint, planStatusSchema, projectBlueprintLifecycleFromRuntime, resolveBlueprintProgressBridgePath, serializeBlueprint, taskStatusSchema, updateBlockedReason, updateTaskStatus, validateEmbeddedPhases, validatePlanLinks, validatePlanState, validatePlanTemplate, } from './index.js';
11
9
  // Services (require filesystem/git)
@@ -4,7 +4,8 @@ import path from 'node:path';
4
4
  import { parseBlueprint } from '#core/parser';
5
5
  import { scanBlueprintDirectory } from '#service/scanner';
6
6
  import { resolveBlueprintRoot } from '#utils/blueprint-root';
7
- import { resolvePackageAsset } from '#utils/package-assets';
7
+ import { getBlueprintDocumentPaths } from '#utils/document-paths.js';
8
+ import { resolvePackageAssetPreferred } from '#utils/package-assets';
8
9
  const RESERVED_BLUEPRINT_SLUGS = new Set([
9
10
  'draft',
10
11
  'planned',
@@ -13,7 +14,10 @@ const RESERVED_BLUEPRINT_SLUGS = new Set([
13
14
  'completed',
14
15
  'archived',
15
16
  ]);
16
- const DEFAULT_TEMPLATE_PATH = resolvePackageAsset('docs/templates/blueprint.md');
17
+ const DEFAULT_TEMPLATE_PATH = resolvePackageAssetPreferred([
18
+ 'docs/templates/blueprint.md',
19
+ 'catalog/docs/templates/blueprint.md',
20
+ ]);
17
21
  function formatDate(date) {
18
22
  return date.toISOString().split('T')[0] ?? date.toISOString();
19
23
  }
@@ -164,7 +168,7 @@ export class BlueprintCreationService {
164
168
  assertGoalProducesUsableSlug(goal, baseSlug);
165
169
  const slug = this.resolveCollisionSafeSlug(baseSlug);
166
170
  const title = sentenceCase(goal);
167
- const outputPath = path.join(this.blueprintsRoot, 'draft', slug, '_overview.md');
171
+ const outputPath = getBlueprintDocumentPaths(this.blueprintsRoot, 'draft', slug).flat;
168
172
  const relativeFilePath = toPortableRelativePath(this.projectRoot, outputPath);
169
173
  const date = formatDate(this.now());
170
174
  const template = type === 'blueprint' ? prepareTemplate(await readFile(this.templatePath, 'utf-8')) : undefined;
@@ -202,11 +206,11 @@ export class BlueprintCreationService {
202
206
  async create(input) {
203
207
  const draft = await this.compileDraft(input);
204
208
  const draftRoot = path.join(this.blueprintsRoot, 'draft');
205
- const finalDir = path.dirname(draft.outputPath);
209
+ const finalPath = draft.outputPath;
206
210
  await mkdir(draftRoot, { recursive: true });
207
- await mkdir(path.dirname(finalDir), { recursive: true });
211
+ await mkdir(path.dirname(finalPath), { recursive: true });
208
212
  const tempDir = await mkdtemp(path.join(draftRoot, `${draft.slug}.tmp-`));
209
- const tempPath = path.join(tempDir, '_overview.md');
213
+ const tempPath = path.join(tempDir, `${draft.slug}.md`);
210
214
  try {
211
215
  await writeFile(tempPath, draft.markdown, 'utf-8');
212
216
  const writtenMarkdown = await readFile(tempPath, 'utf-8');
@@ -214,7 +218,8 @@ export class BlueprintCreationService {
214
218
  if (!validation.valid || !validation.blueprint) {
215
219
  throw new Error(validation.error ?? 'Generated blueprint failed validation.');
216
220
  }
217
- await rename(tempDir, finalDir);
221
+ await rename(tempPath, finalPath);
222
+ await rm(tempDir, { force: true, recursive: true });
218
223
  return {
219
224
  ...draft,
220
225
  blueprint: validation.blueprint,
@@ -244,7 +249,10 @@ export class BlueprintCreationService {
244
249
  }
245
250
  }
246
251
  function blueprintDirectoryExists(blueprintsRoot, slug) {
247
- return [...RESERVED_BLUEPRINT_SLUGS].some((status) => existsSync(path.join(blueprintsRoot, status, slug)));
252
+ return [...RESERVED_BLUEPRINT_SLUGS].some((status) => {
253
+ const paths = getBlueprintDocumentPaths(blueprintsRoot, status, slug);
254
+ return existsSync(paths.directory) || existsSync(paths.flat);
255
+ });
248
256
  }
249
257
  async function removeIfEmpty(directory) {
250
258
  try {