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.
Files changed (128) hide show
  1. package/README.md +129 -0
  2. package/dist/builders/context.d.ts +50 -0
  3. package/dist/builders/context.d.ts.map +1 -0
  4. package/dist/builders/context.js +190 -0
  5. package/dist/cache/index.d.ts +100 -0
  6. package/dist/cache/index.d.ts.map +1 -0
  7. package/dist/cache/index.js +270 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +463 -0
  11. package/dist/commands/package.d.ts +96 -0
  12. package/dist/commands/package.d.ts.map +1 -0
  13. package/dist/commands/package.js +285 -0
  14. package/dist/index.d.ts +7 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +7 -0
  17. package/dist/loader.d.ts +6 -0
  18. package/dist/loader.d.ts.map +1 -0
  19. package/dist/loader.js +361 -0
  20. package/dist/merge.d.ts +16 -0
  21. package/dist/merge.d.ts.map +1 -0
  22. package/dist/merge.js +102 -0
  23. package/dist/package/manifest.d.ts +59 -0
  24. package/dist/package/manifest.d.ts.map +1 -0
  25. package/dist/package/manifest.js +265 -0
  26. package/dist/parser.d.ts +28 -0
  27. package/dist/parser.d.ts.map +1 -0
  28. package/dist/parser.js +220 -0
  29. package/dist/parsers/frontmatter.d.ts +14 -0
  30. package/dist/parsers/frontmatter.d.ts.map +1 -0
  31. package/dist/parsers/frontmatter.js +110 -0
  32. package/dist/parsers/imports.d.ts +48 -0
  33. package/dist/parsers/imports.d.ts.map +1 -0
  34. package/dist/parsers/imports.js +147 -0
  35. package/dist/parsers/links.d.ts +12 -0
  36. package/dist/parsers/links.d.ts.map +1 -0
  37. package/dist/parsers/links.js +79 -0
  38. package/dist/parsers/localdefs.d.ts +6 -0
  39. package/dist/parsers/localdefs.d.ts.map +1 -0
  40. package/dist/parsers/localdefs.js +132 -0
  41. package/dist/parsers/operations.d.ts +32 -0
  42. package/dist/parsers/operations.d.ts.map +1 -0
  43. package/dist/parsers/operations.js +313 -0
  44. package/dist/parsers/sections.d.ts +15 -0
  45. package/dist/parsers/sections.d.ts.map +1 -0
  46. package/dist/parsers/sections.js +173 -0
  47. package/dist/parsers/tools.d.ts +30 -0
  48. package/dist/parsers/tools.d.ts.map +1 -0
  49. package/dist/parsers/tools.js +178 -0
  50. package/dist/parsers/triggers.d.ts +35 -0
  51. package/dist/parsers/triggers.d.ts.map +1 -0
  52. package/dist/parsers/triggers.js +219 -0
  53. package/dist/providers/base.d.ts +60 -0
  54. package/dist/providers/base.d.ts.map +1 -0
  55. package/dist/providers/base.js +34 -0
  56. package/dist/providers/github.d.ts +18 -0
  57. package/dist/providers/github.d.ts.map +1 -0
  58. package/dist/providers/github.js +109 -0
  59. package/dist/providers/gitlab.d.ts +18 -0
  60. package/dist/providers/gitlab.d.ts.map +1 -0
  61. package/dist/providers/gitlab.js +101 -0
  62. package/dist/providers/index.d.ts +13 -0
  63. package/dist/providers/index.d.ts.map +1 -0
  64. package/dist/providers/index.js +17 -0
  65. package/dist/providers/local.d.ts +31 -0
  66. package/dist/providers/local.d.ts.map +1 -0
  67. package/dist/providers/local.js +116 -0
  68. package/dist/providers/url.d.ts +16 -0
  69. package/dist/providers/url.d.ts.map +1 -0
  70. package/dist/providers/url.js +45 -0
  71. package/dist/registry/index.d.ts +99 -0
  72. package/dist/registry/index.d.ts.map +1 -0
  73. package/dist/registry/index.js +320 -0
  74. package/dist/types/schema.d.ts +3259 -0
  75. package/dist/types/schema.d.ts.map +1 -0
  76. package/dist/types/schema.js +258 -0
  77. package/dist/utils/logger.d.ts +19 -0
  78. package/dist/utils/logger.d.ts.map +1 -0
  79. package/dist/utils/logger.js +23 -0
  80. package/dist/utils/slugify.d.ts +14 -0
  81. package/dist/utils/slugify.d.ts.map +1 -0
  82. package/dist/utils/slugify.js +28 -0
  83. package/package.json +61 -0
  84. package/src/__tests__/cache.test.ts +393 -0
  85. package/src/__tests__/cli-package.test.ts +667 -0
  86. package/src/__tests__/fixtures/automated-workflow.busy.md +84 -0
  87. package/src/__tests__/fixtures/concept.busy.md +30 -0
  88. package/src/__tests__/fixtures/document.busy.md +44 -0
  89. package/src/__tests__/fixtures/simple-operation.busy.md +45 -0
  90. package/src/__tests__/fixtures/tool-document.busy.md +71 -0
  91. package/src/__tests__/fixtures/tool.busy.md +54 -0
  92. package/src/__tests__/imports.test.ts +244 -0
  93. package/src/__tests__/integration.test.ts +432 -0
  94. package/src/__tests__/operations.test.ts +408 -0
  95. package/src/__tests__/package-manifest.test.ts +455 -0
  96. package/src/__tests__/providers.test.ts +672 -0
  97. package/src/__tests__/registry.test.ts +402 -0
  98. package/src/__tests__/schema.test.ts +467 -0
  99. package/src/__tests__/tools.test.ts +376 -0
  100. package/src/__tests__/triggers.test.ts +312 -0
  101. package/src/builders/context.ts +294 -0
  102. package/src/cache/index.ts +312 -0
  103. package/src/cli/index.ts +514 -0
  104. package/src/commands/package.ts +392 -0
  105. package/src/index.ts +46 -0
  106. package/src/loader.ts +474 -0
  107. package/src/merge.ts +126 -0
  108. package/src/package/manifest.ts +349 -0
  109. package/src/parser.ts +278 -0
  110. package/src/parsers/frontmatter.ts +135 -0
  111. package/src/parsers/imports.ts +196 -0
  112. package/src/parsers/links.ts +108 -0
  113. package/src/parsers/localdefs.ts +166 -0
  114. package/src/parsers/operations.ts +404 -0
  115. package/src/parsers/sections.ts +230 -0
  116. package/src/parsers/tools.ts +215 -0
  117. package/src/parsers/triggers.ts +252 -0
  118. package/src/providers/base.ts +77 -0
  119. package/src/providers/github.ts +129 -0
  120. package/src/providers/gitlab.ts +121 -0
  121. package/src/providers/index.ts +25 -0
  122. package/src/providers/local.ts +129 -0
  123. package/src/providers/url.ts +56 -0
  124. package/src/registry/index.ts +408 -0
  125. package/src/types/schema.ts +369 -0
  126. package/src/utils/logger.ts +25 -0
  127. package/src/utils/slugify.ts +31 -0
  128. 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
@@ -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
@@ -0,0 +1,6 @@
1
+ import { Repo } from './types/schema.js';
2
+ /**
3
+ * Load and index a workspace from glob patterns
4
+ */
5
+ export declare function loadRepo(globs: string[]): Promise<Repo>;
6
+ //# sourceMappingURL=loader.d.ts.map
@@ -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
@@ -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