@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.
Files changed (111) hide show
  1. package/assets/README.md +17 -26
  2. package/assets/docs/README.md +3 -3
  3. package/assets/docs/agents.md +12 -1
  4. package/assets/docs/cli.md +5 -5
  5. package/assets/docs/composition.md +21 -21
  6. package/assets/docs/persistence.md +7 -10
  7. package/assets/docs/projects.md +30 -18
  8. package/assets/docs/providers.md +1 -1
  9. package/assets/docs/recovery.md +13 -9
  10. package/assets/docs/resources.md +4 -19
  11. package/assets/docs/schemas.md +1 -1
  12. package/assets/docs/workflows.md +95 -7
  13. package/assets/examples/agent-then-analysis/README.md +4 -4
  14. package/assets/examples/agent-then-analysis/input.json +1 -1
  15. package/assets/examples/agent-then-analysis/norn.project.json +1 -1
  16. package/assets/examples/agent-then-analysis/plugin.ts +55 -68
  17. package/assets/examples/caller-selected-continuation/README.md +5 -5
  18. package/assets/examples/caller-selected-continuation/caller.ts +35 -38
  19. package/assets/examples/caller-selected-continuation/input.json +2 -2
  20. package/assets/examples/caller-selected-continuation/norn.project.json +1 -1
  21. package/assets/examples/caller-selected-continuation/producer.ts +15 -23
  22. package/assets/examples/coordinating-multiple-agents/README.md +2 -2
  23. package/assets/examples/coordinating-multiple-agents/input.json +1 -1
  24. package/assets/examples/coordinating-multiple-agents/norn.project.json +1 -1
  25. package/assets/examples/coordinating-multiple-agents/plugin.ts +52 -56
  26. package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +4 -4
  27. package/assets/examples/getting-started/norn.project.json +1 -1
  28. package/assets/examples/getting-started/plugin.ts +16 -26
  29. package/assets/examples/minimal-workflow/README.md +8 -8
  30. package/assets/examples/minimal-workflow/norn.project.json +1 -1
  31. package/assets/examples/minimal-workflow/plugin.ts +11 -25
  32. package/assets/examples/shared-state/README.md +10 -2
  33. package/assets/examples/shared-state/input.json +1 -1
  34. package/assets/examples/shared-state/norn.project.json +1 -1
  35. package/assets/examples/shared-state/plugin.ts +36 -41
  36. package/assets/examples/shared-state/shared-state.ts +75 -0
  37. package/assets/{packages/sdk/src → examples/shared-state}/state-adapter.ts +17 -16
  38. package/assets/examples/worktree-development-loop/README.md +6 -7
  39. package/assets/examples/worktree-development-loop/norn.project.json +1 -1
  40. package/assets/examples/worktree-development-loop/plugin.ts +6 -26
  41. package/assets/examples/worktree-development-loop/scope.ts +4 -0
  42. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +14 -15
  43. package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +3 -4
  44. package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +2 -2
  45. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +34 -33
  46. package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +2 -3
  47. package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +7 -3
  48. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +23 -16
  49. package/assets/examples/worktree-development-loop/workflows/planning/index.ts +2 -3
  50. package/assets/examples/worktree-development-loop/workflows/planning/schema.ts +4 -2
  51. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +40 -31
  52. package/assets/examples/worktree-development-loop/workflows/review/index.ts +3 -4
  53. package/assets/examples/worktree-development-loop/workflows/review/schema.ts +5 -4
  54. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +49 -44
  55. package/assets/examples/worktree-development-loop/workflows/review-router/index.ts +2 -3
  56. package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +4 -3
  57. package/assets/package.json +1 -1
  58. package/assets/packages/cli/src/cli.ts +35 -34
  59. package/assets/packages/cli/src/client.ts +9 -16
  60. package/assets/packages/cli/src/documentation-intro.ts +1 -1
  61. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  62. package/assets/packages/cli/src/internal/agent-response-tool.ts +3 -3
  63. package/assets/packages/cli/src/internal/engine.ts +38 -62
  64. package/assets/packages/cli/src/internal/errors.ts +4 -4
  65. package/assets/packages/cli/src/internal/launch-request.ts +6 -6
  66. package/assets/packages/cli/src/internal/run-state.ts +31 -21
  67. package/assets/packages/cli/src/internal/run.ts +2 -5
  68. package/assets/packages/cli/src/internal/workflow-registry.ts +114 -111
  69. package/assets/packages/cli/src/workflow-loader.ts +214 -0
  70. package/assets/packages/core/src/workflow-transition.ts +2 -2
  71. package/assets/packages/sdk/src/api.ts +133 -289
  72. package/assets/packages/sdk/src/index.ts +1 -1
  73. package/assets/packages/sdk/src/schema.ts +3 -3
  74. package/assets/tests/workflow-ref.test.ts +50 -51
  75. package/dist/cli.js +33 -32
  76. package/dist/client.d.ts +3 -4
  77. package/dist/client.js +3 -8
  78. package/dist/documentation-intro.js +1 -1
  79. package/dist/generated-build-info.d.ts +2 -2
  80. package/dist/generated-build-info.js +2 -2
  81. package/dist/internal/agent-response-tool.js +3 -3
  82. package/dist/internal/engine.d.ts +4 -8
  83. package/dist/internal/engine.js +36 -49
  84. package/dist/internal/errors.d.ts +3 -3
  85. package/dist/internal/errors.js +1 -1
  86. package/dist/internal/launch-request.d.ts +4 -4
  87. package/dist/internal/launch-request.js +2 -2
  88. package/dist/internal/run-state.d.ts +9 -8
  89. package/dist/internal/run-state.js +29 -20
  90. package/dist/internal/run.d.ts +1 -3
  91. package/dist/internal/run.js +3 -5
  92. package/dist/internal/workflow-registry.d.ts +25 -17
  93. package/dist/internal/workflow-registry.js +107 -72
  94. package/dist/{plugin-loader.d.ts → workflow-loader.d.ts} +7 -8
  95. package/dist/workflow-loader.js +235 -0
  96. package/package.json +2 -2
  97. package/assets/examples/worktree-development-loop/manifest.ts +0 -26
  98. package/assets/examples/worktree-development-loop/state.ts +0 -23
  99. package/assets/examples/worktree-development-loop/workflows/development-loop/declaration.ts +0 -8
  100. package/assets/examples/worktree-development-loop/workflows/implementation/declaration.ts +0 -8
  101. package/assets/examples/worktree-development-loop/workflows/planning/declaration.ts +0 -8
  102. package/assets/examples/worktree-development-loop/workflows/review/declaration.ts +0 -8
  103. package/assets/examples/worktree-development-loop/workflows/review-router/declaration.ts +0 -12
  104. package/assets/packages/cli/src/internal/run-resources.ts +0 -23
  105. package/assets/packages/cli/src/internal/state-store.ts +0 -83
  106. package/assets/packages/cli/src/plugin-loader.ts +0 -400
  107. package/dist/internal/run-resources.d.ts +0 -6
  108. package/dist/internal/run-resources.js +0 -26
  109. package/dist/internal/state-store.d.ts +0 -23
  110. package/dist/internal/state-store.js +0 -103
  111. package/dist/plugin-loader.js +0 -341
@@ -1,38 +1,39 @@
1
- import type { NornRunNext, NornRun } from "@vimhead.dev/norn";
2
- import { worktreeDevelopmentLoopManifest } from "../../manifest.ts";
1
+ import { reviewWorkflow } from "../review/execute.ts";
2
+ import type { WorkflowResult } from "@vimhead.dev/norn";
3
+ import { developmentLoopScope } from "../../scope.ts";
4
+ import { implementationArgsSchema } from "./schema.ts";
3
5
  import { ensureCommandSucceeded } from "../../shared/commands.ts";
4
- import { implementationAgentResponseSchema, type ImplementationParams } from "./schema.ts";
6
+ import { implementationAgentResponseSchema } from "./schema.ts";
5
7
 
6
- export async function executeImplementationWorkflow(
7
- run: NornRun,
8
- params: ImplementationParams,
9
- ): Promise<NornRunNext> {
10
- const repositoryPath = await run.state.get(worktreeDevelopmentLoopManifest.states.developmentLoop.repositoryPath);
11
- const planArtifact = await run.state.get(worktreeDevelopmentLoopManifest.states.planning.planArtifact);
12
- const plan = await run.artifacts.read(planArtifact);
13
- const previousReviewArtifact = await run.state.getOptional(worktreeDevelopmentLoopManifest.states.review.reviewArtifact);
14
- const previousReview = previousReviewArtifact ? await run.artifacts.read(previousReviewArtifact) : undefined;
15
- const implementation = await run.agents.prompt({
16
- label: `implementation-${params.iteration}`,
17
- cwd: repositoryPath,
18
- tools: ["read", "grep", "find", "ls", "edit", "write", "bash"],
19
- prompt: buildImplementationPrompt(params.task, plan, params.iteration, previousReview),
20
- response: implementationAgentResponseSchema,
21
- });
22
- await run.state.set(worktreeDevelopmentLoopManifest.states.implementation.implementationSummary, implementation.summary);
23
- const status = await run.commands.run({
24
- label: `implementation-${params.iteration}-status`,
25
- cwd: repositoryPath,
26
- command: "git status --short",
27
- });
28
- await ensureCommandSucceeded(status);
29
- const statusOutput = await run.logs.read(status.stdoutLog);
30
- await run.artifacts.write(`implementation/iteration-${params.iteration}-status.txt`, statusOutput);
31
- return worktreeDevelopmentLoopManifest.workflows.review({
32
- task: params.task,
33
- iteration: params.iteration,
34
- });
35
- }
8
+ export const implementationWorkflow = developmentLoopScope.workflow({
9
+ id: "implementation",
10
+ isEntrypoint: false,
11
+ instructions: "Apply one implementation pass in the current repository.",
12
+ args: implementationArgsSchema,
13
+ async execute({ args, run }): Promise<WorkflowResult> {
14
+ const repositoryPath = args.repositoryPath;
15
+ const planArtifact = args.planArtifact;
16
+ const plan = await run.artifacts.read(planArtifact);
17
+ const previousReviewArtifact = args.previousReviewArtifact;
18
+ const previousReview = previousReviewArtifact ? await run.artifacts.read(previousReviewArtifact) : undefined;
19
+ const implementation = await run.agents.prompt({
20
+ label: `implementation-${args.iteration}`,
21
+ cwd: repositoryPath,
22
+ tools: ["read", "grep", "find", "ls", "edit", "write", "bash"],
23
+ prompt: buildImplementationPrompt(args.task, plan, args.iteration, previousReview),
24
+ response: implementationAgentResponseSchema,
25
+ });
26
+ const status = await run.commands.run({
27
+ label: `implementation-${args.iteration}-status`,
28
+ cwd: repositoryPath,
29
+ command: "git status --short",
30
+ });
31
+ await ensureCommandSucceeded(status);
32
+ const statusOutput = await run.logs.read(status.stdoutLog);
33
+ await run.artifacts.write(`implementation/iteration-${args.iteration}-status.txt`, statusOutput);
34
+ return reviewWorkflow({ ...args, implementationSummary: implementation.summary });
35
+ }
36
+ });
36
37
 
37
38
  function buildImplementationPrompt(task: string, plan: string, iteration: number, previousReview: string | undefined): string {
38
39
  return [
@@ -1,3 +1,2 @@
1
- export { implementationWorkflow } from "./declaration.ts";
2
- export { executeImplementationWorkflow } from "./execute.ts";
3
- export { implementationAgentResponseSchema, implementationParamsSchema, type ImplementationParams } from "./schema.ts";
1
+ export { implementationWorkflow } from "./execute.ts";
2
+ export { implementationAgentResponseSchema, implementationArgsSchema, type ImplementationArgs } from "./schema.ts";
@@ -1,12 +1,16 @@
1
+ import { artifactRefSchema } from "@vimhead.dev/norn";
2
+ import { planningArgsSchema } from "../planning/schema.ts";
1
3
  import { Type, type StaticDecode } from "typebox";
2
4
 
3
- export const implementationParamsSchema = Type.Object({
4
- task: Type.String(),
5
+ export const implementationArgsSchema = Type.Object({
6
+ ...planningArgsSchema.properties,
5
7
  iteration: Type.Integer({ minimum: 1 }),
8
+ planArtifact: artifactRefSchema,
9
+ previousReviewArtifact: Type.Optional(artifactRefSchema),
6
10
  });
7
11
 
8
12
  export const implementationAgentResponseSchema = Type.Object({
9
13
  summary: Type.String(),
10
14
  });
11
15
 
12
- export type ImplementationParams = StaticDecode<typeof implementationParamsSchema>;
16
+ export type ImplementationArgs = StaticDecode<typeof implementationArgsSchema>;
@@ -1,20 +1,27 @@
1
- import type { NornRunNext, NornRun } from "@vimhead.dev/norn";
2
- import { worktreeDevelopmentLoopManifest } from "../../manifest.ts";
3
- import { planningAgentResponseSchema, type PlanningParams } from "./schema.ts";
1
+ import { implementationWorkflow } from "../implementation/execute.ts";
2
+ import type { WorkflowResult } from "@vimhead.dev/norn";
3
+ import { developmentLoopScope } from "../../scope.ts";
4
+ import { planningArgsSchema } from "./schema.ts";
5
+ import { planningAgentResponseSchema } from "./schema.ts";
4
6
 
5
- export async function executePlanningWorkflow(run: NornRun, params: PlanningParams): Promise<NornRunNext> {
6
- const repositoryPath = await run.state.get(worktreeDevelopmentLoopManifest.states.developmentLoop.repositoryPath);
7
- const planning = await run.agents.prompt({
8
- label: "planning",
9
- cwd: repositoryPath,
10
- tools: ["read", "grep", "find", "ls"],
11
- prompt: buildPlanningPrompt(params.task),
12
- response: planningAgentResponseSchema,
13
- });
14
- const planArtifact = await run.artifacts.write("planning/plan.md", planning.plan);
15
- await run.state.set(worktreeDevelopmentLoopManifest.states.planning.planArtifact, planArtifact);
16
- return worktreeDevelopmentLoopManifest.workflows.implementation({ task: params.task, iteration: 1 });
17
- }
7
+ export const planningWorkflow = developmentLoopScope.workflow({
8
+ id: "planning",
9
+ isEntrypoint: false,
10
+ instructions: "Create an implementation plan for a repository task.",
11
+ args: planningArgsSchema,
12
+ async execute({ args, run }): Promise<WorkflowResult> {
13
+ const repositoryPath = args.repositoryPath;
14
+ const planning = await run.agents.prompt({
15
+ label: "planning",
16
+ cwd: repositoryPath,
17
+ tools: ["read", "grep", "find", "ls"],
18
+ prompt: buildPlanningPrompt(args.task),
19
+ response: planningAgentResponseSchema,
20
+ });
21
+ const planArtifact = await run.artifacts.write("planning/plan.md", planning.plan);
22
+ return implementationWorkflow({ ...args, planArtifact, iteration: 1 });
23
+ }
24
+ });
18
25
 
19
26
  function buildPlanningPrompt(task: string): string {
20
27
  return [
@@ -1,3 +1,2 @@
1
- export { planningWorkflow } from "./declaration.ts";
2
- export { executePlanningWorkflow } from "./execute.ts";
3
- export { planningAgentResponseSchema, planningParamsSchema, type PlanningParams } from "./schema.ts";
1
+ export { planningWorkflow } from "./execute.ts";
2
+ export { planningAgentResponseSchema, planningArgsSchema, type PlanningArgs } from "./schema.ts";
@@ -1,7 +1,9 @@
1
1
  import { Type, type StaticDecode } from "typebox";
2
2
 
3
- export const planningParamsSchema = Type.Object({
3
+ export const planningArgsSchema = Type.Object({
4
4
  task: Type.String(),
5
+ repositoryPath: Type.String(),
6
+ maxIterations: Type.Integer({ minimum: 1, maximum: 10 }),
5
7
  });
6
8
 
7
9
  export const planningAgentResponseSchema = Type.Object({
@@ -9,4 +11,4 @@ export const planningAgentResponseSchema = Type.Object({
9
11
  summary: Type.String(),
10
12
  });
11
13
 
12
- export type PlanningParams = StaticDecode<typeof planningParamsSchema>;
14
+ export type PlanningArgs = StaticDecode<typeof planningArgsSchema>;
@@ -1,36 +1,45 @@
1
- import type { NornRunNext, NornRun } from "@vimhead.dev/norn";
2
- import { worktreeDevelopmentLoopManifest } from "../../manifest.ts";
1
+ import { reviewRouterWorkflow } from "../review-router/execute.ts";
2
+ import type { WorkflowResult } from "@vimhead.dev/norn";
3
+ import { developmentLoopScope } from "../../scope.ts";
4
+ import { reviewArgsSchema } from "./schema.ts";
3
5
  import { ensureCommandSucceeded } from "../../shared/commands.ts";
4
- import { reviewAgentResponseSchema, type ReviewParams } from "./schema.ts";
6
+ import { reviewAgentResponseSchema } from "./schema.ts";
5
7
 
6
- export async function executeReviewWorkflow(run: NornRun, params: ReviewParams): Promise<NornRunNext> {
7
- const repositoryPath = await run.state.get(worktreeDevelopmentLoopManifest.states.developmentLoop.repositoryPath);
8
- const planArtifact = await run.state.get(worktreeDevelopmentLoopManifest.states.planning.planArtifact);
9
- const implementationSummary = await run.state.get(worktreeDevelopmentLoopManifest.states.implementation.implementationSummary);
10
- const plan = await run.artifacts.read(planArtifact);
11
- const diff = await run.commands.run({
12
- label: `review-${params.iteration}-diff`,
13
- cwd: repositoryPath,
14
- command: "git status --short && git diff --stat HEAD -- . && git diff HEAD -- .",
15
- });
16
- await ensureCommandSucceeded(diff);
17
- const diffOutput = await run.logs.read(diff.stdoutLog);
18
- await run.artifacts.write(`review/iteration-${params.iteration}-diff.txt`, diffOutput);
19
- const review = await run.agents.prompt({
20
- label: `review-${params.iteration}`,
21
- cwd: repositoryPath,
22
- tools: ["read", "grep", "find", "ls", "bash"],
23
- prompt: buildReviewPrompt(params.task, plan, implementationSummary, diffOutput),
24
- response: reviewAgentResponseSchema,
25
- });
26
- const automatedReviewArtifact = await run.artifacts.write(`review/iteration-${params.iteration}-automated.json`, JSON.stringify(review, null, 2));
27
- return worktreeDevelopmentLoopManifest.workflows.reviewRouter({
28
- iteration: params.iteration,
29
- decision: review.decision,
30
- summary: review.summary,
31
- automatedReviewArtifact,
32
- });
33
- }
8
+ export const reviewWorkflow = developmentLoopScope.workflow({
9
+ id: "review",
10
+ isEntrypoint: false,
11
+ instructions: "Review the current repository boundary changes.",
12
+ args: reviewArgsSchema,
13
+ async execute({ args, run }): Promise<WorkflowResult> {
14
+ const repositoryPath = args.repositoryPath;
15
+ const planArtifact = args.planArtifact;
16
+ const implementationSummary = args.implementationSummary;
17
+ const plan = await run.artifacts.read(planArtifact);
18
+ const diff = await run.commands.run({
19
+ label: `review-${args.iteration}-diff`,
20
+ cwd: repositoryPath,
21
+ command: "git status --short && git diff --stat HEAD -- . && git diff HEAD -- .",
22
+ });
23
+ await ensureCommandSucceeded(diff);
24
+ const diffOutput = await run.logs.read(diff.stdoutLog);
25
+ await run.artifacts.write(`review/iteration-${args.iteration}-diff.txt`, diffOutput);
26
+ const review = await run.agents.prompt({
27
+ label: `review-${args.iteration}`,
28
+ cwd: repositoryPath,
29
+ tools: ["read", "grep", "find", "ls", "bash"],
30
+ prompt: buildReviewPrompt(args.task, plan, implementationSummary, diffOutput),
31
+ response: reviewAgentResponseSchema,
32
+ });
33
+ const automatedReviewArtifact = await run.artifacts.write(`review/iteration-${args.iteration}-automated.json`, JSON.stringify(review, null, 2));
34
+ return reviewRouterWorkflow({
35
+ ...args,
36
+ iteration: args.iteration,
37
+ decision: review.decision,
38
+ summary: review.summary,
39
+ automatedReviewArtifact,
40
+ });
41
+ }
42
+ });
34
43
 
35
44
  function buildReviewPrompt(task: string, plan: string, implementationSummary: string, diff: string): string {
36
45
  return [
@@ -1,10 +1,9 @@
1
- export { reviewWorkflow } from "./declaration.ts";
2
- export { executeReviewWorkflow } from "./execute.ts";
1
+ export { reviewWorkflow } from "./execute.ts";
3
2
  export {
4
3
  reviewAgentResponseSchema,
5
4
  reviewDecisionSchema,
6
- reviewParamsSchema,
5
+ reviewArgsSchema,
7
6
  storedReviewSchema,
8
- type ReviewParams,
7
+ type ReviewArgs,
9
8
  type StoredReview,
10
9
  } from "./schema.ts";
@@ -1,11 +1,12 @@
1
+ import { implementationArgsSchema } from "../implementation/schema.ts";
1
2
  import { artifactRefSchema } from "@vimhead.dev/norn";
2
3
  import { Type, type StaticDecode } from "typebox";
3
4
 
4
5
  export const reviewDecisionSchema = Type.Enum(["accept", "revise", "blocked"]);
5
6
 
6
- export const reviewParamsSchema = Type.Object({
7
- task: Type.String(),
8
- iteration: Type.Integer({ minimum: 1 }),
7
+ export const reviewArgsSchema = Type.Object({
8
+ ...implementationArgsSchema.properties,
9
+ implementationSummary: Type.String(),
9
10
  });
10
11
 
11
12
  export const reviewAgentResponseSchema = Type.Object({
@@ -19,5 +20,5 @@ export const storedReviewSchema = Type.Object({
19
20
  reviewArtifact: artifactRefSchema,
20
21
  });
21
22
 
22
- export type ReviewParams = StaticDecode<typeof reviewParamsSchema>;
23
+ export type ReviewArgs = StaticDecode<typeof reviewArgsSchema>;
23
24
  export type StoredReview = StaticDecode<typeof storedReviewSchema>;
@@ -1,51 +1,56 @@
1
- import type { NornWorkflowExecutionResult, NornRun } from "@vimhead.dev/norn";
2
- import { worktreeDevelopmentLoopManifest } from "../../manifest.ts";
1
+ import { implementationWorkflow } from "../implementation/execute.ts";
2
+ import type { WorkflowResult } from "@vimhead.dev/norn";
3
+ import { developmentLoopScope } from "../../scope.ts";
4
+ import { reviewRouterArgsSchema } from "./schema.ts";
3
5
  import type { StoredReview } from "../review/schema.ts";
4
- import type { ReviewRouterParams } from "./schema.ts";
5
6
 
6
- export async function executeReviewRouterWorkflow(
7
- run: NornRun,
8
- params: ReviewRouterParams,
9
- ): Promise<NornWorkflowExecutionResult> {
10
- const repositoryPath = await run.state.get(worktreeDevelopmentLoopManifest.states.developmentLoop.repositoryPath);
11
- const planArtifact = await run.state.get(worktreeDevelopmentLoopManifest.states.planning.planArtifact);
12
- const currentIteration = await run.state.get(worktreeDevelopmentLoopManifest.states.developmentLoop.currentIteration);
13
- const maxIterations = await run.state.get(worktreeDevelopmentLoopManifest.states.developmentLoop.maxIterations);
14
- const task = await run.state.get(worktreeDevelopmentLoopManifest.states.developmentLoop.task);
15
- const reviewArtifact = await run.artifacts.write(
16
- `review/iteration-${params.iteration}-decision.json`,
17
- JSON.stringify({ decision: params.decision, summary: params.summary, automatedReviewArtifact: params.automatedReviewArtifact }, null, 2),
18
- );
19
- const lastReview: StoredReview = { decision: params.decision, summary: params.summary, reviewArtifact };
7
+ export const reviewRouterWorkflow = developmentLoopScope.workflow({
8
+ id: "reviewRouter",
9
+ isEntrypoint: false,
10
+ instructions: "Route implementation and review iterations based on the latest review decision.",
11
+ gate: {
12
+ enabled: true,
13
+ describe: ({ args }) => `Review iteration ${args.iteration}. Confirm or edit the automated decision before continuing. Plan: ${args.planArtifact.path}.`,
14
+ fields: ["decision", "summary"] as const,
15
+ },
16
+ args: reviewRouterArgsSchema,
17
+ async execute({ args, run }): Promise<WorkflowResult> {
18
+ const repositoryPath = args.repositoryPath;
19
+ const planArtifact = args.planArtifact;
20
+ const currentIteration = args.iteration;
21
+ const maxIterations = args.maxIterations;
22
+ const task = args.task;
23
+ const reviewArtifact = await run.artifacts.write(
24
+ `review/iteration-${args.iteration}-decision.json`,
25
+ JSON.stringify({ decision: args.decision, summary: args.summary, automatedReviewArtifact: args.automatedReviewArtifact }, null, 2),
26
+ );
27
+ const lastReview: StoredReview = { decision: args.decision, summary: args.summary, reviewArtifact };
20
28
 
21
- await run.state.set(worktreeDevelopmentLoopManifest.states.review.reviewDecision, params.decision);
22
- await run.state.set(worktreeDevelopmentLoopManifest.states.review.reviewArtifact, reviewArtifact);
29
+ if (args.decision === "accept") {
30
+ return run.complete({
31
+ summary: `Implementation accepted after ${currentIteration} iteration(s).`,
32
+ artifacts: { plan: planArtifact, review: reviewArtifact },
33
+ data: { status: "done", repositoryPath, iterations: currentIteration, lastReview },
34
+ });
35
+ }
23
36
 
24
- if (params.decision === "accept") {
25
- return run.complete({
26
- summary: `Implementation accepted after ${currentIteration} iteration(s).`,
27
- artifacts: { plan: planArtifact, review: reviewArtifact },
28
- data: { status: "done", repositoryPath, iterations: currentIteration, lastReview },
29
- });
30
- }
37
+ if (args.decision === "blocked") {
38
+ return run.fail({
39
+ summary: args.summary,
40
+ artifacts: { plan: planArtifact, review: reviewArtifact },
41
+ data: { status: "blocked", repositoryPath, iterations: currentIteration, lastReview },
42
+ });
43
+ }
31
44
 
32
- if (params.decision === "blocked") {
33
- return run.fail({
34
- summary: params.summary,
35
- artifacts: { plan: planArtifact, review: reviewArtifact },
36
- data: { status: "blocked", repositoryPath, iterations: currentIteration, lastReview },
37
- });
38
- }
45
+ if (currentIteration >= maxIterations) {
46
+ return run.fail({
47
+ summary: `Maximum iteration count reached after ${currentIteration} iteration(s).`,
48
+ artifacts: { plan: planArtifact, review: reviewArtifact },
49
+ data: { status: "needs-attention", repositoryPath, iterations: currentIteration, lastReview },
50
+ });
51
+ }
39
52
 
40
- if (currentIteration >= maxIterations) {
41
- return run.fail({
42
- summary: `Maximum iteration count reached after ${currentIteration} iteration(s).`,
43
- artifacts: { plan: planArtifact, review: reviewArtifact },
44
- data: { status: "needs-attention", repositoryPath, iterations: currentIteration, lastReview },
45
- });
53
+ const nextIteration = currentIteration + 1;
54
+ return implementationWorkflow({ task, repositoryPath, maxIterations, planArtifact, iteration: nextIteration, previousReviewArtifact: reviewArtifact });
46
55
  }
47
-
48
- const nextIteration = currentIteration + 1;
49
- await run.state.set(worktreeDevelopmentLoopManifest.states.developmentLoop.currentIteration, nextIteration);
50
- return worktreeDevelopmentLoopManifest.workflows.implementation({ task, iteration: nextIteration });
51
- }
56
+ });
@@ -1,3 +1,2 @@
1
- export { reviewRouterWorkflow } from "./declaration.ts";
2
- export { executeReviewRouterWorkflow } from "./execute.ts";
3
- export { reviewRouterParamsSchema, type ReviewRouterParams } from "./schema.ts";
1
+ export { reviewRouterWorkflow } from "./execute.ts";
2
+ export { reviewRouterArgsSchema, type ReviewRouterArgs } from "./schema.ts";
@@ -1,12 +1,13 @@
1
+ import { implementationArgsSchema } from "../implementation/schema.ts";
1
2
  import { artifactRefSchema } from "@vimhead.dev/norn";
2
3
  import { Type, type StaticDecode } from "typebox";
3
4
  import { reviewDecisionSchema } from "../review/schema.ts";
4
5
 
5
- export const reviewRouterParamsSchema = Type.Object({
6
- iteration: Type.Integer({ minimum: 1 }),
6
+ export const reviewRouterArgsSchema = Type.Object({
7
+ ...implementationArgsSchema.properties,
7
8
  decision: reviewDecisionSchema,
8
9
  summary: Type.String(),
9
10
  automatedReviewArtifact: artifactRefSchema,
10
11
  });
11
12
 
12
- export type ReviewRouterParams = StaticDecode<typeof reviewRouterParamsSchema>;
13
+ export type ReviewRouterArgs = StaticDecode<typeof reviewRouterArgsSchema>;
@@ -1 +1 @@
1
- {"version":"0.1.0-tip.35359392805.1"}
1
+ {"version":"0.1.0-tip.35390859630.1"}
@@ -17,9 +17,9 @@ import { clearRunResumeRequest, readRunLaunchRequest, readRunResumeRequest, writ
17
17
  import { readRunMetrics } from "./internal/metrics.ts";
18
18
  import { getRunLeaseOwner, NornRunLease } from "./internal/run-lease.ts";
19
19
  import { generateRunName } from "./internal/run-names.ts";
20
- import { getRunInfo, listRuns, mergeInterruptedWorkflowParams, resolveRunRoot } from "./internal/run-state.ts";
20
+ import { assertRunVersion, getRunInfo, listRuns, mergeInterruptedWorkflowArgs, resolveRunRoot } from "./internal/run-state.ts";
21
21
  import { NornRunStore } from "./internal/run-store.ts";
22
- import { discoverNornProject, findNornProject, inspectNornWorkflow, loadNornProject, NORN_PROJECT_FILE_NAME } from "./plugin-loader.ts";
22
+ import { discoverNornProject, findNornProject, inspectNornWorkflow, loadNornProject, NORN_PROJECT_FILE_NAME } from "./workflow-loader.ts";
23
23
 
24
24
  const RUNS_ROOT = join(".norn", "runs");
25
25
  const RUN_WAIT_INTERVAL_MS = 1000;
@@ -110,9 +110,9 @@ const COMMANDS: readonly CliCommand[] = [
110
110
  {
111
111
  id: "project.inspect",
112
112
  path: ["project", "inspect"],
113
- description: "Use when discovering the active Norn project, plugins, and workflow sources.",
113
+ description: "Use when discovering the active Norn project, configuration, and workflow sources.",
114
114
  usage: "norn project inspect",
115
- output: "JSON object with project metadata under project, isComplete, and plugin diagnostics. Incomplete discovery does not permit execution.",
115
+ output: "JSON object with project metadata under project, isComplete, and registration diagnostics. Incomplete discovery does not permit execution.",
116
116
  examples: ["norn project inspect"],
117
117
  execute: async (args) => {
118
118
  assertNoExtraArgs("project inspect", args);
@@ -125,17 +125,17 @@ const COMMANDS: readonly CliCommand[] = [
125
125
  description: "Use when selecting a Norn workflow for a user task; defaults to entrypoint workflows.",
126
126
  usage: "norn workflows list [--entrypoints|--all]",
127
127
  options: ["--entrypoints: list entrypoint workflows", "--all: include internal workflow steps"],
128
- output: "JSON object with workflow summaries under workflows, isComplete, and plugin diagnostics. Successful discovery can be incomplete; start/resume remain strict.",
128
+ output: "JSON object with workflow summaries under workflows, isComplete, and registration diagnostics. Successful discovery can be incomplete; start/resume remain strict.",
129
129
  examples: ["norn workflows list", "norn workflows list --all"],
130
130
  execute: listWorkflows,
131
131
  },
132
132
  {
133
133
  id: "workflows.inspect",
134
134
  path: ["workflows", "inspect"],
135
- description: "Use when reading a workflow's instructions, params schema, gate contract, and source plugin before starting or editing it.",
135
+ description: "Use when reading a workflow's instructions, args schema, gate contract, and registration source before starting or editing it.",
136
136
  usage: "norn workflows inspect <workflow-id>",
137
137
  arguments: ["workflow-id: fully qualified workflow id"],
138
- output: "JSON object with workflow details, isComplete, and plugin diagnostics. workflow is null if unavailable in an incomplete catalog or its schema cannot be inspected; an unknown id in a complete catalog is an error.",
138
+ output: "JSON object with workflow details, isComplete, and registration diagnostics. workflow is null if unavailable in an incomplete catalog or its schema cannot be inspected; an unknown id in a complete catalog is an error.",
139
139
  examples: ["norn workflows inspect example.plan"],
140
140
  execute: async (args) => {
141
141
  const workflowId = requiredArg("workflows inspect", args, 0, "workflow id");
@@ -146,12 +146,12 @@ const COMMANDS: readonly CliCommand[] = [
146
146
  {
147
147
  id: "runs.start",
148
148
  path: ["runs", "start"],
149
- description: "Use when starting a Norn workflow run after the workflow id and params are known.",
149
+ description: "Use when starting a Norn workflow run after the workflow id and args are known.",
150
150
  usage: "norn runs start <workflow-id>",
151
151
  arguments: ["workflow-id: fully qualified workflow id to start"],
152
- stdin: "Optional JSON object: {\"params\":{...},\"config\":{\"pluginId\":{...}}}.",
152
+ stdin: "Optional JSON object: {\"args\":{...},\"config\":{\"workflowOrScopeId\":{...}}}.",
153
153
  output: "JSON object with started run info under run.",
154
- examples: ["printf '{\"params\":{\"task\":\"Add tests\"}}' | norn runs start example.plan"],
154
+ examples: ["printf '{\"args\":{\"task\":\"Add tests\"}}' | norn runs start example.plan"],
155
155
  execute: async (args) => {
156
156
  const workflowId = requiredArg("runs start", args, 0, "workflow id");
157
157
  await startRun(workflowId, args.slice(1));
@@ -163,9 +163,9 @@ const COMMANDS: readonly CliCommand[] = [
163
163
  description: "Use when resuming an interrupted gate or a checkpoint restored for retry.",
164
164
  usage: "norn runs resume <run>",
165
165
  arguments: ["run: run id, generated name, or run path"],
166
- stdin: "Optional JSON object: {\"params\":{...}}. Interrupted runs require a patch containing only editable gate fields; pending-resume runs do not accept params.",
166
+ stdin: "Optional JSON object: {\"args\":{...}}. Interrupted runs require a patch containing only editable gate fields; pending-resume runs do not accept args.",
167
167
  output: "JSON object with resumed run info under run.",
168
- examples: ["printf '{\"params\":{\"decision\":\"accept\"}}' | norn runs resume quiet-river-lantern"],
168
+ examples: ["printf '{\"args\":{\"decision\":\"accept\"}}' | norn runs resume quiet-river-lantern"],
169
169
  execute: async (args) => {
170
170
  const run = requiredArg("runs resume", args, 0, "run");
171
171
  await resumeRun(run, args.slice(1));
@@ -753,7 +753,7 @@ async function initProject(): Promise<void> {
753
753
  const projectPath = resolve(projectRoot, NORN_PROJECT_FILE_NAME);
754
754
  if (await isFile(projectPath)) throw new Error(`Norn project already exists: ${projectPath}`);
755
755
  await mkdir(resolve(projectRoot, RUNS_ROOT), { recursive: true });
756
- await writeFile(projectPath, `${JSON.stringify({ version: 1, plugins: [], includes: [], config: {} }, null, 2)}\n`, "utf8");
756
+ await writeFile(projectPath, `${JSON.stringify({ version: 1, workflows: [], includes: [], config: {} }, null, 2)}\n`, "utf8");
757
757
  await ensureGitignoreExcludesRunState(projectRoot);
758
758
  writeJson({ project: { path: projectPath, root: projectRoot, runsRoot: resolve(projectRoot, RUNS_ROOT) } });
759
759
  }
@@ -768,21 +768,21 @@ async function inspectProject(): Promise<void> {
768
768
  writeJson({ project, isComplete, diagnostics });
769
769
  }
770
770
 
771
- async function startRun(workflowId: string, args: readonly string[]): Promise<void> {
772
- assertNoStructuredInputArgs("runs start", args);
771
+ async function startRun(workflowId: string, commandArgs: readonly string[]): Promise<void> {
772
+ assertNoStructuredInputArgs("runs start", commandArgs);
773
773
  const project = await loadNornProject(process.cwd());
774
774
  const workflow = project.registry.workflowById(workflowId);
775
775
  if (!workflow) throw new Error(`Unknown workflow: ${workflowId}`);
776
776
  const input = parseStartRunInput(await readStdinJson());
777
- const params = input.params === undefined ? {} : input.params;
778
- Value.Decode(workflow.params, params);
777
+ const args = input.args === undefined ? {} : input.args;
778
+ Value.Decode(workflow.args, args);
779
779
  const configOverride = input.config;
780
780
  const id = randomUUID();
781
781
  const name = generateRunName(new Set((await listRuns(project.projectRoot)).map((run) => run.name)));
782
782
  const runRoot = resolve(project.projectRoot, RUNS_ROOT, id);
783
783
  await mkdir(runRoot, { recursive: true });
784
784
  const createdAt = new Date().toISOString();
785
- await writeRunLaunchRequest(runRoot, { version: 1, type: "run", id, name, workflowId, params, configOverride, createdAt });
785
+ await writeRunLaunchRequest(runRoot, { version: 2, type: "run", id, name, workflowId, args, configOverride, createdAt });
786
786
  await startDetachedExecuteRun(id, project.projectRoot);
787
787
  writeJson({ run: startedRunInfo({ id, name, workflow, runRoot, createdAt }) });
788
788
  }
@@ -796,8 +796,9 @@ async function resumeRun(run: string, args: readonly string[]): Promise<void> {
796
796
  let request: NornRunResumeRequest;
797
797
  try {
798
798
  const runInfo = await getRunInfo(runRoot);
799
- const params = await parseResumeParams(runInfo, input.params);
800
- request = { version: 1, type: "resume", id: runInfo.id, requestId: randomUUID(), params, createdAt: new Date().toISOString() };
799
+ assertRunVersion(runInfo.version);
800
+ const args = await parseResumeArgs(runInfo, input.args);
801
+ request = { version: 2, type: "resume", id: runInfo.id, requestId: randomUUID(), args, createdAt: new Date().toISOString() };
801
802
  await writeRunResumeRequest(runRoot, request);
802
803
  } finally {
803
804
  await lease.release();
@@ -811,18 +812,18 @@ async function resumeRun(run: string, args: readonly string[]): Promise<void> {
811
812
  writeJson({ run: await getRunInfo(runRoot) });
812
813
  }
813
814
 
814
- async function parseResumeParams(runInfo: NornRunInfo, params: unknown): Promise<unknown> {
815
+ async function parseResumeArgs(runInfo: NornRunInfo, args: unknown): Promise<unknown> {
815
816
  if (runInfo.status === "interrupted") {
816
- if (params === undefined) throw new Error(`Interrupted workflow resume requires params: ${runInfo.name}`);
817
+ if (args === undefined) throw new Error(`Interrupted workflow resume requires args: ${runInfo.name}`);
817
818
  if (!runInfo.currentWorkflowId) throw new Error(`Run has no current workflow: ${runInfo.name}`);
818
819
  const project = await loadNornProject(process.cwd());
819
820
  const workflow = project.registry.workflowById(runInfo.currentWorkflowId);
820
821
  if (!workflow) throw new Error(`Unknown workflow for resumed run: ${runInfo.currentWorkflowId}`);
821
- Value.Decode(workflow.params, mergeInterruptedWorkflowParams(runInfo.interruption?.params, params, runInfo.interruption?.fields));
822
- return params;
822
+ Value.Decode(workflow.args, mergeInterruptedWorkflowArgs(runInfo.interruption?.args, args, runInfo.interruption?.fields));
823
+ return args;
823
824
  }
824
825
  if (runInfo.status === "pendingResume") {
825
- if (params !== undefined) throw new Error(`Pending-resume workflows do not accept params: ${runInfo.name}`);
826
+ if (args !== undefined) throw new Error(`Pending-resume workflows do not accept args: ${runInfo.name}`);
826
827
  return undefined;
827
828
  }
828
829
  throw new Error(`Run must be rolled back before resuming: ${runInfo.name}`);
@@ -900,11 +901,11 @@ async function executeRun(runId: string): Promise<void> {
900
901
  try {
901
902
  const project = await loadNornProject(process.cwd());
902
903
  const engine = new NornEngine({ cwd: project.projectRoot, signal: abortController.signal, gateMode: "pause", config: project.projectConfig });
903
- for (const plugin of project.plugins) engine.registerPlugin(plugin);
904
+ engine.registerWorkflows(project.definitions);
904
905
  if (request.type === "run") {
905
906
  const workflow = project.registry.workflowById(request.workflowId);
906
907
  if (!workflow) throw new Error(`Unknown workflow: ${request.workflowId}`);
907
- await engine.runWorkflow(workflow, request.params, { id: request.id, name: request.name, configOverride: request.configOverride });
908
+ await engine.runWorkflow(workflow, request.args, { id: request.id, name: request.name, configOverride: request.configOverride });
908
909
  } else {
909
910
  await engine.resumeRequestedWorkflow({ runRoot, request });
910
911
  }
@@ -1006,7 +1007,7 @@ function startedRunInfo(input: {
1006
1007
  readonly createdAt: string;
1007
1008
  }): NornRunInfo {
1008
1009
  return {
1009
- version: 1,
1010
+ version: 2,
1010
1011
  id: input.id,
1011
1012
  name: input.name,
1012
1013
  path: input.runRoot,
@@ -1058,18 +1059,18 @@ async function readStdin(): Promise<string> {
1058
1059
  });
1059
1060
  }
1060
1061
 
1061
- function parseStartRunInput(value: unknown): { readonly params?: unknown; readonly config?: unknown } {
1062
+ function parseStartRunInput(value: unknown): { readonly args?: unknown; readonly config?: unknown } {
1062
1063
  if (value === undefined) return {};
1063
1064
  const input = parseStructuredInputObject("runs start", value);
1064
- assertStructuredInputKeys("runs start", input, ["params", "config"]);
1065
- return { params: input.params, config: input.config };
1065
+ assertStructuredInputKeys("runs start", input, ["args", "config"]);
1066
+ return { args: input.args, config: input.config };
1066
1067
  }
1067
1068
 
1068
- function parseResumeRunInput(value: unknown): { readonly params?: unknown } {
1069
+ function parseResumeRunInput(value: unknown): { readonly args?: unknown } {
1069
1070
  if (value === undefined) return {};
1070
1071
  const input = parseStructuredInputObject("runs resume", value);
1071
- assertStructuredInputKeys("runs resume", input, ["params"]);
1072
- return { params: input.params };
1072
+ assertStructuredInputKeys("runs resume", input, ["args"]);
1073
+ return { args: input.args };
1073
1074
  }
1074
1075
 
1075
1076
  function parseStructuredInputObject(command: string, value: unknown): Record<string, unknown> {