@webpresso/agent-kit 0.21.5 → 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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +87 -124
- package/bin/_run.js +143 -1
- package/bin/runtime-manifest.json +40 -0
- package/catalog/AGENTS.md.tpl +7 -6
- package/catalog/agent/commands/plan-refine.md +3 -3
- package/catalog/agent/commands/pll.md +2 -0
- package/catalog/agent/guides/parallel-execution.md +2 -0
- package/catalog/agent/rules/extraction-parity.md +27 -1
- package/catalog/agent/rules/public-package-safety.md +24 -1
- package/catalog/agent/skills/pll/SKILL.md +1 -0
- package/catalog/base-kit/.github/workflows/ci.webpresso.yml.tmpl +33 -0
- package/catalog/base-kit/stryker.config.ts.tmpl +2 -2
- package/catalog/docs/templates/blueprint.md +1 -0
- package/catalog/docs/templates/blueprint.yaml +10 -12
- package/commands/blueprint.md +8 -43
- package/dist/esm/audit/blueprint-db-consistency.d.ts +1 -1
- package/dist/esm/audit/blueprint-db-consistency.js +6 -8
- package/dist/esm/audit/blueprint-lifecycle-sql.js +10 -3
- package/dist/esm/audit/cloudflare-deploy-contract.d.ts +3 -0
- package/dist/esm/audit/cloudflare-deploy-contract.js +64 -0
- package/dist/esm/audit/no-legacy-cli-bin.d.ts +3 -0
- package/dist/esm/audit/no-legacy-cli-bin.js +100 -0
- package/dist/esm/audit/package-surface.js +14 -1
- package/dist/esm/audit/repo-guardrails.js +40 -13
- package/dist/esm/audit/roadmap-links.js +23 -10
- package/dist/esm/blueprint/core/schema.d.ts +8 -8
- package/dist/esm/blueprint/core/schema.js +2 -2
- package/dist/esm/blueprint/db/enums.d.ts +1 -1
- package/dist/esm/blueprint/db/ingester.js +18 -10
- package/dist/esm/blueprint/lifecycle/audit.js +9 -2
- package/dist/esm/blueprint/lifecycle/local.js +15 -4
- package/dist/esm/blueprint/service/BlueprintCreationService.js +11 -6
- package/dist/esm/blueprint/service/BlueprintService.js +37 -19
- package/dist/esm/blueprint/service/scanner.js +73 -9
- package/dist/esm/blueprint/tracked-document/schema.d.ts +2 -2
- package/dist/esm/blueprint/utils/document-paths.d.ts +23 -0
- package/dist/esm/blueprint/utils/document-paths.js +91 -0
- package/dist/esm/build/package-manifest.js +7 -0
- package/dist/esm/build/release-policy.d.ts +27 -0
- package/dist/esm/build/release-policy.js +29 -0
- package/dist/esm/build/runtime-targets.d.ts +13 -0
- package/dist/esm/build/runtime-targets.js +48 -0
- package/dist/esm/cli/auto-update/detect-pm.d.ts +15 -0
- package/dist/esm/cli/auto-update/detect-pm.js +24 -9
- package/dist/esm/cli/auto-update/skip.js +9 -1
- package/dist/esm/cli/bundle/agent-command-inventory.d.ts +120 -0
- package/dist/esm/cli/bundle/agent-command-inventory.js +100 -0
- package/dist/esm/cli/bundle/index.d.ts +17 -0
- package/dist/esm/cli/bundle/index.js +15 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +49 -5
- package/dist/esm/cli/commands/audit-core.d.ts +1 -1
- package/dist/esm/cli/commands/audit.js +2 -0
- package/dist/esm/cli/commands/blueprint/router.js +11 -8
- package/dist/esm/cli/commands/hook.d.ts +8 -0
- package/dist/esm/cli/commands/hook.js +47 -0
- package/dist/esm/cli/commands/init/index.js +35 -1
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +1 -1
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/codex-ownership.js +9 -1
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +130 -20
- package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.d.ts +65 -0
- package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.js +64 -0
- package/dist/esm/cli/commands/package-manager.d.ts +15 -0
- package/dist/esm/cli/commands/package-manager.js +42 -0
- package/dist/esm/cli/commands/test.d.ts +1 -0
- package/dist/esm/cli/commands/test.js +2 -1
- package/dist/esm/cli/commands/typecheck.js +5 -20
- package/dist/esm/cli/package-scripts.d.ts +12 -0
- package/dist/esm/cli/package-scripts.js +59 -0
- package/dist/esm/cli/utils.js +3 -22
- package/dist/esm/cli/wp-extensions.d.ts +14 -0
- package/dist/esm/cli/wp-extensions.js +34 -0
- package/dist/esm/config/docs-lint/schemas/common.d.ts +1 -1
- package/dist/esm/config/docs-lint/schemas/implementation-plan.d.ts +2 -2
- package/dist/esm/config/docs-lint/schemas/parent-roadmap.d.ts +1 -1
- package/dist/esm/config/stryker/index.d.ts +85 -0
- package/dist/esm/config/stryker/index.js +31 -0
- package/dist/esm/e2e/command-builder.js +11 -2
- package/dist/esm/e2e/config.d.ts +56 -0
- package/dist/esm/e2e/config.js +114 -0
- package/dist/esm/e2e/execution.js +4 -0
- package/dist/esm/e2e/run-planner.js +1 -0
- package/dist/esm/e2e/types.d.ts +2 -0
- package/dist/esm/format/index.js +1 -3
- package/dist/esm/hooks/guard-switch/index.d.ts +1 -1
- package/dist/esm/hooks/guard-switch/index.js +22 -14
- package/dist/esm/hooks/post-tool/lint-after-edit.d.ts +1 -0
- package/dist/esm/hooks/post-tool/lint-after-edit.js +5 -2
- package/dist/esm/hooks/pretool-guard/validators/file-conventions.js +1 -1
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.d.ts +6 -0
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +27 -2
- package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +2 -1
- package/dist/esm/hooks/pretool-guard/validators/path-contract.js +59 -34
- package/dist/esm/hooks/pretool-guard/validators/plan-frontmatter.js +3 -3
- package/dist/esm/hooks/shared/routing-block.js +18 -4
- package/dist/esm/hooks/shared/validators/blueprint.js +3 -0
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +1 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +5 -2
- package/dist/esm/lint/index.js +1 -1
- package/dist/esm/mcp/auto-discover.d.ts +2 -0
- package/dist/esm/mcp/auto-discover.js +14 -6
- package/dist/esm/mcp/blueprint-server.js +30 -26
- package/dist/esm/mcp/cli.js +21 -0
- package/dist/esm/mcp/runners/test.js +15 -0
- package/dist/esm/mcp/server.d.ts +7 -0
- package/dist/esm/mcp/server.js +16 -27
- package/dist/esm/mcp/tools/_registry.d.ts +3 -0
- package/dist/esm/mcp/tools/_registry.js +21 -0
- package/dist/esm/mcp/tools/audit.d.ts +1 -0
- package/dist/esm/mcp/tools/audit.js +11 -0
- package/dist/esm/mcp/tools/e2e.d.ts +1 -1
- package/dist/esm/mcp/tools/typecheck.js +4 -2
- package/dist/esm/mutation/affected.d.ts +9 -0
- package/dist/esm/mutation/affected.js +36 -0
- package/dist/esm/package.json +5 -0
- package/dist/esm/runtime/package-version.d.ts +2 -0
- package/dist/esm/runtime/package-version.js +43 -0
- package/dist/esm/test/command-builder.d.ts +3 -0
- package/dist/esm/test/command-builder.js +22 -3
- package/dist/esm/tool-runtime/index.d.ts +2 -2
- package/dist/esm/tool-runtime/index.js +2 -1
- package/dist/esm/tool-runtime/resolve-runner.d.ts +3 -0
- package/dist/esm/tool-runtime/resolve-runner.js +7 -5
- package/dist/esm/typecheck/index.js +4 -2
- package/dist/esm/wp-extension/index.d.ts +50 -0
- package/dist/esm/wp-extension/index.js +268 -0
- package/package.json +67 -31
- package/skills/pll/SKILL.md +1 -0
|
@@ -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
|
-
|
|
16
|
-
|
|
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,
|
|
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
|
|
201
|
-
|
|
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,
|
|
219
|
+
upsertBlueprint(db, filePath, blueprintRoot);
|
|
212
220
|
}
|
|
213
221
|
ingested++;
|
|
214
222
|
}
|
|
@@ -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
|
-
|
|
15
|
-
|
|
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
|
|
78
|
-
const
|
|
79
|
-
|
|
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,6 +4,7 @@ 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 { getBlueprintDocumentPaths } from '#utils/document-paths.js';
|
|
7
8
|
import { resolvePackageAssetPreferred } from '#utils/package-assets';
|
|
8
9
|
const RESERVED_BLUEPRINT_SLUGS = new Set([
|
|
9
10
|
'draft',
|
|
@@ -167,7 +168,7 @@ export class BlueprintCreationService {
|
|
|
167
168
|
assertGoalProducesUsableSlug(goal, baseSlug);
|
|
168
169
|
const slug = this.resolveCollisionSafeSlug(baseSlug);
|
|
169
170
|
const title = sentenceCase(goal);
|
|
170
|
-
const outputPath =
|
|
171
|
+
const outputPath = getBlueprintDocumentPaths(this.blueprintsRoot, 'draft', slug).flat;
|
|
171
172
|
const relativeFilePath = toPortableRelativePath(this.projectRoot, outputPath);
|
|
172
173
|
const date = formatDate(this.now());
|
|
173
174
|
const template = type === 'blueprint' ? prepareTemplate(await readFile(this.templatePath, 'utf-8')) : undefined;
|
|
@@ -205,11 +206,11 @@ export class BlueprintCreationService {
|
|
|
205
206
|
async create(input) {
|
|
206
207
|
const draft = await this.compileDraft(input);
|
|
207
208
|
const draftRoot = path.join(this.blueprintsRoot, 'draft');
|
|
208
|
-
const
|
|
209
|
+
const finalPath = draft.outputPath;
|
|
209
210
|
await mkdir(draftRoot, { recursive: true });
|
|
210
|
-
await mkdir(path.dirname(
|
|
211
|
+
await mkdir(path.dirname(finalPath), { recursive: true });
|
|
211
212
|
const tempDir = await mkdtemp(path.join(draftRoot, `${draft.slug}.tmp-`));
|
|
212
|
-
const tempPath = path.join(tempDir,
|
|
213
|
+
const tempPath = path.join(tempDir, `${draft.slug}.md`);
|
|
213
214
|
try {
|
|
214
215
|
await writeFile(tempPath, draft.markdown, 'utf-8');
|
|
215
216
|
const writtenMarkdown = await readFile(tempPath, 'utf-8');
|
|
@@ -217,7 +218,8 @@ export class BlueprintCreationService {
|
|
|
217
218
|
if (!validation.valid || !validation.blueprint) {
|
|
218
219
|
throw new Error(validation.error ?? 'Generated blueprint failed validation.');
|
|
219
220
|
}
|
|
220
|
-
await rename(
|
|
221
|
+
await rename(tempPath, finalPath);
|
|
222
|
+
await rm(tempDir, { force: true, recursive: true });
|
|
221
223
|
return {
|
|
222
224
|
...draft,
|
|
223
225
|
blueprint: validation.blueprint,
|
|
@@ -247,7 +249,10 @@ export class BlueprintCreationService {
|
|
|
247
249
|
}
|
|
248
250
|
}
|
|
249
251
|
function blueprintDirectoryExists(blueprintsRoot, slug) {
|
|
250
|
-
return [...RESERVED_BLUEPRINT_SLUGS].some((status) =>
|
|
252
|
+
return [...RESERVED_BLUEPRINT_SLUGS].some((status) => {
|
|
253
|
+
const paths = getBlueprintDocumentPaths(blueprintsRoot, status, slug);
|
|
254
|
+
return existsSync(paths.directory) || existsSync(paths.flat);
|
|
255
|
+
});
|
|
251
256
|
}
|
|
252
257
|
async function removeIfEmpty(directory) {
|
|
253
258
|
try {
|
|
@@ -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
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
157
|
-
|
|
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 =
|
|
43
|
-
// Get relative path from base directory
|
|
53
|
+
function extractSlugAndGroup(fullPath, baseDir, filePattern = BLUEPRINT_OVERVIEW_FILENAME) {
|
|
44
54
|
const relPath = relative(baseDir, fullPath);
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
@@ -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) {
|