@benzotti/jedi 0.1.34 → 0.1.36

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
@@ -1,13 +1,6 @@
1
- ```
2
- ██╗███████╗██████╗ ██╗
3
- ██║██╔════╝██╔══██╗██║
4
- ██║█████╗ ██║ ██║██║
5
- ██ ██║██╔══╝ ██║ ██║██║
6
- ╚█████╔╝███████╗██████╔╝██║
7
- ╚════╝ ╚══════╝╚═════╝ ╚═╝
8
- ```
1
+ # Jedi
9
2
 
10
- **Context-efficient AI development framework for Claude Code.**
3
+ **Multi-agent development framework for Claude Code — plan, implement, review, and ship with specialist agents and minimal token overhead.**
11
4
 
12
5
  [![npm version](https://img.shields.io/npm/v/@benzotti/jedi)](https://www.npmjs.com/package/@benzotti/jedi) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
13
6
 
@@ -7,6 +7,9 @@
7
7
  #
8
8
  # Optional:
9
9
  # - Set CLICKUP_API_TOKEN secret for ClickUp ticket integration
10
+ # - Set JEDI_COMMIT_LEARNINGS=true repo variable to persist learnings in git
11
+ # - Set JEDI_LEARNINGS_REPO variable to push learnings to a central repo (e.g. org/jedi-learnings)
12
+ # - Set JEDI_LEARNINGS_TOKEN secret (PAT with repo access) when using an external learnings repo
10
13
  # - Run `npx @benzotti/jedi init` locally to customise framework files
11
14
  #
12
15
  # Usage: Comment on any issue or PR with "Hey Jedi" followed by a command:
@@ -21,7 +24,8 @@
21
24
  # reply with feedback to refine, or say "approved" to finalise.
22
25
  #
23
26
  # Learnings: Jedi accumulates team preferences across PRs automatically.
24
- # No files to commit — everything lives in the GitHub Actions cache.
27
+ # By default learnings live in the GitHub Actions cache. Set JEDI_COMMIT_LEARNINGS=true
28
+ # to persist them in git (recommended — cache entries expire after 7 days of inactivity).
25
29
 
26
30
  name: Jedi
27
31
 
@@ -117,6 +121,49 @@ jobs:
117
121
  grep -qxF "$pattern" .git/info/exclude 2>/dev/null || echo "$pattern" >> .git/info/exclude
118
122
  done
119
123
 
124
+ # Fetch shared learnings from an external repo if configured.
125
+ # This merges cross-project learnings into the local framework before agents run.
126
+ - name: Fetch shared learnings
127
+ if: vars.JEDI_LEARNINGS_REPO != ''
128
+ run: |
129
+ LEARNINGS_DIR=".jdi/framework/learnings"
130
+ mkdir -p "$LEARNINGS_DIR"
131
+
132
+ # Clone only the directory for this repo's learnings (sparse checkout)
133
+ REPO_SUBDIR="${{ github.repository }}"
134
+ TMPDIR=$(mktemp -d)
135
+ git clone --depth 1 --filter=blob:none --sparse \
136
+ "https://x-access-token:${LEARNINGS_TOKEN}@github.com/${LEARNINGS_REPO}.git" \
137
+ "$TMPDIR" 2>/dev/null || {
138
+ echo "Could not clone learnings repo — continuing without shared learnings"
139
+ exit 0
140
+ }
141
+
142
+ cd "$TMPDIR"
143
+ git sparse-checkout set "$REPO_SUBDIR" 2>/dev/null || true
144
+ cd - > /dev/null
145
+
146
+ # Copy learnings from external repo into local framework
147
+ # External learnings serve as baseline; cache/local files take precedence
148
+ if [ -d "$TMPDIR/$REPO_SUBDIR" ]; then
149
+ for f in "$TMPDIR/$REPO_SUBDIR"/*.md; do
150
+ [ -f "$f" ] || continue
151
+ BASENAME=$(basename "$f")
152
+ # Only copy if local file doesn't exist (cache/committed files take precedence)
153
+ if [ ! -f "$LEARNINGS_DIR/$BASENAME" ]; then
154
+ cp "$f" "$LEARNINGS_DIR/$BASENAME"
155
+ echo "Loaded shared learning: $BASENAME"
156
+ fi
157
+ done
158
+ else
159
+ echo "No shared learnings found for ${{ github.repository }} — continuing"
160
+ fi
161
+
162
+ rm -rf "$TMPDIR"
163
+ env:
164
+ LEARNINGS_REPO: ${{ vars.JEDI_LEARNINGS_REPO }}
165
+ LEARNINGS_TOKEN: ${{ secrets.JEDI_LEARNINGS_TOKEN || github.token }}
166
+
120
167
  # Configure git for Jedi commits (used when implementing changes on PR branches)
121
168
  - name: Configure git
122
169
  run: |
@@ -171,13 +218,49 @@ jobs:
171
218
  key: jedi-state-${{ github.repository }}-${{ steps.pr.outputs.branch || github.head_ref || github.ref_name }}-${{ github.run_id }}
172
219
 
173
220
  # ── Promote learnings to main baseline when PRs merge ──
221
+ # Only runs when Jedi was actually used on the merged PR
174
222
  promote-learnings:
175
223
  if: github.event_name == 'push'
176
224
  runs-on: ubuntu-latest
177
225
  steps:
226
+ - name: Check if Jedi was involved
227
+ id: check
228
+ run: |
229
+ # Find the PR associated with this merge commit
230
+ PR_NUMBER=$(gh api "repos/$REPO/commits/${{ github.sha }}/pulls" --jq '.[0].number // empty' 2>/dev/null || true)
231
+ if [ -z "$PR_NUMBER" ]; then
232
+ echo "No associated PR found — skipping"
233
+ echo "skip=true" >> "$GITHUB_OUTPUT"
234
+ exit 0
235
+ fi
236
+
237
+ # Check if Jedi commented on this PR (comments from github-actions bot mentioning Jedi)
238
+ JEDI_ACTIVITY=$(gh api "repos/$REPO/issues/$PR_NUMBER/comments" --paginate \
239
+ --jq '[.[] | select(.user.login == "github-actions[bot]" and (.body | test("jedi|Jedi|🤖")))] | length' 2>/dev/null || echo "0")
240
+
241
+ # Also check if jedi[bot] made any commits on the PR
242
+ JEDI_COMMITS=$(gh api "repos/$REPO/pulls/$PR_NUMBER/commits" --paginate \
243
+ --jq '[.[] | select(.commit.author.name == "jedi[bot]")] | length' 2>/dev/null || echo "0")
244
+
245
+ if [ "$JEDI_ACTIVITY" -gt 0 ] || [ "$JEDI_COMMITS" -gt 0 ]; then
246
+ BRANCH=$(gh api "repos/$REPO/pulls/$PR_NUMBER" --jq '.head.ref')
247
+ echo "Jedi was active on PR #$PR_NUMBER (comments: $JEDI_ACTIVITY, commits: $JEDI_COMMITS) — promoting learnings"
248
+ echo "skip=false" >> "$GITHUB_OUTPUT"
249
+ echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
250
+ echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
251
+ else
252
+ echo "No Jedi activity on PR #$PR_NUMBER — skipping"
253
+ echo "skip=true" >> "$GITHUB_OUTPUT"
254
+ fi
255
+ env:
256
+ GH_TOKEN: ${{ github.token }}
257
+ REPO: ${{ github.repository }}
258
+
178
259
  - uses: actions/checkout@v4
260
+ if: steps.check.outputs.skip != 'true'
179
261
 
180
- - name: Restore latest Jedi state
262
+ - name: Restore Jedi state from merged branch
263
+ if: steps.check.outputs.skip != 'true'
181
264
  id: restore
182
265
  uses: actions/cache@v4
183
266
  with:
@@ -189,10 +272,10 @@ jobs:
189
272
  .claude/
190
273
  key: jedi-state-${{ github.repository }}-main-promotion-${{ github.sha }}
191
274
  restore-keys: |
192
- jedi-state-${{ github.repository }}-
275
+ jedi-state-${{ github.repository }}-${{ steps.check.outputs.branch }}-
193
276
 
194
277
  - name: Promote to main baseline
195
- if: steps.restore.outputs.cache-hit != ''
278
+ if: steps.check.outputs.skip != 'true' && steps.restore.outputs.cache-hit != ''
196
279
  uses: actions/cache/save@v4
197
280
  with:
198
281
  path: |
@@ -202,3 +285,93 @@ jobs:
202
285
  .jdi/plans/
203
286
  .claude/
204
287
  key: jedi-state-${{ github.repository }}-main-${{ github.sha }}
288
+
289
+ # Opt-in: commit learnings so they survive cache eviction.
290
+ # Set repo variable JEDI_COMMIT_LEARNINGS=true to enable.
291
+ #
292
+ # Where learnings are committed:
293
+ # - Same repo (default): .jdi/framework/learnings/ on main
294
+ # - External repo: set JEDI_LEARNINGS_REPO (e.g. org/jedi-learnings)
295
+ # and JEDI_LEARNINGS_TOKEN secret. Learnings are namespaced under
296
+ # <owner>/<repo>/ in the external repo to support multiple projects.
297
+ - name: Commit learnings to repo
298
+ if: >-
299
+ steps.check.outputs.skip != 'true'
300
+ && steps.restore.outputs.cache-hit != ''
301
+ && vars.JEDI_COMMIT_LEARNINGS == 'true'
302
+ run: |
303
+ LEARNINGS_SRC=".jdi/framework/learnings"
304
+
305
+ # Only proceed if there are learnings files with content
306
+ if [ ! -d "$LEARNINGS_SRC" ]; then
307
+ echo "No learnings directory found — skipping commit"
308
+ exit 0
309
+ fi
310
+
311
+ # Check if any learnings files have meaningful content (not just headers)
312
+ HAS_CONTENT=false
313
+ for f in "$LEARNINGS_SRC"/*.md; do
314
+ [ -f "$f" ] || continue
315
+ if grep -qvE '^\s*(#|<!--|$)' "$f" 2>/dev/null; then
316
+ HAS_CONTENT=true
317
+ break
318
+ fi
319
+ done
320
+
321
+ if [ "$HAS_CONTENT" != "true" ]; then
322
+ echo "No learnings content to commit — skipping"
323
+ exit 0
324
+ fi
325
+
326
+ git config user.name "jedi[bot]"
327
+ git config user.email "jedi[bot]@users.noreply.github.com"
328
+
329
+ if [ -n "$LEARNINGS_REPO" ]; then
330
+ # ── External repo: clone, copy learnings, push ──
331
+ REPO_SUBDIR="${{ github.repository }}"
332
+ TMPDIR=$(mktemp -d)
333
+
334
+ git clone --depth 1 \
335
+ "https://x-access-token:${LEARNINGS_TOKEN}@github.com/${LEARNINGS_REPO}.git" \
336
+ "$TMPDIR" 2>/dev/null || {
337
+ echo "::warning::Could not clone learnings repo ${LEARNINGS_REPO} — skipping commit"
338
+ exit 0
339
+ }
340
+
341
+ mkdir -p "$TMPDIR/$REPO_SUBDIR"
342
+ cp "$LEARNINGS_SRC"/*.md "$TMPDIR/$REPO_SUBDIR/"
343
+
344
+ cd "$TMPDIR"
345
+ git config user.name "jedi[bot]"
346
+ git config user.email "jedi[bot]@users.noreply.github.com"
347
+ git add "$REPO_SUBDIR"/*.md
348
+
349
+ if git diff --cached --quiet; then
350
+ echo "Learnings unchanged in external repo — nothing to commit"
351
+ else
352
+ git commit -m "chore(jedi): update learnings from ${{ github.repository }}
353
+
354
+ Source: PR #${{ steps.check.outputs.pr_number }} on ${{ github.repository }}
355
+ Learnings accumulated from PR reviews and feedback."
356
+ git push
357
+ echo "Learnings committed to ${LEARNINGS_REPO}/${REPO_SUBDIR}"
358
+ fi
359
+
360
+ cd - > /dev/null
361
+ rm -rf "$TMPDIR"
362
+ else
363
+ # ── Same repo: commit directly to main ──
364
+ git add "$LEARNINGS_SRC"/*.md
365
+ if git diff --cached --quiet; then
366
+ echo "Learnings unchanged — nothing to commit"
367
+ else
368
+ git commit -m "chore(jedi): update team learnings
369
+
370
+ Auto-committed by Jedi after PR #${{ steps.check.outputs.pr_number }} merged.
371
+ These learnings are accumulated from PR reviews and feedback."
372
+ git push
373
+ fi
374
+ fi
375
+ env:
376
+ LEARNINGS_REPO: ${{ vars.JEDI_LEARNINGS_REPO || '' }}
377
+ LEARNINGS_TOKEN: ${{ secrets.JEDI_LEARNINGS_TOKEN || github.token }}
package/dist/index.js CHANGED
@@ -9748,6 +9748,13 @@ async function loadPersistedState(cwd, storage) {
9748
9748
  } else {
9749
9749
  learningsPath = dir;
9750
9750
  }
9751
+ if (!learningsPath && existsSync6(dir)) {
9752
+ const { readdirSync } = await import("fs");
9753
+ const files = readdirSync(dir).filter((f3) => f3.endsWith(".md"));
9754
+ if (files.length > 0) {
9755
+ learningsPath = dir;
9756
+ }
9757
+ }
9751
9758
  const codebaseIndex = await storage.load("codebase-index");
9752
9759
  if (codebaseIndex) {
9753
9760
  const cbDir = join6(cwd, ".jdi", "codebase");
@@ -11610,7 +11617,7 @@ function parseComment(comment, isFollowUp) {
11610
11617
  }
11611
11618
  return { ...base, command: "quick", description };
11612
11619
  }
11613
- if (/^(approved?|lgtm|looks?\s*good|ship\s*it)/i.test(lower)) {
11620
+ if (/\b(approved?|lgtm|looks?\s*good|ship\s*it)\b/i.test(lower)) {
11614
11621
  return { ...base, command: "plan", description: body, clickUpUrl: null, isFeedback: true, isApproval: true };
11615
11622
  }
11616
11623
  if (isFollowUp) {
@@ -12287,7 +12294,7 @@ var stateCommand = defineCommand({
12287
12294
  // package.json
12288
12295
  var package_default = {
12289
12296
  name: "@benzotti/jedi",
12290
- version: "0.1.34",
12297
+ version: "0.1.36",
12291
12298
  description: "JDI - Context-efficient AI development framework for Claude Code",
12292
12299
  type: "module",
12293
12300
  bin: {
@@ -31,7 +31,7 @@ Create an implementation plan using a single planner agent (includes research).
31
31
 
32
32
  ## HARD STOP — Planning Gate
33
33
 
34
- After the user approves the plan, your work is **DONE**. Output: _"Plan approved and finalised. Run `/jdi:implement-plan` when ready to execute."_ Then **STOP completely**. Do NOT invoke `/jdi:implement-plan`, do NOT spawn implementation agents, do NOT begin writing source code. Planning and implementation are separate human-gated phases.
34
+ After the user approves the plan, your work is **DONE**. Output: _"Plan approved and locked in. Let me know when you want to implement."_ Then **STOP completely**. Do NOT invoke `/jdi:implement-plan`, do NOT spawn implementation agents, do NOT begin writing source code. Planning and implementation are separate human-gated phases.
35
35
 
36
36
  Agent base (read FIRST for cache): .jdi/framework/components/meta/AgentBase.md | Agent spec: .jdi/framework/agents/jdi-planner.md
37
37
 
@@ -39,7 +39,7 @@ Follow response templates exactly as instructed in the prompt — do not improvi
39
39
 
40
40
  Planning and implementation are **separate human-gated phases**. NEVER auto-proceed to implementation after planning or plan refinement.
41
41
 
42
- - When the user says "approved" / "lgtm" / "looks good" to a **plan**: this means the plan is finalised. It does NOT mean "go implement it." Finalise the plan review, output _"Plan approved. Run `/jdi:implement-plan` when ready."_, then **STOP**.
42
+ - When the user says "approved" / "lgtm" / "looks good" to a **plan**: this means the plan is finalised. It does NOT mean "go implement it." Finalise the plan review, output _"Plan approved and locked in. Let me know when you want to implement."_, then **STOP**.
43
43
  - When the user provides refinement feedback on a plan, ONLY update the plan files in `.jdi/plans/`. Do NOT implement code.
44
44
  - Implementation ONLY happens when the user explicitly requests it: "implement", "build", "execute", or `/jdi:implement-plan`.
45
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@benzotti/jedi",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "JDI - Context-efficient AI development framework for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {