@vimhead.dev/norn-cli 0.1.0-tip.35240723931.1 → 0.1.0-tip.35343816255.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 (88) hide show
  1. package/assets/README.md +2 -24
  2. package/assets/docs/README.md +3 -1
  3. package/assets/docs/agents.md +3 -3
  4. package/assets/docs/cli.md +7 -7
  5. package/assets/docs/composition.md +64 -34
  6. package/assets/docs/persistence.md +4 -2
  7. package/assets/docs/projects.md +3 -12
  8. package/assets/{setup → docs}/providers.md +4 -3
  9. package/assets/docs/resources.md +7 -7
  10. package/assets/docs/schemas.md +130 -0
  11. package/assets/docs/workflows.md +4 -3
  12. package/assets/examples/agent-then-analysis/README.md +1 -1
  13. package/assets/examples/agent-then-analysis/plugin.ts +16 -15
  14. package/assets/examples/caller-selected-continuation/README.md +75 -0
  15. package/assets/examples/caller-selected-continuation/caller.ts +47 -0
  16. package/assets/examples/caller-selected-continuation/input.json +9 -0
  17. package/assets/examples/caller-selected-continuation/norn.project.json +4 -0
  18. package/assets/examples/caller-selected-continuation/producer.ts +32 -0
  19. package/assets/examples/coordinating-multiple-agents/README.md +1 -1
  20. package/assets/examples/coordinating-multiple-agents/plugin.ts +10 -11
  21. package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +3 -4
  22. package/assets/examples/coordinating-multiple-agents/work-queue.ts +18 -18
  23. package/assets/examples/minimal-workflow/plugin.ts +2 -2
  24. package/assets/examples/shared-state/README.md +1 -1
  25. package/assets/examples/shared-state/plugin.ts +6 -6
  26. package/assets/examples/worktree-development-loop/README.md +105 -44
  27. package/assets/examples/worktree-development-loop/norn.project.json +1 -1
  28. package/assets/examples/worktree-development-loop/state.ts +6 -6
  29. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +1 -1
  30. package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +9 -9
  31. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +1 -1
  32. package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +7 -7
  33. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +1 -1
  34. package/assets/examples/worktree-development-loop/workflows/planning/schema.ts +7 -7
  35. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +1 -1
  36. package/assets/examples/worktree-development-loop/workflows/review/schema.ts +11 -11
  37. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +1 -1
  38. package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +5 -5
  39. package/assets/package.json +1 -1
  40. package/assets/packages/cli/src/cli.ts +15 -32
  41. package/assets/packages/cli/src/client.ts +0 -7
  42. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  43. package/assets/packages/cli/src/internal/agent-response-tool.ts +9 -10
  44. package/assets/packages/cli/src/internal/agents.ts +17 -16
  45. package/assets/packages/cli/src/internal/engine.ts +20 -18
  46. package/assets/packages/cli/src/internal/errors.ts +4 -10
  47. package/assets/packages/cli/src/internal/run-state.ts +4 -0
  48. package/assets/packages/cli/src/internal/run.ts +4 -7
  49. package/assets/packages/cli/src/internal/state-store.ts +22 -14
  50. package/assets/packages/cli/src/internal/workflow-registry.ts +27 -21
  51. package/assets/packages/cli/src/plugin-loader.ts +33 -45
  52. package/assets/packages/cli/src/resources.ts +14 -12
  53. package/assets/packages/core/src/workflow-transition.ts +4 -0
  54. package/assets/packages/sdk/src/api.ts +87 -131
  55. package/assets/packages/sdk/src/files.ts +11 -10
  56. package/assets/packages/sdk/src/index.ts +0 -1
  57. package/assets/packages/sdk/src/resources.ts +2 -2
  58. package/assets/packages/sdk/src/schema.ts +53 -29
  59. package/assets/packages/sdk/src/state-adapter.ts +4 -4
  60. package/assets/tests/workflow-ref.test.ts +121 -77
  61. package/dist/cli.js +20 -36
  62. package/dist/client.d.ts +0 -4
  63. package/dist/client.js +0 -3
  64. package/dist/generated-build-info.d.ts +2 -2
  65. package/dist/generated-build-info.js +2 -2
  66. package/dist/internal/agent-response-tool.d.ts +2 -2
  67. package/dist/internal/agent-response-tool.js +7 -7
  68. package/dist/internal/agents.d.ts +2 -2
  69. package/dist/internal/agents.js +10 -10
  70. package/dist/internal/engine.d.ts +1 -1
  71. package/dist/internal/engine.js +14 -12
  72. package/dist/internal/errors.d.ts +2 -1
  73. package/dist/internal/errors.js +5 -8
  74. package/dist/internal/run-state.js +4 -0
  75. package/dist/internal/run.d.ts +2 -2
  76. package/dist/internal/run.js +10 -6
  77. package/dist/internal/state-store.d.ts +8 -7
  78. package/dist/internal/state-store.js +15 -9
  79. package/dist/internal/workflow-registry.d.ts +14 -6
  80. package/dist/internal/workflow-registry.js +20 -15
  81. package/dist/plugin-loader.d.ts +15 -21
  82. package/dist/plugin-loader.js +35 -47
  83. package/dist/resources.d.ts +1 -1
  84. package/dist/resources.js +15 -13
  85. package/package.json +3 -4
  86. package/assets/packages/sdk/src/seer/config.ts +0 -62
  87. package/assets/packages/sdk/src/seer/index.ts +0 -7
  88. package/assets/setup/releases.md +0 -76
@@ -1,4 +1,5 @@
1
1
  import type { NornPluginDiagnostic } from "@vimhead.dev/norn";
2
+ import { AssertError } from "typebox/value";
2
3
  export declare class NornProjectLoadError extends Error {
3
4
  readonly code = "NORN_PROJECT_INVALID";
4
5
  readonly isComplete = false;
@@ -10,5 +11,5 @@ export declare class NornProjectLoadError extends Error {
10
11
  export declare class NornRunStoppedError extends Error {
11
12
  constructor();
12
13
  }
13
- export declare function zodErrorMessage(error: unknown): string;
14
+ export declare function schemaErrorMessage(error: AssertError): string;
14
15
  export declare function errorMessage(error: unknown): string;
@@ -1,4 +1,5 @@
1
1
  // src/internal/errors.ts
2
+ import { AssertError } from "typebox/value";
2
3
  var NornProjectLoadError = class extends Error {
3
4
  code = "NORN_PROJECT_INVALID";
4
5
  isComplete = false;
@@ -19,20 +20,16 @@ var NornRunStoppedError = class extends Error {
19
20
  this.name = "NornRunStoppedError";
20
21
  }
21
22
  };
22
- function zodErrorMessage(error) {
23
- const issues = error.issues;
24
- if (!Array.isArray(issues) || issues.length === 0) return errorMessage(error);
25
- return issues.slice(0, 3).map((issue) => {
26
- const path = Array.isArray(issue.path) && issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
27
- return `${path}${issue.message ?? "Invalid workflow response"}`;
28
- }).join("; ");
23
+ function schemaErrorMessage(error) {
24
+ return error.cause.errors.slice(0, 3).map((issue) => `${issue.instancePath || "/"}: ${issue.message}`).join("; ");
29
25
  }
30
26
  function errorMessage(error) {
27
+ if (error instanceof AssertError) return schemaErrorMessage(error);
31
28
  return error instanceof Error ? error.message : String(error);
32
29
  }
33
30
  export {
34
31
  NornProjectLoadError,
35
32
  NornRunStoppedError,
36
33
  errorMessage,
37
- zodErrorMessage
34
+ schemaErrorMessage
38
35
  };
@@ -41,6 +41,8 @@ async function writeTextAtomically(path, content) {
41
41
  // src/internal/run-state.ts
42
42
  import { createRunFileCoordinator } from "@vimhead.dev/norn/files";
43
43
  import { runCurrentRoot } from "./run-store.js";
44
+ import { jsonValueSchema } from "@vimhead.dev/norn/schema";
45
+ import { Value } from "typebox/value";
44
46
  var RUN_STATE_FILE_NAME = "run-state.json";
45
47
  var LEGACY_RUN_STATE_FILE_NAME = "runtime-state.json";
46
48
  function mergeInterruptedWorkflowParams(currentParams, params, fields) {
@@ -165,11 +167,13 @@ var NornRunStateStore = class _NornRunStateStore {
165
167
  await this.files.withExclusiveLock(this.path, async (path) => {
166
168
  const current = parseNornRunState(await readRunStateFile({ path, legacyPath: join(dirname2(this.path), LEGACY_RUN_STATE_FILE_NAME) }));
167
169
  const next = { ...current, ...patch, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
170
+ if (next.current?.params !== void 0) Value.Assert(jsonValueSchema, next.current.params);
168
171
  await writeJsonAtomically(path, next);
169
172
  this.state = next;
170
173
  });
171
174
  }
172
175
  async write() {
176
+ if (this.state.current?.params !== void 0) Value.Assert(jsonValueSchema, this.state.current.params);
173
177
  await mkdir2(dirname2(this.path), { recursive: true });
174
178
  await this.files.withExclusiveLock(this.path, (path) => writeJsonAtomically(path, this.state));
175
179
  }
@@ -1,5 +1,5 @@
1
1
  import type { CreateAgentSessionOptions } from "@earendil-works/pi-coding-agent";
2
- import type { NornAnyWorkflowDeclaration, NornProjectRun, NornRunOutcomeMetadata, NornWorkflowIsolationMode, NornWorkflowTarget, NornWorkflowTargetParamsInput, NornRun } from "@vimhead.dev/norn";
2
+ import type { NornAnyWorkflowDeclaration, NornProjectRun, NornRunOutcomeMetadata, NornWorkflowIsolationMode, NornRun } from "@vimhead.dev/norn";
3
3
  import type { NornAgentResponseCollector } from "./agent-response-tool.ts";
4
4
  import type { NornArtifacts } from "./artifacts.ts";
5
5
  import type { NornRunLogs } from "./logs.ts";
@@ -41,7 +41,7 @@ export declare class NornRunContext implements NornProjectRun {
41
41
  private createAgentRunner;
42
42
  path(relativePath: string): string;
43
43
  projectPath(relativePath: string): string;
44
- next<TWorkflow extends NornWorkflowTarget<any>>(workflow: TWorkflow, params: NornWorkflowTargetParamsInput<TWorkflow>): ReturnType<NornRun["next"]>;
44
+ next(workflowId: string, params: unknown): ReturnType<NornRun["next"]>;
45
45
  complete(metadata?: NornRunOutcomeMetadata): ReturnType<NornRun["complete"]>;
46
46
  fail(metadata: NornRunOutcomeMetadata & {
47
47
  readonly summary: string;
@@ -1,5 +1,13 @@
1
1
  // src/internal/run.ts
2
2
  import { isAbsolute, relative, resolve, sep } from "node:path";
3
+
4
+ // ../core/src/workflow-transition.ts
5
+ function createWorkflowTransition(input) {
6
+ if (typeof input.workflowId !== "string" || input.workflowId.trim().length === 0) throw new Error("Workflow transition requires a nonempty workflow ID");
7
+ return { type: "next", workflowId: input.workflowId, params: input.params };
8
+ }
9
+
10
+ // src/internal/run.ts
3
11
  import { NornAgentRunner } from "./agents.js";
4
12
  import { NornCommandRunner } from "./commands.js";
5
13
  var NornRunContext = class _NornRunContext {
@@ -74,12 +82,8 @@ var NornRunContext = class _NornRunContext {
74
82
  projectPath(relativePath) {
75
83
  return this.resolveFromRoot(this.projectRoot, this.projectRoot, relativePath);
76
84
  }
77
- next(workflow, params) {
78
- return {
79
- type: "next",
80
- workflowId: typeof workflow === "string" ? workflow : workflow.id,
81
- params
82
- };
85
+ next(workflowId, params) {
86
+ return createWorkflowTransition({ workflowId, params });
83
87
  }
84
88
  complete(metadata) {
85
89
  return { type: "complete", metadata };
@@ -1,10 +1,11 @@
1
- import type { NornWorkflowStateDefinition, NornWorkflowState } from "@vimhead.dev/norn";
1
+ import type { NornWorkflowState, NornWorkflowStateDefinition } from "@vimhead.dev/norn";
2
2
  import type { NornFileCoordinator } from "@vimhead.dev/norn/files";
3
+ import type { Static, TSchema } from "typebox";
3
4
  export declare class NornMemoryWorkflowState implements NornWorkflowState {
4
5
  private readonly data;
5
- get<T>(state: NornWorkflowStateDefinition<T>): Promise<T>;
6
- getOptional<T>(state: NornWorkflowStateDefinition<T>): Promise<T | undefined>;
7
- set<T>(state: NornWorkflowStateDefinition<T>, value: T): Promise<void>;
6
+ get<Schema extends TSchema>(state: NornWorkflowStateDefinition<Schema>): Promise<Static<Schema>>;
7
+ getOptional<Schema extends TSchema>(state: NornWorkflowStateDefinition<Schema>): Promise<Static<Schema> | undefined>;
8
+ set<Schema extends TSchema>(state: NornWorkflowStateDefinition<Schema>, value: NoInfer<Static<Schema>>): Promise<void>;
8
9
  }
9
10
  export declare class NornJsonWorkflowState implements NornWorkflowState {
10
11
  private readonly input;
@@ -15,8 +16,8 @@ export declare class NornJsonWorkflowState implements NornWorkflowState {
15
16
  readonly coordinateMutation: <T>(path: string, operation: () => Promise<T>) => Promise<T>;
16
17
  });
17
18
  initialize(mode: "create" | "open"): Promise<void>;
18
- get<T>(state: NornWorkflowStateDefinition<T>): Promise<T>;
19
- getOptional<T>(state: NornWorkflowStateDefinition<T>): Promise<T | undefined>;
20
- set<T>(state: NornWorkflowStateDefinition<T>, value: T): Promise<void>;
19
+ get<Schema extends TSchema>(state: NornWorkflowStateDefinition<Schema>): Promise<Static<Schema>>;
20
+ getOptional<Schema extends TSchema>(state: NornWorkflowStateDefinition<Schema>): Promise<Static<Schema> | undefined>;
21
+ set<Schema extends TSchema>(state: NornWorkflowStateDefinition<Schema>, value: NoInfer<Static<Schema>>): Promise<void>;
21
22
  private readStateFile;
22
23
  }
@@ -1,5 +1,7 @@
1
- // src/internal/state-store.ts
2
- import { readFile } from "node:fs/promises";
1
+ // ../core/src/atomic-files.ts
2
+ import { randomUUID } from "node:crypto";
3
+ import { chmod, mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
4
+ import { dirname } from "node:path";
3
5
 
4
6
  // ../core/src/errors.ts
5
7
  function isNodeError(error) {
@@ -7,9 +9,6 @@ function isNodeError(error) {
7
9
  }
8
10
 
9
11
  // ../core/src/atomic-files.ts
10
- import { randomUUID } from "node:crypto";
11
- import { chmod, mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
12
- import { dirname } from "node:path";
13
12
  async function writeJsonAtomically(path, value) {
14
13
  await writeTextAtomically(path, `${JSON.stringify(value, null, 2)}
15
14
  `);
@@ -34,6 +33,13 @@ async function writeTextAtomically(path, content) {
34
33
  }
35
34
 
36
35
  // src/internal/state-store.ts
36
+ import { jsonValueSchema } from "@vimhead.dev/norn/schema";
37
+ import { readFile } from "node:fs/promises";
38
+ import { Value } from "typebox/value";
39
+ function copyStateValue(schema, value) {
40
+ Value.Assert(jsonValueSchema, value);
41
+ return structuredClone(Value.Parse(schema, value));
42
+ }
37
43
  var NornMemoryWorkflowState = class {
38
44
  data = /* @__PURE__ */ new Map();
39
45
  async get(state) {
@@ -43,10 +49,10 @@ var NornMemoryWorkflowState = class {
43
49
  }
44
50
  async getOptional(state) {
45
51
  if (!this.data.has(state.id)) return void 0;
46
- return state.schema.parse(this.data.get(state.id));
52
+ return copyStateValue(state.schema, this.data.get(state.id));
47
53
  }
48
54
  async set(state, value) {
49
- this.data.set(state.id, state.schema.parse(value));
55
+ this.data.set(state.id, copyStateValue(state.schema, value));
50
56
  }
51
57
  };
52
58
  var NornJsonWorkflowState = class {
@@ -74,10 +80,10 @@ var NornJsonWorkflowState = class {
74
80
  async getOptional(state) {
75
81
  const data = await this.input.files.withExclusiveLock(this.stateFile, (path) => this.readStateFile(path));
76
82
  if (!Object.prototype.hasOwnProperty.call(data, state.id)) return void 0;
77
- return state.schema.parse(data[state.id]);
83
+ return copyStateValue(state.schema, data[state.id]);
78
84
  }
79
85
  async set(state, value) {
80
- const parsedValue = state.schema.parse(value);
86
+ const parsedValue = copyStateValue(state.schema, value);
81
87
  await this.input.coordinateMutation(this.stateFile, () => this.input.files.withExclusiveLock(this.stateFile, async (path) => {
82
88
  const data = await this.readStateFile(path);
83
89
  Object.defineProperty(data, state.id, { value: parsedValue, enumerable: true, configurable: true, writable: true });
@@ -1,12 +1,21 @@
1
- import { z } from "zod";
2
- import { type NornAnyWorkflowDeclaration, type NornInspectedWorkflowInfo, type NornRegisteredWorkflowInfo, type NornRunComplete, type NornDispose, type NornRunFail, type NornWorkflowImplementation, type NornRunNext, type NornRunFor, type NornWorkflowPluginInfo } from "@vimhead.dev/norn";
1
+ import { type NornAnyWorkflowDeclaration, type NornDispose, type NornInspectedWorkflowInfo, type NornRegisteredWorkflowInfo, type NornRunComplete, type NornRunFail, type NornRunFor, type NornRunNext, type NornWorkflowImplementation, type NornWorkflowPluginInfo } from "@vimhead.dev/norn";
2
+ import { type TSchema } from "typebox";
3
3
  export type NornRegisteredWorkflow = {
4
4
  workflow: NornAnyWorkflowDeclaration;
5
5
  implementation: NornWorkflowImplementation<NornAnyWorkflowDeclaration, unknown>;
6
- configSchema?: z.ZodType;
7
- config: unknown;
6
+ configuration?: NornRegisteredConfiguration;
8
7
  plugin?: NornWorkflowPluginInfo;
9
8
  };
9
+ export type NornRegisteredConfiguration = {
10
+ readonly schema: TSchema;
11
+ readonly input: unknown;
12
+ readonly value: unknown;
13
+ };
14
+ export declare function decodePluginConfiguration(input: {
15
+ readonly pluginId: string;
16
+ readonly schema: TSchema | undefined;
17
+ readonly value: unknown;
18
+ }): NornRegisteredConfiguration | undefined;
10
19
  export type NornWorkflowStepResult = NornRunNext | {
11
20
  readonly type: "complete";
12
21
  readonly workflow: NornAnyWorkflowDeclaration;
@@ -20,8 +29,7 @@ export declare class NornWorkflowRegistry {
20
29
  private readonly entries;
21
30
  register<TWorkflow extends NornAnyWorkflowDeclaration>(workflow: TWorkflow, implementation: NornWorkflowImplementation<TWorkflow, unknown>, metadata?: {
22
31
  readonly plugin?: NornWorkflowPluginInfo;
23
- readonly configSchema?: z.ZodType;
24
- readonly config?: unknown;
32
+ readonly configuration?: NornRegisteredConfiguration;
25
33
  }): NornDispose;
26
34
  list(options?: {
27
35
  readonly entrypointsOnly?: boolean;
@@ -1,7 +1,15 @@
1
1
  // src/internal/workflow-registry.ts
2
- import { z } from "zod";
3
2
  import { isWorkflowComplete, isWorkflowFail, isWorkflowNext } from "@vimhead.dev/norn";
4
- import { assertWorkflowMetadata, isPlainObject, schemaShape, schemaType, unwrapSchema } from "@vimhead.dev/norn/schema";
3
+ import { assertWorkflowMetadata, inspectSchema, isPlainObject, schemaShape, schemaType, unwrapSchema } from "@vimhead.dev/norn/schema";
4
+ import { Value } from "typebox/value";
5
+ function decodePluginConfiguration(input) {
6
+ if (!input.schema) {
7
+ if (input.value !== void 0) throw new Error(`Norn config provided for plugin without config schema: ${input.pluginId}`);
8
+ return void 0;
9
+ }
10
+ const value = defaultConfigInput(input.schema, input.value);
11
+ return { schema: input.schema, input: value, value: Value.Decode(input.schema, value) };
12
+ }
5
13
  var NornWorkflowRegistry = class {
6
14
  entries = /* @__PURE__ */ new Map();
7
15
  register(workflow, implementation, metadata = {}) {
@@ -9,8 +17,7 @@ var NornWorkflowRegistry = class {
9
17
  const entry = {
10
18
  workflow,
11
19
  implementation,
12
- configSchema: metadata.configSchema,
13
- config: metadata.configSchema ? metadata.configSchema.parse(defaultConfigInput(metadata.configSchema, metadata.config)) : void 0,
20
+ configuration: metadata.configuration,
14
21
  plugin: metadata.plugin
15
22
  };
16
23
  assertWorkflowMetadata(workflow);
@@ -38,7 +45,7 @@ var NornWorkflowRegistry = class {
38
45
  const entry = this.entries.get(workflow.id);
39
46
  if (!entry) throw new Error(`Unknown workflow: ${workflow.id}`);
40
47
  if (!workflow.gate) throw new Error(`Workflow is not gated: ${workflow.id}`);
41
- const parsedParams = workflow.params.parse(params);
48
+ const parsedParams = Value.Decode(workflow.params, params);
42
49
  const parsedConfig = parseExecutionConfig(entry, configOverride);
43
50
  const description = await entry.implementation.gate?.describe(run, parsedParams, parsedConfig);
44
51
  return validateGateDescription(description ?? workflow.id, workflow.id);
@@ -46,7 +53,7 @@ var NornWorkflowRegistry = class {
46
53
  async execute(workflow, run, params, configOverride) {
47
54
  const entry = this.entries.get(workflow.id);
48
55
  if (!entry) throw new Error(`Unknown workflow: ${workflow.id}`);
49
- const parsedParams = workflow.params.parse(params);
56
+ const parsedParams = Value.Decode(workflow.params, params);
50
57
  const parsedConfig = parseExecutionConfig(entry, configOverride);
51
58
  const result = await entry.implementation.execute(run, parsedParams, parsedConfig);
52
59
  if (isWorkflowNext(result)) return result;
@@ -64,13 +71,13 @@ function defaultConfigInput(configSchema, config) {
64
71
  }
65
72
  function parseExecutionConfig(entry, configOverride) {
66
73
  const pluginConfigOverride = pluginConfigOverrideInput(entry, configOverride);
67
- if (!entry.configSchema) {
74
+ if (!entry.configuration) {
68
75
  if (pluginConfigOverride !== void 0) throw new Error(`Run config override provided for plugin without config schema: ${entry.plugin?.id ?? entry.workflow.id}`);
69
76
  return void 0;
70
77
  }
71
- if (pluginConfigOverride === void 0) return entry.config;
72
- const rawConfig = isPlainObject(entry.config) && isPlainObject(pluginConfigOverride) ? deepMerge(entry.config, pluginConfigOverride) : pluginConfigOverride;
73
- return entry.configSchema.parse(rawConfig);
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);
74
81
  }
75
82
  function pluginConfigOverrideInput(entry, configOverride) {
76
83
  if (configOverride === void 0) return void 0;
@@ -105,7 +112,7 @@ function validateGateDescription(description, workflowId) {
105
112
  function inspectedWorkflowInfo(entry) {
106
113
  return {
107
114
  ...workflowInfo(entry),
108
- paramsSchema: jsonSchema(entry.workflow.params),
115
+ paramsSchema: inspectSchema(entry.workflow.params),
109
116
  gate: gateInfo(entry.workflow)
110
117
  };
111
118
  }
@@ -121,9 +128,7 @@ function workflowInfo(entry) {
121
128
  function gateInfo(workflow) {
122
129
  return workflow.gate ? { enabled: true, fields: workflow.gate.fields } : null;
123
130
  }
124
- function jsonSchema(schema) {
125
- return z.toJSONSchema(schema, { io: "input" });
126
- }
127
131
  export {
128
- NornWorkflowRegistry
132
+ NornWorkflowRegistry,
133
+ decodePluginConfiguration
129
134
  };
@@ -1,27 +1,22 @@
1
- import { z } from "zod";
2
1
  import { type NornProjectInspection, type NornProjectPluginInfo, type NornWorkflowCatalogInfo, type NornWorkflowInspection, type NornWorkflowPlugin } from "@vimhead.dev/norn";
2
+ import * as typeboxModule from "typebox";
3
+ import { type StaticDecode } from "typebox";
3
4
  import { NornMemoryWorkflowState } from "./internal/state-store.ts";
4
5
  import { NornWorkflowRegistry, type NornRegisteredWorkflow } from "./internal/workflow-registry.ts";
5
- import { type NornResolvedSeerModeConfig } from "@vimhead.dev/norn/seer";
6
6
  export declare const NORN_PROJECT_FILE_NAME = "norn.project.json";
7
- declare const nornConfigSchema: z.ZodObject<{
8
- plugins: z.ZodDefault<z.ZodArray<z.ZodString>>;
9
- includes: z.ZodDefault<z.ZodArray<z.ZodString>>;
10
- config: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
11
- }, z.core.$strip>;
12
- declare const nornProjectConfigSchema: z.ZodObject<{
13
- plugins: z.ZodDefault<z.ZodArray<z.ZodString>>;
14
- includes: z.ZodDefault<z.ZodArray<z.ZodString>>;
15
- config: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
16
- version: z.ZodDefault<z.ZodLiteral<1>>;
17
- seerMode: z.ZodOptional<z.ZodObject<{
18
- writableRoots: z.ZodArray<z.ZodString>;
19
- }, z.core.$strip>>;
20
- }, z.core.$strip>;
21
- type NornConfig = z.output<typeof nornConfigSchema> & {
22
- readonly seerMode?: never;
23
- };
24
- type NornProjectConfig = z.output<typeof nornProjectConfigSchema>;
7
+ declare const nornConfigSchema: typeboxModule.TObject<{
8
+ plugins: typeboxModule.TArray<typeboxModule.TString>;
9
+ includes: typeboxModule.TArray<typeboxModule.TString>;
10
+ config: typeboxModule.TRecord<"^.*$", typeboxModule.TUnknown>;
11
+ }>;
12
+ declare const nornProjectConfigSchema: typeboxModule.TObject<{
13
+ version: typeboxModule.TLiteral<1>;
14
+ plugins: typeboxModule.TArray<typeboxModule.TString>;
15
+ includes: typeboxModule.TArray<typeboxModule.TString>;
16
+ config: typeboxModule.TRecord<"^.*$", typeboxModule.TUnknown>;
17
+ }>;
18
+ type NornConfig = StaticDecode<typeof nornConfigSchema>;
19
+ type NornProjectConfig = StaticDecode<typeof nornProjectConfigSchema>;
25
20
  type NornConfigFile = {
26
21
  readonly path: string;
27
22
  readonly root: string;
@@ -36,7 +31,6 @@ export type NornProject = {
36
31
  readonly config: NornProjectConfig;
37
32
  readonly configFiles: readonly NornConfigFile[];
38
33
  readonly projectConfig: Record<string, unknown>;
39
- readonly seerMode?: NornResolvedSeerModeConfig;
40
34
  };
41
35
  export type NornLoadedProject = NornProject & {
42
36
  readonly plugins: readonly NornWorkflowPlugin[];
@@ -1,17 +1,6 @@
1
1
  // src/plugin-loader.ts
2
- import { access, readFile, readdir } from "node:fs/promises";
3
- import { dirname, isAbsolute, join, resolve, sep } from "node:path";
4
- import { pathToFileURL } from "node:url";
5
- import { createJiti } from "jiti/static";
6
- import * as typeboxModule from "typebox";
7
- import { z } from "zod";
8
- import * as zodModule from "zod";
9
2
  import * as nornModule from "@vimhead.dev/norn";
10
- import * as nornFilesModule from "@vimhead.dev/norn/files";
11
- import * as nornSchemaModule from "@vimhead.dev/norn/schema";
12
- import * as nornSeerModule from "@vimhead.dev/norn/seer";
13
3
  import { isWorkflowPlugin } from "@vimhead.dev/norn";
14
- import { errorMessage, NornProjectLoadError } from "./internal/errors.js";
15
4
 
16
5
  // ../core/src/errors.ts
17
6
  function isNodeError(error) {
@@ -19,22 +8,31 @@ function isNodeError(error) {
19
8
  }
20
9
 
21
10
  // src/plugin-loader.ts
11
+ import * as nornFilesModule from "@vimhead.dev/norn/files";
12
+ import * as nornSchemaModule from "@vimhead.dev/norn/schema";
13
+ import { inspectSchema } from "@vimhead.dev/norn/schema";
14
+ import { createJiti } from "jiti/static";
15
+ import { access, readdir, readFile } from "node:fs/promises";
16
+ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
17
+ import { pathToFileURL } from "node:url";
18
+ import * as typeboxModule from "typebox";
19
+ import { Type } from "typebox";
20
+ import * as typeboxCompileModule from "typebox/compile";
21
+ import * as typeboxSchemaModule from "typebox/schema";
22
+ import * as typeboxValueModule from "typebox/value";
23
+ import { AssertError, Value } from "typebox/value";
24
+ import { errorMessage, NornProjectLoadError } from "./internal/errors.js";
22
25
  import { NornMemoryWorkflowState } from "./internal/state-store.js";
23
- import { NornWorkflowRegistry } from "./internal/workflow-registry.js";
24
- import { schemaType, unwrapSchema } from "@vimhead.dev/norn/schema";
25
- import { resolveSeerModeConfig } from "@vimhead.dev/norn/seer";
26
+ import { decodePluginConfiguration, NornWorkflowRegistry } from "./internal/workflow-registry.js";
26
27
  var NORN_PROJECT_FILE_NAME = "norn.project.json";
27
- var seerModeConfigSchema = z.object({
28
- writableRoots: z.array(z.string().min(1)).min(1)
28
+ var nornConfigSchema = Type.Object({
29
+ plugins: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
30
+ includes: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
31
+ config: Type.Record(Type.String(), Type.Unknown(), { default: {} })
29
32
  });
30
- var nornConfigSchema = z.object({
31
- plugins: z.array(z.string().min(1)).default([]),
32
- includes: z.array(z.string().min(1)).default([]),
33
- config: z.record(z.string(), z.unknown()).default({})
34
- });
35
- var nornProjectConfigSchema = nornConfigSchema.extend({
36
- version: z.literal(1).default(1),
37
- seerMode: seerModeConfigSchema.optional()
33
+ var nornProjectConfigSchema = Type.Object({
34
+ ...nornConfigSchema.properties,
35
+ version: Type.Literal(1, { default: 1 })
38
36
  });
39
37
  async function loadNornProject(cwd) {
40
38
  const { project, diagnostics } = await collectNornProject(cwd);
@@ -73,8 +71,7 @@ function buildProjectInfo(project) {
73
71
  configPath: project.configPath,
74
72
  configRoot: project.configRoot,
75
73
  configFiles: project.configFiles.map((file) => file.path),
76
- plugins: project.pluginInfos,
77
- seerMode: project.seerMode ?? null
74
+ plugins: project.pluginInfos
78
75
  };
79
76
  }
80
77
  async function findNornProject(cwd) {
@@ -89,8 +86,7 @@ async function findNornProject(cwd) {
89
86
  configRoot: projectRootConfig.root,
90
87
  config: projectRootConfig.config,
91
88
  configFiles,
92
- projectConfig: mergeProjectConfig(configFiles),
93
- seerMode: resolveSeerModeConfig({ configPath: projectRootConfig.path, configRoot: projectRootConfig.root, seerMode: projectRootConfig.config.seerMode })
89
+ projectConfig: mergeProjectConfig(configFiles)
94
90
  };
95
91
  }
96
92
  async function findNearestNornProject(cwd) {
@@ -127,11 +123,11 @@ async function loadNornConfigTree(configFile, visitedPaths) {
127
123
  return [configFile, ...descendants.flat()];
128
124
  }
129
125
  async function readNornProjectConfigFile(path) {
130
- const config = nornProjectConfigSchema.parse(JSON.parse(await readFile(path, "utf8")));
126
+ const config = Value.Parse(nornProjectConfigSchema, Value.Default(nornProjectConfigSchema, JSON.parse(await readFile(path, "utf8"))));
131
127
  return { path, root: dirname(path), config };
132
128
  }
133
129
  async function readNornConfigFile(path) {
134
- const config = nornConfigSchema.parse(JSON.parse(await readFile(path, "utf8")));
130
+ const config = Value.Parse(nornConfigSchema, Value.Default(nornConfigSchema, JSON.parse(await readFile(path, "utf8"))));
135
131
  return { path, root: dirname(path), config };
136
132
  }
137
133
  function createProjectConfigEntry(projectFile) {
@@ -227,15 +223,14 @@ function registerProjectPlugin(input) {
227
223
  let stage = "config";
228
224
  try {
229
225
  const configInput = input.project.projectConfig[plugin.manifest.id];
230
- if (!plugin.manifest.config && configInput !== void 0) throw new Error(`Norn config provided for plugin without config schema: ${plugin.manifest.id}`);
231
- const config = plugin.manifest.config ? plugin.manifest.config.parse(defaultConfigInput(plugin.manifest.config, configInput)) : void 0;
226
+ const configuration = decodePluginConfiguration({ pluginId: plugin.manifest.id, schema: plugin.manifest.config, value: configInput });
232
227
  stage = "schema";
233
228
  const info = {
234
229
  id: plugin.manifest.id,
235
230
  path: source.pluginPath,
236
231
  configPath: source.configPath,
237
- configSchema: plugin.manifest.config ? z.toJSONSchema(plugin.manifest.config, { io: "input" }) : null,
238
- config
232
+ configSchema: plugin.manifest.config ? inspectSchema(plugin.manifest.config) : null,
233
+ config: configuration?.value
239
234
  };
240
235
  stage = "implementation";
241
236
  const implementation = typeof plugin.implementation === "function" ? plugin.implementation({ cwd: input.project.cwd, state: input.state }) : plugin.implementation;
@@ -247,9 +242,9 @@ function registerProjectPlugin(input) {
247
242
  stage = "duplicate";
248
243
  if (input.registry.workflowById(workflow.id)) throw new Error(`Workflow already registered: ${workflow.id}`);
249
244
  stage = "declaration";
250
- unregister.push(input.registry.register(workflow, workflowImplementation, { plugin: workflowPluginInfo(info), configSchema: plugin.manifest.config, config }));
245
+ unregister.push(input.registry.register(workflow, workflowImplementation, { plugin: workflowPluginInfo(info), configuration }));
251
246
  } catch (error) {
252
- diagnostics.push(createPluginDiagnostic({ source, workflowId: workflow.id, stage: error instanceof z.ZodError ? "config" : stage, error }));
247
+ diagnostics.push(createPluginDiagnostic({ source, workflowId: workflow.id, stage, error }));
253
248
  }
254
249
  }
255
250
  if (diagnostics.length === 0) return { loaded: { plugin, info }, diagnostics };
@@ -265,11 +260,7 @@ function createPluginDiagnostic(input) {
265
260
  workflowId: input.workflowId,
266
261
  stage: input.stage,
267
262
  message: errorMessage(input.error),
268
- issues: input.error instanceof z.ZodError ? input.error.issues.map((issue) => ({
269
- path: issue.path.map((part) => typeof part === "symbol" ? String(part) : part),
270
- code: issue.code,
271
- message: issue.message
272
- })) : []
263
+ issues: input.error instanceof AssertError ? input.error.cause.errors : []
273
264
  };
274
265
  }
275
266
  function workflowPluginInfo(info) {
@@ -280,9 +271,10 @@ function nornWorkflowVirtualModules() {
280
271
  "@vimhead.dev/norn": nornModule,
281
272
  "@vimhead.dev/norn/files": nornFilesModule,
282
273
  "@vimhead.dev/norn/schema": nornSchemaModule,
283
- "@vimhead.dev/norn/seer": nornSeerModule,
284
274
  typebox: typeboxModule,
285
- zod: zodModule
275
+ "typebox/value": typeboxValueModule,
276
+ "typebox/compile": typeboxCompileModule,
277
+ "typebox/schema": typeboxSchemaModule
286
278
  };
287
279
  }
288
280
  function mergeProjectConfig(configFiles) {
@@ -315,10 +307,6 @@ function mergeProjectConfigObjects(base, override) {
315
307
  function isPlainObject(value) {
316
308
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
317
309
  }
318
- function defaultConfigInput(configSchema, config) {
319
- if (config !== void 0) return config;
320
- return schemaType(unwrapSchema(configSchema)) === "object" ? {} : void 0;
321
- }
322
310
  async function expandIncludePath(configRoot, includePath) {
323
311
  const resolvedIncludePath = resolveConfigPath(configRoot, includePath);
324
312
  if (!hasGlobSegment(includePath)) return [resolvedIncludePath];
@@ -1,5 +1,5 @@
1
- import { type NornFileCoordinator } from "@vimhead.dev/norn/files";
2
1
  import type { NornResourceDefinition, NornResources } from "@vimhead.dev/norn";
2
+ import { type NornFileCoordinator } from "@vimhead.dev/norn/files";
3
3
  export declare class NornRunResources implements NornResources {
4
4
  private readonly runRoot;
5
5
  readonly files: NornFileCoordinator;
package/dist/resources.js CHANGED
@@ -1,9 +1,7 @@
1
- // src/resources.ts
2
- import { mkdir as mkdir2, readFile } from "node:fs/promises";
3
- import { join, resolve } from "node:path";
4
- import { isDeepStrictEqual } from "node:util";
5
- import { z } from "zod";
6
- import { createRunFileCoordinator } from "@vimhead.dev/norn/files";
1
+ // ../core/src/atomic-files.ts
2
+ import { randomUUID } from "node:crypto";
3
+ import { chmod, mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
4
+ import { dirname } from "node:path";
7
5
 
8
6
  // ../core/src/errors.ts
9
7
  function isNodeError(error) {
@@ -11,9 +9,6 @@ function isNodeError(error) {
11
9
  }
12
10
 
13
11
  // ../core/src/atomic-files.ts
14
- import { randomUUID } from "node:crypto";
15
- import { chmod, mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
16
- import { dirname } from "node:path";
17
12
  async function writeJsonAtomically(path, value) {
18
13
  await writeTextAtomically(path, `${JSON.stringify(value, null, 2)}
19
14
  `);
@@ -38,8 +33,15 @@ async function writeTextAtomically(path, content) {
38
33
  }
39
34
 
40
35
  // src/resources.ts
41
- var definitionSchema = z.strictObject({ name: z.string(), kind: z.string().min(1), configuration: z.json() });
42
- var recordSchema = definitionSchema.extend({ isInitialized: z.boolean() });
36
+ import { createRunFileCoordinator } from "@vimhead.dev/norn/files";
37
+ import { jsonValueSchema } from "@vimhead.dev/norn/schema";
38
+ import { mkdir as mkdir2, readFile } from "node:fs/promises";
39
+ import { join, resolve } from "node:path";
40
+ import { isDeepStrictEqual } from "node:util";
41
+ import { Type } from "typebox";
42
+ import { Value } from "typebox/value";
43
+ var definitionSchema = Type.Object({ name: Type.String(), kind: Type.String({ minLength: 1 }), configuration: jsonValueSchema }, { additionalProperties: false });
44
+ var recordSchema = Type.Object({ ...definitionSchema.properties, isInitialized: Type.Boolean() }, { additionalProperties: false });
43
45
  var NornRunResources = class _NornRunResources {
44
46
  constructor(runRoot) {
45
47
  this.runRoot = runRoot;
@@ -55,7 +57,7 @@ var NornRunResources = class _NornRunResources {
55
57
  }
56
58
  async ensure(definition) {
57
59
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(definition.name)) throw new Error(`Invalid resource name: ${definition.name}`);
58
- const identity = definitionSchema.parse({ name: definition.name, kind: definition.kind, configuration: definition.configuration });
60
+ const identity = structuredClone(Value.Parse(definitionSchema, { name: definition.name, kind: definition.kind, configuration: definition.configuration }));
59
61
  const existing = this.entries.get(definition.name);
60
62
  if (existing) {
61
63
  if (!isDeepStrictEqual(existing.definition, identity)) throw new Error(`Incompatible resource definition: ${definition.name}`);
@@ -77,7 +79,7 @@ var NornRunResources = class _NornRunResources {
77
79
  return this.files.withExclusiveLock(join(directory, "definition.json"), async (path) => {
78
80
  let stored;
79
81
  try {
80
- stored = recordSchema.parse(JSON.parse(await readFile(path, "utf8")));
82
+ stored = Value.Parse(recordSchema, JSON.parse(await readFile(path, "utf8")));
81
83
  } catch (error) {
82
84
  if (!isNodeError(error) || error.code !== "ENOENT") throw error;
83
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vimhead.dev/norn-cli",
3
- "version": "0.1.0-tip.35240723931.1",
3
+ "version": "0.1.0-tip.35343816255.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",
@@ -42,9 +42,8 @@
42
42
  "dependencies": {
43
43
  "@earendil-works/pi-coding-agent": "0.85.1",
44
44
  "jiti": "^2.7.0",
45
- "typebox": "^1.3.17",
46
- "zod": "^4.4.3",
47
- "@vimhead.dev/norn": "0.1.0-tip.35240723931.1"
45
+ "typebox": "^1.3.33",
46
+ "@vimhead.dev/norn": "0.1.0-tip.35343816255.1"
48
47
  },
49
48
  "scripts": {
50
49
  "build": "node ../../scripts/build-package.ts"