@kody-ade/kody-engine-lite 0.1.63 → 0.1.65
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/dist/agent-runner.d.ts +4 -0
- package/dist/agent-runner.js +122 -0
- package/dist/bin/cli.js +162 -8
- package/dist/ci/parse-inputs.d.ts +6 -0
- package/dist/ci/parse-inputs.js +76 -0
- package/dist/ci/parse-safety.d.ts +6 -0
- package/dist/ci/parse-safety.js +22 -0
- package/dist/cli/args.d.ts +13 -0
- package/dist/cli/args.js +42 -0
- package/dist/cli/litellm.d.ts +2 -0
- package/dist/cli/litellm.js +85 -0
- package/dist/cli/task-resolution.d.ts +2 -0
- package/dist/cli/task-resolution.js +41 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.js +72 -0
- package/dist/context.d.ts +4 -0
- package/dist/context.js +83 -0
- package/dist/definitions.d.ts +3 -0
- package/dist/definitions.js +59 -0
- package/dist/entry.d.ts +1 -0
- package/dist/entry.js +236 -0
- package/dist/git-utils.d.ts +13 -0
- package/dist/git-utils.js +174 -0
- package/dist/github-api.d.ts +14 -0
- package/dist/github-api.js +114 -0
- package/dist/kody-utils.d.ts +1 -0
- package/dist/kody-utils.js +9 -0
- package/dist/learning/auto-learn.d.ts +2 -0
- package/dist/learning/auto-learn.js +169 -0
- package/dist/logger.d.ts +14 -0
- package/dist/logger.js +51 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.js +20 -0
- package/dist/observer.d.ts +9 -0
- package/dist/observer.js +80 -0
- package/dist/pipeline/complexity.d.ts +3 -0
- package/dist/pipeline/complexity.js +12 -0
- package/dist/pipeline/executor-registry.d.ts +3 -0
- package/dist/pipeline/executor-registry.js +20 -0
- package/dist/pipeline/hooks.d.ts +17 -0
- package/dist/pipeline/hooks.js +110 -0
- package/dist/pipeline/questions.d.ts +2 -0
- package/dist/pipeline/questions.js +44 -0
- package/dist/pipeline/runner-selection.d.ts +2 -0
- package/dist/pipeline/runner-selection.js +13 -0
- package/dist/pipeline/state.d.ts +4 -0
- package/dist/pipeline/state.js +37 -0
- package/dist/pipeline.d.ts +3 -0
- package/dist/pipeline.js +213 -0
- package/dist/preflight.d.ts +1 -0
- package/dist/preflight.js +69 -0
- package/dist/retrospective.d.ts +26 -0
- package/dist/retrospective.js +211 -0
- package/dist/stages/agent.d.ts +2 -0
- package/dist/stages/agent.js +94 -0
- package/dist/stages/gate.d.ts +2 -0
- package/dist/stages/gate.js +32 -0
- package/dist/stages/review.d.ts +2 -0
- package/dist/stages/review.js +32 -0
- package/dist/stages/ship.d.ts +3 -0
- package/dist/stages/ship.js +154 -0
- package/dist/stages/verify.d.ts +2 -0
- package/dist/stages/verify.js +94 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.js +1 -0
- package/dist/validators.d.ts +8 -0
- package/dist/validators.js +42 -0
- package/dist/verify-runner.d.ts +11 -0
- package/dist/verify-runner.js +110 -0
- package/kody.config.schema.json +66 -0
- package/package.json +8 -9
- package/prompts/taskify.md +5 -0
- package/templates/kody.yml +6 -1
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type StageName = "taskify" | "plan" | "build" | "verify" | "review" | "review-fix" | "ship";
|
|
2
|
+
export type StageType = "agent" | "gate" | "deterministic";
|
|
3
|
+
export type PipelineState = "pending" | "running" | "completed" | "failed" | "timeout";
|
|
4
|
+
export interface StageDefinition {
|
|
5
|
+
name: StageName;
|
|
6
|
+
type: StageType;
|
|
7
|
+
modelTier: "cheap" | "mid" | "strong";
|
|
8
|
+
timeout: number;
|
|
9
|
+
maxRetries: number;
|
|
10
|
+
outputFile?: string;
|
|
11
|
+
retryWithAgent?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface StageState {
|
|
14
|
+
state: PipelineState;
|
|
15
|
+
startedAt?: string;
|
|
16
|
+
completedAt?: string;
|
|
17
|
+
retries: number;
|
|
18
|
+
error?: string;
|
|
19
|
+
outputFile?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PipelineStatus {
|
|
22
|
+
taskId: string;
|
|
23
|
+
state: "running" | "completed" | "failed";
|
|
24
|
+
stages: Record<StageName, StageState>;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
}
|
|
28
|
+
export interface StageResult {
|
|
29
|
+
outcome: "completed" | "failed" | "timed_out";
|
|
30
|
+
outputFile?: string;
|
|
31
|
+
error?: string;
|
|
32
|
+
retries: number;
|
|
33
|
+
}
|
|
34
|
+
export interface AgentResult {
|
|
35
|
+
outcome: "completed" | "failed" | "timed_out";
|
|
36
|
+
output?: string;
|
|
37
|
+
error?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface AgentRunnerOptions {
|
|
40
|
+
cwd?: string;
|
|
41
|
+
env?: Record<string, string>;
|
|
42
|
+
}
|
|
43
|
+
export interface AgentRunner {
|
|
44
|
+
run(stageName: string, prompt: string, model: string, timeout: number, taskDir: string, options?: AgentRunnerOptions): Promise<AgentResult>;
|
|
45
|
+
healthCheck(): Promise<boolean>;
|
|
46
|
+
}
|
|
47
|
+
export interface PipelineContext {
|
|
48
|
+
taskId: string;
|
|
49
|
+
taskDir: string;
|
|
50
|
+
projectDir: string;
|
|
51
|
+
runners: Record<string, AgentRunner>;
|
|
52
|
+
input: {
|
|
53
|
+
mode: "full" | "rerun" | "status";
|
|
54
|
+
fromStage?: string;
|
|
55
|
+
dryRun?: boolean;
|
|
56
|
+
issueNumber?: number;
|
|
57
|
+
feedback?: string;
|
|
58
|
+
local?: boolean;
|
|
59
|
+
complexity?: "low" | "medium" | "high";
|
|
60
|
+
};
|
|
61
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ValidationResult {
|
|
2
|
+
valid: boolean;
|
|
3
|
+
error?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function stripFences(content: string): string;
|
|
6
|
+
export declare function validateTaskJson(content: string): ValidationResult;
|
|
7
|
+
export declare function validatePlanMd(content: string): ValidationResult;
|
|
8
|
+
export declare function validateReviewMd(content: string): ValidationResult;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const REQUIRED_TASK_FIELDS = [
|
|
2
|
+
"task_type",
|
|
3
|
+
"title",
|
|
4
|
+
"description",
|
|
5
|
+
"scope",
|
|
6
|
+
"risk_level",
|
|
7
|
+
];
|
|
8
|
+
export function stripFences(content) {
|
|
9
|
+
return content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
10
|
+
}
|
|
11
|
+
export function validateTaskJson(content) {
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(stripFences(content));
|
|
14
|
+
for (const field of REQUIRED_TASK_FIELDS) {
|
|
15
|
+
if (!(field in parsed)) {
|
|
16
|
+
return { valid: false, error: `Missing field: ${field}` };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return { valid: true };
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return {
|
|
23
|
+
valid: false,
|
|
24
|
+
error: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function validatePlanMd(content) {
|
|
29
|
+
if (content.length < 10) {
|
|
30
|
+
return { valid: false, error: "Plan is too short (< 10 chars)" };
|
|
31
|
+
}
|
|
32
|
+
if (!/^##\s+\w+/m.test(content)) {
|
|
33
|
+
return { valid: false, error: "Plan has no markdown h2 sections" };
|
|
34
|
+
}
|
|
35
|
+
return { valid: true };
|
|
36
|
+
}
|
|
37
|
+
export function validateReviewMd(content) {
|
|
38
|
+
if (/pass/i.test(content) || /fail/i.test(content)) {
|
|
39
|
+
return { valid: true };
|
|
40
|
+
}
|
|
41
|
+
return { valid: false, error: "Review must contain 'pass' or 'fail'" };
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface VerifyResult {
|
|
2
|
+
pass: boolean;
|
|
3
|
+
errors: string[];
|
|
4
|
+
summary: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Parse a command string into [executable, ...args], respecting quoted arguments.
|
|
8
|
+
* e.g., 'pnpm -s "test:unit"' → ["pnpm", "-s", "test:unit"]
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseCommand(cmd: string): string[];
|
|
11
|
+
export declare function runQualityGates(taskDir: string, projectRoot?: string): VerifyResult;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
|
+
import { getProjectConfig, VERIFY_COMMAND_TIMEOUT_MS } from "./config.js";
|
|
3
|
+
import { logger } from "./logger.js";
|
|
4
|
+
function isExecError(err) {
|
|
5
|
+
return typeof err === "object" && err !== null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Parse a command string into [executable, ...args], respecting quoted arguments.
|
|
9
|
+
* e.g., 'pnpm -s "test:unit"' → ["pnpm", "-s", "test:unit"]
|
|
10
|
+
*/
|
|
11
|
+
export function parseCommand(cmd) {
|
|
12
|
+
const parts = [];
|
|
13
|
+
let current = "";
|
|
14
|
+
let inQuote = null;
|
|
15
|
+
for (const ch of cmd) {
|
|
16
|
+
if (inQuote) {
|
|
17
|
+
if (ch === inQuote) {
|
|
18
|
+
inQuote = null;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
current += ch;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else if (ch === '"' || ch === "'") {
|
|
25
|
+
inQuote = ch;
|
|
26
|
+
}
|
|
27
|
+
else if (/\s/.test(ch)) {
|
|
28
|
+
if (current) {
|
|
29
|
+
parts.push(current);
|
|
30
|
+
current = "";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
current += ch;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (current)
|
|
38
|
+
parts.push(current);
|
|
39
|
+
if (inQuote)
|
|
40
|
+
logger.warn(`Unclosed quote in command: ${cmd}`);
|
|
41
|
+
return parts;
|
|
42
|
+
}
|
|
43
|
+
function runCommand(cmd, cwd, timeout) {
|
|
44
|
+
const parts = parseCommand(cmd);
|
|
45
|
+
if (parts.length === 0) {
|
|
46
|
+
return { success: true, output: "", timedOut: false };
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const output = execFileSync(parts[0], parts.slice(1), {
|
|
50
|
+
cwd,
|
|
51
|
+
timeout,
|
|
52
|
+
encoding: "utf-8",
|
|
53
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
54
|
+
env: { ...process.env, FORCE_COLOR: "0" },
|
|
55
|
+
});
|
|
56
|
+
return { success: true, output: output ?? "", timedOut: false };
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
const stdout = isExecError(err) ? err.stdout ?? "" : "";
|
|
60
|
+
const stderr = isExecError(err) ? err.stderr ?? "" : "";
|
|
61
|
+
const killed = isExecError(err) ? !!err.killed : false;
|
|
62
|
+
return { success: false, output: `${stdout}${stderr}`, timedOut: killed };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function parseErrors(output) {
|
|
66
|
+
const errors = [];
|
|
67
|
+
for (const line of output.split("\n")) {
|
|
68
|
+
if (/error|Error|ERROR|failed|Failed|FAIL|warning:|Warning:|WARN/i.test(line)) {
|
|
69
|
+
errors.push(line.slice(0, 500));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return errors;
|
|
73
|
+
}
|
|
74
|
+
function extractSummary(output, cmdName) {
|
|
75
|
+
const summaryPatterns = /Test Suites|Tests|Coverage|ERRORS|FAILURES|success|completed/i;
|
|
76
|
+
const lines = output.split("\n").filter((l) => summaryPatterns.test(l));
|
|
77
|
+
return lines.slice(-3).map((l) => `[${cmdName}] ${l.trim()}`);
|
|
78
|
+
}
|
|
79
|
+
export function runQualityGates(taskDir, projectRoot) {
|
|
80
|
+
const config = getProjectConfig();
|
|
81
|
+
const cwd = projectRoot ?? process.cwd();
|
|
82
|
+
const allErrors = [];
|
|
83
|
+
const allSummary = [];
|
|
84
|
+
let allPass = true;
|
|
85
|
+
const commands = [
|
|
86
|
+
{ name: "typecheck", cmd: config.quality.typecheck },
|
|
87
|
+
{ name: "test", cmd: config.quality.testUnit },
|
|
88
|
+
];
|
|
89
|
+
if (config.quality.lint) {
|
|
90
|
+
commands.push({ name: "lint", cmd: config.quality.lint });
|
|
91
|
+
}
|
|
92
|
+
for (const { name, cmd } of commands) {
|
|
93
|
+
if (!cmd)
|
|
94
|
+
continue;
|
|
95
|
+
logger.info(` Running ${name}: ${cmd}`);
|
|
96
|
+
const result = runCommand(cmd, cwd, VERIFY_COMMAND_TIMEOUT_MS);
|
|
97
|
+
if (result.timedOut) {
|
|
98
|
+
allErrors.push(`${name}: timed out after ${VERIFY_COMMAND_TIMEOUT_MS / 1000}s`);
|
|
99
|
+
allPass = false;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!result.success) {
|
|
103
|
+
allPass = false;
|
|
104
|
+
const errors = parseErrors(result.output);
|
|
105
|
+
allErrors.push(...errors.map((e) => `[${name}] ${e}`));
|
|
106
|
+
}
|
|
107
|
+
allSummary.push(...extractSummary(result.output, name));
|
|
108
|
+
}
|
|
109
|
+
return { pass: allPass, errors: allErrors, summary: allSummary };
|
|
110
|
+
}
|
package/kody.config.schema.json
CHANGED
|
@@ -128,6 +128,72 @@
|
|
|
128
128
|
}
|
|
129
129
|
},
|
|
130
130
|
"additionalProperties": false
|
|
131
|
+
},
|
|
132
|
+
"mcp": {
|
|
133
|
+
"type": "object",
|
|
134
|
+
"description": "MCP (Model Context Protocol) server configuration. Enables external tools like browser automation.",
|
|
135
|
+
"properties": {
|
|
136
|
+
"enabled": {
|
|
137
|
+
"type": "boolean",
|
|
138
|
+
"description": "Enable MCP server integration",
|
|
139
|
+
"default": false
|
|
140
|
+
},
|
|
141
|
+
"servers": {
|
|
142
|
+
"type": "object",
|
|
143
|
+
"description": "Named MCP server definitions. Each key is a server name.",
|
|
144
|
+
"additionalProperties": {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"properties": {
|
|
147
|
+
"command": {
|
|
148
|
+
"type": "string",
|
|
149
|
+
"description": "Command to start the MCP server (e.g., 'npx')"
|
|
150
|
+
},
|
|
151
|
+
"args": {
|
|
152
|
+
"type": "array",
|
|
153
|
+
"items": { "type": "string" },
|
|
154
|
+
"description": "Command arguments"
|
|
155
|
+
},
|
|
156
|
+
"env": {
|
|
157
|
+
"type": "object",
|
|
158
|
+
"additionalProperties": { "type": "string" },
|
|
159
|
+
"description": "Environment variables for the server process"
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"required": ["command"]
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"stages": {
|
|
166
|
+
"type": "array",
|
|
167
|
+
"items": { "type": "string" },
|
|
168
|
+
"description": "Stages that can use MCP tools. Defaults to [\"build\", \"verify\", \"review\", \"review-fix\"]",
|
|
169
|
+
"default": ["build", "verify", "review", "review-fix"]
|
|
170
|
+
},
|
|
171
|
+
"devServer": {
|
|
172
|
+
"type": "object",
|
|
173
|
+
"description": "Dev server configuration for browser tool verification",
|
|
174
|
+
"properties": {
|
|
175
|
+
"command": {
|
|
176
|
+
"type": "string",
|
|
177
|
+
"description": "Command to start the dev server (e.g., 'pnpm dev')"
|
|
178
|
+
},
|
|
179
|
+
"url": {
|
|
180
|
+
"type": "string",
|
|
181
|
+
"description": "URL where the dev server will be accessible (e.g., 'http://localhost:3000')"
|
|
182
|
+
},
|
|
183
|
+
"readyPattern": {
|
|
184
|
+
"type": "string",
|
|
185
|
+
"description": "Regex pattern to match in stdout when server is ready. Default: 'Ready in|compiled|started server|Local:'"
|
|
186
|
+
},
|
|
187
|
+
"readyTimeout": {
|
|
188
|
+
"type": "number",
|
|
189
|
+
"description": "Seconds to wait for the server to be ready. Default: 30"
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
"required": ["command", "url"]
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
"required": ["enabled", "servers"],
|
|
196
|
+
"additionalProperties": false
|
|
131
197
|
}
|
|
132
198
|
},
|
|
133
199
|
"additionalProperties": false
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine-lite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.65",
|
|
4
4
|
"description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,13 +13,6 @@
|
|
|
13
13
|
"templates",
|
|
14
14
|
"kody.config.schema.json"
|
|
15
15
|
],
|
|
16
|
-
"scripts": {
|
|
17
|
-
"kody": "tsx src/entry.ts",
|
|
18
|
-
"build": "tsup",
|
|
19
|
-
"test": "vitest run",
|
|
20
|
-
"typecheck": "tsc --noEmit",
|
|
21
|
-
"prepublishOnly": "pnpm build"
|
|
22
|
-
},
|
|
23
16
|
"dependencies": {
|
|
24
17
|
"dotenv": "^16.4.7"
|
|
25
18
|
},
|
|
@@ -32,5 +25,11 @@
|
|
|
32
25
|
},
|
|
33
26
|
"engines": {
|
|
34
27
|
"node": ">=22"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"kody": "tsx src/entry.ts",
|
|
31
|
+
"build": "tsup",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"typecheck": "tsc --noEmit"
|
|
35
34
|
}
|
|
36
|
-
}
|
|
35
|
+
}
|
package/prompts/taskify.md
CHANGED
|
@@ -18,9 +18,14 @@ Required JSON format:
|
|
|
18
18
|
"description": "Clear description of what the task requires",
|
|
19
19
|
"scope": ["list", "of", "exact/file/paths", "affected"],
|
|
20
20
|
"risk_level": "low | medium | high",
|
|
21
|
+
"hasUI": true,
|
|
21
22
|
"questions": []
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
hasUI heuristics:
|
|
26
|
+
- true: task touches frontend files (.tsx, .jsx, .vue, .svelte, .css, .scss, .html), UI components, pages, layouts, or styles
|
|
27
|
+
- false: task is purely backend, CLI, API, database, config, docs, or infrastructure
|
|
28
|
+
|
|
24
29
|
Risk level heuristics:
|
|
25
30
|
- low: single file change, no breaking changes, docs, config, isolated scripts, test additions, style changes
|
|
26
31
|
- medium: multiple files, possible side effects, API changes, new dependencies, refactoring existing logic
|
package/templates/kody.yml
CHANGED
|
@@ -224,6 +224,9 @@ jobs:
|
|
|
224
224
|
- name: Install Claude Code CLI
|
|
225
225
|
run: npm install -g @anthropic-ai/claude-code
|
|
226
226
|
|
|
227
|
+
- name: Install LiteLLM proxy
|
|
228
|
+
run: pip install 'litellm[proxy]'
|
|
229
|
+
|
|
227
230
|
- name: Configure git
|
|
228
231
|
run: |
|
|
229
232
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
@@ -232,6 +235,7 @@ jobs:
|
|
|
232
235
|
- name: Run Kody pipeline
|
|
233
236
|
env:
|
|
234
237
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
238
|
+
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
235
239
|
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
|
236
240
|
TASK_ID: ${{ github.event.inputs.task_id || needs.parse.outputs.task_id }}
|
|
237
241
|
MODE: ${{ github.event.inputs.mode || needs.parse.outputs.mode || 'full' }}
|
|
@@ -254,7 +258,8 @@ jobs:
|
|
|
254
258
|
[ -n "$TASK_ID" ] && ARGS="$ARGS --task-id $TASK_ID"
|
|
255
259
|
[ -n "$PR_NUMBER" ] && ARGS="$ARGS --pr-number $PR_NUMBER"
|
|
256
260
|
[ -n "$FROM_STAGE" ] && ARGS="$ARGS --from $FROM_STAGE"
|
|
257
|
-
# FEEDBACK is passed via env var
|
|
261
|
+
# FEEDBACK is also passed via env var (avoids shell escaping issues)
|
|
262
|
+
[ -n "$FEEDBACK" ] && ARGS="$ARGS --feedback \"$FEEDBACK\""
|
|
258
263
|
[ "$DRY_RUN" = "true" ] && ARGS="$ARGS --dry-run"
|
|
259
264
|
kody-engine-lite $CMD $ARGS
|
|
260
265
|
fi
|