@1mancompany/onemancompany 0.7.32 → 0.7.33

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": "@1mancompany/onemancompany",
3
- "version": "0.7.32",
3
+ "version": "0.7.33",
4
4
  "description": "The AI Operating System for One-Person Companies",
5
5
  "bin": {
6
6
  "onemancompany": "bin/cli.js"
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "onemancompany"
3
- version = "0.7.32"
3
+ version = "0.7.33"
4
4
  description = "A one-man company simulation with pixel art visualization and LangChain AI agents"
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -37,6 +37,12 @@ from onemancompany.core.models import (
37
37
  )
38
38
  from onemancompany.core.store import _read_yaml, _write_yaml, mark_dirty
39
39
 
40
+ # ---------------------------------------------------------------------------
41
+ # Configurable constants
42
+ # ---------------------------------------------------------------------------
43
+
44
+ HISTORY_MAX_ENTRIES: int = 100
45
+
40
46
  # ---------------------------------------------------------------------------
41
47
  # Per-slug threading locks (same pattern as project_archive.py)
42
48
  # ---------------------------------------------------------------------------
@@ -269,8 +275,8 @@ def _append_history(data: dict, field: str, old_value, new_value, changed_by: st
269
275
  "new_value": str(new_value) if new_value is not None else None,
270
276
  "changed_by": changed_by,
271
277
  })
272
- if len(data["history"]) > 100:
273
- data["history"] = data["history"][-100:]
278
+ if len(data["history"]) > HISTORY_MAX_ENTRIES:
279
+ data["history"] = data["history"][-HISTORY_MAX_ENTRIES:]
274
280
 
275
281
 
276
282
  def create_issue(
@@ -355,8 +361,12 @@ def list_issues(
355
361
  priority: IssuePriority | None = None,
356
362
  labels: list[str] | None = None,
357
363
  sprint: str | None = None,
364
+ assignee_id: str | None = None,
358
365
  ) -> list[dict]:
359
- """List issues for a product, optionally filtered."""
366
+ """List issues for a product, optionally filtered.
367
+
368
+ assignee_id: filter by assignee. Empty string "" means unassigned.
369
+ """
360
370
  issues_path = _issues_dir(slug)
361
371
  if not issues_path.exists():
362
372
  return []
@@ -378,6 +388,14 @@ def list_issues(
378
388
  continue
379
389
  if sprint is not None and data.get("sprint") != sprint:
380
390
  continue
391
+ if assignee_id is not None:
392
+ issue_assignee = data.get("assignee_id") or ""
393
+ if assignee_id == "":
394
+ # Filter for unassigned
395
+ if issue_assignee:
396
+ continue
397
+ elif issue_assignee != assignee_id:
398
+ continue
381
399
  results.append(data)
382
400
  return results
383
401
 
@@ -23,6 +23,16 @@ from onemancompany.core.system_cron import system_cron
23
23
  # Priorities that auto-trigger project creation
24
24
  _AUTO_PROJECT_PRIORITIES = {IssuePriority.P0.value, IssuePriority.P1.value}
25
25
 
26
+ # ---------------------------------------------------------------------------
27
+ # Configurable thresholds (B4 audit: extracted from inline magic numbers)
28
+ # ---------------------------------------------------------------------------
29
+
30
+ KR_LAGGING_THRESHOLD: int = 50 # KR progress % below which it's "lagging"
31
+ MAX_ACTIVE_PROJECTS: int = 3 # Max concurrent active projects per product
32
+ BACKLOG_GROOMING_THRESHOLD: int = 5 # P2/P3 unscheduled issues before grooming nudge
33
+ STALE_REVIEW_HOURS: int = 24 # Hours before an open review is considered stale
34
+ BLOCKED_DAYS_THRESHOLD: int = 7 # Days before a blocked issue is flagged
35
+ UNHANDLED_BACKLOG_THRESHOLD: int = 2 # Unhandled backlog issues before alert
26
36
 
27
37
  # ---------------------------------------------------------------------------
28
38
  # Trigger handlers
@@ -332,7 +342,7 @@ async def check_kr_progress(product_slug: str) -> list[dict]:
332
342
  if target <= 0:
333
343
  continue
334
344
  progress_pct = current / target * 100
335
- if progress_pct >= 50:
345
+ if progress_pct >= KR_LAGGING_THRESHOLD:
336
346
  continue
337
347
 
338
348
  # Check if an open issue already exists for this KR
@@ -415,7 +425,7 @@ async def run_product_check(product_slug: str) -> dict:
415
425
 
416
426
  # High priority + no active project → create project
417
427
  if priority in _AUTO_PROJECT_PRIORITIES and not linked:
418
- if len(active_for_product) >= 3:
428
+ if len(active_for_product) >= MAX_ACTIVE_PROJECTS:
419
429
  logger.debug("[PRODUCT_CHECK] Skipping project for issue {} — 3+ active projects", issue["id"])
420
430
  continue
421
431
  project_id = await _create_project_for_issue(product_slug, issue)
@@ -430,7 +440,7 @@ async def run_product_check(product_slug: str) -> dict:
430
440
 
431
441
  # Has assignee but no project → create project
432
442
  elif issue.get("assignee_id") and not linked:
433
- if len(active_for_product) >= 3:
443
+ if len(active_for_product) >= MAX_ACTIVE_PROJECTS:
434
444
  continue
435
445
  project_id = await _create_project_for_issue(product_slug, issue)
436
446
  if project_id:
@@ -484,34 +494,31 @@ async def run_product_check(product_slug: str) -> dict:
484
494
  logger.debug("[PRODUCT_CHECK] Invalid end_date '{}' on sprint {}", end_date_str, active_sprint.get("id"))
485
495
 
486
496
  # --- Step 4: Backlog grooming reminder ---
487
- _BACKLOG_GROOMING_THRESHOLD = 5
488
497
  unscheduled_low = [
489
498
  i for i in all_issues
490
499
  if i.get("priority") in (IssuePriority.P2.value, IssuePriority.P3.value)
491
500
  and not i.get("sprint")
492
501
  and i.get("status") not in (IssueStatus.DONE.value, IssueStatus.RELEASED.value)
493
502
  ]
494
- if len(unscheduled_low) >= _BACKLOG_GROOMING_THRESHOLD:
503
+ if len(unscheduled_low) >= BACKLOG_GROOMING_THRESHOLD:
495
504
  actions_taken.append(f"{len(unscheduled_low)} P2/P3 issues unscheduled — backlog grooming needed")
496
505
 
497
- # --- Step 5: Stale review check (open > 24h) ---
506
+ # --- Step 5: Stale review check ---
498
507
  from datetime import datetime as _datetime, timedelta as _timedelta
499
508
 
500
509
  open_reviews = prod.list_reviews(product_slug, status="open")
501
- _STALE_REVIEW_HOURS = 24
502
510
  stale_reviews = []
503
511
  for rev in open_reviews:
504
512
  try:
505
513
  created = _datetime.fromisoformat(rev.get("created_at", ""))
506
- if _datetime.now() - created > _timedelta(hours=_STALE_REVIEW_HOURS):
514
+ if _datetime.now() - created > _timedelta(hours=STALE_REVIEW_HOURS):
507
515
  stale_reviews.append(rev)
508
516
  except (ValueError, TypeError):
509
517
  logger.debug("[PRODUCT_CHECK] Invalid created_at on review {}", rev.get("id"))
510
518
  if stale_reviews:
511
- actions_taken.append(f"{len(stale_reviews)} stale review(s) open > {_STALE_REVIEW_HOURS}h")
519
+ actions_taken.append(f"{len(stale_reviews)} stale review(s) open > {STALE_REVIEW_HOURS}h")
512
520
 
513
- # --- Step 6: Blocked issue check (blocked > 7 days) ---
514
- _BLOCKED_DAYS_THRESHOLD = 7
521
+ # --- Step 6: Blocked issue check ---
515
522
  for issue in all_issues:
516
523
  if issue.get("status") in (IssueStatus.DONE.value, IssueStatus.RELEASED.value):
517
524
  continue
@@ -532,9 +539,9 @@ async def run_product_check(product_slug: str) -> dict:
532
539
  oldest_blocked_at = link_created
533
540
  except (ValueError, TypeError):
534
541
  logger.debug("[PRODUCT_CHECK] Invalid created_at on link in issue {}", issue.get("id"))
535
- if oldest_blocked_at and _datetime.now() - oldest_blocked_at > _timedelta(days=_BLOCKED_DAYS_THRESHOLD):
542
+ if oldest_blocked_at and _datetime.now() - oldest_blocked_at > _timedelta(days=BLOCKED_DAYS_THRESHOLD):
536
543
  actions_taken.append(
537
- f"Issue '{issue['title']}' blocked for >{_BLOCKED_DAYS_THRESHOLD} days"
544
+ f"Issue '{issue['title']}' blocked for >{BLOCKED_DAYS_THRESHOLD} days"
538
545
  )
539
546
 
540
547
  # --- Step 7: Check if owner review is needed ---
@@ -546,7 +553,7 @@ async def run_product_check(product_slug: str) -> dict:
546
553
  i for i in all_issues
547
554
  if i.get("status") == IssueStatus.BACKLOG.value and not i.get("linked_task_ids")
548
555
  ]
549
- if len(unhandled_backlog) > 2:
556
+ if len(unhandled_backlog) > UNHANDLED_BACKLOG_THRESHOLD:
550
557
  needs_review = True
551
558
  review_reasons.append(f"{len(unhandled_backlog)} unhandled backlog issues")
552
559
 
@@ -570,7 +577,7 @@ async def run_product_check(product_slug: str) -> dict:
570
577
  logger.debug("[PRODUCT_CHECK] Invalid end_date on sprint {} for review check", active_sprint.get("id"))
571
578
 
572
579
  # Backlog grooming threshold → needs owner review
573
- if len(unscheduled_low) >= _BACKLOG_GROOMING_THRESHOLD:
580
+ if len(unscheduled_low) >= BACKLOG_GROOMING_THRESHOLD:
574
581
  needs_review = True
575
582
  review_reasons.append(f"{len(unscheduled_low)} P2/P3 issues need sprint assignment")
576
583