@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.
- package/assets/README.md +17 -26
- package/assets/docs/README.md +3 -3
- package/assets/docs/agents.md +12 -1
- package/assets/docs/cli.md +5 -5
- package/assets/docs/composition.md +21 -21
- package/assets/docs/persistence.md +7 -10
- package/assets/docs/projects.md +30 -18
- package/assets/docs/providers.md +1 -1
- package/assets/docs/recovery.md +13 -9
- package/assets/docs/resources.md +4 -19
- package/assets/docs/schemas.md +1 -1
- package/assets/docs/workflows.md +95 -7
- package/assets/examples/agent-then-analysis/README.md +4 -4
- package/assets/examples/agent-then-analysis/input.json +1 -1
- package/assets/examples/agent-then-analysis/norn.project.json +1 -1
- package/assets/examples/agent-then-analysis/plugin.ts +55 -68
- package/assets/examples/caller-selected-continuation/README.md +5 -5
- package/assets/examples/caller-selected-continuation/caller.ts +35 -38
- package/assets/examples/caller-selected-continuation/input.json +2 -2
- package/assets/examples/caller-selected-continuation/norn.project.json +1 -1
- package/assets/examples/caller-selected-continuation/producer.ts +15 -23
- package/assets/examples/coordinating-multiple-agents/README.md +2 -2
- package/assets/examples/coordinating-multiple-agents/input.json +1 -1
- package/assets/examples/coordinating-multiple-agents/norn.project.json +1 -1
- package/assets/examples/coordinating-multiple-agents/plugin.ts +52 -56
- package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +4 -4
- package/assets/examples/getting-started/norn.project.json +1 -1
- package/assets/examples/getting-started/plugin.ts +16 -26
- package/assets/examples/minimal-workflow/README.md +8 -8
- package/assets/examples/minimal-workflow/norn.project.json +1 -1
- package/assets/examples/minimal-workflow/plugin.ts +11 -25
- package/assets/examples/shared-state/README.md +10 -2
- package/assets/examples/shared-state/input.json +1 -1
- package/assets/examples/shared-state/norn.project.json +1 -1
- package/assets/examples/shared-state/plugin.ts +36 -41
- package/assets/examples/shared-state/shared-state.ts +75 -0
- package/assets/{packages/sdk/src → examples/shared-state}/state-adapter.ts +17 -16
- package/assets/examples/worktree-development-loop/README.md +6 -7
- package/assets/examples/worktree-development-loop/norn.project.json +1 -1
- package/assets/examples/worktree-development-loop/plugin.ts +6 -26
- package/assets/examples/worktree-development-loop/scope.ts +4 -0
- package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +14 -15
- package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +3 -4
- package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +2 -2
- package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +34 -33
- package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +2 -3
- package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +7 -3
- package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +23 -16
- package/assets/examples/worktree-development-loop/workflows/planning/index.ts +2 -3
- package/assets/examples/worktree-development-loop/workflows/planning/schema.ts +4 -2
- package/assets/examples/worktree-development-loop/workflows/review/execute.ts +40 -31
- package/assets/examples/worktree-development-loop/workflows/review/index.ts +3 -4
- package/assets/examples/worktree-development-loop/workflows/review/schema.ts +5 -4
- package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +49 -44
- package/assets/examples/worktree-development-loop/workflows/review-router/index.ts +2 -3
- package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +4 -3
- package/assets/package.json +1 -1
- package/assets/packages/cli/src/cli.ts +35 -34
- package/assets/packages/cli/src/client.ts +9 -16
- package/assets/packages/cli/src/documentation-intro.ts +1 -1
- package/assets/packages/cli/src/generated-build-info.ts +2 -2
- package/assets/packages/cli/src/internal/agent-response-tool.ts +3 -3
- package/assets/packages/cli/src/internal/engine.ts +38 -62
- package/assets/packages/cli/src/internal/errors.ts +4 -4
- package/assets/packages/cli/src/internal/launch-request.ts +6 -6
- package/assets/packages/cli/src/internal/run-state.ts +31 -21
- package/assets/packages/cli/src/internal/run.ts +2 -5
- package/assets/packages/cli/src/internal/workflow-registry.ts +114 -111
- package/assets/packages/cli/src/workflow-loader.ts +214 -0
- package/assets/packages/core/src/workflow-transition.ts +2 -2
- package/assets/packages/sdk/src/api.ts +133 -289
- package/assets/packages/sdk/src/index.ts +1 -1
- package/assets/packages/sdk/src/schema.ts +3 -3
- package/assets/tests/workflow-ref.test.ts +50 -51
- package/dist/cli.js +33 -32
- package/dist/client.d.ts +3 -4
- package/dist/client.js +3 -8
- package/dist/documentation-intro.js +1 -1
- package/dist/generated-build-info.d.ts +2 -2
- package/dist/generated-build-info.js +2 -2
- package/dist/internal/agent-response-tool.js +3 -3
- package/dist/internal/engine.d.ts +4 -8
- package/dist/internal/engine.js +36 -49
- package/dist/internal/errors.d.ts +3 -3
- package/dist/internal/errors.js +1 -1
- package/dist/internal/launch-request.d.ts +4 -4
- package/dist/internal/launch-request.js +2 -2
- package/dist/internal/run-state.d.ts +9 -8
- package/dist/internal/run-state.js +29 -20
- package/dist/internal/run.d.ts +1 -3
- package/dist/internal/run.js +3 -5
- package/dist/internal/workflow-registry.d.ts +25 -17
- package/dist/internal/workflow-registry.js +107 -72
- package/dist/{plugin-loader.d.ts → workflow-loader.d.ts} +7 -8
- package/dist/workflow-loader.js +235 -0
- package/package.json +2 -2
- package/assets/examples/worktree-development-loop/manifest.ts +0 -26
- package/assets/examples/worktree-development-loop/state.ts +0 -23
- package/assets/examples/worktree-development-loop/workflows/development-loop/declaration.ts +0 -8
- package/assets/examples/worktree-development-loop/workflows/implementation/declaration.ts +0 -8
- package/assets/examples/worktree-development-loop/workflows/planning/declaration.ts +0 -8
- package/assets/examples/worktree-development-loop/workflows/review/declaration.ts +0 -8
- package/assets/examples/worktree-development-loop/workflows/review-router/declaration.ts +0 -12
- package/assets/packages/cli/src/internal/run-resources.ts +0 -23
- package/assets/packages/cli/src/internal/state-store.ts +0 -83
- package/assets/packages/cli/src/plugin-loader.ts +0 -400
- package/dist/internal/run-resources.d.ts +0 -6
- package/dist/internal/run-resources.js +0 -26
- package/dist/internal/state-store.d.ts +0 -23
- package/dist/internal/state-store.js +0 -103
- package/dist/plugin-loader.js +0 -341
|
@@ -1,39 +1,79 @@
|
|
|
1
1
|
// src/internal/workflow-registry.ts
|
|
2
|
-
import { isWorkflowComplete, isWorkflowFail, isWorkflowNext } from "@vimhead.dev/norn";
|
|
2
|
+
import { isWorkflowComplete, isWorkflowDeclaration, isWorkflowFail, isWorkflowNext } from "@vimhead.dev/norn";
|
|
3
3
|
import { assertWorkflowMetadata, inspectSchema, isPlainObject, schemaShape, schemaType, unwrapSchema } from "@vimhead.dev/norn/schema";
|
|
4
4
|
import { Value } from "typebox/value";
|
|
5
|
-
function
|
|
5
|
+
function decodeConfiguration(input) {
|
|
6
6
|
if (!input.schema) {
|
|
7
|
-
if (input.value !== void 0) throw new Error(`Norn config provided
|
|
8
|
-
return void 0;
|
|
7
|
+
if (input.value !== void 0) throw new Error(`Norn config provided without config schema: ${input.key}`);
|
|
8
|
+
return { key: input.key, schema: void 0, input: void 0, value: void 0 };
|
|
9
|
+
}
|
|
10
|
+
const value = input.value === void 0 && schemaType(unwrapSchema(input.schema)) === "object" ? {} : input.value;
|
|
11
|
+
return { key: input.key, schema: input.schema, input: value, value: Value.Decode(input.schema, value) };
|
|
12
|
+
}
|
|
13
|
+
function assertCompatibleConfigurationOwners(workflows) {
|
|
14
|
+
const owners = /* @__PURE__ */ new Map();
|
|
15
|
+
for (const workflow of workflows) {
|
|
16
|
+
for (const owner of [
|
|
17
|
+
{ key: workflow.id, kind: "workflow", definition: workflow },
|
|
18
|
+
...workflow.scope ? [{ key: workflow.scope.id, kind: "scope", definition: workflow.scope }] : []
|
|
19
|
+
]) {
|
|
20
|
+
const previous = owners.get(owner.key);
|
|
21
|
+
if (previous && previous.kind !== owner.kind) throw new Error(`Ambiguous configuration key: ${owner.key}`);
|
|
22
|
+
if (previous && owner.kind === "scope" && previous.definition !== owner.definition) throw new Error(`Duplicate workflow scope id: ${owner.key}; import one shared scope definition`);
|
|
23
|
+
owners.set(owner.key, owner);
|
|
24
|
+
}
|
|
9
25
|
}
|
|
10
|
-
const value = defaultConfigInput(input.schema, input.value);
|
|
11
|
-
return { schema: input.schema, input: value, value: Value.Decode(input.schema, value) };
|
|
12
26
|
}
|
|
13
27
|
var NornWorkflowRegistry = class {
|
|
14
28
|
entries = /* @__PURE__ */ new Map();
|
|
15
|
-
|
|
29
|
+
scopes = /* @__PURE__ */ new Map();
|
|
30
|
+
register(input) {
|
|
31
|
+
const { workflow, config, source } = input;
|
|
32
|
+
assertWorkflowDefinition(workflow);
|
|
16
33
|
if (this.entries.has(workflow.id)) throw new Error(`Workflow already registered: ${workflow.id}`);
|
|
34
|
+
assertCompatibleConfigurationOwners([...this.sortedEntries().map((entry2) => entry2.workflow), workflow]);
|
|
17
35
|
const entry = {
|
|
18
36
|
workflow,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
37
|
+
source,
|
|
38
|
+
scopeId: workflow.scope?.id,
|
|
39
|
+
configuration: decodeConfiguration({ key: workflow.id, schema: workflow.config, value: configurationInput(config, workflow.id) })
|
|
22
40
|
};
|
|
23
|
-
|
|
24
|
-
|
|
41
|
+
if (workflow.scope && !this.scopes.has(workflow.scope.id)) {
|
|
42
|
+
this.scopes.set(workflow.scope.id, {
|
|
43
|
+
definition: workflow.scope,
|
|
44
|
+
configuration: decodeConfiguration({ key: workflow.scope.id, schema: workflow.scope.config, value: configurationInput(config, workflow.scope.id) })
|
|
45
|
+
});
|
|
46
|
+
}
|
|
25
47
|
this.entries.set(workflow.id, entry);
|
|
26
48
|
return () => {
|
|
27
|
-
if (this.entries.get(workflow.id)
|
|
49
|
+
if (this.entries.get(workflow.id) !== entry) return;
|
|
50
|
+
this.entries.delete(workflow.id);
|
|
51
|
+
if (entry.scopeId && ![...this.entries.values()].some((other) => other.scopeId === entry.scopeId)) this.scopes.delete(entry.scopeId);
|
|
28
52
|
};
|
|
29
53
|
}
|
|
30
54
|
list(options = {}) {
|
|
31
|
-
|
|
32
|
-
return entries.map((entry) => workflowInfo(entry));
|
|
55
|
+
return (options.entrypointsOnly ? this.launchableEntries() : this.sortedEntries()).map(workflowInfo);
|
|
33
56
|
}
|
|
34
57
|
inspect(workflowId) {
|
|
35
58
|
const entry = this.entries.get(workflowId);
|
|
36
|
-
|
|
59
|
+
const scope = entry?.scopeId ? this.scopes.get(entry.scopeId) : void 0;
|
|
60
|
+
return entry ? {
|
|
61
|
+
...workflowInfo(entry),
|
|
62
|
+
argsSchema: inspectSchema(entry.workflow.args),
|
|
63
|
+
configSchema: entry.configuration.schema ? inspectSchema(entry.configuration.schema) : null,
|
|
64
|
+
scope: scope ? {
|
|
65
|
+
id: scope.definition.id,
|
|
66
|
+
configKey: scope.configuration.key,
|
|
67
|
+
configSchema: scope.configuration.schema ? inspectSchema(scope.configuration.schema) : null
|
|
68
|
+
} : void 0,
|
|
69
|
+
gate: gateInfo(entry.workflow)
|
|
70
|
+
} : void 0;
|
|
71
|
+
}
|
|
72
|
+
configurationInfos() {
|
|
73
|
+
return [
|
|
74
|
+
...this.sortedEntries().map((entry) => ({ configuration: entry.configuration, scopeId: null })),
|
|
75
|
+
...[...this.scopes.values()].map((scope) => ({ configuration: scope.configuration, scopeId: scope.definition.id }))
|
|
76
|
+
].map(({ configuration, scopeId }) => ({ key: configuration.key, scopeId, configSchema: configuration.schema ? inspectSchema(configuration.schema) : null, config: configuration.value }));
|
|
37
77
|
}
|
|
38
78
|
launchableEntries() {
|
|
39
79
|
return this.sortedEntries().filter(({ workflow }) => workflow.isEntrypoint);
|
|
@@ -41,80 +81,71 @@ var NornWorkflowRegistry = class {
|
|
|
41
81
|
workflowById(workflowId) {
|
|
42
82
|
return this.entries.get(workflowId)?.workflow;
|
|
43
83
|
}
|
|
44
|
-
async describeGate(workflow, run,
|
|
45
|
-
const entry = this.
|
|
46
|
-
if (!entry) throw new Error(`Unknown workflow: ${workflow.id}`);
|
|
84
|
+
async describeGate(workflow, run, args, configOverride) {
|
|
85
|
+
const entry = this.requireEntry(workflow.id);
|
|
47
86
|
if (!workflow.gate) throw new Error(`Workflow is not gated: ${workflow.id}`);
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
return
|
|
87
|
+
const context = this.createExecutionContext({ entry, run, args, configOverride });
|
|
88
|
+
const description = await entry.workflow.gate?.describe?.(context) ?? workflow.id;
|
|
89
|
+
if (typeof description !== "string" || description.trim().length === 0) throw new Error(`Workflow gate description must not be empty: ${workflow.id}`);
|
|
90
|
+
return description.trim();
|
|
52
91
|
}
|
|
53
|
-
async execute(workflow, run,
|
|
54
|
-
const entry = this.
|
|
55
|
-
|
|
56
|
-
const parsedParams = Value.Decode(workflow.params, params);
|
|
57
|
-
const parsedConfig = parseExecutionConfig(entry, configOverride);
|
|
58
|
-
const result = await entry.implementation.execute(run, parsedParams, parsedConfig);
|
|
92
|
+
async execute(workflow, run, args, configOverride) {
|
|
93
|
+
const entry = this.requireEntry(workflow.id);
|
|
94
|
+
const result = await entry.workflow.execute(this.createExecutionContext({ entry, run, args, configOverride }));
|
|
59
95
|
if (isWorkflowNext(result)) return result;
|
|
60
96
|
if (isWorkflowComplete(result)) return { type: "complete", workflow, metadata: result.metadata };
|
|
61
97
|
if (isWorkflowFail(result)) return { type: "fail", workflow, metadata: result.metadata };
|
|
62
98
|
throw new Error(`Workflow returned invalid control result: ${workflow.id}`);
|
|
63
99
|
}
|
|
100
|
+
createExecutionContext(input) {
|
|
101
|
+
const { entry, run, configOverride } = input;
|
|
102
|
+
const scope = entry.scopeId ? this.scopes.get(entry.scopeId) : void 0;
|
|
103
|
+
return {
|
|
104
|
+
args: Value.Decode(entry.workflow.args, input.args),
|
|
105
|
+
config: parseExecutionConfig(entry.configuration, configOverride),
|
|
106
|
+
...scope ? { scope: { id: scope.definition.id, config: parseExecutionConfig(scope.configuration, configOverride) } } : {},
|
|
107
|
+
run
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
requireEntry(id) {
|
|
111
|
+
const entry = this.entries.get(id);
|
|
112
|
+
if (!entry) throw new Error(`Unknown workflow: ${id}`);
|
|
113
|
+
return entry;
|
|
114
|
+
}
|
|
64
115
|
sortedEntries() {
|
|
65
|
-
return
|
|
116
|
+
return [...this.entries.values()].sort((left, right) => left.workflow.id.localeCompare(right.workflow.id));
|
|
66
117
|
}
|
|
67
118
|
};
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
return schemaType(unwrapSchema(configSchema)) === "object" ? {} : void 0;
|
|
71
|
-
}
|
|
72
|
-
function parseExecutionConfig(entry, configOverride) {
|
|
73
|
-
const pluginConfigOverride = pluginConfigOverrideInput(entry, configOverride);
|
|
74
|
-
if (!entry.configuration) {
|
|
75
|
-
if (pluginConfigOverride !== void 0) throw new Error(`Run config override provided for plugin without config schema: ${entry.plugin?.id ?? entry.workflow.id}`);
|
|
76
|
-
return void 0;
|
|
77
|
-
}
|
|
78
|
-
if (pluginConfigOverride === void 0) return entry.configuration.value;
|
|
79
|
-
const rawConfig = isPlainObject(entry.configuration.input) && isPlainObject(pluginConfigOverride) ? deepMerge(entry.configuration.input, pluginConfigOverride) : pluginConfigOverride;
|
|
80
|
-
return Value.Decode(entry.configuration.schema, rawConfig);
|
|
119
|
+
function configurationInput(config, key) {
|
|
120
|
+
return Object.hasOwn(config, key) ? config[key] : void 0;
|
|
81
121
|
}
|
|
82
|
-
function
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
122
|
+
function parseExecutionConfig(configuration, overrides) {
|
|
123
|
+
if (overrides !== void 0 && !isPlainObject(overrides)) throw new Error("Run config override must be an object keyed by workflow or scope id");
|
|
124
|
+
const override = overrides ? configurationInput(overrides, configuration.key) : void 0;
|
|
125
|
+
if (override === void 0) return configuration.value;
|
|
126
|
+
if (!configuration.schema) throw new Error(`Run config override provided without config schema: ${configuration.key}`);
|
|
127
|
+
const input = isPlainObject(configuration.input) && isPlainObject(override) ? deepMerge(configuration.input, override) : override;
|
|
128
|
+
return Value.Decode(configuration.schema, input);
|
|
87
129
|
}
|
|
88
130
|
function deepMerge(base, override) {
|
|
89
131
|
const result = { ...base };
|
|
90
|
-
for (const [key, value] of Object.entries(override))
|
|
91
|
-
const baseValue = result[key];
|
|
92
|
-
result[key] = isPlainObject(baseValue) && isPlainObject(value) ? deepMerge(baseValue, value) : value;
|
|
93
|
-
}
|
|
132
|
+
for (const [key, value] of Object.entries(override)) result[key] = isPlainObject(result[key]) && isPlainObject(value) ? deepMerge(result[key], value) : value;
|
|
94
133
|
return result;
|
|
95
134
|
}
|
|
135
|
+
function assertWorkflowDefinition(workflow) {
|
|
136
|
+
assertWorkflowMetadata(workflow);
|
|
137
|
+
if (!isWorkflowDeclaration(workflow)) throw new Error("Invalid workflow definition");
|
|
138
|
+
assertGateWorkflow(workflow);
|
|
139
|
+
}
|
|
96
140
|
function assertGateWorkflow(workflow) {
|
|
97
141
|
if (!workflow.gate) return;
|
|
98
142
|
if (workflow.gate.enabled !== true) throw new Error(`Workflow gate must be enabled with true: ${workflow.id}`);
|
|
143
|
+
if (workflow.gate.describe !== void 0 && typeof workflow.gate.describe !== "function") throw new Error(`Invalid gate description callback: ${workflow.id}`);
|
|
99
144
|
if (!workflow.gate.fields) return;
|
|
100
|
-
const
|
|
101
|
-
if (schemaType(
|
|
102
|
-
const
|
|
103
|
-
for (const field of workflow.gate.fields) {
|
|
104
|
-
if (!Object.prototype.hasOwnProperty.call(paramsShape, field)) throw new Error(`Unknown workflow gate field ${field}: ${workflow.id}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function validateGateDescription(description, workflowId) {
|
|
108
|
-
const trimmed = description.trim();
|
|
109
|
-
if (trimmed.length === 0) throw new Error(`Workflow gate description must not be empty: ${workflowId}`);
|
|
110
|
-
return trimmed;
|
|
111
|
-
}
|
|
112
|
-
function inspectedWorkflowInfo(entry) {
|
|
113
|
-
return {
|
|
114
|
-
...workflowInfo(entry),
|
|
115
|
-
paramsSchema: inspectSchema(entry.workflow.params),
|
|
116
|
-
gate: gateInfo(entry.workflow)
|
|
117
|
-
};
|
|
145
|
+
const argsSchema = unwrapSchema(workflow.args);
|
|
146
|
+
if (schemaType(argsSchema) !== "object") throw new Error(`Workflow gate fields require object args: ${workflow.id}`);
|
|
147
|
+
const shape = schemaShape(argsSchema);
|
|
148
|
+
for (const field of workflow.gate.fields) if (!Object.hasOwn(shape, field)) throw new Error(`Unknown workflow gate field ${field}: ${workflow.id}`);
|
|
118
149
|
}
|
|
119
150
|
function workflowInfo(entry) {
|
|
120
151
|
return {
|
|
@@ -122,7 +153,9 @@ function workflowInfo(entry) {
|
|
|
122
153
|
instructions: entry.workflow.instructions,
|
|
123
154
|
isEntrypoint: entry.workflow.isEntrypoint,
|
|
124
155
|
isolation: entry.workflow.isolation,
|
|
125
|
-
|
|
156
|
+
source: entry.source,
|
|
157
|
+
configKey: entry.workflow.id,
|
|
158
|
+
scope: entry.scopeId ? { id: entry.scopeId } : void 0
|
|
126
159
|
};
|
|
127
160
|
}
|
|
128
161
|
function gateInfo(workflow) {
|
|
@@ -130,5 +163,7 @@ function gateInfo(workflow) {
|
|
|
130
163
|
}
|
|
131
164
|
export {
|
|
132
165
|
NornWorkflowRegistry,
|
|
133
|
-
|
|
166
|
+
assertCompatibleConfigurationOwners,
|
|
167
|
+
assertWorkflowDefinition,
|
|
168
|
+
decodeConfiguration
|
|
134
169
|
};
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import { type NornProjectInspection, type
|
|
1
|
+
import { type NornAnyWorkflowDeclaration, type NornProjectInspection, type NornProjectConfigurationInfo, type NornWorkflowCatalogInfo, type NornWorkflowInspection, type NornWorkflowSource } from "@vimhead.dev/norn";
|
|
2
2
|
import * as typeboxModule from "typebox";
|
|
3
3
|
import { type StaticDecode } from "typebox";
|
|
4
|
-
import { NornMemoryWorkflowState } from "./internal/state-store.ts";
|
|
5
4
|
import { NornWorkflowRegistry, type NornRegisteredWorkflow } from "./internal/workflow-registry.ts";
|
|
6
5
|
export declare const NORN_PROJECT_FILE_NAME = "norn.project.json";
|
|
7
6
|
declare const nornConfigSchema: typeboxModule.TObject<{
|
|
8
|
-
|
|
7
|
+
workflows: typeboxModule.TArray<typeboxModule.TString>;
|
|
9
8
|
includes: typeboxModule.TArray<typeboxModule.TString>;
|
|
10
9
|
config: typeboxModule.TRecord<"^.*$", typeboxModule.TUnknown>;
|
|
11
10
|
}>;
|
|
12
11
|
declare const nornProjectConfigSchema: typeboxModule.TObject<{
|
|
13
|
-
|
|
14
|
-
plugins: typeboxModule.TArray<typeboxModule.TString>;
|
|
12
|
+
workflows: typeboxModule.TArray<typeboxModule.TString>;
|
|
15
13
|
includes: typeboxModule.TArray<typeboxModule.TString>;
|
|
16
14
|
config: typeboxModule.TRecord<"^.*$", typeboxModule.TUnknown>;
|
|
15
|
+
version: typeboxModule.TLiteral<1>;
|
|
17
16
|
}>;
|
|
18
17
|
type NornConfig = StaticDecode<typeof nornConfigSchema>;
|
|
19
18
|
type NornProjectConfig = StaticDecode<typeof nornProjectConfigSchema>;
|
|
@@ -33,11 +32,11 @@ export type NornProject = {
|
|
|
33
32
|
readonly projectConfig: Record<string, unknown>;
|
|
34
33
|
};
|
|
35
34
|
export type NornLoadedProject = NornProject & {
|
|
36
|
-
readonly
|
|
37
|
-
readonly
|
|
35
|
+
readonly definitions: readonly NornAnyWorkflowDeclaration[];
|
|
36
|
+
readonly modules: readonly NornWorkflowSource[];
|
|
37
|
+
readonly configurations: readonly NornProjectConfigurationInfo[];
|
|
38
38
|
readonly registry: NornWorkflowRegistry;
|
|
39
39
|
readonly workflows: readonly NornRegisteredWorkflow[];
|
|
40
|
-
readonly state: NornMemoryWorkflowState;
|
|
41
40
|
};
|
|
42
41
|
export declare function loadNornProject(cwd: string): Promise<NornLoadedProject>;
|
|
43
42
|
export declare function discoverNornProject(cwd: string): Promise<NornProjectInspection & NornWorkflowCatalogInfo>;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// src/workflow-loader.ts
|
|
2
|
+
import * as nornModule from "@vimhead.dev/norn";
|
|
3
|
+
import { isWorkflowDeclaration } 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/workflow-loader.ts
|
|
11
|
+
import * as nornFilesModule from "@vimhead.dev/norn/files";
|
|
12
|
+
import * as nornSchemaModule from "@vimhead.dev/norn/schema";
|
|
13
|
+
import { inspectSchema, isPlainObject } 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 { assertCompatibleConfigurationOwners, assertWorkflowDefinition, NornWorkflowRegistry } from "./internal/workflow-registry.js";
|
|
26
|
+
var NORN_PROJECT_FILE_NAME = "norn.project.json";
|
|
27
|
+
var nornConfigSchema = Type.Object({
|
|
28
|
+
workflows: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
|
|
29
|
+
includes: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
|
|
30
|
+
config: Type.Record(Type.String(), Type.Unknown(), { default: {} })
|
|
31
|
+
});
|
|
32
|
+
var nornProjectConfigSchema = Type.Object({ ...nornConfigSchema.properties, version: Type.Literal(1, { default: 1 }) });
|
|
33
|
+
async function loadNornProject(cwd) {
|
|
34
|
+
const { project, diagnostics } = await collectNornProject(cwd);
|
|
35
|
+
if (diagnostics.length) throw new NornProjectLoadError({ diagnostics });
|
|
36
|
+
return project;
|
|
37
|
+
}
|
|
38
|
+
async function discoverNornProject(cwd) {
|
|
39
|
+
const { project, diagnostics } = await collectNornProject(cwd);
|
|
40
|
+
return { project: buildProjectInfo(project), workflows: project.registry.list(), isComplete: diagnostics.length === 0, diagnostics };
|
|
41
|
+
}
|
|
42
|
+
async function inspectNornWorkflow(input) {
|
|
43
|
+
const { project, diagnostics } = await collectNornProject(input.cwd);
|
|
44
|
+
const summary = project.registry.list().find((workflow) => workflow.id === input.workflowId);
|
|
45
|
+
if (!summary) {
|
|
46
|
+
if (!diagnostics.length) throw new Error(`Unknown workflow: ${input.workflowId}`);
|
|
47
|
+
return { workflow: null, isComplete: false, diagnostics };
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return { workflow: project.registry.inspect(input.workflowId) ?? null, isComplete: diagnostics.length === 0, diagnostics };
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (!summary.source) throw error;
|
|
53
|
+
return { workflow: null, isComplete: false, diagnostics: [...diagnostics, createDiagnostic({ source: summary.source, workflowId: summary.id, scopeId: summary.scope?.id ?? null, stage: "schema", error })] };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function buildProjectInfo(project) {
|
|
57
|
+
return {
|
|
58
|
+
cwd: project.cwd,
|
|
59
|
+
projectPath: project.projectPath,
|
|
60
|
+
projectRoot: project.projectRoot,
|
|
61
|
+
configPath: project.configPath,
|
|
62
|
+
configRoot: project.configRoot,
|
|
63
|
+
configFiles: project.configFiles.map((file) => file.path),
|
|
64
|
+
modules: project.modules,
|
|
65
|
+
configurations: project.configurations
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function findNornProject(cwd) {
|
|
69
|
+
const projectPath = await findNearestNornProject(cwd);
|
|
70
|
+
const projectFile = await readConfigFile({ path: projectPath, isProject: true });
|
|
71
|
+
const configFiles = await loadConfigTree(projectFile, /* @__PURE__ */ new Set());
|
|
72
|
+
return {
|
|
73
|
+
cwd: resolve(cwd),
|
|
74
|
+
projectPath,
|
|
75
|
+
projectRoot: projectFile.root,
|
|
76
|
+
configPath: projectPath,
|
|
77
|
+
configRoot: projectFile.root,
|
|
78
|
+
config: projectFile.config,
|
|
79
|
+
configFiles,
|
|
80
|
+
projectConfig: mergeProjectConfig(configFiles)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function findNearestNornProject(cwd) {
|
|
84
|
+
let current = resolve(cwd);
|
|
85
|
+
while (true) {
|
|
86
|
+
const path = join(current, NORN_PROJECT_FILE_NAME);
|
|
87
|
+
try {
|
|
88
|
+
await access(path);
|
|
89
|
+
return path;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (!isNodeError(error) || error.code !== "ENOENT") throw error;
|
|
92
|
+
}
|
|
93
|
+
const parent = dirname(current);
|
|
94
|
+
if (parent === current) throw new Error(`Could not find ${NORN_PROJECT_FILE_NAME} from ${cwd}`);
|
|
95
|
+
current = parent;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function loadConfigTree(file, visited) {
|
|
99
|
+
if (visited.has(file.path)) return [];
|
|
100
|
+
visited.add(file.path);
|
|
101
|
+
const files = [file];
|
|
102
|
+
for (const include of file.config.includes) {
|
|
103
|
+
for (const path of await expandIncludePath(file.root, include)) files.push(...await loadConfigTree(await readConfigFile({ path, isProject: false }), visited));
|
|
104
|
+
}
|
|
105
|
+
return files;
|
|
106
|
+
}
|
|
107
|
+
async function readConfigFile(input) {
|
|
108
|
+
const raw = JSON.parse(await readFile(input.path, "utf8"));
|
|
109
|
+
if (isPlainObject(raw) && Object.hasOwn(raw, "plugins")) throw new Error(`Replace plugins with workflows in ${input.path}; registration modules must export workflow arrays`);
|
|
110
|
+
const schema = input.isProject ? nornProjectConfigSchema : nornConfigSchema;
|
|
111
|
+
const config = Value.Parse(schema, Value.Default(schema, raw));
|
|
112
|
+
return { path: input.path, root: dirname(input.path), config: { ...config, version: 1 } };
|
|
113
|
+
}
|
|
114
|
+
async function collectNornProject(cwd) {
|
|
115
|
+
const project = await findNornProject(cwd);
|
|
116
|
+
const imported = await importWorkflowModules(project);
|
|
117
|
+
const diagnostics = [...imported.diagnostics];
|
|
118
|
+
const registry = new NornWorkflowRegistry();
|
|
119
|
+
const loaded = [];
|
|
120
|
+
const conflicts = collectModuleConflicts(imported.modules);
|
|
121
|
+
for (const candidate of imported.modules) {
|
|
122
|
+
const conflict = conflicts.get(candidate);
|
|
123
|
+
if (conflict) {
|
|
124
|
+
diagnostics.push(...conflict);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const unregister = [];
|
|
128
|
+
const errors = [];
|
|
129
|
+
for (const workflow of candidate.definitions) {
|
|
130
|
+
let stage = "schema";
|
|
131
|
+
try {
|
|
132
|
+
if (workflow.config) inspectSchema(workflow.config);
|
|
133
|
+
if (workflow.scope?.config) inspectSchema(workflow.scope.config);
|
|
134
|
+
stage = "declaration";
|
|
135
|
+
assertWorkflowDefinition(workflow);
|
|
136
|
+
stage = "config";
|
|
137
|
+
unregister.push(registry.register({ workflow, config: project.projectConfig, source: candidate.source }));
|
|
138
|
+
} catch (error) {
|
|
139
|
+
errors.push(createDiagnostic({ source: candidate.source, workflowId: workflow.id, scopeId: workflow.scope?.id ?? null, stage, error }));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (errors.length) {
|
|
143
|
+
for (const dispose of unregister) dispose();
|
|
144
|
+
diagnostics.push(...errors);
|
|
145
|
+
} else loaded.push(candidate);
|
|
146
|
+
}
|
|
147
|
+
const definitions = loaded.flatMap((module) => module.definitions);
|
|
148
|
+
return { project: { ...project, definitions, modules: loaded.map((module) => module.source), configurations: registry.configurationInfos(), registry, workflows: registry.launchableEntries() }, diagnostics };
|
|
149
|
+
}
|
|
150
|
+
function collectModuleConflicts(modules) {
|
|
151
|
+
const conflicts = /* @__PURE__ */ new Map();
|
|
152
|
+
const byId = /* @__PURE__ */ new Map();
|
|
153
|
+
for (const module of modules) for (const workflow of module.definitions) byId.set(workflow.id, [...byId.get(workflow.id) ?? [], module]);
|
|
154
|
+
for (const [id, owners] of byId) {
|
|
155
|
+
if (owners.length < 2) continue;
|
|
156
|
+
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}`) })]);
|
|
157
|
+
}
|
|
158
|
+
for (let index = 0; index < modules.length; index++) {
|
|
159
|
+
for (let other = index; other < modules.length; other++) {
|
|
160
|
+
const candidates = [.../* @__PURE__ */ new Set([modules[index], modules[other]])];
|
|
161
|
+
try {
|
|
162
|
+
assertCompatibleConfigurationOwners(candidates.flatMap((module) => module.definitions));
|
|
163
|
+
} catch (error) {
|
|
164
|
+
for (const module of candidates) conflicts.set(module, [...conflicts.get(module) ?? [], createDiagnostic({ source: module.source, workflowId: null, scopeId: null, stage: "config", error })]);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return conflicts;
|
|
169
|
+
}
|
|
170
|
+
async function importWorkflowModules(project) {
|
|
171
|
+
const modules = [];
|
|
172
|
+
const diagnostics = [];
|
|
173
|
+
const jiti = createJiti(pathToFileURL(project.configPath).href, { moduleCache: false, tryNative: false, virtualModules: nornWorkflowVirtualModules() });
|
|
174
|
+
const cache = /* @__PURE__ */ Object.create(null);
|
|
175
|
+
for (const file of project.configFiles) for (const path of file.config.workflows) {
|
|
176
|
+
const source = { configPath: file.path, path: resolveConfigPath(file.root, path) };
|
|
177
|
+
let stage = "import";
|
|
178
|
+
try {
|
|
179
|
+
const cached = cache[source.path];
|
|
180
|
+
const module = cached?.loaded ? cached.exports : await jiti.evalModule(await readFile(source.path, "utf8"), { filename: source.path, cache, async: true });
|
|
181
|
+
stage = "declaration";
|
|
182
|
+
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}`);
|
|
183
|
+
modules.push({ definitions: module.default, source });
|
|
184
|
+
} catch (error) {
|
|
185
|
+
for (const [path2, module] of Object.entries(cache)) if (!module.loaded) delete cache[path2];
|
|
186
|
+
diagnostics.push(createDiagnostic({ source, workflowId: null, scopeId: null, stage, error }));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { modules, diagnostics };
|
|
190
|
+
}
|
|
191
|
+
function createDiagnostic(input) {
|
|
192
|
+
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 : [] };
|
|
193
|
+
}
|
|
194
|
+
function nornWorkflowVirtualModules() {
|
|
195
|
+
return { "@vimhead.dev/norn": nornModule, "@vimhead.dev/norn/files": nornFilesModule, "@vimhead.dev/norn/schema": nornSchemaModule, typebox: typeboxModule, "typebox/value": typeboxValueModule, "typebox/compile": typeboxCompileModule, "typebox/schema": typeboxSchemaModule };
|
|
196
|
+
}
|
|
197
|
+
function mergeProjectConfig(files) {
|
|
198
|
+
const [project, ...reusable] = files;
|
|
199
|
+
let config = {};
|
|
200
|
+
for (const file of reusable) config = mergeConfig({ base: config, override: file.config.config, path: ["config"], rejectConflicts: true });
|
|
201
|
+
return mergeConfig({ base: config, override: project?.config.config ?? {}, path: ["config"], rejectConflicts: false });
|
|
202
|
+
}
|
|
203
|
+
function mergeConfig(input) {
|
|
204
|
+
const result = { ...input.base };
|
|
205
|
+
for (const [key, value] of Object.entries(input.override)) {
|
|
206
|
+
const previous = result[key];
|
|
207
|
+
const path = [...input.path, key];
|
|
208
|
+
if (isPlainObject(previous) && isPlainObject(value)) result[key] = mergeConfig({ base: previous, override: value, path, rejectConflicts: input.rejectConflicts });
|
|
209
|
+
else if (input.rejectConflicts && Object.hasOwn(result, key) && JSON.stringify(previous) !== JSON.stringify(value)) throw new Error(`Conflicting Norn reusable config at ${path.join(".")}`);
|
|
210
|
+
else result[key] = value;
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
async function expandIncludePath(root, path) {
|
|
215
|
+
const segments = path.split(/[\\/]+/).filter((segment) => segment.length && segment !== ".");
|
|
216
|
+
return segments.includes("*") ? expandGlobSegments(isAbsolute(path) ? sep : root, segments) : [resolveConfigPath(root, path)];
|
|
217
|
+
}
|
|
218
|
+
async function expandGlobSegments(root, segments) {
|
|
219
|
+
if (!segments.length) return [root];
|
|
220
|
+
const [segment, ...remaining] = segments;
|
|
221
|
+
if (segment !== "*") return expandGlobSegments(join(root, segment), remaining);
|
|
222
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
223
|
+
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();
|
|
224
|
+
}
|
|
225
|
+
function resolveConfigPath(root, path) {
|
|
226
|
+
if (!path.length) throw new Error("Norn path must not be empty");
|
|
227
|
+
return isAbsolute(path) ? path : resolve(root, path);
|
|
228
|
+
}
|
|
229
|
+
export {
|
|
230
|
+
NORN_PROJECT_FILE_NAME,
|
|
231
|
+
discoverNornProject,
|
|
232
|
+
findNornProject,
|
|
233
|
+
inspectNornWorkflow,
|
|
234
|
+
loadNornProject
|
|
235
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vimhead.dev/norn-cli",
|
|
3
|
-
"version": "0.1.0-tip.
|
|
3
|
+
"version": "0.1.0-tip.35390859630.1",
|
|
4
4
|
"description": "Harness-agnostic runtime for agent-driven and code-driven workflows, built primarily for agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
44
44
|
"jiti": "^2.7.0",
|
|
45
45
|
"typebox": "^1.3.34",
|
|
46
|
-
"@vimhead.dev/norn": "0.1.0-tip.
|
|
46
|
+
"@vimhead.dev/norn": "0.1.0-tip.35390859630.1"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "node ../../scripts/build-package.ts"
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { definePluginManifest } from "@vimhead.dev/norn";
|
|
2
|
-
import { developmentLoopState, implementationState, planningState, reviewState } from "./state.ts";
|
|
3
|
-
import { developmentLoopWorkflow } from "./workflows/development-loop/declaration.ts";
|
|
4
|
-
import { developmentLoopConfigSchema } from "./workflows/development-loop/schema.ts";
|
|
5
|
-
import { implementationWorkflow } from "./workflows/implementation/declaration.ts";
|
|
6
|
-
import { planningWorkflow } from "./workflows/planning/declaration.ts";
|
|
7
|
-
import { reviewRouterWorkflow } from "./workflows/review-router/declaration.ts";
|
|
8
|
-
import { reviewWorkflow } from "./workflows/review/declaration.ts";
|
|
9
|
-
|
|
10
|
-
export const worktreeDevelopmentLoopManifest = definePluginManifest({
|
|
11
|
-
id: "worktreeDevelopmentLoop",
|
|
12
|
-
config: developmentLoopConfigSchema,
|
|
13
|
-
workflows: {
|
|
14
|
-
planning: planningWorkflow,
|
|
15
|
-
implementation: implementationWorkflow,
|
|
16
|
-
review: reviewWorkflow,
|
|
17
|
-
reviewRouter: reviewRouterWorkflow,
|
|
18
|
-
developmentLoop: developmentLoopWorkflow,
|
|
19
|
-
},
|
|
20
|
-
states: {
|
|
21
|
-
developmentLoop: developmentLoopState,
|
|
22
|
-
planning: planningState,
|
|
23
|
-
implementation: implementationState,
|
|
24
|
-
review: reviewState,
|
|
25
|
-
},
|
|
26
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { artifactRefSchema, type NornWorkflowPluginStateTree } from "@vimhead.dev/norn";
|
|
2
|
-
import { Type } from "typebox";
|
|
3
|
-
import { reviewDecisionSchema } from "./workflows/review/schema.ts";
|
|
4
|
-
|
|
5
|
-
export const developmentLoopState = {
|
|
6
|
-
task: Type.String(),
|
|
7
|
-
maxIterations: Type.Integer({ minimum: 1, maximum: 10 }),
|
|
8
|
-
currentIteration: Type.Integer({ minimum: 1 }),
|
|
9
|
-
repositoryPath: Type.String(),
|
|
10
|
-
} as const satisfies NornWorkflowPluginStateTree;
|
|
11
|
-
|
|
12
|
-
export const planningState = {
|
|
13
|
-
planArtifact: artifactRefSchema,
|
|
14
|
-
} as const satisfies NornWorkflowPluginStateTree;
|
|
15
|
-
|
|
16
|
-
export const implementationState = {
|
|
17
|
-
implementationSummary: Type.String(),
|
|
18
|
-
} as const satisfies NornWorkflowPluginStateTree;
|
|
19
|
-
|
|
20
|
-
export const reviewState = {
|
|
21
|
-
reviewDecision: reviewDecisionSchema,
|
|
22
|
-
reviewArtifact: artifactRefSchema,
|
|
23
|
-
} as const satisfies NornWorkflowPluginStateTree;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { NornWorkflowDefinition } from "@vimhead.dev/norn";
|
|
2
|
-
import { developmentLoopParamsSchema } from "./schema.ts";
|
|
3
|
-
|
|
4
|
-
export const developmentLoopWorkflow = {
|
|
5
|
-
isEntrypoint: true,
|
|
6
|
-
instructions: "Plan once, then loop implementation and review in a workspace repository copy. Call this when a repository task should run through planning, implementation, and review.",
|
|
7
|
-
params: developmentLoopParamsSchema,
|
|
8
|
-
} as const satisfies NornWorkflowDefinition;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { NornWorkflowDefinition } from "@vimhead.dev/norn";
|
|
2
|
-
import { implementationParamsSchema } from "./schema.ts";
|
|
3
|
-
|
|
4
|
-
export const implementationWorkflow = {
|
|
5
|
-
isEntrypoint: false,
|
|
6
|
-
instructions: "Apply one implementation pass in the current repository.",
|
|
7
|
-
params: implementationParamsSchema,
|
|
8
|
-
} as const satisfies NornWorkflowDefinition;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { NornWorkflowDefinition } from "@vimhead.dev/norn";
|
|
2
|
-
import { planningParamsSchema } from "./schema.ts";
|
|
3
|
-
|
|
4
|
-
export const planningWorkflow = {
|
|
5
|
-
isEntrypoint: false,
|
|
6
|
-
instructions: "Create an implementation plan for a repository task.",
|
|
7
|
-
params: planningParamsSchema,
|
|
8
|
-
} as const satisfies NornWorkflowDefinition;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { NornWorkflowDefinition } from "@vimhead.dev/norn";
|
|
2
|
-
import { reviewParamsSchema } from "./schema.ts";
|
|
3
|
-
|
|
4
|
-
export const reviewWorkflow = {
|
|
5
|
-
isEntrypoint: false,
|
|
6
|
-
instructions: "Review the current repository boundary changes.",
|
|
7
|
-
params: reviewParamsSchema,
|
|
8
|
-
} as const satisfies NornWorkflowDefinition;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { NornWorkflowDefinition } from "@vimhead.dev/norn";
|
|
2
|
-
import { reviewRouterParamsSchema } from "./schema.ts";
|
|
3
|
-
|
|
4
|
-
export const reviewRouterWorkflow = {
|
|
5
|
-
isEntrypoint: false,
|
|
6
|
-
instructions: "Route implementation and review iterations based on the latest review decision.",
|
|
7
|
-
gate: {
|
|
8
|
-
enabled: true,
|
|
9
|
-
fields: ["decision", "summary"] as const,
|
|
10
|
-
},
|
|
11
|
-
params: reviewRouterParamsSchema,
|
|
12
|
-
} as const satisfies NornWorkflowDefinition;
|