busy-cli 0.1.2
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/README.md +129 -0
- package/dist/builders/context.d.ts +50 -0
- package/dist/builders/context.d.ts.map +1 -0
- package/dist/builders/context.js +190 -0
- package/dist/cache/index.d.ts +100 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +270 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +463 -0
- package/dist/commands/package.d.ts +96 -0
- package/dist/commands/package.d.ts.map +1 -0
- package/dist/commands/package.js +285 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/loader.d.ts +6 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +361 -0
- package/dist/merge.d.ts +16 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +102 -0
- package/dist/package/manifest.d.ts +59 -0
- package/dist/package/manifest.d.ts.map +1 -0
- package/dist/package/manifest.js +265 -0
- package/dist/parser.d.ts +28 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +220 -0
- package/dist/parsers/frontmatter.d.ts +14 -0
- package/dist/parsers/frontmatter.d.ts.map +1 -0
- package/dist/parsers/frontmatter.js +110 -0
- package/dist/parsers/imports.d.ts +48 -0
- package/dist/parsers/imports.d.ts.map +1 -0
- package/dist/parsers/imports.js +147 -0
- package/dist/parsers/links.d.ts +12 -0
- package/dist/parsers/links.d.ts.map +1 -0
- package/dist/parsers/links.js +79 -0
- package/dist/parsers/localdefs.d.ts +6 -0
- package/dist/parsers/localdefs.d.ts.map +1 -0
- package/dist/parsers/localdefs.js +132 -0
- package/dist/parsers/operations.d.ts +32 -0
- package/dist/parsers/operations.d.ts.map +1 -0
- package/dist/parsers/operations.js +313 -0
- package/dist/parsers/sections.d.ts +15 -0
- package/dist/parsers/sections.d.ts.map +1 -0
- package/dist/parsers/sections.js +173 -0
- package/dist/parsers/tools.d.ts +30 -0
- package/dist/parsers/tools.d.ts.map +1 -0
- package/dist/parsers/tools.js +178 -0
- package/dist/parsers/triggers.d.ts +35 -0
- package/dist/parsers/triggers.d.ts.map +1 -0
- package/dist/parsers/triggers.js +219 -0
- package/dist/providers/base.d.ts +60 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +34 -0
- package/dist/providers/github.d.ts +18 -0
- package/dist/providers/github.d.ts.map +1 -0
- package/dist/providers/github.js +109 -0
- package/dist/providers/gitlab.d.ts +18 -0
- package/dist/providers/gitlab.d.ts.map +1 -0
- package/dist/providers/gitlab.js +101 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +17 -0
- package/dist/providers/local.d.ts +31 -0
- package/dist/providers/local.d.ts.map +1 -0
- package/dist/providers/local.js +116 -0
- package/dist/providers/url.d.ts +16 -0
- package/dist/providers/url.d.ts.map +1 -0
- package/dist/providers/url.js +45 -0
- package/dist/registry/index.d.ts +99 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +320 -0
- package/dist/types/schema.d.ts +3259 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +258 -0
- package/dist/utils/logger.d.ts +19 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +23 -0
- package/dist/utils/slugify.d.ts +14 -0
- package/dist/utils/slugify.d.ts.map +1 -0
- package/dist/utils/slugify.js +28 -0
- package/package.json +61 -0
- package/src/__tests__/cache.test.ts +393 -0
- package/src/__tests__/cli-package.test.ts +667 -0
- package/src/__tests__/fixtures/automated-workflow.busy.md +84 -0
- package/src/__tests__/fixtures/concept.busy.md +30 -0
- package/src/__tests__/fixtures/document.busy.md +44 -0
- package/src/__tests__/fixtures/simple-operation.busy.md +45 -0
- package/src/__tests__/fixtures/tool-document.busy.md +71 -0
- package/src/__tests__/fixtures/tool.busy.md +54 -0
- package/src/__tests__/imports.test.ts +244 -0
- package/src/__tests__/integration.test.ts +432 -0
- package/src/__tests__/operations.test.ts +408 -0
- package/src/__tests__/package-manifest.test.ts +455 -0
- package/src/__tests__/providers.test.ts +672 -0
- package/src/__tests__/registry.test.ts +402 -0
- package/src/__tests__/schema.test.ts +467 -0
- package/src/__tests__/tools.test.ts +376 -0
- package/src/__tests__/triggers.test.ts +312 -0
- package/src/builders/context.ts +294 -0
- package/src/cache/index.ts +312 -0
- package/src/cli/index.ts +514 -0
- package/src/commands/package.ts +392 -0
- package/src/index.ts +46 -0
- package/src/loader.ts +474 -0
- package/src/merge.ts +126 -0
- package/src/package/manifest.ts +349 -0
- package/src/parser.ts +278 -0
- package/src/parsers/frontmatter.ts +135 -0
- package/src/parsers/imports.ts +196 -0
- package/src/parsers/links.ts +108 -0
- package/src/parsers/localdefs.ts +166 -0
- package/src/parsers/operations.ts +404 -0
- package/src/parsers/sections.ts +230 -0
- package/src/parsers/tools.ts +215 -0
- package/src/parsers/triggers.ts +252 -0
- package/src/providers/base.ts +77 -0
- package/src/providers/github.ts +129 -0
- package/src/providers/gitlab.ts +121 -0
- package/src/providers/index.ts +25 -0
- package/src/providers/local.ts +129 -0
- package/src/providers/url.ts +56 -0
- package/src/registry/index.ts +408 -0
- package/src/types/schema.ts +369 -0
- package/src/utils/logger.ts +25 -0
- package/src/utils/slugify.ts +31 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package Management Commands
|
|
3
|
+
*
|
|
4
|
+
* Implementation of busy init, check, and package commands.
|
|
5
|
+
*/
|
|
6
|
+
import { promises as fs } from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import { CacheManager, deriveCachePath } from '../cache/index.js';
|
|
9
|
+
import { PackageRegistry, deriveEntryId, deriveCategory } from '../registry/index.js';
|
|
10
|
+
import { providerRegistry } from '../providers/index.js';
|
|
11
|
+
import { isPackageManifestUrl, fetchPackageFromManifest } from '../package/manifest.js';
|
|
12
|
+
// Ensure providers are registered
|
|
13
|
+
import '../providers/local.js';
|
|
14
|
+
import '../providers/github.js';
|
|
15
|
+
import '../providers/gitlab.js';
|
|
16
|
+
import '../providers/url.js';
|
|
17
|
+
/**
|
|
18
|
+
* Initialize a BUSY workspace
|
|
19
|
+
*/
|
|
20
|
+
export async function initWorkspace(workspaceRoot) {
|
|
21
|
+
const created = [];
|
|
22
|
+
const skipped = [];
|
|
23
|
+
// Initialize package registry
|
|
24
|
+
const registry = new PackageRegistry(workspaceRoot);
|
|
25
|
+
const packagePath = path.join(workspaceRoot, 'package.busy.md');
|
|
26
|
+
const packageExists = await fs.stat(packagePath).then(() => true).catch(() => false);
|
|
27
|
+
if (packageExists) {
|
|
28
|
+
skipped.push('package.busy.md');
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
await registry.init();
|
|
32
|
+
created.push('package.busy.md');
|
|
33
|
+
}
|
|
34
|
+
// Initialize cache manager
|
|
35
|
+
const cache = new CacheManager(workspaceRoot);
|
|
36
|
+
const librariesPath = path.join(workspaceRoot, '.libraries');
|
|
37
|
+
const librariesExists = await fs.stat(librariesPath).then(() => true).catch(() => false);
|
|
38
|
+
if (librariesExists) {
|
|
39
|
+
skipped.push('.libraries');
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
await cache.init();
|
|
43
|
+
created.push('.libraries');
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
workspaceRoot,
|
|
47
|
+
initialized: true,
|
|
48
|
+
created,
|
|
49
|
+
skipped,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check workspace coherence
|
|
54
|
+
*/
|
|
55
|
+
export async function checkWorkspace(workspaceRoot, options) {
|
|
56
|
+
const errors = [];
|
|
57
|
+
const warnings = [];
|
|
58
|
+
// Load registry
|
|
59
|
+
const registry = new PackageRegistry(workspaceRoot);
|
|
60
|
+
try {
|
|
61
|
+
await registry.load();
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw new Error(`Workspace not initialized: ${error instanceof Error ? error.message : error}`);
|
|
65
|
+
}
|
|
66
|
+
const cache = new CacheManager(workspaceRoot);
|
|
67
|
+
const packages = registry.getPackages();
|
|
68
|
+
// Check each package
|
|
69
|
+
for (const pkg of packages) {
|
|
70
|
+
// Check if cached file exists
|
|
71
|
+
const cachePath = pkg.cached.startsWith('.libraries/')
|
|
72
|
+
? pkg.cached.slice('.libraries/'.length)
|
|
73
|
+
: pkg.cached;
|
|
74
|
+
const fullPath = path.join(workspaceRoot, pkg.cached);
|
|
75
|
+
const exists = await fs.stat(fullPath).then(() => true).catch(() => false);
|
|
76
|
+
if (!exists) {
|
|
77
|
+
errors.push(`Package "${pkg.id}": cached file not found at ${pkg.cached}`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
// Check integrity if specified
|
|
81
|
+
if (pkg.integrity) {
|
|
82
|
+
const isValid = await cache.verifyIntegrity(cachePath, pkg.integrity);
|
|
83
|
+
if (!isValid) {
|
|
84
|
+
warnings.push(`Package "${pkg.id}": integrity mismatch`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
workspaceRoot,
|
|
90
|
+
valid: errors.length === 0,
|
|
91
|
+
errors,
|
|
92
|
+
warnings,
|
|
93
|
+
packages: packages.length,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Add a package from URL
|
|
98
|
+
*
|
|
99
|
+
* If the URL points to a package.busy.md manifest, fetches the entire package.
|
|
100
|
+
* Otherwise, fetches a single file.
|
|
101
|
+
*/
|
|
102
|
+
export async function addPackage(workspaceRoot, url) {
|
|
103
|
+
// Check if this is a package manifest URL
|
|
104
|
+
if (isPackageManifestUrl(url)) {
|
|
105
|
+
// Use manifest-based package installation
|
|
106
|
+
const result = await fetchPackageFromManifest(workspaceRoot, url);
|
|
107
|
+
// Find provider for URL to get provider name and resolve source path
|
|
108
|
+
const provider = providerRegistry.findProvider(url);
|
|
109
|
+
const parsed = provider?.parse(url);
|
|
110
|
+
// Use absolute path for local sources, original URL for remote
|
|
111
|
+
const resolvedSource = provider?.name === 'local' && parsed
|
|
112
|
+
? parsed.path
|
|
113
|
+
: url;
|
|
114
|
+
return {
|
|
115
|
+
id: result.name,
|
|
116
|
+
source: resolvedSource,
|
|
117
|
+
provider: provider?.name || 'url',
|
|
118
|
+
cached: result.cached,
|
|
119
|
+
version: result.version,
|
|
120
|
+
integrity: result.integrity,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// Single file installation (existing behavior)
|
|
124
|
+
const provider = providerRegistry.findProvider(url);
|
|
125
|
+
if (!provider) {
|
|
126
|
+
throw new Error(`No provider found for URL: ${url}`);
|
|
127
|
+
}
|
|
128
|
+
// Parse URL
|
|
129
|
+
const parsed = provider.parse(url);
|
|
130
|
+
// Derive entry ID
|
|
131
|
+
const entryId = deriveEntryId(url);
|
|
132
|
+
// Derive cache path
|
|
133
|
+
const cachePath = deriveCachePath(parsed);
|
|
134
|
+
// Fetch content
|
|
135
|
+
const content = await provider.fetch(url);
|
|
136
|
+
// Save to cache
|
|
137
|
+
const cache = new CacheManager(workspaceRoot);
|
|
138
|
+
await cache.init();
|
|
139
|
+
const saved = await cache.save(cachePath, content);
|
|
140
|
+
// Derive category
|
|
141
|
+
const category = deriveCategory(url);
|
|
142
|
+
// Create package entry
|
|
143
|
+
const entry = {
|
|
144
|
+
id: entryId,
|
|
145
|
+
description: '',
|
|
146
|
+
source: url,
|
|
147
|
+
provider: provider.name,
|
|
148
|
+
cached: `.libraries/${cachePath}`,
|
|
149
|
+
version: parsed.ref || 'latest',
|
|
150
|
+
fetched: new Date().toISOString(),
|
|
151
|
+
integrity: saved.integrity,
|
|
152
|
+
category,
|
|
153
|
+
};
|
|
154
|
+
// Load registry and add package
|
|
155
|
+
const registry = new PackageRegistry(workspaceRoot);
|
|
156
|
+
try {
|
|
157
|
+
await registry.load();
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
await registry.init();
|
|
161
|
+
await registry.load();
|
|
162
|
+
}
|
|
163
|
+
registry.addPackage(entry);
|
|
164
|
+
await registry.save();
|
|
165
|
+
return {
|
|
166
|
+
id: entryId,
|
|
167
|
+
source: url,
|
|
168
|
+
provider: provider.name,
|
|
169
|
+
cached: entry.cached,
|
|
170
|
+
version: entry.version,
|
|
171
|
+
integrity: saved.integrity,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Remove a package
|
|
176
|
+
*/
|
|
177
|
+
export async function removePackage(workspaceRoot, packageId) {
|
|
178
|
+
// Load registry
|
|
179
|
+
const registry = new PackageRegistry(workspaceRoot);
|
|
180
|
+
await registry.load();
|
|
181
|
+
// Get package info before removing
|
|
182
|
+
const pkg = registry.getPackage(packageId);
|
|
183
|
+
if (!pkg) {
|
|
184
|
+
return { id: packageId, removed: false };
|
|
185
|
+
}
|
|
186
|
+
// Remove from registry
|
|
187
|
+
registry.removePackage(packageId);
|
|
188
|
+
await registry.save();
|
|
189
|
+
// Remove cached file
|
|
190
|
+
const cache = new CacheManager(workspaceRoot);
|
|
191
|
+
const cachePath = pkg.cached.startsWith('.libraries/')
|
|
192
|
+
? pkg.cached.slice('.libraries/'.length)
|
|
193
|
+
: pkg.cached;
|
|
194
|
+
await cache.delete(cachePath);
|
|
195
|
+
return { id: packageId, removed: true };
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* List all packages
|
|
199
|
+
*/
|
|
200
|
+
export async function listPackages(workspaceRoot) {
|
|
201
|
+
// Load registry
|
|
202
|
+
const registry = new PackageRegistry(workspaceRoot);
|
|
203
|
+
await registry.load();
|
|
204
|
+
const packages = registry.getPackages();
|
|
205
|
+
return { packages };
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get package info
|
|
209
|
+
*/
|
|
210
|
+
export async function getPackageInfo(workspaceRoot, packageId) {
|
|
211
|
+
// Load registry
|
|
212
|
+
const registry = new PackageRegistry(workspaceRoot);
|
|
213
|
+
await registry.load();
|
|
214
|
+
return registry.getPackage(packageId) || null;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Upgrade a package to latest version
|
|
218
|
+
*/
|
|
219
|
+
export async function upgradePackage(workspaceRoot, packageId) {
|
|
220
|
+
// Load registry
|
|
221
|
+
const registry = new PackageRegistry(workspaceRoot);
|
|
222
|
+
await registry.load();
|
|
223
|
+
// Get current package
|
|
224
|
+
const pkg = registry.getPackage(packageId);
|
|
225
|
+
if (!pkg) {
|
|
226
|
+
throw new Error(`Package not found: ${packageId}`);
|
|
227
|
+
}
|
|
228
|
+
// Find provider
|
|
229
|
+
const provider = providerRegistry.findProvider(pkg.source);
|
|
230
|
+
if (!provider) {
|
|
231
|
+
throw new Error(`No provider found for package source: ${pkg.source}`);
|
|
232
|
+
}
|
|
233
|
+
// Parse current URL
|
|
234
|
+
const parsed = provider.parse(pkg.source);
|
|
235
|
+
// Get latest version
|
|
236
|
+
let latestVersion;
|
|
237
|
+
try {
|
|
238
|
+
if (provider.getLatestVersion) {
|
|
239
|
+
latestVersion = await provider.getLatestVersion(parsed);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
throw new Error('Provider does not support version resolution');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
throw new Error(`Failed to get latest version: ${error instanceof Error ? error.message : error}`);
|
|
247
|
+
}
|
|
248
|
+
const oldVersion = pkg.version;
|
|
249
|
+
// Check if already at latest
|
|
250
|
+
if (latestVersion === oldVersion) {
|
|
251
|
+
return {
|
|
252
|
+
id: packageId,
|
|
253
|
+
upgraded: false,
|
|
254
|
+
oldVersion,
|
|
255
|
+
newVersion: latestVersion,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
// Build new URL with latest version
|
|
259
|
+
const newUrl = pkg.source.replace(oldVersion, latestVersion);
|
|
260
|
+
// Fetch new content
|
|
261
|
+
const content = await provider.fetch(newUrl);
|
|
262
|
+
// Save to cache
|
|
263
|
+
const cache = new CacheManager(workspaceRoot);
|
|
264
|
+
const cachePath = pkg.cached.startsWith('.libraries/')
|
|
265
|
+
? pkg.cached.slice('.libraries/'.length)
|
|
266
|
+
: pkg.cached;
|
|
267
|
+
const saved = await cache.save(cachePath, content);
|
|
268
|
+
// Update package entry
|
|
269
|
+
const updatedEntry = {
|
|
270
|
+
...pkg,
|
|
271
|
+
source: newUrl,
|
|
272
|
+
version: latestVersion,
|
|
273
|
+
fetched: new Date().toISOString(),
|
|
274
|
+
integrity: saved.integrity,
|
|
275
|
+
};
|
|
276
|
+
registry.addPackage(updatedEntry);
|
|
277
|
+
await registry.save();
|
|
278
|
+
return {
|
|
279
|
+
id: packageId,
|
|
280
|
+
upgraded: true,
|
|
281
|
+
oldVersion,
|
|
282
|
+
newVersion: latestVersion,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=package.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { loadRepo } from './loader.js';
|
|
2
|
+
export { buildContext, writeContext, get, parentsOf, childrenOf, getConceptContext } from './builders/context.js';
|
|
3
|
+
export { mergeRepos, extendRepo, loadRepoFromJSON } from './merge.js';
|
|
4
|
+
export type { DocId, Slug, Section, ConceptBase, BusyDocument, LocalDef, Operation, ImportDef, EdgeRole, Edge, File, Repo, ContextPayload, FrontMatter, } from './types/schema.js';
|
|
5
|
+
export type { BuildOpts, ConceptContext } from './builders/context.js';
|
|
6
|
+
export { DocIdSchema, SlugSchema, SectionIdSchema, ConceptIdSchema, SectionSchema, ConceptBaseSchema, LocalDefSchema, SetupSchema, OperationSchema, ImportDefSchema, BusyDocumentSchema, PlaybookSchema, EdgeRoleSchema, EdgeSchema, FileSchema, RepoSchema, ContextPayloadSchema, FrontMatterSchema, } from './types/schema.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAClH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGtE,YAAY,EACV,KAAK,EACL,IAAI,EACJ,OAAO,EACP,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,EACR,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,cAAc,EACd,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvE,OAAO,EACL,WAAW,EACX,UAAU,EACV,eAAe,EACf,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,UAAU,EACV,UAAU,EACV,UAAU,EACV,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Main exports
|
|
2
|
+
export { loadRepo } from './loader.js';
|
|
3
|
+
export { buildContext, writeContext, get, parentsOf, childrenOf, getConceptContext } from './builders/context.js';
|
|
4
|
+
export { mergeRepos, extendRepo, loadRepoFromJSON } from './merge.js';
|
|
5
|
+
// Zod Schema exports for validation in other repos
|
|
6
|
+
export { DocIdSchema, SlugSchema, SectionIdSchema, ConceptIdSchema, SectionSchema, ConceptBaseSchema, LocalDefSchema, SetupSchema, OperationSchema, ImportDefSchema, BusyDocumentSchema, PlaybookSchema, EdgeRoleSchema, EdgeSchema, FileSchema, RepoSchema, ContextPayloadSchema, FrontMatterSchema, } from './types/schema.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,IAAI,EAUL,MAAM,mBAAmB,CAAC;AAS3B;;GAEG;AACH,wBAAsB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+T7D"}
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import fg from 'fast-glob';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { parseFrontMatter } from './parsers/frontmatter.js';
|
|
5
|
+
import { parseSections, getAllSections, findSection } from './parsers/sections.js';
|
|
6
|
+
import { extractLocalDefs } from './parsers/localdefs.js';
|
|
7
|
+
import { extractOperations } from './parsers/operations.js';
|
|
8
|
+
import { extractImports, legacyResolveImportTarget } from './parsers/imports.js';
|
|
9
|
+
import { extractLinksFromSection } from './parsers/links.js';
|
|
10
|
+
import { debug, warn } from './utils/logger.js';
|
|
11
|
+
/**
|
|
12
|
+
* Load and index a workspace from glob patterns
|
|
13
|
+
*/
|
|
14
|
+
export async function loadRepo(globs) {
|
|
15
|
+
debug.parser('Loading repo from globs: %o', globs);
|
|
16
|
+
// Find all markdown files
|
|
17
|
+
const filePaths = await fg(globs, {
|
|
18
|
+
absolute: true,
|
|
19
|
+
onlyFiles: true,
|
|
20
|
+
});
|
|
21
|
+
debug.parser('Found %d files', filePaths.length);
|
|
22
|
+
// Sort files for determinism
|
|
23
|
+
filePaths.sort();
|
|
24
|
+
// Build file map for resolution
|
|
25
|
+
const fileMap = new Map();
|
|
26
|
+
// First pass: parse frontmatter to build file map
|
|
27
|
+
const fileContents = new Map();
|
|
28
|
+
for (const filePath of filePaths) {
|
|
29
|
+
const content = await readFile(filePath, 'utf-8');
|
|
30
|
+
fileContents.set(filePath, content);
|
|
31
|
+
const { docId } = parseFrontMatter(content, filePath);
|
|
32
|
+
// Store multiple variants for resolution:
|
|
33
|
+
// - Full basename: "document.busy.md"
|
|
34
|
+
// - Without .busy: "document.md" (for imports that reference old extension)
|
|
35
|
+
// - Just name: "document"
|
|
36
|
+
const basename = path.basename(filePath);
|
|
37
|
+
const withoutBusy = basename.replace('.busy.md', '.md');
|
|
38
|
+
const nameOnly = basename.replace(/\.busy\.md$/, '').replace(/\.md$/, '');
|
|
39
|
+
fileMap.set(basename, { docId, path: filePath });
|
|
40
|
+
if (withoutBusy !== basename) {
|
|
41
|
+
fileMap.set(withoutBusy, { docId, path: filePath });
|
|
42
|
+
}
|
|
43
|
+
fileMap.set(nameOnly, { docId, path: filePath });
|
|
44
|
+
}
|
|
45
|
+
// Second pass: parse documents
|
|
46
|
+
const files = []; // Lightweight file representations
|
|
47
|
+
const docs = []; // Full concept definitions
|
|
48
|
+
const allLocaldefs = new Map();
|
|
49
|
+
const allOperations = new Map();
|
|
50
|
+
const allImports = [];
|
|
51
|
+
const allEdges = [];
|
|
52
|
+
const allSections = new Map();
|
|
53
|
+
// Store document parts temporarily before building final docs
|
|
54
|
+
const docParts = new Map();
|
|
55
|
+
for (const filePath of filePaths) {
|
|
56
|
+
const content = fileContents.get(filePath);
|
|
57
|
+
// Parse frontmatter
|
|
58
|
+
const { frontmatter, content: mdContent, docId, kind, types, extends: extends_ } = parseFrontMatter(content, filePath);
|
|
59
|
+
// Parse sections
|
|
60
|
+
const sections = parseSections(mdContent, docId, filePath);
|
|
61
|
+
// Create file representation (lightweight - just sections)
|
|
62
|
+
files.push({
|
|
63
|
+
docId,
|
|
64
|
+
path: filePath,
|
|
65
|
+
name: frontmatter.Name,
|
|
66
|
+
sections,
|
|
67
|
+
});
|
|
68
|
+
// Index all sections
|
|
69
|
+
for (const section of getAllSections(sections)) {
|
|
70
|
+
allSections.set(section.id, section);
|
|
71
|
+
}
|
|
72
|
+
// Extract local definitions
|
|
73
|
+
const localdefs = extractLocalDefs(sections, docId, filePath);
|
|
74
|
+
for (const localdef of localdefs) {
|
|
75
|
+
allLocaldefs.set(localdef.id, localdef);
|
|
76
|
+
}
|
|
77
|
+
// Extract operations
|
|
78
|
+
const operations = extractOperations(sections, docId, filePath);
|
|
79
|
+
for (const operation of operations) {
|
|
80
|
+
allOperations.set(operation.id, operation);
|
|
81
|
+
}
|
|
82
|
+
// Extract setup (if present)
|
|
83
|
+
const setupSection = findSection(sections, 'setup');
|
|
84
|
+
const setup = setupSection ? {
|
|
85
|
+
kind: 'setup',
|
|
86
|
+
id: `${docId}::setup`, // Use :: for concept IDs
|
|
87
|
+
docId,
|
|
88
|
+
slug: 'setup',
|
|
89
|
+
name: 'Setup',
|
|
90
|
+
content: setupSection.content,
|
|
91
|
+
types: [],
|
|
92
|
+
extends: [],
|
|
93
|
+
sectionRef: setupSection.id, // sectionRef uses # for section references
|
|
94
|
+
} : undefined;
|
|
95
|
+
// Extract imports
|
|
96
|
+
const { imports, symbols } = extractImports(content, docId);
|
|
97
|
+
// Store document parts
|
|
98
|
+
docParts.set(docId, {
|
|
99
|
+
filePath,
|
|
100
|
+
content,
|
|
101
|
+
frontmatter,
|
|
102
|
+
docId,
|
|
103
|
+
types,
|
|
104
|
+
extends: extends_,
|
|
105
|
+
sections,
|
|
106
|
+
localdefs,
|
|
107
|
+
operations,
|
|
108
|
+
setup,
|
|
109
|
+
imports,
|
|
110
|
+
symbols,
|
|
111
|
+
});
|
|
112
|
+
// Resolve imports
|
|
113
|
+
for (const importDef of imports) {
|
|
114
|
+
const resolved = legacyResolveImportTarget(importDef.target, docId, fileMap);
|
|
115
|
+
// Store resolved as ConceptId (string) per schema
|
|
116
|
+
if (resolved.docId) {
|
|
117
|
+
const resolvedId = resolved.slug
|
|
118
|
+
? `${resolved.docId}#${resolved.slug}`
|
|
119
|
+
: resolved.docId;
|
|
120
|
+
importDef.resolved = resolvedId;
|
|
121
|
+
// Create import edge
|
|
122
|
+
allEdges.push({
|
|
123
|
+
from: docId,
|
|
124
|
+
to: resolvedId,
|
|
125
|
+
role: 'imports',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Update symbol table (keeps object format for easy lookup)
|
|
129
|
+
if (symbols[importDef.label]) {
|
|
130
|
+
symbols[importDef.label] = resolved;
|
|
131
|
+
}
|
|
132
|
+
allImports.push(importDef);
|
|
133
|
+
}
|
|
134
|
+
// Extract links and create edges
|
|
135
|
+
for (const section of getAllSections(sections)) {
|
|
136
|
+
const linkEdges = extractLinksFromSection(section, section.content, symbols, fileMap);
|
|
137
|
+
allEdges.push(...linkEdges);
|
|
138
|
+
}
|
|
139
|
+
// Create extends edges for local definitions
|
|
140
|
+
for (const localdef of localdefs) {
|
|
141
|
+
for (const parent of localdef.extends) {
|
|
142
|
+
const resolvedParent = resolveSymbol(parent, docId, allLocaldefs, docs, symbols);
|
|
143
|
+
if (resolvedParent) {
|
|
144
|
+
allEdges.push({
|
|
145
|
+
from: localdef.id,
|
|
146
|
+
to: resolvedParent,
|
|
147
|
+
role: 'extends',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
warn(`Unresolved extends: ${parent} in ${localdef.id}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Build final document structures with inline arrays
|
|
157
|
+
for (const [docId, parts] of docParts) {
|
|
158
|
+
const isPlaybook = parts.types.some((t) => t.toLowerCase() === 'playbook');
|
|
159
|
+
if (isPlaybook) {
|
|
160
|
+
// Extract sequence from ExecutePlaybook operation
|
|
161
|
+
const sequence = extractPlaybookSequence(parts.sections);
|
|
162
|
+
const doc = {
|
|
163
|
+
kind: 'playbook',
|
|
164
|
+
id: parts.docId,
|
|
165
|
+
docId: parts.docId,
|
|
166
|
+
slug: parts.docId.toLowerCase(),
|
|
167
|
+
name: parts.frontmatter.Name,
|
|
168
|
+
content: parts.content,
|
|
169
|
+
types: parts.types,
|
|
170
|
+
extends: parts.extends,
|
|
171
|
+
sectionRef: `${parts.docId}#`, // Root reference
|
|
172
|
+
imports: parts.imports,
|
|
173
|
+
localdefs: parts.localdefs,
|
|
174
|
+
setup: parts.setup,
|
|
175
|
+
operations: parts.operations,
|
|
176
|
+
sequence,
|
|
177
|
+
};
|
|
178
|
+
docs.push(doc);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
const doc = {
|
|
182
|
+
kind: 'document',
|
|
183
|
+
id: parts.docId,
|
|
184
|
+
docId: parts.docId,
|
|
185
|
+
slug: parts.docId.toLowerCase(),
|
|
186
|
+
name: parts.frontmatter.Name,
|
|
187
|
+
content: parts.content,
|
|
188
|
+
types: parts.types,
|
|
189
|
+
extends: parts.extends,
|
|
190
|
+
sectionRef: `${parts.docId}#`, // Root reference
|
|
191
|
+
imports: parts.imports,
|
|
192
|
+
localdefs: parts.localdefs,
|
|
193
|
+
setup: parts.setup,
|
|
194
|
+
operations: parts.operations,
|
|
195
|
+
};
|
|
196
|
+
docs.push(doc);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Inherit operations from parent documents
|
|
200
|
+
inheritOperations(docs, allOperations);
|
|
201
|
+
// Build concepts array (includes all documents as ConceptBase)
|
|
202
|
+
const concepts = docs.map((doc) => ({
|
|
203
|
+
kind: doc.kind,
|
|
204
|
+
id: doc.id,
|
|
205
|
+
docId: doc.docId,
|
|
206
|
+
slug: doc.slug,
|
|
207
|
+
name: doc.name,
|
|
208
|
+
content: doc.content,
|
|
209
|
+
types: doc.types,
|
|
210
|
+
extends: doc.extends,
|
|
211
|
+
sectionRef: doc.sectionRef,
|
|
212
|
+
children: [], // ConceptBase has children for hierarchy
|
|
213
|
+
}));
|
|
214
|
+
// Build byId index
|
|
215
|
+
const byId = {};
|
|
216
|
+
for (const concept of concepts) {
|
|
217
|
+
byId[concept.id] = concept;
|
|
218
|
+
}
|
|
219
|
+
for (const [id, section] of allSections) {
|
|
220
|
+
byId[id] = section;
|
|
221
|
+
}
|
|
222
|
+
for (const [id, localdef] of allLocaldefs) {
|
|
223
|
+
byId[id] = localdef;
|
|
224
|
+
}
|
|
225
|
+
for (const [id, operation] of allOperations) {
|
|
226
|
+
byId[id] = operation;
|
|
227
|
+
}
|
|
228
|
+
// Reclassify edges based on target type
|
|
229
|
+
// Links to operations should be 'calls', links to defs/concepts should be 'ref'
|
|
230
|
+
for (const edge of allEdges) {
|
|
231
|
+
if (edge.role === 'ref') {
|
|
232
|
+
const target = byId[edge.to];
|
|
233
|
+
if (target && target.kind === 'operation') {
|
|
234
|
+
edge.role = 'calls';
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Build byFile index
|
|
239
|
+
const byFile = {};
|
|
240
|
+
for (const doc of docs) {
|
|
241
|
+
const bySlug = {};
|
|
242
|
+
const parts = docParts.get(doc.docId);
|
|
243
|
+
if (parts) {
|
|
244
|
+
for (const section of getAllSections(parts.sections)) {
|
|
245
|
+
bySlug[section.slug] = section;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
byFile[doc.docId] = { concept: doc, bySlug };
|
|
249
|
+
}
|
|
250
|
+
const repo = {
|
|
251
|
+
files,
|
|
252
|
+
concepts,
|
|
253
|
+
localdefs: Object.fromEntries(allLocaldefs),
|
|
254
|
+
operations: Object.fromEntries(allOperations),
|
|
255
|
+
imports: allImports,
|
|
256
|
+
byId,
|
|
257
|
+
byFile,
|
|
258
|
+
edges: allEdges,
|
|
259
|
+
};
|
|
260
|
+
debug.parser('Loaded repo: %d docs, %d concepts, %d localdefs, %d operations, %d imports, %d edges', docs.length, concepts.length, allLocaldefs.size, allOperations.size, allImports.length, allEdges.length);
|
|
261
|
+
return repo;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Inherit operations from parent documents
|
|
265
|
+
* Inherits from:
|
|
266
|
+
* 1. Documents in the 'extends' array (explicit extension)
|
|
267
|
+
* 2. Documents in the 'types' array (implicit type-based inheritance)
|
|
268
|
+
*/
|
|
269
|
+
function inheritOperations(docs, allOperations) {
|
|
270
|
+
// Build doc lookup by name
|
|
271
|
+
const docByName = new Map();
|
|
272
|
+
for (const doc of docs) {
|
|
273
|
+
docByName.set(doc.name, doc);
|
|
274
|
+
}
|
|
275
|
+
// Process each document
|
|
276
|
+
for (const doc of docs) {
|
|
277
|
+
// Collect parent names from both extends and types
|
|
278
|
+
const parentNames = [...doc.extends, ...doc.types];
|
|
279
|
+
if (parentNames.length === 0)
|
|
280
|
+
continue;
|
|
281
|
+
// Get operations currently in this document
|
|
282
|
+
const existingOps = new Set();
|
|
283
|
+
for (const [id, op] of allOperations) {
|
|
284
|
+
if (op.docId === doc.docId) {
|
|
285
|
+
existingOps.add(op.slug);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Inherit from parent documents
|
|
289
|
+
for (const parentName of parentNames) {
|
|
290
|
+
const parentDoc = docByName.get(parentName);
|
|
291
|
+
if (!parentDoc) {
|
|
292
|
+
debug.parser('Parent document not found: %s', parentName);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
// Find all operations in parent document
|
|
296
|
+
for (const [id, op] of allOperations) {
|
|
297
|
+
if (op.docId === parentDoc.docId) {
|
|
298
|
+
// If operation not overridden in child, inherit it
|
|
299
|
+
if (!existingOps.has(op.slug)) {
|
|
300
|
+
const inheritedOp = {
|
|
301
|
+
...op,
|
|
302
|
+
id: `${doc.docId}::${op.slug}`, // Use :: for concept IDs
|
|
303
|
+
docId: doc.docId,
|
|
304
|
+
};
|
|
305
|
+
allOperations.set(inheritedOp.id, inheritedOp);
|
|
306
|
+
existingOps.add(op.slug);
|
|
307
|
+
debug.parser('Inherited operation %s from %s to %s', op.name, parentDoc.name, doc.name);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Resolve a symbol (name or label) to a node ID
|
|
316
|
+
*/
|
|
317
|
+
function resolveSymbol(nameOrLabel, currentDocId, localdefs, docs, symbols) {
|
|
318
|
+
// 1. Check for LocalDef in same doc
|
|
319
|
+
const localdefId = `${currentDocId}::${nameOrLabel.toLowerCase()}`;
|
|
320
|
+
if (localdefs.has(localdefId)) {
|
|
321
|
+
return localdefId;
|
|
322
|
+
}
|
|
323
|
+
// 2. Check for Concept/Doc by Name
|
|
324
|
+
const doc = docs.find((d) => d.name === nameOrLabel);
|
|
325
|
+
if (doc) {
|
|
326
|
+
return doc.docId;
|
|
327
|
+
}
|
|
328
|
+
// 3. Check import symbol table
|
|
329
|
+
const symbol = symbols[nameOrLabel];
|
|
330
|
+
if (symbol?.docId) {
|
|
331
|
+
return symbol.slug ? `${symbol.docId}#${symbol.slug}` : symbol.docId;
|
|
332
|
+
}
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Extract sequence of operations from a playbook's ExecutePlaybook operation
|
|
337
|
+
* Looks for sections with "Step" in the title and extracts Target metadata
|
|
338
|
+
*/
|
|
339
|
+
function extractPlaybookSequence(sections) {
|
|
340
|
+
const sequence = [];
|
|
341
|
+
// Find ExecutePlaybook operation in the Operations section
|
|
342
|
+
const allSecs = getAllSections(sections);
|
|
343
|
+
const executePlaybook = allSecs.find((sec) => sec.title.toLowerCase() === 'executeplaybook');
|
|
344
|
+
if (!executePlaybook) {
|
|
345
|
+
return sequence;
|
|
346
|
+
}
|
|
347
|
+
// Look for child sections that are steps (contain "step" in title, case-insensitive)
|
|
348
|
+
for (const child of executePlaybook.children) {
|
|
349
|
+
if (child.title.toLowerCase().includes('step')) {
|
|
350
|
+
// Extract Target field from content
|
|
351
|
+
// Pattern: - **Target:** `OperationName`
|
|
352
|
+
const targetMatch = child.content.match(/^\s*-\s*\*\*Target:\*\*\s*`([^`]+)`/m);
|
|
353
|
+
if (targetMatch) {
|
|
354
|
+
sequence.push(targetMatch[1]);
|
|
355
|
+
debug.parser('Found playbook sequence step: %s -> %s', child.title, targetMatch[1]);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return sequence;
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=loader.js.map
|
package/dist/merge.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Repo } from './types/schema.js';
|
|
2
|
+
/**
|
|
3
|
+
* Merge multiple repos into a single repo
|
|
4
|
+
* Later repos override earlier ones when there are conflicts
|
|
5
|
+
*/
|
|
6
|
+
export declare function mergeRepos(...repos: Repo[]): Repo;
|
|
7
|
+
/**
|
|
8
|
+
* Extend a base repo with additional files
|
|
9
|
+
* This is a convenience wrapper around mergeRepos
|
|
10
|
+
*/
|
|
11
|
+
export declare function extendRepo(baseRepo: Repo, extensionRepo: Repo): Repo;
|
|
12
|
+
/**
|
|
13
|
+
* Load a repo from JSON and validate it against the schema
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadRepoFromJSON(json: string): Repo;
|
|
16
|
+
//# sourceMappingURL=merge.d.ts.map
|