@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
@@ -11,6 +11,7 @@ import { parseBlueprint } from '#core/parser';
11
11
  import { applyBlueprintLifecycleToFile } from '#lifecycle/local';
12
12
  import { resolveBlueprintRoot } from '#utils/blueprint-root';
13
13
  import { emitTraceArtifact, generateBlueprintLifecycleTrace } from '#utils/decision-trace-artifacts';
14
+ import { getBlueprintDocumentCandidates } from '#utils/document-paths.js';
14
15
  import { BlueprintNotFoundError } from '#utils/errors';
15
16
  import { computeBlueprintQuerySummary, matchesBlueprintFilters, sortBlueprintRecords, toBlueprintRecord, } from './blueprint-records.js';
16
17
  import { linkBlueprintToTechDebt, unlinkBlueprintFromTechDebt, } from './blueprint-tech-debt-links.js';
@@ -74,25 +75,28 @@ export class BlueprintService extends TrackedDocumentService {
74
75
  }
75
76
  async get(slug) {
76
77
  // Try direct path first (supports both 'in-progress/foo' and 'foo')
77
- const planPath = path.join(this.baseDir, slug, '_overview.md');
78
- try {
79
- await fs.access(planPath);
80
- const content = await fs.readFile(planPath, 'utf-8');
81
- return parseBlueprint(content, slug);
82
- }
83
- catch {
84
- // Scan all plans to find a match by slug
85
- const scannedPlans = scanBlueprintDirectory({
86
- baseDir: this.baseDir,
87
- includeSpecialFolders: true,
88
- });
89
- const found = scannedPlans.find((p) => p.slug === slug || p.slug.endsWith(`/${slug}`));
90
- if (!found) {
91
- throw new BlueprintNotFoundError(slug, planPath, scannedPlans.map((p) => p.slug));
78
+ const directCandidates = getBlueprintDocumentCandidates(this.baseDir, slug);
79
+ for (const candidate of directCandidates) {
80
+ try {
81
+ await fs.access(candidate);
82
+ const content = await fs.readFile(candidate, 'utf-8');
83
+ return parseBlueprint(content, slug);
84
+ }
85
+ catch {
86
+ // Keep trying the remaining canonical shapes before falling back to a scan.
92
87
  }
93
- const content = await fs.readFile(found.path, 'utf-8');
94
- return parseBlueprint(content, found.slug);
95
88
  }
89
+ const searchedPath = directCandidates[0] ?? path.join(this.baseDir, slug, '_overview.md');
90
+ const scannedPlans = scanBlueprintDirectory({
91
+ baseDir: this.baseDir,
92
+ includeSpecialFolders: true,
93
+ });
94
+ const found = scannedPlans.find((p) => p.slug === slug || p.slug.endsWith(`/${slug}`));
95
+ if (!found) {
96
+ throw new BlueprintNotFoundError(slug, searchedPath, scannedPlans.map((p) => p.slug));
97
+ }
98
+ const content = await fs.readFile(found.path, 'utf-8');
99
+ return parseBlueprint(content, found.slug);
96
100
  }
97
101
  async query(options) {
98
102
  const scannedPlans = scanBlueprintDirectory({ baseDir: this.baseDir });
@@ -153,8 +157,22 @@ export class BlueprintService extends TrackedDocumentService {
153
157
  * @returns Array of TechDebtRecord objects
154
158
  */
155
159
  async getLinkedTechDebt(bpSlug) {
156
- const blueprintPath = path.join(this.baseDir, bpSlug, '_overview.md');
157
- const content = await fs.readFile(blueprintPath, 'utf-8');
160
+ const directCandidates = getBlueprintDocumentCandidates(this.baseDir, bpSlug);
161
+ let resolvedBlueprintPath = null;
162
+ for (const candidate of directCandidates) {
163
+ try {
164
+ await fs.access(candidate);
165
+ resolvedBlueprintPath = candidate;
166
+ break;
167
+ }
168
+ catch {
169
+ // Keep trying the other canonical shape.
170
+ }
171
+ }
172
+ const filePath = resolvedBlueprintPath ??
173
+ directCandidates[0] ??
174
+ path.join(this.baseDir, bpSlug, '_overview.md');
175
+ const content = await fs.readFile(filePath, 'utf-8');
158
176
  const parsed = matter(content);
159
177
  const data = JSON.parse(JSON.stringify(parsed.data));
160
178
  const linkedTechDebtSlugs = data.linked_tech_debt_slugs ?? [];
@@ -7,10 +7,18 @@
7
7
  import { existsSync, readdirSync, statSync } from 'node:fs';
8
8
  import { isAbsolute, join, relative, resolve } from 'node:path';
9
9
  import { resolveBlueprintRoot } from '#utils/blueprint-root';
10
+ import { BLUEPRINT_OVERVIEW_FILENAME, parseBlueprintDocumentRelativePath, } from '#utils/document-paths.js';
11
+ const BLUEPRINT_STATUS_FOLDERS = new Set([
12
+ 'draft',
13
+ 'planned',
14
+ 'parked',
15
+ 'in-progress',
16
+ 'completed',
17
+ 'archived',
18
+ ]);
10
19
  /** Special folder prefixes that indicate archived/deferred plans */
11
20
  const SPECIAL_FOLDERS = ['_completed', '_future', '_deprioritized'];
12
21
  /** Standard plan overview filename */
13
- const OVERVIEW_FILENAME = '_overview.md';
14
22
  /**
15
23
  * Check if a path component is a special folder.
16
24
  */
@@ -28,6 +36,9 @@ function findSpecialFolderType(pathSegments) {
28
36
  }
29
37
  return undefined;
30
38
  }
39
+ function isStatusFolder(name) {
40
+ return BLUEPRINT_STATUS_FOLDERS.has(name);
41
+ }
31
42
  /**
32
43
  * Extract the slug and group from a plan path.
33
44
  *
@@ -39,15 +50,42 @@ function findSpecialFolderType(pathSegments) {
39
50
  * - 'webpresso/blueprints/_completed/old-plan/_overview.md'
40
51
  * -> slug: '_completed/old-plan', group: null (special folder)
41
52
  */
42
- function extractSlugAndGroup(fullPath, baseDir, filePattern = OVERVIEW_FILENAME) {
43
- // Get relative path from base directory
53
+ function extractSlugAndGroup(fullPath, baseDir, filePattern = BLUEPRINT_OVERVIEW_FILENAME) {
44
54
  const relPath = relative(baseDir, fullPath);
45
- // Split into segments and remove the document filename
46
- const segments = relPath.split('/').filter((s) => s !== filePattern && s !== '');
55
+ const canonicalDocument = parseBlueprintDocumentRelativePath(relPath);
56
+ if (canonicalDocument) {
57
+ const slug = `${canonicalDocument.state}/${canonicalDocument.slug}`;
58
+ return {
59
+ slug,
60
+ group: canonicalDocument.state,
61
+ };
62
+ }
63
+ const relSegments = relPath.split('/').filter((s) => s !== '');
64
+ const segments = [...relSegments];
65
+ if (!segments.length) {
66
+ return { slug: '', group: null };
67
+ }
68
+ if (filePattern === BLUEPRINT_OVERVIEW_FILENAME) {
69
+ if (segments[segments.length - 1] === filePattern) {
70
+ segments.pop();
71
+ }
72
+ }
73
+ else if (filePattern.toLowerCase() === 'readme.md') {
74
+ if (segments[segments.length - 1] === filePattern) {
75
+ segments.pop();
76
+ }
77
+ }
78
+ else {
79
+ const last = segments[segments.length - 1] ?? '';
80
+ if (last === filePattern) {
81
+ segments[segments.length - 1] = last.replace(/\.md$/i, '');
82
+ }
83
+ }
47
84
  if (!segments.length) {
48
85
  return { slug: '', group: null };
49
86
  }
50
- // Filter out special folders from the slug calculation for group determination
87
+ // Filter out archival special folders from group determination. Lifecycle
88
+ // status folders are part of the public slug and remain valid groups.
51
89
  const nonSpecialSegments = segments.filter((s) => !isSpecialFolder(s));
52
90
  // The slug is the full path (including special folders)
53
91
  const slug = segments.join('/');
@@ -68,7 +106,7 @@ function extractSlugAndGroup(fullPath, baseDir, filePattern = OVERVIEW_FILENAME)
68
106
  * Check if an entry should be skipped during directory traversal.
69
107
  */
70
108
  function shouldSkipEntry(entry) {
71
- return entry.startsWith('.') || entry === 'node_modules';
109
+ return entry.startsWith('.') || entry.startsWith('__') || entry === 'node_modules';
72
110
  }
73
111
  /**
74
112
  * Safely get file stats, returning null on error.
@@ -126,11 +164,22 @@ function processEntry(entry, dir, baseDir, filePattern, includeSpecialFolders, r
126
164
  }
127
165
  return;
128
166
  }
167
+ if (filePattern === BLUEPRINT_OVERVIEW_FILENAME &&
168
+ entry.endsWith('.md') &&
169
+ entry !== 'README.md') {
170
+ const relativeParentSegments = relative(baseDir, dir).split('/').filter((s) => s !== '');
171
+ if (relativeParentSegments.length === 1 && isStatusFolder(relativeParentSegments[0] ?? '')) {
172
+ const plan = processPlanFile(fullPath, baseDir, includeSpecialFolders, entry);
173
+ if (plan) {
174
+ results.push(plan);
175
+ }
176
+ }
177
+ }
129
178
  }
130
179
  /**
131
180
  * Process a plan file (_overview.md or _overview.md) and create a ScannedBlueprint if applicable.
132
181
  */
133
- function processPlanFile(fullPath, baseDir, includeSpecialFolders, filePattern = OVERVIEW_FILENAME) {
182
+ function processPlanFile(fullPath, baseDir, includeSpecialFolders, filePattern = BLUEPRINT_OVERVIEW_FILENAME) {
134
183
  const relativePath = relative(baseDir, fullPath);
135
184
  // Skip files in hidden directories (defense-in-depth check)
136
185
  if (containsHiddenDirectory(relativePath)) {
@@ -192,6 +241,21 @@ export function scanDocumentDirectory(options) {
192
241
  }
193
242
  const results = [];
194
243
  scanDirectory(absoluteBaseDir, absoluteBaseDir, filePattern, includeSpecialFolders, results);
244
+ const duplicates = new Map();
245
+ for (const result of results) {
246
+ const existing = duplicates.get(result.slug);
247
+ if (existing) {
248
+ existing.push(result.path);
249
+ }
250
+ else {
251
+ duplicates.set(result.slug, [result.path]);
252
+ }
253
+ }
254
+ const duplicate = Array.from(duplicates.entries()).find(([, paths]) => paths.length > 1);
255
+ if (duplicate) {
256
+ const [slug, paths] = duplicate;
257
+ throw new Error(`Duplicate blueprint slug "${slug}" found in multiple canonical shapes: ${paths.join(', ')}`);
258
+ }
195
259
  return results;
196
260
  }
197
261
  /**
@@ -205,7 +269,7 @@ export function scanBlueprintDirectory(options) {
205
269
  const includeSpecialFolders = options?.includeSpecialFolders ?? false;
206
270
  return scanDocumentDirectory({
207
271
  baseDir,
208
- filePattern: OVERVIEW_FILENAME,
272
+ filePattern: BLUEPRINT_OVERVIEW_FILENAME,
209
273
  includeSpecialFolders,
210
274
  });
211
275
  }
@@ -18,8 +18,8 @@ export declare const trackedDocumentStatusSchema: z.ZodEnum<{
18
18
  completed: "completed";
19
19
  draft: "draft";
20
20
  planned: "planned";
21
- "in-progress": "in-progress";
22
21
  parked: "parked";
22
+ "in-progress": "in-progress";
23
23
  archived: "archived";
24
24
  }>;
25
25
  export type TrackedDocumentStatus = z.infer<typeof trackedDocumentStatusSchema>;
@@ -54,8 +54,8 @@ export declare const trackedDocumentFrontmatterSchema: z.ZodObject<{
54
54
  completed: "completed";
55
55
  draft: "draft";
56
56
  planned: "planned";
57
- "in-progress": "in-progress";
58
57
  parked: "parked";
58
+ "in-progress": "in-progress";
59
59
  archived: "archived";
60
60
  }>;
61
61
  last_updated: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodDate]>>;
@@ -0,0 +1,23 @@
1
+ export declare const BLUEPRINT_OVERVIEW_FILENAME = "_overview.md";
2
+ export declare const BLUEPRINT_STATUSES: readonly ["draft", "planned", "parked", "in-progress", "completed", "archived"];
3
+ export type BlueprintStatus = (typeof BLUEPRINT_STATUSES)[number];
4
+ export type BlueprintShape = 'flat' | 'folder';
5
+ export interface BlueprintDocumentPath {
6
+ relativePath: string;
7
+ shape: BlueprintShape;
8
+ slug: string;
9
+ state: BlueprintStatus;
10
+ }
11
+ export declare function normalizeBlueprintPath(filePath: string): string;
12
+ export declare function isBlueprintStatus(value: string | undefined): value is BlueprintStatus;
13
+ export declare function isBlueprintSlugSegment(value: string | undefined): value is string;
14
+ export declare function parseBlueprintDocumentRelativePath(filePath: string): BlueprintDocumentPath | null;
15
+ export declare function isBlueprintSupportingMarkdownRelativePath(filePath: string): boolean;
16
+ export declare function getBlueprintDocumentPaths(root: string, state: BlueprintStatus, slug: string): {
17
+ directory: string;
18
+ flat: string;
19
+ folder: string;
20
+ };
21
+ export declare function getBlueprintDocumentCandidates(root: string, slug: string): string[];
22
+ export declare function getBlueprintAlternateDocumentPath(root: string, filePath: string): string | null;
23
+ //# sourceMappingURL=document-paths.d.ts.map
@@ -0,0 +1,91 @@
1
+ import path from 'node:path';
2
+ export const BLUEPRINT_OVERVIEW_FILENAME = '_overview.md';
3
+ export const BLUEPRINT_STATUSES = [
4
+ 'draft',
5
+ 'planned',
6
+ 'parked',
7
+ 'in-progress',
8
+ 'completed',
9
+ 'archived',
10
+ ];
11
+ const BLUEPRINT_STATUS_SET = new Set(BLUEPRINT_STATUSES);
12
+ const BLUEPRINT_SLUG_SEGMENT_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
13
+ export function normalizeBlueprintPath(filePath) {
14
+ return filePath.replace(/\\/g, '/').replace(/^\/+/, '');
15
+ }
16
+ export function isBlueprintStatus(value) {
17
+ return value !== undefined && BLUEPRINT_STATUS_SET.has(value);
18
+ }
19
+ export function isBlueprintSlugSegment(value) {
20
+ return value !== undefined && BLUEPRINT_SLUG_SEGMENT_PATTERN.test(value);
21
+ }
22
+ export function parseBlueprintDocumentRelativePath(filePath) {
23
+ const normalized = normalizeBlueprintPath(filePath);
24
+ const parts = normalized.split('/').filter((segment) => segment.length > 0);
25
+ const [state, second, third] = parts;
26
+ if (!isBlueprintStatus(state)) {
27
+ return null;
28
+ }
29
+ if (parts.length === 2 && typeof second === 'string' && second.endsWith('.md')) {
30
+ const slug = second.slice(0, -3);
31
+ if (!isBlueprintSlugSegment(slug)) {
32
+ return null;
33
+ }
34
+ return {
35
+ relativePath: `${state}/${slug}.md`,
36
+ shape: 'flat',
37
+ slug,
38
+ state,
39
+ };
40
+ }
41
+ if (parts.length === 3 &&
42
+ isBlueprintSlugSegment(second) &&
43
+ third === BLUEPRINT_OVERVIEW_FILENAME) {
44
+ return {
45
+ relativePath: `${state}/${second}/${BLUEPRINT_OVERVIEW_FILENAME}`,
46
+ shape: 'folder',
47
+ slug: second,
48
+ state,
49
+ };
50
+ }
51
+ return null;
52
+ }
53
+ export function isBlueprintSupportingMarkdownRelativePath(filePath) {
54
+ const normalized = normalizeBlueprintPath(filePath);
55
+ const parts = normalized.split('/').filter((segment) => segment.length > 0);
56
+ const [state, slug, doc] = parts;
57
+ return (parts.length === 3 &&
58
+ isBlueprintStatus(state) &&
59
+ isBlueprintSlugSegment(slug) &&
60
+ typeof doc === 'string' &&
61
+ doc.endsWith('.md') &&
62
+ doc !== BLUEPRINT_OVERVIEW_FILENAME);
63
+ }
64
+ export function getBlueprintDocumentPaths(root, state, slug) {
65
+ const directory = path.join(root, state, slug);
66
+ return {
67
+ directory,
68
+ flat: path.join(root, state, `${slug}.md`),
69
+ folder: path.join(directory, BLUEPRINT_OVERVIEW_FILENAME),
70
+ };
71
+ }
72
+ export function getBlueprintDocumentCandidates(root, slug) {
73
+ const normalized = normalizeBlueprintPath(slug);
74
+ const segments = normalized.split('/').filter((segment) => segment.length > 0);
75
+ const [state, name] = segments;
76
+ if (segments.length !== 2 || !isBlueprintStatus(state) || !isBlueprintSlugSegment(name)) {
77
+ return [];
78
+ }
79
+ const { flat, folder } = getBlueprintDocumentPaths(root, state, name);
80
+ return [flat, folder];
81
+ }
82
+ export function getBlueprintAlternateDocumentPath(root, filePath) {
83
+ const normalized = normalizeBlueprintPath(path.relative(root, filePath));
84
+ const parsed = parseBlueprintDocumentRelativePath(normalized);
85
+ if (!parsed) {
86
+ return null;
87
+ }
88
+ const paths = getBlueprintDocumentPaths(root, parsed.state, parsed.slug);
89
+ return parsed.shape === 'flat' ? paths.folder : paths.flat;
90
+ }
91
+ //# sourceMappingURL=document-paths.js.map
@@ -3,4 +3,15 @@
3
3
  * package root) is found. Works whether running from src/ or dist/esm/.
4
4
  */
5
5
  export declare function resolvePackageAsset(relativeFromRoot: string): string;
6
+ /**
7
+ * Resolve the first existing candidate, in priority order. Use when an asset
8
+ * lives at one path in the source checkout but a different path in the
9
+ * published tarball — e.g. templates authored under repo-root `docs/templates/`
10
+ * but shipped under `catalog/docs/templates/` because the npm `files` list
11
+ * includes `catalog/` and not `docs/`. Prefers the dev/source location and
12
+ * falls back to the shipped one, mirroring `bin/_run.js`'s source→built path
13
+ * translation. Falls back to cwd-relative on the first candidate when none
14
+ * exist, matching `resolvePackageAsset`'s last-resort behavior.
15
+ */
16
+ export declare function resolvePackageAssetPreferred(candidates: readonly string[]): string;
6
17
  //# sourceMappingURL=package-assets.d.ts.map
@@ -1,10 +1,10 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  /**
4
- * Walk up from this file's location until the given path (relative to the
5
- * package root) is found. Works whether running from src/ or dist/esm/.
4
+ * Walk up from this file's location looking for `relativeFromRoot`. Returns the
5
+ * first existing match, or `null` if none is found within the ancestor budget.
6
6
  */
7
- export function resolvePackageAsset(relativeFromRoot) {
7
+ function findPackageAsset(relativeFromRoot) {
8
8
  let dir = path.dirname(new URL(import.meta.url).pathname);
9
9
  for (let i = 0; i < 8; i++) {
10
10
  const candidate = path.join(dir, relativeFromRoot);
@@ -15,6 +15,35 @@ export function resolvePackageAsset(relativeFromRoot) {
15
15
  break;
16
16
  dir = parent;
17
17
  }
18
- return path.join(process.cwd(), relativeFromRoot);
18
+ return null;
19
+ }
20
+ /**
21
+ * Walk up from this file's location until the given path (relative to the
22
+ * package root) is found. Works whether running from src/ or dist/esm/.
23
+ */
24
+ export function resolvePackageAsset(relativeFromRoot) {
25
+ return findPackageAsset(relativeFromRoot) ?? path.join(process.cwd(), relativeFromRoot);
26
+ }
27
+ /**
28
+ * Resolve the first existing candidate, in priority order. Use when an asset
29
+ * lives at one path in the source checkout but a different path in the
30
+ * published tarball — e.g. templates authored under repo-root `docs/templates/`
31
+ * but shipped under `catalog/docs/templates/` because the npm `files` list
32
+ * includes `catalog/` and not `docs/`. Prefers the dev/source location and
33
+ * falls back to the shipped one, mirroring `bin/_run.js`'s source→built path
34
+ * translation. Falls back to cwd-relative on the first candidate when none
35
+ * exist, matching `resolvePackageAsset`'s last-resort behavior.
36
+ */
37
+ export function resolvePackageAssetPreferred(candidates) {
38
+ for (const relativeFromRoot of candidates) {
39
+ const found = findPackageAsset(relativeFromRoot);
40
+ if (found)
41
+ return found;
42
+ }
43
+ const primary = candidates[0];
44
+ if (primary === undefined) {
45
+ throw new Error('resolvePackageAssetPreferred requires at least one candidate path');
46
+ }
47
+ return path.join(process.cwd(), primary);
19
48
  }
20
49
  //# sourceMappingURL=package-assets.js.map
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { parse as parseYaml } from 'yaml';
4
+ import { RUNTIME_TARGETS } from './runtime-targets.js';
4
5
  const DEPENDENCY_SECTIONS = [
5
6
  'dependencies',
6
7
  'devDependencies',
@@ -74,6 +75,12 @@ export function createPackedManifest(manifest, workspaceCatalogs) {
74
75
  if ('bin' in packedManifest) {
75
76
  packedManifest.bin = normalizePackedBinField(packedManifest.bin);
76
77
  }
78
+ if (typeof manifest.version === 'string') {
79
+ packedManifest.optionalDependencies = {
80
+ ...packedManifest.optionalDependencies,
81
+ ...Object.fromEntries(RUNTIME_TARGETS.map((target) => [target.packageName, manifest.version])),
82
+ };
83
+ }
77
84
  return packedManifest;
78
85
  }
79
86
  function writeJson(filePath, value) {
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Release-time policy decisions for the publish pipeline.
3
+ *
4
+ * The per-platform native runtime packages (`@webpresso/agent-kit-runtime-*`)
5
+ * are a deferred capability: they are not yet created on the registry, the main
6
+ * package declares no `optionalDependencies` on them, and the plugin manifest
7
+ * still launches via the node entrypoint rather than a staged native `bin/wp`.
8
+ *
9
+ * Until that native-distribution work is intentionally activated, the release
10
+ * MUST NOT attempt to publish the matrix: a first-time `npm publish` of a
11
+ * never-created scoped package returns 404, which previously aborted the entire
12
+ * release before the main `@webpresso/agent-kit` package published (the root
13
+ * cause of the 0.22.x publish stall).
14
+ *
15
+ * The matrix publish is therefore gated behind an explicit opt-in. When the
16
+ * native-distribution feature lands, the release workflow sets
17
+ * `WP_PUBLISH_RUNTIME_MATRIX=1` (after bootstrapping the scoped packages on the
18
+ * registry) to turn it back on.
19
+ */
20
+ export declare const PUBLISH_RUNTIME_MATRIX_ENV = "WP_PUBLISH_RUNTIME_MATRIX";
21
+ /**
22
+ * Whether the release pipeline should build, stage, and publish the per-platform
23
+ * native runtime matrix. Defaults to `false` (matrix deferred); enabled only
24
+ * when `WP_PUBLISH_RUNTIME_MATRIX=1` is set explicitly.
25
+ */
26
+ export declare function shouldPublishRuntimeMatrix(env: NodeJS.ProcessEnv): boolean;
27
+ //# sourceMappingURL=release-policy.d.ts.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Release-time policy decisions for the publish pipeline.
3
+ *
4
+ * The per-platform native runtime packages (`@webpresso/agent-kit-runtime-*`)
5
+ * are a deferred capability: they are not yet created on the registry, the main
6
+ * package declares no `optionalDependencies` on them, and the plugin manifest
7
+ * still launches via the node entrypoint rather than a staged native `bin/wp`.
8
+ *
9
+ * Until that native-distribution work is intentionally activated, the release
10
+ * MUST NOT attempt to publish the matrix: a first-time `npm publish` of a
11
+ * never-created scoped package returns 404, which previously aborted the entire
12
+ * release before the main `@webpresso/agent-kit` package published (the root
13
+ * cause of the 0.22.x publish stall).
14
+ *
15
+ * The matrix publish is therefore gated behind an explicit opt-in. When the
16
+ * native-distribution feature lands, the release workflow sets
17
+ * `WP_PUBLISH_RUNTIME_MATRIX=1` (after bootstrapping the scoped packages on the
18
+ * registry) to turn it back on.
19
+ */
20
+ export const PUBLISH_RUNTIME_MATRIX_ENV = 'WP_PUBLISH_RUNTIME_MATRIX';
21
+ /**
22
+ * Whether the release pipeline should build, stage, and publish the per-platform
23
+ * native runtime matrix. Defaults to `false` (matrix deferred); enabled only
24
+ * when `WP_PUBLISH_RUNTIME_MATRIX=1` is set explicitly.
25
+ */
26
+ export function shouldPublishRuntimeMatrix(env) {
27
+ return env[PUBLISH_RUNTIME_MATRIX_ENV] === '1';
28
+ }
29
+ //# sourceMappingURL=release-policy.js.map
@@ -0,0 +1,13 @@
1
+ export interface RuntimeTarget {
2
+ readonly id: string;
3
+ readonly bunTarget: string;
4
+ readonly os: NodeJS.Platform;
5
+ readonly cpu: NodeJS.Architecture;
6
+ readonly packageName: string;
7
+ }
8
+ export declare const RUNTIME_BINARY_NAME = "wp";
9
+ export declare const RUNTIME_TARGETS: readonly RuntimeTarget[];
10
+ export declare function runtimeBinaryFilename(target: RuntimeTarget): string;
11
+ export declare function runtimePackageDirName(packageName: string): string;
12
+ export declare function resolveRuntimeTarget(platform?: NodeJS.Platform, arch?: NodeJS.Architecture): RuntimeTarget | undefined;
13
+ //# sourceMappingURL=runtime-targets.d.ts.map
@@ -0,0 +1,48 @@
1
+ export const RUNTIME_BINARY_NAME = 'wp';
2
+ export const RUNTIME_TARGETS = [
3
+ {
4
+ id: 'darwin-arm64',
5
+ bunTarget: 'bun-darwin-arm64',
6
+ os: 'darwin',
7
+ cpu: 'arm64',
8
+ packageName: '@webpresso/agent-kit-runtime-darwin-arm64',
9
+ },
10
+ {
11
+ id: 'darwin-x64',
12
+ bunTarget: 'bun-darwin-x64',
13
+ os: 'darwin',
14
+ cpu: 'x64',
15
+ packageName: '@webpresso/agent-kit-runtime-darwin-x64',
16
+ },
17
+ {
18
+ id: 'linux-x64',
19
+ bunTarget: 'bun-linux-x64',
20
+ os: 'linux',
21
+ cpu: 'x64',
22
+ packageName: '@webpresso/agent-kit-runtime-linux-x64',
23
+ },
24
+ {
25
+ id: 'linux-arm64',
26
+ bunTarget: 'bun-linux-arm64',
27
+ os: 'linux',
28
+ cpu: 'arm64',
29
+ packageName: '@webpresso/agent-kit-runtime-linux-arm64',
30
+ },
31
+ {
32
+ id: 'windows-x64',
33
+ bunTarget: 'bun-windows-x64',
34
+ os: 'win32',
35
+ cpu: 'x64',
36
+ packageName: '@webpresso/agent-kit-runtime-windows-x64',
37
+ },
38
+ ];
39
+ export function runtimeBinaryFilename(target) {
40
+ return target.os === 'win32' ? `${RUNTIME_BINARY_NAME}.exe` : RUNTIME_BINARY_NAME;
41
+ }
42
+ export function runtimePackageDirName(packageName) {
43
+ return packageName.split('/').at(-1) ?? packageName;
44
+ }
45
+ export function resolveRuntimeTarget(platform = process.platform, arch = process.arch) {
46
+ return RUNTIME_TARGETS.find((target) => target.os === platform && target.cpu === arch);
47
+ }
48
+ //# sourceMappingURL=runtime-targets.js.map
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * sync-catalog-doc-templates.ts
4
+ *
5
+ * `docs/templates/` (repo root) is the editable source of truth for the
6
+ * blueprint / doc templates. `catalog/docs/templates/` is the published +
7
+ * scaffolded mirror: the npm `files` list ships `catalog/` but NOT repo-root
8
+ * `docs/` (which holds internal docs that must stay private), and `wp init`
9
+ * scaffolds templates into a consumer from `catalog/docs/templates/`.
10
+ *
11
+ * The two MUST stay byte-identical. Nothing synced them before, so they
12
+ * drifted — this script regenerates the mirror from the source and is wired
13
+ * into the build. The colocated drift test fails CI if they ever diverge.
14
+ *
15
+ * Default: regenerate the mirror from the source.
16
+ * `--check`: exit non-zero and list drift without writing (CI / pre-publish).
17
+ */
18
+ /**
19
+ * Names that are out of sync between source and mirror: content mismatch,
20
+ * missing from the mirror, or orphaned in the mirror.
21
+ */
22
+ export declare function diffDocTemplateMirror(): string[];
23
+ //# sourceMappingURL=sync-catalog-doc-templates.d.ts.map