@vimhead.dev/norn-cli 0.1.0-tip.35359392805.1 → 0.1.0-tip.35390859630.1

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 (111) hide show
  1. package/assets/README.md +17 -26
  2. package/assets/docs/README.md +3 -3
  3. package/assets/docs/agents.md +12 -1
  4. package/assets/docs/cli.md +5 -5
  5. package/assets/docs/composition.md +21 -21
  6. package/assets/docs/persistence.md +7 -10
  7. package/assets/docs/projects.md +30 -18
  8. package/assets/docs/providers.md +1 -1
  9. package/assets/docs/recovery.md +13 -9
  10. package/assets/docs/resources.md +4 -19
  11. package/assets/docs/schemas.md +1 -1
  12. package/assets/docs/workflows.md +95 -7
  13. package/assets/examples/agent-then-analysis/README.md +4 -4
  14. package/assets/examples/agent-then-analysis/input.json +1 -1
  15. package/assets/examples/agent-then-analysis/norn.project.json +1 -1
  16. package/assets/examples/agent-then-analysis/plugin.ts +55 -68
  17. package/assets/examples/caller-selected-continuation/README.md +5 -5
  18. package/assets/examples/caller-selected-continuation/caller.ts +35 -38
  19. package/assets/examples/caller-selected-continuation/input.json +2 -2
  20. package/assets/examples/caller-selected-continuation/norn.project.json +1 -1
  21. package/assets/examples/caller-selected-continuation/producer.ts +15 -23
  22. package/assets/examples/coordinating-multiple-agents/README.md +2 -2
  23. package/assets/examples/coordinating-multiple-agents/input.json +1 -1
  24. package/assets/examples/coordinating-multiple-agents/norn.project.json +1 -1
  25. package/assets/examples/coordinating-multiple-agents/plugin.ts +52 -56
  26. package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +4 -4
  27. package/assets/examples/getting-started/norn.project.json +1 -1
  28. package/assets/examples/getting-started/plugin.ts +16 -26
  29. package/assets/examples/minimal-workflow/README.md +8 -8
  30. package/assets/examples/minimal-workflow/norn.project.json +1 -1
  31. package/assets/examples/minimal-workflow/plugin.ts +11 -25
  32. package/assets/examples/shared-state/README.md +10 -2
  33. package/assets/examples/shared-state/input.json +1 -1
  34. package/assets/examples/shared-state/norn.project.json +1 -1
  35. package/assets/examples/shared-state/plugin.ts +36 -41
  36. package/assets/examples/shared-state/shared-state.ts +75 -0
  37. package/assets/{packages/sdk/src → examples/shared-state}/state-adapter.ts +17 -16
  38. package/assets/examples/worktree-development-loop/README.md +6 -7
  39. package/assets/examples/worktree-development-loop/norn.project.json +1 -1
  40. package/assets/examples/worktree-development-loop/plugin.ts +6 -26
  41. package/assets/examples/worktree-development-loop/scope.ts +4 -0
  42. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +14 -15
  43. package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +3 -4
  44. package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +2 -2
  45. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +34 -33
  46. package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +2 -3
  47. package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +7 -3
  48. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +23 -16
  49. package/assets/examples/worktree-development-loop/workflows/planning/index.ts +2 -3
  50. package/assets/examples/worktree-development-loop/workflows/planning/schema.ts +4 -2
  51. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +40 -31
  52. package/assets/examples/worktree-development-loop/workflows/review/index.ts +3 -4
  53. package/assets/examples/worktree-development-loop/workflows/review/schema.ts +5 -4
  54. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +49 -44
  55. package/assets/examples/worktree-development-loop/workflows/review-router/index.ts +2 -3
  56. package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +4 -3
  57. package/assets/package.json +1 -1
  58. package/assets/packages/cli/src/cli.ts +35 -34
  59. package/assets/packages/cli/src/client.ts +9 -16
  60. package/assets/packages/cli/src/documentation-intro.ts +1 -1
  61. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  62. package/assets/packages/cli/src/internal/agent-response-tool.ts +3 -3
  63. package/assets/packages/cli/src/internal/engine.ts +38 -62
  64. package/assets/packages/cli/src/internal/errors.ts +4 -4
  65. package/assets/packages/cli/src/internal/launch-request.ts +6 -6
  66. package/assets/packages/cli/src/internal/run-state.ts +31 -21
  67. package/assets/packages/cli/src/internal/run.ts +2 -5
  68. package/assets/packages/cli/src/internal/workflow-registry.ts +114 -111
  69. package/assets/packages/cli/src/workflow-loader.ts +214 -0
  70. package/assets/packages/core/src/workflow-transition.ts +2 -2
  71. package/assets/packages/sdk/src/api.ts +133 -289
  72. package/assets/packages/sdk/src/index.ts +1 -1
  73. package/assets/packages/sdk/src/schema.ts +3 -3
  74. package/assets/tests/workflow-ref.test.ts +50 -51
  75. package/dist/cli.js +33 -32
  76. package/dist/client.d.ts +3 -4
  77. package/dist/client.js +3 -8
  78. package/dist/documentation-intro.js +1 -1
  79. package/dist/generated-build-info.d.ts +2 -2
  80. package/dist/generated-build-info.js +2 -2
  81. package/dist/internal/agent-response-tool.js +3 -3
  82. package/dist/internal/engine.d.ts +4 -8
  83. package/dist/internal/engine.js +36 -49
  84. package/dist/internal/errors.d.ts +3 -3
  85. package/dist/internal/errors.js +1 -1
  86. package/dist/internal/launch-request.d.ts +4 -4
  87. package/dist/internal/launch-request.js +2 -2
  88. package/dist/internal/run-state.d.ts +9 -8
  89. package/dist/internal/run-state.js +29 -20
  90. package/dist/internal/run.d.ts +1 -3
  91. package/dist/internal/run.js +3 -5
  92. package/dist/internal/workflow-registry.d.ts +25 -17
  93. package/dist/internal/workflow-registry.js +107 -72
  94. package/dist/{plugin-loader.d.ts → workflow-loader.d.ts} +7 -8
  95. package/dist/workflow-loader.js +235 -0
  96. package/package.json +2 -2
  97. package/assets/examples/worktree-development-loop/manifest.ts +0 -26
  98. package/assets/examples/worktree-development-loop/state.ts +0 -23
  99. package/assets/examples/worktree-development-loop/workflows/development-loop/declaration.ts +0 -8
  100. package/assets/examples/worktree-development-loop/workflows/implementation/declaration.ts +0 -8
  101. package/assets/examples/worktree-development-loop/workflows/planning/declaration.ts +0 -8
  102. package/assets/examples/worktree-development-loop/workflows/review/declaration.ts +0 -8
  103. package/assets/examples/worktree-development-loop/workflows/review-router/declaration.ts +0 -12
  104. package/assets/packages/cli/src/internal/run-resources.ts +0 -23
  105. package/assets/packages/cli/src/internal/state-store.ts +0 -83
  106. package/assets/packages/cli/src/plugin-loader.ts +0 -400
  107. package/dist/internal/run-resources.d.ts +0 -6
  108. package/dist/internal/run-resources.js +0 -26
  109. package/dist/internal/state-store.d.ts +0 -23
  110. package/dist/internal/state-store.js +0 -103
  111. package/dist/plugin-loader.js +0 -341
@@ -1,341 +0,0 @@
1
- // src/plugin-loader.ts
2
- import * as nornModule from "@vimhead.dev/norn";
3
- import { isWorkflowPlugin } from "@vimhead.dev/norn";
4
-
5
- // ../core/src/errors.ts
6
- function isNodeError(error) {
7
- return error instanceof Error && "code" in error;
8
- }
9
-
10
- // src/plugin-loader.ts
11
- import * as nornFilesModule from "@vimhead.dev/norn/files";
12
- import * as nornSchemaModule from "@vimhead.dev/norn/schema";
13
- import { inspectSchema } from "@vimhead.dev/norn/schema";
14
- import { createJiti } from "jiti/static";
15
- import { access, readdir, readFile } from "node:fs/promises";
16
- import { dirname, isAbsolute, join, resolve, sep } from "node:path";
17
- import { pathToFileURL } from "node:url";
18
- import * as typeboxModule from "typebox";
19
- import { Type } from "typebox";
20
- import * as typeboxCompileModule from "typebox/compile";
21
- import * as typeboxSchemaModule from "typebox/schema";
22
- import * as typeboxValueModule from "typebox/value";
23
- import { AssertError, Value } from "typebox/value";
24
- import { errorMessage, NornProjectLoadError } from "./internal/errors.js";
25
- import { NornMemoryWorkflowState } from "./internal/state-store.js";
26
- import { decodePluginConfiguration, NornWorkflowRegistry } from "./internal/workflow-registry.js";
27
- var NORN_PROJECT_FILE_NAME = "norn.project.json";
28
- var nornConfigSchema = Type.Object({
29
- plugins: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
30
- includes: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
31
- config: Type.Record(Type.String(), Type.Unknown(), { default: {} })
32
- });
33
- var nornProjectConfigSchema = Type.Object({
34
- ...nornConfigSchema.properties,
35
- version: Type.Literal(1, { default: 1 })
36
- });
37
- async function loadNornProject(cwd) {
38
- const { project, diagnostics } = await collectNornProject(cwd);
39
- if (diagnostics.length > 0) throw new NornProjectLoadError({ diagnostics });
40
- return project;
41
- }
42
- async function discoverNornProject(cwd) {
43
- const { project, diagnostics } = await collectNornProject(cwd);
44
- return { project: buildProjectInfo(project), workflows: project.registry.list(), isComplete: diagnostics.length === 0, diagnostics };
45
- }
46
- async function inspectNornWorkflow(input) {
47
- const { project, diagnostics } = await collectNornProject(input.cwd);
48
- const summary = project.registry.list().find((workflow) => workflow.id === input.workflowId);
49
- if (!summary) {
50
- if (diagnostics.length === 0) throw new Error(`Unknown workflow: ${input.workflowId}`);
51
- return { workflow: null, isComplete: false, diagnostics };
52
- }
53
- const info = summary.plugin;
54
- if (!info?.path || !info.configPath) throw new Error(`Loaded workflow lacks plugin source: ${input.workflowId}`);
55
- try {
56
- return { workflow: project.registry.inspect(input.workflowId) ?? null, isComplete: diagnostics.length === 0, diagnostics };
57
- } catch (error) {
58
- return { workflow: null, isComplete: false, diagnostics: [...diagnostics, createPluginDiagnostic({
59
- source: { configPath: info.configPath, pluginPath: info.path, pluginId: info.id },
60
- workflowId: input.workflowId,
61
- stage: "schema",
62
- error
63
- })] };
64
- }
65
- }
66
- function buildProjectInfo(project) {
67
- return {
68
- cwd: project.cwd,
69
- projectPath: project.projectPath,
70
- projectRoot: project.projectRoot,
71
- configPath: project.configPath,
72
- configRoot: project.configRoot,
73
- configFiles: project.configFiles.map((file) => file.path),
74
- plugins: project.pluginInfos
75
- };
76
- }
77
- async function findNornProject(cwd) {
78
- const projectPath = await findNearestNornProject(cwd);
79
- const projectRootConfig = await readNornProjectConfigFile(projectPath);
80
- const configFiles = await loadIncludedNornConfigFiles(projectRootConfig, /* @__PURE__ */ new Set([projectRootConfig.path]));
81
- return {
82
- cwd: resolve(cwd),
83
- projectPath: projectRootConfig.path,
84
- projectRoot: projectRootConfig.root,
85
- configPath: projectRootConfig.path,
86
- configRoot: projectRootConfig.root,
87
- config: projectRootConfig.config,
88
- configFiles,
89
- projectConfig: mergeProjectConfig(configFiles)
90
- };
91
- }
92
- async function findNearestNornProject(cwd) {
93
- let current = resolve(cwd);
94
- while (true) {
95
- const projectPath = join(current, NORN_PROJECT_FILE_NAME);
96
- try {
97
- await access(projectPath);
98
- return projectPath;
99
- } catch (error) {
100
- if (!isNodeError(error) || error.code !== "ENOENT") throw error;
101
- }
102
- const parent = dirname(current);
103
- if (parent === current) throw new Error(`Could not find ${NORN_PROJECT_FILE_NAME} from ${cwd}`);
104
- current = parent;
105
- }
106
- }
107
- async function loadIncludedNornConfigFiles(projectFile, visitedPaths) {
108
- const includedConfigFiles = await Promise.all(projectFile.config.includes.map(async (includePath) => {
109
- const configPaths = await expandIncludePath(projectFile.root, includePath);
110
- return Promise.all(configPaths.map(readNornConfigFile));
111
- }));
112
- const descendants = await Promise.all(includedConfigFiles.flat().map((includedConfigFile) => loadNornConfigTree(includedConfigFile, visitedPaths)));
113
- return [createProjectConfigEntry(projectFile), ...descendants.flat()];
114
- }
115
- async function loadNornConfigTree(configFile, visitedPaths) {
116
- if (visitedPaths.has(configFile.path)) return [];
117
- visitedPaths.add(configFile.path);
118
- const includedConfigFiles = await Promise.all(configFile.config.includes.map(async (includePath) => {
119
- const configPaths = await expandIncludePath(configFile.root, includePath);
120
- return Promise.all(configPaths.map(readNornConfigFile));
121
- }));
122
- const descendants = await Promise.all(includedConfigFiles.flat().map((includedConfigFile) => loadNornConfigTree(includedConfigFile, visitedPaths)));
123
- return [configFile, ...descendants.flat()];
124
- }
125
- async function readNornProjectConfigFile(path) {
126
- const config = Value.Parse(nornProjectConfigSchema, Value.Default(nornProjectConfigSchema, JSON.parse(await readFile(path, "utf8"))));
127
- return { path, root: dirname(path), config };
128
- }
129
- async function readNornConfigFile(path) {
130
- const config = Value.Parse(nornConfigSchema, Value.Default(nornConfigSchema, JSON.parse(await readFile(path, "utf8"))));
131
- return { path, root: dirname(path), config };
132
- }
133
- function createProjectConfigEntry(projectFile) {
134
- return {
135
- path: projectFile.path,
136
- root: projectFile.root,
137
- config: {
138
- plugins: projectFile.config.plugins,
139
- includes: projectFile.config.includes,
140
- config: projectFile.config.config
141
- }
142
- };
143
- }
144
- async function collectNornProject(cwd) {
145
- const project = await findNornProject(cwd);
146
- const imported = await importWorkflowPlugins(project);
147
- const diagnostics = [...imported.diagnostics];
148
- const registry = new NornWorkflowRegistry();
149
- const state = new NornMemoryWorkflowState();
150
- const loaded = [];
151
- const conflicts = collectPluginConflicts(imported.plugins);
152
- for (const candidate of imported.plugins) {
153
- const pluginConflicts = conflicts.get(candidate);
154
- if (pluginConflicts) {
155
- diagnostics.push(...pluginConflicts);
156
- continue;
157
- }
158
- const result = registerProjectPlugin({ candidate, project, registry, state });
159
- diagnostics.push(...result.diagnostics);
160
- if (result.loaded) loaded.push(result.loaded);
161
- }
162
- return {
163
- project: { ...project, plugins: loaded.map((entry) => entry.plugin), pluginInfos: loaded.map((entry) => entry.info), registry, workflows: registry.launchableEntries(), state },
164
- diagnostics
165
- };
166
- }
167
- function collectPluginConflicts(plugins) {
168
- const byPluginId = /* @__PURE__ */ new Map();
169
- const byWorkflowId = /* @__PURE__ */ new Map();
170
- for (const candidate of plugins) {
171
- const id = candidate.plugin.manifest.id;
172
- byPluginId.set(id, [...byPluginId.get(id) ?? [], candidate]);
173
- for (const workflowId of new Set(Object.values(candidate.plugin.manifest.workflows).map((workflow) => workflow.id))) {
174
- byWorkflowId.set(workflowId, [...byWorkflowId.get(workflowId) ?? [], candidate]);
175
- }
176
- }
177
- const conflicts = /* @__PURE__ */ new Map();
178
- for (const [pluginId, candidates] of byPluginId) {
179
- if (candidates.length < 2) continue;
180
- for (const candidate of candidates) conflicts.set(candidate, [createPluginDiagnostic({
181
- source: candidate.source,
182
- workflowId: null,
183
- stage: "duplicate",
184
- error: new Error(`Duplicate Norn plugin id: ${pluginId} (${candidates.map((other) => other.source.pluginPath).join(", ")})`)
185
- })]);
186
- }
187
- for (const [workflowId, candidates] of byWorkflowId) {
188
- const distinctPlugins = candidates.filter((candidate) => byPluginId.get(candidate.plugin.manifest.id)?.length === 1);
189
- if (distinctPlugins.length < 2) continue;
190
- for (const candidate of distinctPlugins) conflicts.set(candidate, [...conflicts.get(candidate) ?? [], createPluginDiagnostic({
191
- source: candidate.source,
192
- workflowId,
193
- stage: "duplicate",
194
- error: new Error(`Workflow already registered: ${workflowId} (${distinctPlugins.map((other) => other.source.pluginPath).join(", ")})`)
195
- })]);
196
- }
197
- return conflicts;
198
- }
199
- async function importWorkflowPlugins(project) {
200
- const plugins = [];
201
- const diagnostics = [];
202
- for (const configFile of project.configFiles) {
203
- const jiti = createJiti(pathToFileURL(configFile.path).href, { moduleCache: false, virtualModules: nornWorkflowVirtualModules() });
204
- for (const pluginPath of configFile.config.plugins) {
205
- const source = { configPath: configFile.path, pluginPath: resolveConfigPath(configFile.root, pluginPath), pluginId: null };
206
- let stage = "import";
207
- try {
208
- const module = await jiti.import(pathToFileURL(source.pluginPath).href);
209
- stage = "declaration";
210
- if (!isWorkflowPlugin(module?.default)) throw new Error(`Norn plugin must be the default export: ${source.pluginPath}`);
211
- plugins.push({ plugin: module.default, source: { ...source, pluginId: module.default.manifest.id } });
212
- } catch (error) {
213
- diagnostics.push(createPluginDiagnostic({ source, workflowId: null, stage, error }));
214
- }
215
- }
216
- }
217
- return { plugins, diagnostics };
218
- }
219
- function registerProjectPlugin(input) {
220
- const { plugin, source } = input.candidate;
221
- const diagnostics = [];
222
- const unregister = [];
223
- let stage = "config";
224
- try {
225
- const configInput = input.project.projectConfig[plugin.manifest.id];
226
- const configuration = decodePluginConfiguration({ pluginId: plugin.manifest.id, schema: plugin.manifest.config, value: configInput });
227
- stage = "schema";
228
- const info = {
229
- id: plugin.manifest.id,
230
- path: source.pluginPath,
231
- configPath: source.configPath,
232
- configSchema: plugin.manifest.config ? inspectSchema(plugin.manifest.config) : null,
233
- config: configuration?.value
234
- };
235
- stage = "implementation";
236
- const implementation = typeof plugin.implementation === "function" ? plugin.implementation({ cwd: input.project.cwd, state: input.state }) : plugin.implementation;
237
- for (const [key, workflow] of Object.entries(plugin.manifest.workflows)) {
238
- stage = "implementation";
239
- try {
240
- const workflowImplementation = implementation.workflows?.[key];
241
- if (typeof workflowImplementation?.execute !== "function") throw new Error(`Missing implementation for workflow ${workflow.id}`);
242
- stage = "duplicate";
243
- if (input.registry.workflowById(workflow.id)) throw new Error(`Workflow already registered: ${workflow.id}`);
244
- stage = "declaration";
245
- unregister.push(input.registry.register(workflow, workflowImplementation, { plugin: workflowPluginInfo(info), configuration }));
246
- } catch (error) {
247
- diagnostics.push(createPluginDiagnostic({ source, workflowId: workflow.id, stage, error }));
248
- }
249
- }
250
- if (diagnostics.length === 0) return { loaded: { plugin, info }, diagnostics };
251
- } catch (error) {
252
- diagnostics.push(createPluginDiagnostic({ source, workflowId: null, stage, error }));
253
- }
254
- for (const dispose of unregister) dispose();
255
- return { loaded: null, diagnostics };
256
- }
257
- function createPluginDiagnostic(input) {
258
- return {
259
- ...input.source,
260
- workflowId: input.workflowId,
261
- stage: input.stage,
262
- message: errorMessage(input.error),
263
- issues: input.error instanceof AssertError ? input.error.cause.errors : []
264
- };
265
- }
266
- function workflowPluginInfo(info) {
267
- return { id: info.id, path: info.path, configPath: info.configPath };
268
- }
269
- function nornWorkflowVirtualModules() {
270
- return {
271
- "@vimhead.dev/norn": nornModule,
272
- "@vimhead.dev/norn/files": nornFilesModule,
273
- "@vimhead.dev/norn/schema": nornSchemaModule,
274
- typebox: typeboxModule,
275
- "typebox/value": typeboxValueModule,
276
- "typebox/compile": typeboxCompileModule,
277
- "typebox/schema": typeboxSchemaModule
278
- };
279
- }
280
- function mergeProjectConfig(configFiles) {
281
- const [projectFile, ...reusableConfigFiles] = configFiles;
282
- let reusableConfig = {};
283
- for (const configFile of reusableConfigFiles) {
284
- reusableConfig = mergeReusableConfigObjects(reusableConfig, configFile.config.config, ["config"]);
285
- }
286
- return mergeProjectConfigObjects(reusableConfig, projectFile?.config.config ?? {});
287
- }
288
- function mergeReusableConfigObjects(base, override, path) {
289
- const result = { ...base };
290
- for (const [key, overrideValue] of Object.entries(override)) {
291
- result[key] = Object.prototype.hasOwnProperty.call(result, key) ? mergeReusableConfigValue(result[key], overrideValue, [...path, key]) : overrideValue;
292
- }
293
- return result;
294
- }
295
- function mergeReusableConfigValue(base, override, path) {
296
- if (isPlainObject(base) && isPlainObject(override)) return mergeReusableConfigObjects(base, override, path);
297
- if (JSON.stringify(base) === JSON.stringify(override)) return base;
298
- throw new Error(`Conflicting Norn reusable config at ${path.join(".")}`);
299
- }
300
- function mergeProjectConfigObjects(base, override) {
301
- const result = { ...base };
302
- for (const [key, overrideValue] of Object.entries(override)) {
303
- result[key] = isPlainObject(result[key]) && isPlainObject(overrideValue) ? mergeProjectConfigObjects(result[key], overrideValue) : overrideValue;
304
- }
305
- return result;
306
- }
307
- function isPlainObject(value) {
308
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
309
- }
310
- async function expandIncludePath(configRoot, includePath) {
311
- const resolvedIncludePath = resolveConfigPath(configRoot, includePath);
312
- if (!hasGlobSegment(includePath)) return [resolvedIncludePath];
313
- return expandGlobSegments(isAbsolute(includePath) ? sep : configRoot, splitPathSegments(includePath));
314
- }
315
- async function expandGlobSegments(root, segments) {
316
- if (segments.length === 0) return [root];
317
- const [segment, ...remainingSegments] = segments;
318
- if (segment === "*") {
319
- const entries = await readdir(root, { withFileTypes: true });
320
- const expanded = await Promise.all(entries.filter((entry) => entry.isDirectory()).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => expandGlobSegments(join(root, entry.name), remainingSegments)));
321
- return expanded.flat();
322
- }
323
- return expandGlobSegments(join(root, segment), remainingSegments);
324
- }
325
- function resolveConfigPath(configRoot, path) {
326
- if (path.length === 0) throw new Error("Norn path must not be empty");
327
- return isAbsolute(path) ? path : resolve(configRoot, path);
328
- }
329
- function hasGlobSegment(path) {
330
- return splitPathSegments(path).includes("*");
331
- }
332
- function splitPathSegments(path) {
333
- return path.split(/[\\/]+/).filter((segment) => segment.length > 0 && segment !== ".");
334
- }
335
- export {
336
- NORN_PROJECT_FILE_NAME,
337
- discoverNornProject,
338
- findNornProject,
339
- inspectNornWorkflow,
340
- loadNornProject
341
- };