@vimhead.dev/norn-cli 0.1.0-tip.35359392805.1 → 0.1.0-tip.35390859630.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/README.md +17 -26
- package/assets/docs/README.md +3 -3
- package/assets/docs/agents.md +12 -1
- package/assets/docs/cli.md +5 -5
- package/assets/docs/composition.md +21 -21
- package/assets/docs/persistence.md +7 -10
- package/assets/docs/projects.md +30 -18
- package/assets/docs/providers.md +1 -1
- package/assets/docs/recovery.md +13 -9
- package/assets/docs/resources.md +4 -19
- package/assets/docs/schemas.md +1 -1
- package/assets/docs/workflows.md +95 -7
- package/assets/examples/agent-then-analysis/README.md +4 -4
- package/assets/examples/agent-then-analysis/input.json +1 -1
- package/assets/examples/agent-then-analysis/norn.project.json +1 -1
- package/assets/examples/agent-then-analysis/plugin.ts +55 -68
- package/assets/examples/caller-selected-continuation/README.md +5 -5
- package/assets/examples/caller-selected-continuation/caller.ts +35 -38
- package/assets/examples/caller-selected-continuation/input.json +2 -2
- package/assets/examples/caller-selected-continuation/norn.project.json +1 -1
- package/assets/examples/caller-selected-continuation/producer.ts +15 -23
- package/assets/examples/coordinating-multiple-agents/README.md +2 -2
- package/assets/examples/coordinating-multiple-agents/input.json +1 -1
- package/assets/examples/coordinating-multiple-agents/norn.project.json +1 -1
- package/assets/examples/coordinating-multiple-agents/plugin.ts +52 -56
- package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +4 -4
- package/assets/examples/getting-started/norn.project.json +1 -1
- package/assets/examples/getting-started/plugin.ts +16 -26
- package/assets/examples/minimal-workflow/README.md +8 -8
- package/assets/examples/minimal-workflow/norn.project.json +1 -1
- package/assets/examples/minimal-workflow/plugin.ts +11 -25
- package/assets/examples/shared-state/README.md +10 -2
- package/assets/examples/shared-state/input.json +1 -1
- package/assets/examples/shared-state/norn.project.json +1 -1
- package/assets/examples/shared-state/plugin.ts +36 -41
- package/assets/examples/shared-state/shared-state.ts +75 -0
- package/assets/{packages/sdk/src → examples/shared-state}/state-adapter.ts +17 -16
- package/assets/examples/worktree-development-loop/README.md +6 -7
- package/assets/examples/worktree-development-loop/norn.project.json +1 -1
- package/assets/examples/worktree-development-loop/plugin.ts +6 -26
- package/assets/examples/worktree-development-loop/scope.ts +4 -0
- package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +14 -15
- package/assets/examples/worktree-development-loop/workflows/development-loop/index.ts +3 -4
- package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +2 -2
- package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +34 -33
- package/assets/examples/worktree-development-loop/workflows/implementation/index.ts +2 -3
- package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +7 -3
- package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +23 -16
- package/assets/examples/worktree-development-loop/workflows/planning/index.ts +2 -3
- package/assets/examples/worktree-development-loop/workflows/planning/schema.ts +4 -2
- package/assets/examples/worktree-development-loop/workflows/review/execute.ts +40 -31
- package/assets/examples/worktree-development-loop/workflows/review/index.ts +3 -4
- package/assets/examples/worktree-development-loop/workflows/review/schema.ts +5 -4
- package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +49 -44
- package/assets/examples/worktree-development-loop/workflows/review-router/index.ts +2 -3
- package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +4 -3
- package/assets/package.json +1 -1
- package/assets/packages/cli/src/cli.ts +35 -34
- package/assets/packages/cli/src/client.ts +9 -16
- package/assets/packages/cli/src/documentation-intro.ts +1 -1
- package/assets/packages/cli/src/generated-build-info.ts +2 -2
- package/assets/packages/cli/src/internal/agent-response-tool.ts +3 -3
- package/assets/packages/cli/src/internal/engine.ts +38 -62
- package/assets/packages/cli/src/internal/errors.ts +4 -4
- package/assets/packages/cli/src/internal/launch-request.ts +6 -6
- package/assets/packages/cli/src/internal/run-state.ts +31 -21
- package/assets/packages/cli/src/internal/run.ts +2 -5
- package/assets/packages/cli/src/internal/workflow-registry.ts +114 -111
- package/assets/packages/cli/src/workflow-loader.ts +214 -0
- package/assets/packages/core/src/workflow-transition.ts +2 -2
- package/assets/packages/sdk/src/api.ts +133 -289
- package/assets/packages/sdk/src/index.ts +1 -1
- package/assets/packages/sdk/src/schema.ts +3 -3
- package/assets/tests/workflow-ref.test.ts +50 -51
- package/dist/cli.js +33 -32
- package/dist/client.d.ts +3 -4
- package/dist/client.js +3 -8
- package/dist/documentation-intro.js +1 -1
- package/dist/generated-build-info.d.ts +2 -2
- package/dist/generated-build-info.js +2 -2
- package/dist/internal/agent-response-tool.js +3 -3
- package/dist/internal/engine.d.ts +4 -8
- package/dist/internal/engine.js +36 -49
- package/dist/internal/errors.d.ts +3 -3
- package/dist/internal/errors.js +1 -1
- package/dist/internal/launch-request.d.ts +4 -4
- package/dist/internal/launch-request.js +2 -2
- package/dist/internal/run-state.d.ts +9 -8
- package/dist/internal/run-state.js +29 -20
- package/dist/internal/run.d.ts +1 -3
- package/dist/internal/run.js +3 -5
- package/dist/internal/workflow-registry.d.ts +25 -17
- package/dist/internal/workflow-registry.js +107 -72
- package/dist/{plugin-loader.d.ts → workflow-loader.d.ts} +7 -8
- package/dist/workflow-loader.js +235 -0
- package/package.json +2 -2
- package/assets/examples/worktree-development-loop/manifest.ts +0 -26
- package/assets/examples/worktree-development-loop/state.ts +0 -23
- package/assets/examples/worktree-development-loop/workflows/development-loop/declaration.ts +0 -8
- package/assets/examples/worktree-development-loop/workflows/implementation/declaration.ts +0 -8
- package/assets/examples/worktree-development-loop/workflows/planning/declaration.ts +0 -8
- package/assets/examples/worktree-development-loop/workflows/review/declaration.ts +0 -8
- package/assets/examples/worktree-development-loop/workflows/review-router/declaration.ts +0 -12
- package/assets/packages/cli/src/internal/run-resources.ts +0 -23
- package/assets/packages/cli/src/internal/state-store.ts +0 -83
- package/assets/packages/cli/src/plugin-loader.ts +0 -400
- package/dist/internal/run-resources.d.ts +0 -6
- package/dist/internal/run-resources.js +0 -26
- package/dist/internal/state-store.d.ts +0 -23
- package/dist/internal/state-store.js +0 -103
- package/dist/plugin-loader.js +0 -341
|
@@ -1,38 +1,39 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
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
|
|
6
|
+
import { implementationAgentResponseSchema } from "./schema.ts";
|
|
5
7
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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 "./
|
|
2
|
-
export {
|
|
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
|
|
4
|
-
|
|
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
|
|
16
|
+
export type ImplementationArgs = StaticDecode<typeof implementationArgsSchema>;
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 "./
|
|
2
|
-
export {
|
|
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
|
|
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
|
|
14
|
+
export type PlanningArgs = StaticDecode<typeof planningArgsSchema>;
|
|
@@ -1,36 +1,45 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
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
|
|
6
|
+
import { reviewAgentResponseSchema } from "./schema.ts";
|
|
5
7
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 "./
|
|
2
|
-
export { executeReviewWorkflow } from "./execute.ts";
|
|
1
|
+
export { reviewWorkflow } from "./execute.ts";
|
|
3
2
|
export {
|
|
4
3
|
reviewAgentResponseSchema,
|
|
5
4
|
reviewDecisionSchema,
|
|
6
|
-
|
|
5
|
+
reviewArgsSchema,
|
|
7
6
|
storedReviewSchema,
|
|
8
|
-
type
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
23
|
+
export type ReviewArgs = StaticDecode<typeof reviewArgsSchema>;
|
|
23
24
|
export type StoredReview = StaticDecode<typeof storedReviewSchema>;
|
|
@@ -1,51 +1,56 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
41
|
-
return
|
|
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 "./
|
|
2
|
-
export {
|
|
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
|
|
6
|
-
|
|
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
|
|
13
|
+
export type ReviewRouterArgs = StaticDecode<typeof reviewRouterArgsSchema>;
|
package/assets/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.1.0-tip.
|
|
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,
|
|
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 "./
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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: {\"
|
|
152
|
+
stdin: "Optional JSON object: {\"args\":{...},\"config\":{\"workflowOrScopeId\":{...}}}.",
|
|
153
153
|
output: "JSON object with started run info under run.",
|
|
154
|
-
examples: ["printf '{\"
|
|
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: {\"
|
|
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 '{\"
|
|
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,
|
|
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,
|
|
772
|
-
assertNoStructuredInputArgs("runs start",
|
|
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
|
|
778
|
-
Value.Decode(workflow.
|
|
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:
|
|
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
|
-
|
|
800
|
-
|
|
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
|
|
815
|
+
async function parseResumeArgs(runInfo: NornRunInfo, args: unknown): Promise<unknown> {
|
|
815
816
|
if (runInfo.status === "interrupted") {
|
|
816
|
-
if (
|
|
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.
|
|
822
|
-
return
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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
|
|
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, ["
|
|
1065
|
-
return {
|
|
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
|
|
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, ["
|
|
1072
|
-
return {
|
|
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> {
|