@kody-ade/kody-engine 0.4.232 → 0.4.234

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/bin/kody.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "@kody-ade/kody-engine",
18
- version: "0.4.232",
18
+ version: "0.4.234",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -3121,6 +3121,7 @@ function loadProfile(profilePath) {
3121
3121
  action: typeof r.action === "string" && r.action.trim() ? r.action.trim() : void 0,
3122
3122
  executable: execRef,
3123
3123
  describe: typeof r.describe === "string" ? r.describe : base.describe,
3124
+ capabilityKind: parseCapabilityKind(profilePath, r.capabilityKind) ?? base.capabilityKind,
3124
3125
  staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : base.staff,
3125
3126
  every: typeof r.every === "string" && r.every.trim() ? r.every.trim() : void 0,
3126
3127
  dutyTools: parseStringArray(r.dutyTools ?? r.tools) ?? base.dutyTools,
@@ -3165,6 +3166,7 @@ function loadProfile(profilePath) {
3165
3166
  action: typeof r.action === "string" && r.action.trim() ? r.action.trim() : void 0,
3166
3167
  executable: void 0,
3167
3168
  describe: typeof r.describe === "string" ? r.describe : "",
3169
+ capabilityKind: parseCapabilityKind(profilePath, r.capabilityKind),
3168
3170
  // Optional persona to run as. Empty/blank string → undefined (no persona).
3169
3171
  staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : void 0,
3170
3172
  // Optional recurrence cadence (scheduled duty). Blank → undefined (on-demand).
@@ -3255,6 +3257,13 @@ function requireString(p, r, key) {
3255
3257
  }
3256
3258
  return v;
3257
3259
  }
3260
+ function parseCapabilityKind(p, raw) {
3261
+ if (raw === void 0 || raw === null || raw === "") return void 0;
3262
+ if (typeof raw !== "string" || !VALID_CAPABILITY_KINDS.has(raw)) {
3263
+ throw new ProfileError(p, `"capabilityKind" must be one of: observe | act | verify`);
3264
+ }
3265
+ return raw;
3266
+ }
3258
3267
  function parseStringArray(raw) {
3259
3268
  if (!Array.isArray(raw)) return void 0;
3260
3269
  const values = raw.map((t) => String(t).trim()).filter(Boolean);
@@ -3331,6 +3340,17 @@ function parseCliTools(p, raw) {
3331
3340
  if (!Array.isArray(raw)) throw new ProfileError(p, `"cliTools" must be an array or absent`);
3332
3341
  const out = [];
3333
3342
  for (const [i, item] of raw.entries()) {
3343
+ if (typeof item === "string" && item.trim()) {
3344
+ const name = item.trim();
3345
+ out.push({
3346
+ name,
3347
+ install: { required: false, checkCommand: `command -v ${name}` },
3348
+ verify: `command -v ${name}`,
3349
+ usage: "",
3350
+ allowedUses: []
3351
+ });
3352
+ continue;
3353
+ }
3334
3354
  if (!item || typeof item !== "object") {
3335
3355
  throw new ProfileError(p, `cliTools[${i}] must be an object`);
3336
3356
  }
@@ -3490,7 +3510,7 @@ function parseScriptList(p, key, raw) {
3490
3510
  }
3491
3511
  return out;
3492
3512
  }
3493
- var VALID_INPUT_TYPES, VALID_PERMISSION_MODES, VALID_ROLES, VALID_CONTAINER_CHILD_TARGETS, VALID_PHASES, KNOWN_PROFILE_KEYS;
3513
+ var VALID_INPUT_TYPES, VALID_PERMISSION_MODES, VALID_ROLES, VALID_CONTAINER_CHILD_TARGETS, VALID_PHASES, VALID_CAPABILITY_KINDS, KNOWN_PROFILE_KEYS;
3494
3514
  var init_profile = __esm({
3495
3515
  "src/profile.ts"() {
3496
3516
  "use strict";
@@ -3506,6 +3526,7 @@ var init_profile = __esm({
3506
3526
  VALID_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
3507
3527
  VALID_CONTAINER_CHILD_TARGETS = /* @__PURE__ */ new Set(["issue", "pr"]);
3508
3528
  VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementing", "reviewing", "shipped", "failed", "idle"]);
3529
+ VALID_CAPABILITY_KINDS = /* @__PURE__ */ new Set(["observe", "act", "verify"]);
3509
3530
  KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
3510
3531
  "name",
3511
3532
  "action",
@@ -3515,6 +3536,7 @@ var init_profile = __esm({
3515
3536
  "dutyTools",
3516
3537
  "tools",
3517
3538
  "mentions",
3539
+ "capabilityKind",
3518
3540
  "stage",
3519
3541
  "readsFrom",
3520
3542
  "writesTo",
@@ -5723,6 +5745,132 @@ var init_state2 = __esm({
5723
5745
  }
5724
5746
  });
5725
5747
 
5748
+ // src/goal/typeDefinitions.ts
5749
+ function cloneRoute(route) {
5750
+ return route.map((step) => ({
5751
+ stage: step.stage,
5752
+ evidence: step.evidence,
5753
+ duty: step.duty,
5754
+ ...step.executable ? { executable: step.executable } : {},
5755
+ ...step.args ? { args: structuredClone(step.args) } : {}
5756
+ }));
5757
+ }
5758
+ function stringArray(value) {
5759
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : null;
5760
+ }
5761
+ function routeArray(value) {
5762
+ if (!Array.isArray(value)) return null;
5763
+ const route = [];
5764
+ for (const item of value) {
5765
+ if (!item || typeof item !== "object" || Array.isArray(item)) return null;
5766
+ const raw = item;
5767
+ if (typeof raw.stage !== "string" || typeof raw.evidence !== "string" || typeof raw.duty !== "string") return null;
5768
+ route.push({
5769
+ stage: raw.stage,
5770
+ evidence: raw.evidence,
5771
+ duty: raw.duty,
5772
+ executable: typeof raw.executable === "string" ? raw.executable : void 0,
5773
+ args: raw.args && typeof raw.args === "object" && !Array.isArray(raw.args) ? { ...raw.args } : void 0
5774
+ });
5775
+ }
5776
+ return route;
5777
+ }
5778
+ function managedGoalTypeDefinition(type) {
5779
+ return Object.prototype.hasOwnProperty.call(GOAL_TYPE_DEFINITIONS, type) ? GOAL_TYPE_DEFINITIONS[type] : null;
5780
+ }
5781
+ function expandManagedGoalState(state) {
5782
+ const type = typeof state.extra.type === "string" ? state.extra.type : "";
5783
+ const definition = managedGoalTypeDefinition(type);
5784
+ if (!definition) return state;
5785
+ const destination = state.extra.destination && typeof state.extra.destination === "object" && !Array.isArray(state.extra.destination) ? { ...state.extra.destination } : {};
5786
+ const outcome = typeof destination.outcome === "string" ? destination.outcome : "";
5787
+ const evidence = stringArray(destination.evidence);
5788
+ const duties = stringArray(state.extra.duties);
5789
+ const route = routeArray(state.extra.route);
5790
+ const facts = state.extra.facts && typeof state.extra.facts === "object" && !Array.isArray(state.extra.facts) ? { ...state.extra.facts } : {};
5791
+ const blockers = stringArray(state.extra.blockers);
5792
+ return {
5793
+ ...state,
5794
+ extra: {
5795
+ ...state.extra,
5796
+ type: definition.type,
5797
+ destination: {
5798
+ ...destination,
5799
+ outcome,
5800
+ evidence: evidence && evidence.length > 0 ? evidence : [...definition.evidence]
5801
+ },
5802
+ duties: duties && duties.length > 0 ? duties : [...definition.duties],
5803
+ route: route && route.length > 0 ? route : cloneRoute(definition.route),
5804
+ facts,
5805
+ blockers: blockers ?? []
5806
+ }
5807
+ };
5808
+ }
5809
+ var GOAL_TYPE_DEFINITIONS;
5810
+ var init_typeDefinitions = __esm({
5811
+ "src/goal/typeDefinitions.ts"() {
5812
+ "use strict";
5813
+ GOAL_TYPE_DEFINITIONS = {
5814
+ improve: {
5815
+ type: "improve",
5816
+ evidence: ["planReady", "changeImplemented", "changeVerified"],
5817
+ duties: ["plan", "fix", "review"],
5818
+ route: [
5819
+ { stage: "plan", evidence: "planReady", duty: "plan", executable: "plan" },
5820
+ { stage: "implement", evidence: "changeImplemented", duty: "fix", executable: "fix" },
5821
+ { stage: "review", evidence: "changeVerified", duty: "review", executable: "review" }
5822
+ ]
5823
+ },
5824
+ maintain: {
5825
+ type: "maintain",
5826
+ evidence: [],
5827
+ duties: ["cleanup", "code-health", "docs-health", "documentation-maintenance", "memory-compaction", "repo-graph", "skills-research"],
5828
+ route: []
5829
+ },
5830
+ monitor: {
5831
+ type: "monitor",
5832
+ evidence: [],
5833
+ duties: ["health-check", "pr-health-triage", "qa-sweep"],
5834
+ route: []
5835
+ },
5836
+ release: {
5837
+ type: "release",
5838
+ evidence: ["releasePrExists", "mainMerged", "productionDeployed"],
5839
+ duties: ["release", "release-merge", "vercel-production-deploy"],
5840
+ route: [
5841
+ {
5842
+ stage: "release",
5843
+ evidence: "releasePrExists",
5844
+ duty: "release",
5845
+ executable: "release-prepare",
5846
+ args: { issue: { fact: "issue" }, goal: { fact: "goalId" } }
5847
+ },
5848
+ {
5849
+ stage: "merge",
5850
+ evidence: "mainMerged",
5851
+ duty: "release-merge",
5852
+ executable: "release-merge",
5853
+ args: { pr: { fact: "releasePr" }, issue: { fact: "issue" }, goal: { fact: "goalId" } }
5854
+ },
5855
+ {
5856
+ stage: "publish",
5857
+ evidence: "productionDeployed",
5858
+ duty: "vercel-production-deploy",
5859
+ executable: "vercel-production-deploy",
5860
+ args: { goal: { fact: "goalId" } }
5861
+ }
5862
+ ]
5863
+ },
5864
+ checklist: {
5865
+ type: "checklist",
5866
+ evidence: ["checklistComplete"],
5867
+ duties: ["task-verifier"],
5868
+ route: [{ stage: "verify", evidence: "checklistComplete", duty: "task-verifier", executable: "task-verifier" }]
5869
+ }
5870
+ };
5871
+ }
5872
+ });
5873
+
5726
5874
  // src/stateBranch.ts
5727
5875
  function is404(err) {
5728
5876
  const msg = err instanceof Error ? err.message : String(err);
@@ -6335,12 +6483,64 @@ function readSimpleGoalTaskSummary(goalId, cwd) {
6335
6483
  const open = issues.filter((issue) => String(issue.state ?? "").toLowerCase() === "open").length;
6336
6484
  return { total, open };
6337
6485
  }
6486
+ function ensureIssueFactIfNeeded(goal, goalId, cwd) {
6487
+ if (!routeNeedsIssueFact(goal)) return;
6488
+ const existing = normalizeIssueNumber(goal.facts.issue);
6489
+ if (existing !== null) {
6490
+ goal.facts.issue = existing;
6491
+ return;
6492
+ }
6493
+ goal.facts.issue = findExistingGoalIssue(goalId, cwd) ?? createGoalIssue(goal, goalId, cwd);
6494
+ }
6495
+ function routeNeedsIssueFact(goal) {
6496
+ return goal.route.some(
6497
+ (step) => Object.values(step.args ?? {}).some((value) => {
6498
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
6499
+ const record = value;
6500
+ return Object.keys(record).length === 1 && record.fact === "issue";
6501
+ })
6502
+ );
6503
+ }
6504
+ function normalizeIssueNumber(value) {
6505
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
6506
+ if (typeof value === "string" && /^\d+$/.test(value)) return Number(value);
6507
+ return null;
6508
+ }
6509
+ function goalIssueMarker(goalId) {
6510
+ return `<!-- kody-managed-goal: ${goalId} -->`;
6511
+ }
6512
+ function findExistingGoalIssue(goalId, cwd) {
6513
+ const marker = goalIssueMarker(goalId);
6514
+ const raw = gh(["issue", "list", "--state", "all", "--limit", "100", "--json", "number,body"], { cwd });
6515
+ const issues = JSON.parse(raw);
6516
+ const match = issues.find((issue) => typeof issue.number === "number" && issue.body?.includes(marker));
6517
+ return match?.number ?? null;
6518
+ }
6519
+ function createGoalIssue(goal, goalId, cwd) {
6520
+ const prefix = goal.type === "release" ? "Release" : "Goal";
6521
+ const outcome = goal.destination.outcome.trim() || goalId;
6522
+ const title = `${prefix}: ${outcome}`.slice(0, 120);
6523
+ const body = [
6524
+ `Managed goal: \`${goalId}\``,
6525
+ "",
6526
+ `Finish line: ${outcome}`,
6527
+ "",
6528
+ "This issue was created by Kody so goal duties that require an issue can run end to end.",
6529
+ "",
6530
+ goalIssueMarker(goalId)
6531
+ ].join("\n");
6532
+ const out = gh(["issue", "create", "--title", title, "--body-file", "-"], { input: body, cwd });
6533
+ const match = out.match(/\/issues\/(\d+)(?:[/?#]|$)/);
6534
+ if (!match) throw new Error(`gh issue create returned unexpected output: ${out}`);
6535
+ return Number(match[1]);
6536
+ }
6338
6537
  var advanceManagedGoal;
6339
6538
  var init_advanceManagedGoal = __esm({
6340
6539
  "src/scripts/advanceManagedGoal.ts"() {
6341
6540
  "use strict";
6342
6541
  init_manager();
6343
6542
  init_state2();
6543
+ init_typeDefinitions();
6344
6544
  init_issue();
6345
6545
  init_goalDutyScheduling();
6346
6546
  advanceManagedGoal = async (ctx) => {
@@ -6352,6 +6552,7 @@ var init_advanceManagedGoal = __esm({
6352
6552
  return;
6353
6553
  }
6354
6554
  ctx.data.goalOriginalStateText = serializeGoalState(goal.raw);
6555
+ goal.raw = expandManagedGoalState(goal.raw);
6355
6556
  const managed = managedGoalFromState(goal.raw);
6356
6557
  if (!managed) {
6357
6558
  ctx.output.reason = "goal has no managed-goal contract; nothing to advance";
@@ -6363,6 +6564,17 @@ var init_advanceManagedGoal = __esm({
6363
6564
  if (previousGoalIdFact === void 0) delete managed.facts.goalId;
6364
6565
  else managed.facts.goalId = previousGoalIdFact;
6365
6566
  };
6567
+ try {
6568
+ ensureIssueFactIfNeeded(managed, goal.id, ctx.cwd);
6569
+ } catch (err) {
6570
+ const reason = `failed to prepare goal issue fact: ${err instanceof Error ? err.message : String(err)}`;
6571
+ managed.stage = "blocked";
6572
+ if (!managed.blockers.includes(reason)) managed.blockers.push(reason);
6573
+ restoreGoalIdFact();
6574
+ goal.raw = writeManagedGoalToState({ ...goal.raw, state: goal.state }, managed);
6575
+ ctx.output.reason = reason;
6576
+ return;
6577
+ }
6366
6578
  if (isDutyCadenceGoal(managed, goal.raw.extra)) {
6367
6579
  const previousScheduleState = goal.raw.extra.scheduleState && typeof goal.raw.extra.scheduleState === "object" ? goal.raw.extra.scheduleState : void 0;
6368
6580
  const decision2 = await planGoalDutySchedule({
@@ -16554,15 +16766,23 @@ async function runCi(argv) {
16554
16766
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
16555
16767
  let manualWorkflowDispatch = false;
16556
16768
  let forceRunAction = null;
16769
+ let forceRunCliArgs = {};
16557
16770
  if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs42.existsSync(dispatchEventPath)) {
16558
16771
  try {
16559
16772
  const evt = JSON.parse(fs42.readFileSync(dispatchEventPath, "utf-8"));
16560
16773
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
16561
16774
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
16562
16775
  const dutyInput = String(evt?.inputs?.duty ?? evt?.inputs?.executable ?? "").trim();
16776
+ const messageInput = String(evt?.inputs?.message ?? "").trim();
16563
16777
  const noTarget = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
16564
- if (noTarget && dutyInput) forceRunAction = dutyInput;
16565
- else manualWorkflowDispatch = noTarget;
16778
+ if (noTarget && dutyInput) {
16779
+ forceRunAction = dutyInput;
16780
+ if (dutyInput === "goal-manager" && messageInput) {
16781
+ forceRunCliArgs = { goal: messageInput };
16782
+ }
16783
+ } else {
16784
+ manualWorkflowDispatch = noTarget;
16785
+ }
16566
16786
  } catch {
16567
16787
  manualWorkflowDispatch = false;
16568
16788
  }
@@ -16575,6 +16795,10 @@ async function runCi(argv) {
16575
16795
  `);
16576
16796
  return 64;
16577
16797
  }
16798
+ if (route.executable === "goal-manager" && typeof forceRunCliArgs.goal !== "string") {
16799
+ process.stderr.write("[kody] manual goal-manager run requires message goal id\n");
16800
+ return 64;
16801
+ }
16578
16802
  process.stdout.write(`\u2192 kody: manual one-shot run of duty action ${route.action} (${route.duty})
16579
16803
 
16580
16804
  `);
@@ -16611,7 +16835,7 @@ async function runCi(argv) {
16611
16835
  action: route.action,
16612
16836
  duty: route.duty,
16613
16837
  executable: route.executable,
16614
- cliArgs: route.cliArgs,
16838
+ cliArgs: { ...route.cliArgs, ...forceRunCliArgs },
16615
16839
  flavor: "instant",
16616
16840
  force: true
16617
16841
  },
@@ -15,6 +15,8 @@ import type { Phase } from "../state.js"
15
15
  // Profile shape (mirrors the JSON on disk).
16
16
  // ────────────────────────────────────────────────────────────────────────────
17
17
 
18
+ export type CapabilityKind = "observe" | "act" | "verify"
19
+
18
20
  export interface Profile {
19
21
  name: string
20
22
  /**
@@ -33,6 +35,11 @@ export interface Profile {
33
35
  */
34
36
  staff?: string
35
37
  describe: string
38
+ /**
39
+ * Author-facing capability promise for a duty/executable. This classifies the
40
+ * shape of result it should return; it does not change executor control flow.
41
+ */
42
+ capabilityKind?: CapabilityKind
36
43
  /**
37
44
  * Semantic role — what this executable IS, not when it runs.
38
45
  * - primitive: single-step agent executor (flow → agent → verify → commit → PR).
@@ -390,6 +397,58 @@ export interface OutputContract {
390
397
  }
391
398
  }
392
399
 
400
+ export interface CapabilityAlert {
401
+ level?: "info" | "warning" | "error"
402
+ message: string
403
+ }
404
+
405
+ export interface CapabilitySuggestedAction {
406
+ action: string
407
+ args?: Record<string, unknown>
408
+ reason?: string
409
+ }
410
+
411
+ export interface CapabilityResourceRef {
412
+ type: string
413
+ id?: string | number
414
+ number?: number
415
+ url?: string
416
+ name?: string
417
+ }
418
+
419
+ export interface CapabilityEvidenceItem {
420
+ source?: string
421
+ message: string
422
+ url?: string
423
+ }
424
+
425
+ export interface ObserveResult {
426
+ kind: "observe"
427
+ facts?: Record<string, unknown>
428
+ alerts?: CapabilityAlert[]
429
+ suggestedActions?: CapabilitySuggestedAction[]
430
+ evidence?: Record<string, unknown>
431
+ }
432
+
433
+ export interface ActResult {
434
+ kind: "act"
435
+ status: "created" | "changed" | "triggered" | "skipped" | "failed"
436
+ changedResources?: CapabilityResourceRef[]
437
+ createdResources?: CapabilityResourceRef[]
438
+ actionResult?: Record<string, unknown>
439
+ evidence?: Record<string, unknown>
440
+ }
441
+
442
+ export interface VerifyResult {
443
+ kind: "verify"
444
+ passed: boolean
445
+ evidence?: CapabilityEvidenceItem[]
446
+ blockers?: string[]
447
+ facts?: Record<string, unknown>
448
+ }
449
+
450
+ export type CapabilityResult = ObserveResult | ActResult | VerifyResult
451
+
393
452
  // ────────────────────────────────────────────────────────────────────────────
394
453
  // Run-time context passed to every script.
395
454
  // ────────────────────────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.232",
3
+ "version": "0.4.234",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",