@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
|
-
**
|
|
3
|
+
**Multi-agent development framework for Claude Code — plan, implement, review, and ship with specialist agents and minimal token overhead.**
|
|
11
4
|
|
|
12
5
|
[](https://www.npmjs.com/package/@benzotti/jedi) [](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
|
-
#
|
|
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
|
|
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 (
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|