@kody-ade/kody-engine 0.2.51 → 0.2.53

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/README.md CHANGED
@@ -31,17 +31,42 @@ Every top-level command is its own auto-discovered executable (`run`, `fix`, `fi
31
31
  ## Commands
32
32
 
33
33
  ```
34
- kody2 run --issue <N> # implement an issue
35
- kody2 plan --issue <N> # produce a plan artifact for run
36
- kody2 fix --pr <N> [--feedback ...] # apply PR review feedback
37
- kody2 fix-ci --pr <N> [--run-id <ID>] # fix failing CI
38
- kody2 resolve --pr <N> # merge default branch in, resolve conflicts
39
- kody2 sync --pr <N> # merge default branch into PR branch (no agent)
40
- kody2 review --pr <N> # read-only structured PR review
41
- kody2 ci --issue <N> # CI preflight + run (issue/PR automation)
42
- kody2 chat [--session <id>] # dashboard-driven chat session
34
+ # issue-triggered, agent writes code
35
+ kody2 run --issue <N> # implement an issue end-to-end (branch, code, PR)
36
+
37
+ # issue-triggered, agent read-only (no commits)
38
+ kody2 plan --issue <N> # produce a plan artifact for run
39
+ kody2 research --issue <N> # map repo context, surface questions/gaps
40
+
41
+ # PR-triggered, agent writes code
42
+ kody2 fix --pr <N> [--feedback ...] # apply PR review feedback
43
+ kody2 fix-ci --pr <N> [--run-id <ID>] # fix failing CI
44
+ kody2 resolve --pr <N> # merge default branch in, resolve conflicts
45
+
46
+ # PR-triggered, agent read-only
47
+ kody2 review --pr <N> # structured diff review (fast, diff only)
48
+ kody2 ui-review --pr <N> [--preview-url <URL>] # UI/UX review — browses preview via Playwright
49
+
50
+ # no agent (deterministic)
51
+ kody2 sync --pr <N> # merge default branch into PR branch
52
+ kody2 release --mode prepare|finalize [--bump patch|minor|major] [--dry-run]
53
+ kody2 init [--force] # scaffold consumer repo
54
+ kody2 orchestrate --issue <N> [--flow plan-build-review] # chain plan → run → review → fix
55
+
56
+ # engine entrypoints
57
+ kody2 ci --issue <N> # CI preflight + auto-dispatch from GHA event
58
+ kody2 chat [--session <id>] # dashboard-driven chat session
43
59
  ```
44
60
 
61
+ ### `ui-review`
62
+
63
+ `ui-review` adds UI/UX verification to the review surface. It runs the usual diff-based review AND drives the running preview deployment via the Playwright CLI — writing a throwaway spec under `.kody2/ui-review/`, running it, capturing screenshots, and folding the observed behavior into the review verdict.
64
+
65
+ - Preview URL resolution: `--preview-url` flag → `$PREVIEW_URL` → `http://localhost:3000`.
66
+ - Credentials: committed in `.kody2/qa-guide.md` (scaffolded by `kody2 init` when a UI is detected, with `CHANGE_ME` placeholders). The agent reads the guide and uses any credentials it finds.
67
+ - Auto-discovery: routes, roles, login page, admin path, Payload CMS collections, API routes, env vars — fed to the agent so it knows *what* to browse without you spelling it out.
68
+ - Falls back to a diff-only review when the preview URL is unreachable.
69
+
45
70
  `kody2 chat` reads `.kody/sessions/<id>.jsonl`, runs one agent turn, appends
46
71
  the reply, and writes `chat.message` + `chat.done` events to
47
72
  `.kody/events/<id>.jsonl` (plus optional HTTP push to a dashboard ingest URL).
package/dist/bin/kody2.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@kody-ade/kody-engine",
6
- version: "0.2.51",
6
+ version: "0.2.53",
7
7
  description: "kody2 \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -593,24 +593,12 @@ function autoDispatch(opts) {
593
593
  if (!sub) {
594
594
  return asDispatch(defaultExec, targetNum);
595
595
  }
596
- if (sub === "orchestrate" || sub === "orchestrator") {
597
- const flow = extractFlowName(afterTag);
598
- if (flow) {
599
- return {
600
- executable: `orchestrator-${flow}`,
601
- cliArgs: { issue: targetNum, flow },
602
- target: targetNum
603
- };
604
- }
605
- return {
606
- executable: "orchestrator-plan-build-review",
607
- cliArgs: { issue: targetNum, flow: "plan-build-review" },
608
- target: targetNum
609
- };
610
- }
611
596
  if (sub === "build") {
612
597
  return { executable: "run", cliArgs: { issue: targetNum }, target: targetNum };
613
598
  }
599
+ if (sub === "orchestrate" || sub === "orchestrator") {
600
+ return { executable: "bug", cliArgs: { issue: targetNum }, target: targetNum };
601
+ }
614
602
  return asDispatch(sub, targetNum);
615
603
  }
616
604
  function asDispatch(executable, target) {
@@ -626,10 +614,6 @@ function extractSubcommand(afterTag) {
626
614
  if (!match) return null;
627
615
  return match[1];
628
616
  }
629
- function extractFlowName(afterTag) {
630
- const match = afterTag.match(/--flow[=\s]+([a-z][a-z0-9-]{0,60})/);
631
- return match ? match[1] : null;
632
- }
633
617
  function extractFeedback(afterTag) {
634
618
  const cleaned = afterTag.replace(/^(fix|please|kindly)(?:[\s:,.-]+|$)/i, "").trim();
635
619
  return cleaned.length > 0 ? cleaned : void 0;
@@ -1209,7 +1193,7 @@ var advanceFlow = async (ctx, profile) => {
1209
1193
  );
1210
1194
  }
1211
1195
  }
1212
- const body = "@kody2 orchestrate";
1196
+ const body = `@kody2 ${flow.name}`;
1213
1197
  try {
1214
1198
  execFileSync3("gh", ["issue", "comment", String(flow.issueNumber), "--body", body], {
1215
1199
  timeout: API_TIMEOUT_MS2,
@@ -2651,6 +2635,188 @@ function computeFailureReason(ctx) {
2651
2635
 
2652
2636
  // src/scripts/finishFlow.ts
2653
2637
  import { execFileSync as execFileSync9 } from "child_process";
2638
+
2639
+ // src/registry.ts
2640
+ import * as fs14 from "fs";
2641
+ import * as path13 from "path";
2642
+ function getExecutablesRoot() {
2643
+ const here = path13.dirname(new URL(import.meta.url).pathname);
2644
+ const candidates = [
2645
+ path13.join(here, "executables"),
2646
+ // dev: src/
2647
+ path13.join(here, "..", "executables"),
2648
+ // built: dist/bin → dist/executables
2649
+ path13.join(here, "..", "src", "executables")
2650
+ // fallback
2651
+ ];
2652
+ for (const c of candidates) {
2653
+ if (fs14.existsSync(c) && fs14.statSync(c).isDirectory()) return c;
2654
+ }
2655
+ return candidates[0];
2656
+ }
2657
+ function listExecutables(root = getExecutablesRoot()) {
2658
+ if (!fs14.existsSync(root)) return [];
2659
+ const entries = fs14.readdirSync(root, { withFileTypes: true });
2660
+ const out = [];
2661
+ for (const ent of entries) {
2662
+ if (!ent.isDirectory()) continue;
2663
+ const profilePath = path13.join(root, ent.name, "profile.json");
2664
+ if (fs14.existsSync(profilePath) && fs14.statSync(profilePath).isFile()) {
2665
+ out.push({ name: ent.name, profilePath });
2666
+ }
2667
+ }
2668
+ return out.sort((a, b) => a.name.localeCompare(b.name));
2669
+ }
2670
+ function hasExecutable(name, root = getExecutablesRoot()) {
2671
+ if (!isSafeName(name)) return false;
2672
+ const profilePath = path13.join(root, name, "profile.json");
2673
+ return fs14.existsSync(profilePath) && fs14.statSync(profilePath).isFile();
2674
+ }
2675
+ function isSafeName(name) {
2676
+ return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
2677
+ }
2678
+ function parseGenericFlags(argv) {
2679
+ const args = {};
2680
+ const positional = [];
2681
+ for (let i = 0; i < argv.length; i++) {
2682
+ const arg = argv[i];
2683
+ if (!arg.startsWith("--")) {
2684
+ positional.push(arg);
2685
+ continue;
2686
+ }
2687
+ const key = arg.slice(2);
2688
+ const next = argv[i + 1];
2689
+ const value = next !== void 0 && !next.startsWith("--") ? (i++, next) : true;
2690
+ args[key] = value;
2691
+ if (key.includes("-")) {
2692
+ const camel = key.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
2693
+ if (camel !== key && args[camel] === void 0) args[camel] = value;
2694
+ }
2695
+ }
2696
+ if (positional.length > 0) args._ = positional;
2697
+ return args;
2698
+ }
2699
+
2700
+ // src/lifecycleLabels.ts
2701
+ var KODY_LABEL_PREFIX = "kody:";
2702
+ function collectProfileLabels() {
2703
+ const byLabel = /* @__PURE__ */ new Map();
2704
+ for (const exe of listExecutables()) {
2705
+ let profile;
2706
+ try {
2707
+ profile = loadProfile(exe.profilePath);
2708
+ } catch {
2709
+ continue;
2710
+ }
2711
+ for (const entry of [...profile.scripts.preflight, ...profile.scripts.postflight]) {
2712
+ const spec = extractLabelSpec(entry);
2713
+ if (spec) byLabel.set(spec.label, spec);
2714
+ }
2715
+ }
2716
+ return [...byLabel.values()];
2717
+ }
2718
+ function extractLabelSpec(entry) {
2719
+ const w = entry.with;
2720
+ if (!w) return null;
2721
+ const label = typeof w.label === "string" ? w.label : null;
2722
+ if (!label || !label.startsWith(KODY_LABEL_PREFIX)) return null;
2723
+ return {
2724
+ label,
2725
+ color: typeof w.color === "string" ? w.color : void 0,
2726
+ description: typeof w.description === "string" ? w.description : void 0
2727
+ };
2728
+ }
2729
+ function ensureLabels(cwd) {
2730
+ const result = { created: [], failed: [] };
2731
+ for (const spec of collectProfileLabels()) {
2732
+ try {
2733
+ createLabelInRepo(spec, cwd);
2734
+ result.created.push(spec.label);
2735
+ } catch (err) {
2736
+ result.failed.push({ label: spec.label, reason: errMsg(err) });
2737
+ }
2738
+ }
2739
+ return result;
2740
+ }
2741
+ function getIssueLabels(issueNumber, cwd) {
2742
+ try {
2743
+ const output = gh2(
2744
+ ["issue", "view", String(issueNumber), "--json", "labels", "--jq", ".labels[].name"],
2745
+ { cwd }
2746
+ );
2747
+ return output.split("\n").filter(Boolean);
2748
+ } catch {
2749
+ return [];
2750
+ }
2751
+ }
2752
+ function addLabel(issueNumber, label, cwd) {
2753
+ gh2(["issue", "edit", String(issueNumber), "--add-label", label], { cwd });
2754
+ }
2755
+ function removeLabel(issueNumber, label, cwd) {
2756
+ try {
2757
+ gh2(["issue", "edit", String(issueNumber), "--remove-label", label], { cwd });
2758
+ } catch {
2759
+ }
2760
+ }
2761
+ function createLabelInRepo(spec, cwd) {
2762
+ const args = ["label", "create", spec.label, "--force"];
2763
+ if (spec.color) args.push("--color", spec.color);
2764
+ if (spec.description) args.push("--description", spec.description);
2765
+ gh2(args, { cwd });
2766
+ }
2767
+ function setKodyLabel(issueNumber, spec, cwd) {
2768
+ const target = spec.label;
2769
+ if (!target.startsWith(KODY_LABEL_PREFIX)) {
2770
+ process.stderr.write(
2771
+ `[kody2] setKodyLabel: refusing to set non-kody label "${target}"
2772
+ `
2773
+ );
2774
+ return;
2775
+ }
2776
+ const present = getIssueLabels(issueNumber, cwd);
2777
+ for (const label of present) {
2778
+ if (label !== target && label.startsWith(KODY_LABEL_PREFIX)) {
2779
+ removeLabel(issueNumber, label, cwd);
2780
+ }
2781
+ }
2782
+ try {
2783
+ addLabel(issueNumber, target, cwd);
2784
+ } catch (err) {
2785
+ if (looksLikeMissingLabel(err)) {
2786
+ try {
2787
+ createLabelInRepo(spec, cwd);
2788
+ addLabel(issueNumber, target, cwd);
2789
+ return;
2790
+ } catch (retryErr) {
2791
+ process.stderr.write(
2792
+ `[kody2] setKodyLabel: create+retry failed for ${target} on #${issueNumber}: ${errMsg(retryErr)}
2793
+ `
2794
+ );
2795
+ return;
2796
+ }
2797
+ }
2798
+ process.stderr.write(
2799
+ `[kody2] setKodyLabel: failed to add ${target} on #${issueNumber}: ${errMsg(err)}
2800
+ `
2801
+ );
2802
+ }
2803
+ }
2804
+ function looksLikeMissingLabel(err) {
2805
+ const msg = errMsg(err).toLowerCase();
2806
+ return msg.includes("not found") || msg.includes("could not add label") || msg.includes("could not resolve to a label");
2807
+ }
2808
+ function errMsg(err) {
2809
+ if (err instanceof Error) return err.message;
2810
+ if (typeof err === "object" && err !== null) {
2811
+ const e = err;
2812
+ const stderr = e.stderr?.toString().trim();
2813
+ if (stderr) return stderr;
2814
+ if (e.message) return e.message;
2815
+ }
2816
+ return String(err);
2817
+ }
2818
+
2819
+ // src/scripts/finishFlow.ts
2654
2820
  var API_TIMEOUT_MS5 = 3e4;
2655
2821
  var STATUS_ICON = {
2656
2822
  "review-passed": "\u2705",
@@ -2665,6 +2831,18 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
2665
2831
  const flowName = state?.flow?.name ?? "(unknown flow)";
2666
2832
  if (state) state.flow = void 0;
2667
2833
  if (!issueNumber) return;
2834
+ const label = typeof args?.label === "string" ? args.label : void 0;
2835
+ if (label && label.startsWith(KODY_LABEL_PREFIX)) {
2836
+ setKodyLabel(
2837
+ issueNumber,
2838
+ {
2839
+ label,
2840
+ color: typeof args?.color === "string" ? args.color : void 0,
2841
+ description: typeof args?.description === "string" ? args.description : void 0
2842
+ },
2843
+ ctx.cwd
2844
+ );
2845
+ }
2668
2846
  const icon = STATUS_ICON[reason] ?? "\u2139\uFE0F";
2669
2847
  const prSuffix = state?.core.prUrl ? `
2670
2848
 
@@ -2788,7 +2966,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
2788
2966
 
2789
2967
  // src/gha.ts
2790
2968
  import { execFileSync as execFileSync11 } from "child_process";
2791
- import * as fs14 from "fs";
2969
+ import * as fs15 from "fs";
2792
2970
  function getRunUrl() {
2793
2971
  const server = process.env.GITHUB_SERVER_URL;
2794
2972
  const repo = process.env.GITHUB_REPOSITORY;
@@ -2799,10 +2977,10 @@ function getRunUrl() {
2799
2977
  function reactToTriggerComment(cwd) {
2800
2978
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
2801
2979
  const eventPath = process.env.GITHUB_EVENT_PATH;
2802
- if (!eventPath || !fs14.existsSync(eventPath)) return;
2980
+ if (!eventPath || !fs15.existsSync(eventPath)) return;
2803
2981
  let event = null;
2804
2982
  try {
2805
- event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
2983
+ event = JSON.parse(fs15.readFileSync(eventPath, "utf-8"));
2806
2984
  } catch {
2807
2985
  return;
2808
2986
  }
@@ -3045,67 +3223,6 @@ import { execFileSync as execFileSync13 } from "child_process";
3045
3223
  import * as fs17 from "fs";
3046
3224
  import * as path15 from "path";
3047
3225
 
3048
- // src/registry.ts
3049
- import * as fs15 from "fs";
3050
- import * as path13 from "path";
3051
- function getExecutablesRoot() {
3052
- const here = path13.dirname(new URL(import.meta.url).pathname);
3053
- const candidates = [
3054
- path13.join(here, "executables"),
3055
- // dev: src/
3056
- path13.join(here, "..", "executables"),
3057
- // built: dist/bin → dist/executables
3058
- path13.join(here, "..", "src", "executables")
3059
- // fallback
3060
- ];
3061
- for (const c of candidates) {
3062
- if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
3063
- }
3064
- return candidates[0];
3065
- }
3066
- function listExecutables(root = getExecutablesRoot()) {
3067
- if (!fs15.existsSync(root)) return [];
3068
- const entries = fs15.readdirSync(root, { withFileTypes: true });
3069
- const out = [];
3070
- for (const ent of entries) {
3071
- if (!ent.isDirectory()) continue;
3072
- const profilePath = path13.join(root, ent.name, "profile.json");
3073
- if (fs15.existsSync(profilePath) && fs15.statSync(profilePath).isFile()) {
3074
- out.push({ name: ent.name, profilePath });
3075
- }
3076
- }
3077
- return out.sort((a, b) => a.name.localeCompare(b.name));
3078
- }
3079
- function hasExecutable(name, root = getExecutablesRoot()) {
3080
- if (!isSafeName(name)) return false;
3081
- const profilePath = path13.join(root, name, "profile.json");
3082
- return fs15.existsSync(profilePath) && fs15.statSync(profilePath).isFile();
3083
- }
3084
- function isSafeName(name) {
3085
- return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
3086
- }
3087
- function parseGenericFlags(argv) {
3088
- const args = {};
3089
- const positional = [];
3090
- for (let i = 0; i < argv.length; i++) {
3091
- const arg = argv[i];
3092
- if (!arg.startsWith("--")) {
3093
- positional.push(arg);
3094
- continue;
3095
- }
3096
- const key = arg.slice(2);
3097
- const next = argv[i + 1];
3098
- const value = next !== void 0 && !next.startsWith("--") ? (i++, next) : true;
3099
- args[key] = value;
3100
- if (key.includes("-")) {
3101
- const camel = key.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
3102
- if (camel !== key && args[camel] === void 0) args[camel] = value;
3103
- }
3104
- }
3105
- if (positional.length > 0) args._ = positional;
3106
- return args;
3107
- }
3108
-
3109
3226
  // src/scripts/loadQaGuide.ts
3110
3227
  import * as fs16 from "fs";
3111
3228
  import * as path14 from "path";
@@ -3297,7 +3414,13 @@ function performInit(cwd, force) {
3297
3414
  fs17.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
3298
3415
  wrote.push(`.github/workflows/kody2-${exe.name}.yml`);
3299
3416
  }
3300
- return { wrote, skipped };
3417
+ let labels;
3418
+ try {
3419
+ labels = ensureLabels(cwd);
3420
+ } catch {
3421
+ labels = void 0;
3422
+ }
3423
+ return { wrote, skipped, labels };
3301
3424
  }
3302
3425
  function renderScheduledWorkflow(name, cron) {
3303
3426
  return `# Scheduled kody2 executable: ${name}
@@ -3334,12 +3457,24 @@ jobs:
3334
3457
  var initFlow = async (ctx) => {
3335
3458
  const force = ctx.args.force === true;
3336
3459
  const cwd = ctx.cwd;
3337
- const { wrote, skipped } = performInit(cwd, force);
3460
+ const { wrote, skipped, labels } = performInit(cwd, force);
3338
3461
  process.stdout.write("\u2192 kody2 init\n");
3339
3462
  for (const f of wrote) process.stdout.write(` wrote ${f}
3340
3463
  `);
3341
3464
  for (const f of skipped) process.stdout.write(` skipped ${f} (already exists; pass --force to overwrite)
3342
3465
  `);
3466
+ if (labels) {
3467
+ if (labels.created.length > 0) {
3468
+ process.stdout.write(` labels ensured ${labels.created.length} lifecycle label(s)
3469
+ `);
3470
+ }
3471
+ if (labels.failed.length > 0) {
3472
+ process.stdout.write(
3473
+ ` labels ${labels.failed.length} failed (gh auth missing? will self-heal on first run)
3474
+ `
3475
+ );
3476
+ }
3477
+ }
3343
3478
  process.stdout.write(
3344
3479
  wrote.length > 0 ? `
3345
3480
  Done. Edit kody.config.json to pick your model, then push the workflow file.
@@ -4261,6 +4396,36 @@ function synthesizeAction(ctx) {
4261
4396
  };
4262
4397
  }
4263
4398
 
4399
+ // src/scripts/setLifecycleLabel.ts
4400
+ var setLifecycleLabel = async (ctx, _profile, args) => {
4401
+ const label = args?.label;
4402
+ if (typeof label !== "string" || !label.startsWith(KODY_LABEL_PREFIX)) {
4403
+ process.stderr.write(
4404
+ `[kody2] setLifecycleLabel: missing or invalid "label" arg (must start with "${KODY_LABEL_PREFIX}"): ${String(label)}
4405
+ `
4406
+ );
4407
+ return;
4408
+ }
4409
+ const issueNumber = resolveTargetNumber(ctx.args);
4410
+ if (issueNumber === void 0) return;
4411
+ setKodyLabel(
4412
+ issueNumber,
4413
+ {
4414
+ label,
4415
+ color: typeof args?.color === "string" ? args.color : void 0,
4416
+ description: typeof args?.description === "string" ? args.description : void 0
4417
+ },
4418
+ ctx.cwd
4419
+ );
4420
+ };
4421
+ function resolveTargetNumber(args) {
4422
+ const issue = args.issue;
4423
+ if (typeof issue === "number" && Number.isFinite(issue)) return issue;
4424
+ const pr = args.pr;
4425
+ if (typeof pr === "number" && Number.isFinite(pr)) return pr;
4426
+ return void 0;
4427
+ }
4428
+
4264
4429
  // src/scripts/skipAgent.ts
4265
4430
  var skipAgent = async (ctx) => {
4266
4431
  ctx.skipAgent = true;
@@ -4269,14 +4434,14 @@ var skipAgent = async (ctx) => {
4269
4434
  // src/scripts/startFlow.ts
4270
4435
  import { execFileSync as execFileSync16 } from "child_process";
4271
4436
  var API_TIMEOUT_MS6 = 3e4;
4272
- var startFlow = async (ctx, _profile, _agentResult, args) => {
4437
+ var startFlow = async (ctx, profile, _agentResult, args) => {
4273
4438
  const entry = args?.entry;
4274
4439
  if (!entry) {
4275
4440
  process.stderr.write("[kody2 startFlow] missing `with.entry` \u2014 skipping\n");
4276
4441
  return;
4277
4442
  }
4278
4443
  const target = args?.target ?? "issue";
4279
- const flowName = ctx.args.flow ?? "default";
4444
+ const flowName = profile.name;
4280
4445
  const issueNumber = ctx.args.issue;
4281
4446
  if (!issueNumber) {
4282
4447
  process.stderr.write("[kody2 startFlow] no --issue arg \u2014 skipping\n");
@@ -4607,6 +4772,7 @@ var preflightScripts = {
4607
4772
  discoverQaContext,
4608
4773
  resolvePreviewUrl,
4609
4774
  composePrompt,
4775
+ setLifecycleLabel,
4610
4776
  skipAgent
4611
4777
  };
4612
4778
  var postflightScripts = {
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "orchestrator-plan-build-review",
3
- "describe": "Deterministic orchestrator for the plan → build → review (→ fix on concerns/fail) flow. No agent — the postflight entries ARE the transition table, evaluated top-to-bottom via runWhen.",
2
+ "name": "bug",
3
+ "describe": "Sub-orchestrator for bug / enhancement issues — plan → run → review (→ fix on concerns/fail). No agent — the postflight entries ARE the transition table, evaluated top-to-bottom via runWhen.",
4
4
  "inputs": [
5
5
  {
6
6
  "name": "issue",
@@ -8,13 +8,6 @@
8
8
  "type": "int",
9
9
  "required": true,
10
10
  "describe": "GitHub issue number to drive the flow on."
11
- },
12
- {
13
- "name": "flow",
14
- "flag": "--flow",
15
- "type": "string",
16
- "required": false,
17
- "describe": "Flow name (cosmetic — recorded in state.flow.name)."
18
11
  }
19
12
  ],
20
13
  "claudeCode": {
@@ -34,6 +27,14 @@
34
27
  "cliTools": [],
35
28
  "scripts": {
36
29
  "preflight": [
30
+ {
31
+ "script": "setLifecycleLabel",
32
+ "with": {
33
+ "label": "kody:orchestrating",
34
+ "color": "1d76db",
35
+ "description": "kody2: orchestrating a multi-stage flow"
36
+ }
37
+ },
37
38
  { "script": "loadIssueContext" },
38
39
  { "script": "loadTaskState" },
39
40
  { "script": "skipAgent" }
@@ -48,19 +49,23 @@
48
49
  { "script": "dispatch", "with": { "next": "review", "target": "pr" },
49
50
  "runWhen": { "data.taskState.core.lastOutcome.type": "RUN_COMPLETED" } },
50
51
 
51
- { "script": "finishFlow", "with": { "reason": "review-passed" },
52
+ { "script": "finishFlow",
53
+ "with": { "reason": "review-passed", "label": "kody:done", "color": "0e8a16", "description": "kody2: PR ready for human review/merge" },
52
54
  "runWhen": { "data.taskState.core.lastOutcome.type": "REVIEW_PASS" } },
53
55
 
54
56
  { "script": "dispatch", "with": { "next": "fix", "target": "pr" },
55
57
  "runWhen": { "data.taskState.core.lastOutcome.type": ["REVIEW_CONCERNS", "REVIEW_FAIL"] } },
56
58
 
57
- { "script": "finishFlow", "with": { "reason": "review-failed" },
59
+ { "script": "finishFlow",
60
+ "with": { "reason": "review-failed", "label": "kody:failed", "color": "e11d21", "description": "kody2: flow failed" },
58
61
  "runWhen": { "data.taskState.core.lastOutcome.type": "REVIEW_FAILED" } },
59
62
 
60
- { "script": "finishFlow", "with": { "reason": "fix-applied" },
63
+ { "script": "finishFlow",
64
+ "with": { "reason": "fix-applied", "label": "kody:done", "color": "0e8a16", "description": "kody2: PR ready for human review/merge" },
61
65
  "runWhen": { "data.taskState.core.lastOutcome.type": "FIX_COMPLETED" } },
62
66
 
63
- { "script": "finishFlow", "with": { "reason": "aborted" },
67
+ { "script": "finishFlow",
68
+ "with": { "reason": "aborted", "label": "kody:failed", "color": "e11d21", "description": "kody2: flow failed" },
64
69
  "runWhen": { "data.taskState.core.lastOutcome.type": ["PLAN_FAILED", "RUN_FAILED", "FIX_FAILED", "AGENT_NOT_RUN"] } },
65
70
 
66
71
  { "script": "persistFlowState" }
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "chore",
3
+ "describe": "Sub-orchestrator for chore / docs / dep-bump issues — run → review (→ fix on concerns/fail). Skips planning entirely. No agent.",
4
+ "inputs": [
5
+ {
6
+ "name": "issue",
7
+ "flag": "--issue",
8
+ "type": "int",
9
+ "required": true,
10
+ "describe": "GitHub issue number to drive the flow on."
11
+ }
12
+ ],
13
+ "claudeCode": {
14
+ "model": "inherit",
15
+ "permissionMode": "default",
16
+ "maxTurns": 0,
17
+ "maxThinkingTokens": null,
18
+ "systemPromptAppend": null,
19
+ "tools": [],
20
+ "hooks": [],
21
+ "skills": [],
22
+ "commands": [],
23
+ "subagents": [],
24
+ "plugins": [],
25
+ "mcpServers": []
26
+ },
27
+ "cliTools": [],
28
+ "scripts": {
29
+ "preflight": [
30
+ {
31
+ "script": "setLifecycleLabel",
32
+ "with": {
33
+ "label": "kody:orchestrating",
34
+ "color": "1d76db",
35
+ "description": "kody2: orchestrating a multi-stage flow"
36
+ }
37
+ },
38
+ { "script": "loadIssueContext" },
39
+ { "script": "loadTaskState" },
40
+ { "script": "skipAgent" }
41
+ ],
42
+ "postflight": [
43
+ { "script": "startFlow", "with": { "entry": "run", "target": "issue" },
44
+ "runWhen": { "data.taskState.core.lastOutcome": null } },
45
+
46
+ { "script": "dispatch", "with": { "next": "review", "target": "pr" },
47
+ "runWhen": { "data.taskState.core.lastOutcome.type": "RUN_COMPLETED" } },
48
+
49
+ { "script": "finishFlow",
50
+ "with": { "reason": "review-passed", "label": "kody:done", "color": "0e8a16", "description": "kody2: PR ready for human review/merge" },
51
+ "runWhen": { "data.taskState.core.lastOutcome.type": "REVIEW_PASS" } },
52
+
53
+ { "script": "dispatch", "with": { "next": "fix", "target": "pr" },
54
+ "runWhen": { "data.taskState.core.lastOutcome.type": ["REVIEW_CONCERNS", "REVIEW_FAIL"] } },
55
+
56
+ { "script": "finishFlow",
57
+ "with": { "reason": "review-failed", "label": "kody:failed", "color": "e11d21", "description": "kody2: flow failed" },
58
+ "runWhen": { "data.taskState.core.lastOutcome.type": "REVIEW_FAILED" } },
59
+
60
+ { "script": "finishFlow",
61
+ "with": { "reason": "fix-applied", "label": "kody:done", "color": "0e8a16", "description": "kody2: PR ready for human review/merge" },
62
+ "runWhen": { "data.taskState.core.lastOutcome.type": "FIX_COMPLETED" } },
63
+
64
+ { "script": "finishFlow",
65
+ "with": { "reason": "aborted", "label": "kody:failed", "color": "e11d21", "description": "kody2: flow failed" },
66
+ "runWhen": { "data.taskState.core.lastOutcome.type": ["RUN_FAILED", "FIX_FAILED", "AGENT_NOT_RUN"] } },
67
+
68
+ { "script": "persistFlowState" }
69
+ ]
70
+ },
71
+ "output": {
72
+ "actionTypes": [
73
+ "FLOW_STARTED",
74
+ "FLOW_COMPLETED",
75
+ "FLOW_ABORTED"
76
+ ]
77
+ }
78
+ }
@@ -0,0 +1,5 @@
1
+ <!--
2
+ Placeholder. The chore sub-orchestrator runs with maxTurns: 0 and a
3
+ `skipAgent` preflight, so this prompt is never sent to Claude. The
4
+ transition logic lives entirely in profile.json's postflight entries.
5
+ -->
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "feature",
3
+ "describe": "Sub-orchestrator for feature / refactor issues — research → plan → run → review (→ fix on concerns/fail). No agent — the postflight entries ARE the transition table, evaluated top-to-bottom via runWhen.",
4
+ "inputs": [
5
+ {
6
+ "name": "issue",
7
+ "flag": "--issue",
8
+ "type": "int",
9
+ "required": true,
10
+ "describe": "GitHub issue number to drive the flow on."
11
+ }
12
+ ],
13
+ "claudeCode": {
14
+ "model": "inherit",
15
+ "permissionMode": "default",
16
+ "maxTurns": 0,
17
+ "maxThinkingTokens": null,
18
+ "systemPromptAppend": null,
19
+ "tools": [],
20
+ "hooks": [],
21
+ "skills": [],
22
+ "commands": [],
23
+ "subagents": [],
24
+ "plugins": [],
25
+ "mcpServers": []
26
+ },
27
+ "cliTools": [],
28
+ "scripts": {
29
+ "preflight": [
30
+ {
31
+ "script": "setLifecycleLabel",
32
+ "with": {
33
+ "label": "kody:orchestrating",
34
+ "color": "1d76db",
35
+ "description": "kody2: orchestrating a multi-stage flow"
36
+ }
37
+ },
38
+ { "script": "loadIssueContext" },
39
+ { "script": "loadTaskState" },
40
+ { "script": "skipAgent" }
41
+ ],
42
+ "postflight": [
43
+ { "script": "startFlow", "with": { "entry": "research", "target": "issue" },
44
+ "runWhen": { "data.taskState.core.lastOutcome": null } },
45
+
46
+ { "script": "dispatch", "with": { "next": "plan", "target": "issue" },
47
+ "runWhen": { "data.taskState.core.lastOutcome.type": "RESEARCH_COMPLETED" } },
48
+
49
+ { "script": "dispatch", "with": { "next": "run", "target": "issue" },
50
+ "runWhen": { "data.taskState.core.lastOutcome.type": "PLAN_COMPLETED" } },
51
+
52
+ { "script": "dispatch", "with": { "next": "review", "target": "pr" },
53
+ "runWhen": { "data.taskState.core.lastOutcome.type": "RUN_COMPLETED" } },
54
+
55
+ { "script": "finishFlow",
56
+ "with": { "reason": "review-passed", "label": "kody:done", "color": "0e8a16", "description": "kody2: PR ready for human review/merge" },
57
+ "runWhen": { "data.taskState.core.lastOutcome.type": "REVIEW_PASS" } },
58
+
59
+ { "script": "dispatch", "with": { "next": "fix", "target": "pr" },
60
+ "runWhen": { "data.taskState.core.lastOutcome.type": ["REVIEW_CONCERNS", "REVIEW_FAIL"] } },
61
+
62
+ { "script": "finishFlow",
63
+ "with": { "reason": "review-failed", "label": "kody:failed", "color": "e11d21", "description": "kody2: flow failed" },
64
+ "runWhen": { "data.taskState.core.lastOutcome.type": "REVIEW_FAILED" } },
65
+
66
+ { "script": "finishFlow",
67
+ "with": { "reason": "fix-applied", "label": "kody:done", "color": "0e8a16", "description": "kody2: PR ready for human review/merge" },
68
+ "runWhen": { "data.taskState.core.lastOutcome.type": "FIX_COMPLETED" } },
69
+
70
+ { "script": "finishFlow",
71
+ "with": { "reason": "aborted", "label": "kody:failed", "color": "e11d21", "description": "kody2: flow failed" },
72
+ "runWhen": { "data.taskState.core.lastOutcome.type": ["RESEARCH_FAILED", "PLAN_FAILED", "RUN_FAILED", "FIX_FAILED", "AGENT_NOT_RUN"] } },
73
+
74
+ { "script": "persistFlowState" }
75
+ ]
76
+ },
77
+ "output": {
78
+ "actionTypes": [
79
+ "FLOW_STARTED",
80
+ "FLOW_COMPLETED",
81
+ "FLOW_ABORTED"
82
+ ]
83
+ }
84
+ }
@@ -0,0 +1,5 @@
1
+ <!--
2
+ Placeholder. The feature sub-orchestrator runs with maxTurns: 0 and a
3
+ `skipAgent` preflight, so this prompt is never sent to Claude. The
4
+ transition logic lives entirely in profile.json's postflight entries.
5
+ -->
@@ -40,6 +40,14 @@
40
40
  "cliTools": [],
41
41
  "scripts": {
42
42
  "preflight": [
43
+ {
44
+ "script": "setLifecycleLabel",
45
+ "with": {
46
+ "label": "kody:fixing",
47
+ "color": "e99695",
48
+ "description": "kody2: applying review feedback"
49
+ }
50
+ },
43
51
  {
44
52
  "script": "fixFlow"
45
53
  },
@@ -40,6 +40,14 @@
40
40
  "cliTools": [],
41
41
  "scripts": {
42
42
  "preflight": [
43
+ {
44
+ "script": "setLifecycleLabel",
45
+ "with": {
46
+ "label": "kody:fixing",
47
+ "color": "e99695",
48
+ "description": "kody2: applying review feedback"
49
+ }
50
+ },
43
51
  {
44
52
  "script": "fixCiFlow"
45
53
  },
@@ -31,6 +31,14 @@
31
31
  "cliTools": [],
32
32
  "scripts": {
33
33
  "preflight": [
34
+ {
35
+ "script": "setLifecycleLabel",
36
+ "with": {
37
+ "label": "kody:planning",
38
+ "color": "5319e7",
39
+ "description": "kody2: producing an implementation plan"
40
+ }
41
+ },
34
42
  {
35
43
  "script": "loadIssueContext"
36
44
  },
@@ -31,6 +31,14 @@
31
31
  "cliTools": [],
32
32
  "scripts": {
33
33
  "preflight": [
34
+ {
35
+ "script": "setLifecycleLabel",
36
+ "with": {
37
+ "label": "kody:researching",
38
+ "color": "1d76db",
39
+ "description": "kody2: researching the issue"
40
+ }
41
+ },
34
42
  {
35
43
  "script": "loadIssueContext"
36
44
  },
@@ -33,6 +33,14 @@
33
33
  "cliTools": [],
34
34
  "scripts": {
35
35
  "preflight": [
36
+ {
37
+ "script": "setLifecycleLabel",
38
+ "with": {
39
+ "label": "kody:resolving",
40
+ "color": "b60205",
41
+ "description": "kody2: resolving merge conflicts"
42
+ }
43
+ },
36
44
  {
37
45
  "script": "resolveFlow"
38
46
  },
@@ -31,6 +31,14 @@
31
31
  "cliTools": [],
32
32
  "scripts": {
33
33
  "preflight": [
34
+ {
35
+ "script": "setLifecycleLabel",
36
+ "with": {
37
+ "label": "kody:reviewing",
38
+ "color": "d93f0b",
39
+ "description": "kody2: reviewing a PR"
40
+ }
41
+ },
34
42
  {
35
43
  "script": "reviewFlow"
36
44
  },
@@ -33,6 +33,14 @@
33
33
  "cliTools": [],
34
34
  "scripts": {
35
35
  "preflight": [
36
+ {
37
+ "script": "setLifecycleLabel",
38
+ "with": {
39
+ "label": "kody:running",
40
+ "color": "fbca04",
41
+ "description": "kody2: implementing the change"
42
+ }
43
+ },
36
44
  {
37
45
  "script": "runFlow"
38
46
  },
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "spec",
3
+ "describe": "Sub-orchestrator for spec / RFC / design-doc issues — research → plan (stop). Terminates at the plan artifact; no run, no PR. No agent.",
4
+ "inputs": [
5
+ {
6
+ "name": "issue",
7
+ "flag": "--issue",
8
+ "type": "int",
9
+ "required": true,
10
+ "describe": "GitHub issue number to drive the flow on."
11
+ }
12
+ ],
13
+ "claudeCode": {
14
+ "model": "inherit",
15
+ "permissionMode": "default",
16
+ "maxTurns": 0,
17
+ "maxThinkingTokens": null,
18
+ "systemPromptAppend": null,
19
+ "tools": [],
20
+ "hooks": [],
21
+ "skills": [],
22
+ "commands": [],
23
+ "subagents": [],
24
+ "plugins": [],
25
+ "mcpServers": []
26
+ },
27
+ "cliTools": [],
28
+ "scripts": {
29
+ "preflight": [
30
+ {
31
+ "script": "setLifecycleLabel",
32
+ "with": {
33
+ "label": "kody:orchestrating",
34
+ "color": "1d76db",
35
+ "description": "kody2: orchestrating a multi-stage flow"
36
+ }
37
+ },
38
+ { "script": "loadIssueContext" },
39
+ { "script": "loadTaskState" },
40
+ { "script": "skipAgent" }
41
+ ],
42
+ "postflight": [
43
+ { "script": "startFlow", "with": { "entry": "research", "target": "issue" },
44
+ "runWhen": { "data.taskState.core.lastOutcome": null } },
45
+
46
+ { "script": "dispatch", "with": { "next": "plan", "target": "issue" },
47
+ "runWhen": { "data.taskState.core.lastOutcome.type": "RESEARCH_COMPLETED" } },
48
+
49
+ { "script": "finishFlow",
50
+ "with": { "reason": "spec-ready", "label": "kody:done", "color": "0e8a16", "description": "kody2: spec/plan artifact ready" },
51
+ "runWhen": { "data.taskState.core.lastOutcome.type": "PLAN_COMPLETED" } },
52
+
53
+ { "script": "finishFlow",
54
+ "with": { "reason": "aborted", "label": "kody:failed", "color": "e11d21", "description": "kody2: flow failed" },
55
+ "runWhen": { "data.taskState.core.lastOutcome.type": ["RESEARCH_FAILED", "PLAN_FAILED", "AGENT_NOT_RUN"] } },
56
+
57
+ { "script": "persistFlowState" }
58
+ ]
59
+ },
60
+ "output": {
61
+ "actionTypes": [
62
+ "FLOW_STARTED",
63
+ "FLOW_COMPLETED",
64
+ "FLOW_ABORTED"
65
+ ]
66
+ }
67
+ }
@@ -0,0 +1,5 @@
1
+ <!--
2
+ Placeholder. The spec sub-orchestrator runs with maxTurns: 0 and a
3
+ `skipAgent` preflight, so this prompt is never sent to Claude. The
4
+ transition logic lives entirely in profile.json's postflight entries.
5
+ -->
@@ -26,6 +26,14 @@
26
26
  "cliTools": [],
27
27
  "scripts": {
28
28
  "preflight": [
29
+ {
30
+ "script": "setLifecycleLabel",
31
+ "with": {
32
+ "label": "kody:syncing",
33
+ "color": "c5def5",
34
+ "description": "kody2: syncing PR with base"
35
+ }
36
+ },
29
37
  {
30
38
  "script": "syncFlow"
31
39
  }
@@ -59,6 +59,7 @@
59
59
  "outputArtifacts": [],
60
60
  "scripts": {
61
61
  "preflight": [
62
+ { "script": "setLifecycleLabel", "with": { "label": "kody:reviewing", "color": "d93f0b", "description": "kody2: reviewing a PR" } },
62
63
  { "script": "reviewFlow" },
63
64
  { "script": "loadTaskState" },
64
65
  { "script": "loadConventions" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.2.51",
3
+ "version": "0.2.53",
4
4
  "description": "kody2 — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",