@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,6 +1,8 @@
1
1
  // src/workflow-loader.ts
2
2
  import * as nornModule from "@vimhead.dev/norn";
3
- import { isWorkflowDeclaration } from "@vimhead.dev/norn";
3
+ import {
4
+ isWorkflowDeclaration
5
+ } from "@vimhead.dev/norn";
4
6
 
5
7
  // ../core/src/errors.ts
6
8
  function isNodeError(error) {
@@ -21,14 +23,21 @@ import * as typeboxSchemaModule from "typebox/schema";
21
23
  import * as typeboxValueModule from "typebox/value";
22
24
  import { AssertError, Value } from "typebox/value";
23
25
  import { errorMessage, NornProjectLoadError } from "./internal/errors.js";
24
- import { assertCompatibleConfigurationOwners, assertWorkflowDefinition, NornWorkflowRegistry } from "./internal/workflow-registry.js";
26
+ import {
27
+ assertCompatibleConfigurationOwners,
28
+ assertWorkflowDefinition,
29
+ NornWorkflowRegistry
30
+ } from "./internal/workflow-registry.js";
25
31
  var NORN_PROJECT_FILE_NAME = "norn.project.json";
26
32
  var nornConfigSchema = Type.Object({
27
33
  workflows: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
28
34
  includes: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
29
35
  config: Type.Record(Type.String(), Type.Unknown(), { default: {} })
30
36
  });
31
- var nornProjectConfigSchema = Type.Object({ ...nornConfigSchema.properties, version: Type.Literal(1, { default: 1 }) });
37
+ var nornProjectConfigSchema = Type.Object({
38
+ ...nornConfigSchema.properties,
39
+ version: Type.Literal(1, { default: 1 })
40
+ });
32
41
  async function loadNornProject(cwd) {
33
42
  const { project, diagnostics } = await collectNornProject(cwd);
34
43
  if (diagnostics.length) throw new NornProjectLoadError({ diagnostics });
@@ -36,20 +45,43 @@ async function loadNornProject(cwd) {
36
45
  }
37
46
  async function discoverNornProject(cwd) {
38
47
  const { project, diagnostics } = await collectNornProject(cwd);
39
- return { project: buildProjectInfo(project), workflows: project.registry.list(), isComplete: diagnostics.length === 0, diagnostics };
48
+ return {
49
+ project: buildProjectInfo(project),
50
+ workflows: project.registry.list(),
51
+ isComplete: diagnostics.length === 0,
52
+ diagnostics
53
+ };
40
54
  }
41
55
  async function inspectNornWorkflow(input) {
42
56
  const { project, diagnostics } = await collectNornProject(input.cwd);
43
57
  const summary = project.registry.list().find((workflow) => workflow.id === input.workflowId);
44
58
  if (!summary) {
45
- if (!diagnostics.length) throw new Error(`Unknown workflow: ${input.workflowId}`);
59
+ if (!diagnostics.length)
60
+ throw new Error(`Unknown workflow: ${input.workflowId}`);
46
61
  return { workflow: null, isComplete: false, diagnostics };
47
62
  }
48
63
  try {
49
- return { workflow: project.registry.inspect(input.workflowId) ?? null, isComplete: diagnostics.length === 0, diagnostics };
64
+ return {
65
+ workflow: project.registry.inspect(input.workflowId) ?? null,
66
+ isComplete: diagnostics.length === 0,
67
+ diagnostics
68
+ };
50
69
  } catch (error) {
51
70
  if (!summary.source) throw error;
52
- return { workflow: null, isComplete: false, diagnostics: [...diagnostics, createDiagnostic({ source: summary.source, workflowId: summary.id, scopeId: summary.scope?.id ?? null, stage: "schema", error })] };
71
+ return {
72
+ workflow: null,
73
+ isComplete: false,
74
+ diagnostics: [
75
+ ...diagnostics,
76
+ createDiagnostic({
77
+ source: summary.source,
78
+ workflowId: summary.id,
79
+ scopeId: summary.scope?.id ?? null,
80
+ stage: "schema",
81
+ error
82
+ })
83
+ ]
84
+ };
53
85
  }
54
86
  }
55
87
  function buildProjectInfo(project) {
@@ -66,7 +98,10 @@ function buildProjectInfo(project) {
66
98
  }
67
99
  async function findNornProject(cwd) {
68
100
  const projectPath = await findNearestNornProject(cwd);
69
- const projectFile = await readConfigFile({ path: projectPath, isProject: true });
101
+ const projectFile = await readConfigFile({
102
+ path: projectPath,
103
+ isProject: true
104
+ });
70
105
  const configFiles = await loadConfigTree(projectFile, /* @__PURE__ */ new Set());
71
106
  return {
72
107
  cwd: resolve(cwd),
@@ -90,7 +125,8 @@ async function findNearestNornProject(cwd) {
90
125
  if (!isNodeError(error) || error.code !== "ENOENT") throw error;
91
126
  }
92
127
  const parent = dirname(current);
93
- if (parent === current) throw new Error(`Could not find ${NORN_PROJECT_FILE_NAME} from ${cwd}`);
128
+ if (parent === current)
129
+ throw new Error(`Could not find ${NORN_PROJECT_FILE_NAME} from ${cwd}`);
94
130
  current = parent;
95
131
  }
96
132
  }
@@ -99,16 +135,29 @@ async function loadConfigTree(file, visited) {
99
135
  visited.add(file.path);
100
136
  const files = [file];
101
137
  for (const include of file.config.includes) {
102
- for (const path of await expandIncludePath(file.root, include)) files.push(...await loadConfigTree(await readConfigFile({ path, isProject: false }), visited));
138
+ for (const path of await expandIncludePath(file.root, include))
139
+ files.push(
140
+ ...await loadConfigTree(
141
+ await readConfigFile({ path, isProject: false }),
142
+ visited
143
+ )
144
+ );
103
145
  }
104
146
  return files;
105
147
  }
106
148
  async function readConfigFile(input) {
107
149
  const raw = JSON.parse(await readFile(input.path, "utf8"));
108
- if (isPlainObject(raw) && Object.hasOwn(raw, "plugins")) throw new Error(`Replace plugins with workflows in ${input.path}; registration modules must export workflow arrays`);
150
+ if (isPlainObject(raw) && Object.hasOwn(raw, "plugins"))
151
+ throw new Error(
152
+ `Replace plugins with workflows in ${input.path}; registration modules must export workflow arrays`
153
+ );
109
154
  const schema = input.isProject ? nornProjectConfigSchema : nornConfigSchema;
110
155
  const config = Value.Parse(schema, Value.Default(schema, raw));
111
- return { path: input.path, root: dirname(input.path), config: { ...config, version: 1 } };
156
+ return {
157
+ path: input.path,
158
+ root: dirname(input.path),
159
+ config: { ...config, version: 1 }
160
+ };
112
161
  }
113
162
  async function collectNornProject(cwd) {
114
163
  const project = await findNornProject(cwd);
@@ -133,9 +182,23 @@ async function collectNornProject(cwd) {
133
182
  stage = "declaration";
134
183
  assertWorkflowDefinition(workflow);
135
184
  stage = "config";
136
- unregister.push(registry.register({ workflow, config: project.projectConfig, source: candidate.source }));
185
+ unregister.push(
186
+ registry.register({
187
+ workflow,
188
+ config: project.projectConfig,
189
+ source: candidate.source
190
+ })
191
+ );
137
192
  } catch (error) {
138
- errors.push(createDiagnostic({ source: candidate.source, workflowId: workflow.id, scopeId: workflow.scope?.id ?? null, stage, error }));
193
+ errors.push(
194
+ createDiagnostic({
195
+ source: candidate.source,
196
+ workflowId: workflow.id,
197
+ scopeId: workflow.scope?.id ?? null,
198
+ stage,
199
+ error
200
+ })
201
+ );
139
202
  }
140
203
  }
141
204
  if (errors.length) {
@@ -144,23 +207,57 @@ async function collectNornProject(cwd) {
144
207
  } else loaded.push(candidate);
145
208
  }
146
209
  const definitions = loaded.flatMap((module) => module.definitions);
147
- return { project: { ...project, definitions, modules: loaded.map((module) => module.source), configurations: registry.configurationInfos(), registry, workflows: registry.launchableEntries() }, diagnostics };
210
+ return {
211
+ project: {
212
+ ...project,
213
+ definitions,
214
+ modules: loaded.map((module) => module.source),
215
+ configurations: registry.configurationInfos(),
216
+ registry,
217
+ workflows: registry.launchableEntries()
218
+ },
219
+ diagnostics
220
+ };
148
221
  }
149
222
  function collectModuleConflicts(modules) {
150
223
  const conflicts = /* @__PURE__ */ new Map();
151
224
  const byId = /* @__PURE__ */ new Map();
152
- for (const module of modules) for (const workflow of module.definitions) byId.set(workflow.id, [...byId.get(workflow.id) ?? [], module]);
225
+ for (const module of modules)
226
+ for (const workflow of module.definitions)
227
+ byId.set(workflow.id, [...byId.get(workflow.id) ?? [], module]);
153
228
  for (const [id, owners] of byId) {
154
229
  if (owners.length < 2) continue;
155
- 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}`) })]);
230
+ for (const module of new Set(owners))
231
+ conflicts.set(module, [
232
+ ...conflicts.get(module) ?? [],
233
+ createDiagnostic({
234
+ source: module.source,
235
+ workflowId: id,
236
+ scopeId: null,
237
+ stage: "duplicate",
238
+ error: new Error(`Workflow already registered: ${id}`)
239
+ })
240
+ ]);
156
241
  }
157
242
  for (let index = 0; index < modules.length; index++) {
158
243
  for (let other = index; other < modules.length; other++) {
159
244
  const candidates = [.../* @__PURE__ */ new Set([modules[index], modules[other]])];
160
245
  try {
161
- assertCompatibleConfigurationOwners(candidates.flatMap((module) => module.definitions));
246
+ assertCompatibleConfigurationOwners(
247
+ candidates.flatMap((module) => module.definitions)
248
+ );
162
249
  } catch (error) {
163
- for (const module of candidates) conflicts.set(module, [...conflicts.get(module) ?? [], createDiagnostic({ source: module.source, workflowId: null, scopeId: null, stage: "config", error })]);
250
+ for (const module of candidates)
251
+ conflicts.set(module, [
252
+ ...conflicts.get(module) ?? [],
253
+ createDiagnostic({
254
+ source: module.source,
255
+ workflowId: null,
256
+ scopeId: null,
257
+ stage: "config",
258
+ error
259
+ })
260
+ ]);
164
261
  }
165
262
  }
166
263
  }
@@ -169,43 +266,100 @@ function collectModuleConflicts(modules) {
169
266
  async function importWorkflowModules(project) {
170
267
  const modules = [];
171
268
  const diagnostics = [];
172
- const jiti = createJiti(pathToFileURL(project.configPath).href, { moduleCache: false, tryNative: false, virtualModules: nornWorkflowVirtualModules() });
269
+ const jiti = createJiti(pathToFileURL(project.configPath).href, {
270
+ moduleCache: false,
271
+ tryNative: false,
272
+ virtualModules: nornWorkflowVirtualModules()
273
+ });
173
274
  const cache = /* @__PURE__ */ Object.create(null);
174
- for (const file of project.configFiles) for (const path of file.config.workflows) {
175
- const source = { configPath: file.path, path: resolveConfigPath(file.root, path) };
176
- let stage = "import";
177
- try {
178
- const cached = cache[source.path];
179
- const module = cached?.loaded ? cached.exports : await jiti.evalModule(await readFile(source.path, "utf8"), { filename: source.path, cache, async: true });
180
- stage = "declaration";
181
- 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}`);
182
- modules.push({ definitions: module.default, source });
183
- } catch (error) {
184
- for (const [path2, module] of Object.entries(cache)) if (!module.loaded) delete cache[path2];
185
- diagnostics.push(createDiagnostic({ source, workflowId: null, scopeId: null, stage, error }));
275
+ for (const file of project.configFiles)
276
+ for (const path of file.config.workflows) {
277
+ const source = {
278
+ configPath: file.path,
279
+ path: resolveConfigPath(file.root, path)
280
+ };
281
+ let stage = "import";
282
+ try {
283
+ const cached = cache[source.path];
284
+ const module = cached?.loaded ? cached.exports : await jiti.evalModule(await readFile(source.path, "utf8"), {
285
+ filename: source.path,
286
+ cache,
287
+ async: true
288
+ });
289
+ stage = "declaration";
290
+ if (!Array.isArray(module?.default) || !Array.from(module.default).every(isWorkflowDeclaration))
291
+ throw new Error(
292
+ `Workflow module must default-export an array of complete workflow definitions: ${source.path}`
293
+ );
294
+ modules.push({ definitions: module.default, source });
295
+ } catch (error) {
296
+ for (const [path2, module] of Object.entries(cache))
297
+ if (!module.loaded) delete cache[path2];
298
+ diagnostics.push(
299
+ createDiagnostic({
300
+ source,
301
+ workflowId: null,
302
+ scopeId: null,
303
+ stage,
304
+ error
305
+ })
306
+ );
307
+ }
186
308
  }
187
- }
188
309
  return { modules, diagnostics };
189
310
  }
190
311
  function createDiagnostic(input) {
191
- 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 : [] };
312
+ return {
313
+ configPath: input.source.configPath,
314
+ modulePath: input.source.path,
315
+ scopeId: input.scopeId,
316
+ workflowId: input.workflowId,
317
+ stage: input.stage,
318
+ message: errorMessage(input.error),
319
+ issues: input.error instanceof AssertError ? input.error.cause.errors : []
320
+ };
192
321
  }
193
322
  function nornWorkflowVirtualModules() {
194
- return { "@vimhead.dev/norn": nornModule, "@vimhead.dev/norn/schema": nornSchemaModule, typebox: typeboxModule, "typebox/value": typeboxValueModule, "typebox/compile": typeboxCompileModule, "typebox/schema": typeboxSchemaModule };
323
+ return {
324
+ "@vimhead.dev/norn": nornModule,
325
+ "@vimhead.dev/norn/schema": nornSchemaModule,
326
+ typebox: typeboxModule,
327
+ "typebox/value": typeboxValueModule,
328
+ "typebox/compile": typeboxCompileModule,
329
+ "typebox/schema": typeboxSchemaModule
330
+ };
195
331
  }
196
332
  function mergeProjectConfig(files) {
197
333
  const [project, ...reusable] = files;
198
334
  let config = {};
199
- for (const file of reusable) config = mergeConfig({ base: config, override: file.config.config, path: ["config"], rejectConflicts: true });
200
- return mergeConfig({ base: config, override: project?.config.config ?? {}, path: ["config"], rejectConflicts: false });
335
+ for (const file of reusable)
336
+ config = mergeConfig({
337
+ base: config,
338
+ override: file.config.config,
339
+ path: ["config"],
340
+ rejectConflicts: true
341
+ });
342
+ return mergeConfig({
343
+ base: config,
344
+ override: project?.config.config ?? {},
345
+ path: ["config"],
346
+ rejectConflicts: false
347
+ });
201
348
  }
202
349
  function mergeConfig(input) {
203
350
  const result = { ...input.base };
204
351
  for (const [key, value] of Object.entries(input.override)) {
205
352
  const previous = result[key];
206
353
  const path = [...input.path, key];
207
- if (isPlainObject(previous) && isPlainObject(value)) result[key] = mergeConfig({ base: previous, override: value, path, rejectConflicts: input.rejectConflicts });
208
- else if (input.rejectConflicts && Object.hasOwn(result, key) && JSON.stringify(previous) !== JSON.stringify(value)) throw new Error(`Conflicting Norn reusable config at ${path.join(".")}`);
354
+ if (isPlainObject(previous) && isPlainObject(value))
355
+ result[key] = mergeConfig({
356
+ base: previous,
357
+ override: value,
358
+ path,
359
+ rejectConflicts: input.rejectConflicts
360
+ });
361
+ else if (input.rejectConflicts && Object.hasOwn(result, key) && JSON.stringify(previous) !== JSON.stringify(value))
362
+ throw new Error(`Conflicting Norn reusable config at ${path.join(".")}`);
209
363
  else result[key] = value;
210
364
  }
211
365
  return result;
@@ -217,9 +371,12 @@ async function expandIncludePath(root, path) {
217
371
  async function expandGlobSegments(root, segments) {
218
372
  if (!segments.length) return [root];
219
373
  const [segment, ...remaining] = segments;
220
- if (segment !== "*") return expandGlobSegments(join(root, segment), remaining);
374
+ if (segment !== "*")
375
+ return expandGlobSegments(join(root, segment), remaining);
221
376
  const entries = await readdir(root, { withFileTypes: true });
222
- 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();
377
+ return (await Promise.all(
378
+ entries.filter((entry) => entry.isDirectory()).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => expandGlobSegments(join(root, entry.name), remaining))
379
+ )).flat();
223
380
  }
224
381
  function resolveConfigPath(root, path) {
225
382
  if (!path.length) throw new Error("Norn path must not be empty");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vimhead.dev/norn-cli",
3
- "version": "0.1.0-tip.35614826665.1",
3
+ "version": "0.1.0-tip.35724621678.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.35614826665.1"
46
+ "@vimhead.dev/norn": "0.1.0-tip.35724621678.1"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "node ../../scripts/build-package.ts"