@kody-ade/kody-engine 0.4.18 → 0.4.20

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
@@ -348,31 +410,43 @@ open_count=$(echo "$issues_json" | python3 -c "import json,sys; print(sum(1 for
348
410
  if [ "$open_count" = "0" ]; then
349
411
  echo "[goal-tick] all $total task(s) closed — finalising goal"
350
412
 
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`.
413
+ # Promote (or open) the goal PR. The active path opens this PR as a draft
414
+ # once the goal branch exists, so by finalize it almost always already
415
+ # exists we just mark it ready-for-review and refresh the body. Older
416
+ # goals from before the early-PR change may still need first-time creation
417
+ # here as a fallback.
354
418
  goal_pr_url=""
355
419
  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 "")
420
+ existing_pr=$(gh pr list --head "$goal_branch" --state open --json number,url,isDraft --jq '.[0]' 2>/dev/null || echo "")
421
+ title="goal: ${goal_id}"
422
+ goal_issue_number=$(read_state_field "goalIssueNumber")
423
+ # `Closes #N` auto-closes the umbrella goal issue on PR merge — that's
424
+ # how the dashboard learns the goal is done. Skip the line gracefully
425
+ # if no umbrella issue was ever opened (older goals from before this
426
+ # change, or `gh issue create` failed silently during a tick).
427
+ if [ -n "$goal_issue_number" ] && [ "$goal_issue_number" != "0" ]; then
428
+ 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")
429
+ else
430
+ 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")
431
+ fi
357
432
  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
433
  goal_pr_url=$(gh pr create \
370
434
  --head "$goal_branch" \
371
435
  --base "$default_branch" \
372
436
  --title "$title" \
373
437
  --body "$body" 2>/dev/null || echo "")
374
438
  else
439
+ existing_num=$(echo "$existing_pr" | python3 -c "import json,sys; print(json.load(sys.stdin).get('number',''))")
375
440
  goal_pr_url=$(echo "$existing_pr" | python3 -c "import json,sys; print(json.load(sys.stdin).get('url',''))")
441
+ is_draft=$(echo "$existing_pr" | python3 -c "import json,sys; print('true' if json.load(sys.stdin).get('isDraft') else 'false')")
442
+ # Refresh the body with the finalize copy so reviewers see the right
443
+ # framing. Best-effort — failure is non-fatal.
444
+ gh pr edit "$existing_num" --body "$body" >/dev/null 2>&1 || true
445
+ if [ "$is_draft" = "true" ]; then
446
+ echo "[goal-tick] promoting draft goal PR #${existing_num} to ready-for-review"
447
+ gh pr ready "$existing_num" >/dev/null 2>&1 \
448
+ || echo "[goal-tick] failed to mark PR #${existing_num} ready (continuing)"
449
+ fi
376
450
  fi
377
451
  else
378
452
  echo "[goal-tick] goal branch ${goal_branch} not found on origin — skipping final PR"
@@ -470,6 +544,11 @@ else
470
544
  fi
471
545
  fi
472
546
 
547
+ # Open the draft goal PR now that the branch exists. The PR is the dashboard's
548
+ # single anchor for the goal's branch + preview + CI; finalize promotes it
549
+ # from draft to ready-for-review when every task has closed.
550
+ ensure_goal_pr
551
+
473
552
  echo "[goal-tick] dispatching @kody on task #$next_issue (--base $goal_branch)"
474
553
  gh issue comment "$next_issue" --body "@kody --base ${goal_branch}"
475
554
  gh issue edit "$next_issue" --add-label "$dispatched_label"
@@ -47,8 +47,19 @@ If you fail to emit this block, or the JSON is invalid, the tick fails and the g
47
47
  ## Rules
48
48
 
49
49
  - Never edit, create, or delete files in the working tree.
50
- - Never commit or push.
50
+ - Never commit or push via `git`. The only permitted commit path is `gh api -X PUT` against the report file (see exception below).
51
51
  - Only shell calls allowed: `gh`. Everything must go through it.
52
52
  - Keep each tick focused: do one action per candidate per wake. The cron will call you again.
53
53
  - If state says you're waiting on something, just check and re-emit — don't spawn a duplicate.
54
54
  - Honour the job body's `## Restrictions` over any inferred shortcut.
55
+
56
+ ### Single permitted write: the job's report file
57
+
58
+ A job MAY (optionally — only if its body asks for it) write a single
59
+ markdown report file at the canonical path:
60
+
61
+ ```
62
+ .kody/reports/{{jobSlug}}.md
63
+ ```
64
+
65
+ Only that exact path. Only via `gh api -X PUT /repos/<owner>/<repo>/contents/.kody/reports/{{jobSlug}}.md` (with base64 content + `sha` of the existing file when updating). All other writes — code files, other report paths, other slugs — remain forbidden. The dashboard's `/reports` page surfaces these files automatically; this is the canonical channel for a job's diagnostic output when an issue comment isn't expressive enough.
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,86 @@
1
+ # watch-stale-prs
2
+
3
+ > Weekly digest of open PRs that haven't been touched in a while. Writes a
4
+ > markdown report at `.kody/reports/watch-stale-prs.md` (surfaced by the
5
+ > dashboard's `/reports` page).
6
+
7
+ ## Job
8
+
9
+ Find every open PR untouched for **≥ 7 days** and write a report listing
10
+ them, sorted by staleness (oldest first). When there are no stale PRs,
11
+ write a short "all clear" report so operators know the check ran.
12
+
13
+ **Cadence guard.** Skip this tick unless `data.lastRunAt` is null or older
14
+ than 7 days from now. When skipping, emit the same state back unchanged
15
+ with no `gh` calls.
16
+
17
+ ### What "stale" means
18
+
19
+ A PR is stale if:
20
+
21
+ - `state` is `OPEN`, AND
22
+ - `updatedAt` is more than 7 days before now.
23
+
24
+ Use `gh pr list --state open --limit 100 --json number,title,url,updatedAt,author`
25
+ to enumerate. Filter and sort client-side; do not call `gh` once per PR.
26
+
27
+ ### Report shape
28
+
29
+ Write to `.kody/reports/watch-stale-prs.md`. Overwrite each run.
30
+
31
+ When stale PRs exist:
32
+
33
+ ```markdown
34
+ # Stale PRs — <ISO date>
35
+
36
+ 🟡 <N> PR(s) untouched for > 7 days.
37
+
38
+ | # | Title | Author | Days stale | Updated |
39
+ |---|-------|--------|------------|---------|
40
+ | [#123](url) | <title> | @user | 14 | 2026-04-25 |
41
+ | ... | | | | |
42
+ ```
43
+
44
+ When none:
45
+
46
+ ```markdown
47
+ # Stale PRs — <ISO date>
48
+
49
+ 🟢 No open PRs untouched for more than 7 days.
50
+ ```
51
+
52
+ Truncate to the 50 oldest if the list is longer; append a final line
53
+ `> … and N more not shown`.
54
+
55
+ ## Allowed Commands
56
+
57
+ - `gh pr list --state open --limit 100 --json number,title,url,updatedAt,author`
58
+ - `gh api -X GET /repos/{owner}/{repo}/contents/.kody/reports/watch-stale-prs.md`
59
+ — only to fetch the existing file's `sha` for an update.
60
+ - `gh api -X PUT /repos/{owner}/{repo}/contents/.kody/reports/watch-stale-prs.md`
61
+ — to write the report (base64-encoded `content`, `message`, and `sha`
62
+ when updating). This is the **only** permitted write path for this job.
63
+
64
+ ## Restrictions
65
+
66
+ - Never edit, create, or delete any other file in the working tree.
67
+ - Never `git commit`, `git push`, or open a PR.
68
+ - Never post comments on PRs or issues; the report file is the only
69
+ output channel.
70
+ - Never call `gh` per-PR — one `pr list` is enough.
71
+
72
+ ## State
73
+
74
+ `cursor`:
75
+
76
+ - `"idle"` — between runs (cadence guard skipped this tick or the run
77
+ finished cleanly).
78
+
79
+ `data`:
80
+
81
+ - `lastRunAt` (string, ISO) — when the report was last written. Used by
82
+ the cadence guard.
83
+ - `lastStaleCount` (number) — how many stale PRs were in the most recent
84
+ report. Diagnostic only.
85
+
86
+ `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.18",
3
+ "version": "0.4.20",
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
- }