keystone-cli 0.8.0 → 1.0.0

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 (103) hide show
  1. package/README.md +486 -54
  2. package/package.json +8 -2
  3. package/src/__fixtures__/index.ts +100 -0
  4. package/src/cli.ts +809 -90
  5. package/src/db/memory-db.ts +35 -1
  6. package/src/db/workflow-db.test.ts +24 -0
  7. package/src/db/workflow-db.ts +469 -14
  8. package/src/expression/evaluator.ts +68 -4
  9. package/src/parser/agent-parser.ts +6 -3
  10. package/src/parser/config-schema.ts +38 -2
  11. package/src/parser/schema.ts +192 -7
  12. package/src/parser/test-schema.ts +29 -0
  13. package/src/parser/workflow-parser.test.ts +54 -0
  14. package/src/parser/workflow-parser.ts +153 -7
  15. package/src/runner/aggregate-error.test.ts +57 -0
  16. package/src/runner/aggregate-error.ts +46 -0
  17. package/src/runner/audit-verification.test.ts +2 -2
  18. package/src/runner/auto-heal.test.ts +1 -1
  19. package/src/runner/blueprint-executor.test.ts +63 -0
  20. package/src/runner/blueprint-executor.ts +157 -0
  21. package/src/runner/concurrency-limit.test.ts +82 -0
  22. package/src/runner/debug-repl.ts +18 -3
  23. package/src/runner/durable-timers.test.ts +200 -0
  24. package/src/runner/engine-executor.test.ts +464 -0
  25. package/src/runner/engine-executor.ts +491 -0
  26. package/src/runner/foreach-executor.ts +30 -12
  27. package/src/runner/llm-adapter.test.ts +282 -5
  28. package/src/runner/llm-adapter.ts +581 -8
  29. package/src/runner/llm-clarification.test.ts +79 -21
  30. package/src/runner/llm-errors.ts +83 -0
  31. package/src/runner/llm-executor.test.ts +258 -219
  32. package/src/runner/llm-executor.ts +226 -29
  33. package/src/runner/mcp-client.ts +70 -3
  34. package/src/runner/mcp-manager.test.ts +52 -52
  35. package/src/runner/mcp-manager.ts +12 -5
  36. package/src/runner/mcp-server.test.ts +117 -78
  37. package/src/runner/mcp-server.ts +13 -4
  38. package/src/runner/optimization-runner.ts +48 -31
  39. package/src/runner/reflexion.test.ts +1 -1
  40. package/src/runner/resource-pool.test.ts +113 -0
  41. package/src/runner/resource-pool.ts +164 -0
  42. package/src/runner/shell-executor.ts +130 -32
  43. package/src/runner/standard-tools-integration.test.ts +36 -36
  44. package/src/runner/standard-tools.test.ts +18 -0
  45. package/src/runner/standard-tools.ts +110 -37
  46. package/src/runner/step-executor.test.ts +176 -16
  47. package/src/runner/step-executor.ts +530 -86
  48. package/src/runner/stream-utils.test.ts +14 -0
  49. package/src/runner/subflow-outputs.test.ts +103 -0
  50. package/src/runner/test-harness.ts +161 -0
  51. package/src/runner/tool-integration.test.ts +73 -79
  52. package/src/runner/workflow-runner.test.ts +492 -15
  53. package/src/runner/workflow-runner.ts +1438 -79
  54. package/src/runner/workflow-subflows.test.ts +255 -0
  55. package/src/templates/agents/keystone-architect.md +17 -12
  56. package/src/templates/agents/tester.md +21 -0
  57. package/src/templates/child-rollback.yaml +11 -0
  58. package/src/templates/decompose-implement.yaml +53 -0
  59. package/src/templates/decompose-problem.yaml +159 -0
  60. package/src/templates/decompose-research.yaml +52 -0
  61. package/src/templates/decompose-review.yaml +51 -0
  62. package/src/templates/dev.yaml +134 -0
  63. package/src/templates/engine-example.yaml +33 -0
  64. package/src/templates/fan-out-fan-in.yaml +61 -0
  65. package/src/templates/memory-service.yaml +1 -1
  66. package/src/templates/parent-rollback.yaml +16 -0
  67. package/src/templates/robust-automation.yaml +1 -1
  68. package/src/templates/scaffold-feature.yaml +29 -27
  69. package/src/templates/scaffold-generate.yaml +41 -0
  70. package/src/templates/scaffold-plan.yaml +53 -0
  71. package/src/types/status.ts +3 -0
  72. package/src/ui/dashboard.tsx +4 -3
  73. package/src/utils/assets.macro.ts +36 -0
  74. package/src/utils/auth-manager.ts +585 -8
  75. package/src/utils/blueprint-utils.test.ts +49 -0
  76. package/src/utils/blueprint-utils.ts +80 -0
  77. package/src/utils/circuit-breaker.test.ts +177 -0
  78. package/src/utils/circuit-breaker.ts +160 -0
  79. package/src/utils/config-loader.test.ts +100 -13
  80. package/src/utils/config-loader.ts +44 -17
  81. package/src/utils/constants.ts +62 -0
  82. package/src/utils/error-renderer.test.ts +267 -0
  83. package/src/utils/error-renderer.ts +320 -0
  84. package/src/utils/json-parser.test.ts +4 -0
  85. package/src/utils/json-parser.ts +18 -1
  86. package/src/utils/mermaid.ts +4 -0
  87. package/src/utils/paths.test.ts +46 -0
  88. package/src/utils/paths.ts +70 -0
  89. package/src/utils/process-sandbox.test.ts +128 -0
  90. package/src/utils/process-sandbox.ts +293 -0
  91. package/src/utils/rate-limiter.test.ts +143 -0
  92. package/src/utils/rate-limiter.ts +221 -0
  93. package/src/utils/redactor.test.ts +23 -15
  94. package/src/utils/redactor.ts +65 -25
  95. package/src/utils/resource-loader.test.ts +54 -0
  96. package/src/utils/resource-loader.ts +158 -0
  97. package/src/utils/sandbox.test.ts +69 -4
  98. package/src/utils/sandbox.ts +69 -6
  99. package/src/utils/schema-validator.ts +65 -0
  100. package/src/utils/workflow-registry.test.ts +57 -0
  101. package/src/utils/workflow-registry.ts +45 -25
  102. /package/src/expression/{evaluator.audit.test.ts → evaluator-audit.test.ts} +0 -0
  103. /package/src/runner/{mcp-client.audit.test.ts → mcp-client-audit.test.ts} +0 -0
@@ -0,0 +1,134 @@
1
+ name: dev
2
+ description: "Self-bootstrapping DevMode workflow for Keystone CLI"
3
+
4
+ inputs:
5
+ task:
6
+ type: string
7
+ description: "The development task to perform"
8
+ auto_approve:
9
+ type: boolean
10
+ default: false
11
+ description: "If true, skip the plan approval step"
12
+
13
+ outputs:
14
+ summary: ${{ steps.implement.output.summary }}
15
+ files_changed: ${{ steps.implement.output.files_changed }}
16
+
17
+ steps:
18
+ - id: analyze
19
+ type: llm
20
+ agent: keystone-architect
21
+ useStandardTools: true
22
+ prompt: |
23
+ Analyze the following task and the current project structure.
24
+ Task: ${{ inputs.task }}
25
+
26
+ Identify the relevant files and components that need to be modified or created.
27
+ Provide a high-level summary of your analysis.
28
+ outputSchema:
29
+ type: object
30
+ properties:
31
+ analysis: { type: string }
32
+ affected_files: { type: array, items: { type: string } }
33
+ required: [analysis, affected_files]
34
+
35
+ - id: plan
36
+ type: llm
37
+ agent: keystone-architect
38
+ needs: [analyze]
39
+ prompt: |
40
+ Based on the analysis, create a detailed implementation plan for:
41
+ ${{ inputs.task }}
42
+
43
+ Analysis:
44
+ ${{ steps.analyze.output.analysis }}
45
+
46
+ Proposed Implementation Strategy:
47
+ 1. What changes are needed in which files?
48
+ 2. Are there any new files to be created?
49
+ 3. What are the potential risks or breaking changes?
50
+ 4. How will the changes be verified?
51
+ outputSchema:
52
+ type: object
53
+ properties:
54
+ plan_summary: { type: string }
55
+ detailed_steps: { type: array, items: { type: string } }
56
+ required: [plan_summary, detailed_steps]
57
+
58
+ - id: approve_plan
59
+ type: human
60
+ if: "!${{ inputs.auto_approve }}"
61
+ needs: [plan]
62
+ message: |
63
+ Proposed Plan for: ${{ inputs.task }}
64
+
65
+ Summary:
66
+ ${{ steps.plan.output.plan_summary }}
67
+
68
+ Detailed Steps:
69
+ ${{ steps.plan.output.detailed_steps.join('\n') }}
70
+
71
+ Do you want to proceed with this plan? (yes/no)
72
+ inputType: confirm
73
+
74
+ - id: implement
75
+ type: llm
76
+ agent: software-engineer
77
+ needs: [approve_plan]
78
+ useStandardTools: true
79
+ allowInsecure: true
80
+ prompt: |
81
+ Implement the following plan for the task:
82
+ ${{ inputs.task }}
83
+
84
+ Plan:
85
+ ${{ steps.plan.output.plan_summary }}
86
+
87
+ Detailed Steps:
88
+ ${{ steps.plan.output.detailed_steps.join('\n') }}
89
+
90
+ Use your tools to modify or create the necessary files.
91
+ outputSchema:
92
+ type: object
93
+ properties:
94
+ summary: { type: string }
95
+ files_changed: { type: array, items: { type: string } }
96
+ required: [summary]
97
+
98
+ - id: verify
99
+ type: llm
100
+ agent: tester
101
+ needs: [implement]
102
+ useStandardTools: true
103
+ allowInsecure: true
104
+ prompt: |
105
+ Verify the changes made for the task:
106
+ ${{ inputs.task }}
107
+
108
+ Files changed:
109
+ ${{ steps.implement.output.files_changed.join(', ') }}
110
+
111
+ Run relevant tests and perform any necessary checks to ensure the implementation is correct and hasn't introduced regressions.
112
+ outputSchema:
113
+ type: object
114
+ properties:
115
+ test_results: { type: string }
116
+ success: { type: boolean }
117
+ required: [test_results, success]
118
+
119
+ - id: final_review
120
+ type: human
121
+ needs: [verify]
122
+ message: |
123
+ Implementation Complete for: ${{ inputs.task }}
124
+
125
+ Summary of changes:
126
+ ${{ steps.implement.output.summary }}
127
+
128
+ Test Results:
129
+ ${{ steps.verify.output.test_results }}
130
+
131
+ Status: ${{ steps.verify.output.success ? 'PASSED' : 'FAILED' }}
132
+
133
+ Should we finalize these changes? (yes/no)
134
+ inputType: confirm
@@ -0,0 +1,33 @@
1
+ name: engine-example
2
+ description: "Run an allowlisted external CLI and capture a structured summary"
3
+
4
+ # Requires an allowlisted engine in .keystone/config.yaml:
5
+ # engines:
6
+ # allowlist:
7
+ # bun:
8
+ # command: bun
9
+ # version: "1.3."
10
+ # versionArgs: ["--version"]
11
+
12
+ inputs:
13
+ message:
14
+ type: string
15
+ default: "Hello engine"
16
+
17
+ steps:
18
+ - id: run_engine
19
+ type: engine
20
+ command: bun
21
+ args:
22
+ - -e
23
+ - 'const fs = require("node:fs"); const input = fs.readFileSync(0, "utf8"); const data = input ? JSON.parse(input) : {}; const summary = { received: data.message || null }; fs.writeFileSync(process.env.KEYSTONE_ENGINE_SUMMARY_PATH, JSON.stringify(summary));'
24
+ input:
25
+ message: ${{ inputs.message }}
26
+ env:
27
+ PATH: ${{ env.PATH }}
28
+ cwd: .
29
+ outputSchema:
30
+ type: object
31
+ properties:
32
+ received: { type: string }
33
+ required: [received]
@@ -0,0 +1,61 @@
1
+ name: fan-out-fan-in-example
2
+ description: Demonstrates dynamic join conditions and nested compensations
3
+
4
+ inputs:
5
+ should_fail:
6
+ type: boolean
7
+ default: false
8
+
9
+ steps:
10
+ - id: prepare
11
+ type: shell
12
+ run: echo "Preparing..."
13
+ compensate:
14
+ id: cleanup_prepare
15
+ type: shell
16
+ run: echo "Cleaning up preparation..."
17
+
18
+ - id: parallel_1
19
+ type: shell
20
+ run: sleep 2 && echo "Parallel 1 done"
21
+ needs: [prepare]
22
+ compensate:
23
+ id: undo_1
24
+ type: shell
25
+ run: echo "Undoing Parallel 1..."
26
+
27
+ - id: parallel_2
28
+ type: shell
29
+ run: |
30
+ echo "Parallel 2 failing intentionally..."
31
+ exit 1
32
+ needs: [prepare]
33
+ compensate:
34
+ id: undo_2
35
+ type: shell
36
+ run: echo "Undoing Parallel 2..."
37
+
38
+ - id: early_join
39
+ type: join
40
+ condition: any
41
+ needs: [parallel_1, parallel_2]
42
+
43
+ - id: after_early_join
44
+ type: shell
45
+ run: echo "One of the parallel steps finished! Proceeding early..."
46
+ needs: [early_join]
47
+
48
+ - id: final_join
49
+ type: join
50
+ condition: all
51
+ needs: [parallel_1, parallel_2]
52
+
53
+ - id: conclude
54
+ type: shell
55
+ run: echo "Both parallel steps finished! Workflow complete."
56
+ needs: [final_join]
57
+
58
+ compensate:
59
+ id: workflow_cleanup
60
+ type: shell
61
+ run: echo "Performing final top-level workflow cleanup..."
@@ -38,7 +38,7 @@ steps:
38
38
  ${{ steps.recall_preference.output[0].content }}
39
39
 
40
40
  What programming language should I use? Answer in one word.
41
- schema:
41
+ outputSchema:
42
42
  type: object
43
43
  properties:
44
44
  language:
@@ -0,0 +1,16 @@
1
+ name: nested-rollback-parent
2
+ description: Parent workflow that triggers a child and then fails
3
+
4
+ steps:
5
+ - id: run_child
6
+ type: workflow
7
+ path: ./child-rollback.yaml
8
+ compensate:
9
+ id: parent_post_child_undo
10
+ type: shell
11
+ run: echo "Parent cleanup after child rollback..."
12
+
13
+ - id: fail_step
14
+ type: shell
15
+ run: exit 1
16
+ needs: [run_child]
@@ -23,7 +23,7 @@ steps:
23
23
  needs: [auto_heal_demo]
24
24
  prompt: |
25
25
  Generate a list of 3 random colors. Just list them.
26
- schema:
26
+ outputSchema:
27
27
  type: object
28
28
  properties:
29
29
  colors:
@@ -7,37 +7,39 @@ steps:
7
7
  message: "Describe the workflow you want to build:"
8
8
  inputType: text
9
9
 
10
- - id: design
11
- type: llm
12
- agent: keystone-architect
10
+ - id: blueprint
11
+ type: blueprint
13
12
  needs: [get_requirements]
14
- allowClarification: true
15
- useStandardTools: true
13
+ agent: keystone-architect
16
14
  prompt: |
17
- The user wants to build the following:
18
- <user_requirements>
19
- ${{ steps.get_requirements.output }}
20
- </user_requirements>
21
- Generate the necessary Keystone workflow and optionally create an agent where appropriate.
22
- schema:
23
- type: object
24
- properties:
25
- files:
26
- type: array
27
- items:
28
- type: object
29
- properties:
30
- path:
31
- type: string
32
- content:
33
- type: string
34
- required: [path, content]
35
- required: [files]
15
+ Generate a system blueprint for the following requirements:
16
+ ${{ steps.get_requirements.output }}
17
+
18
+ - id: plan
19
+ type: workflow
20
+ needs: [blueprint]
21
+ path: scaffold-plan
22
+ inputs:
23
+ requirements: ${{ steps.get_requirements.output }}
24
+ blueprint: ${{ steps.blueprint.output }}
25
+ outputMapping:
26
+ files: files
27
+
28
+ - id: generate
29
+ type: workflow
30
+ needs: [blueprint, plan]
31
+ path: scaffold-generate
32
+ inputs:
33
+ requirements: ${{ steps.get_requirements.output }}
34
+ blueprint: ${{ steps.blueprint.output }}
35
+ files: ${{ steps.plan.outputs.files }}
36
+ outputMapping:
37
+ files: files
36
38
 
37
39
  - id: write_files
38
40
  type: file
39
- needs: [design]
40
- foreach: ${{ steps.design.output.files }}
41
+ needs: [generate]
42
+ foreach: ${{ steps.generate.outputs.files }}
41
43
  op: write
42
44
  path: ${{ item.path }}
43
45
  content: ${{ item.content }}
@@ -47,4 +49,4 @@ steps:
47
49
  needs: [write_files]
48
50
  run: |
49
51
  echo "Scaffolding complete. Files created:"
50
- echo "${{ steps.design.output.files.map(f => f.path).join('\n') }}"
52
+ echo "${{ steps.generate.outputs && steps.generate.outputs.files ? steps.generate.outputs.files.map(f => f.path).join('\n') : '(dry run: no files generated)' }}"
@@ -0,0 +1,41 @@
1
+ name: scaffold-generate
2
+ description: "Generate file contents from a file plan"
3
+
4
+ inputs:
5
+ requirements: { type: string }
6
+ blueprint: { type: object }
7
+ files: { type: array }
8
+
9
+ outputs:
10
+ files: ${{ steps.generate.output }}
11
+
12
+ steps:
13
+ - id: generate
14
+ type: llm
15
+ agent: software-engineer
16
+ allowClarification: true
17
+ useStandardTools: true
18
+ foreach: ${{ inputs.files }}
19
+ prompt: |
20
+ You are generating the content for a single file based on a system blueprint and a specific file spec.
21
+
22
+ <system_blueprint>
23
+ ${{ inputs.blueprint }}
24
+ </system_blueprint>
25
+
26
+ Overall user requirements:
27
+ ${{ inputs.requirements }}
28
+
29
+ File spec for this file:
30
+ ${{ item }}
31
+
32
+ Produce the full file content for the given path. Ensure it aligns with the architectural constraints and dependencies defined in the blueprint.
33
+ Return only JSON with fields: path, content.
34
+ outputSchema:
35
+ type: object
36
+ properties:
37
+ path:
38
+ type: string
39
+ content:
40
+ type: string
41
+ required: [path, content]
@@ -0,0 +1,53 @@
1
+ name: scaffold-plan
2
+ description: "Plan the files for a new workflow and optional agents"
3
+
4
+ inputs:
5
+ requirements: { type: string }
6
+ blueprint: { type: object }
7
+
8
+ outputs:
9
+ files: ${{ steps.plan.output.files }}
10
+
11
+ steps:
12
+ - id: plan
13
+ type: llm
14
+ agent: keystone-architect
15
+ allowClarification: true
16
+ useStandardTools: true
17
+ prompt: |
18
+ You are planning a file structure based on a system blueprint.
19
+
20
+ <system_blueprint>
21
+ ${{ inputs.blueprint }}
22
+ </system_blueprint>
23
+
24
+ User requirements:
25
+ ${{ inputs.requirements }}
26
+
27
+ Create a file plan. Follow the architecture and file list provided in the blueprint.
28
+ Use paths under:
29
+ - .keystone/workflows/ for workflows
30
+ - .keystone/workflows/agents/ for agents
31
+
32
+ Each file should include:
33
+ - path: relative path
34
+ - purpose: short purpose statement
35
+ - notes: optional implementation guidance
36
+
37
+ Return only the structured JSON required by the schema.
38
+ outputSchema:
39
+ type: object
40
+ properties:
41
+ files:
42
+ type: array
43
+ items:
44
+ type: object
45
+ properties:
46
+ path:
47
+ type: string
48
+ purpose:
49
+ type: string
50
+ notes:
51
+ type: string
52
+ required: [path, purpose]
53
+ required: [files]
@@ -10,6 +10,8 @@ export const StepStatus = {
10
10
  SUSPENDED: 'suspended',
11
11
  SKIPPED: 'skipped',
12
12
  RUNNING: 'running',
13
+ CANCELED: 'canceled',
14
+ WAITING: 'waiting', // Waiting on durable timer
13
15
  } as const;
14
16
 
15
17
  export type StepStatusType = (typeof StepStatus)[keyof typeof StepStatus];
@@ -20,6 +22,7 @@ export const WorkflowStatus = {
20
22
  PAUSED: 'paused',
21
23
  SUSPENDED: 'suspended',
22
24
  RUNNING: 'running',
25
+ CANCELED: 'canceled',
23
26
  } as const;
24
27
 
25
28
  export type WorkflowStatusType = (typeof WorkflowStatus)[keyof typeof WorkflowStatus];
@@ -1,6 +1,7 @@
1
1
  import { Box, Newline, Text, render, useInput } from 'ink';
2
2
  import React, { useState, useEffect, useCallback, useMemo } from 'react';
3
3
  import { WorkflowDb } from '../db/workflow-db.ts';
4
+ import { ConsoleLogger } from '../utils/logger.ts';
4
5
 
5
6
  interface Run {
6
7
  id: string;
@@ -10,6 +11,8 @@ interface Run {
10
11
  total_tokens?: number;
11
12
  }
12
13
 
14
+ const logger = new ConsoleLogger();
15
+
13
16
  const Dashboard = () => {
14
17
  const [runs, setRuns] = useState<Run[]>([]);
15
18
  const [loading, setLoading] = useState(true);
@@ -50,9 +53,7 @@ const Dashboard = () => {
50
53
  );
51
54
  setRuns(runsWithUsage);
52
55
  } catch (error) {
53
- // Dashboard is UI, console.error is acceptable but let's be consistent if possible.
54
- // For now we keep it as is or could use a toast.
55
- console.error('Failed to fetch runs:', error);
56
+ logger.error(`Failed to fetch runs: ${String(error)}`);
56
57
  } finally {
57
58
  setLoading(false);
58
59
  }
@@ -0,0 +1,36 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import { join, relative } from 'node:path';
3
+
4
+ /**
5
+ * Bun macro to read all files in a directory and return them as a map.
6
+ * This runs at build-time.
7
+ */
8
+ export function bundleAssets(): Record<string, string> {
9
+ const dirPath = process.env.ASSETS_DIR || '.keystone';
10
+ const assets: Record<string, string> = {};
11
+
12
+ if (!existsSync(dirPath)) {
13
+ // If directory doesn't exist, return empty (e.g. during dev of CLI itself)
14
+ return {};
15
+ }
16
+
17
+ function walk(currentDir: string) {
18
+ const files = readdirSync(currentDir);
19
+ for (const file of files) {
20
+ const fullPath = join(currentDir, file);
21
+ const stats = statSync(fullPath);
22
+
23
+ if (stats.isDirectory()) {
24
+ walk(fullPath);
25
+ } else if (stats.isFile()) {
26
+ // Use relative path as key
27
+ const relPath = relative(dirPath, fullPath);
28
+ assets[relPath] = readFileSync(fullPath, 'utf8');
29
+ }
30
+ }
31
+ }
32
+
33
+ walk(dirPath);
34
+
35
+ return assets;
36
+ }