@vimhead.dev/norn-cli 0.1.0-tip.35614826665.1 → 0.1.0-tip.35724621678.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 (119) hide show
  1. package/assets/README.md +41 -13
  2. package/assets/docs/README.md +16 -16
  3. package/assets/docs/agents.md +23 -15
  4. package/assets/docs/cli.md +10 -8
  5. package/assets/docs/composition.md +7 -7
  6. package/assets/docs/persistence.md +22 -22
  7. package/assets/docs/projects.md +6 -6
  8. package/assets/docs/recovery.md +10 -10
  9. package/assets/docs/schemas.md +42 -36
  10. package/assets/docs/workflows.md +39 -32
  11. package/assets/examples/agent-then-analysis/input.json +3 -3
  12. package/assets/examples/agent-then-analysis/norn.project.json +2 -2
  13. package/assets/examples/agent-then-analysis/plugin.ts +37 -9
  14. package/assets/examples/caller-owned-routing/README.md +6 -6
  15. package/assets/examples/caller-owned-routing/assessment.ts +18 -4
  16. package/assets/examples/caller-owned-routing/contracts.ts +7 -2
  17. package/assets/examples/caller-owned-routing/input.json +12 -12
  18. package/assets/examples/caller-owned-routing/norn.project.json +2 -2
  19. package/assets/examples/caller-owned-routing/revision.ts +7 -2
  20. package/assets/examples/caller-owned-routing/router.ts +14 -3
  21. package/assets/examples/caller-selected-continuation/caller.ts +38 -12
  22. package/assets/examples/caller-selected-continuation/input.json +7 -7
  23. package/assets/examples/caller-selected-continuation/norn.project.json +2 -2
  24. package/assets/examples/caller-selected-continuation/producer.ts +10 -3
  25. package/assets/examples/coordinating-multiple-agents/README.md +6 -6
  26. package/assets/examples/coordinating-multiple-agents/input.json +20 -8
  27. package/assets/examples/coordinating-multiple-agents/norn.project.json +2 -2
  28. package/assets/examples/coordinating-multiple-agents/plugin.ts +165 -46
  29. package/assets/examples/coordinating-multiple-agents/queue-tools.ts +30 -7
  30. package/assets/examples/coordinating-multiple-agents/work-queue.ts +211 -63
  31. package/assets/examples/getting-started/norn.project.json +2 -2
  32. package/assets/examples/getting-started/plugin.ts +33 -13
  33. package/assets/examples/minimal-workflow/norn.project.json +2 -2
  34. package/assets/examples/minimal-workflow/plugin.ts +12 -3
  35. package/assets/examples/shared-state/README.md +6 -6
  36. package/assets/examples/shared-state/input.json +1 -1
  37. package/assets/examples/shared-state/norn.project.json +2 -2
  38. package/assets/examples/shared-state/plugin.ts +44 -15
  39. package/assets/examples/shared-state/shared-state.ts +46 -13
  40. package/assets/examples/shared-state/state-tools.ts +68 -16
  41. package/assets/examples/worktree-development-loop/README.md +12 -12
  42. package/assets/examples/worktree-development-loop/norn.project.json +7 -7
  43. package/assets/examples/worktree-development-loop/plugin.ts +7 -1
  44. package/assets/examples/worktree-development-loop/scope.ts +4 -1
  45. package/assets/examples/worktree-development-loop/shared/commands.ts +6 -2
  46. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +18 -4
  47. package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +4 -1
  48. package/assets/examples/worktree-development-loop/workflows/development-loop/repository.ts +11 -1
  49. package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +6 -2
  50. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +40 -10
  51. package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +5 -1
  52. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +1 -1
  53. package/assets/examples/worktree-development-loop/workflows/planning/index.ts +5 -1
  54. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +33 -9
  55. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +51 -8
  56. package/assets/package.json +1 -1
  57. package/assets/packages/cli/src/build-info.ts +4 -1
  58. package/assets/packages/cli/src/bun/cli.ts +21 -5
  59. package/assets/packages/cli/src/cli.ts +585 -170
  60. package/assets/packages/cli/src/client.ts +194 -36
  61. package/assets/packages/cli/src/documentation.ts +135 -30
  62. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  63. package/assets/packages/cli/src/internal/agent-directory.ts +7 -2
  64. package/assets/packages/cli/src/internal/agent-response-tool.ts +57 -15
  65. package/assets/packages/cli/src/internal/agents.ts +248 -60
  66. package/assets/packages/cli/src/internal/commands.ts +82 -20
  67. package/assets/packages/cli/src/internal/documentation-bundle.ts +44 -13
  68. package/assets/packages/cli/src/internal/engine.ts +396 -87
  69. package/assets/packages/cli/src/internal/errors.ts +13 -5
  70. package/assets/packages/cli/src/internal/execution-context.ts +20 -12
  71. package/assets/packages/cli/src/internal/file-coordinator.ts +95 -26
  72. package/assets/packages/cli/src/internal/file-names.ts +4 -1
  73. package/assets/packages/cli/src/internal/launch-request.ts +74 -22
  74. package/assets/packages/cli/src/internal/logs.ts +10 -2
  75. package/assets/packages/cli/src/internal/metrics.ts +247 -61
  76. package/assets/packages/cli/src/internal/pi-assets.ts +105 -23
  77. package/assets/packages/cli/src/internal/run-lease.ts +72 -22
  78. package/assets/packages/cli/src/internal/run-log.ts +49 -20
  79. package/assets/packages/cli/src/internal/run-names.ts +120 -12
  80. package/assets/packages/cli/src/internal/run-state.ts +306 -71
  81. package/assets/packages/cli/src/internal/run-store.ts +216 -53
  82. package/assets/packages/cli/src/internal/usage.ts +23 -7
  83. package/assets/packages/cli/src/internal/worker-directory.ts +9 -3
  84. package/assets/packages/cli/src/internal/workflow-registry.ts +281 -70
  85. package/assets/packages/cli/src/internal/working-directory.ts +2 -1
  86. package/assets/packages/cli/src/workflow-loader.ts +341 -74
  87. package/assets/packages/core/src/atomic-files.ts +13 -4
  88. package/assets/packages/core/src/workflow-transition.ts +13 -2
  89. package/assets/packages/sdk/src/api.ts +248 -80
  90. package/assets/packages/sdk/src/index.ts +6 -1
  91. package/assets/packages/sdk/src/schema.ts +75 -21
  92. package/assets/tests/workflow-ref.test.ts +242 -57
  93. package/bin/norn.mjs +13 -4
  94. package/dist/cli.js +344 -90
  95. package/dist/client.js +100 -22
  96. package/dist/documentation.js +102 -25
  97. package/dist/generated-build-info.d.ts +2 -2
  98. package/dist/generated-build-info.js +2 -2
  99. package/dist/internal/agent-directory.js +3 -1
  100. package/dist/internal/agent-response-tool.js +42 -11
  101. package/dist/internal/agents.js +149 -37
  102. package/dist/internal/commands.js +65 -16
  103. package/dist/internal/documentation-bundle.js +23 -11
  104. package/dist/internal/engine.js +304 -66
  105. package/dist/internal/errors.js +4 -2
  106. package/dist/internal/execution-context.js +2 -1
  107. package/dist/internal/file-coordinator.js +59 -19
  108. package/dist/internal/launch-request.js +32 -14
  109. package/dist/internal/metrics.js +138 -38
  110. package/dist/internal/pi-assets.js +70 -19
  111. package/dist/internal/run-lease.js +29 -9
  112. package/dist/internal/run-log.js +24 -12
  113. package/dist/internal/run-state.js +172 -48
  114. package/dist/internal/run-store.js +157 -45
  115. package/dist/internal/worker-directory.js +6 -2
  116. package/dist/internal/workflow-registry.js +123 -32
  117. package/dist/internal/working-directory.js +2 -1
  118. package/dist/workflow-loader.js +198 -41
  119. package/package.json +2 -2
@@ -1,9 +1,39 @@
1
- import { isWorkflowComplete, isWorkflowDeclaration, isWorkflowFail, isWorkflowNext, type NornAnyWorkflowDeclaration, type NornDispose, type NornInspectedWorkflowInfo, type NornRegisteredWorkflowInfo, type NornRunComplete, type NornRunFail, type NornRunNext, type NornWorkflowPaths, type NornWorkflowContext, type NornWorkflowGateInfo, type NornWorkflowSource, type NornWorkflowScopeInfo, type NornProjectConfigurationInfo } from "@vimhead.dev/norn";
2
- import { assertWorkflowMetadata, inspectSchema, isPlainObject, schemaShape, schemaType, unwrapSchema } from "@vimhead.dev/norn/schema";
1
+ import {
2
+ isWorkflowComplete,
3
+ isWorkflowDeclaration,
4
+ isWorkflowFail,
5
+ isWorkflowNext,
6
+ type NornAnyWorkflowDeclaration,
7
+ type NornDispose,
8
+ type NornInspectedWorkflowInfo,
9
+ type NornRegisteredWorkflowInfo,
10
+ type NornRunComplete,
11
+ type NornRunFail,
12
+ type NornRunNext,
13
+ type NornWorkflowPaths,
14
+ type NornWorkflowContext,
15
+ type NornWorkflowGateInfo,
16
+ type NornWorkflowSource,
17
+ type NornWorkflowScopeInfo,
18
+ type NornProjectConfigurationInfo,
19
+ } from "@vimhead.dev/norn";
20
+ import {
21
+ assertWorkflowMetadata,
22
+ inspectSchema,
23
+ isPlainObject,
24
+ schemaShape,
25
+ schemaType,
26
+ unwrapSchema,
27
+ } from "@vimhead.dev/norn/schema";
3
28
  import type { TSchema } from "typebox";
4
29
  import { Value } from "typebox/value";
5
30
 
6
- export type NornRegisteredConfiguration = { readonly key: string; readonly schema: TSchema | undefined; readonly input: unknown; readonly value: unknown };
31
+ export type NornRegisteredConfiguration = {
32
+ readonly key: string;
33
+ readonly schema: TSchema | undefined;
34
+ readonly input: unknown;
35
+ readonly value: unknown;
36
+ };
7
37
  export type NornRegisteredWorkflow = {
8
38
  readonly workflow: NornAnyWorkflowDeclaration;
9
39
  readonly configuration: NornRegisteredConfiguration;
@@ -11,27 +41,72 @@ export type NornRegisteredWorkflow = {
11
41
  readonly source: NornWorkflowSource | undefined;
12
42
  };
13
43
 
14
- type NornRegisteredScope = { readonly definition: NornWorkflowScopeInfo; readonly configuration: NornRegisteredConfiguration };
44
+ type NornRegisteredScope = {
45
+ readonly definition: NornWorkflowScopeInfo;
46
+ readonly configuration: NornRegisteredConfiguration;
47
+ };
15
48
 
16
- export function decodeConfiguration(input: { readonly key: string; readonly schema: TSchema | undefined; readonly value: unknown }): NornRegisteredConfiguration {
49
+ export function decodeConfiguration(input: {
50
+ readonly key: string;
51
+ readonly schema: TSchema | undefined;
52
+ readonly value: unknown;
53
+ }): NornRegisteredConfiguration {
17
54
  if (!input.schema) {
18
- if (input.value !== undefined) throw new Error(`Norn config provided without config schema: ${input.key}`);
19
- return { key: input.key, schema: undefined, input: undefined, value: undefined };
55
+ if (input.value !== undefined)
56
+ throw new Error(
57
+ `Norn config provided without config schema: ${input.key}`,
58
+ );
59
+ return {
60
+ key: input.key,
61
+ schema: undefined,
62
+ input: undefined,
63
+ value: undefined,
64
+ };
20
65
  }
21
- const value = input.value === undefined && schemaType(unwrapSchema(input.schema)) === "object" ? {} : input.value;
22
- return { key: input.key, schema: input.schema, input: value, value: Value.Decode(input.schema, value) };
66
+ const value =
67
+ input.value === undefined &&
68
+ schemaType(unwrapSchema(input.schema)) === "object"
69
+ ? {}
70
+ : input.value;
71
+ return {
72
+ key: input.key,
73
+ schema: input.schema,
74
+ input: value,
75
+ value: Value.Decode(input.schema, value),
76
+ };
23
77
  }
24
78
 
25
- export function assertCompatibleConfigurationOwners(workflows: readonly NornAnyWorkflowDeclaration[]): void {
26
- const owners = new Map<string, { readonly kind: "workflow" | "scope"; readonly definition: object }>();
79
+ export function assertCompatibleConfigurationOwners(
80
+ workflows: readonly NornAnyWorkflowDeclaration[],
81
+ ): void {
82
+ const owners = new Map<
83
+ string,
84
+ { readonly kind: "workflow" | "scope"; readonly definition: object }
85
+ >();
27
86
  for (const workflow of workflows) {
28
87
  for (const owner of [
29
88
  { key: workflow.id, kind: "workflow" as const, definition: workflow },
30
- ...(workflow.scope ? [{ key: workflow.scope.id, kind: "scope" as const, definition: workflow.scope }] : []),
89
+ ...(workflow.scope
90
+ ? [
91
+ {
92
+ key: workflow.scope.id,
93
+ kind: "scope" as const,
94
+ definition: workflow.scope,
95
+ },
96
+ ]
97
+ : []),
31
98
  ]) {
32
99
  const previous = owners.get(owner.key);
33
- if (previous && previous.kind !== owner.kind) throw new Error(`Ambiguous configuration key: ${owner.key}`);
34
- if (previous && owner.kind === "scope" && previous.definition !== owner.definition) throw new Error(`Duplicate workflow scope id: ${owner.key}; import one shared scope definition`);
100
+ if (previous && previous.kind !== owner.kind)
101
+ throw new Error(`Ambiguous configuration key: ${owner.key}`);
102
+ if (
103
+ previous &&
104
+ owner.kind === "scope" &&
105
+ previous.definition !== owner.definition
106
+ )
107
+ throw new Error(
108
+ `Duplicate workflow scope id: ${owner.key}; import one shared scope definition`,
109
+ );
35
110
  owners.set(owner.key, owner);
36
111
  }
37
112
  }
@@ -39,102 +114,192 @@ export function assertCompatibleConfigurationOwners(workflows: readonly NornAnyW
39
114
 
40
115
  export type NornWorkflowStepResult =
41
116
  | NornRunNext
42
- | { readonly type: "complete"; readonly workflow: NornAnyWorkflowDeclaration; readonly metadata?: NornRunComplete["metadata"] }
43
- | { readonly type: "fail"; readonly workflow: NornAnyWorkflowDeclaration; readonly metadata: NornRunFail["metadata"] };
117
+ | {
118
+ readonly type: "complete";
119
+ readonly workflow: NornAnyWorkflowDeclaration;
120
+ readonly metadata?: NornRunComplete["metadata"];
121
+ }
122
+ | {
123
+ readonly type: "fail";
124
+ readonly workflow: NornAnyWorkflowDeclaration;
125
+ readonly metadata: NornRunFail["metadata"];
126
+ };
44
127
 
45
128
  type NornExecutionContextInput = {
46
- readonly execution: Pick<NornWorkflowContext, "run" | "agents" | "commands" | "logs">;
129
+ readonly execution: Pick<
130
+ NornWorkflowContext,
131
+ "run" | "agents" | "commands" | "logs"
132
+ >;
47
133
  readonly paths: NornWorkflowPaths;
48
134
  readonly args: unknown;
49
135
  readonly configOverride: unknown;
50
136
  };
51
137
 
52
- type NornWorkflowExecutionInput = NornExecutionContextInput & { readonly workflow: NornAnyWorkflowDeclaration };
138
+ type NornWorkflowExecutionInput = NornExecutionContextInput & {
139
+ readonly workflow: NornAnyWorkflowDeclaration;
140
+ };
53
141
 
54
142
  export class NornWorkflowRegistry {
55
143
  private readonly entries = new Map<string, NornRegisteredWorkflow>();
56
144
  private readonly scopes = new Map<string, NornRegisteredScope>();
57
145
 
58
- register(input: { readonly workflow: NornAnyWorkflowDeclaration; readonly config: Readonly<Record<string, unknown>>; readonly source: NornWorkflowSource | undefined }): NornDispose {
146
+ register(input: {
147
+ readonly workflow: NornAnyWorkflowDeclaration;
148
+ readonly config: Readonly<Record<string, unknown>>;
149
+ readonly source: NornWorkflowSource | undefined;
150
+ }): NornDispose {
59
151
  const { workflow, config, source } = input;
60
152
  assertWorkflowDefinition(workflow);
61
- if (this.entries.has(workflow.id)) throw new Error(`Workflow already registered: ${workflow.id}`);
62
- assertCompatibleConfigurationOwners([...this.sortedEntries().map(entry => entry.workflow), workflow]);
153
+ if (this.entries.has(workflow.id))
154
+ throw new Error(`Workflow already registered: ${workflow.id}`);
155
+ assertCompatibleConfigurationOwners([
156
+ ...this.sortedEntries().map((entry) => entry.workflow),
157
+ workflow,
158
+ ]);
63
159
  const entry: NornRegisteredWorkflow = {
64
- workflow, source, scopeId: workflow.scope?.id,
65
- configuration: decodeConfiguration({ key: workflow.id, schema: workflow.config, value: configurationInput(config, workflow.id) }),
160
+ workflow,
161
+ source,
162
+ scopeId: workflow.scope?.id,
163
+ configuration: decodeConfiguration({
164
+ key: workflow.id,
165
+ schema: workflow.config,
166
+ value: configurationInput(config, workflow.id),
167
+ }),
66
168
  };
67
169
  if (workflow.scope && !this.scopes.has(workflow.scope.id)) {
68
170
  this.scopes.set(workflow.scope.id, {
69
171
  definition: workflow.scope,
70
- configuration: decodeConfiguration({ key: workflow.scope.id, schema: workflow.scope.config, value: configurationInput(config, workflow.scope.id) }),
172
+ configuration: decodeConfiguration({
173
+ key: workflow.scope.id,
174
+ schema: workflow.scope.config,
175
+ value: configurationInput(config, workflow.scope.id),
176
+ }),
71
177
  });
72
178
  }
73
179
  this.entries.set(workflow.id, entry);
74
180
  return () => {
75
181
  if (this.entries.get(workflow.id) !== entry) return;
76
182
  this.entries.delete(workflow.id);
77
- if (entry.scopeId && ![...this.entries.values()].some(other => other.scopeId === entry.scopeId)) this.scopes.delete(entry.scopeId);
183
+ if (
184
+ entry.scopeId &&
185
+ ![...this.entries.values()].some(
186
+ (other) => other.scopeId === entry.scopeId,
187
+ )
188
+ )
189
+ this.scopes.delete(entry.scopeId);
78
190
  };
79
191
  }
80
192
 
81
- list(options: { readonly entrypointsOnly?: boolean } = {}): NornRegisteredWorkflowInfo[] {
82
- return (options.entrypointsOnly ? this.launchableEntries() : this.sortedEntries()).map(workflowInfo);
193
+ list(
194
+ options: { readonly entrypointsOnly?: boolean } = {},
195
+ ): NornRegisteredWorkflowInfo[] {
196
+ return (
197
+ options.entrypointsOnly ? this.launchableEntries() : this.sortedEntries()
198
+ ).map(workflowInfo);
83
199
  }
84
200
 
85
201
  inspect(workflowId: string): NornInspectedWorkflowInfo | undefined {
86
202
  const entry = this.entries.get(workflowId);
87
203
  const scope = entry?.scopeId ? this.scopes.get(entry.scopeId) : undefined;
88
- return entry ? {
89
- ...workflowInfo(entry),
90
- argsSchema: inspectSchema(entry.workflow.args),
91
- configSchema: entry.configuration.schema ? inspectSchema(entry.configuration.schema) : null,
92
- scope: scope ? {
93
- id: scope.definition.id,
94
- configKey: scope.configuration.key,
95
- configSchema: scope.configuration.schema ? inspectSchema(scope.configuration.schema) : null,
96
- } : undefined,
97
- gate: gateInfo(entry.workflow),
98
- } : undefined;
204
+ return entry
205
+ ? {
206
+ ...workflowInfo(entry),
207
+ argsSchema: inspectSchema(entry.workflow.args),
208
+ configSchema: entry.configuration.schema
209
+ ? inspectSchema(entry.configuration.schema)
210
+ : null,
211
+ scope: scope
212
+ ? {
213
+ id: scope.definition.id,
214
+ configKey: scope.configuration.key,
215
+ configSchema: scope.configuration.schema
216
+ ? inspectSchema(scope.configuration.schema)
217
+ : null,
218
+ }
219
+ : undefined,
220
+ gate: gateInfo(entry.workflow),
221
+ }
222
+ : undefined;
99
223
  }
100
224
 
101
225
  configurationInfos(): NornProjectConfigurationInfo[] {
102
226
  return [
103
- ...this.sortedEntries().map(entry => ({ configuration: entry.configuration, scopeId: null })),
104
- ...[...this.scopes.values()].map(scope => ({ configuration: scope.configuration, scopeId: scope.definition.id })),
105
- ].map(({ configuration, scopeId }) => ({ key: configuration.key, scopeId, configSchema: configuration.schema ? inspectSchema(configuration.schema) : null, config: configuration.value }));
227
+ ...this.sortedEntries().map((entry) => ({
228
+ configuration: entry.configuration,
229
+ scopeId: null,
230
+ })),
231
+ ...[...this.scopes.values()].map((scope) => ({
232
+ configuration: scope.configuration,
233
+ scopeId: scope.definition.id,
234
+ })),
235
+ ].map(({ configuration, scopeId }) => ({
236
+ key: configuration.key,
237
+ scopeId,
238
+ configSchema: configuration.schema
239
+ ? inspectSchema(configuration.schema)
240
+ : null,
241
+ config: configuration.value,
242
+ }));
106
243
  }
107
244
 
108
- launchableEntries(): NornRegisteredWorkflow[] { return this.sortedEntries().filter(({ workflow }) => workflow.entrypoint !== false); }
109
- workflowById(workflowId: string): NornAnyWorkflowDeclaration | undefined { return this.entries.get(workflowId)?.workflow; }
245
+ launchableEntries(): NornRegisteredWorkflow[] {
246
+ return this.sortedEntries().filter(
247
+ ({ workflow }) => workflow.entrypoint !== false,
248
+ );
249
+ }
250
+ workflowById(workflowId: string): NornAnyWorkflowDeclaration | undefined {
251
+ return this.entries.get(workflowId)?.workflow;
252
+ }
110
253
 
111
254
  async describeGate(input: NornWorkflowExecutionInput): Promise<string> {
112
255
  const { workflow } = input;
113
256
  const entry = this.requireEntry(workflow.id);
114
- if (!workflow.gate) throw new Error(`Workflow is not gated: ${workflow.id}`);
257
+ if (!workflow.gate)
258
+ throw new Error(`Workflow is not gated: ${workflow.id}`);
115
259
  const context = this.createExecutionContext({ ...input, entry });
116
- const description = await entry.workflow.gate?.describe?.(context) ?? workflow.id;
117
- if (typeof description !== "string" || description.trim().length === 0) throw new Error(`Workflow gate description must not be empty: ${workflow.id}`);
260
+ const description =
261
+ (await entry.workflow.gate?.describe?.(context)) ?? workflow.id;
262
+ if (typeof description !== "string" || description.trim().length === 0)
263
+ throw new Error(
264
+ `Workflow gate description must not be empty: ${workflow.id}`,
265
+ );
118
266
  return description.trim();
119
267
  }
120
268
 
121
- async execute(input: NornWorkflowExecutionInput): Promise<NornWorkflowStepResult> {
269
+ async execute(
270
+ input: NornWorkflowExecutionInput,
271
+ ): Promise<NornWorkflowStepResult> {
122
272
  const { workflow } = input;
123
273
  const entry = this.requireEntry(workflow.id);
124
- const result = await entry.workflow.execute(this.createExecutionContext({ ...input, entry }));
274
+ const result = await entry.workflow.execute(
275
+ this.createExecutionContext({ ...input, entry }),
276
+ );
125
277
  if (isWorkflowNext(result)) return result;
126
- if (isWorkflowComplete(result)) return { type: "complete", workflow, metadata: result.metadata };
127
- if (isWorkflowFail(result)) return { type: "fail", workflow, metadata: result.metadata };
278
+ if (isWorkflowComplete(result))
279
+ return { type: "complete", workflow, metadata: result.metadata };
280
+ if (isWorkflowFail(result))
281
+ return { type: "fail", workflow, metadata: result.metadata };
128
282
  throw new Error(`Workflow returned invalid control result: ${workflow.id}`);
129
283
  }
130
284
 
131
- private createExecutionContext(input: NornExecutionContextInput & { readonly entry: NornRegisteredWorkflow }): NornWorkflowContext {
285
+ private createExecutionContext(
286
+ input: NornExecutionContextInput & {
287
+ readonly entry: NornRegisteredWorkflow;
288
+ },
289
+ ): NornWorkflowContext {
132
290
  const { entry, execution, paths, configOverride } = input;
133
291
  const scope = entry.scopeId ? this.scopes.get(entry.scopeId) : undefined;
134
292
  return {
135
293
  args: Value.Decode(entry.workflow.args, input.args),
136
294
  config: parseExecutionConfig(entry.configuration, configOverride),
137
- ...(scope ? { scope: { id: scope.definition.id, config: parseExecutionConfig(scope.configuration, configOverride) } } : {}),
295
+ ...(scope
296
+ ? {
297
+ scope: {
298
+ id: scope.definition.id,
299
+ config: parseExecutionConfig(scope.configuration, configOverride),
300
+ },
301
+ }
302
+ : {}),
138
303
  paths,
139
304
  run: execution.run,
140
305
  agents: execution.agents,
@@ -148,55 +313,101 @@ export class NornWorkflowRegistry {
148
313
  if (!entry) throw new Error(`Unknown workflow: ${id}`);
149
314
  return entry;
150
315
  }
151
- private sortedEntries(): NornRegisteredWorkflow[] { return [...this.entries.values()].sort((left, right) => left.workflow.id.localeCompare(right.workflow.id)); }
316
+ private sortedEntries(): NornRegisteredWorkflow[] {
317
+ return [...this.entries.values()].sort((left, right) =>
318
+ left.workflow.id.localeCompare(right.workflow.id),
319
+ );
320
+ }
152
321
  }
153
322
 
154
- function configurationInput(config: Readonly<Record<string, unknown>>, key: string): unknown {
323
+ function configurationInput(
324
+ config: Readonly<Record<string, unknown>>,
325
+ key: string,
326
+ ): unknown {
155
327
  return Object.hasOwn(config, key) ? config[key] : undefined;
156
328
  }
157
329
 
158
- function parseExecutionConfig(configuration: NornRegisteredConfiguration, overrides: unknown): unknown {
159
- if (overrides !== undefined && !isPlainObject(overrides)) throw new Error("Run config override must be an object keyed by workflow or scope id");
160
- const override = overrides ? configurationInput(overrides, configuration.key) : undefined;
330
+ function parseExecutionConfig(
331
+ configuration: NornRegisteredConfiguration,
332
+ overrides: unknown,
333
+ ): unknown {
334
+ if (overrides !== undefined && !isPlainObject(overrides))
335
+ throw new Error(
336
+ "Run config override must be an object keyed by workflow or scope id",
337
+ );
338
+ const override = overrides
339
+ ? configurationInput(overrides, configuration.key)
340
+ : undefined;
161
341
  if (override === undefined) return configuration.value;
162
- if (!configuration.schema) throw new Error(`Run config override provided without config schema: ${configuration.key}`);
163
- const input = isPlainObject(configuration.input) && isPlainObject(override) ? deepMerge(configuration.input, override) : override;
342
+ if (!configuration.schema)
343
+ throw new Error(
344
+ `Run config override provided without config schema: ${configuration.key}`,
345
+ );
346
+ const input =
347
+ isPlainObject(configuration.input) && isPlainObject(override)
348
+ ? deepMerge(configuration.input, override)
349
+ : override;
164
350
  return Value.Decode(configuration.schema, input);
165
351
  }
166
352
 
167
- function deepMerge(base: Record<string, unknown>, override: Record<string, unknown>): Record<string, unknown> {
353
+ function deepMerge(
354
+ base: Record<string, unknown>,
355
+ override: Record<string, unknown>,
356
+ ): Record<string, unknown> {
168
357
  const result = { ...base };
169
- for (const [key, value] of Object.entries(override)) result[key] = isPlainObject(result[key]) && isPlainObject(value) ? deepMerge(result[key], value) : value;
358
+ for (const [key, value] of Object.entries(override))
359
+ result[key] =
360
+ isPlainObject(result[key]) && isPlainObject(value)
361
+ ? deepMerge(result[key], value)
362
+ : value;
170
363
  return result;
171
364
  }
172
365
 
173
- export function assertWorkflowDefinition(workflow: NornAnyWorkflowDeclaration): void {
366
+ export function assertWorkflowDefinition(
367
+ workflow: NornAnyWorkflowDeclaration,
368
+ ): void {
174
369
  assertWorkflowMetadata(workflow);
175
- if (!isWorkflowDeclaration(workflow)) throw new Error("Invalid workflow definition");
370
+ if (!isWorkflowDeclaration(workflow))
371
+ throw new Error("Invalid workflow definition");
176
372
  assertGateWorkflow(workflow);
177
373
  }
178
374
 
179
375
  function assertGateWorkflow(workflow: NornAnyWorkflowDeclaration): void {
180
376
  if (!workflow.gate) return;
181
- if (workflow.gate.enabled !== true) throw new Error(`Workflow gate must be enabled with true: ${workflow.id}`);
182
- if (workflow.gate.describe !== undefined && typeof workflow.gate.describe !== "function") throw new Error(`Invalid gate description callback: ${workflow.id}`);
377
+ if (workflow.gate.enabled !== true)
378
+ throw new Error(`Workflow gate must be enabled with true: ${workflow.id}`);
379
+ if (
380
+ workflow.gate.describe !== undefined &&
381
+ typeof workflow.gate.describe !== "function"
382
+ )
383
+ throw new Error(`Invalid gate description callback: ${workflow.id}`);
183
384
  if (!workflow.gate.fields) return;
184
385
  const argsSchema = unwrapSchema(workflow.args);
185
- if (schemaType(argsSchema) !== "object") throw new Error(`Workflow gate fields require object args: ${workflow.id}`);
386
+ if (schemaType(argsSchema) !== "object")
387
+ throw new Error(`Workflow gate fields require object args: ${workflow.id}`);
186
388
  const shape = schemaShape(argsSchema);
187
- for (const field of workflow.gate.fields) if (!Object.hasOwn(shape, field)) throw new Error(`Unknown workflow gate field ${field}: ${workflow.id}`);
389
+ for (const field of workflow.gate.fields)
390
+ if (!Object.hasOwn(shape, field))
391
+ throw new Error(`Unknown workflow gate field ${field}: ${workflow.id}`);
188
392
  }
189
393
 
190
- function workflowInfo(entry: NornRegisteredWorkflow): NornRegisteredWorkflowInfo {
394
+ function workflowInfo(
395
+ entry: NornRegisteredWorkflow,
396
+ ): NornRegisteredWorkflowInfo {
191
397
  return {
192
398
  id: entry.workflow.id,
193
- instructions: entry.workflow.entrypoint === false ? undefined : entry.workflow.entrypoint.instructions,
399
+ instructions:
400
+ entry.workflow.entrypoint === false
401
+ ? undefined
402
+ : entry.workflow.entrypoint.instructions,
194
403
  isEntrypoint: entry.workflow.entrypoint !== false,
195
404
  source: entry.source,
196
405
  configKey: entry.workflow.id,
197
406
  scope: entry.scopeId ? { id: entry.scopeId } : undefined,
198
407
  };
199
408
  }
200
- function gateInfo(workflow: NornAnyWorkflowDeclaration): NornWorkflowGateInfo | null {
409
+ function gateInfo(
410
+ workflow: NornAnyWorkflowDeclaration,
411
+ ): NornWorkflowGateInfo | null {
201
412
  return workflow.gate ? { enabled: true, fields: workflow.gate.fields } : null;
202
413
  }
@@ -1,6 +1,7 @@
1
1
  import { isAbsolute, resolve } from "node:path";
2
2
 
3
3
  export function requireAbsoluteWorkingDirectory(cwd: string): string {
4
- if (typeof cwd !== "string" || !isAbsolute(cwd)) throw new Error("Working directory (cwd) must be an absolute path");
4
+ if (typeof cwd !== "string" || !isAbsolute(cwd))
5
+ throw new Error("Working directory (cwd) must be an absolute path");
5
6
  return resolve(cwd);
6
7
  }