@kody-ade/kody-engine 0.4.19 → 0.4.21

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.
@@ -73,7 +73,7 @@
73
73
  { "script": "loadTaskState" },
74
74
  { "script": "loadConventions" },
75
75
  { "script": "loadPriorArt" },
76
- { "script": "loadVaultContext" },
76
+ { "script": "loadMemoryContext" },
77
77
  { "script": "loadCoverageRules" },
78
78
  { "script": "composePrompt" }
79
79
  ],
@@ -21,7 +21,7 @@ You are Kody, an autonomous engineer. Apply the feedback below to the existing P
21
21
 
22
22
  If a prior-art block is present above, scan it before editing — those are earlier attempts (possibly by you, possibly by a human) at the same fix. Note what was rejected and why; do not repeat a discarded approach.
23
23
 
24
- {{vaultContext}}
24
+ {{memoryContext}}
25
25
 
26
26
  # Required steps
27
27
  1. **Extract** every actionable item from the feedback. A structured review uses headings like `### Concerns`, `### Suggestions`, and `### Bugs`; each bullet under those headings is a distinct item. `### Strengths`, `### Summary`, and `### Bottom line` are NOT items — skip them. If the feedback has no headings (plain inline feedback), treat the whole feedback as one item.
File without changes
@@ -196,6 +196,68 @@ with open(path, "w") as f:
196
196
  PY
197
197
  }
198
198
 
199
+ ensure_goal_pr() {
200
+ # Open a draft goal PR (`goal-<id>` → default branch) early in the goal's
201
+ # life so the dashboard has a single anchor that ties together the umbrella
202
+ # issue, the goal branch, and the Vercel preview deploy. Without this PR,
203
+ # the umbrella issue is just a label-tagged issue with no link to its
204
+ # branch, so the dashboard can't surface preview/CI/branch on the umbrella
205
+ # row.
206
+ #
207
+ # Lifecycle:
208
+ # - Created here as DRAFT on every active tick once origin/<goal_branch>
209
+ # exists. Body carries `Closes #<umbrellaNumber>` so the umbrella
210
+ # auto-closes on merge.
211
+ # - Promoted to ready-for-review by the finalize path when all child
212
+ # tasks close (see below).
213
+ #
214
+ # Lookup order:
215
+ # 1. state.json `goalPrUrl` — fast path; skip if already populated.
216
+ # 2. `gh pr list --head <goal_branch>` — recovery path when state.json
217
+ # lost the field (e.g. older goals from before this change).
218
+ # 3. Create a fresh draft PR.
219
+ local existing_url existing_num
220
+ existing_url=$(read_state_field "goalPrUrl")
221
+ if [ -n "$existing_url" ]; then
222
+ return 0
223
+ fi
224
+
225
+ # Goal branch must exist on origin before we can open a PR.
226
+ if ! git ls-remote --exit-code --heads origin "$goal_branch" >/dev/null 2>&1; then
227
+ return 0
228
+ fi
229
+
230
+ # Recovery: PR may already exist from a prior tick that didn't persist the
231
+ # URL. Match by head ref.
232
+ existing_num=$(gh pr list --head "$goal_branch" --state open --json number --jq '.[0].number // empty' 2>/dev/null || echo "")
233
+ if [ -n "$existing_num" ] && [[ "$existing_num" =~ ^[0-9]+$ ]]; then
234
+ existing_url=$(gh pr view "$existing_num" --json url --jq .url 2>/dev/null || echo "")
235
+ else
236
+ local title body goal_issue_number
237
+ title="goal: ${goal_id}"
238
+ goal_issue_number=$(read_state_field "goalIssueNumber")
239
+ if [ -n "$goal_issue_number" ] && [ "$goal_issue_number" != "0" ]; then
240
+ body=$(printf "Tracking integration PR for goal **%s**.\n\nChild task PRs merge into \`%s\`. This PR is held in **draft** until every task is complete, then promoted to ready-for-review by goal-tick.\n\nCloses #%s\n" "$goal_id" "$goal_branch" "$goal_issue_number")
241
+ else
242
+ body=$(printf "Tracking integration PR for goal **%s**.\n\nChild task PRs merge into \`%s\`. Held in **draft** until every task is complete.\n" "$goal_id" "$goal_branch")
243
+ fi
244
+ existing_url=$(gh pr create \
245
+ --draft \
246
+ --head "$goal_branch" \
247
+ --base "$default_branch" \
248
+ --title "$title" \
249
+ --body "$body" 2>/dev/null || echo "")
250
+ if [ -z "$existing_url" ]; then
251
+ echo "[goal-tick] ensure_goal_pr: gh pr create failed (continuing without goal PR)"
252
+ return 0
253
+ fi
254
+ echo "[goal-tick] opened draft goal PR ${existing_url} for ${goal_id}"
255
+ fi
256
+
257
+ # Persist URL into state.json so subsequent ticks skip the lookup.
258
+ set_state_field "goalPrUrl" "$existing_url"
259
+ }
260
+
199
261
  list_goal_issues() {
200
262
  # Up to 100 goal-labelled issues. PRs filtered out. Also filters out the
201
263
  # umbrella goal issue (if any) — it shares the `goal:<id>` label so the
@@ -273,6 +335,16 @@ ensure_label "$failed_label" "b60205" "kody goal-runner: task failed; needs huma
273
335
  # counting child tasks, so list_goal_issues can filter it out cleanly.
274
336
  ensure_goal_issue
275
337
 
338
+ # Open the draft goal PR if the goal branch already exists. Must run BEFORE
339
+ # any of the early exits below (in_flight check, no-undispatched-task idle,
340
+ # etc.) — otherwise active goals that always have a task in flight would
341
+ # never get past the in_flight gate to reach the late call site, leaving
342
+ # the umbrella row without its branch + preview anchor in the dashboard.
343
+ # `ensure_goal_pr` is a safe no-op when the branch hasn't been created yet
344
+ # (the lazy-branch-creation block at the dispatch site handles that case;
345
+ # the next tick picks up the PR creation here).
346
+ ensure_goal_pr
347
+
276
348
  # Merge ready goal-task PRs into the goal branch. We own the merge here
277
349
  # instead of relying on GitHub's `--auto` flag (which requires the repo's
278
350
  # "Allow auto-merge" setting and silently no-ops when disabled). Only merge
@@ -348,31 +420,43 @@ open_count=$(echo "$issues_json" | python3 -c "import json,sys; print(sum(1 for
348
420
  if [ "$open_count" = "0" ]; then
349
421
  echo "[goal-tick] all $total task(s) closed — finalising goal"
350
422
 
351
- # Open the goal PR if it doesn't already exist. We only care about origin/<goal_branch>:
352
- # the goal branch should exist (created by goal-scheduler). If not, log and skip
353
- # PR creation but still mark state=done so the goal moves out of `active`.
423
+ # Promote (or open) the goal PR. The active path opens this PR as a draft
424
+ # once the goal branch exists, so by finalize it almost always already
425
+ # exists we just mark it ready-for-review and refresh the body. Older
426
+ # goals from before the early-PR change may still need first-time creation
427
+ # here as a fallback.
354
428
  goal_pr_url=""
355
429
  if git ls-remote --exit-code --heads origin "$goal_branch" >/dev/null 2>&1; then
356
- existing_pr=$(gh pr list --head "$goal_branch" --state open --json number,url --jq '.[0]' 2>/dev/null || echo "")
430
+ existing_pr=$(gh pr list --head "$goal_branch" --state open --json number,url,isDraft --jq '.[0]' 2>/dev/null || echo "")
431
+ title="goal: ${goal_id}"
432
+ goal_issue_number=$(read_state_field "goalIssueNumber")
433
+ # `Closes #N` auto-closes the umbrella goal issue on PR merge — that's
434
+ # how the dashboard learns the goal is done. Skip the line gracefully
435
+ # if no umbrella issue was ever opened (older goals from before this
436
+ # change, or `gh issue create` failed silently during a tick).
437
+ if [ -n "$goal_issue_number" ] && [ "$goal_issue_number" != "0" ]; then
438
+ body=$(printf "Final integration PR for goal **%s**.\n\nAll task issues are closed and merged into \`%s\`. Ready for review.\n\nCloses #%s\n" "$goal_id" "$goal_branch" "$goal_issue_number")
439
+ else
440
+ body=$(printf "Final integration PR for goal **%s**.\n\nAll task issues are closed and merged into \`%s\`. Ready for review.\n" "$goal_id" "$goal_branch")
441
+ fi
357
442
  if [ -z "$existing_pr" ] || [ "$existing_pr" = "null" ]; then
358
- title="goal: ${goal_id}"
359
- goal_issue_number=$(read_state_field "goalIssueNumber")
360
- # `Closes #N` auto-closes the umbrella goal issue on PR merge — that's
361
- # how the dashboard learns the goal is done. Skip the line gracefully
362
- # if no umbrella issue was ever opened (older goals from before this
363
- # change, or `gh issue create` failed silently during a tick).
364
- if [ -n "$goal_issue_number" ] && [ "$goal_issue_number" != "0" ]; then
365
- body=$(printf "Final integration PR for goal **%s**.\n\nAll task issues are closed and merged into \`%s\`. Ready for review.\n\nCloses #%s\n" "$goal_id" "$goal_branch" "$goal_issue_number")
366
- else
367
- body=$(printf "Final integration PR for goal **%s**.\n\nAll task issues are closed and merged into \`%s\`. Ready for review.\n" "$goal_id" "$goal_branch")
368
- fi
369
443
  goal_pr_url=$(gh pr create \
370
444
  --head "$goal_branch" \
371
445
  --base "$default_branch" \
372
446
  --title "$title" \
373
447
  --body "$body" 2>/dev/null || echo "")
374
448
  else
449
+ existing_num=$(echo "$existing_pr" | python3 -c "import json,sys; print(json.load(sys.stdin).get('number',''))")
375
450
  goal_pr_url=$(echo "$existing_pr" | python3 -c "import json,sys; print(json.load(sys.stdin).get('url',''))")
451
+ is_draft=$(echo "$existing_pr" | python3 -c "import json,sys; print('true' if json.load(sys.stdin).get('isDraft') else 'false')")
452
+ # Refresh the body with the finalize copy so reviewers see the right
453
+ # framing. Best-effort — failure is non-fatal.
454
+ gh pr edit "$existing_num" --body "$body" >/dev/null 2>&1 || true
455
+ if [ "$is_draft" = "true" ]; then
456
+ echo "[goal-tick] promoting draft goal PR #${existing_num} to ready-for-review"
457
+ gh pr ready "$existing_num" >/dev/null 2>&1 \
458
+ || echo "[goal-tick] failed to mark PR #${existing_num} ready (continuing)"
459
+ fi
376
460
  fi
377
461
  else
378
462
  echo "[goal-tick] goal branch ${goal_branch} not found on origin — skipping final PR"
@@ -469,6 +553,8 @@ else
469
553
  fi
470
554
  fi
471
555
  fi
556
+ # (`ensure_goal_pr` runs at the top of the active path so it's reached even
557
+ # when this tick exits early via the in_flight gate; not duplicated here.)
472
558
 
473
559
  echo "[goal-tick] dispatching @kody on task #$next_issue (--base $goal_branch)"
474
560
  gh issue comment "$next_issue" --body "@kody --base ${goal_branch}"
File without changes
File without changes
File without changes
File without changes
@@ -64,7 +64,7 @@
64
64
  "script": "loadConventions"
65
65
  },
66
66
  {
67
- "script": "loadVaultContext"
67
+ "script": "loadMemoryContext"
68
68
  },
69
69
  {
70
70
  "script": "loadCoverageRules"
@@ -9,7 +9,7 @@ You are Kody, an autonomous engineer. A `git merge origin/{{baseBranch}}` into P
9
9
 
10
10
  {{conflictedFiles}}
11
11
 
12
- {{preferBlock}}{{conventionsBlock}}{{vaultContext}}{{toolsUsage}}# Working-tree conflict markers (truncated)
12
+ {{preferBlock}}{{conventionsBlock}}{{memoryContext}}{{toolsUsage}}# Working-tree conflict markers (truncated)
13
13
 
14
14
  {{conflictMarkersPreview}}
15
15
 
File without changes
@@ -53,7 +53,7 @@
53
53
  { "script": "loadTaskState" },
54
54
  { "script": "resolveArtifacts" },
55
55
  { "script": "loadPriorArt" },
56
- { "script": "loadVaultContext" },
56
+ { "script": "loadMemoryContext" },
57
57
  { "script": "loadConventions" },
58
58
  { "script": "loadCoverageRules" },
59
59
  { "script": "composePrompt" }
@@ -25,7 +25,7 @@ If the plan above is non-empty, TREAT IT AS AUTHORITATIVE — follow its file li
25
25
 
26
26
  If a prior-art block is present above, READ THE DIFFS — those are failed or superseded attempts at this same issue. Identify what went wrong (review comments, the fact they were closed without merging, or behavioural gaps in the diff itself) and pick a different approach. Repeating a prior failed attempt is a hard failure even if your tests pass locally.
27
27
 
28
- {{vaultContext}}
28
+ {{memoryContext}}
29
29
 
30
30
  # Required steps (all in this one session — no handoff)
31
31
  1. **Research** — read the issue carefully, then meet the research floor below before any Edit/Write. Use Grep/Glob/Read to investigate.
@@ -0,0 +1,89 @@
1
+ ---
2
+ every: 7d
3
+ ---
4
+
5
+ # watch-stale-prs
6
+
7
+ > Weekly digest of open PRs that haven't been touched in a while. Writes a
8
+ > markdown report at `.kody/reports/watch-stale-prs.md` (surfaced by the
9
+ > dashboard's `/reports` page).
10
+ >
11
+ > Cadence is enforced by the engine via the `every: 7d` frontmatter — this
12
+ > file only fires once per 7 days regardless of how often `job-scheduler`
13
+ > wakes. No prose cadence guard needed.
14
+
15
+ ## Job
16
+
17
+ Find every open PR untouched for **≥ 7 days** and write a report listing
18
+ them, sorted by staleness (oldest first). When there are no stale PRs,
19
+ write a short "all clear" report so operators know the check ran.
20
+
21
+ ### What "stale" means
22
+
23
+ A PR is stale if:
24
+
25
+ - `state` is `OPEN`, AND
26
+ - `updatedAt` is more than 7 days before now.
27
+
28
+ Use `gh pr list --state open --limit 100 --json number,title,url,updatedAt,author`
29
+ to enumerate. Filter and sort client-side; do not call `gh` once per PR.
30
+
31
+ ### Report shape
32
+
33
+ Write to `.kody/reports/watch-stale-prs.md`. Overwrite each run.
34
+
35
+ When stale PRs exist:
36
+
37
+ ```markdown
38
+ # Stale PRs — <ISO date>
39
+
40
+ 🟡 <N> PR(s) untouched for > 7 days.
41
+
42
+ | # | Title | Author | Days stale | Updated |
43
+ |---|-------|--------|------------|---------|
44
+ | [#123](url) | <title> | @user | 14 | 2026-04-25 |
45
+ | ... | | | | |
46
+ ```
47
+
48
+ When none:
49
+
50
+ ```markdown
51
+ # Stale PRs — <ISO date>
52
+
53
+ 🟢 No open PRs untouched for more than 7 days.
54
+ ```
55
+
56
+ Truncate to the 50 oldest if the list is longer; append a final line
57
+ `> … and N more not shown`.
58
+
59
+ ## Allowed Commands
60
+
61
+ - `gh pr list --state open --limit 100 --json number,title,url,updatedAt,author`
62
+ - `gh api -X GET /repos/{owner}/{repo}/contents/.kody/reports/watch-stale-prs.md`
63
+ — only to fetch the existing file's `sha` for an update.
64
+ - `gh api -X PUT /repos/{owner}/{repo}/contents/.kody/reports/watch-stale-prs.md`
65
+ — to write the report (base64-encoded `content`, `message`, and `sha`
66
+ when updating). This is the **only** permitted write path for this job.
67
+
68
+ ## Restrictions
69
+
70
+ - Never edit, create, or delete any other file in the working tree.
71
+ - Never `git commit`, `git push`, or open a PR.
72
+ - Never post comments on PRs or issues; the report file is the only
73
+ output channel.
74
+ - Never call `gh` per-PR — one `pr list` is enough.
75
+
76
+ ## State
77
+
78
+ `cursor`: always `"idle"` — this job has no phases; each fire is a
79
+ one-shot report write.
80
+
81
+ `data`:
82
+
83
+ - `lastStaleCount` (number) — how many stale PRs were in the most recent
84
+ report. Diagnostic only; the engine ignores it.
85
+
86
+ (Engine-managed fields like `lastFiredAt` live under `data` automatically;
87
+ do not write or rely on them from the prompt.)
88
+
89
+ `done`: always `false` — this job is evergreen.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.19",
3
+ "version": "0.4.21",
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",
@@ -12,6 +12,18 @@
12
12
  "templates",
13
13
  "kody.config.schema.json"
14
14
  ],
15
+ "scripts": {
16
+ "kody": "tsx bin/kody.ts",
17
+ "build": "tsup && node scripts/copy-assets.cjs",
18
+ "test": "vitest run tests/unit tests/int --no-coverage",
19
+ "test:e2e": "vitest run tests/e2e --no-coverage",
20
+ "test:all": "vitest run tests --no-coverage",
21
+ "typecheck": "tsc --noEmit",
22
+ "lint": "biome check",
23
+ "lint:fix": "biome check --write",
24
+ "format": "biome format --write",
25
+ "prepublishOnly": "pnpm build"
26
+ },
15
27
  "dependencies": {
16
28
  "@actions/cache": "^6.0.0",
17
29
  "@anthropic-ai/claude-agent-sdk": "0.2.119"
@@ -32,16 +44,5 @@
32
44
  "url": "git+https://github.com/aharonyaircohen/kody-engine.git"
33
45
  },
34
46
  "homepage": "https://github.com/aharonyaircohen/kody-engine",
35
- "bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
36
- "scripts": {
37
- "kody": "tsx bin/kody.ts",
38
- "build": "tsup && node scripts/copy-assets.cjs",
39
- "test": "vitest run tests/unit tests/int --no-coverage",
40
- "test:e2e": "vitest run tests/e2e --no-coverage",
41
- "test:all": "vitest run tests --no-coverage",
42
- "typecheck": "tsc --noEmit",
43
- "lint": "biome check",
44
- "lint:fix": "biome check --write",
45
- "format": "biome format --write"
46
- }
47
- }
47
+ "bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
48
+ }
@@ -1,48 +0,0 @@
1
- {
2
- "name": "memorize",
3
- "role": "watch",
4
- "describe": "Scheduled: synthesize recently merged PRs into the project's .kody/vault/ markdown wiki and open a PR with the changes.",
5
- "kind": "scheduled",
6
- "schedule": "0 3 * * *",
7
- "inputs": [],
8
- "claudeCode": {
9
- "model": "inherit",
10
- "permissionMode": "acceptEdits",
11
- "maxTurns": null,
12
- "maxThinkingTokens": null,
13
- "systemPromptAppend": null,
14
- "tools": ["Read", "Write", "Edit", "Bash", "Grep", "Glob"],
15
- "hooks": ["block-git"],
16
- "skills": [],
17
- "commands": [],
18
- "subagents": [],
19
- "plugins": [],
20
- "mcpServers": []
21
- },
22
- "cliTools": [
23
- {
24
- "name": "gh",
25
- "install": {
26
- "required": true,
27
- "checkCommand": "command -v gh"
28
- },
29
- "verify": "gh auth status",
30
- "usage": "Use `gh` only for read-only inspection (`gh pr view`, `gh pr list`, `gh api`) when you need extra context on a referenced PR. Never use it to commit, push, or open PRs — the wrapper does that.",
31
- "allowedUses": ["pr", "api", "issue"]
32
- }
33
- ],
34
- "inputArtifacts": [],
35
- "outputArtifacts": [],
36
- "scripts": {
37
- "preflight": [
38
- { "script": "memorizeFlow" },
39
- { "script": "composePrompt" }
40
- ],
41
- "postflight": [
42
- { "script": "parseAgentResult" },
43
- { "script": "abortUnfinishedGitOps" },
44
- { "script": "commitAndPush" },
45
- { "script": "ensureMemorizePr" }
46
- ]
47
- }
48
- }
@@ -1,59 +0,0 @@
1
- You are **kody memorize**, the project's long-term memory keeper. You synthesize recently merged work into a markdown knowledge base at `.kody/vault/` so future kody runs can recall decisions, conventions, and component knowledge.
2
-
3
- ## What you have
4
-
5
- ### Recent merged PRs (since {{vaultSinceIso}})
6
-
7
- {{recentPrs}}
8
-
9
- ### Existing vault index
10
-
11
- Vault root: `.kody/vault/`
12
-
13
- {{vaultIndex}}
14
-
15
- ## What to do
16
-
17
- 1. **Read each recent PR's title, body, and (if useful) diff via `gh pr view <n>` / `gh pr diff <n>`.**
18
- 2. **Map each PR to the concept pages it affects** — files like `architecture/<area>.md`, `conventions/<topic>.md`, `decisions/<slug>.md`, `components/<name>.md`, or whatever organization the existing vault uses. If the vault is empty, start with a small set of pages reflecting what you actually learned.
19
- 3. **Update or create those pages.** Each page is a concept (e.g. "executor", "release flow"), NOT a per-PR log. A PR contributes one or more updates — small additions, edits to keep current, links back to the PR URL.
20
- 4. **Cross-link** related pages with relative markdown links so the vault forms a connected graph.
21
- 5. **Be terse.** Each page is reference material, not a story. One short paragraph per fact, bullet lists where useful, links instead of recapping.
22
- 6. **Don't duplicate the codebase.** Capture *what was decided* and *why*, not *how* the code looks — the code is authoritative for that.
23
- 7. **Don't invent.** If a PR's intent isn't clear, skip it rather than guessing.
24
-
25
- ## Page conventions
26
-
27
- - Filename: `kebab-case.md`.
28
- - Frontmatter (YAML) on every page:
29
- ```yaml
30
- ---
31
- title: <Human Title>
32
- type: architecture | convention | decision | component | runbook
33
- updated: {{vaultUpdatedIso}}
34
- sources:
35
- - <PR URL or file path>
36
- ---
37
- ```
38
- - Body: one short intro paragraph, then sections.
39
- - Cross-references via relative links: `[executor](../architecture/executor.md)`.
40
-
41
- ## Rules
42
-
43
- - Edit files only under `.kody/vault/`. Do not touch any other path.
44
- - Do not commit or push. The wrapper does it.
45
- - Do not run `git` or `gh` for anything except read-only inspection of referenced PRs.
46
- - If there is nothing meaningful to add (PRs are trivial chores, all already captured, etc.), say so and emit `DONE` with `COMMIT_MSG: chore(vault): no updates`. The wrapper will detect no changes and skip the PR.
47
-
48
- ## Output contract (MANDATORY)
49
-
50
- End your response with these lines, exactly:
51
-
52
- ```
53
- DONE
54
- COMMIT_MSG: chore(vault): <one-line summary>
55
- PR_SUMMARY:
56
- <2–6 line summary of what changed in the vault and why>
57
- ```
58
-
59
- If you decide to abort with no changes, emit `DONE` with the no-updates `COMMIT_MSG` and a brief `PR_SUMMARY` saying so.
@@ -1,30 +0,0 @@
1
- {
2
- "name": "watch-stale-prs",
3
- "role": "watch",
4
- "describe": "Scheduled: list open PRs untouched for N days and report. No agent invocation.",
5
- "kind": "scheduled",
6
- "schedule": "0 8 * * MON",
7
- "inputs": [],
8
- "claudeCode": {
9
- "model": "inherit",
10
- "permissionMode": "default",
11
- "maxTurns": null,
12
- "systemPromptAppend": null,
13
- "tools": [],
14
- "hooks": [],
15
- "skills": [],
16
- "commands": [],
17
- "subagents": [],
18
- "plugins": [],
19
- "mcpServers": []
20
- },
21
- "cliTools": [],
22
- "scripts": {
23
- "preflight": [
24
- {
25
- "script": "watchStalePrsFlow"
26
- }
27
- ],
28
- "postflight": []
29
- }
30
- }