@webpresso/agent-kit 0.21.5 → 0.24.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 +80 -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 +65 -0
- package/dist/esm/e2e/config.js +126 -0
- package/dist/esm/e2e/execution.js +4 -0
- package/dist/esm/e2e/load-host-adapter.d.ts +6 -1
- package/dist/esm/e2e/load-host-adapter.js +27 -9
- 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
|
@@ -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._-]+(
|
|
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
|
-
|
|
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
|
|
118
|
-
if (!
|
|
123
|
+
const relativeBlueprintPath = relative(blueprintsRoot, canonicalPath);
|
|
124
|
+
if (!parseBlueprintDocumentRelativePath(relativeBlueprintPath) ||
|
|
125
|
+
isBlueprintSupportingMarkdownRelativePath(relativeBlueprintPath)) {
|
|
119
126
|
continue;
|
|
120
|
-
|
|
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
|
|
134
|
+
const parsedPath = parseBlueprintDocumentRelativePath(relativeBlueprintPath);
|
|
135
|
+
if (!parsedPath)
|
|
136
|
+
continue;
|
|
137
|
+
const key = `${status}/${parsedPath.slug}`;
|
|
127
138
|
records.push({
|
|
128
|
-
file: relativePath(root,
|
|
139
|
+
file: relativePath(root, canonicalPath),
|
|
129
140
|
key,
|
|
130
|
-
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._-]+)(
|
|
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
|
-
|
|
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
|