@jesdi/skills 0.0.5 → 0.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jesdi/skills",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "jesdi's agent skills (content package consumed by @jesdi/skills-cli)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,14 +29,31 @@ def _as_int(value):
29
29
 
30
30
 
31
31
  def merge_sources(project_items, issue_rows):
32
- """Join Project field values (item-list) with issue bodies/state (issue list)."""
32
+ """Join Project field values (item-list) with issue bodies/state (issue list).
33
+
34
+ Join key is the issue number when the token can expand item content; when
35
+ content is redacted (project-scope token + private repo), fall back to the
36
+ title — GitHub syncs linked-item titles to issue titles. Ambiguous titles
37
+ (duplicates) and titles matching no issue are skipped, as are drafts.
38
+ """
33
39
  bodies = {row["number"]: row for row in issue_rows}
40
+ by_title, dup_titles = {}, set()
41
+ for row in issue_rows:
42
+ title = row.get("title")
43
+ if title in by_title:
44
+ dup_titles.add(title)
45
+ elif title is not None:
46
+ by_title[title] = row
34
47
  merged = []
35
48
  for item in project_items:
36
49
  content = item.get("content") or {}
37
50
  number = content.get("number")
38
51
  if number is None:
39
- continue # draft item (no linked issue) — not part of the work graph
52
+ row = by_title.get(item.get("title"))
53
+ if row is None or item.get("title") in dup_titles:
54
+ continue # draft, board-only, or ambiguous — not joinable
55
+ number = row["number"]
56
+ content = {"title": row.get("title", ""), "url": row.get("url", "")}
40
57
  row = bodies.get(number, {})
41
58
  merged.append({
42
59
  "number": number,
@@ -183,7 +200,7 @@ def main(owner, project_number, repo, as_json=False):
183
200
  project_items = payload.get("items", [])
184
201
  issue_rows = _gh_json([
185
202
  "gh", "issue", "list", "--repo", repo, "--state", "all",
186
- "--limit", "500", "--json", "number,body,state,labels",
203
+ "--limit", "500", "--json", "number,title,url,body,state,labels",
187
204
  ])
188
205
  issues = merge_sources(project_items, issue_rows)
189
206
  result = rank_issues(issues)
@@ -37,6 +37,46 @@ def test_merge_sources_joins_fields_and_bodies():
37
37
  assert thirtyfour["impact"] is None and thirtyfour["status"] is None
38
38
 
39
39
 
40
+ def test_merge_sources_falls_back_to_title_join_when_content_redacted():
41
+ # A project-scope token can list items and field values but cannot expand
42
+ # the linked issue of a private repo: no "content", empty "repository".
43
+ # GitHub syncs linked-item titles to issue titles, so title is the join key.
44
+ project_items = [
45
+ {"id": "PVTI_a", "title": "Add X", "repository": "",
46
+ "status": "Ready", "impact": 4, "effort": 2},
47
+ ]
48
+ issue_rows = [
49
+ {"number": 12, "title": "Add X", "url": "u/12",
50
+ "body": "Blocked by: #34", "state": "open",
51
+ "labels": [{"name": "auto"}]},
52
+ ]
53
+ out = rank.merge_sources(project_items, issue_rows)
54
+ assert len(out) == 1
55
+ twelve = out[0]
56
+ assert twelve["number"] == 12
57
+ assert twelve["url"] == "u/12"
58
+ assert twelve["impact"] == 4 and twelve["status"] == "Ready"
59
+ assert twelve["body"] == "Blocked by: #34"
60
+ assert twelve["labels"] == ["auto"]
61
+
62
+
63
+ def test_merge_sources_redacted_item_without_matching_title_is_skipped():
64
+ out = rank.merge_sources(
65
+ [{"id": "PVTI_a", "title": "board-only note", "status": "Ready"}],
66
+ [{"number": 1, "title": "Other", "url": "u/1", "body": "", "state": "open"}],
67
+ )
68
+ assert out == []
69
+
70
+
71
+ def test_merge_sources_title_join_skips_ambiguous_duplicate_titles():
72
+ project_items = [{"id": "PVTI_a", "title": "Dup", "status": "Ready"}]
73
+ issue_rows = [
74
+ {"number": 1, "title": "Dup", "url": "u/1", "body": "", "state": "open"},
75
+ {"number": 2, "title": "Dup", "url": "u/2", "body": "", "state": "open"},
76
+ ]
77
+ assert rank.merge_sources(project_items, issue_rows) == []
78
+
79
+
40
80
  def test_merge_sources_missing_body_row_defaults_open_empty():
41
81
  out = rank.merge_sources(
42
82
  [{"content": {"number": 7, "title": "T", "url": "u/7"}, "impact": 3, "effort": 1}],
@@ -249,7 +289,9 @@ def test_main_json_emits_machine_readable_rows(monkeypatch, capsys):
249
289
  {"content": {"number": 5, "title": "Task", "url": "u/5"},
250
290
  "status": "Ready", "impact": 4, "effort": 2}]})
251
291
  else:
252
- assert "labels" in args[args.index("--json") + 1]
292
+ fields = args[args.index("--json") + 1]
293
+ assert "labels" in fields
294
+ assert "title" in fields and "url" in fields # title-join keys
253
295
  stdout = jsonlib.dumps([
254
296
  {"number": 5, "body": "", "state": "open",
255
297
  "labels": [{"name": "auto"}]}])
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-07-23T11:21:38.913Z",
3
+ "generatedAt": "2026-07-23T15:49:56.550Z",
4
4
  "skills": [
5
5
  {
6
6
  "name": "backlog",
7
- "version": "0.1.2",
8
- "hash": "sha256-40e91ee1f155c778ee596fd4f459cacbdcf8221bf95b0f9f2e45652207aaa275",
7
+ "version": "0.1.3",
8
+ "hash": "sha256-afe5ef66a86a4bbb1ce654e9ec861be80873e36e1cb45fbe132564a25dc26c1d",
9
9
  "description": "Manage a GitHub-Issues-backed backlog and a Projects-v2 work graph via five verbs. Use when the user wants to CAPTURE an idea/research thread (\"add to backlog\", \"capture this\", \"note this idea\", \"log a task\"), TRIAGE the inbox (\"triage the backlog\", \"score the inbox\"), see what to work on NEXT (\"what's next\", \"what should I work on\", \"ranked backlog\", \"next actionable task\"), LAND a finished item (\"mark done\", \"close out #N\"), or SETUP the board the first time. Ranks open issues by impact÷effort, respecting `Blocked by:` edges, so only actionable work surfaces."
10
10
  },
11
11
  {