@vimhead.dev/norn-cli 0.1.0-tip.35359392805.1 → 0.1.0-tip.35436871363.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 (146) hide show
  1. package/assets/README.md +22 -27
  2. package/assets/docs/README.md +5 -5
  3. package/assets/docs/agents.md +48 -9
  4. package/assets/docs/cli.md +7 -7
  5. package/assets/docs/composition.md +25 -25
  6. package/assets/docs/persistence.md +34 -23
  7. package/assets/docs/projects.md +30 -18
  8. package/assets/docs/providers.md +3 -3
  9. package/assets/docs/recovery.md +13 -9
  10. package/assets/docs/schemas.md +1 -1
  11. package/assets/docs/workflows.md +105 -12
  12. package/assets/examples/agent-then-analysis/README.md +10 -10
  13. package/assets/examples/agent-then-analysis/input.json +1 -1
  14. package/assets/examples/agent-then-analysis/norn.project.json +1 -1
  15. package/assets/examples/agent-then-analysis/plugin.ts +57 -68
  16. package/assets/examples/caller-selected-continuation/README.md +15 -14
  17. package/assets/examples/caller-selected-continuation/caller.ts +37 -38
  18. package/assets/examples/caller-selected-continuation/input.json +2 -2
  19. package/assets/examples/caller-selected-continuation/norn.project.json +1 -1
  20. package/assets/examples/caller-selected-continuation/producer.ts +19 -24
  21. package/assets/examples/coordinating-multiple-agents/README.md +27 -17
  22. package/assets/examples/coordinating-multiple-agents/input.json +1 -1
  23. package/assets/examples/coordinating-multiple-agents/norn.project.json +1 -1
  24. package/assets/examples/coordinating-multiple-agents/plugin.ts +79 -60
  25. package/assets/examples/coordinating-multiple-agents/queue-tools.ts +43 -0
  26. package/assets/examples/coordinating-multiple-agents/work-queue.ts +60 -65
  27. package/assets/examples/getting-started/README.md +2 -2
  28. package/assets/examples/getting-started/norn.project.json +1 -1
  29. package/assets/examples/getting-started/plugin.ts +20 -26
  30. package/assets/examples/minimal-workflow/README.md +13 -12
  31. package/assets/examples/minimal-workflow/norn.project.json +1 -1
  32. package/assets/examples/minimal-workflow/plugin.ts +14 -25
  33. package/assets/examples/shared-state/README.md +12 -4
  34. package/assets/examples/shared-state/input.json +1 -1
  35. package/assets/examples/shared-state/norn.project.json +1 -1
  36. package/assets/examples/shared-state/plugin.ts +49 -41
  37. package/assets/examples/shared-state/shared-state.ts +56 -0
  38. package/assets/examples/shared-state/state-tools.ts +68 -0
  39. package/assets/examples/worktree-development-loop/README.md +18 -17
  40. package/assets/examples/worktree-development-loop/norn.project.json +1 -1
  41. package/assets/examples/worktree-development-loop/plugin.ts +6 -26
  42. package/assets/examples/worktree-development-loop/scope.ts +4 -0
  43. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +14 -15
  44. package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +3 -4
  45. package/assets/examples/worktree-development-loop/workflows/development-loop/repository.ts +5 -3
  46. package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +2 -2
  47. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +35 -33
  48. package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +2 -3
  49. package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +6 -3
  50. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +27 -16
  51. package/assets/examples/worktree-development-loop/workflows/planning/index.ts +2 -3
  52. package/assets/examples/worktree-development-loop/workflows/planning/schema.ts +4 -2
  53. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +43 -31
  54. package/assets/examples/worktree-development-loop/workflows/review/index.ts +3 -4
  55. package/assets/examples/worktree-development-loop/workflows/review/schema.ts +6 -6
  56. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +50 -44
  57. package/assets/examples/worktree-development-loop/workflows/review-router/index.ts +2 -3
  58. package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +5 -5
  59. package/assets/package.json +1 -1
  60. package/assets/packages/cli/src/cli.ts +44 -37
  61. package/assets/packages/cli/src/client.ts +9 -16
  62. package/assets/packages/cli/src/documentation-intro.ts +1 -1
  63. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  64. package/assets/packages/cli/src/internal/agent-response-tool.ts +3 -3
  65. package/assets/packages/cli/src/internal/agents.ts +24 -32
  66. package/assets/packages/cli/src/internal/commands.ts +2 -14
  67. package/assets/packages/cli/src/internal/engine.ts +63 -101
  68. package/assets/packages/cli/src/internal/errors.ts +4 -4
  69. package/assets/packages/cli/src/internal/launch-request.ts +6 -6
  70. package/assets/packages/cli/src/internal/logs.ts +1 -1
  71. package/assets/packages/cli/src/internal/run-log.ts +1 -1
  72. package/assets/packages/cli/src/internal/run-state.ts +38 -23
  73. package/assets/packages/cli/src/internal/run.ts +4 -62
  74. package/assets/packages/cli/src/internal/worker-directory.ts +17 -0
  75. package/assets/packages/cli/src/internal/workflow-registry.ts +126 -111
  76. package/assets/packages/cli/src/internal/working-directory.ts +6 -0
  77. package/assets/packages/cli/src/workflow-loader.ts +213 -0
  78. package/assets/packages/core/src/workflow-transition.ts +2 -2
  79. package/assets/packages/sdk/src/api.ts +141 -329
  80. package/assets/packages/sdk/src/index.ts +1 -4
  81. package/assets/packages/sdk/src/schema.ts +3 -3
  82. package/assets/tests/workflow-ref.test.ts +53 -55
  83. package/dist/cli.js +41 -35
  84. package/dist/client.d.ts +3 -4
  85. package/dist/client.js +3 -8
  86. package/dist/documentation-intro.js +1 -1
  87. package/dist/generated-build-info.d.ts +2 -2
  88. package/dist/generated-build-info.js +2 -2
  89. package/dist/internal/agent-response-tool.js +3 -3
  90. package/dist/internal/agents.d.ts +0 -4
  91. package/dist/internal/agents.js +30 -46
  92. package/dist/internal/commands.d.ts +0 -4
  93. package/dist/internal/commands.js +2 -10
  94. package/dist/internal/engine.d.ts +4 -8
  95. package/dist/internal/engine.js +57 -84
  96. package/dist/internal/errors.d.ts +3 -3
  97. package/dist/internal/errors.js +1 -1
  98. package/dist/internal/file-coordinator.d.ts +17 -0
  99. package/dist/internal/file-coordinator.js +162 -0
  100. package/dist/internal/launch-request.d.ts +4 -4
  101. package/dist/internal/launch-request.js +2 -2
  102. package/dist/internal/logs.d.ts +1 -1
  103. package/dist/internal/run-log.d.ts +1 -1
  104. package/dist/internal/run-state.d.ts +11 -8
  105. package/dist/internal/run-state.js +34 -22
  106. package/dist/internal/run.d.ts +3 -23
  107. package/dist/internal/run.js +4 -50
  108. package/dist/internal/worker-directory.d.ts +1 -0
  109. package/dist/internal/worker-directory.js +26 -0
  110. package/dist/internal/workflow-registry.d.ts +35 -17
  111. package/dist/internal/workflow-registry.js +110 -73
  112. package/dist/internal/working-directory.d.ts +1 -0
  113. package/dist/internal/working-directory.js +9 -0
  114. package/dist/{plugin-loader.d.ts → workflow-loader.d.ts} +7 -8
  115. package/dist/workflow-loader.js +234 -0
  116. package/package.json +2 -2
  117. package/assets/docs/resources.md +0 -61
  118. package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +0 -51
  119. package/assets/examples/worktree-development-loop/manifest.ts +0 -26
  120. package/assets/examples/worktree-development-loop/state.ts +0 -23
  121. package/assets/examples/worktree-development-loop/workflows/development-loop/declaration.ts +0 -8
  122. package/assets/examples/worktree-development-loop/workflows/implementation/declaration.ts +0 -8
  123. package/assets/examples/worktree-development-loop/workflows/planning/declaration.ts +0 -8
  124. package/assets/examples/worktree-development-loop/workflows/review/declaration.ts +0 -8
  125. package/assets/examples/worktree-development-loop/workflows/review-router/declaration.ts +0 -12
  126. package/assets/packages/cli/src/internal/artifacts.ts +0 -26
  127. package/assets/packages/cli/src/internal/resource-bindings.ts +0 -35
  128. package/assets/packages/cli/src/internal/run-resources.ts +0 -23
  129. package/assets/packages/cli/src/internal/state-store.ts +0 -83
  130. package/assets/packages/cli/src/plugin-loader.ts +0 -400
  131. package/assets/packages/cli/src/resources.ts +0 -69
  132. package/assets/packages/sdk/src/agent-resource-adapter.ts +0 -11
  133. package/assets/packages/sdk/src/resources.ts +0 -20
  134. package/assets/packages/sdk/src/state-adapter.ts +0 -75
  135. package/dist/internal/artifacts.d.ts +0 -10
  136. package/dist/internal/artifacts.js +0 -29
  137. package/dist/internal/resource-bindings.d.ts +0 -13
  138. package/dist/internal/resource-bindings.js +0 -34
  139. package/dist/internal/run-resources.d.ts +0 -6
  140. package/dist/internal/run-resources.js +0 -26
  141. package/dist/internal/state-store.d.ts +0 -23
  142. package/dist/internal/state-store.js +0 -103
  143. package/dist/plugin-loader.js +0 -341
  144. package/dist/resources.d.ts +0 -11
  145. package/dist/resources.js +0 -100
  146. /package/assets/packages/{sdk/src/files.ts → cli/src/internal/file-coordinator.ts} +0 -0
@@ -0,0 +1,213 @@
1
+ import * as nornModule from "@vimhead.dev/norn";
2
+ import { isWorkflowDeclaration, type NornAnyWorkflowDeclaration, type NornDispose, type NornWorkflowDiagnostic, type NornProjectInfo, type NornProjectInspection, type NornProjectConfigurationInfo, type NornWorkflowCatalogInfo, type NornWorkflowInspection, type NornWorkflowSource } from "@vimhead.dev/norn";
3
+ import { isNodeError } from "@vimhead.dev/norn-core/errors";
4
+ import * as nornSchemaModule from "@vimhead.dev/norn/schema";
5
+ import { inspectSchema, isPlainObject } from "@vimhead.dev/norn/schema";
6
+ import { createJiti, type ModuleCache } from "jiti/static";
7
+ import { access, readdir, readFile } from "node:fs/promises";
8
+ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
9
+ import { pathToFileURL } from "node:url";
10
+ import * as typeboxModule from "typebox";
11
+ import { Type, type StaticDecode } from "typebox";
12
+ import * as typeboxCompileModule from "typebox/compile";
13
+ import * as typeboxSchemaModule from "typebox/schema";
14
+ import * as typeboxValueModule from "typebox/value";
15
+ import { AssertError, Value } from "typebox/value";
16
+ import { errorMessage, NornProjectLoadError } from "./internal/errors.ts";
17
+ import { assertCompatibleConfigurationOwners, assertWorkflowDefinition, NornWorkflowRegistry, type NornRegisteredWorkflow } from "./internal/workflow-registry.ts";
18
+
19
+ export const NORN_PROJECT_FILE_NAME = "norn.project.json";
20
+ const nornConfigSchema = Type.Object({
21
+ workflows: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
22
+ includes: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
23
+ config: Type.Record(Type.String(), Type.Unknown(), { default: {} }),
24
+ });
25
+ const nornProjectConfigSchema = Type.Object({ ...nornConfigSchema.properties, version: Type.Literal(1, { default: 1 }) });
26
+ type NornConfig = StaticDecode<typeof nornConfigSchema>;
27
+ type NornProjectConfig = StaticDecode<typeof nornProjectConfigSchema>;
28
+ type NornConfigFile = { readonly path: string; readonly root: string; readonly config: NornConfig };
29
+ type NornProjectConfigFile = Omit<NornConfigFile, "config"> & { readonly config: NornProjectConfig };
30
+ export type NornProject = {
31
+ readonly cwd: string; readonly projectPath: string; readonly projectRoot: string;
32
+ readonly configPath: string; readonly configRoot: string; readonly config: NornProjectConfig;
33
+ readonly configFiles: readonly NornConfigFile[]; readonly projectConfig: Record<string, unknown>;
34
+ };
35
+ export type NornLoadedProject = NornProject & {
36
+ readonly definitions: readonly NornAnyWorkflowDeclaration[];
37
+ readonly modules: readonly NornWorkflowSource[];
38
+ readonly configurations: readonly NornProjectConfigurationInfo[];
39
+ readonly registry: NornWorkflowRegistry;
40
+ readonly workflows: readonly NornRegisteredWorkflow[];
41
+ };
42
+ type ImportedWorkflowModule = { readonly definitions: readonly NornAnyWorkflowDeclaration[]; readonly source: NornWorkflowSource };
43
+
44
+ export async function loadNornProject(cwd: string): Promise<NornLoadedProject> {
45
+ const { project, diagnostics } = await collectNornProject(cwd);
46
+ if (diagnostics.length) throw new NornProjectLoadError({ diagnostics });
47
+ return project;
48
+ }
49
+ export async function discoverNornProject(cwd: string): Promise<NornProjectInspection & NornWorkflowCatalogInfo> {
50
+ const { project, diagnostics } = await collectNornProject(cwd);
51
+ return { project: buildProjectInfo(project), workflows: project.registry.list(), isComplete: diagnostics.length === 0, diagnostics };
52
+ }
53
+ export async function inspectNornWorkflow(input: { readonly cwd: string; readonly workflowId: string }): Promise<NornWorkflowInspection> {
54
+ const { project, diagnostics } = await collectNornProject(input.cwd);
55
+ const summary = project.registry.list().find(workflow => workflow.id === input.workflowId);
56
+ if (!summary) {
57
+ if (!diagnostics.length) throw new Error(`Unknown workflow: ${input.workflowId}`);
58
+ return { workflow: null, isComplete: false, diagnostics };
59
+ }
60
+ try { return { workflow: project.registry.inspect(input.workflowId) ?? null, isComplete: diagnostics.length === 0, diagnostics }; }
61
+ catch (error) {
62
+ if (!summary.source) throw error;
63
+ return { workflow: null, isComplete: false, diagnostics: [...diagnostics, createDiagnostic({ source: summary.source, workflowId: summary.id, scopeId: summary.scope?.id ?? null, stage: "schema", error })] };
64
+ }
65
+ }
66
+ function buildProjectInfo(project: NornLoadedProject): NornProjectInfo {
67
+ return {
68
+ cwd: project.cwd, projectPath: project.projectPath, projectRoot: project.projectRoot,
69
+ configPath: project.configPath, configRoot: project.configRoot, configFiles: project.configFiles.map(file => file.path),
70
+ modules: project.modules, configurations: project.configurations,
71
+ };
72
+ }
73
+ export async function findNornProject(cwd: string): Promise<NornProject> {
74
+ const projectPath = await findNearestNornProject(cwd);
75
+ const projectFile = await readConfigFile({ path: projectPath, isProject: true });
76
+ const configFiles = await loadConfigTree(projectFile, new Set());
77
+ return {
78
+ cwd: resolve(cwd), projectPath, projectRoot: projectFile.root, configPath: projectPath, configRoot: projectFile.root,
79
+ config: projectFile.config, configFiles, projectConfig: mergeProjectConfig(configFiles),
80
+ };
81
+ }
82
+ async function findNearestNornProject(cwd: string): Promise<string> {
83
+ let current = resolve(cwd);
84
+ while (true) {
85
+ const path = join(current, NORN_PROJECT_FILE_NAME);
86
+ try { await access(path); return path; }
87
+ catch (error) { if (!isNodeError(error) || error.code !== "ENOENT") throw error; }
88
+ const parent = dirname(current);
89
+ if (parent === current) throw new Error(`Could not find ${NORN_PROJECT_FILE_NAME} from ${cwd}`);
90
+ current = parent;
91
+ }
92
+ }
93
+ async function loadConfigTree(file: NornConfigFile, visited: Set<string>): Promise<NornConfigFile[]> {
94
+ if (visited.has(file.path)) return [];
95
+ visited.add(file.path);
96
+ const files: NornConfigFile[] = [file];
97
+ for (const include of file.config.includes) {
98
+ for (const path of await expandIncludePath(file.root, include)) files.push(...await loadConfigTree(await readConfigFile({ path, isProject: false }), visited));
99
+ }
100
+ return files;
101
+ }
102
+ async function readConfigFile(input: { readonly path: string; readonly isProject: boolean }): Promise<NornProjectConfigFile> {
103
+ const raw: unknown = JSON.parse(await readFile(input.path, "utf8"));
104
+ if (isPlainObject(raw) && Object.hasOwn(raw, "plugins")) throw new Error(`Replace plugins with workflows in ${input.path}; registration modules must export workflow arrays`);
105
+ const schema = input.isProject ? nornProjectConfigSchema : nornConfigSchema;
106
+ const config = Value.Parse(schema, Value.Default(schema, raw));
107
+ return { path: input.path, root: dirname(input.path), config: { ...config, version: 1 } };
108
+ }
109
+ async function collectNornProject(cwd: string): Promise<{ readonly project: NornLoadedProject; readonly diagnostics: NornWorkflowDiagnostic[] }> {
110
+ const project = await findNornProject(cwd);
111
+ const imported = await importWorkflowModules(project);
112
+ const diagnostics = [...imported.diagnostics];
113
+ const registry = new NornWorkflowRegistry();
114
+ const loaded: ImportedWorkflowModule[] = [];
115
+ const conflicts = collectModuleConflicts(imported.modules);
116
+ for (const candidate of imported.modules) {
117
+ const conflict = conflicts.get(candidate);
118
+ if (conflict) { diagnostics.push(...conflict); continue; }
119
+ const unregister: NornDispose[] = [];
120
+ const errors: NornWorkflowDiagnostic[] = [];
121
+ for (const workflow of candidate.definitions) {
122
+ let stage: NornWorkflowDiagnostic["stage"] = "schema";
123
+ try {
124
+ if (workflow.config) inspectSchema(workflow.config);
125
+ if (workflow.scope?.config) inspectSchema(workflow.scope.config);
126
+ stage = "declaration";
127
+ assertWorkflowDefinition(workflow);
128
+ stage = "config";
129
+ unregister.push(registry.register({ workflow, config: project.projectConfig, source: candidate.source }));
130
+ } catch (error) { errors.push(createDiagnostic({ source: candidate.source, workflowId: workflow.id, scopeId: workflow.scope?.id ?? null, stage, error })); }
131
+ }
132
+ if (errors.length) { for (const dispose of unregister) dispose(); diagnostics.push(...errors); }
133
+ else loaded.push(candidate);
134
+ }
135
+ const definitions = loaded.flatMap(module => module.definitions);
136
+ return { project: { ...project, definitions, modules: loaded.map(module => module.source), configurations: registry.configurationInfos(), registry, workflows: registry.launchableEntries() }, diagnostics };
137
+ }
138
+ function collectModuleConflicts(modules: readonly ImportedWorkflowModule[]): Map<ImportedWorkflowModule, NornWorkflowDiagnostic[]> {
139
+ const conflicts = new Map<ImportedWorkflowModule, NornWorkflowDiagnostic[]>();
140
+ const byId = new Map<string, ImportedWorkflowModule[]>();
141
+ for (const module of modules) for (const workflow of module.definitions) byId.set(workflow.id, [...byId.get(workflow.id) ?? [], module]);
142
+ for (const [id, owners] of byId) {
143
+ if (owners.length < 2) continue;
144
+ for (const module of new Set(owners)) conflicts.set(module, [...conflicts.get(module) ?? [], createDiagnostic({ source: module.source, workflowId: id, scopeId: null, stage: "duplicate", error: new Error(`Workflow already registered: ${id}`) })]);
145
+ }
146
+ for (let index = 0; index < modules.length; index++) {
147
+ for (let other = index; other < modules.length; other++) {
148
+ const candidates = [...new Set([modules[index], modules[other]])];
149
+ try { assertCompatibleConfigurationOwners(candidates.flatMap(module => module.definitions)); }
150
+ catch (error) { for (const module of candidates) conflicts.set(module, [...conflicts.get(module) ?? [], createDiagnostic({ source: module.source, workflowId: null, scopeId: null, stage: "config", error })]); }
151
+ }
152
+ }
153
+ return conflicts;
154
+ }
155
+ async function importWorkflowModules(project: NornProject): Promise<{ readonly modules: ImportedWorkflowModule[]; readonly diagnostics: NornWorkflowDiagnostic[] }> {
156
+ const modules: ImportedWorkflowModule[] = [];
157
+ const diagnostics: NornWorkflowDiagnostic[] = [];
158
+ const jiti = createJiti(pathToFileURL(project.configPath).href, { moduleCache: false, tryNative: false, virtualModules: nornWorkflowVirtualModules() });
159
+ const cache: ModuleCache = Object.create(null);
160
+ for (const file of project.configFiles) for (const path of file.config.workflows) {
161
+ const source = { configPath: file.path, path: resolveConfigPath(file.root, path) };
162
+ let stage: NornWorkflowDiagnostic["stage"] = "import";
163
+ try {
164
+ const cached = cache[source.path];
165
+ const module = (cached?.loaded ? cached.exports : await jiti.evalModule(await readFile(source.path, "utf8"), { filename: source.path, cache, async: true })) as { default?: unknown };
166
+ stage = "declaration";
167
+ if (!Array.isArray(module?.default) || !Array.from(module.default).every(isWorkflowDeclaration)) throw new Error(`Workflow module must default-export an array of complete workflow definitions: ${source.path}`);
168
+ modules.push({ definitions: module.default, source });
169
+ } catch (error) {
170
+ for (const [path, module] of Object.entries(cache)) if (!module.loaded) delete cache[path];
171
+ diagnostics.push(createDiagnostic({ source, workflowId: null, scopeId: null, stage, error }));
172
+ }
173
+ }
174
+ return { modules, diagnostics };
175
+ }
176
+ function createDiagnostic(input: { readonly source: NornWorkflowSource; readonly workflowId: string | null; readonly scopeId: string | null; readonly stage: NornWorkflowDiagnostic["stage"]; readonly error: unknown }): NornWorkflowDiagnostic {
177
+ return { configPath: input.source.configPath, modulePath: input.source.path, scopeId: input.scopeId, workflowId: input.workflowId, stage: input.stage, message: errorMessage(input.error), issues: input.error instanceof AssertError ? input.error.cause.errors : [] };
178
+ }
179
+ function nornWorkflowVirtualModules(): Record<string, unknown> {
180
+ return { "@vimhead.dev/norn": nornModule, "@vimhead.dev/norn/schema": nornSchemaModule, typebox: typeboxModule, "typebox/value": typeboxValueModule, "typebox/compile": typeboxCompileModule, "typebox/schema": typeboxSchemaModule };
181
+ }
182
+ function mergeProjectConfig(files: readonly NornConfigFile[]): Record<string, unknown> {
183
+ const [project, ...reusable] = files;
184
+ let config: Record<string, unknown> = {};
185
+ for (const file of reusable) config = mergeConfig({ base: config, override: file.config.config, path: ["config"], rejectConflicts: true });
186
+ return mergeConfig({ base: config, override: project?.config.config ?? {}, path: ["config"], rejectConflicts: false });
187
+ }
188
+ function mergeConfig(input: { readonly base: Record<string, unknown>; readonly override: Record<string, unknown>; readonly path: readonly string[]; readonly rejectConflicts: boolean }): Record<string, unknown> {
189
+ const result = { ...input.base };
190
+ for (const [key, value] of Object.entries(input.override)) {
191
+ const previous = result[key];
192
+ const path = [...input.path, key];
193
+ if (isPlainObject(previous) && isPlainObject(value)) result[key] = mergeConfig({ base: previous, override: value, path, rejectConflicts: input.rejectConflicts });
194
+ else if (input.rejectConflicts && Object.hasOwn(result, key) && JSON.stringify(previous) !== JSON.stringify(value)) throw new Error(`Conflicting Norn reusable config at ${path.join(".")}`);
195
+ else result[key] = value;
196
+ }
197
+ return result;
198
+ }
199
+ async function expandIncludePath(root: string, path: string): Promise<string[]> {
200
+ const segments = path.split(/[\\/]+/).filter(segment => segment.length && segment !== ".");
201
+ return segments.includes("*") ? expandGlobSegments(isAbsolute(path) ? sep : root, segments) : [resolveConfigPath(root, path)];
202
+ }
203
+ async function expandGlobSegments(root: string, segments: readonly string[]): Promise<string[]> {
204
+ if (!segments.length) return [root];
205
+ const [segment, ...remaining] = segments;
206
+ if (segment !== "*") return expandGlobSegments(join(root, segment), remaining);
207
+ const entries = await readdir(root, { withFileTypes: true });
208
+ return (await Promise.all(entries.filter(entry => entry.isDirectory()).sort((left, right) => left.name.localeCompare(right.name)).map(entry => expandGlobSegments(join(root, entry.name), remaining)))).flat();
209
+ }
210
+ function resolveConfigPath(root: string, path: string): string {
211
+ if (!path.length) throw new Error("Norn path must not be empty");
212
+ return isAbsolute(path) ? path : resolve(root, path);
213
+ }
@@ -1,4 +1,4 @@
1
- export function createWorkflowTransition(input: { readonly workflowId: string; readonly params: unknown }): { readonly type: "next"; readonly workflowId: string; readonly params: unknown } {
1
+ export function createWorkflowTransition(input: { readonly workflowId: string; readonly args: unknown }): { readonly type: "next"; readonly workflowId: string; readonly args: unknown } {
2
2
  if (typeof input.workflowId !== "string" || input.workflowId.trim().length === 0) throw new Error("Workflow transition requires a nonempty workflow ID");
3
- return { type: "next", workflowId: input.workflowId, params: input.params };
3
+ return { type: "next", workflowId: input.workflowId, args: input.args };
4
4
  }