devops-bot-sdk 1.4.78__tar.gz → 1.4.81__tar.gz

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.
Files changed (38) hide show
  1. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/pyproject.toml +1 -1
  4. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/client.py +1 -1
  6. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/ipc/handlers.py +46 -8
  7. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/README.md +0 -0
  8. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
  9. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  10. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  11. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/devops_bot_sdk.egg-info/requires.txt +0 -0
  12. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  13. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/collectors/__init__.py +0 -0
  14. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/collectors/files.py +0 -0
  15. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/collectors/process.py +0 -0
  16. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/collectors/screenshot.py +0 -0
  17. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/config.py +0 -0
  18. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/crucial.py +0 -0
  19. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/exceptions.py +0 -0
  20. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/git_ops.py +0 -0
  21. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/graphify.py +0 -0
  22. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/hooks/__init__.py +0 -0
  23. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/hooks/crucial_guard.py +0 -0
  24. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/ipc/__init__.py +0 -0
  25. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/ipc/electron_bridge.py +0 -0
  26. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/local_exec.py +0 -0
  27. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/models/__init__.py +0 -0
  28. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/models/envelope.py +0 -0
  29. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/models/requests.py +0 -0
  30. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/models/responses.py +0 -0
  31. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/models/snapshots.py +0 -0
  32. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/py.typed +0 -0
  33. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/run_auto.py +0 -0
  34. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/sse.py +0 -0
  35. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/test.py +0 -0
  36. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/test_pipeline.py +0 -0
  37. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/sdk/updater.py +0 -0
  38. {devops_bot_sdk-1.4.78 → devops_bot_sdk-1.4.81}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.78
3
+ Version: 1.4.81
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.78
3
+ Version: 1.4.81
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devops-bot-sdk"
7
- version = "1.4.78"
7
+ version = "1.4.81"
8
8
  description = "DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app"
9
9
  readme = "README.md"
10
10
  license = "LicenseRef-Proprietary"
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.4.78
3
+ Version: 1.4.81
4
4
 
5
5
  Public surface:
6
6
  BackendClient.from_config() — create client from ~/.agentos/config.toml
@@ -30,7 +30,7 @@ Rules:
30
30
  - All data egress through submit_webhook only
31
31
  """
32
32
 
33
- __version__ = "1.4.78"
33
+ __version__ = "1.4.81"
34
34
  __author__ = "AgentOS"
35
35
 
36
36
  from sdk.client import BackendClient
@@ -58,7 +58,7 @@ def _notify_status(task_id: str, status: str) -> None:
58
58
  pass
59
59
 
60
60
 
61
- SDK_VERSION = "1.4.78"
61
+ SDK_VERSION = "1.4.81"
62
62
  _POLL_INTERVAL = 3.0
63
63
  _POLL_TIMEOUT = 600.0
64
64
  _ORCHESTRATE_TIMEOUT = 2700.0 # 45 min — covers approval wait + VPS execution time
@@ -531,7 +531,7 @@ _PRD_FORMAT = (
531
531
 
532
532
 
533
533
  def _build_epic_prompt(jira_key: str, summary: str, description: str | None,
534
- human_instructions: str | None) -> str:
534
+ human_instructions: str | None, story_count: int | None = None) -> str:
535
535
  """Read-only prompt that turns an Epic into a decomposition JSON."""
536
536
  parts: list[str] = [
537
537
  "You are decomposing a JIRA EPIC into implementable user stories. This is "
@@ -578,9 +578,12 @@ def _build_epic_prompt(jira_key: str, summary: str, description: str | None,
578
578
  "}\n\n"
579
579
  "Do NOT produce humanInstructions for the stories — created stories are clean "
580
580
  "user-story artifacts; implementation instructions are added later.\n"
581
- "Create as many stories as the epic genuinely needs (do not pad or truncate "
582
- "artificially); each story must be independently implementable.\n"
583
- "assigneeEmail is OPTIONAL per story: set it ONLY when the epic explicitly "
581
+ + (f"Generate EXACTLY {story_count} user stor{'y' if story_count == 1 else 'ies'} "
582
+ "the epic explicitly asks for this many; do not produce more or fewer.\n"
583
+ if story_count else
584
+ "Create as many stories as the epic genuinely needs (do not pad or truncate "
585
+ "artificially); each story must be independently implementable.\n")
586
+ + "assigneeEmail is OPTIONAL per story: set it ONLY when the epic explicitly "
584
587
  "assigns that specific work to a named person/email (e.g. 'assign the API to "
585
588
  "alice@x.com'); otherwise omit it.\n\n"
586
589
  "=== EPIC FORMAT (epic_description MUST follow this exactly) ===\n"
@@ -650,6 +653,37 @@ _STORIES_DIR_HINT = re.compile(
650
653
  )
651
654
 
652
655
 
656
+ # An explicit "build N user stories" count in the description/instructions.
657
+ _STORY_COUNT_WORDS = {
658
+ "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6,
659
+ "seven": 7, "eight": 8, "nine": 9, "ten": 10, "eleven": 11, "twelve": 12,
660
+ }
661
+ _STORY_COUNT_RE = re.compile(
662
+ r"\b(\d{1,2})\s+(?:user\s+)?stor(?:y|ies)\b"
663
+ r"|\b(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)"
664
+ r"\s+(?:user\s+)?stor(?:y|ies)\b",
665
+ re.I,
666
+ )
667
+
668
+
669
+ def _extract_story_count(text: str | None) -> int | None:
670
+ """A requested number of user stories from the text (e.g. "build 3 user stories",
671
+ "create five stories"), or None. Bounded to a sane max."""
672
+ if not text:
673
+ return None
674
+ m = _STORY_COUNT_RE.search(text)
675
+ if not m:
676
+ return None
677
+ if m.group(1):
678
+ try:
679
+ n = int(m.group(1))
680
+ except ValueError:
681
+ return None
682
+ else:
683
+ n = _STORY_COUNT_WORDS.get((m.group(2) or "").lower(), 0)
684
+ return min(n, 50) if n and n >= 1 else None
685
+
686
+
653
687
  def _extract_stories_dir(text: str | None) -> str | None:
654
688
  """A directory the instructions ask the user stories to be WRITTEN into, or None.
655
689
 
@@ -845,12 +879,16 @@ async def _handle_epic(
845
879
  ).model_dump())
846
880
 
847
881
  # 1. Generate the decomposition, grounded in the codebase where one exists.
882
+ # If the description/instructions ask for a specific number of stories, honor
883
+ # it exactly; otherwise the model decides (bounded by _MAX_EPIC_STORIES).
884
+ story_count = _extract_story_count(description) or _extract_story_count(human)
885
+ limit = story_count or _MAX_EPIC_STORIES
848
886
  try:
849
887
  await graphify.prepare(project_path)
850
888
  except Exception: # noqa: BLE001
851
889
  pass
852
890
  gen = await _run_generation(
853
- _build_epic_prompt(jira_key, summary, description, human),
891
+ _build_epic_prompt(jira_key, summary, description, human, story_count=story_count),
854
892
  project_path, send=send, thread_id=thread_id,
855
893
  label=f"Epic {jira_key}: generating user stories",
856
894
  github_token=await client.github_token(),
@@ -893,7 +931,7 @@ async def _handle_epic(
893
931
  await send(Envelope(
894
932
  type="step_update", thread_id=thread_id,
895
933
  data={"jira_task_id": tid, "current_step": f"Epic {jira_key}: marked Done — "
896
- f"creating {min(len(stories), _MAX_EPIC_STORIES)} user Story(ies)"},
934
+ f"creating {min(len(stories), limit)} user Story(ies)"},
897
935
  ).model_dump())
898
936
 
899
937
  # 3. Create a To Do child Task per story, inheriting the Epic's fields.
@@ -902,7 +940,7 @@ async def _handle_epic(
902
940
  # inherited assignee — but ONLY on the CHILD task, never on the parent Epic.
903
941
  desc_email = _extract_email(description) or _extract_email(human)
904
942
  created: list[str] = []
905
- for st in stories[:_MAX_EPIC_STORIES]:
943
+ for st in stories[:limit]:
906
944
  if not isinstance(st, dict) or not (st.get("summary") or "").strip():
907
945
  continue
908
946
  # Precedence: a per-story email the model extracted → an email in the
@@ -945,7 +983,7 @@ async def _handle_epic(
945
983
  d = Path(stories_dir).expanduser()
946
984
  d.mkdir(parents=True, exist_ok=True) # create from scratch
947
985
  written = 0
948
- for i, st in enumerate(stories[:_MAX_EPIC_STORIES], 1):
986
+ for i, st in enumerate(stories[:limit], 1):
949
987
  if not isinstance(st, dict) or not (st.get("summary") or "").strip():
950
988
  continue
951
989
  title = str(st.get("summary", "")).strip()