From 9bf104295e7293192c44bbb7f57926b6631a9672 Mon Sep 17 00:00:00 2001 From: Dom <39115308+djm81@users.noreply.github.com> Date: Tue, 3 Feb 2026 09:50:27 +0100 Subject: [PATCH 1/6] chore: release 0.26.16 daily standup support (#178) --- .cursorignore | 18 + .github/workflows/pr-orchestrator.yml | 3 + CHANGELOG.md | 33 + docs/_layouts/default.html | 1 + docs/getting-started/README.md | 1 + .../tutorial-backlog-refine-ai-ide.md | 23 +- .../tutorial-daily-standup-sprint-review.md | 199 ++++ docs/guides/agile-scrum-workflows.md | 42 + docs/guides/devops-adapter-integration.md | 51 + docs/index.md | 6 +- .../CHANGE_VALIDATION.md | 79 ++ .../design.md | 38 + .../proposal.md | 33 + .../specs/backlog-add/spec.md | 126 +++ .../tasks.md | 80 ++ .../CHANGE_VALIDATION.md | 372 +------ .../proposal.md | 2 +- .../specs/devops-sync/spec.md | 17 + .../CHANGE_VALIDATION.md | 0 .../design.md | 93 ++ .../proposal.md | 42 + .../specs/daily-standup/spec.md | 335 ++++++ .../tasks.md | 177 ++++ .../CHANGE_VALIDATION.md | 28 + .../proposal.md | 31 + .../specs/daily-standup/spec.md | 59 ++ .../daily-standup-exceptions-first/tasks.md | 41 + .../daily-standup-progress-support/design.md | 32 - .../proposal.md | 31 - .../specs/daily-standup/spec.md | 71 -- .../daily-standup-progress-support/tasks.md | 74 -- .../CHANGE_VALIDATION.md | 28 + .../patch-mode-preview-apply/proposal.md | 32 + .../specs/patch-mode/spec.md | 57 ++ .../changes/patch-mode-preview-apply/tasks.md | 37 + .../CHANGE_VALIDATION.md | 77 +- .../proposal.md | 3 +- .../specs/sprint-planning/spec.md | 18 + .../CHANGE_VALIDATION.md | 77 +- .../proposal.md | 3 +- .../specs/story-complexity/spec.md | 20 + .../CHANGE_VALIDATION.md | 28 + .../changes/unify-policies-engine/proposal.md | 35 + .../specs/policy-engine/spec.md | 59 ++ .../changes/unify-policies-engine/tasks.md | 36 + openspec/specs/daily-standup/spec.md | 329 ++++++ pyproject.toml | 12 +- resources/prompts/specfact.backlog-daily.md | 109 ++ setup.py | 2 +- src/__init__.py | 2 +- src/specfact_cli/__init__.py | 2 +- src/specfact_cli/adapters/ado.py | 5 + src/specfact_cli/adapters/github.py | 30 + src/specfact_cli/backlog/adapters/base.py | 31 + src/specfact_cli/commands/backlog_commands.py | 966 +++++++++++++++++- src/specfact_cli/common/logger_setup.py | 14 +- src/specfact_cli/runtime.py | 14 +- tests/e2e/test_terminal_output_modes.py | 7 +- tests/unit/commands/test_backlog_config.py | 229 +++++ tests/unit/commands/test_backlog_daily.py | 522 ++++++++++ tests/unit/test_runtime.py | 21 +- 61 files changed, 4185 insertions(+), 758 deletions(-) create mode 100644 .cursorignore create mode 100644 docs/getting-started/tutorial-daily-standup-sprint-review.md create mode 100644 openspec/changes/add-backlog-add-interactive-issue-creation/CHANGE_VALIDATION.md create mode 100644 openspec/changes/add-backlog-add-interactive-issue-creation/design.md create mode 100644 openspec/changes/add-backlog-add-interactive-issue-creation/proposal.md create mode 100644 openspec/changes/add-backlog-add-interactive-issue-creation/specs/backlog-add/spec.md create mode 100644 openspec/changes/add-backlog-add-interactive-issue-creation/tasks.md rename openspec/changes/{daily-standup-progress-support => archive/2026-02-02-daily-standup-progress-support}/CHANGE_VALIDATION.md (100%) create mode 100644 openspec/changes/archive/2026-02-02-daily-standup-progress-support/design.md create mode 100644 openspec/changes/archive/2026-02-02-daily-standup-progress-support/proposal.md create mode 100644 openspec/changes/archive/2026-02-02-daily-standup-progress-support/specs/daily-standup/spec.md create mode 100644 openspec/changes/archive/2026-02-02-daily-standup-progress-support/tasks.md create mode 100644 openspec/changes/daily-standup-exceptions-first/CHANGE_VALIDATION.md create mode 100644 openspec/changes/daily-standup-exceptions-first/proposal.md create mode 100644 openspec/changes/daily-standup-exceptions-first/specs/daily-standup/spec.md create mode 100644 openspec/changes/daily-standup-exceptions-first/tasks.md delete mode 100644 openspec/changes/daily-standup-progress-support/design.md delete mode 100644 openspec/changes/daily-standup-progress-support/proposal.md delete mode 100644 openspec/changes/daily-standup-progress-support/specs/daily-standup/spec.md delete mode 100644 openspec/changes/daily-standup-progress-support/tasks.md create mode 100644 openspec/changes/patch-mode-preview-apply/CHANGE_VALIDATION.md create mode 100644 openspec/changes/patch-mode-preview-apply/proposal.md create mode 100644 openspec/changes/patch-mode-preview-apply/specs/patch-mode/spec.md create mode 100644 openspec/changes/patch-mode-preview-apply/tasks.md create mode 100644 openspec/changes/unify-policies-engine/CHANGE_VALIDATION.md create mode 100644 openspec/changes/unify-policies-engine/proposal.md create mode 100644 openspec/changes/unify-policies-engine/specs/policy-engine/spec.md create mode 100644 openspec/changes/unify-policies-engine/tasks.md create mode 100644 openspec/specs/daily-standup/spec.md create mode 100644 resources/prompts/specfact.backlog-daily.md create mode 100644 tests/unit/commands/test_backlog_config.py create mode 100644 tests/unit/commands/test_backlog_daily.py diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 00000000..35849332 --- /dev/null +++ b/.cursorignore @@ -0,0 +1,18 @@ +# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) +node_modules/ +.venv/ +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +.env.development +.env.test +.env.production +build/ +__pycache__/ +.ruff_cache/ +.pytest_cache/ +.semgrep +.coverage_cache/ +dist/ \ No newline at end of file diff --git a/.github/workflows/pr-orchestrator.yml b/.github/workflows/pr-orchestrator.yml index 2931f81a..1697a39a 100644 --- a/.github/workflows/pr-orchestrator.yml +++ b/.github/workflows/pr-orchestrator.yml @@ -21,6 +21,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: changes: name: Detect code changes diff --git a/CHANGELOG.md b/CHANGELOG.md index b58d950c..c0652953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,39 @@ All notable changes to this project will be documented in this file. --- +## [0.26.16] - 2026-02-02 + +### Added (0.26.16) + +- **Daily standup and progress support** (OpenSpec change `daily-standup-progress-support`, fixes [#168](https://github.com/nold-ai/specfact-cli/issues/168)) + - **`specfact backlog daily `**: Standup view listing my/filtered backlog items with id, title, status, last-updated; optional standup summary lines (yesterday/today/blockers) when present in item body (parsed from `**Yesterday:**`, `**Today:**`, `**Blockers:**`). + - **`--assignee`, `--state`, `--labels`, `--limit`**: Filter items; assignee filter for "my items" standup. + - **`--post` with `--yesterday`, `--today`, `--blockers`**: Post standup comment to the first item's linked issue when the adapter supports comments (GitHub/ADO); adapters that do not support comments report clearly without attempting to post. + - **Adapter capability**: `BacklogAdapter.supports_add_comment()` (default `False`); GitHub and ADO adapters override to return `True` when configured; `add_comment(item, body)` used for posting. + - **Docs**: `docs/guides/agile-scrum-workflows.md` (daily standup bullet), `docs/guides/devops-adapter-integration.md` (standup comments). +- **Daily standup defaults, iteration/sprint, unassigned items view** (OpenSpec change `daily-standup-progress-support`, extends [#168](https://github.com/nold-ai/specfact-cli/issues/168)) + - **Default standup scope**: When `--state`/`--limit`/`--assignee` are not passed, defaults apply (state=open, limit=20, optional assignee from config). Configure via `SPECFACT_STANDUP_STATE`, `SPECFACT_STANDUP_LIMIT`, `SPECFACT_STANDUP_ASSIGNEE` or optional `.specfact/standup.yaml`; CLI options override config/env. + - **`--iteration` and `--sprint`**: Filter standup view to current iteration/sprint when adapter supports it (e.g. ADO); pass-through to fetch and filters. Sprint/iteration end date displayed when provided by adapter or config (`standup.sprint_end_date`, `SPECFACT_STANDUP_SPRINT_END`). + - **Unassigned/pending items**: Second table **Pending / open for commitment** with unassigned items (same scope); `--show-unassigned`/`--no-show-unassigned` (default true), `--unassigned-only` to show only unassigned. + - **`--blockers-first`**: Sort so items with non-empty blockers appear first. Optional priority/value column when `show_priority` or `show_value` in standup config and BacklogItem has priority/business_value (value-driven/SAFe). + - **Docs**: `docs/guides/agile-scrum-workflows.md` (daily standup: default scope, iteration/sprint, unassigned, blockers-first, priority, Kanban vs Scrum/SAFe, out-of-scope); `docs/guides/devops-adapter-integration.md` (standup config, iteration/sprint and sprint end date per adapter, blockers-first/priority, sprint goal in board/sprint settings). +- **Interactive step-by-step review and Copilot export** (OpenSpec change `daily-standup-progress-support`, extends [#168](https://github.com/nold-ai/specfact-cli/issues/168)) + - **`--interactive`**: Step-by-step review with arrow-key selection (questionary): pick a story to view full detail (refine-like: description, acceptance criteria, standup fields, comments from adapter when available); navigation: Next story, Previous story, Back to list, Exit. Complementary to the backlog; not a replacement. + - **`--copilot-export `**: Write summarized progress per story (same scope as daily) to a Markdown file for Copilot slash-command use during standup; one section per item (ID, title, status, assignees, last updated, progress, blockers, optional value score). + - **`--suggest-next`**: In interactive mode, show suggested next item by value score (business_value / max(1, story_points × priority)) for pending items. + - **Adapter**: Optional `get_comments(item)` on BacklogAdapter (default `[]`); GitHub adapter implements to fetch issue comments for interactive detail view. + - **Docs**: `docs/guides/agile-scrum-workflows.md` (interactive review, Copilot export); `docs/guides/devops-adapter-integration.md` (comment fetch, value score/suggestions). +- **Project backlog context** (OpenSpec change `daily-standup-progress-support`, extends [#168](https://github.com/nold-ai/specfact-cli/issues/168)) + - **`.specfact/backlog.yaml`**: Store org/project per adapter (e.g. `github.repo_owner`, `github.repo_name`; `ado.org`, `ado.project`, `ado.team`); no tokens; resolution order: CLI args > env (`SPECFACT_GITHUB_REPO_OWNER`, `SPECFACT_ADO_ORG`, etc.) > file. Used by all backlog commands (daily, refine, sync bridge) so adapter context can be omitted after one-time config. + - **Git remote inference**: When org/repo or org/project are still missing after CLI/env/config, infer from `git remote get-url origin` when run from a clone (GitHub HTTPS/SSH; ADO HTTPS, SSH with keys `git@ssh.dev.azure.com:v3/...`, other SSH `user@dev.azure.com:v3/...`). Clear error with guidance if inference fails. + - **Docs**: `docs/guides/devops-adapter-integration.md` (project backlog context, git fallback); tutorial and agile-scrum updated for auto-detect. +- **Daily standup: slash prompt, summarize, and tutorial** (OpenSpec change `daily-standup-progress-support`, extends [#168](https://github.com/nold-ai/specfact-cli/issues/168)) + - **`resources/prompts/specfact.backlog-daily.md`**: Slash-command prompt for interactive walkthrough with DevOps team (story-by-story, current focus, issues/open questions, discussion notes as comments); use as `specfact.daily` or `specfact.backlog-daily`. + - **`--summarize`** (stdout) and **`--summarize-to `**: Output a prompt (instruction + filter context + standup data) for slash command or Copilot to generate a standup summary. Per-item data includes **body (description)** and **comments (annotations)** when adapter supports `get_comments`; wrapped in `--- BEGIN STANDUP PROMPT ---` / `--- END STANDUP PROMPT ---` for extraction. Command returns after output (no standup tables when summarizing). + - **Tutorial**: `docs/getting-started/tutorial-daily-standup-sprint-review.md`; linked in `docs/_layouts/default.html` and `docs/index.md`. + +--- + ## [0.26.15] - 2026-01-30 ### Added (0.26.15) diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 691780d5..53a6b3fa 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -134,6 +134,7 @@

  • Installation
  • First Steps
  • Tutorial: Backlog Refine with AI IDE
  • +
  • Tutorial: Daily Standup and Sprint Review
  • Guides

    diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md index 34597183..008ee3a4 100644 --- a/docs/getting-started/README.md +++ b/docs/getting-started/README.md @@ -47,6 +47,7 @@ uvx specfact-cli@latest plan init my-project --interactive - 📖 **[DevOps Backlog Integration](../guides/devops-adapter-integration.md)** 🆕 **NEW FEATURE** - Integrate SpecFact into agile DevOps workflows - 📖 **[Backlog Refinement](../guides/backlog-refinement.md)** 🆕 **NEW FEATURE** - AI-assisted template-driven refinement for standardizing work items - 📖 **[Tutorial: Backlog Refine with AI IDE](tutorial-backlog-refine-ai-ide.md)** 🆕 - End-to-end for agile DevOps teams: slash prompt, story quality, underspecification, splitting, DoR, custom templates +- 📖 **[Tutorial: Daily Standup and Sprint Review](tutorial-daily-standup-sprint-review.md)** 🆕 - End-to-end daily standup: auto-detect repo (GitHub/ADO), view standup table, post comment, interactive, Copilot export - 📖 **[Use Cases](../guides/use-cases.md)** - See real-world examples - 📖 **[Command Reference](../reference/commands.md)** - Learn all available commands diff --git a/docs/getting-started/tutorial-backlog-refine-ai-ide.md b/docs/getting-started/tutorial-backlog-refine-ai-ide.md index ee57f451..f8df2893 100644 --- a/docs/getting-started/tutorial-backlog-refine-ai-ide.md +++ b/docs/getting-started/tutorial-backlog-refine-ai-ide.md @@ -36,14 +36,24 @@ This tutorial walks agile DevOps teams through integrating SpecFact CLI backlog ## Step 1: Run Backlog Refine and Get Items -From your repo root (or where your backlog lives): +From your **repo root** (or where your backlog lives): ```bash -# GitHub: fetch open items that need refinement (default: ignore already-refined) -specfact backlog refine github --repo-owner OWNER --repo-name REPO --search "is:open label:feature" --limit 5 --preview +# GitHub: org/repo are auto-detected from git remote when run from a GitHub clone +specfact backlog refine github --search "is:open label:feature" --limit 5 --preview # Or export to a temp file for your AI IDE to process (recommended for interactive loop) -specfact backlog refine github --repo-owner OWNER --repo-name REPO --export-to-tmp --search "is:open label:feature" --limit 5 +specfact backlog refine github --export-to-tmp --search "is:open label:feature" --limit 5 +``` + +**Auto-detect from clone**: When you run from a **GitHub** clone (e.g. `https://github.com/owner/repo` or `git@github.com:owner/repo.git`), SpecFact infers `repo_owner` and `repo_name` from `git remote get-url origin`—no `--repo-owner`/`--repo-name` needed. When you run from an **Azure DevOps** clone (e.g. `https://dev.azure.com/org/project/_git/repo`; SSH keys: `git@ssh.dev.azure.com:v3/org/project/repo`; other SSH: `user@dev.azure.com:v3/org/project/repo`), org and project are inferred. Override with `.specfact/backlog.yaml`, env vars (`SPECFACT_GITHUB_REPO_OWNER`, `SPECFACT_ADO_ORG`, etc.), or CLI options when not in the repo or to override. + +If you're **not** in a clone, pass adapter context explicitly: + +```bash +specfact backlog refine github --repo-owner OWNER --repo-name REPO --search "is:open label:feature" --limit 5 --preview +# or ADO: +specfact backlog refine ado --ado-org ORG --ado-project PROJECT --state Active --limit 5 --preview ``` - Use `--ignore-refined` (default) so `--limit` applies to items that **need** refinement @@ -116,10 +126,11 @@ When you’re satisfied with the refined content: ```bash # If you used --export-to-tmp, save the refined file as ...-refined.md, then: -specfact backlog refine github --repo-owner OWNER --repo-name REPO --import-from-tmp --write +# (From repo root, org/repo or org/project are auto-detected from git remote) +specfact backlog refine github --import-from-tmp --write # Or run refine interactively with --write (use with care; confirm each item) -specfact backlog refine github --repo-owner OWNER --repo-name REPO --write --labels feature --limit 3 +specfact backlog refine github --write --labels feature --limit 3 ``` Use `--preview` (default) until you’re confident; use `--write` only when you want to update the remote backlog. diff --git a/docs/getting-started/tutorial-daily-standup-sprint-review.md b/docs/getting-started/tutorial-daily-standup-sprint-review.md new file mode 100644 index 00000000..edd6e502 --- /dev/null +++ b/docs/getting-started/tutorial-daily-standup-sprint-review.md @@ -0,0 +1,199 @@ +--- +layout: default +title: Tutorial - Daily Standup and Sprint Review with SpecFact CLI +description: End-to-end daily standup and sprint review using specfact backlog daily. Auto-detect repo from git (GitHub or Azure DevOps), view standup table, post standup comments, use interactive mode and Copilot export. +permalink: /getting-started/tutorial-daily-standup-sprint-review/ +--- + +# Tutorial: Daily Standup and Sprint Review with SpecFact CLI + +This tutorial walks you through a complete **daily standup and sprint review** workflow using SpecFact CLI: view your backlog items, optionally post standup comments to issues, use interactive step-through and Copilot export—with **no need to pass org/repo or org/project** when you run from your cloned repo. + +**Time**: ~10–15 minutes +**Outcome**: End-to-end flow from "clone + auth" to standup view, optional post, interactive review, and Copilot-ready export. + +--- + +## What You'll Learn + +- Run **`specfact backlog daily`** and see your standup table (assigned + unassigned items) with **auto-detected** GitHub org/repo or Azure DevOps org/project from the git remote +- Use **`.specfact/backlog.yaml`** or environment variables when you're not in the repo (e.g. CI) or to override +- **Post a standup comment** to the first (or selected) item with `--yesterday`, `--today`, `--blockers` and `--post` +- Use **`--interactive`** for step-by-step story review (arrow-key selection, full detail, **existing comments on each issue** when the adapter supports them) +- Use **`--copilot-export `** to write a Markdown summary for Copilot slash-command during standup +- Use **`--summarize`** or **`--summarize-to `** to output a **prompt** (instruction + filter context + standup data) for a slash command (e.g. `specfact.daily`) or copy-paste to Copilot to **generate a standup summary** +- Use the **`specfact.backlog-daily`** (or `specfact.daily`) slash prompt for interactive walkthrough with the DevOps team story-by-story (focus, issues, open questions, discussion notes as comments) +- Filter by **`--assignee`**, **`--sprint`** / **`--iteration`**, **`--blockers-first`**, and optional **`--suggest-next`** + +--- + +## Prerequisites + +- SpecFact CLI installed (`uvx specfact-cli@latest` or `pip install specfact-cli`) +- **Authenticated** to your backlog provider: `specfact auth github` or Azure DevOps (PAT in env) +- A **clone** of your repo (GitHub or Azure DevOps) so the CLI can auto-detect org/repo or org/project from `git remote origin` + +--- + +## Step 1: Run Daily Standup (Auto-Detect Repo) + +From your **repo root** (where `.git` lives): + +```bash +# GitHub: org/repo are inferred from git remote origin +specfact backlog daily github + +# Azure DevOps: org/project are inferred from git remote origin +# (e.g. https://dev.azure.com/... or git@ssh.dev.azure.com:v3/... for SSH keys; user@dev.azure.com:v3/... if not using SSH keys) +specfact backlog daily ado +``` + +**What you see**: + +- **Daily standup** table: your assigned (or filtered) items with ID, title, status, last updated, yesterday/today/blockers columns +- **Pending / open for commitment**: unassigned items in the same scope + +**No `--repo-owner`/`--repo-name` (GitHub) or `--ado-org`/`--ado-project` (ADO) needed** when the repo was cloned from that provider—SpecFact reads `git remote get-url origin` and infers the context. + +If you're **not** in a clone (e.g. different directory), use one of: + +- **`.specfact/backlog.yaml`** in the project (see [Project backlog context](../guides/devops-adapter-integration.md#project-backlog-context-specfactbacklogyaml)) +- **Environment variables**: `SPECFACT_GITHUB_REPO_OWNER`, `SPECFACT_GITHUB_REPO_NAME` or `SPECFACT_ADO_ORG`, `SPECFACT_ADO_PROJECT` +- **CLI options**: `--repo-owner` / `--repo-name` or `--ado-org` / `--ado-project` + +--- + +## Step 2: Filter and Scope + +Narrow the list to your sprint or assignee: + +```bash +# My items only (GitHub: login; ADO: current user) +specfact backlog daily github --assignee me + +# Current sprint (when adapter supports it, e.g. ADO) +specfact backlog daily ado --sprint current + +# Open items, limit 10, blockers first +specfact backlog daily github --state open --limit 10 --blockers-first +``` + +Default scope is **state=open**, **limit=20**; overridable via `SPECFACT_STANDUP_STATE`, `SPECFACT_STANDUP_LIMIT`, or `.specfact/standup.yaml`. + +--- + +## Step 3: Post a Standup Comment (Optional) + +To add a **standup comment** to the **first** item in the list (e.g. the one you're working on), pass **values** for yesterday/today/blockers and `--post`: + +```bash +specfact backlog daily github \ + --yesterday "Worked on daily standup and progress support" \ + --today "Will add tests and docs" \ + --blockers "None" \ + --post +``` + +**Expected**: The CLI posts a comment on that item's issue (GitHub issue or ADO work item) with a standup block (Yesterday / Today / Blockers). You'll see: `✓ Standup comment posted to `. + +**Important**: You must pass **values** for at least one of `--yesterday`, `--today`, or `--blockers`. Using `--post` alone (or with flags but no text) will prompt you to add values; see the in-command message and help. + +--- + +## Step 4: Interactive Step-Through (Optional) + +For a **refine-like** walkthrough (select item → view full detail → next/previous/back/exit): + +```bash +specfact backlog daily github --interactive +``` + +- Use the menu to **select** an item (arrow keys). +- View **full detail** (description, acceptance criteria, standup fields, and **existing comments annotated to that issue** when the adapter supports fetching comments—e.g. GitHub issue comments, ADO work item discussion). +- Choose **Next story**, **Previous story**, **Back to list**, or **Exit**. + +Use **`--suggest-next`** to show a suggested next item by value score (business value / (story points × priority)) when the data is available. + +--- + +## Step 5: Export for Copilot (Optional) + +To feed a **summary file** into your AI IDE (e.g. for a Copilot slash-command during standup): + +```bash +specfact backlog daily github --copilot-export ./standup-summary.md +``` + +The file contains one section per item (ID, title, status, assignees, last updated, progress, blockers). You can open it in your IDE and use it with Copilot. Same scope as the standup table (state, assignee, limit, etc.). + +--- + +## Step 6: Standup Summary Prompt (Optional) + +To get a **prompt** you can paste into Copilot or feed to a slash command (e.g. `specfact.daily`) so an AI can **generate a short standup summary** (e.g. "Today: 3 in progress, 1 blocked, 2 pending commitment"): + +```bash +# Print prompt to stdout (copy-paste to Copilot) +specfact backlog daily github --summarize + +# Write prompt to a file (e.g. for slash command) +specfact backlog daily github --summarize-to ./standup-prompt.md +``` + +The output includes an instruction to generate a standup summary, the applied filter context (adapter, state, sprint, assignee, limit), and the same per-item data as `--copilot-export`. Use it with the **`specfact.backlog-daily`** slash prompt for interactive team walkthrough (story-by-story, current focus, issues/open questions, discussion notes as comments). + +--- + +## End-to-End Example: One Standup Session + +1. **Authenticate once** (if not already): + + ```bash + specfact auth github + ``` + +2. **Open your repo** and run daily (repo auto-detected): + + ```bash + cd /path/to/your-repo + specfact backlog daily github + ``` + +3. **Optional: post today's standup** to the first item: + + ```bash + specfact backlog daily github \ + --yesterday "Implemented backlog context and git inference" \ + --today "Docs and tests for daily standup tutorial" \ + --blockers "None" \ + --post + ``` + +4. **Optional: interactive review** or **Copilot export**: + + ```bash + specfact backlog daily github --interactive + # or + specfact backlog daily github --copilot-export ./standup.md + ``` + +--- + +## Summary + +| Goal | How | +|------|-----| +| View standup without typing org/repo | Run `specfact backlog daily github` or `ado` from **repo root**; org/repo or org/project are **auto-detected** from git remote. | +| Override or use outside repo | Use `.specfact/backlog.yaml`, env vars (`SPECFACT_GITHUB_REPO_OWNER`, etc.), or CLI `--repo-owner`/`--repo-name` or `--ado-org`/`--ado-project`. | +| Post standup to first item | Use `--yesterday "..."` `--today "..."` `--blockers "..."` and `--post` (values required). | +| Step through stories with full detail (including issue comments) | Use `--interactive`; optionally `--suggest-next`. | +| Feed standup into Copilot | Use `--copilot-export `. | +| Generate standup summary via AI (slash command or Copilot) | Use `--summarize` (stdout) or `--summarize-to `; use with `specfact.backlog-daily` slash prompt. | + +--- + +## Related Documentation + +- **[Agile/Scrum Workflows](../guides/agile-scrum-workflows.md)** — Daily standup, iteration/sprint, unassigned items, blockers-first +- **[DevOps Adapter Integration](../guides/devops-adapter-integration.md)** — Project backlog context (`.specfact/backlog.yaml`), env vars, **Git fallback (auto-detect from clone)** for GitHub and Azure DevOps +- **[Backlog Refinement Guide](../guides/backlog-refinement.md)** — Template-driven refinement (complementary to daily standup) diff --git a/docs/guides/agile-scrum-workflows.md b/docs/guides/agile-scrum-workflows.md index c436c552..0a6f2139 100644 --- a/docs/guides/agile-scrum-workflows.md +++ b/docs/guides/agile-scrum-workflows.md @@ -14,6 +14,7 @@ SpecFact CLI supports real-world agile/scrum practices through: - **Definition of Ready (DoR)**: Automatic validation of story readiness for sprint planning - **Backlog Refinement** 🆕: AI-assisted template-driven refinement for standardizing work items from DevOps backlogs +- **Daily Standup**: Use `specfact backlog daily ` to list my/filtered items with status and last activity. Default scope (state=open, limit=20, optional assignee=me) is applied when not overridden; configure via `SPECFACT_STANDUP_STATE`, `SPECFACT_STANDUP_LIMIT`, `SPECFACT_STANDUP_ASSIGNEE` or `.specfact/standup.yaml`. Use `--iteration` / `--sprint` (e.g. `--sprint current`) to focus on current iteration when the adapter supports it; sprint/iteration end date is shown when provided by adapter or config (`standup.sprint_end_date`). A second table **Pending / open for commitment** lists unassigned items (same scope); use `--show-unassigned`/`--no-show-unassigned` or `--unassigned-only`. Use `--blockers-first` to sort items with blockers first; enable `show_priority` or `show_value` in standup config for optional priority/value column (value-driven/SAFe). Optional standup summary (yesterday/today/blockers) from item body; optionally post standup comment to linked issue via `--post` when the adapter supports comments (e.g. GitHub). **Interactive step-by-step review**: Use `--interactive` to select stories with arrow keys (questionary) and view full detail (refine-like: description, acceptance criteria, standup fields, comments when adapter supports); navigate with Next/Previous/Back to list/Exit. Use `--suggest-next` to show suggested next item by value score (business_value / (story_points × priority)). **Copilot export**: Use `--copilot-export ` to write a summarized Markdown file of each story for use with Copilot slash-command during standup (complementary aid, not replacement for backlog). **Kanban**: omit iteration/sprint and use state + limit; unassigned = pullable work. **Scrum/SAFe**: use `--sprint current` and optional priority/value. **Out of scope**: Sprint goal is in your board/sprint settings (not displayed by CLI). Stale/at-risk flags (e.g. "no update in N days") are not in scope—use last updated + blockers. Structured "blocked by" (link to another issue) is not in scope; only free-text blockers are supported. - **Dependency Management**: Track story-to-story and feature-to-feature dependencies - **Prioritization**: Priority levels, ranking, and business value scoring - **Sprint Planning**: Target sprint/release assignment and story point tracking @@ -22,6 +23,47 @@ SpecFact CLI supports real-world agile/scrum practices through: **🆕 NEW: Backlog Refinement Integration** - Use `specfact backlog refine` to standardize backlog items from GitHub Issues, Azure DevOps, and other tools into template-compliant format before importing into project bundles. See [Backlog Refinement Guide](backlog-refinement.md) for complete documentation. +**Tutorial**: For an end-to-end daily standup and sprint review walkthrough (auto-detect repo, view standup, post comment, interactive, Copilot export), see **[Tutorial: Daily Standup and Sprint Review](../getting-started/tutorial-daily-standup-sprint-review.md)**. + +## Daily Standup and Sprint Review + +Use **`specfact backlog daily `** to list your standup items (assigned + unassigned) with status and last activity. **By default, GitHub org/repo or Azure DevOps org/project are auto-detected from the git remote** when you run from your cloned repo—no `--repo-owner`/`--repo-name` or `--ado-org`/`--ado-project` needed after authenticating once. + +### Auto-Detect from Clone + +- **GitHub**: When run from a **GitHub** clone (e.g. `https://github.com/owner/repo` or `git@github.com:owner/repo.git`), SpecFact infers `repo_owner` and `repo_name` from `git remote get-url origin`. +- **Azure DevOps**: When run from an **ADO** clone (e.g. `https://dev.azure.com/org/project/_git/repo`; SSH keys: `git@ssh.dev.azure.com:v3/org/project/repo`; other SSH: `user@dev.azure.com:v3/org/project/repo`), SpecFact infers `org` and `project` from the remote URL. + +Override with `.specfact/backlog.yaml`, environment variables (`SPECFACT_GITHUB_REPO_OWNER`, `SPECFACT_ADO_ORG`, etc.), or CLI options when not in the repo or to override. See [Project backlog context](../guides/devops-adapter-integration.md#project-backlog-context-specfactbacklogyaml). + +### End-to-End Example: One Standup Session + +```bash +# 1. Authenticate once (if not already) +specfact auth github + +# 2. From repo root: view standup (repo auto-detected) +cd /path/to/your-repo +specfact backlog daily github + +# 3. Optional: post standup comment to first item (pass values for yesterday/today/blockers) +specfact backlog daily github \ + --yesterday "Worked on X" \ + --today "Will do Y" \ + --blockers "None" \ + --post + +# 4. Optional: interactive step-through, Copilot export, or standup summary prompt +specfact backlog daily github --interactive # step-through; detail view shows existing comments on each issue +# or +specfact backlog daily github --copilot-export ./standup.md +# or +specfact backlog daily github --summarize # prompt to stdout for AI to generate standup summary +specfact backlog daily github --summarize-to ./standup-prompt.md +``` + +Use the **`specfact.backlog-daily`** (or `specfact.daily`) slash prompt for interactive walkthrough with the DevOps team story-by-story (current focus, issues/open questions, discussion notes as comments). Default scope: **state=open**, **limit=20**; configure via `SPECFACT_STANDUP_*` or `.specfact/standup.yaml`. Use `--assignee me`, `--sprint current`, `--blockers-first`, `--interactive`, `--suggest-next`, `--copilot-export `, `--summarize`, and `--summarize-to ` as needed. See [Tutorial: Daily Standup and Sprint Review](../getting-started/tutorial-daily-standup-sprint-review.md) for the full walkthrough. + ## Persona-Based Workflows SpecFact uses persona-based workflows where different roles work on different aspects: diff --git a/docs/guides/devops-adapter-integration.md b/docs/guides/devops-adapter-integration.md index 412f42aa..e29dbfaf 100644 --- a/docs/guides/devops-adapter-integration.md +++ b/docs/guides/devops-adapter-integration.md @@ -19,6 +19,7 @@ SpecFact CLI supports **bidirectional synchronization** between OpenSpec change - **Issue Creation**: Export OpenSpec change proposals as GitHub Issues (or other DevOps backlog items) - **Progress Tracking**: Automatically detect code changes and add progress comments to issues +- **Standup Comments**: Use `specfact backlog daily --post` with `--yesterday`, `--today`, `--blockers` to post a standup summary as a comment on the linked issue (GitHub/ADO adapters that support comments). Standup config: set defaults via env (`SPECFACT_STANDUP_STATE`, `SPECFACT_STANDUP_LIMIT`, `SPECFACT_STANDUP_ASSIGNEE`, `SPECFACT_STANDUP_SPRINT_END`) or optional `.specfact/standup.yaml` (e.g. `default_state`, `limit`, `sprint`, `show_priority`, `suggest_next`). Iteration/sprint and sprint end date support depend on the adapter (ADO supports current iteration and iteration path; see adapter docs). Use `--blockers-first` and config `show_priority`/`show_value` for time-critical and value-driven standups. **Interactive review** (`--interactive`): step-through stories with arrow-key selection; detail view shows **existing comments annotated to each issue** when the adapter implements `get_comments(item)` (GitHub adapter supports it). **Value score / suggested next**: when BacklogItem has `story_points`, `business_value`, and `priority`, use `--suggest-next` or config `suggest_next` to show suggested next item (business_value / (story_points × priority)). **Standup summary prompt** (`--summarize` or `--summarize-to PATH`): output a prompt (instruction + filter context + standup data) for slash command or Copilot to generate a standup summary. **Slash prompt** `specfact.backlog-daily` (or `specfact.daily`): use with IDE/Copilot for interactive team walkthrough story-by-story (current focus, issues/open questions, discussion notes as comments); prompt file at `resources/prompts/specfact.backlog-daily.md`. **Sprint goal** is stored in your board/sprint settings and is not displayed or edited by the CLI. - **Content Sanitization**: Protect internal information when syncing to public repositories - **Separate Repository Support**: Handle cases where OpenSpec proposals and source code are in different repositories @@ -192,6 +193,56 @@ specfact sync bridge --adapter github --mode export-only \ --repo /path/to/openspec-repo ``` +### Project backlog context (.specfact/backlog.yaml) + +Store project-level adapter context (org, repo, project per adapter) so you do not have to pass `--repo-owner`, `--repo-name`, `--ado-org`, `--ado-project`, or `--ado-team` on every backlog command after authenticating once. + +**Resolution order**: Explicit CLI options override environment variables; environment variables override the config file. **Tokens are never read from the file**—only from CLI or env. + +**Config search path**: `SPECFACT_CONFIG_DIR` (if set) or `.specfact/` in the current working directory. File name: `backlog.yaml`. + +**File format** (YAML; optional top-level `backlog` key for nesting): + +```yaml +# Optional: wrap under top-level key +backlog: + github: + repo_owner: your-org + repo_name: your-repo + ado: + org: your-org + project: YourProject + team: Your Team +``` + +Or without the top-level key: + +```yaml +github: + repo_owner: your-org + repo_name: your-repo +ado: + org: your-org + project: YourProject + team: Your Team +``` + +**Environment variables** (override file; CLI overrides env): + +| Adapter | Env vars | +|--------|----------| +| GitHub | `SPECFACT_GITHUB_REPO_OWNER`, `SPECFACT_GITHUB_REPO_NAME` | +| Azure DevOps | `SPECFACT_ADO_ORG`, `SPECFACT_ADO_PROJECT`, `SPECFACT_ADO_TEAM` | + +**Git fallback (auto-detect from clone)**: + +- **GitHub**: When repo is not set via CLI, env, or file, SpecFact infers `repo_owner` and `repo_name` from `git remote get-url origin` when run inside a **GitHub** clone (e.g. `https://github.com/owner/repo` or `git@github.com:owner/repo.git`). No `--repo-owner`/`--repo-name` needed when you run from the repo root. +- **Azure DevOps**: When org/project are not set via CLI, env, or file, SpecFact infers `org` and `project` from the remote URL when run inside an **ADO** clone. Supported formats: `https://dev.azure.com/org/project/_git/repo`; SSH with keys: `git@ssh.dev.azure.com:v3/org/project/repo`; SSH without keys (other auth): `user@dev.azure.com:v3/org/project/repo` (no `ssh.` subdomain). No `--ado-org`/`--ado-project` needed when you run from the repo root. + +So after authenticating once, **running from the repo root is enough** for both GitHub and ADO—org/repo or org/project are detected automatically from the git remote. + +Applies to all backlog commands: `specfact backlog daily`, `specfact backlog refine`, `specfact sync bridge`, etc. + --- ## When to Use `--bundle` vs Direct Export diff --git a/docs/index.md b/docs/index.md index e9c6befc..1dae48e7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,8 +22,9 @@ SpecFact CLI helps you modernize legacy codebases by automatically extracting sp 1. **[Installation](getting-started/installation.md)** - Get started in 60 seconds 2. **[First Steps](getting-started/first-steps.md)** - Run your first command 3. **[Tutorial: Backlog Refine with AI IDE](getting-started/tutorial-backlog-refine-ai-ide.md)** - Integrate backlog refinement with your AI IDE (agile DevOps) -4. **[Modernizing Legacy Code](guides/brownfield-engineer.md)** ⭐ **PRIMARY** - Brownfield-first guide -5. **[The Brownfield Journey](guides/brownfield-journey.md)** ⭐ - Complete modernization workflow +4. **[Tutorial: Daily Standup and Sprint Review](getting-started/tutorial-daily-standup-sprint-review.md)** - Daily standup view, post comments, and Copilot export (GitHub/ADO) +5. **[Modernizing Legacy Code](guides/brownfield-engineer.md)** ⭐ **PRIMARY** - Brownfield-first guide +6. **[The Brownfield Journey](guides/brownfield-journey.md)** ⭐ - Complete modernization workflow ### Using GitHub Spec-Kit? @@ -61,6 +62,7 @@ SpecFact CLI helps you modernize legacy codebases by automatically extracting sp - **[Backlog Refinement Guide](guides/backlog-refinement.md)** 🆕 **NEW** - AI-assisted template-driven refinement for standardizing work items - **[Tutorial: Backlog Refine with AI IDE](getting-started/tutorial-backlog-refine-ai-ide.md)** - End-to-end tutorial for agile DevOps teams (slash prompt, DoR, split stories, underspec/overspec) + - **[Tutorial: Daily Standup and Sprint Review](getting-started/tutorial-daily-standup-sprint-review.md)** - Daily standup view, post yesterday/today/blockers, interactive mode, Copilot export (GitHub/ADO) - **Template Detection**: Automatically detect which template matches a backlog item with priority-based resolution - **AI-Assisted Refinement**: Generate prompts for IDE AI copilots to refine unstructured input - **Confidence Scoring**: Validate refined content and provide confidence scores diff --git a/openspec/changes/add-backlog-add-interactive-issue-creation/CHANGE_VALIDATION.md b/openspec/changes/add-backlog-add-interactive-issue-creation/CHANGE_VALIDATION.md new file mode 100644 index 00000000..c2012c82 --- /dev/null +++ b/openspec/changes/add-backlog-add-interactive-issue-creation/CHANGE_VALIDATION.md @@ -0,0 +1,79 @@ +# Change Validation Report: add-backlog-add-interactive-issue-creation + +**Validation Date**: 2026-01-31T00:32:54+01:00 +**Change Proposal**: [proposal.md](./proposal.md) +**Validation Method**: Dry-run simulation and format/OpenSpec compliance check + +## Executive Summary + +- **Breaking Changes**: 1 interface extension (new abstract method on BacklogAdapterMixin); all concrete backlog adapters must implement it. +- **Dependent Files**: 2 affected (GitHubAdapter, AdoAdapter); no existing callers of create_issue. +- **Impact Level**: Low +- **Validation Result**: Pass +- **User Decision**: N/A (no breaking-change options required) + +## Breaking Changes Detected + +### Interface: BacklogAdapterMixin.create_issue + +- **Type**: New abstract method +- **Old Signature**: (none; method does not exist) +- **New Signature**: `create_issue(project_id: str, payload: dict) -> dict` +- **Breaking**: Yes for implementors (any class inheriting BacklogAdapterMixin must implement the new method) +- **Dependent Files**: + - `src/specfact_cli/adapters/github.py`: Must implement create_issue + - `src/specfact_cli/adapters/ado.py`: Must implement create_issue + +**Mitigation**: Change scope already includes implementing create_issue in both GitHub and ADO adapters; no external dependents of BacklogAdapterMixin exist outside this repo. No scope extension needed. + +## Dependencies Affected + +### Critical Updates Required + +- `src/specfact_cli/adapters/github.py`: Implement create_issue (in scope) +- `src/specfact_cli/adapters/ado.py`: Implement create_issue (in scope) + +### Recommended Updates + +- None + +## Impact Assessment + +- **Code Impact**: Additive; new command and adapter method. Existing refine/sync/analyze-deps unchanged. +- **Test Impact**: New tests for create_issue and add command (TDD in tasks). +- **Documentation Impact**: docs/guides/agile-scrum-workflows.md, backlog guide for backlog add workflow. +- **Release Impact**: Minor (new feature). + +## Dependency on add-backlog-dependency-analysis-and-commands + +- **Note**: The plan states this change "Depends on" add-backlog-dependency-analysis-and-commands (BacklogGraphBuilder, fetch_all_issues, fetch_relationships). If that change is not yet merged, implementation can use minimal graph usage (e.g. fetch_backlog_item to validate parent exists) as stated in proposal Impact. No ambiguity; design and tasks already allow fallback. + +## Format Validation + +- **proposal.md Format**: Pass + - Title format: Correct (# Change: ...) + - Required sections: All present (Why, What Changes, Capabilities, Impact) + - "What Changes" format: Correct (NEW/EXTEND bullets) + - "Capabilities" section: Present (backlog-add) + - "Impact" format: Correct + - Source Tracking section: Present (GitHub #173) +- **tasks.md Format**: Pass + - Section headers: Hierarchical numbered (## 1. ... ## 10.) + - Task format: - [ ] N.N Description + - Sub-task format: Indented - [ ] N.N.N + - Config.yaml compliance: Pass (TDD section, branch first, PR last, version/changelog task, GitHub issue task) +- **specs/backlog-add/spec.md Format**: Pass (ADDED requirements, Given/When/Then) +- **design.md Format**: Pass (bridge adapter, sequence, contract, fallback) +- **Config.yaml Compliance**: Pass + +## OpenSpec Validation + +- **Status**: Pass +- **Validation Command**: `openspec validate add-backlog-add-interactive-issue-creation --strict` +- **Issues Found**: 0 +- **Issues Fixed**: 0 + +## Validation Artifacts + +- No temporary workspace used (validation was format and dependency analysis only). +- Breaking change is in-scope (adapter implementations are part of the change). diff --git a/openspec/changes/add-backlog-add-interactive-issue-creation/design.md b/openspec/changes/add-backlog-add-interactive-issue-creation/design.md new file mode 100644 index 00000000..e513d038 --- /dev/null +++ b/openspec/changes/add-backlog-add-interactive-issue-creation/design.md @@ -0,0 +1,38 @@ +# Design: Add backlog add (interactive issue creation) + +## Bridge adapter integration + +- **Create method**: Extend `BacklogAdapterMixin` with abstract `create_issue(project_id: str, payload: dict) -> dict`. Payload is unified: type (epic|feature|story|task|bug|spike|custom), title, description, optional parent_id, optional sprint, optional custom fields. Adapter maps to provider: GitHub Issues API (POST /repos/{owner}/{repo}/issues) with title, body, labels for type; ADO Create Work Item API with work item type from template type_mapping and parent relation when parent_id present. +- **Adapter capability**: GitHub and ADO adapters implement create_issue; return dict with id, key (or number), url. Read-only or unsupported adapters may raise or return clear "not supported" for future extensibility. + +## Sequence (add flow) + +```text +User → specfact backlog add --type story --parent FEAT-123 --title "T" [--body "B"] [--check-dor] + → CLI loads config & template (creation_hierarchy, type_mapping) + → CLI fetches graph (fetch_all_issues, fetch_relationships) or uses cached graph + → CLI validates: parent FEAT-123 exists, type Story allowed under parent type + → Optional: DoR check on draft (reuse backlog refine DoR loader) + → CLI builds unified payload (type, title, description, parent_id) + → adapter.create_issue(project_id, payload) → provider API + → CLI outputs created id, key, url +``` + +## Contract enforcement + +- New public methods: `create_issue` on adapters, add-command entry point and validation helpers shall have @icontract and @beartype. +- Payload schema (unified) can be a Pydantic model for validation before passing to adapter. + +## Creation hierarchy + +- **Source**: Optional `creation_hierarchy` in template YAML or backlog_config (e.g. under .specfact/spec.yaml). Format: map child type to list of allowed parent types (e.g. story: [feature, epic], task: [story]). +- **Default**: When absent, derive from dependency_rules (PARENT_CHILD) and type_mapping if possible; otherwise allow no parent or any existing type and document behavior. +- **Validation**: Before create, resolve parent_id in graph; check parent's effective type is in allowed list for the new item's type. + +## Fallback / offline + +- Add command requires network for create. Graph load may use cached baseline if available (from add-backlog-dependency-analysis-and-commands); otherwise fetch. Failure (auth, rate limit) is reported; no silent swallow. + +## Alignment with existing backlog commands + +- Reuse DoR loading and rules from `specfact backlog refine --check-dor` when --check-dor is set. Reuse BacklogGraphBuilder and DependencyAnalyzer (from add-backlog-dependency-analysis-and-commands) when available for parent validation and cycle avoidance; if that change is not merged, minimal validation (e.g. parent exists via fetch_backlog_item) suffices for v1. diff --git a/openspec/changes/add-backlog-add-interactive-issue-creation/proposal.md b/openspec/changes/add-backlog-add-interactive-issue-creation/proposal.md new file mode 100644 index 00000000..e3575397 --- /dev/null +++ b/openspec/changes/add-backlog-add-interactive-issue-creation/proposal.md @@ -0,0 +1,33 @@ +# Change: Add backlog add (interactive issue creation) + +## Why + +After implementing backlog adapters and dependency analysis (add-backlog-dependency-analysis-and-commands), teams can analyze and sync backlog items but cannot create new issues from the CLI with proper scoping, hierarchy alignment, and Definition of Ready (DoR) checks. Without a dedicated add flow, users create issues manually in GitHub/ADO and risk orphaned or misaligned items. Adding `specfact backlog add` enables interactive creation with AI copilot assistance: draft → review → enhance → validate (graph, DoR) → create, so new issues fit the existing backlog structure and value chain. + +## What Changes + +- **NEW**: Add CLI command `specfact backlog add` (or alias `specfact backlog add-issue`) for interactive creation of backlog issues (epic, feature, story, task, bug, spike) with optional parent, title, body, and DoR validation. +- **NEW**: Support multiple backlog levels (epic, feature, story, task, bug, spike, custom) with configurable creation hierarchy (allowed parent types per child type) via template or backlog_config; default derived from existing type_mapping and dependency_rules. +- **NEW**: Extend `BacklogAdapterMixin` with abstract method `create_issue(project_id: str, payload: dict) -> dict` returning created item (id, key, url); implement in GitHub and ADO adapters with unified payload shape and provider-specific mapping. +- **NEW**: Add spec delta and implementation for add flow: load graph (BacklogGraphBuilder, fetch_all_issues, fetch_relationships), resolve type and parent from template/hierarchy, validate parent exists and allowed type, optional DoR check (reuse backlog refine DoR), map draft to provider payload, call adapter create_issue, output created id/key/url. +- **EXTEND**: Template or backlog_config with optional creation_hierarchy (allowed parent types per child type) so Scrum/SAFe/Kanban and custom hierarchies work without code changes. +- **EXTEND**: Documentation (agile-scrum-workflows, backlog-refinement) for backlog add workflow, interactive creation, DoR, and slash-prompt usage. + +## Capabilities + +- **backlog-add**: Interactive creation of backlog issues with type/parent selection, draft validation (graph and DoR), and create via adapter; multi-level support (epic, feature, story, task, bug, spike, custom) with configurable hierarchy. + +## Impact + +- **Affected specs**: New `openspec/changes/add-backlog-add-interactive-issue-creation/specs/backlog-add/spec.md` (Given/When/Then for add flow, hierarchy, create via adapter). +- **Affected code**: `src/specfact_cli/adapters/backlog_base.py` (abstract create_issue), `github.py` and `ado.py` (implement create_issue); `src/specfact_cli/commands/backlog_commands.py` or backlog command module (add `specfact backlog add` subcommand); optional creation_hierarchy loader and validation using BacklogGraphBuilder and DependencyAnalyzer (from add-backlog-dependency-analysis-and-commands when available). +- **Affected documentation** (): docs/guides/agile-scrum-workflows.md, backlog-refinement or backlog guide for backlog add, interactive creation, DoR, slash prompt. +- **Integration points**: BacklogGraphBuilder, DependencyAnalyzer, fetch_all_issues, fetch_relationships (add-backlog-dependency-analysis-and-commands); DoR from backlog-refinement; templates and backlog_config. +- **Backward compatibility**: Additive only; new command and adapter method; existing refine/sync/analyze-deps unchanged. Depends on add-backlog-dependency-analysis-and-commands for graph/templates; can be implemented with minimal graph usage (e.g. fetch + validate parent) if that change is not yet merged. + +## Source Tracking + +- **GitHub Issue**: #173 +- **Issue URL**: +- **Repository**: nold-ai/specfact-cli +- **Last Synced Status**: proposed diff --git a/openspec/changes/add-backlog-add-interactive-issue-creation/specs/backlog-add/spec.md b/openspec/changes/add-backlog-add-interactive-issue-creation/specs/backlog-add/spec.md new file mode 100644 index 00000000..ff68a80d --- /dev/null +++ b/openspec/changes/add-backlog-add-interactive-issue-creation/specs/backlog-add/spec.md @@ -0,0 +1,126 @@ +# Backlog Add (Interactive Issue Creation) + +## ADDED Requirements + +### Requirement: Backlog adapter create method + +The system SHALL extend backlog adapters with a create method that accepts a unified payload and returns the created item (id, key, url). + +**Rationale**: Creation is currently out-of-band (user creates in GitHub/ADO UI). CLI-driven creation with consistent payload shape allows draft → validate → create flow. + +#### Scenario: Create issue via GitHub adapter + +**Given**: A GitHub adapter is configured and project_id (owner/repo) is set + +**When**: The user or add command calls `create_issue(project_id, payload)` with payload containing type, title, description, and optional parent_id + +**Then**: The adapter maps the unified payload to GitHub Issues API (e.g. POST /repos/{owner}/{repo}/issues) and creates the issue + +**And**: The method returns a dict with id, key (or number), and url of the created issue + +**Acceptance Criteria**: + +- Payload is provider-agnostic (type, title, description, parent_id, optional fields) +- Adapter performs provider-specific mapping (e.g. GitHub labels for type, body for description) +- Failure (auth, validation) is reported; no silent swallow + +#### Scenario: Create work item via ADO adapter + +**Given**: An ADO adapter is configured and project_id is set + +**When**: The user or add command calls `create_issue(project_id, payload)` with payload containing type, title, description, and optional parent_id + +**Then**: The adapter maps the unified payload to ADO Create Work Item API and creates the work item + +**And**: The method returns a dict with id, key, and url of the created work item + +**Acceptance Criteria**: + +- ADO work item type is derived from unified type via template type_mapping +- Parent link is created when parent_id is present and adapter supports it + +### Requirement: Backlog add command + +The system SHALL provide a `specfact backlog add` command that supports interactive creation of backlog issues with type selection, optional parent, title/body, validation (parent exists, allowed type, optional DoR), and create via adapter. + +**Rationale**: Teams need a single flow to add well-scoped, hierarchy-aligned issues from CLI or slash prompt. + +#### Scenario: Add story with parent + +**Given**: A backlog graph or project is loaded (e.g. from fetch_all_issues and fetch_relationships or existing graph) + +**And**: Template or backlog_config defines allowed types and creation hierarchy (e.g. Story may have parent Feature or Epic) + +**When**: The user runs `specfact backlog add --type story --parent FEAT-123 --title "Implement X" --body "As a user..."` (or equivalent interactive prompts) + +**Then**: The system validates that parent FEAT-123 exists in the graph and that Story is allowed under that parent type + +**And**: If validation passes, the system builds a unified payload and calls the adapter's create_issue(project_id, payload) + +**And**: The CLI outputs the created issue id, key, and url + +**Acceptance Criteria**: + +- Validation fails clearly when parent does not exist or type is not allowed +- Optional --check-dor runs DoR rules (from backlog-refinement / .specfact/dor.yaml) on the draft and warns or fails when not met + +#### Scenario: Add issue with custom hierarchy + +**Given**: backlog_config (or template) defines creation_hierarchy with custom allowed parent types per child type (e.g. Spike may have parent Epic or Feature) + +**When**: The user runs `specfact backlog add --type spike --parent EPIC-1 --title "Spike: evaluate Y"` + +**Then**: The system loads creation hierarchy from config and validates that Spike is allowed under Epic + +**And**: If allowed, the system creates the issue and optionally links parent + +**Acceptance Criteria**: + +- Hierarchy rules are read from template or backlog_config; no hardcoded hierarchy +- Multiple levels (epic, feature, story, task, bug, spike, custom) are supported + +#### Scenario: Non-interactive (scripted) add + +**Given**: All required options are provided on the command line (e.g. --type, --title, --non-interactive) + +**When**: The user runs `specfact backlog add --type story --title "T" --body "B" --non-interactive` + +**Then**: The system does not prompt for missing fields; it uses provided values or fails with clear error for missing required fields + +**And**: Validation (parent if provided, DoR if --check-dor) runs before create + +**Acceptance Criteria**: + +- Required fields are documented (e.g. type, title; body may be optional per provider) +- Missing required fields in non-interactive mode result in clear error exit + +### Requirement: Creation hierarchy configuration + +The system SHALL support configurable creation hierarchy (allowed parent types per child type) via template or backlog_config so that Scrum, SAFe, Kanban, and custom hierarchies work without code changes. + +**Rationale**: Different frameworks and orgs use different trees (e.g. Story under Feature vs Story under Epic); configuration avoids hardcoding. + +#### Scenario: Default hierarchy from template + +**Given**: A template (e.g. ado_scrum) is selected and does not define creation_hierarchy + +**When**: The add command needs to validate parent type for a new item + +**Then**: The system derives allowed parent types from existing type_mapping and dependency_rules (e.g. PARENT_CHILD) where possible + +**And**: If derivation is not possible, a conservative default (e.g. any type or no parent) is used and documented + +#### Scenario: Custom hierarchy in backlog_config + +**Given**: ProjectBundle.metadata.backlog_config (or .specfact/spec.yaml backlog section) contains creation_hierarchy with entries such as story: [feature, epic], task: [story] + +**When**: The user adds an item with --type story --parent FEAT-1 + +**Then**: The system validates that "feature" is in the allowed parent types for "story" and that FEAT-1 exists and has type Feature + +**And**: Validation fails clearly if parent type is not allowed + +**Acceptance Criteria**: + +- creation_hierarchy is optional; when absent, default or derived rules apply +- Validation uses both existence of parent in graph and allowed type from hierarchy diff --git a/openspec/changes/add-backlog-add-interactive-issue-creation/tasks.md b/openspec/changes/add-backlog-add-interactive-issue-creation/tasks.md new file mode 100644 index 00000000..0f20c6f1 --- /dev/null +++ b/openspec/changes/add-backlog-add-interactive-issue-creation/tasks.md @@ -0,0 +1,80 @@ +# Tasks: Add backlog add (interactive issue creation) + +## TDD / SDD order (enforced) + +Per `openspec/config.yaml`, **tests before code** apply to any task that adds or changes behavior. + +1. **Spec deltas** define behavior (Given/When/Then) in `openspec/changes/add-backlog-add-interactive-issue-creation/specs/backlog-add/spec.md`. +2. **Tests second**: Write unit/integration tests from those scenarios; run tests and **expect failure** (no implementation yet). +3. **Code last**: Implement until tests pass and behavior satisfies the spec. + +Do not implement production code for new behavior until the corresponding tests exist and have been run (expecting failure). + +--- + +## 1. Create git branch from dev + +- [ ] 1.1 Ensure we're on dev and up to date: `git checkout dev && git pull origin dev` +- [ ] 1.2 Create branch with Development link to issue (if exists): `gh issue develop 173 --repo nold-ai/specfact-cli --name feature/add-backlog-add-interactive-issue-creation --checkout` +- [ ] 1.3 Or create branch without issue link: `git checkout -b feature/add-backlog-add-interactive-issue-creation` (if no issue yet) +- [ ] 1.4 Verify branch was created: `git branch --show-current` + +## 2. Create GitHub issue in nold-ai/specfact-cli (mandatory) + +- [ ] 2.1 If issue not yet created: create issue in nold-ai/specfact-cli: `gh issue create --repo nold-ai/specfact-cli --title "[Change] Add backlog add (interactive issue creation)" --body-file --label "enhancement" --label "change-proposal"`. If issue already exists (e.g. #173), skip and ensure proposal.md Source Tracking is up to date. +- [ ] 2.2 Use body from proposal (Why, What Changes, Acceptance Criteria); add footer `*OpenSpec Change Proposal: add-backlog-add-interactive-issue-creation*` +- [ ] 2.3 Update `proposal.md` Source Tracking section with issue number, issue URL, repository nold-ai/specfact-cli, Last Synced Status: proposed +- [ ] 2.4 Link issue to project (optional): `gh project item-add 1 --owner nold-ai --url ` (requires `gh auth refresh -s project` if needed) + +## 3. Verify spec deltas (SDD: specs first) + +- [ ] 3.1 Confirm `specs/backlog-add/spec.md` exists and is complete (ADDED requirements, Given/When/Then for create_issue, add command, creation hierarchy). +- [ ] 3.2 Map scenarios to implementation: create via GitHub/ADO, add command with parent validation, custom hierarchy from config, non-interactive mode. + +## 4. Tests first (TDD: write tests from spec scenarios; expect failure) + +- [ ] 4.1 Write unit tests for adapter create_issue: mock GitHub/ADO API; assert payload mapping and return shape (id, key, url). +- [ ] 4.2 Write unit or integration tests from `specs/backlog-add/spec.md` scenarios: add with parent validation, hierarchy from config, non-interactive add, DoR check when --check-dor. +- [ ] 4.3 Run tests: `hatch run smart-test-unit` (or equivalent); **expect failure** (no implementation yet). +- [ ] 4.4 Document which scenarios are covered by which test modules. + +## 5. Extend BacklogAdapterMixin with create_issue (TDD: code until tests pass) + +- [ ] 5.1 Add abstract method `create_issue(project_id: str, payload: dict) -> dict` to `BacklogAdapterMixin` in `src/specfact_cli/adapters/backlog_base.py` with @abstractmethod, @beartype, and @icontract. +- [ ] 5.2 Implement `create_issue` in GitHub adapter: map payload to GitHub Issues API (POST /repos/{owner}/{repo}/issues); return dict with id, key (number), url. +- [ ] 5.3 Implement `create_issue` in ADO adapter: map payload to ADO Create Work Item API; set parent relation when parent_id present; return dict with id, key, url. +- [ ] 5.4 Run adapter create tests; **expect pass**; fix until tests pass. + +## 6. Implement creation hierarchy and add command (TDD: code until tests pass) + +- [ ] 6.1 Define optional creation_hierarchy in template or backlog_config schema (child type → list of allowed parent types); implement loader (from ProjectBundle.metadata.backlog_config or .specfact/spec.yaml). +- [ ] 6.2 Implement add command: options --adapter, --project-id, --template, --type, --parent, --title, --body, --non-interactive, --check-dor; interactive prompts when key args missing (unless --non-interactive). +- [ ] 6.3 Implement flow: load graph (fetch_all_issues + fetch_relationships or BacklogGraphBuilder when available); resolve type and parent; validate parent exists and allowed type from creation_hierarchy; optional DoR check (reuse backlog refine DoR); build payload; call adapter.create_issue; output id, key, url. +- [ ] 6.4 Register `specfact backlog add` in backlog command group (same place as refine, analyze-deps). +- [ ] 6.5 Run add-command tests; **expect pass**; fix until tests pass. + +## 7. Quality gates + +- [ ] 7.1 Run format and type-check: `hatch run format`, `hatch run type-check`. +- [ ] 7.2 Run contract test: `hatch run contract-test`. +- [ ] 7.3 Run full test suite: `hatch run smart-test` (or `hatch run smart-test-full`). +- [ ] 7.4 Ensure all new public APIs have @icontract and @beartype where applicable. + +## 8. Documentation research and review + +- [ ] 8.1 Identify affected documentation: docs/guides/agile-scrum-workflows.md, backlog-refinement or backlog guide. +- [ ] 8.2 Update agile-scrum-workflows (or backlog guide): add section for backlog add (`specfact backlog add`), interactive creation, DoR, slash prompt usage. +- [ ] 8.3 If adding a new doc page: set front-matter (layout, title, permalink, description) and update docs/_layouts/default.html sidebar if needed. + +## 9. Version and changelog (patch bump; required before PR) + +- [ ] 9.1 Bump **patch** version in `pyproject.toml` (e.g. X.Y.Z → X.Y.(Z+1)). +- [ ] 9.2 Sync version in `setup.py`, `src/__init__.py`, `src/specfact_cli/__init__.py` to match pyproject.toml. +- [ ] 9.3 Add CHANGELOG.md entry under new [X.Y.Z] - YYYY-MM-DD section: **Added** – Backlog add (interactive issue creation): `specfact backlog add` with type/parent selection, DoR validation, and create via adapter. + +## 10. Create Pull Request to dev + +- [ ] 10.1 Ensure all changes are committed: `git add .` and `git commit -m "feat(backlog): add backlog add for interactive issue creation"` +- [ ] 10.2 Push to remote: `git push origin feature/add-backlog-add-interactive-issue-creation` +- [ ] 10.3 Create PR: `gh pr create --repo nold-ai/specfact-cli --base dev --head feature/add-backlog-add-interactive-issue-creation --title "feat(backlog): add backlog add for interactive issue creation" --body-file ` (use repo PR template; add OpenSpec change ID `add-backlog-add-interactive-issue-creation` and summary; reference GitHub issue with `Fixes nold-ai/specfact-cli#173`). +- [ ] 10.4 Verify PR and branch are linked to issue in Development section. diff --git a/openspec/changes/add-backlog-dependency-analysis-and-commands/CHANGE_VALIDATION.md b/openspec/changes/add-backlog-dependency-analysis-and-commands/CHANGE_VALIDATION.md index 85c79f93..d28a2172 100644 --- a/openspec/changes/add-backlog-dependency-analysis-and-commands/CHANGE_VALIDATION.md +++ b/openspec/changes/add-backlog-dependency-analysis-and-commands/CHANGE_VALIDATION.md @@ -1,367 +1,27 @@ # Change Validation Report: add-backlog-dependency-analysis-and-commands -**Validation Date**: 2026-01-17 22:52:15 +0100 -**Change Proposal**: [proposal.md](./proposal.md) -**Validation Method**: Dry-run analysis against existing specs and codebase +**Validation Date**: 2026-02-02 +**Plan Reference**: specfact-cli-internal/docs/internal/implementation/2026-02-01-backlog-changes-improvement.md (E4) +**Validation Method**: Plan alignment + OpenSpec strict validation ## Executive Summary -- **Breaking Changes**: 0 detected (all new functionality) -- **Dependent Files**: 0 affected (new modules and commands) -- **Impact Level**: Medium (new features, extends existing patterns) -- **Validation Result**: Pass (with clarifications needed) -- **User Decision**: Proceed with clarifications +- **Plan Enhancement (E4)**: Dependency analysis extended with coordination artifacts: dependency contract per edge, ROAM list seed, critical path narrative; `--export json|md`; dependency review packet (Markdown). +- **Breaking Changes**: 0 (additive only). +- **Validation Result**: Pass. +- **OpenSpec Validation**: `openspec validate add-backlog-dependency-analysis-and-commands --strict` — valid. -## Format Validation - -- **proposal.md Format**: Pass - - Title format: Correct (`# Change: Add backlog dependency analysis and command suites`) - - Required sections: All present (Why, What Changes, Impact) - - "What Changes" format: Correct (uses NEW/EXTEND markers) - - "Impact" format: Correct (lists Affected specs, Affected code, Integration points) -- **tasks.md Format**: Pass - - Section headers: Correct (uses `## 1.`, `## 2.`, etc.) - - Task format: Correct (uses `- [ ] 1.1 [Description]`) - - Sub-task format: Correct (uses `- [ ] 1.1.1 [Description]` indented) -- **Format Issues Found**: 0 -- **Format Issues Fixed**: 0 (minor whitespace fixes applied) - -## Ambiguities and Clarifications Needed - -### 1. CRITICAL: Missing Adapter Methods for Bulk Fetching - -**Issue**: The proposal and spec deltas assume adapters have `fetch_issues()` and `fetch_relationships()` methods, but these methods don't exist in the current adapter interface. - -**Current State**: - -- `GitHubAdapter` has `fetch_backlog_item(item_ref: str)` for fetching a single item -- `BridgeAdapter` interface defines `import_artifact()` and `export_artifact()` but no bulk fetching methods -- No method exists for fetching all issues or relationships from a repository - -**Impact**: - -- Tasks 1.4.4, 2.1.3, 2.2.3 assume `adapter_instance.fetch_issues()` and `adapter_instance.fetch_relationships()` exist -- Spec delta in `devops-sync/spec.md` references these methods in scenarios - -**Recommendation**: - -- **Option A (Recommended)**: Extend `BacklogAdapterMixin` with abstract methods: - - ```python - @abstractmethod - def fetch_all_issues(self, project_id: str, filters: dict | None = None) -> list[dict[str, Any]]: - """Fetch all backlog items from provider.""" - - @abstractmethod - def fetch_relationships(self, project_id: str) -> list[dict[str, Any]]: - """Fetch all relationships/dependencies from provider.""" - ``` - -- **Option B**: Use existing `import_artifact()` with a special artifact key like `"backlog_items"` and `"backlog_relationships"` -- **Option C**: Create a new interface `BacklogGraphAdapter` that extends `BacklogAdapterMixin` with graph-specific methods - -**Action Required**: Update proposal.md to specify which approach will be used, and update tasks.md to include implementation of these methods in Phase 1. - -### 2. CLI Command Registration Location - -**Issue**: The proposal doesn't specify where `backlog` and `delta` command groups should be registered in `cli.py`. - -**Current State**: - -- Commands are registered in `cli.py` using `app.add_typer()` in logical workflow order -- Current order: init → import → migrate → plan → project → generate → enforce → repro → sdd → spec → contract → sync → drift → analyze → validate - -**Recommendation**: - -- Register `backlog` command group after `sync` (since it extends sync capabilities) -- Register `delta` command group after `backlog` (since it depends on backlog) -- Suggested location in `cli.py`: - - ```python - # 11. Synchronization - app.add_typer(sync.app, name="sync", ...) - - # 11.7. Backlog Management - app.add_typer(backlog.app, name="backlog", help="Backlog dependency analysis and sync") - - # 11.8. Delta Analysis - app.add_typer(delta.app, name="delta", help="Backlog delta analysis and impact tracking") - ``` - -**Action Required**: Update tasks.md to include CLI registration step (e.g., task 1.4.11 should include registering `backlog_app` in `cli.py`). - -### 3. Plan Bundle Format Extension - -**Issue**: The proposal mentions "extends with dependency graph data" but doesn't specify how `BacklogGraph` integrates with existing `ProjectBundle` model. - -**Current State**: - -- `ProjectBundle` model is defined in `src/specfact_cli/models/project.py` -- Plan bundles are stored as YAML files in `.specfact/plans/` directory -- Bundle format is versioned (currently v1.1 with change tracking support) - -**Recommendation**: - -- Add optional `backlog_graph: BacklogGraph | None` field to `ProjectBundle` model -- Or create separate storage for backlog graphs (e.g., `.specfact/backlog-graphs/`) -- Specify serialization format (JSON vs YAML) for `BacklogGraph` model -- Consider versioning: Should this be v1.2 or v2.0? - -**Action Required**: Update proposal.md to specify: - -- Where backlog graph data is stored (in bundle vs separate file) -- How `BacklogGraph` serializes to YAML/JSON -- Whether this requires bundle format version bump - -### 4. Project Configuration Storage - -**Issue**: The proposal mentions storing backlog configuration in `.specfact/config.yaml`, but the codebase uses `ProjectBundle` model for project configuration. - -**Current State**: - -- Project configuration is stored in `ProjectBundle.metadata` field -- `.specfact/config.yaml` doesn't exist as a standard file (may be project-specific) -- `ProjectBundle` has `ProjectMetadata` model for metadata - -**Recommendation**: - -- Store backlog configuration in `ProjectBundle.metadata` as a nested dict -- Or extend `ProjectMetadata` model with optional `backlog_config` field -- Clarify: Is `.specfact/config.yaml` a new file, or should it be `ProjectBundle.metadata`? - -**Action Required**: Update proposal.md and tasks.md to specify: - -- Where backlog config is stored (ProjectBundle.metadata vs separate config file) -- How `link-backlog` command updates the configuration -- How other commands read the configuration - -### 5. Console Output Patterns - -**Issue**: The proposal doesn't specify UI/UX patterns for command output, but the codebase has established patterns. - -**Current State**: - -- Commands use `rich.console.Console()` for output -- Helper functions in `specfact_cli.utils.console` for formatted output -- Commands use `specfact_cli.utils.print_*` helpers (print_error, print_info, print_success, print_warning) -- Progress bars use `specfact_cli.utils.progress` module - -**Recommendation**: - -- Use existing console helpers for consistent output -- Use `print_validation_report()` pattern for dependency analysis reports -- Use `Table` from `rich.table` for tabular data (items, dependencies, cycles) -- Use `Panel` from `rich.panel` for section headers -- Follow existing command patterns (see `project_cmd.py`, `sync.py` for examples) - -**Action Required**: Update tasks.md to reference existing console utilities and patterns. - -### 6. Template File Location - -**Issue**: The proposal mentions template YAML files but doesn't specify where they're stored. - -**Current State**: - -- Resources are stored in `src/specfact_cli/resources/` directory -- Templates could be stored there or in `src/specfact_cli/backlog/mappers/templates/` - -**Recommendation**: - -- Store templates in `src/specfact_cli/resources/backlog-templates/` for consistency with other resources -- Or store in `src/specfact_cli/backlog/mappers/templates/` for co-location with mapper code -- Specify: Are templates bundled with code or user-configurable? - -**Action Required**: Update tasks.md to specify template file location (task 1.2.2). - -### 7. Baseline File Format and Location - -**Issue**: The proposal mentions baseline files (`.specfact/backlog-baseline.json`) but doesn't specify the format or how it relates to plan bundles. - -**Current State**: - -- Plan bundles are stored as YAML files -- Baseline could be JSON (as specified) or YAML (for consistency) - -**Recommendation**: - -- Use YAML format for consistency with plan bundles -- Or use JSON for performance (faster parsing for large graphs) -- Specify: Should baseline be a serialized `BacklogGraph` or a separate format? +## Alignment with Plan E4 -**Action Required**: Update proposal.md and tasks.md to specify baseline file format and structure. +- **E4**: Extend add-backlog-dependency to emit coordination artifacts. **Done**: proposal.md and specs/devops-sync/spec.md updated with dependency contract, ROAM seed, critical path narrative; acceptance: `backlog analyze-deps` can export "dependency review packet" (Markdown). -### 8. Delta Command Group Naming +## USP / Value-Add -**Issue**: The proposal creates a new `delta` command group, but "delta" is a generic term that might conflict with future features. +- **Teams can use directly**: Dependency contract, ROAM seed, critical path narrative—feeds SAFe Δ5 and coordination workflows. +- **Machine + human**: `--export json|md` supports CI and human review. -**Current State**: - -- Existing commands: `sync`, `drift`, `analyze` -- "Delta" could refer to code deltas, spec deltas, or backlog deltas - -**Recommendation**: - -- Consider `backlog delta` subcommands instead of top-level `delta` command group -- Or use `backlog-delta` as command name -- Or keep `delta` but document it's backlog-specific - -**Action Required**: Update proposal.md to clarify command structure: - -- Option A: `specfact backlog delta status` (subcommand) -- Option B: `specfact delta status` (top-level, backlog-specific) -- Option C: `specfact backlog-delta status` (hyphenated top-level) - -## Dependency Analysis - -### Files to Create (New Modules) - -All new files are in new directories, so no breaking changes: - -- `src/specfact_cli/backlog/graph/models.py` - New -- `src/specfact_cli/backlog/graph/builders.py` - New -- `src/specfact_cli/backlog/graph/analyzers.py` - New -- `src/specfact_cli/backlog/mappers/` - New directory -- `src/specfact_cli/backlog/commands/` - New directory - -### Files to Modify (Extensions) - -**Low Risk (No Breaking Changes)**: - -- `src/specfact_cli/cli.py` - Add command group registration (no interface changes) -- `src/specfact_cli/commands/project_cmd.py` - Add new commands (no breaking changes) -- `src/specfact_cli/models/project.py` - Extend with optional fields (backward compatible) -- `src/specfact_cli/adapters/backlog_base.py` - Extend with new abstract methods (if Option A chosen for Issue #1) - -**Impact Assessment**: - -- **Code Impact**: Low - All changes are additive -- **Test Impact**: Medium - New test files needed for new modules -- **Documentation Impact**: Medium - New commands need documentation -- **Release Impact**: Minor version bump (v0.26.0, v0.27.0, v0.28.0 as planned) - -## Integration Points Validation - -### Bridge Adapter Architecture - -**Status**: ✅ Compatible - -- Uses existing `AdapterRegistry.get_adapter()` pattern -- Extends `BacklogAdapterMixin` (already exists) -- No breaking changes to adapter interface (if Option A chosen for Issue #1) - -### Plan Bundle Format - -**Status**: ⚠️ Needs Clarification - -- Proposal mentions extending bundle format but doesn't specify how -- Need to decide: in-bundle vs separate file storage -- Need to specify serialization format - -### Project Configuration - -**Status**: ⚠️ Needs Clarification - -- Proposal mentions `.specfact/config.yaml` but codebase uses `ProjectBundle.metadata` -- Need to align with existing patterns - -### CLI Command Structure - -**Status**: ✅ Compatible - -- Follows existing Typer patterns -- Uses existing console utilities -- Needs registration location specified (Issue #2) - -## Spec Alignment Check - -### bridge-adapter Spec - -**Status**: ✅ Aligned - -- Spec delta correctly extends bridge-adapter spec -- Uses adapter registry pattern correctly -- References existing adapter methods appropriately (except Issue #1) - -### devops-sync Spec - -**Status**: ⚠️ Needs Update - -- Spec delta assumes `fetch_issues()` and `fetch_relationships()` methods exist -- Need to update spec delta to reflect chosen approach for Issue #1 - -### data-models Spec - -**Status**: ✅ Aligned - -- No changes needed (dependency graph models are new, not extending change tracking models) - -## Recommendations - -### High Priority (Must Address Before Implementation) - -1. ✅ **Resolve Issue #1**: Choose approach for bulk fetching (Option A implemented - abstract methods added to BacklogAdapterMixin) -2. ✅ **Resolve Issue #3**: Specify plan bundle format extension approach (BacklogGraph stored in ProjectBundle.backlog_graph field, v1.2 format, separate JSON baseline files) -3. ✅ **Resolve Issue #4**: Clarify project configuration storage location (ProjectBundle.metadata.backlog_config, not separate config file) - -### Medium Priority (Should Address) - -1. ✅ **Resolve Issue #2**: Specify CLI registration location in tasks.md (backlog after sync, delta after backlog) -2. ✅ **Resolve Issue #5**: Add console output pattern references to tasks.md (rich.table.Table, rich.panel.Panel, specfact_cli.utils.console helpers) -3. ✅ **Resolve Issue #8**: Clarify delta command naming (separate command group `delta`, clearly backlog-specific) - -### Low Priority (Nice to Have) - -1. ✅ **Resolve Issue #6**: Specify template file location (src/specfact_cli/resources/backlog-templates/) -2. ✅ **Resolve Issue #7**: Specify baseline file format (JSON format for performance, serialized BacklogGraph model) - -## Next Steps - -1. ✅ **Update proposal.md** with clarifications for Issues #1, #3, #4, #8 - COMPLETED -2. ✅ **Update tasks.md** with: - - Implementation of bulk fetching methods (Issue #1) - COMPLETED (task 1.4) - - CLI registration steps (Issue #2) - COMPLETED (tasks 1.5.14, 2.2.10) - - Console output pattern references (Issue #5) - COMPLETED (multiple tasks) - - Template file location (Issue #6) - COMPLETED (task 1.2.2) - - Baseline file format specification (Issue #7) - COMPLETED (tasks 2.1.5, 3.4.1) -3. ✅ **Update spec deltas** to reflect chosen approach for Issue #1 - COMPLETED (bridge-adapter and devops-sync specs updated) -4. **Re-validate** after updates - READY FOR VALIDATION - -## Validation Artifacts - -- **Temporary workspace**: Not created (dry-run analysis only) -- **Interface scaffolds**: Not created (no interface changes detected) -- **Dependency graph**: Analyzed via codebase search and file reading -- **Breaking changes**: None detected (all new functionality) - -## OpenSpec Validation - -- **Status**: Ready for validation (all clarifications implemented) -- **Validation Command**: `openspec validate add-backlog-dependency-analysis-and-commands --strict` -- **Issues Found**: 0 (format validation passed) -- **Re-validated**: Yes (all clarifications implemented) - -## Implementation Status - -### Clarifications Implemented - -- ✅ **Issue #1**: Extended `BacklogAdapterMixin` with abstract methods `fetch_all_issues()` and `fetch_relationships()` (Option A) -- ✅ **Issue #2**: Specified CLI registration location (backlog after sync, delta after backlog) -- ✅ **Issue #3**: Specified plan bundle format extension (BacklogGraph in ProjectBundle.backlog_graph field, v1.2, separate JSON baseline) -- ✅ **Issue #4**: Clarified project configuration storage (ProjectBundle.metadata.backlog_config, not separate file) -- ✅ **Issue #5**: Added console output pattern references (rich.table.Table, rich.panel.Panel, specfact_cli.utils.console helpers) -- ✅ **Issue #6**: Specified template file location (src/specfact_cli/resources/backlog-templates/) -- ✅ **Issue #7**: Specified baseline file format (JSON format, serialized BacklogGraph model) -- ✅ **Issue #8**: Clarified delta command naming (separate command group, backlog-specific) - -### Files Updated - -- ✅ `proposal.md` - Added clarifications for all issues -- ✅ `tasks.md` - Added implementation details for all clarifications -- ✅ `specs/bridge-adapter/spec.md` - Added bulk fetching methods requirement -- ✅ `specs/devops-sync/spec.md` - Updated scenarios to use bulk fetching methods - ---- - -**Validation Result**: **PASS - Ready for Implementation** +## Format Validation -All ambiguities have been resolved and clarifications have been implemented in the change artifacts. The proposal is now ready for OpenSpec validation and implementation. All issues are non-breaking and the implementation approach is clearly specified. +- proposal.md: E4 EXTEND bullet and acceptance added. +- specs: New requirement (Dependency review packet and coordination artifacts) with Given/When/Then. +- tasks.md: Unchanged; format OK. diff --git a/openspec/changes/add-backlog-dependency-analysis-and-commands/proposal.md b/openspec/changes/add-backlog-dependency-analysis-and-commands/proposal.md index d6531a69..85910c8f 100644 --- a/openspec/changes/add-backlog-dependency-analysis-and-commands/proposal.md +++ b/openspec/changes/add-backlog-dependency-analysis-and-commands/proposal.md @@ -35,7 +35,7 @@ After implementing backlog adapters for ADO and GitHub with directional sync (v0 - **EXTEND**: Extend `BacklogAdapterMixin` (or `BacklogAdapter` interface from `add-generic-backlog-abstraction`) with abstract methods `fetch_all_issues()` and `fetch_relationships()` for bulk backlog data fetching (required for dependency graph building). - **NOTE**: The `search_issues()` and `list_work_items()` methods from `add-template-driven-backlog-refinement` are wrapper methods that call `fetch_all_issues()` with filtering. Both changes coordinate on adapter method naming. - **EXTEND**: Add optional `backlog_graph: BacklogGraph | None` field to `ProjectBundle` model (v1.2) for storing dependency graph data in plan bundles, with separate JSON baseline files (`.specfact/backlog-baseline.json`) for delta comparison. - +- **EXTEND** (plan E4): Add outputs that teams can use directly: "dependency contract" per edge (what/when/acceptance), ROAM list seed (feeds SAFe safe-pi-planning-essentials), "critical path narrative" for humans (short, evidence-based). Add `--export json|md` for analyzers. **Acceptance**: `specfact backlog analyze-deps` can export a "dependency review packet" (Markdown). --- diff --git a/openspec/changes/add-backlog-dependency-analysis-and-commands/specs/devops-sync/spec.md b/openspec/changes/add-backlog-dependency-analysis-and-commands/specs/devops-sync/spec.md index 99d41b5a..1a47a1c3 100644 --- a/openspec/changes/add-backlog-dependency-analysis-and-commands/specs/devops-sync/spec.md +++ b/openspec/changes/add-backlog-dependency-analysis-and-commands/specs/devops-sync/spec.md @@ -279,3 +279,20 @@ The system SHALL support DevOps flow stages configuration in `.specfact/spec.yam - **THEN** `devops-flow` command uses these stage definitions - **AND** available actions for each stage are defined by configuration + +### Requirement: Dependency review packet and coordination artifacts (E4 extension) + +The system SHALL support exporting coordination artifacts from dependency analysis: "dependency contract" per edge (what/when/acceptance), ROAM list seed (for SAFe PI planning), and "critical path narrative" for humans (short, evidence-based). `specfact backlog analyze-deps` SHALL support `--export json|md` and SHALL be able to export a "dependency review packet" (Markdown). + +**Rationale**: Plan E4—teams need dependency review packet for coordination and SAFe ROAM. + +#### Scenario: Export dependency review packet + +- **GIVEN** a backlog graph has been built and analyzed +- **WHEN** user runs `specfact backlog analyze-deps --export md` (or equivalent) +- **THEN** the system emits a dependency review packet (Markdown) that includes: dependency contract per edge (what/when/acceptance), ROAM list seed when applicable, and critical path narrative (short, evidence-based) +- **AND** `--export json` emits machine-readable equivalent when specified + +**Acceptance Criteria**: + +- `backlog analyze-deps` can export a "dependency review packet" (Markdown); coordination artifacts (dependency contract, ROAM seed, critical path narrative) are included when applicable. diff --git a/openspec/changes/daily-standup-progress-support/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-02-02-daily-standup-progress-support/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/daily-standup-progress-support/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-02-02-daily-standup-progress-support/CHANGE_VALIDATION.md diff --git a/openspec/changes/archive/2026-02-02-daily-standup-progress-support/design.md b/openspec/changes/archive/2026-02-02-daily-standup-progress-support/design.md new file mode 100644 index 00000000..9073e320 --- /dev/null +++ b/openspec/changes/archive/2026-02-02-daily-standup-progress-support/design.md @@ -0,0 +1,93 @@ +# Design: Daily standup and progress support + +## Default standup scope and config + +- **Default state**: When user does not pass `--state`, apply a default so closed/done items are excluded (e.g. `open` for GitHub, "Active" or equivalent for ADO). Built-in default: `open`; overridable by config or env (e.g. `SPECFACT_STANDUP_STATE`, or `standup.default_state` in `.specfact/standup.yaml`). +- **Default assignee**: Optional default `assignee=me` (current user when resolvable from adapter or env) so "my items" are shown by default. Config: `SPECFACT_STANDUP_ASSIGNEE` or `standup.default_assignee`; when unset, no default assignee (show all in scope). +- **Default limit**: Cap number of items (e.g. 20 or 30) for scannable output. Built-in default: e.g. 20; overridable by config or env (e.g. `SPECFACT_STANDUP_LIMIT`, or `standup.limit` in config file). +- **Config file**: Optional `.specfact/standup.yaml` (or under `SPECFACT_CONFIG_DIR`) with keys such as `default_state`, `default_assignee`, `limit`, `sprint` (current or name), `show_unassigned` (bool). Env vars override config file; CLI options override both. +- **Kanban**: For Kanban, teams can omit iteration/sprint and rely on state + limit; unassigned items represent pullable work. + +## Current iteration/sprint + +- **Adapter support**: Use existing `iteration` / `sprint` fields on `BacklogItem` and existing `_apply_filters(..., iteration=..., sprint=...)`. For "current" sprint, adapter or a small helper resolves "current" to the active iteration (e.g. ADO team's current iteration; GitHub may use project board or labels). +- **daily command**: Add `--iteration` and `--sprint` to `specfact backlog daily` (same as in `refine`). Support value `current` when adapter can resolve it; otherwise use literal value. Config can set `sprint: current` for standup-only default. +- **Sprint/iteration context**: When `--sprint current` or config sprint is in use and the adapter (or config) provides an iteration/sprint end date, display it in the standup view (e.g. header or first line: "Sprint ends: YYYY-MM-DD (N days)"). If the adapter does not provide it, support optional config (e.g. `standup.sprint_end_date` or `iteration_end_date`) for manual override. No new adapter contract required if using existing iteration/sprint metadata or config. +- **Fallback**: If adapter does not provide iteration/sprint or "current" cannot be resolved, omit iteration/sprint filter and document in help; no crash. + +## Unassigned/pending items + +- **Definition**: Items in the same scope (state, iteration/sprint) whose `assignees` is empty or None. +- **Presentation**: Two options (implement one or both): + - **Separate table**: After the main "Daily standup" (assigned) table, render a second table titled e.g. "Pending / open for commitment" with unassigned items (same columns or subset: ID, Title, Status, Last updated). Omit section when there are no unassigned items. + - **Parameter**: `--unassigned` or `--show-pending` to include the unassigned table; default can be true for standup (show both) or false (only assigned). `--unassigned-only` shows only unassigned items (single table). +- **Scope**: Unassigned items use the same filters (state, iteration, sprint, labels) so they are "in sprint / open but not yet assigned." +- **Order**: Keep assigned table first (primary standup), unassigned second (commitment/pick-up). + +## Blockers and time-critical + +- **Blockers prominence**: Optionally sort the standup table so rows with non-empty Blockers appear first, or add a `--blockers-first` flag, so time-critical issues are visible at a glance. + +## Value / priority (optional) + +- When available on `BacklogItem`, show priority (or `business_value` / `value_points`) in the daily table or via a config option, to support value-driven (SAFe/WSJF) focus. + +## Bridge adapter integration + +- **Standup view**: Reads from existing OpenSpec change proposals and/or backlog adapter data (same sources as `specfact sync bridge` and backlog commands). No new adapter contract; reuse existing list/fetch APIs. +- **Post standup comment**: When user opts in, use existing GitHub (and optionally ADO) adapter to add a comment to the linked issue. GitHub: use Issues API `POST /repos/{owner}/{repo}/issues/{issue_number}/comments` with standup body. ADO: use Work Item Comments API if available; otherwise document as future extension. +- **Adapter capability**: Adapters that support "post comment" expose a method (e.g. `post_comment(issue_id, body)`) or equivalent; standup command calls it when adapter supports it and user opts in. Read-only or unsupported adapters return a clear "not supported" so the CLI does not attempt to post. + +## Sequence (post standup comment) + +```text +User → specfact standup --post-standup --standup-text "Yesterday: X. Today: Y. Blockers: Z" + → CLI resolves change proposal → Source Tracking → issue number + repo + → CLI gets adapter for repo (e.g. GitHub) + → If adapter supports post_comment: adapter.post_comment(issue_number, formatted_body) + → GitHub API: POST .../issues/{n}/comments + → CLI reports success or failure +``` + +## Contract enforcement + +- New public functions (e.g. standup view builder, comment poster) shall have @icontract and @beartype. +- Adapter interface extension (post_comment) optional with default "not supported" to keep backward compatibility. + +## Fallback / offline + +- Standup view is read-only from local/cached data; no network required for view-only. +- Post comment requires network and auth; failure (rate limit, auth) is reported; no silent swallow. + +## Alignment with existing sync bridge + +- Existing `specfact sync bridge --add-progress-comment` and `--track-code-changes` already add progress comments to GitHub issues. Standup comment can reuse or extend that path (e.g. standup format as a variant of progress comment) to avoid duplicate comment-posting logic. Standup/progress is exposed under the backlog command group as `specfact backlog daily` (no top-level standup or scrum command). + +## Interactive step-by-step review + +- **Trigger**: `specfact backlog daily --interactive` (or equivalent flag). Same scope as non-interactive daily (state, iteration/sprint, assignee, limit, unassigned) so items shown are the same as the table view. +- **Selection UI**: Use questionary (or equivalent) for arrow-key selection, consistent with existing template field mapping (e.g. `backlog_commands.py` questionary.select). Choices: one per backlog item in scope, label format e.g. `{id} - {title} [{status}] ({assignee})` so user can pick an item to inspect. +- **Detail view**: When user selects an item, display full details comparable to `specfact backlog refine `: ID, title, status, assignees, last updated, description/body, acceptance criteria, story points, business value, priority (when available), standup fields (yesterday/today/blockers). Fetch and display **existing comments** from the adapter (e.g. `_get_issue_comments` / `_get_work_item_comments`); highlight blocked status if blockers non-empty. +- **Navigation**: After showing detail, present choices: "Next story", "Previous story", "Back to list", "Exit". "Next/Previous" move to next/previous item in current ordered list without re-opening the full menu; "Back to list" returns to the item selector; "Exit" ends the command. +- **Next-best-item suggestion**: When adapter or BacklogItem provides story_points, business_value, priority: compute a value score for pending (e.g. todo/unassigned) items, e.g. `value_score = business_value / (story_points * priority)` (guard against zero). Optionally show "Suggested next: - (score: X)" in the detail view or in the list. Config or flag to enable/disable (e.g. `--suggest-next` or standup config). +- **Sprint goal alignment**: When sprint goal is provided by adapter or config (e.g. `standup.sprint_goal` or adapter metadata), optional hint in interactive view (e.g. "Sprint goal: …") so user can align; no editing of sprint goal. If not available, omit. +- **Contract**: New public helpers (e.g. interactive walkthrough, detail renderer) shall have @icontract and @beartype. Reuse existing refine and comment-fetch paths where possible. + +## Export to file for Copilot + +- **Trigger**: `specfact backlog daily --copilot-export <path>` (or `--export-copilot <path>`). Same scope as daily (current iteration/sprint, active/blocked/todo). When both `--interactive` and `--copilot-export` are given, export can run after interactive session or in addition; design choice: export runs on same fetched list (no need to re-fetch). +- **Content**: One section per backlog item in scope. Per item include: ID, title, status, assignee(s), last updated, short progress summary (standup fields if present), blockers, and optionally value score / priority / story points so Copilot can assist with "which to pick next" and "what's blocked". Format: Markdown with clear headings (e.g. `## <id> - <title>`) and bullet points for quick scanning and Copilot slash-command use. +- **Purpose**: File is for use during standup with Copilot (e.g. paste or reference in slash command) to summarize current progress and next steps per story; complementary to the backlog, not a replacement. +- **Contract**: Export builder function shall have @icontract and @beartype; idempotent write to file (overwrite or configurable). + +## Value score and "next to pick" (optional) + +- **Formula**: When story_points, business_value, and priority are available, use e.g. `value_score = business_value / max(1, story_points * priority)` so higher score = higher value per unit effort/priority. Use for (1) optional column in daily table, (2) optional "suggested next" in interactive view, (3) optional line in Copilot export. +- **When data missing**: Omit score or column; no fake values. Document which adapters/fields provide the data. + +## Out of scope / Future work (not in this change) + +- **Stale / at-risk items** (e.g. "no update in N days"): Possible future "gaps" enhancement; not a requirement in this change. Many teams get what they need from "last updated" + blockers; no explicit "at risk" flag or threshold in scope. +- **Sprint goal**: Not displayed or edited; when provided by adapter/config, used only for optional alignment hint in interactive/export. Users see sprint goal in their board/sprint settings. +- **Structured "blocked by"**: Only free-text blockers (standup blockers field) are in scope. BacklogItem has no first-class `blocked_by` (link to another issue). Acceptable for typical standups; structured dependency links are out of scope. +- **Replacement for backlog**: Interactive and export are complementary aids; they do not replace the backlog or board. diff --git a/openspec/changes/archive/2026-02-02-daily-standup-progress-support/proposal.md b/openspec/changes/archive/2026-02-02-daily-standup-progress-support/proposal.md new file mode 100644 index 00000000..b0e1a803 --- /dev/null +++ b/openspec/changes/archive/2026-02-02-daily-standup-progress-support/proposal.md @@ -0,0 +1,42 @@ +# Change: Daily standup and progress support + +## Why + + +Bridge comments and sync already support exporting/updating change proposals and issues. For daily standup there is no structured "standup" view that aggregates my items, recent activity, and blockers; progress/standup notes are not first-class (e.g. yesterday/today/blockers format) that could be pushed to issue comments. Teams duplicate standup info in tools; SpecFact can surface progress from OpenSpec/bridge and optionally publish to GitHub/ADO so standup updates are visible where the team works. Daily standups should focus on current iteration/sprint, unassigned work for commitment, and early visibility of blockers and time-critical items so DevOps teams can deliver the right features. + +## What Changes + + +- **NEW**: Add a lightweight standup/progress view under the backlog command group: list my change proposals or backlog items (by assignee or filter), with last-updated and status; optional one-line summary for yesterday/today/blockers from proposal or linked issue body. Expose as `specfact backlog daily` (no top-level `specfact standup`). +- **NEW**: Optional mode to post standup summary as a comment on linked issues via `specfact backlog daily` (e.g. `--post`) or reuse of `specfact sync bridge --add-progress-comment` with standup format (e.g. GitHub issue comment). +- **NEW**: Default standup scope for meaningful daily standups: default state filter (e.g. open/active so closed items are excluded), optional default assignee (e.g. current user / "me" when resolvable), and default limit (e.g. 20–30 items). Configurable via environment variables and/or `.specfact/standup.yaml` (or equivalent) so teams can tailor defaults. +- **NEW**: Current iteration/sprint focus: when the adapter supports iteration or sprint (e.g. GitHub Projects, ADO iteration), allow filtering or scoping the standup view to the current iteration/sprint so "new items to start" in the sprint are visible and can be committed to during standup. +- **NEW**: Unassigned/pending items view: show items with no assignee (backfill, open sprint, or pending) in a separate table or via an additional parameter (e.g. `--unassigned` / `--pending`) so the team can discuss and commit to picking them up during standup, alongside assigned "in progress" items. +- **EXTEND**: When viewing current iteration/sprint, show sprint/iteration end date when provided by adapter or config so teams see "days left in sprint"; optionally surface priority or value in the standup view for value-driven (e.g. SAFe) teams. +- **EXTEND**: Bridge/adapters: support posting comment to linked issue when adapter supports it (e.g. GitHub). +- **EXTEND**: Documentation (agile-scrum-workflows, devops-adapter-integration) for daily standup with SpecFact, including default scope, iteration/sprint, unassigned items, Kanban vs Scrum/SAFe usage, and time-critical/blockers focus. +- **NEW**: Interactive step-by-step review: `specfact backlog daily --interactive` (or equivalent) presents backlog items in the current scope via arrow-key selection (e.g. questionary); user selects an item to see full details (as in `specfact backlog refine` per item), including progress, existing comments, and optional "next best item" suggestion based on value/priority/complexity (e.g. business value / (story points × priority)) for pending items. Complementary to the backlog; no replacement for the backlog itself. +- **NEW**: Export to file for Copilot: option (e.g. `--copilot-export <path>`) to write a summarized view of each story (current iteration/sprint, active/blocked/todo) to a file for use with Copilot slash-command interactive review during standup—assisting next steps and summarizing progress per story for team discussion. Format is Markdown or team-friendly so Copilot can assist with informed feedback and decision-making (blocked items, which item to pick next). +- **NEW**: Project backlog context: store non-sensitive adapter context (org, project per adapter) in the repo so users do not have to pass `--repo-owner`/`--repo-name` or `--ado-org`/`--ado-project` every time after authenticating once. Use `.specfact/backlog.yaml` (no tokens, no users); resolution order: CLI args > env (e.g. `SPECFACT_GITHUB_REPO_OWNER`) > file. Applies to all backlog commands (daily, refine, sync bridge, etc.). +- **CLARIFY**: Interactive detail view shows **existing comments** annotated to each issue (fetched via adapter when supported; e.g. GitHub issue comments, ADO work item discussion). Document in tutorial and spec. +- **NEW**: Slash-command prompt for daily standup: add `resources/prompts/specfact.backlog-daily.md` (analogous to `specfact.backlog-refine.md`) so teams can run refinement interactively with the DevOps team story-by-story: explain/highlight current focus, surface found issues or open questions, allow adding discussion notes as additional annotation comments (realistic daily standup scope). Invocable via slash command (e.g. `specfact.daily` or `specfact.backlog-daily`). +- **NEW**: `--summarize` flag: when set, the CLI produces a **prompt** (instructions plus applied filters and filtered standup output) suitable for use in an interactive slash-command prompt (e.g. `specfact.daily`) or copy-paste to Copilot, so an LLM can generate a meaningful **summary of the daily standup status**. Output includes filter context (adapter, state, sprint, assignee, limit) and the same per-item data as `--copilot-export`; format is prompt-ready for "generate a standup summary from this data." + +## Capabilities +- **daily-standup**: Standup view (list my/filtered items with status and last activity; optional standup summary lines; default scope for state/assignee/limit; current iteration/sprint and sprint end date when supported; separate unassigned/pending items for commitment; optional blockers-first and priority/value for time-critical and value-driven focus; Kanban/Scrum/SAFe usage) and optional post standup comment to linked issue via adapter. +- **daily-standup-interactive**: Interactive selection (arrow-key, e.g. questionary) for step-by-step walkthrough of user stories in scope; per-item detail view (refine-like: description, acceptance criteria, progress, existing comments); optional next-best-item suggestion using value score (e.g. business value / (story points × priority)) when sprint goal/complexity/value/priority are available; alignment with sprint goal when defined and available. +- **daily-standup-copilot-export**: Export to file (e.g. `--copilot-export <path>`) of summarized progress per story (active/blocked/todo in current iteration/sprint) for Copilot slash-command use during standup—next steps and current progress per story for team discussion; complementary aid, not replacement for backlog. +- **backlog-project-context**: Project-level backlog config (`.specfact/backlog.yaml`) with org/project per adapter (e.g. `github.repo_owner`, `github.repo_name`; `ado.org`, `ado.project`, `ado.team`); no secrets; CLI and env override file; used by `_build_adapter_kwargs` so backlog commands can omit adapter context after one-time config. +- **daily-standup-prompt**: Slash-command prompt file `specfact.backlog-daily.md` for interactive walkthrough with DevOps team (story-by-story, current focus, issues/open questions, discussion notes as comments); usable as `specfact.daily` or `specfact.backlog-daily`. +- **daily-standup-summarize**: `--summarize` flag outputs a prompt (filters + filtered standup data) for slash command or Copilot to generate a standup summary. + +--- + +## Source Tracking + +<!-- source_repo: nold-ai/specfact-cli --> +- **GitHub Issue**: #168 +- **Issue URL**: <https://github.com/nold-ai/specfact-cli/issues/168> +- **Last Synced Status**: proposed +<!-- content_hash: 0908d647c2941d20 --> diff --git a/openspec/changes/archive/2026-02-02-daily-standup-progress-support/specs/daily-standup/spec.md b/openspec/changes/archive/2026-02-02-daily-standup-progress-support/specs/daily-standup/spec.md new file mode 100644 index 00000000..b3ebf501 --- /dev/null +++ b/openspec/changes/archive/2026-02-02-daily-standup-progress-support/specs/daily-standup/spec.md @@ -0,0 +1,335 @@ +# Daily Standup + +## Out of scope (not requirements) + +The following are explicitly **not** in scope for this change; they are documented so stakeholders know where the boundary is: + +- **Stale / at-risk items** (e.g. "no update in N days"): Not required. Many teams rely on "last updated" + blockers; no SHALL for at-risk flags or thresholds. +- **Sprint goal**: Not displayed or edited by this change; users see sprint goal in their board/sprint settings (document in user docs). +- **Structured "blocked by"** (e.g. link to another issue): Only free-text blockers are in scope; BacklogItem has no first-class `blocked_by`. Acceptable for typical standups. + +## ADDED Requirements + +### Requirement: Standup view + +The system SHALL provide a standup or progress view that lists change proposals or backlog items (by assignee or filter) with last-updated and status, and optional one-line summary for yesterday/today/blockers. + +**Rationale**: Teams need a single place to see "my items" and recent activity for daily standup without duplicating data in multiple tools. + +#### Scenario: List my items with status and last activity + +**Given**: A user has change proposals or backlog items assigned to them (or a filter is applied) + +**When**: The user runs the standup view (e.g. `specfact backlog daily` or equivalent under the backlog command group) + +**Then**: The system lists items (change proposal id or backlog item id, title, status, last-updated) for the user or filter + +**And**: Optional standup summary lines (yesterday/today/blockers) are shown when available from proposal or linked issue body + +**Acceptance Criteria**: + +- Output is readable (e.g. table or structured list) +- Last-updated is displayed per item +- Optional standup fields (yesterday, today, blockers) shown when present in source data + +#### Scenario: Standup view with assignee filter + +**Given**: A repo with multiple change proposals or backlog items and assignee metadata + +**When**: The user runs standup view with assignee filter (e.g. `--assignee me` or current user) + +**Then**: Only items matching the assignee are listed + +**And**: If no assignee filter is applied, all items (or default scope) are listed per command contract + +### Requirement: Default standup scope (meaningful daily standups) + +The system SHALL support a default standup scope so daily standups focus on active work, not the full backlog: default state filter (e.g. open/active), optional default assignee (e.g. current user when resolvable), and default limit (e.g. 20–30 items). Defaults SHALL be overridable by explicit options and SHALL be configurable via environment variables and/or a config file (e.g. `.specfact/standup.yaml`). + +**Rationale**: Without defaults, `specfact backlog daily` can list the entire backlog; standups should default to "active items and recent activity" so the view is immediately useful. + +#### Scenario: Standup view uses default scope when no filters given + +**Given**: Standup defaults are configured (e.g. state=open, limit=20) or built-in (state=open, limit=20) + +**When**: The user runs standup view without explicit `--state`, `--assignee`, or `--limit` + +**Then**: The system applies the default state filter (e.g. open) so closed/done items are excluded + +**And**: The system applies the default limit (e.g. 20) so output is scannable + +**And**: If configured, default assignee (e.g. "me") is applied so "my items" are shown by default + +**Acceptance Criteria**: + +- Explicit `--state`, `--assignee`, `--limit` override defaults +- Config (env or file) takes precedence over built-in defaults when present +- When adapter has no "open" equivalent, state default is documented (e.g. skip or use adapter's active state) + +### Requirement: Current iteration/sprint focus + +The system SHALL support focusing the standup view on the current iteration or sprint when the adapter provides iteration/sprint metadata (e.g. GitHub Projects, ADO iteration). A parameter (e.g. `--sprint current` or `--iteration current`) or config SHALL filter items to the current iteration/sprint so "new items to start" in the sprint are visible and can be committed to during standup. + +**Rationale**: Daily standups need to see both "what I'm working on" and "what's in the sprint but not yet assigned" so the team can commit to new work. + +#### Scenario: Standup view filtered to current iteration/sprint + +**Given**: An adapter that supports iteration or sprint (e.g. ADO with iteration path, or GitHub with project/sprint) + +**When**: The user runs standup view with current iteration/sprint (e.g. `--sprint current` or config `sprint: current`) + +**Then**: The system lists only items in the current iteration/sprint (or current active sprint when adapter supports it) + +**And**: If the adapter does not support iteration/sprint, the option is ignored or reported clearly; no crash + +**Acceptance Criteria**: + +- When supported, "current" resolves to the adapter's notion of current sprint/iteration +- When current iteration/sprint is in use and the adapter or config provides an iteration/sprint end date, the standup view displays it (e.g. "Sprint ends: DATE (N days)") +- Documentation states which adapters support iteration/sprint filtering + +### Requirement: Unassigned/pending items view + +The system SHALL show items with no assignee (backfill, open sprint, or pending) so the team can discuss and commit to picking them up during standup. Unassigned items SHALL be available either in a separate table (e.g. "Pending / open for commitment") or via an additional parameter (e.g. `--unassigned` / `--pending`) that includes or exclusively shows unassigned items in the same scope (e.g. same state and iteration). + +**Rationale**: Standups are not only about "what I did / what I'll do" but also "new items in the iteration that need commitment from the team"; unassigned items must be visible and discussable. + +#### Scenario: Unassigned items shown for standup commitment + +**Given**: A backlog with items in the current scope (e.g. open, current sprint) some of which are assigned and some unassigned + +**When**: The user runs standup view with unassigned items enabled (e.g. default, or `--unassigned`, or `--show-pending`) + +**Then**: The system shows assigned items (e.g. in a "My / assigned" table or section) and unassigned items (e.g. in a separate "Pending / open for commitment" table or section) so both can be discussed + +**And**: Unassigned items use the same scope (state, iteration/sprint if applied) so they are relevant to the current iteration + +**Acceptance Criteria**: + +- Unassigned items are clearly labeled (e.g. separate table title or column) +- Option to show only unassigned (e.g. `--unassigned-only`) is available for teams that want to run "pick up" separately +- When no unassigned items exist in scope, the unassigned section is omitted or shows "None" + +### Requirement: Blockers and time-critical prominence + +The system SHALL support optionally sorting or surfacing items with non-empty blockers so time-critical issues are visible at a glance (e.g. sort rows with blockers first, or a `--blockers-first` flag). + +**Rationale**: Daily standups need to surface blockers early so the team can address time-critical issues during the current iteration. + +#### Scenario: Standup view with blockers first + +**Given**: Backlog items in the standup view, some with non-empty standup blockers text + +**When**: The user runs standup view with blockers-first enabled (e.g. `--blockers-first` or default sort) + +**Then**: Items with non-empty blockers are listed first (or in a dedicated order) so blockers are easy to spot + +**Acceptance Criteria**: + +- When supported, items with non-empty blockers may be listed first (e.g. sort or `--blockers-first`), so blockers are easy to spot + +### Requirement: Optional priority/value in standup view + +The system SHALL support optionally showing priority or business value in the standup view when available on BacklogItem and enabled by config, so value-driven (e.g. SAFe) teams can focus on the right features. + +**Rationale**: Value-driven prioritization (WSJF, priority) helps teams deliver the right features during the iteration. + +#### Scenario: Standup view shows priority or value when enabled + +**Given**: Backlog items have priority or business_value (or value_points) and standup config enables showing priority/value + +**When**: The user runs standup view with priority/value display enabled (e.g. config or option) + +**Then**: The standup table includes a priority or value column (or equivalent) when the data is present on items + +**Acceptance Criteria**: + +- When priority or business value is available on BacklogItem and enabled by config, the standup view may display it (e.g. optional column) + +### Requirement: Post standup comment to linked issue + +The system SHALL support posting a standup summary as a comment on the linked issue (e.g. GitHub issue comment) when the user opts in and the adapter supports it. + +**Rationale**: Standup updates should be visible in the DevOps backend (GitHub, ADO) so the team sees progress where they work. + +#### Scenario: Post standup comment via GitHub adapter + +**Given**: A change proposal with Source Tracking linking to a GitHub issue (e.g. nold-ai/specfact-cli#N) + +**And**: The user has provided standup text (yesterday/today/blockers format) and opts to post (e.g. `specfact backlog daily --post` or equivalent) + +**When**: The user runs `specfact backlog daily --post` (or equivalent) and GitHub adapter is configured + +**Then**: The system adds a comment to the linked GitHub issue with the standup text (format: Yesterday / Today / Blockers or team-defined format) + +**And**: The comment is clearly identifiable (e.g. "Standup YYYY-MM-DD" or configurable prefix) + +**Acceptance Criteria**: + +- Comment is posted only when user opts in and adapter supports comments +- Format is configurable or follows a simple standard (yesterday, today, blockers) +- Failure to post (e.g. auth, rate limit) is reported clearly; no silent swallow + +#### Scenario: Post standup when adapter does not support comments + +**Given**: An adapter that does not support posting comments (e.g. read-only or no comment API) + +**When**: The user runs `specfact backlog daily --post` (or equivalent) + +**Then**: The system reports that posting is not supported for this adapter and does not attempt to post + +**Acceptance Criteria**: + +- Clear message; no crash or undefined behavior + +### Requirement: Interactive step-by-step review + +The system SHALL support an interactive step-by-step review of backlog items in the same scope as the standup view (state, iteration/sprint, assignee, limit, unassigned), using arrow-key selection (e.g. questionary) so the user can walk through each story and see full details including progress and existing comments. The feature is complementary to the backlog; it does not replace the backlog or board. + +**Rationale**: Teams need to review each story in detail during standup (blocked items, which to pick next) with minimal context switching; interactive selection and refine-like detail support informed feedback and decision-making. + +#### Scenario: Interactive selection presents items and shows detail on choice + +**Given**: Backlog items in the current scope (e.g. current iteration/sprint, active/blocked/todo) and the user runs `specfact backlog daily --interactive` (or equivalent) + +**When**: The interactive mode starts + +**Then**: The system presents a list of choices (one per backlog item), selectable by arrow keys (e.g. questionary), with each choice showing a summary line (e.g. id, title, status, assignee) + +**And**: When the user selects an item, the system displays full details comparable to `specfact backlog refine` for that item: ID, title, status, assignees, last updated, description/body, acceptance criteria, story points, business value, priority when available, standup fields (yesterday/today/blockers), and **existing comments annotated to that issue** (fetched via adapter when supported, e.g. GitHub issue comments, ADO work item discussion) + +**And**: Items with non-empty blockers are clearly indicated (e.g. blocked status highlighted) + +**Acceptance Criteria**: + +- Selection UI is consistent with existing questionary usage (e.g. template field mapping) +- Detail view reuses or aligns with refine output (description, acceptance criteria, **comments**) +- **Comments**: The system SHALL show comments annotated to the selected issue when the adapter supports fetching comments (e.g. get_comments); omitted or empty when adapter does not support or returns none + +#### Scenario: Interactive navigation (next / previous / back / exit) + +**Given**: User is in interactive mode and has just viewed detail for one item + +**When**: The system presents navigation choices (e.g. "Next story", "Previous story", "Back to list", "Exit") + +**Then**: "Next story" shows detail for the next item in the current ordered list without re-opening the full item menu + +**And**: "Previous story" shows detail for the previous item + +**And**: "Back to list" returns to the item selector menu + +**And**: "Exit" ends the command + +**Acceptance Criteria**: + +- Next/Previous wrap or stop at list boundaries (documented behavior) +- No re-fetch of list when moving next/previous; use already-fetched items + +#### Scenario: Optional next-best-item suggestion and sprint goal hint + +**Given**: BacklogItem or adapter provides story_points, business_value, and priority for some items; optionally sprint goal is provided by adapter or config + +**When**: Interactive mode is run with suggestion enabled (e.g. config or `--suggest-next`) + +**Then**: The system may show a "Suggested next: <id> - <title>" (or value score) for pending (e.g. todo/unassigned) items using a value score (e.g. business_value / max(1, story_points * priority)) so higher value per effort is suggested + +**And**: When sprint goal is available (adapter or config), the interactive view may display an optional hint (e.g. "Sprint goal: …") so the user can align; sprint goal is not edited by the system + +**Acceptance Criteria**: + +- Value score is omitted when required fields are missing; no fake values +- Suggestion is optional (config or flag); default can be off to avoid noise +- Sprint goal hint is optional and read-only + +### Requirement: Export to file for Copilot + +The system SHALL support exporting a summarized view of each backlog item in the current standup scope (current iteration/sprint, active/blocked/todo) to a file, formatted for use with Copilot slash-command interactive review during standup (e.g. summarize progress and next steps per story for team discussion). The export is complementary to the backlog; it does not replace the backlog or board. + +**Rationale**: Teams using Copilot during standup need a concise, file-based summary of each story so Copilot can assist with next steps and current progress; the file is for paste or reference in slash commands. + +#### Scenario: Copilot export writes summarized items to file + +**Given**: Backlog items in the current scope (same as standup: state, iteration/sprint, assignee, limit) and the user runs `specfact backlog daily --copilot-export <path>` (or equivalent) + +**When**: The command runs (with or without `--interactive`; export uses the same fetched list) + +**Then**: The system writes a file at the given path with one section per backlog item in scope + +**And**: Each section includes at least: ID, title, status, assignee(s), last updated, short progress summary (standup fields if present), blockers; optionally value score, priority, story points when available + +**And**: Format is Markdown with clear headings (e.g. `## <id> - <title>`) and bullet points for quick scanning and Copilot use + +**Acceptance Criteria**: + +- File is overwritten (idempotent write) or behavior is configurable +- Export builder has @icontract and @beartype where applicable +- When both `--interactive` and `--copilot-export` are given, export runs on the same fetched list (no requirement to re-fetch) + +### Requirement: Slash-command prompt for daily standup (specfact.backlog-daily) + +The system SHALL provide a prompt file (e.g. `resources/prompts/specfact.backlog-daily.md`) analogous to `specfact.backlog-refine.md`, so teams can run the daily standup flow interactively with the DevOps team via a slash command (e.g. `specfact.daily` or `specfact.backlog-daily`). The prompt SHALL instruct the AI to walk through stories story-by-story, explain and highlight current focus, surface found issues or open questions, and allow adding discussion notes as additional annotation comments on the issue (realistic daily standup scope). + +**Rationale**: Teams need a single, reusable prompt for IDE/Copilot that drives a structured standup review (refinement-style walkthrough with option to add discussion notes as comments), without duplicating instructions in each session. + +#### Scenario: Slash command invokes daily standup prompt + +**Given**: The user invokes the slash command (e.g. `/specfact.daily` or `/specfact.backlog-daily`) with optional adapter and filter arguments + +**When**: The prompt file is loaded and combined with the current context (e.g. CLI output or `--summarize` output) + +**Then**: The AI follows the prompt to present items story-by-story, highlight focus, issues, and open questions, and may suggest or add discussion notes as comments when the user approves + +**Acceptance Criteria**: + +- Prompt file exists under `resources/prompts/` and is documented (e.g. in tutorial and devops-adapter-integration) +- Prompt content aligns with interactive daily flow: story-by-story review, current focus, issues/open questions, discussion notes as comments +- Prompt can be used with `specfact backlog daily` output (e.g. `--copilot-export` or `--summarize`) as input context + +### Requirement: Standup summary prompt (--summarize) + +The system SHALL support a `--summarize` flag on `specfact backlog daily` that produces a **prompt** (instructions plus applied filters and filtered standup output) suitable for use in an interactive slash command (e.g. `specfact.daily`) or copy-paste to Copilot, so an LLM can generate a meaningful **summary of the daily standup status**. + +**Rationale**: Teams want one command that dumps the current standup view into a prompt-ready format, so Copilot or a slash command can then produce a short narrative summary (e.g. "Today's standup: 3 in progress, 1 blocked, 2 pending commitment …") without manually re-typing filters or data. + +#### Scenario: --summarize outputs prompt with filters and data + +**Given**: Backlog items in the current scope (same as standup: state, iteration/sprint, assignee, limit) and the user runs `specfact backlog daily --summarize` (stdout) or `--summarize-to <path>` (write to file) + +**When**: The command runs with the same filters as the standup view + +**Then**: The system outputs (to stdout or to the given path) a prompt that includes: (1) brief instruction that the following data is the current standup view and the LLM should generate a concise standup summary; (2) the applied filter context (adapter, state, sprint, assignee, limit); (3) per-item data including **body (description)** and **comments (annotations)** when available, plus ID, title, status, assignees, last updated, progress, blockers, optional value score, so the LLM can produce a **meaningful** summary + +**And**: The output is formatted so it can be pasted into Copilot or used as input to a slash command (e.g. `specfact.daily`) to produce a standup summary + +**Acceptance Criteria**: + +- `--summarize` uses the same fetched list and filters as the standup view (and as `--copilot-export`) +- Output includes filter context and per-item data; **per-item data SHALL include body (description)** and **comments (annotations)** when the adapter supports fetching comments, so the LLM can create a meaningful summary +- Format is prompt-ready (e.g. Markdown with clear "Generate a standup summary from the following" instruction) +- When `--summarize` or `--summarize-to` is used, the command outputs **only** the prompt (no standup tables) and then exits +- When `--summarize-to <path>` is given, write to file; when `--summarize` only is given, output to stdout +- When both `--summarize` and `--copilot-export` are given, both outputs can be produced from the same fetched list + +### Requirement: Project backlog context (no secrets) + +The system SHALL support storing project-level backlog context (org, project per adapter) in the repo so users do not have to pass adapter context (e.g. `--repo-owner`, `--repo-name`, `--ado-org`, `--ado-project`) every time after authenticating once. Context SHALL be stored in `.specfact/backlog.yaml` (or equivalent) and SHALL contain only non-sensitive identifiers (no tokens, no user names). Resolution order SHALL be: explicit CLI args > environment variables (e.g. `SPECFACT_GITHUB_REPO_OWNER`) > file. Tokens SHALL never be read from file. + +**Rationale**: After one-time authentication (tokens in env or keychain), teams want to set org/project once per repo so all backlog commands (daily, refine, sync bridge) work without repeating adapter options. + +#### Scenario: Adapter context from project config when not passed + +**Given**: A repo with `.specfact/backlog.yaml` containing e.g. `github.repo_owner`, `github.repo_name` (or `ado.org`, `ado.project`, `ado.team`) + +**When**: The user runs a backlog command (e.g. `specfact backlog daily github`) without passing `--repo-owner` or `--repo-name` + +**Then**: The system uses repo_owner and repo_name from the project config (or env) so the command succeeds without explicit options + +**And**: Explicit CLI options override config and env + +**Acceptance Criteria**: + +- File format supports per-adapter keys (e.g. `github.repo_owner`, `github.repo_name`; `ado.org`, `ado.project`, `ado.team`) +- Env overrides file (e.g. `SPECFACT_GITHUB_REPO_OWNER`, `SPECFACT_ADO_ORG`) +- Tokens are never read from file; only from CLI or env +- Config is loaded from `SPECFACT_CONFIG_DIR` or `.specfact/` in cwd; first found wins +- When org/repo or org/project are still missing after CLI, env, and file, the system MAY infer from `git remote get-url origin` when run from a clone (GitHub or Azure DevOps URL formats); supported ADO formats: HTTPS, SSH with keys (`git@ssh.dev.azure.com:v3/...`), SSH without keys (`user@dev.azure.com:v3/...`). If inference fails or not in a clone, the system SHALL report a clear error with guidance (CLI, env, or `.specfact/backlog.yaml`). diff --git a/openspec/changes/archive/2026-02-02-daily-standup-progress-support/tasks.md b/openspec/changes/archive/2026-02-02-daily-standup-progress-support/tasks.md new file mode 100644 index 00000000..aba8d166 --- /dev/null +++ b/openspec/changes/archive/2026-02-02-daily-standup-progress-support/tasks.md @@ -0,0 +1,177 @@ +# Tasks: Daily standup and progress support + +## TDD / SDD order (enforced) + +Per `openspec/config.yaml`, **tests before code** apply to any task that adds or changes behavior. + +1. **Spec deltas** define behavior (Given/When/Then) in `openspec/changes/daily-standup-progress-support/specs/daily-standup/spec.md`. +2. **Tests second**: Write unit/integration tests from those scenarios; run tests and **expect failure** (no implementation yet). +3. **Code last**: Implement until tests pass and behavior satisfies the spec. + +Do not implement production code for new behavior until the corresponding tests exist and have been run (expecting failure). + +--- + +## 1. Create git branch from dev + +- [x] 1.1 Ensure we're on dev and up to date: `git checkout dev && git pull origin dev` +- [x] 1.2 Create branch with Development link to issue (if exists): `gh issue develop <issue-number> --repo nold-ai/specfact-cli --name feature/daily-standup-progress-support --checkout` +- [x] 1.3 Or create branch without issue link: `git checkout -b feature/daily-standup-progress-support` (if no issue yet) +- [x] 1.4 Verify branch was created: `git branch --show-current` + +## 2. Create GitHub issue in nold-ai/specfact-cli (mandatory) + +- [x] 2.1 Create issue in nold-ai/specfact-cli: `gh issue create --repo nold-ai/specfact-cli --title "[Change] Daily standup and progress support" --body-file <path> --label "enhancement" --label "change-proposal"` +- [x] 2.2 Use body from proposal (Why, What Changes, Acceptance Criteria); add footer `*OpenSpec Change Proposal: daily-standup-progress-support*` +- [x] 2.3 Update `proposal.md` Source Tracking section with issue number, issue URL, repository nold-ai/specfact-cli, Last Synced Status: proposed +- [x] 2.4 Link issue to project (optional): `gh project item-add 1 --owner nold-ai --url <issue-url>` (requires `gh auth refresh -s project` if needed) + +## 3. Verify spec deltas (SDD: specs first) + +- [x] 3.1 Confirm `specs/daily-standup/spec.md` exists and is complete (ADDED requirements, Given/When/Then scenarios for standup view and post standup comment). +- [x] 3.2 Map scenarios to implementation: list my items with status/last activity, assignee filter, post standup comment via GitHub, adapter does not support comments. + +## 4. Tests first (TDD: write tests from spec scenarios; expect failure) + +- [x] 4.1 Write unit or integration tests from `specs/daily-standup/spec.md` scenarios: standup view lists items with status and last-updated; optional standup summary lines; assignee filter; post standup comment (mock adapter); adapter without comment support reports clearly. +- [x] 4.2 Run tests: `hatch run smart-test-unit` (or equivalent); **expect failure** (no implementation yet). +- [x] 4.3 Document which scenarios are covered by which test modules. + +## 5. Implement standup view and optional comment (TDD: code until tests pass) + +- [x] 5.1 Implement standup view: query change proposals and/or backlog items by assignee or filter; display item id, title, status, last-updated; optional standup fields (yesterday/today/blockers) when present in source. +- [x] 5.2 Expose via `specfact backlog daily` (backlog command group); keep scope minimal (read-only view from existing data). Do not add a top-level `specfact standup` command. +- [x] 5.3 Optional: implement post standup comment: when user opts in and adapter supports comments (e.g. GitHub), add comment to linked issue with standup text (format: Yesterday / Today / Blockers); use existing GitHub adapter comment API if available. +- [x] 5.4 When adapter does not support comments, report clearly; do not attempt to post. +- [x] 5.5 Add or extend bridge/adapters to support posting comment (e.g. GitHub issue comment); ensure @icontract and @beartype on new public APIs. +- [x] 5.6 Run tests again; **expect pass**; fix until all tests pass. + +## 6. Tests first (TDD): default scope, iteration/sprint, unassigned + +- [x] 6.1 Write tests from `specs/daily-standup/spec.md` for default standup scope: when no `--state`/`--limit` given, applied defaults exclude closed items and cap count; explicit options override defaults. +- [x] 6.2 Write tests for current iteration/sprint: when `--sprint current` or `--iteration current` is used and adapter supports it, only items in current sprint/iteration are listed; when unsupported, no crash and clear behavior. +- [x] 6.3 Write tests for unassigned items: standup view shows unassigned items in a separate table or section when enabled; `--unassigned-only` shows only unassigned; same scope (state, iteration) applies to unassigned. +- [x] 6.4 Write tests for sprint/iteration end date: when current sprint is in use and adapter or config provides end date, view displays it (e.g. "Sprint ends: DATE (N days)"); optional config fallback when adapter does not provide it. +- [x] 6.5 Write tests for blockers-first and optional priority: when `--blockers-first` or sort is used, items with non-empty blockers appear first; when config enables priority/value column and BacklogItem has it, column is shown. +- [x] 6.6 Run tests: `hatch run smart-test-unit` (or equivalent); **expect failure** (implementation not yet done). + +## 7. Implement: default scope, iteration/sprint, unassigned view (TDD: code until tests pass) + +- [x] 7.1 Implement default standup scope: read defaults from env (e.g. `SPECFACT_STANDUP_STATE`, `SPECFACT_STANDUP_LIMIT`) and/or optional `.specfact/standup.yaml`; apply default state (e.g. open), optional default assignee (me when resolvable), default limit (e.g. 20) when user does not pass options. CLI options override config/env. +- [x] 7.2 Add `--iteration` and `--sprint` to `specfact backlog daily`; pass through to `_fetch_backlog_items` and `_apply_filters`. When value is `current`, resolve via adapter when supported; otherwise use literal. Document which adapters support iteration/sprint. +- [x] 7.2a When `--sprint current` or config sprint is used, display sprint/iteration end date when provided by adapter or config (e.g. header line "Sprint ends: YYYY-MM-DD (N days)"); support optional config (e.g. `standup.sprint_end_date`) when adapter does not provide it; document adapter support. +- [x] 7.3 Implement unassigned items: after the main standup table, add a second table "Pending / open for commitment" with unassigned items in the same scope (state, iteration, sprint). Add option `--show-unassigned` (default true for standup) and `--unassigned-only` to show only unassigned. Omit unassigned section when none in scope. +- [x] 7.3a Optional: add `--blockers-first` or sort rows with non-empty blockers first so time-critical issues are visible at a glance; optional: show priority or value column when available on BacklogItem and enabled by config (for value-driven/SAFe teams). +- [x] 7.4 Run tests again; **expect pass**; fix until all pass. + +## 8. Quality gates + +- [x] 8.1 Run format and type-check: `hatch run format`, `hatch run type-check`. +- [x] 8.2 Run contract test: `hatch run contract-test`. +- [x] 8.3 Run full test suite: `hatch run smart-test` (or `hatch run smart-test-full`). +- [x] 8.4 Ensure any new or modified public APIs have @icontract and @beartype where applicable. + +## 9. Documentation research and review + +- [x] 9.1 Identify affected documentation: docs/guides/agile-scrum-workflows.md, docs/guides/devops-adapter-integration.md. +- [x] 9.2 Update agile-scrum-workflows.md: add section or subsection for daily standup with SpecFact (`specfact backlog daily` view, default scope, iteration/sprint, sprint end date, unassigned items, blockers-first, optional priority/value, Kanban vs Scrum/SAFe usage, optional post standup comment to linked issue). Note out-of-scope: sprint goal (see board/sprint settings), stale/at-risk flags (use last updated + blockers), structured blocked_by (free-text blockers only). +- [x] 9.3 Update devops-adapter-integration.md: document standup comment posting when using GitHub/ADO adapter; document standup config (env, standup.yaml), iteration/sprint and sprint end date support per adapter, and blockers-first/priority options. Note: sprint goal is in board/sprint settings; not displayed by CLI. +- [x] 9.4 If adding a new doc page: set front-matter (layout, title, permalink, description) and update docs/_layouts/default.html sidebar if needed. +- [x] 9.5 Add daily standup tutorial to docs: create docs/getting-started/tutorial-daily-standup-sprint-review.md; add link in docs/_layouts/default.html (Getting Started sidebar) and in docs/index.md (Quick Start and DevOps & Backlog Sync sections). + +## 10. Version and changelog (patch bump; required before PR) + +- [x] 10.1 Bump **patch** version in `pyproject.toml` (e.g. X.Y.Z → X.Y.(Z+1)). +- [x] 10.2 Sync version in `setup.py`, `src/__init__.py`, `src/specfact_cli/__init__.py` to match pyproject.toml. +- [x] 10.3 Add CHANGELOG.md entry under new [X.Y.Z] - YYYY-MM-DD section: **Added** – Daily standup defaults (state/limit/config), current iteration/sprint focus, unassigned/pending items view for commitment. + +## 11. Verify spec deltas (interactive selection and Copilot export) + +- [x] 11.1 Confirm `specs/daily-standup/spec.md` includes ADDED requirements and Given/When/Then scenarios for interactive step-by-step review (selection UI, detail view, navigation, optional next-best suggestion and sprint goal hint) and for export to file for Copilot. +- [x] 11.2 Map scenarios to implementation: interactive questionary selection, refine-like detail + comments, next/previous/back/exit; Copilot export same scope, Markdown sections per item, idempotent write. + +## 12. Tests first (TDD): interactive selection and Copilot export + +- [x] 12.1 Write unit or integration tests from `specs/daily-standup/spec.md` for interactive mode: when `--interactive` is used, items are presented (e.g. questionary or mock); selecting an item shows detail (refine-like, comments when adapter supports); navigation choices (next/previous/back/exit) behave as specified. +- [x] 12.2 Write tests for Copilot export: when `--copilot-export <path>` is used, file is written with one section per item, Markdown headings and bullets, same scope as daily; idempotent overwrite. +- [x] 12.3 Write tests for optional value score and next-best suggestion: when story_points, business_value, priority are available and suggestion enabled, value score is computed (e.g. business_value / max(1, story_points * priority)); when data missing, score omitted. +- [x] 12.4 Run tests: `hatch run smart-test-unit` (or equivalent); **expect failure** (no implementation yet). +- [x] 12.5 Document which spec scenarios are covered by which test modules. + +## 13. Implement interactive selection and Copilot export (TDD: code until tests pass) + +- [x] 13.1 Implement interactive mode: add `--interactive` to `specfact backlog daily`; after fetching items (same scope as daily), present questionary.select (or equivalent) with one choice per item (e.g. `{id} - {title} [{status}] ({assignee})`); on selection, display full detail (reuse refine output or build same content: description, acceptance criteria, standup fields, comments via adapter when available); highlight blocked when blockers non-empty. +- [x] 13.2 Implement navigation: after detail view, present choices "Next story", "Previous story", "Back to list", "Exit"; next/previous use current list index without re-fetch; back returns to item selector; exit ends command. +- [x] 13.3 Optional: implement next-best-item suggestion (config or `--suggest-next`): compute value_score = business_value / max(1, story_points * priority) for pending items; show "Suggested next: …" in interactive view when enabled; optional sprint goal hint when adapter/config provides it. +- [x] 13.4 Implement Copilot export: add `--copilot-export <path>` to `specfact backlog daily`; build Markdown content (one section per item: ID, title, status, assignees, last updated, progress summary, blockers, optional value score); write to path (overwrite); use same fetched list when combined with `--interactive`. +- [x] 13.5 Ensure new public helpers (interactive walkthrough, detail renderer, export builder) have @icontract and @beartype. +- [x] 13.6 Run tests again; **expect pass**; fix until all pass. + +## 14. Quality gates (interactive and export) + +- [x] 14.1 Run format and type-check: `hatch run format`, `hatch run type-check`. +- [x] 14.2 Run contract test: `hatch run contract-test`. +- [x] 14.3 Run full test suite: `hatch run smart-test` (or `hatch run smart-test-full`). +- [x] 14.4 Ensure any new public APIs have @icontract and @beartype where applicable. + +## 15. Documentation (interactive and Copilot export) + +- [x] 15.1 Identify affected documentation: docs/guides/agile-scrum-workflows.md, docs/guides/devops-adapter-integration.md. +- [x] 15.2 Update agile-scrum-workflows.md: add subsection for interactive step-by-step review (`--interactive`, questionary selection, detail view, navigation, optional next-best suggestion and sprint goal hint) and for Copilot export (`--copilot-export <path>`, format, use with Copilot slash command during standup). Note: complementary aid, not replacement for backlog. +- [x] 15.3 Update devops-adapter-integration.md: document comment fetch for interactive detail view; document which adapters provide story_points, business_value, priority, sprint goal for value score and suggestions. +- [x] 15.4 If adding a new doc page: set front-matter and update docs/_layouts/default.html sidebar if needed. Daily standup tutorial added to sidebar and docs/index.md (see 9.5). +- [x] 15.5 Document ADO git remote formats in docs and code: SSH key auth uses `git@ssh.dev.azure.com:v3/...`; other SSH auth uses `user@dev.azure.com:v3/...` (no `ssh.` subdomain). Updated devops-adapter-integration.md, agile-scrum-workflows.md, tutorial-daily-standup-sprint-review.md, tutorial-backlog-refine-ai-ide.md, and docstring in backlog_commands.py. + +## 16. Version and changelog (patch bump for interactive and export) + +- [x] 16.1 Bump **patch** version in `pyproject.toml` (e.g. X.Y.Z → X.Y.(Z+1)). (Skipped: use existing 0.26.16 per user request.) +- [x] 16.2 Sync version in `setup.py`, `src/__init__.py`, `src/specfact_cli/__init__.py` to match pyproject.toml. +- [x] 16.3 Add CHANGELOG.md entry under new [X.Y.Z] - YYYY-MM-DD section: **Added** – Interactive step-by-step review (`specfact backlog daily --interactive`) with arrow-key selection and refine-like detail including comments; export to file for Copilot (`--copilot-export <path>`); optional next-best-item suggestion (value score) and sprint goal hint. + +## 17. Verify spec deltas (project backlog context) + +- [x] 17.1 Confirm `specs/daily-standup/spec.md` includes ADDED requirement and Given/When/Then for project backlog context (`.specfact/backlog.yaml`, org/project per adapter, no secrets, resolution order CLI > env > file). +- [x] 17.2 Map scenarios to implementation: `_load_backlog_config()`, `_build_adapter_kwargs()` merge with config and env. + +## 18. Tests first (TDD): project backlog context + +- [x] 18.1 Write unit tests for `_load_backlog_config()`: when `.specfact/backlog.yaml` exists with `github.repo_owner`/`repo_name` or `ado.org`/`project`/`team`, config is returned; when file missing or empty, empty dict; when file has top-level `backlog` key, nested structure is used. +- [x] 18.2 Write unit tests for `_build_adapter_kwargs()`: when CLI args are None, values are taken from config then env (e.g. `SPECFACT_GITHUB_REPO_OWNER`); when CLI args are set, they override; tokens are never read from config. +- [x] 18.3 Run tests: `hatch run smart-test-unit` (or equivalent); **expect failure** (implementation not yet done). + +## 19. Implement project backlog context (TDD: code until tests pass) + +- [x] 19.1 Implement `_load_backlog_config()`: same search path as standup (SPECFACT_CONFIG_DIR, .specfact); read `.specfact/backlog.yaml`; return dict with keys e.g. `github`, `ado` and nested repo_owner/repo_name or org/project/team; no tokens or users in file. +- [x] 19.2 Update `_build_adapter_kwargs()`: load backlog config; for each adapter, resolve org/project/repo_owner/repo_name/team in order: explicit arg > env (SPECFACT_GITHUB_REPO_OWNER, etc.) > config; pass resolved values into kwargs; tokens only from args (env handled by caller). +- [x] 19.3 Run tests again; **expect pass**; fix until all pass. +- [x] 19.4 Document `.specfact/backlog.yaml` format and env vars in devops-adapter-integration.md. + +## 20. Verify spec deltas (comments, backlog-daily prompt, --summarize) + +- [x] 20.1 Confirm spec explicitly states that interactive detail view shows **comments** annotated to each issue (adapter-supported); document in tutorial. +- [x] 20.2 Confirm `specs/daily-standup/spec.md` includes ADDED requirements: slash-command prompt `specfact.backlog-daily.md` (story-by-story walkthrough, focus, issues/open questions, discussion notes as comments) and `--summarize` (prompt with filters + filtered output for LLM standup summary). +- [x] 20.3 Map scenarios to implementation: prompt file under `resources/prompts/specfact.backlog-daily.md`; `--summarize` (stdout) and `--summarize-to <path>` on `backlog daily` outputting prompt (instruction + filter context + per-item data). + +## 21. Tests first (TDD): backlog-daily prompt and --summarize + +- [x] 21.1 Write unit tests for `--summarize`: when `--summarize` is used, output (stdout or file) contains filter context (adapter, state, sprint, assignee, limit) and per-item data consistent with copilot-export; when path given (--summarize-to), file is written; idempotent overwrite. +- [x] 21.2 Write test that prompt file `resources/prompts/specfact.backlog-daily.md` exists and contains expected sections (purpose, parameters, workflow for story-by-story review, discussion notes as comments). +- [x] 21.3 Run tests: `hatch run smart-test-unit` (or equivalent); expect pass after implementation. + +## 22. Implement backlog-daily prompt and --summarize (TDD: code until tests pass) + +- [x] 22.1 Create `resources/prompts/specfact.backlog-daily.md`: structure analogous to `specfact.backlog-refine.md`; purpose = daily standup interactive walkthrough with DevOps team; story-by-story review, highlight current focus, surface issues/open questions, allow adding discussion notes as annotation comments; reference `specfact backlog daily` and options (--interactive, --copilot-export, --summarize, --summarize-to). +- [x] 22.2 Add `--summarize` (stdout) and `--summarize-to PATH` to `specfact backlog daily`: when set, build prompt content (instruction to generate standup summary + filter context + same per-item data as copilot-export); use same fetched list as standup view. +- [x] 22.3 Run tests again; expect pass; fix until all pass. + +## 23. Documentation (comments, prompt, --summarize) + +- [x] 23.1 Update tutorial-daily-standup-sprint-review.md: state explicitly that **interactive detail view shows comments** on each issue (when adapter supports it); add step or note for `--summarize` (prompt for slash command / Copilot to generate standup summary); mention `specfact.backlog-daily` slash prompt. +- [x] 23.2 Update agile-scrum-workflows.md and devops-adapter-integration.md: document that interactive daily shows issue comments; document `specfact.backlog-daily` prompt and `--summarize`/`--summarize-to` flag. + +## 24. Create Pull Request to dev + +- [x] 24.1 Ensure all changes are committed: `git add .` and `git commit -m "feat(backlog): daily standup defaults, iteration/sprint, unassigned items view"` +- [x] 24.2 Push to remote: `git push origin feature/daily-standup-progress-support` +- [x] 24.3 Create PR: `gh pr create --repo nold-ai/specfact-cli --base dev --head feature/daily-standup-progress-support --title "feat(backlog): daily standup defaults, iteration/sprint, unassigned items view" --body-file <path>` (use repo PR template; add OpenSpec change ID `daily-standup-progress-support` and summary; reference GitHub issue with `Fixes nold-ai/specfact-cli#168`). **Created**: <https://github.com/nold-ai/specfact-cli/pull/174> +- [x] 24.4 Verify PR and branch are linked to issue in Development section. diff --git a/openspec/changes/daily-standup-exceptions-first/CHANGE_VALIDATION.md b/openspec/changes/daily-standup-exceptions-first/CHANGE_VALIDATION.md new file mode 100644 index 00000000..7b5ec68b --- /dev/null +++ b/openspec/changes/daily-standup-exceptions-first/CHANGE_VALIDATION.md @@ -0,0 +1,28 @@ +# Change Validation Report: daily-standup-exceptions-first + +**Validation Date**: 2026-02-02 +**Plan Reference**: specfact-cli-internal/docs/internal/implementation/2026-02-01-backlog-changes-improvement.md (E1) +**Validation Method**: Plan alignment + OpenSpec strict validation + +## Executive Summary + +- **Plan Delta (E1)**: New change extending archived daily-standup-progress-support with exceptions-first section order, `--mode scrum|kanban|safe`, patch integration. +- **Breaking Changes**: 0 (additive; extends archived standup). +- **Validation Result**: Pass. +- **OpenSpec Validation**: `openspec validate daily-standup-exceptions-first --strict` — valid. + +## Alignment with Plan E1 + +- **E1**: Extend daily-standup to exceptions-first + flow/policy hooks. **Done**: New change proposal with default section order (blockers → policy failures → aging → normal), `--mode scrum|kanban|safe`, patch hook; acceptance: `backlog daily` includes "Exceptions" section by default. + +## USP / Value-Add + +- **Exceptions-first UX**: Plan guiding principle—default outputs highlight blockers/risks before normal status. +- **Ceremony-native**: Mode switch supports Scrum/Kanban/SAFe without rewriting configs (“Loved” metric). +- **Actionable**: Patch integration for standup notes (patch-mode-preview-apply). + +## Format Validation + +- proposal.md: Why, What Changes, Capabilities, Impact, Source Tracking present. +- specs/daily-standup/spec.md: Given/When/Then for exceptions-first order, mode, patch. +- tasks.md: TDD/SDD order section; branch first, PR last; format OK. diff --git a/openspec/changes/daily-standup-exceptions-first/proposal.md b/openspec/changes/daily-standup-exceptions-first/proposal.md new file mode 100644 index 00000000..d80b330b --- /dev/null +++ b/openspec/changes/daily-standup-exceptions-first/proposal.md @@ -0,0 +1,31 @@ +# Change: Daily standup exceptions-first and flow/policy hooks (E1 delta) + +## Why + +The archived change `daily-standup-progress-support` (#168) delivers standup view, interactive review, and Copilot export. Teams love tools that surface blockers and risks first. This delta extends standup with exceptions-first default section order, optional `--mode scrum|kanban|safe`, and integration with Policy Engine and patch mode so standup output highlights policy failures and aging/stalled work before normal status. + +## What Changes + +- **EXTEND** (plan E1): Default section order for `specfact backlog daily`: (1) blockers and dependency-critical items, (2) policy failures (DoR/DoD/flow), (3) aging items / stalled work (when data exists), (4) normal status. +- **NEW**: Add `--mode scrum|kanban|safe` to change defaults for filters and sections. +- **EXTEND**: Integrate `--patch` (patch-mode-preview-apply) to propose standup notes or missing fields as patch. +- **EXTEND**: Documentation (agile-scrum-workflows, devops-adapter-integration) for exceptions-first standup and mode switch. + +## Capabilities + +- **daily-standup-exceptions**: Exceptions-first section order (blockers, policy failures, aging, normal); `--mode scrum|kanban|safe`; optional patch integration for standup notes. + +## Impact + +- **Affected specs**: New `openspec/changes/daily-standup-exceptions-first/specs/daily-standup/spec.md` (delta on daily-standup; Given/When/Then for exceptions-first order, mode, patch). +- **Affected code**: `src/specfact_cli/commands/backlog_commands.py` (daily command: section order, --mode, patch hook); depends on unify-policies-engine and patch-mode-preview-apply when available. +- **Affected documentation** (<https://docs.specfact.io>): docs/guides/agile-scrum-workflows.md, docs/guides/devops-adapter-integration.md. +- **Integration points**: Archived daily-standup-progress-support; unify-policies-engine (Δ1); patch-mode-preview-apply (Δ2). +- **Backward compatibility**: Additive; default section order becomes exceptions-first when policy/flow data exists; existing standup UX unchanged otherwise. + +## Source Tracking + +- **GitHub Issue**: #175 +- **Issue URL**: <https://github.com/nold-ai/specfact-cli/issues/175> +- **Repository**: nold-ai/specfact-cli +- **Last Synced Status**: proposed diff --git a/openspec/changes/daily-standup-exceptions-first/specs/daily-standup/spec.md b/openspec/changes/daily-standup-exceptions-first/specs/daily-standup/spec.md new file mode 100644 index 00000000..cc230f59 --- /dev/null +++ b/openspec/changes/daily-standup-exceptions-first/specs/daily-standup/spec.md @@ -0,0 +1,59 @@ +# Daily standup exceptions-first (E1 delta) + +## ADDED Requirements + +Delta on top of archived `daily-standup-progress-support`; extends `specfact backlog daily` with exceptions-first order and mode. + +### Requirement: Exceptions-first section order + +The system SHALL order `specfact backlog daily` output sections by default as: (1) blockers and dependency-critical items, (2) policy failures (DoR/DoD/flow when Policy Engine available), (3) aging items / stalled work (when data exists), (4) normal status. + +**Rationale**: Plan E1—teams see risks first. + +#### Scenario: Standup output shows exceptions first + +**Given**: Policy Engine (unify-policies-engine) and/or aging/flow data are available + +**When**: The user runs `specfact backlog daily` (no override) + +**Then**: The output includes an "Exceptions" section by default (blockers, policy failures, aging/stalled when available) before normal status + +**Acceptance Criteria**: + +- `backlog daily` includes an "Exceptions" section by default when exception data exists. + +### Requirement: Mode switch (scrum|kanban|safe) + +The system SHALL support `--mode scrum|kanban|safe` to change defaults for filters and sections (e.g. Kanban: flow columns; SAFe: PI context). + +**Rationale**: Plan E1—ceremony-native defaults per framework. + +#### Scenario: Standup with mode + +**Given**: SpecFact CLI and backlog adapter + +**When**: The user runs `specfact backlog daily --mode kanban` + +**Then**: Default filters and section behavior align with Kanban (e.g. flow-focused); when `--mode safe`, PI context when available + +**Acceptance Criteria**: + +- `--mode scrum|kanban|safe` changes defaults; existing backlog daily behavior otherwise unchanged. + +### Requirement: Patch integration for standup notes + +The system SHALL integrate with patch mode (patch-mode-preview-apply) to propose standup notes or missing fields as patch when `--patch` is used. + +**Rationale**: Plan E1—actionable standup output. + +#### Scenario: Standup with patch proposal + +**Given**: Patch mode is available + +**When**: The user runs `specfact backlog daily --patch` + +**Then**: The command may emit a patch proposal (standup notes or missing fields) for user review/apply + +**Acceptance Criteria**: + +- When patch mode is available and `--patch` is set, standup can propose patch; no silent writes. diff --git a/openspec/changes/daily-standup-exceptions-first/tasks.md b/openspec/changes/daily-standup-exceptions-first/tasks.md new file mode 100644 index 00000000..2b36fa1d --- /dev/null +++ b/openspec/changes/daily-standup-exceptions-first/tasks.md @@ -0,0 +1,41 @@ +# Tasks: Daily standup exceptions-first (E1 delta) + +## TDD / SDD order (enforced) + +Per `openspec/config.yaml`, **tests before code** apply to any task that adds or changes behavior. + +1. **Spec deltas** define behavior in `openspec/changes/daily-standup-exceptions-first/specs/daily-standup/spec.md`. +2. **Tests second**: Write unit/integration tests from those scenarios; run tests and **expect failure**. +3. **Code last**: Implement until tests pass. + +--- + +## 1. Create git branch from dev + +- [ ] 1.1 Ensure we're on dev and up to date: `git checkout dev && git pull origin dev` +- [ ] 1.2 Create branch: `git checkout -b feature/daily-standup-exceptions-first` +- [ ] 1.3 Verify branch: `git branch --show-current` + +## 2. Tests first (exceptions-first order, --mode, patch hook) + +- [ ] 2.1 Write tests from spec: exceptions-first section order, --mode scrum|kanban|safe, patch hook when available. +- [ ] 2.2 Run tests: `hatch run smart-test-unit`; **expect failure**. + +## 3. Implement exceptions-first and mode + +- [ ] 3.1 Implement default section order: blockers → policy failures → aging → normal (when data available). +- [ ] 3.2 Add `--mode scrum|kanban|safe` to `specfact backlog daily`; adjust defaults per mode. +- [ ] 3.3 Integrate patch hook when patch-mode-preview-apply available and `--patch` set. +- [ ] 3.4 Run tests; **expect pass**. + +## 4. Quality gates and documentation + +- [ ] 4.1 Run format and type-check: `hatch run format`, `hatch run type-check`. +- [ ] 4.2 Run contract test: `hatch run contract-test`. +- [ ] 4.3 Update docs: agile-scrum-workflows.md, devops-adapter-integration.md (exceptions-first, --mode). +- [ ] 4.4 Add CHANGELOG entry; sync version. + +## 5. Create Pull Request to dev + +- [ ] 5.1 Commit and push: `git add .` then `git commit -m "feat(backlog): daily standup exceptions-first and --mode scrum|kanban|safe"` and `git push origin feature/daily-standup-exceptions-first` +- [ ] 5.2 Create PR to dev using repo PR template; reference this change ID. diff --git a/openspec/changes/daily-standup-progress-support/design.md b/openspec/changes/daily-standup-progress-support/design.md deleted file mode 100644 index ee54b289..00000000 --- a/openspec/changes/daily-standup-progress-support/design.md +++ /dev/null @@ -1,32 +0,0 @@ -# Design: Daily standup and progress support - -## Bridge adapter integration - -- **Standup view**: Reads from existing OpenSpec change proposals and/or backlog adapter data (same sources as `specfact sync bridge` and backlog commands). No new adapter contract; reuse existing list/fetch APIs. -- **Post standup comment**: When user opts in, use existing GitHub (and optionally ADO) adapter to add a comment to the linked issue. GitHub: use Issues API `POST /repos/{owner}/{repo}/issues/{issue_number}/comments` with standup body. ADO: use Work Item Comments API if available; otherwise document as future extension. -- **Adapter capability**: Adapters that support "post comment" expose a method (e.g. `post_comment(issue_id, body)`) or equivalent; standup command calls it when adapter supports it and user opts in. Read-only or unsupported adapters return a clear "not supported" so the CLI does not attempt to post. - -## Sequence (post standup comment) - -``` -User → specfact standup --post-standup --standup-text "Yesterday: X. Today: Y. Blockers: Z" - → CLI resolves change proposal → Source Tracking → issue number + repo - → CLI gets adapter for repo (e.g. GitHub) - → If adapter supports post_comment: adapter.post_comment(issue_number, formatted_body) - → GitHub API: POST .../issues/{n}/comments - → CLI reports success or failure -``` - -## Contract enforcement - -- New public functions (e.g. standup view builder, comment poster) shall have @icontract and @beartype. -- Adapter interface extension (post_comment) optional with default "not supported" to keep backward compatibility. - -## Fallback / offline - -- Standup view is read-only from local/cached data; no network required for view-only. -- Post comment requires network and auth; failure (rate limit, auth) is reported; no silent swallow. - -## Alignment with existing sync bridge - -- Existing `specfact sync bridge --add-progress-comment` and `--track-code-changes` already add progress comments to GitHub issues. Standup comment can reuse or extend that path (e.g. standup format as a variant of progress comment) to avoid duplicate comment-posting logic. Standup/progress is exposed under the backlog command group as `specfact backlog daily` (no top-level standup or scrum command). diff --git a/openspec/changes/daily-standup-progress-support/proposal.md b/openspec/changes/daily-standup-progress-support/proposal.md deleted file mode 100644 index 318dabb9..00000000 --- a/openspec/changes/daily-standup-progress-support/proposal.md +++ /dev/null @@ -1,31 +0,0 @@ -# Change: Daily standup and progress support - -## Why - -Bridge comments and sync already support exporting/updating change proposals and issues. For daily standup there is no structured "standup" view that aggregates my items, recent activity, and blockers; progress/standup notes are not first-class (e.g. yesterday/today/blockers format) that could be pushed to issue comments. Teams duplicate standup info in tools; SpecFact can surface progress from OpenSpec/bridge and optionally publish to GitHub/ADO so standup updates are visible where the team works. - -## What Changes - -- **NEW**: Add a lightweight standup/progress view under the backlog command group: list my change proposals or backlog items (by assignee or filter), with last-updated and status; optional one-line summary for yesterday/today/blockers from proposal or linked issue body. Expose as `specfact backlog daily` (no top-level `specfact standup`). -- **NEW**: Optional mode to post standup summary as a comment on linked issues via `specfact backlog daily` (e.g. `--post`) or reuse of `specfact sync bridge --add-progress-comment` with standup format (e.g. GitHub issue comment). -- **EXTEND**: Bridge/adapters: support posting comment to linked issue when adapter supports it (e.g. GitHub). -- **EXTEND**: Documentation (agile-scrum-workflows, devops-adapter-integration) for daily standup with SpecFact. - -## Capabilities - -- **daily-standup**: Standup view (list my/filtered items with status and last activity; optional standup summary lines) and optional post standup comment to linked issue via adapter. - -## Impact - -- **Affected specs**: New `openspec/changes/daily-standup-progress-support/specs/daily-standup/spec.md` (Given/When/Then for standup view and comment). -- **Affected code**: `src/specfact_cli/commands/` (extend backlog command group with `backlog daily` subcommand for standup view and optional comment post); bridge/adapters extended to post comment when supported (e.g. GitHub). -- **Affected documentation** (<https://docs.specfact.io>): docs/guides/agile-scrum-workflows.md, docs/guides/devops-adapter-integration.md for daily standup workflow. -- **Integration points**: Existing `specfact sync bridge`, GitHub/ADO adapters; OpenSpec change proposals and backlog items. -- **Backward compatibility**: Additive only; existing sync/bridge behavior unchanged unless user opts into standup view or comment post. - -## Source Tracking - -- **GitHub Issue**: #168 -- **Issue URL**: <https://github.com/nold-ai/specfact-cli/issues/168> -- **Repository**: nold-ai/specfact-cli -- **Last Synced Status**: proposed diff --git a/openspec/changes/daily-standup-progress-support/specs/daily-standup/spec.md b/openspec/changes/daily-standup-progress-support/specs/daily-standup/spec.md deleted file mode 100644 index 232b9420..00000000 --- a/openspec/changes/daily-standup-progress-support/specs/daily-standup/spec.md +++ /dev/null @@ -1,71 +0,0 @@ -# Daily Standup - -## ADDED Requirements - -### Requirement: Standup view - -The system SHALL provide a standup or progress view that lists change proposals or backlog items (by assignee or filter) with last-updated and status, and optional one-line summary for yesterday/today/blockers. - -**Rationale**: Teams need a single place to see "my items" and recent activity for daily standup without duplicating data in multiple tools. - -#### Scenario: List my items with status and last activity - -**Given**: A user has change proposals or backlog items assigned to them (or a filter is applied) - -**When**: The user runs the standup view (e.g. `specfact backlog daily` or equivalent under the backlog command group) - -**Then**: The system lists items (change proposal id or backlog item id, title, status, last-updated) for the user or filter - -**And**: Optional standup summary lines (yesterday/today/blockers) are shown when available from proposal or linked issue body - -**Acceptance Criteria**: - -- Output is readable (e.g. table or structured list) -- Last-updated is displayed per item -- Optional standup fields (yesterday, today, blockers) shown when present in source data - -#### Scenario: Standup view with assignee filter - -**Given**: A repo with multiple change proposals or backlog items and assignee metadata - -**When**: The user runs standup view with assignee filter (e.g. `--assignee me` or current user) - -**Then**: Only items matching the assignee are listed - -**And**: If no assignee filter is applied, all items (or default scope) are listed per command contract - -### Requirement: Post standup comment to linked issue - -The system SHALL support posting a standup summary as a comment on the linked issue (e.g. GitHub issue comment) when the user opts in and the adapter supports it. - -**Rationale**: Standup updates should be visible in the DevOps backend (GitHub, ADO) so the team sees progress where they work. - -#### Scenario: Post standup comment via GitHub adapter - -**Given**: A change proposal with Source Tracking linking to a GitHub issue (e.g. nold-ai/specfact-cli#N) - -**And**: The user has provided standup text (yesterday/today/blockers format) and opts to post (e.g. `specfact backlog daily --post` or equivalent) - -**When**: The user runs `specfact backlog daily --post` (or equivalent) and GitHub adapter is configured - -**Then**: The system adds a comment to the linked GitHub issue with the standup text (format: Yesterday / Today / Blockers or team-defined format) - -**And**: The comment is clearly identifiable (e.g. "Standup YYYY-MM-DD" or configurable prefix) - -**Acceptance Criteria**: - -- Comment is posted only when user opts in and adapter supports comments -- Format is configurable or follows a simple standard (yesterday, today, blockers) -- Failure to post (e.g. auth, rate limit) is reported clearly; no silent swallow - -#### Scenario: Post standup when adapter does not support comments - -**Given**: An adapter that does not support posting comments (e.g. read-only or no comment API) - -**When**: The user runs `specfact backlog daily --post` (or equivalent) - -**Then**: The system reports that posting is not supported for this adapter and does not attempt to post - -**Acceptance Criteria**: - -- Clear message; no crash or undefined behavior diff --git a/openspec/changes/daily-standup-progress-support/tasks.md b/openspec/changes/daily-standup-progress-support/tasks.md deleted file mode 100644 index bddc59ab..00000000 --- a/openspec/changes/daily-standup-progress-support/tasks.md +++ /dev/null @@ -1,74 +0,0 @@ -# Tasks: Daily standup and progress support - -## TDD / SDD order (enforced) - -Per `openspec/config.yaml`, **tests before code** apply to any task that adds or changes behavior. - -1. **Spec deltas** define behavior (Given/When/Then) in `openspec/changes/daily-standup-progress-support/specs/daily-standup/spec.md`. -2. **Tests second**: Write unit/integration tests from those scenarios; run tests and **expect failure** (no implementation yet). -3. **Code last**: Implement until tests pass and behavior satisfies the spec. - -Do not implement production code for new behavior until the corresponding tests exist and have been run (expecting failure). - ---- - -## 1. Create git branch from dev - -- [ ] 1.1 Ensure we're on dev and up to date: `git checkout dev && git pull origin dev` -- [ ] 1.2 Create branch with Development link to issue (if exists): `gh issue develop <issue-number> --repo nold-ai/specfact-cli --name feature/daily-standup-progress-support --checkout` -- [ ] 1.3 Or create branch without issue link: `git checkout -b feature/daily-standup-progress-support` (if no issue yet) -- [ ] 1.4 Verify branch was created: `git branch --show-current` - -## 2. Create GitHub issue in nold-ai/specfact-cli (mandatory) - -- [ ] 2.1 Create issue in nold-ai/specfact-cli: `gh issue create --repo nold-ai/specfact-cli --title "[Change] Daily standup and progress support" --body-file <path> --label "enhancement" --label "change-proposal"` -- [ ] 2.2 Use body from proposal (Why, What Changes, Acceptance Criteria); add footer `*OpenSpec Change Proposal: daily-standup-progress-support*` -- [ ] 2.3 Update `proposal.md` Source Tracking section with issue number, issue URL, repository nold-ai/specfact-cli, Last Synced Status: proposed -- [ ] 2.4 Link issue to project (optional): `gh project item-add 1 --owner nold-ai --url <issue-url>` (requires `gh auth refresh -s project` if needed) - -## 3. Verify spec deltas (SDD: specs first) - -- [ ] 3.1 Confirm `specs/daily-standup/spec.md` exists and is complete (ADDED requirements, Given/When/Then scenarios for standup view and post standup comment). -- [ ] 3.2 Map scenarios to implementation: list my items with status/last activity, assignee filter, post standup comment via GitHub, adapter does not support comments. - -## 4. Tests first (TDD: write tests from spec scenarios; expect failure) - -- [ ] 4.1 Write unit or integration tests from `specs/daily-standup/spec.md` scenarios: standup view lists items with status and last-updated; optional standup summary lines; assignee filter; post standup comment (mock adapter); adapter without comment support reports clearly. -- [ ] 4.2 Run tests: `hatch run smart-test-unit` (or equivalent); **expect failure** (no implementation yet). -- [ ] 4.3 Document which scenarios are covered by which test modules. - -## 5. Implement standup view and optional comment (TDD: code until tests pass) - -- [ ] 5.1 Implement standup view: query change proposals and/or backlog items by assignee or filter; display item id, title, status, last-updated; optional standup fields (yesterday/today/blockers) when present in source. -- [ ] 5.2 Expose via `specfact backlog daily` (backlog command group); keep scope minimal (read-only view from existing data). Do not add a top-level `specfact standup` command. -- [ ] 5.3 Optional: implement post standup comment: when user opts in and adapter supports comments (e.g. GitHub), add comment to linked issue with standup text (format: Yesterday / Today / Blockers); use existing GitHub adapter comment API if available. -- [ ] 5.4 When adapter does not support comments, report clearly; do not attempt to post. -- [ ] 5.5 Add or extend bridge/adapters to support posting comment (e.g. GitHub issue comment); ensure @icontract and @beartype on new public APIs. -- [ ] 5.6 Run tests again; **expect pass**; fix until all tests pass. - -## 6. Quality gates - -- [ ] 6.1 Run format and type-check: `hatch run format`, `hatch run type-check`. -- [ ] 6.2 Run contract test: `hatch run contract-test`. -- [ ] 6.3 Run full test suite: `hatch run smart-test` (or `hatch run smart-test-full`). -- [ ] 6.4 Ensure any new or modified public APIs have @icontract and @beartype where applicable. - -## 7. Documentation research and review - -- [ ] 7.1 Identify affected documentation: docs/guides/agile-scrum-workflows.md, docs/guides/devops-adapter-integration.md. -- [ ] 7.2 Update agile-scrum-workflows.md: add section or subsection for daily standup with SpecFact (`specfact backlog daily` view, optional post standup comment to linked issue). -- [ ] 7.3 Update devops-adapter-integration.md: document standup comment posting when using GitHub/ADO adapter (if implemented). -- [ ] 7.4 If adding a new doc page: set front-matter (layout, title, permalink, description) and update docs/_layouts/default.html sidebar if needed. - -## 8. Version and changelog (patch bump; required before PR) - -- [ ] 8.1 Bump **patch** version in `pyproject.toml` (e.g. X.Y.Z → X.Y.(Z+1)). -- [ ] 8.2 Sync version in `setup.py`, `src/__init__.py`, `src/specfact_cli/__init__.py` to match pyproject.toml. -- [ ] 8.3 Add CHANGELOG.md entry under new [X.Y.Z] - YYYY-MM-DD section: **Added** – Daily standup and progress support (standup view, optional post standup comment to linked issue). - -## 9. Create Pull Request to dev - -- [ ] 9.1 Ensure all changes are committed: `git add .` and `git commit -m "feat(backlog): add backlog daily for standup view and optional comment post"` -- [ ] 9.2 Push to remote: `git push origin feature/daily-standup-progress-support` -- [ ] 9.3 Create PR: `gh pr create --repo nold-ai/specfact-cli --base dev --head feature/daily-standup-progress-support --title "feat(backlog): add backlog daily for standup view and optional comment post" --body-file <path>` (use repo PR template; add OpenSpec change ID `daily-standup-progress-support` and summary; reference GitHub issue with `Fixes nold-ai/specfact-cli#<issue-number>`). -- [ ] 9.4 Verify PR and branch are linked to issue in Development section. diff --git a/openspec/changes/patch-mode-preview-apply/CHANGE_VALIDATION.md b/openspec/changes/patch-mode-preview-apply/CHANGE_VALIDATION.md new file mode 100644 index 00000000..192ca585 --- /dev/null +++ b/openspec/changes/patch-mode-preview-apply/CHANGE_VALIDATION.md @@ -0,0 +1,28 @@ +# Change Validation Report: patch-mode-preview-apply + +**Validation Date**: 2026-02-02 +**Plan Reference**: specfact-cli-internal/docs/internal/implementation/2026-02-01-backlog-changes-improvement.md (Δ2) +**Validation Method**: Plan alignment + OpenSpec strict validation + +## Executive Summary + +- **Plan Delta (Δ2)**: Patch pipeline for backlog/spec/config edits; generate-only by default; `--apply` (local), `--write` (upstream) with explicit confirmation; idempotent posts. +- **Breaking Changes**: 0 (new capability). +- **Validation Result**: Pass. +- **OpenSpec Validation**: `openspec validate patch-mode-preview-apply --strict` — valid. + +## Alignment with Plan Δ2 + +- **Δ2**: Patch mode (previewable, confirmable). **Done**: proposal and spec define backlog refine --patch (emit file/summary), patch apply <file> (local + preflight), patch apply --write (confirmation, idempotent); zero accidental writes. + +## USP / Value-Add + +- **Trust by design**: Plan guiding principle—any write requires explicit `--write` + preview/diff and idempotency. +- **Actionable**: “>80% of refinement findings actionable via patch mode” (plan); standup notes, split proposals, AC improvements as patch. +- **Unlocks**: E1 (standup patch), E3 (split proposal patch), E5 (backlog add draft patch). + +## Format Validation + +- proposal.md: Why, What Changes, Capabilities, Impact, Source Tracking present. +- specs/patch-mode/spec.md: Given/When/Then for generate, apply local, write upstream. +- tasks.md: TDD/SDD order; branch first, PR last; format OK. diff --git a/openspec/changes/patch-mode-preview-apply/proposal.md b/openspec/changes/patch-mode-preview-apply/proposal.md new file mode 100644 index 00000000..257fcd7f --- /dev/null +++ b/openspec/changes/patch-mode-preview-apply/proposal.md @@ -0,0 +1,32 @@ +# Change: Patch mode for backlog/spec edits (previewable, confirmable) — Δ2 + +## Why + +Reporting findings is not enough; teams love tools that propose fixes they can safely apply. A patch pipeline that generates unified diffs for backlog body updates, OpenSpec proposal/spec updates, and config updates—with `--apply` (local) and `--write` (upstream) gating and idempotency for posted comments/updates—ensures zero accidental writes and trust by design. + +## What Changes + +- **NEW**: Add a patch pipeline that can generate unified diffs for: backlog issue body updates (AC improvements, missing fields), OpenSpec proposal/spec updates, config updates (policy config, mapping templates). +- **NEW**: Add `--apply` + `--write` gating: default = generate patch only; `--apply` = apply locally; `--write` = push to GitHub/ADO only with explicit confirmation. +- **NEW**: Add idempotency for posted comments/updates (no duplicates). +- **NEW**: CLI: `specfact backlog refine --patch` emits a patch file and summary; `specfact patch apply <patchfile>` applies locally with preflight check; `specfact patch apply --write` updates upstream only with explicit confirmation. +- **EXTEND**: Documentation (agile-scrum-workflows, devops-adapter-integration) for patch mode. + +## Capabilities + +- **patch-mode**: Patch pipeline (generate diffs for backlog body, OpenSpec, config); `--apply` (local) and `--write` (upstream) gating; idempotent posts; `backlog refine --patch`, `patch apply <file>`, `patch apply --write` with confirmation. + +## Impact + +- **Affected specs**: New `openspec/changes/patch-mode-preview-apply/specs/patch-mode/spec.md` (Given/When/Then for patch generation, apply local, write upstream). +- **Affected code**: New module or commands for patch pipeline (e.g. `src/specfact_cli/commands/patch_commands.py` or under backlog); `specfact patch apply`; integration with backlog refine, Policy Engine (suggest → patch). +- **Affected documentation** (<https://docs.specfact.io>): docs/guides/agile-scrum-workflows.md, docs/guides/devops-adapter-integration.md. +- **Integration points**: unify-policies-engine (suggest → patch), daily-standup-exceptions-first (standup notes patch), story-complexity-splitting-hints-support (split proposal patch). +- **Backward compatibility**: Additive; no writes without explicit `--apply` or `--write` + confirmation. + +## Source Tracking + +- **GitHub Issue**: #177 +- **Issue URL**: <https://github.com/nold-ai/specfact-cli/issues/177> +- **Repository**: nold-ai/specfact-cli +- **Last Synced Status**: proposed diff --git a/openspec/changes/patch-mode-preview-apply/specs/patch-mode/spec.md b/openspec/changes/patch-mode-preview-apply/specs/patch-mode/spec.md new file mode 100644 index 00000000..87b2c76c --- /dev/null +++ b/openspec/changes/patch-mode-preview-apply/specs/patch-mode/spec.md @@ -0,0 +1,57 @@ +# Patch mode (previewable, confirmable) + +## ADDED Requirements + +### Requirement: Patch generation (backlog, OpenSpec, config) + +The system SHALL support generating unified diffs for: backlog issue body updates (AC, missing fields), OpenSpec proposal/spec updates, config updates (policy, mapping templates). Default behavior SHALL be generate-only (no apply, no write). + +**Rationale**: Plan Δ2—trust by design; no accidental writes. + +#### Scenario: Generate patch from backlog refine + +**Given**: Backlog refine has identified improvements (e.g. missing AC, body updates) + +**When**: The user runs `specfact backlog refine --patch` + +**Then**: The system emits a patch file and summary; no changes are applied or written upstream + +**Acceptance Criteria**: + +- `specfact backlog refine --patch` emits a patch file and summary; no apply/write by default. + +### Requirement: Apply locally with preflight + +The system SHALL provide `specfact patch apply <patchfile>` that applies the patch locally with a preflight check; user confirmation or explicit flag required. + +**Rationale**: Plan Δ2—previewable, confirmable. + +#### Scenario: Apply patch locally + +**Given**: A patch file generated by refine --patch or policy suggest + +**When**: The user runs `specfact patch apply <patchfile>` + +**Then**: The system runs a preflight check and applies the patch locally only; no upstream write + +**Acceptance Criteria**: + +- `specfact patch apply <patchfile>` applies locally with preflight; no upstream update. + +### Requirement: Write upstream with explicit confirmation + +The system SHALL provide `specfact patch apply --write` (or equivalent) that updates upstream (GitHub/ADO) only with explicit user confirmation; idempotent for posted comments/updates (no duplicates). + +**Rationale**: Plan Δ2—zero accidental writes. + +#### Scenario: Write patch upstream + +**Given**: A patch has been applied locally or user confirms write + +**When**: The user runs `specfact patch apply --write` with confirmation + +**Then**: The system updates upstream (e.g. issue body, comment) only after confirmation; idempotent (no duplicate comments/updates) + +**Acceptance Criteria**: + +- `specfact patch apply --write` updates upstream only with explicit confirmation; idempotent posts. diff --git a/openspec/changes/patch-mode-preview-apply/tasks.md b/openspec/changes/patch-mode-preview-apply/tasks.md new file mode 100644 index 00000000..89abc622 --- /dev/null +++ b/openspec/changes/patch-mode-preview-apply/tasks.md @@ -0,0 +1,37 @@ +# Tasks: Patch mode (Δ2) + +## TDD / SDD order (enforced) + +Per `openspec/config.yaml`, **tests before code** apply. + +1. Spec deltas define behavior in `specs/patch-mode/spec.md`. +2. **Tests second**: Write tests from spec scenarios; run tests and **expect failure**. +3. **Code last**: Implement until tests pass. + +--- + +## 1. Create git branch from dev + +- [ ] 1.1 Ensure on dev and up to date; create branch `feature/patch-mode-preview-apply`; verify. + +## 2. Tests first (patch generate, apply local, write upstream) + +- [ ] 2.1 Write tests from spec: backlog refine --patch (emit file, no apply); patch apply <file> (local, preflight); patch apply --write (confirmation, idempotent). +- [ ] 2.2 Run tests: `hatch run smart-test-unit`; **expect failure**. + +## 3. Implement patch mode + +- [ ] 3.1 Implement patch pipeline (generate diffs for backlog body, OpenSpec, config). +- [ ] 3.2 Add `specfact backlog refine --patch` (emit patch file and summary). +- [ ] 3.3 Add `specfact patch apply <patchfile>` (preflight, apply local only). +- [ ] 3.4 Add `specfact patch apply --write` (explicit confirmation, idempotent upstream updates). +- [ ] 3.5 Run tests; **expect pass**. + +## 4. Quality gates and documentation + +- [ ] 4.1 Run format, type-check, contract-test. +- [ ] 4.2 Update docs (agile-scrum-workflows, devops-adapter-integration); CHANGELOG; version sync. + +## 5. Create Pull Request to dev + +- [ ] 5.1 Commit, push, create PR to dev; use repo PR template. diff --git a/openspec/changes/sprint-planning-capacity-commitment-support/CHANGE_VALIDATION.md b/openspec/changes/sprint-planning-capacity-commitment-support/CHANGE_VALIDATION.md index 95ca49fa..5dee3802 100644 --- a/openspec/changes/sprint-planning-capacity-commitment-support/CHANGE_VALIDATION.md +++ b/openspec/changes/sprint-planning-capacity-commitment-support/CHANGE_VALIDATION.md @@ -1,73 +1,28 @@ # Change Validation Report: sprint-planning-capacity-commitment-support -**Validation Date**: 2026-01-30 -**Change Proposal**: [proposal.md](./proposal.md) -**Validation Method**: Dry-run and format/config compliance check +**Validation Date**: 2026-02-02 +**Plan Reference**: specfact-cli-internal/docs/internal/implementation/2026-02-01-backlog-changes-improvement.md (E2) +**Validation Method**: Plan alignment + OpenSpec strict validation ## Executive Summary -- **Breaking Changes**: 0 detected -- **Dependent Files**: Additive only (new `specfact backlog sprint-summary` subcommand; existing `backlog` Typer group in `backlog_commands.py` will gain a new callback; capacity config and commitment aggregation are new modules) -- **Impact Level**: Low -- **Validation Result**: Pass -- **User Decision**: N/A (no breaking changes) -- **Command placement**: Sprint summary is under backlog command group (`specfact backlog sprint-summary`); no top-level `specfact sprint` command (per plan) +- **Plan Enhancement (E2)**: Sprint summary extended with risk rollup, DoR coverage, optional sprint_goal and alignment hints. +- **Breaking Changes**: 0 (additive only). +- **Validation Result**: Pass. +- **OpenSpec Validation**: `openspec validate sprint-planning-capacity-commitment-support --strict` — valid. -## Breaking Changes Detected +## Alignment with Plan E2 -None. Change is additive: new sprint capacity config, commitment sum, sprint-summary output; existing backlog behavior unchanged. +- **E2**: Extend sprint-planning with risk + goal alignment. **Done**: proposal.md and specs/sprint-planning/spec.md updated with optional sprint_goal, risk rollup (explainable-risk-rollups), DoR coverage (Policy Engine); acceptance: sprint summary includes capacity, committed, risk, top blockers, DoR pass rate. -## Dependencies Affected +## USP / Value-Add -- **Critical**: None -- **Recommended**: Reuse BacklogItem.sprint and BacklogItem.story_points from existing models; capacity loader pattern similar to DoR/DoD config loaders. -- **Optional**: None - -## Impact Assessment - -- **Code Impact**: New subcommand (sprint-summary); new or extended config loader (sprint_capacity.yaml); commitment aggregation from backlog items. -- **Test Impact**: New tests from spec scenarios (capacity config load, commitment sum, over/under output, sprint-summary CLI). -- **Documentation Impact**: agile-scrum-workflows.md, backlog-refinement.md for sprint planning. -- **Release Impact**: Patch (additive feature). +- **Trust by design**: Sprint summary remains read-only; risk/DoR are reported, not auto-applied. +- **One policy engine**: DoR coverage integrates with unify-policies-engine when available. +- **Measurable**: Capacity, committed, risk, DoR pass rate in one view supports “Loved” metric (plan). ## Format Validation -- **proposal.md Format**: Pass - - Title format: Correct (`# Change: Sprint planning (capacity and commitment) support`) - - Required sections: All present (Why, What Changes, Capabilities, Impact) - - "What Changes" format: Correct (bullet list with NEW/EXTEND) - - "Capabilities" section: Present (sprint-planning) - - "Impact" format: Correct - - Source Tracking section: Present (GitHub Issue #170, URL, repository) -- **tasks.md Format**: Pass - - Section headers: Hierarchical numbered format - - Task format: `- [ ] N.N [Description]` - - Sub-task format: Indented `- [ ] N.N.N` - - Config.yaml compliance: Pass - - TDD order section at top; tests before implementation (Section 4 before Section 5) - - Branch creation first (Section 1); PR creation last (Section 9) - - GitHub issue creation task (Section 2) for nold-ai/specfact-cli - - Version and changelog task (Section 8) before PR; patch bump and CHANGELOG sync - - Quality gates, documentation tasks present -- **specs Format**: Pass (Given/When/Then in specs/sprint-planning/spec.md) -- **design.md Format**: Pass (sequence, contract enforcement, fallback documented) -- **Config.yaml Compliance**: Pass - -## OpenSpec Validation - -- **Status**: Pass -- **Validation Command**: `openspec validate sprint-planning-capacity-commitment-support --strict` -- **Issues Found**: 0 -- **Issues Fixed**: 0 - -## Recommended Improvements Applied - -1. **GitHub issue mandatory**: Issue #170 created in nold-ai/specfact-cli; proposal Source Tracking updated. -2. **Patch version and changelog**: Task 8 bumps patch version, syncs pyproject.toml/setup.py/src __init__.py, and adds CHANGELOG.md entry. -3. **TDD order**: TDD/SDD section at top of tasks.md; Section 4 (tests first, expect failure) before Section 5 (implement until tests pass). -4. **Backlog harmonization**: Sprint planning is under `specfact backlog sprint-summary`; no top-level `specfact sprint` command. - -## Validation Artifacts - -- No temporary workspace used (dry-run analysis only). -- Change directory: `openspec/changes/sprint-planning-capacity-commitment-support/` +- proposal.md: Required sections (Why, What Changes, Capabilities, Impact) present; E2 extension and acceptance criteria added. +- specs: Given/When/Then for new requirement (Sprint summary with risk and DoR). +- tasks.md: Existing structure retained; no format issues. diff --git a/openspec/changes/sprint-planning-capacity-commitment-support/proposal.md b/openspec/changes/sprint-planning-capacity-commitment-support/proposal.md index 4f608dc2..ae9daafe 100644 --- a/openspec/changes/sprint-planning-capacity-commitment-support/proposal.md +++ b/openspec/changes/sprint-planning-capacity-commitment-support/proposal.md @@ -10,10 +10,11 @@ SpecFact CLI supports sprint/release assignment and story points at the backlog- - **NEW**: When exporting or listing backlog items filtered by sprint, compute total story points for that sprint and compare to capacity (if configured). - **NEW**: Add optional output (CLI and/or export) that shows: sprint id, total committed points, capacity, difference (over/under). Expose under the **backlog command group** as `specfact backlog sprint-summary` (or similar subcommand); do not add a top-level `specfact sprint` command. - **EXTEND**: Documentation (agile-scrum-workflows, backlog-refinement) for sprint planning support. +- **EXTEND** (plan E2): Optional `sprint_goal` support in config; show alignment hints. Include risk rollup (explainable-risk-rollups) in sprint summary output. Add "DoR coverage" summary for sprint scope via Policy Engine (unify-policies-engine). **Acceptance**: Sprint summary includes: capacity, committed, risk, top blockers, DoR pass rate. ## Capabilities -- **sprint-planning**: Capacity config load, commitment sum by sprint, over/under commitment comparison, sprint-summary CLI/export output. +- **sprint-planning**: Capacity config load, commitment sum by sprint, over/under commitment comparison, sprint-summary CLI/export output; optional sprint_goal and alignment hints; risk rollup and DoR coverage when Policy Engine and risk rollups are available. ## Impact diff --git a/openspec/changes/sprint-planning-capacity-commitment-support/specs/sprint-planning/spec.md b/openspec/changes/sprint-planning-capacity-commitment-support/specs/sprint-planning/spec.md index f2e95586..8c97082a 100644 --- a/openspec/changes/sprint-planning-capacity-commitment-support/specs/sprint-planning/spec.md +++ b/openspec/changes/sprint-planning-capacity-commitment-support/specs/sprint-planning/spec.md @@ -75,3 +75,21 @@ The system SHALL expose sprint summary under the backlog command group (e.g. `sp **Acceptance Criteria**: - Command is discoverable under `specfact backlog --help`; behavior matches spec scenarios above. + +### Requirement: Sprint summary with risk and DoR (E2 extension) + +The system SHALL include in sprint summary output (when dependencies are available): risk rollup (top blockers, risk level), and DoR coverage (pass rate for sprint scope) via Policy Engine. Optional sprint_goal in config SHALL be shown as alignment hint when present. + +**Rationale**: Plan E2—teams need capacity, committed, risk, top blockers, and DoR pass rate in one view. + +#### Scenario: Sprint summary includes risk and DoR + +**Given**: Policy Engine (unify-policies-engine) and risk rollups (explainable-risk-rollups) are available; sprint has items with DoR state + +**When**: The user runs `specfact backlog sprint-summary` for that sprint + +**Then**: The output includes capacity, committed, and when available: risk level, top blockers, DoR pass rate; if sprint_goal is in config, show alignment hint + +**Acceptance Criteria**: + +- Sprint summary includes: capacity, committed, risk (when available), top blockers (when available), DoR pass rate (when Policy Engine available). Optional sprint_goal and alignment hints. diff --git a/openspec/changes/story-complexity-splitting-hints-support/CHANGE_VALIDATION.md b/openspec/changes/story-complexity-splitting-hints-support/CHANGE_VALIDATION.md index 83a93d5d..b7d864f6 100644 --- a/openspec/changes/story-complexity-splitting-hints-support/CHANGE_VALIDATION.md +++ b/openspec/changes/story-complexity-splitting-hints-support/CHANGE_VALIDATION.md @@ -1,74 +1,27 @@ # Change Validation Report: story-complexity-splitting-hints-support -**Validation Date**: 2026-01-30 -**Change Proposal**: [proposal.md](./proposal.md) -**Validation Method**: Dry-run and format/config compliance check +**Validation Date**: 2026-02-02 +**Plan Reference**: specfact-cli-internal/docs/internal/implementation/2026-02-01-backlog-changes-improvement.md (E3) +**Validation Method**: Plan alignment + OpenSpec strict validation ## Executive Summary -- **Breaking Changes**: 0 detected -- **Dependent Files**: Additive only (extend `specfact backlog refine` with complexity/splitting; new or extended module for complexity score and splitting suggestion; existing backlog_commands.py and refinement flow) -- **Impact Level**: Low -- **Validation Result**: Pass -- **User Decision**: N/A (no breaking changes) -- **Command placement**: Complexity and splitting integrated into `specfact backlog refine` only; no top-level scrum/refine command (per plan) +- **Plan Enhancement (E3)**: Splitting suggestions extended to be dependency-aware (edges, blast radius); patch output for split proposal when patch mode available. +- **Breaking Changes**: 0 (additive only). +- **Validation Result**: Pass. +- **OpenSpec Validation**: `openspec validate story-complexity-splitting-hints-support --strict` — valid. -## Breaking Changes Detected +## Alignment with Plan E3 -None. Change is additive: complexity score, needs_splitting predicate, splitting suggestion in refinement output and export-to-tmp; existing refine behavior unchanged for non-complex items. +- **E3**: Extend story-complexity to be dependency-aware. **Done**: proposal.md and specs/story-complexity/spec.md updated with dependency edges, blast radius, patch output (patch-mode-preview-apply); acceptance: splitting recommendation includes "dependency impact" section. -## Dependencies Affected +## USP / Value-Add -- **Critical**: None -- **Recommended**: Reuse BacklogItem (story_points, business_value, acceptance_criteria) from existing models; align with existing refinement result type and export-to-tmp format. -- **Optional**: None - -## Impact Assessment - -- **Code Impact**: New or extended module (complexity score, needs_splitting, splitting suggestion); integration into backlog refine output and export-to-tmp. -- **Test Impact**: New tests from spec scenarios (complexity score, needs_splitting, splitting suggestion, refinement output for complex items). -- **Documentation Impact**: backlog-refinement.md for complexity and splitting hints. -- **Release Impact**: Patch (additive feature). +- **Actionable**: Split proposal as patch (titles, AC, links) when patch mode available—aligns with “>80% of refinement findings actionable via patch mode” (plan). +- **Dependency-aware**: Minimizes cross-team coupling; blast radius visible. ## Format Validation -- **proposal.md Format**: Pass - - Title format: Correct (`# Change: Story complexity and splitting hints support`) - - Required sections: All present (Why, What Changes, Capabilities, Impact) - - "What Changes" format: Correct (bullet list with NEW/EXTEND) - - "Capabilities" section: Present (story-complexity) - - "Impact" format: Correct - - Source Tracking section: Present (GitHub Issue #171, URL, repository) -- **tasks.md Format**: Pass - - Section headers: Hierarchical numbered format - - Task format: `- [ ] N.N [Description]` - - Sub-task format: Indented `- [ ] N.N.N` - - Config.yaml compliance: Pass - - TDD order section at top; tests before implementation (Section 4 before Section 5) - - Branch creation first (Section 1); PR creation last (Section 9) - - GitHub issue creation task (Section 2) for nold-ai/specfact-cli - - Version and changelog task (Section 8) before PR; patch bump and CHANGELOG sync - - Quality gates, documentation tasks present -- **specs Format**: Pass (Given/When/Then in specs/story-complexity/spec.md) -- **design.md Format**: Pass (sequence, contract enforcement, fallback documented) -- **Config.yaml Compliance**: Pass - -## OpenSpec Validation - -- **Status**: Pass -- **Validation Command**: `openspec validate story-complexity-splitting-hints-support --strict` -- **Issues Found**: 0 -- **Issues Fixed**: 0 - -## Recommended Improvements Applied - -1. **GitHub issue mandatory**: Issue #171 created in nold-ai/specfact-cli; proposal Source Tracking updated. -2. **Patch version and changelog**: Task 8 bumps patch version, syncs pyproject.toml/setup.py/src __init__.py, and adds CHANGELOG entry. -3. **TDD order**: TDD/SDD section at top of tasks.md; Section 4 (tests first, expect failure) before Section 5 (implement until tests pass). -4. **Backlog harmonization**: Complexity and splitting integrated into `specfact backlog refine` only; no top-level scrum/refine command. -5. **Spec alignment**: Spec delta references main `openspec/specs/backlog-refinement/spec.md` Story Complexity Analysis; scenarios restate requirements for this change scope. - -## Validation Artifacts - -- No temporary workspace used (dry-run analysis only). -- Change directory: `openspec/changes/story-complexity-splitting-hints-support/` +- proposal.md: E3 extension and acceptance criteria added. +- specs: New requirement (Dependency-aware splitting) with Given/When/Then. +- tasks.md: Unchanged; format OK. diff --git a/openspec/changes/story-complexity-splitting-hints-support/proposal.md b/openspec/changes/story-complexity-splitting-hints-support/proposal.md index 8488794c..0ba2dfc2 100644 --- a/openspec/changes/story-complexity-splitting-hints-support/proposal.md +++ b/openspec/changes/story-complexity-splitting-hints-support/proposal.md @@ -10,10 +10,11 @@ The backlog-refinement spec (openspec/specs/backlog-refinement/spec.md) includes - **NEW**: Add splitting detection that suggests split points and rationale (e.g. by acceptance criteria or logical boundaries). - **EXTEND**: Integrate into **backlog refine** flow (`specfact backlog refine`): when refinement completes for a complex story, include a "Story splitting suggestion" block in the output (and in export-to-tmp format) with recommended split points and rationale. All agile/backlog features stay under the backlog command group; no top-level scrum/refine command. - **EXTEND**: Documentation (backlog-refinement guide, reference) for complexity and splitting hints. +- **EXTEND** (plan E3): Splitting suggestions SHALL consider dependency edges (minimize cross-team coupling) and "blast radius" signals (modules touched, component tags when available). Provide patch output (patch-mode-preview-apply): "split proposal" as suggested child stories with titles + AC + links. **Acceptance**: Splitting recommendation includes "dependency impact" section. ## Capabilities -- **story-complexity**: Complexity score (story_points, business_value), needs_splitting predicate (configurable threshold), splitting suggestion (rationale + split points), integration into refinement output/export. +- **story-complexity**: Complexity score (story_points, business_value), needs_splitting predicate (configurable threshold), splitting suggestion (rationale + split points), integration into refinement output/export; dependency-aware splitting (edges, blast radius) and patch output for split proposal when dependency analysis and patch mode are available. ## Impact diff --git a/openspec/changes/story-complexity-splitting-hints-support/specs/story-complexity/spec.md b/openspec/changes/story-complexity-splitting-hints-support/specs/story-complexity/spec.md index 43225ce3..18c07a9a 100644 --- a/openspec/changes/story-complexity-splitting-hints-support/specs/story-complexity/spec.md +++ b/openspec/changes/story-complexity-splitting-hints-support/specs/story-complexity/spec.md @@ -93,3 +93,23 @@ The system SHALL integrate complexity and splitting into `specfact backlog refin **Acceptance Criteria**: - Behavior is discoverable as part of existing `specfact backlog refine`; no new top-level commands. + +### Requirement: Dependency-aware splitting (E3 extension) + +The system SHALL consider dependency edges (minimize cross-team coupling) and blast radius (modules touched, component tags when available) when generating splitting suggestions. When patch mode (patch-mode-preview-apply) is available, SHALL provide "split proposal" as suggested child stories with titles, AC, and links. Splitting recommendation output SHALL include a "dependency impact" section when dependency data exists. + +**Rationale**: Plan E3—splitting should reduce cross-team coupling and surface blast radius. + +#### Scenario: Splitting suggestion includes dependency impact + +**Given**: Dependency graph (add-backlog-dependency-analysis-and-commands) and optional patch mode are available; item has dependencies or touched modules + +**When**: Splitting suggestion is generated for a complex story + +**Then**: The suggestion includes a "dependency impact" section (cross-team edges, blast radius when available) + +**And**: When patch mode is used, split proposal is emitted as suggested child stories (titles, AC, links) + +**Acceptance Criteria**: + +- Splitting recommendation includes "dependency impact" section when dependency data exists; patch output for split proposal when patch mode available. diff --git a/openspec/changes/unify-policies-engine/CHANGE_VALIDATION.md b/openspec/changes/unify-policies-engine/CHANGE_VALIDATION.md new file mode 100644 index 00000000..f1817c6f --- /dev/null +++ b/openspec/changes/unify-policies-engine/CHANGE_VALIDATION.md @@ -0,0 +1,28 @@ +# Change Validation Report: unify-policies-engine + +**Validation Date**: 2026-02-02 +**Plan Reference**: specfact-cli-internal/docs/internal/implementation/2026-02-01-backlog-changes-improvement.md (Δ1) +**Validation Method**: Plan alignment + OpenSpec strict validation + +## Executive Summary + +- **Plan Delta (Δ1)**: Unified Policy Engine (DoR/DoD/Flow/PI); `policy.validate` (deterministic), `policy.suggest` (AI-assisted, patch-ready); config `.specfact/policy.yaml`; JSON + Markdown output. +- **Breaking Changes**: 0 (new capability). +- **Validation Result**: Pass. +- **OpenSpec Validation**: `openspec validate unify-policies-engine --strict` — valid. + +## Alignment with Plan Δ1 + +- **Δ1**: One policy engine for DoR, DoD, Kanban entry/exit, SAFe PI readiness. **Done**: proposal and spec define validate/suggest, config, result format (rule id, severity, evidence, recommended action); no network required when using snapshots. + +## USP / Value-Add + +- **One policy engine**: Plan guiding principle—DoR, DoD, Kanban/SAFe policies share one mechanism and consistent reporting. +- **Trust by design**: validate = deterministic; suggest = confidence-scored, patch-ready; no silent writes. +- **Foundation**: Unlocks E1 (standup exceptions), E2 (DoR coverage in sprint summary), E5 (backlog add policy-first). + +## Format Validation + +- proposal.md: Why, What Changes, Capabilities, Impact, Source Tracking present. +- specs/policy-engine/spec.md: Given/When/Then for validate, suggest, config. +- tasks.md: TDD/SDD order; branch first, PR last; format OK. diff --git a/openspec/changes/unify-policies-engine/proposal.md b/openspec/changes/unify-policies-engine/proposal.md new file mode 100644 index 00000000..e845d7b3 --- /dev/null +++ b/openspec/changes/unify-policies-engine/proposal.md @@ -0,0 +1,35 @@ +# Change: Unified Policy Engine (DoR/DoD/Flow/PI) — Δ1 + +## Why + +Teams love tools that enforce working agreements consistently. Today DoR/DoD are fragmented across features; Kanban/SAFe policies are not first-class. A single Policy framework with `policy.validate` (hard failures; deterministic) and `policy.suggest` (AI-assisted; confidence-scored; patch-ready) gives one mechanism for DoR, DoD, Kanban entry/exit, and SAFe PI readiness so refinement, planning, and standup share the same quality gates. + +## What Changes + +- **NEW**: Introduce a single "Policy" framework with: + - `policy.validate` (hard failures; deterministic) + - `policy.suggest` (AI-assisted; confidence-scored; patch-ready) +- **NEW**: First policies shipped: Scrum (DoR + DoD), Kanban (entry/exit policies per column), SAFe (PI readiness policy hooks, minimal baseline). +- **NEW**: Produce machine-readable output: JSON for CI gates and Markdown for humans. +- **NEW**: Config: `.specfact/policy.yaml`; `specfact policy validate` runs without network access (against snapshots when applicable). +- **EXTEND**: Policy results include: rule id, severity, evidence pointer (field/path), and recommended action. +- **EXTEND**: Documentation (agile-scrum-workflows, devops-adapter-integration) for Policy Engine. + +## Capabilities + +- **policy-engine**: Policy framework (validate, suggest); DoR/DoD/Flow/PI policies; JSON and Markdown output; config-driven rules; evidence and recommended action per result. + +## Impact + +- **Affected specs**: New `openspec/changes/unify-policies-engine/specs/policy-engine/spec.md` (Given/When/Then for validate, suggest, policies, output formats). +- **Affected code**: New module for policy engine (e.g. `src/specfact_cli/policy/` or under commands); CLI `specfact policy validate`, `specfact policy suggest`; integration points for refinement, standup, sprint-summary (DoR coverage). +- **Affected documentation** (<https://docs.specfact.io>): docs/guides/agile-scrum-workflows.md, docs/guides/devops-adapter-integration.md. +- **Integration points**: definition-of-done-support, daily-standup-exceptions-first, sprint-planning-capacity-commitment-support (DoR coverage), patch-mode-preview-apply (suggest → patch). +- **Backward compatibility**: Additive; existing behavior unchanged until callers use Policy Engine. + +## Source Tracking + +- **GitHub Issue**: #176 +- **Issue URL**: <https://github.com/nold-ai/specfact-cli/issues/176> +- **Repository**: nold-ai/specfact-cli +- **Last Synced Status**: proposed diff --git a/openspec/changes/unify-policies-engine/specs/policy-engine/spec.md b/openspec/changes/unify-policies-engine/specs/policy-engine/spec.md new file mode 100644 index 00000000..5583476e --- /dev/null +++ b/openspec/changes/unify-policies-engine/specs/policy-engine/spec.md @@ -0,0 +1,59 @@ +# Policy Engine (DoR/DoD/Flow/PI) + +## ADDED Requirements + +### Requirement: Policy validate (deterministic, hard failures) + +The system SHALL provide `specfact policy validate` that runs policy rules deterministically and reports hard failures (rule id, severity, evidence pointer, recommended action). It SHALL run without network access when using snapshots. + +**Rationale**: Plan Δ1—consistent quality gates. + +#### Scenario: Validate policies + +**Given**: A project with `.specfact/policy.yaml` and backlog/spec snapshot + +**When**: The user runs `specfact policy validate` + +**Then**: The system evaluates all configured policies and outputs failures (rule id, severity, evidence pointer, recommended action) + +**And**: Output is machine-readable (JSON) and human-readable (Markdown) + +**Acceptance Criteria**: + +- Policy results include: rule id, severity, evidence pointer (field/path), recommended action; no network required when using snapshots. + +### Requirement: Policy suggest (AI-assisted, patch-ready) + +The system SHALL provide `specfact policy suggest` that proposes fixes with confidence scores and patch-ready output when applicable; user confirmation required before apply. + +**Rationale**: Plan Δ1—actionable suggestions without silent writes. + +#### Scenario: Suggest policy fixes + +**Given**: Policy validate has reported failures + +**When**: The user runs `specfact policy suggest` + +**Then**: The system proposes fixes (e.g. missing fields, DoR gaps) with confidence and optional patch; no write without explicit user action + +**Acceptance Criteria**: + +- Suggestions are confidence-scored and patch-ready; no automatic writes. + +### Requirement: Policy config + +The system SHALL support policy configuration in `.specfact/policy.yaml` (Scrum: DoR/DoD; Kanban: entry/exit per column; SAFe: PI readiness hooks). + +**Rationale**: Plan Δ1—one config, one engine. + +#### Scenario: Load policy config + +**Given**: `.specfact/policy.yaml` exists with DoR/DoD rules + +**When**: The user runs `specfact policy validate` + +**Then**: The system loads policy config and applies rules; missing or invalid config is reported clearly + +**Acceptance Criteria**: + +- A project can define policies in `.specfact/policy.yaml`; loader does not crash on missing/invalid config. diff --git a/openspec/changes/unify-policies-engine/tasks.md b/openspec/changes/unify-policies-engine/tasks.md new file mode 100644 index 00000000..c79271f6 --- /dev/null +++ b/openspec/changes/unify-policies-engine/tasks.md @@ -0,0 +1,36 @@ +# Tasks: Unified Policy Engine (Δ1) + +## TDD / SDD order (enforced) + +Per `openspec/config.yaml`, **tests before code** apply. + +1. Spec deltas define behavior in `specs/policy-engine/spec.md`. +2. **Tests second**: Write tests from spec scenarios; run tests and **expect failure**. +3. **Code last**: Implement until tests pass. + +--- + +## 1. Create git branch from dev + +- [ ] 1.1 Ensure on dev and up to date; create branch `feature/unify-policies-engine`; verify. + +## 2. Tests first (policy validate, suggest, config) + +- [ ] 2.1 Write tests from spec: policy validate (deterministic, output format), policy suggest (confidence, no auto-write), config load. +- [ ] 2.2 Run tests: `hatch run smart-test-unit`; **expect failure**. + +## 3. Implement Policy Engine + +- [ ] 3.1 Implement policy config loader (`.specfact/policy.yaml`); schema for DoR/DoD/Flow/PI. +- [ ] 3.2 Implement `specfact policy validate` (deterministic, JSON + Markdown output; rule id, severity, evidence, recommended action). +- [ ] 3.3 Implement `specfact policy suggest` (AI-assisted, confidence-scored, patch-ready; no write without explicit action). +- [ ] 3.4 Run tests; **expect pass**. + +## 4. Quality gates and documentation + +- [ ] 4.1 Run format, type-check, contract-test. +- [ ] 4.2 Update docs (agile-scrum-workflows, devops-adapter-integration); CHANGELOG; version sync. + +## 5. Create Pull Request to dev + +- [ ] 5.1 Commit, push, create PR to dev; use repo PR template. diff --git a/openspec/specs/daily-standup/spec.md b/openspec/specs/daily-standup/spec.md new file mode 100644 index 00000000..7eac98d0 --- /dev/null +++ b/openspec/specs/daily-standup/spec.md @@ -0,0 +1,329 @@ +# daily-standup Specification + +## Purpose +TBD - created by archiving change daily-standup-progress-support. Update Purpose after archive. +## Requirements +### Requirement: Standup view + +The system SHALL provide a standup or progress view that lists change proposals or backlog items (by assignee or filter) with last-updated and status, and optional one-line summary for yesterday/today/blockers. + +**Rationale**: Teams need a single place to see "my items" and recent activity for daily standup without duplicating data in multiple tools. + +#### Scenario: List my items with status and last activity + +**Given**: A user has change proposals or backlog items assigned to them (or a filter is applied) + +**When**: The user runs the standup view (e.g. `specfact backlog daily` or equivalent under the backlog command group) + +**Then**: The system lists items (change proposal id or backlog item id, title, status, last-updated) for the user or filter + +**And**: Optional standup summary lines (yesterday/today/blockers) are shown when available from proposal or linked issue body + +**Acceptance Criteria**: + +- Output is readable (e.g. table or structured list) +- Last-updated is displayed per item +- Optional standup fields (yesterday, today, blockers) shown when present in source data + +#### Scenario: Standup view with assignee filter + +**Given**: A repo with multiple change proposals or backlog items and assignee metadata + +**When**: The user runs standup view with assignee filter (e.g. `--assignee me` or current user) + +**Then**: Only items matching the assignee are listed + +**And**: If no assignee filter is applied, all items (or default scope) are listed per command contract + +### Requirement: Default standup scope (meaningful daily standups) + +The system SHALL support a default standup scope so daily standups focus on active work, not the full backlog: default state filter (e.g. open/active), optional default assignee (e.g. current user when resolvable), and default limit (e.g. 20–30 items). Defaults SHALL be overridable by explicit options and SHALL be configurable via environment variables and/or a config file (e.g. `.specfact/standup.yaml`). + +**Rationale**: Without defaults, `specfact backlog daily` can list the entire backlog; standups should default to "active items and recent activity" so the view is immediately useful. + +#### Scenario: Standup view uses default scope when no filters given + +**Given**: Standup defaults are configured (e.g. state=open, limit=20) or built-in (state=open, limit=20) + +**When**: The user runs standup view without explicit `--state`, `--assignee`, or `--limit` + +**Then**: The system applies the default state filter (e.g. open) so closed/done items are excluded + +**And**: The system applies the default limit (e.g. 20) so output is scannable + +**And**: If configured, default assignee (e.g. "me") is applied so "my items" are shown by default + +**Acceptance Criteria**: + +- Explicit `--state`, `--assignee`, `--limit` override defaults +- Config (env or file) takes precedence over built-in defaults when present +- When adapter has no "open" equivalent, state default is documented (e.g. skip or use adapter's active state) + +### Requirement: Current iteration/sprint focus + +The system SHALL support focusing the standup view on the current iteration or sprint when the adapter provides iteration/sprint metadata (e.g. GitHub Projects, ADO iteration). A parameter (e.g. `--sprint current` or `--iteration current`) or config SHALL filter items to the current iteration/sprint so "new items to start" in the sprint are visible and can be committed to during standup. + +**Rationale**: Daily standups need to see both "what I'm working on" and "what's in the sprint but not yet assigned" so the team can commit to new work. + +#### Scenario: Standup view filtered to current iteration/sprint + +**Given**: An adapter that supports iteration or sprint (e.g. ADO with iteration path, or GitHub with project/sprint) + +**When**: The user runs standup view with current iteration/sprint (e.g. `--sprint current` or config `sprint: current`) + +**Then**: The system lists only items in the current iteration/sprint (or current active sprint when adapter supports it) + +**And**: If the adapter does not support iteration/sprint, the option is ignored or reported clearly; no crash + +**Acceptance Criteria**: + +- When supported, "current" resolves to the adapter's notion of current sprint/iteration +- When current iteration/sprint is in use and the adapter or config provides an iteration/sprint end date, the standup view displays it (e.g. "Sprint ends: DATE (N days)") +- Documentation states which adapters support iteration/sprint filtering + +### Requirement: Unassigned/pending items view + +The system SHALL show items with no assignee (backfill, open sprint, or pending) so the team can discuss and commit to picking them up during standup. Unassigned items SHALL be available either in a separate table (e.g. "Pending / open for commitment") or via an additional parameter (e.g. `--unassigned` / `--pending`) that includes or exclusively shows unassigned items in the same scope (e.g. same state and iteration). + +**Rationale**: Standups are not only about "what I did / what I'll do" but also "new items in the iteration that need commitment from the team"; unassigned items must be visible and discussable. + +#### Scenario: Unassigned items shown for standup commitment + +**Given**: A backlog with items in the current scope (e.g. open, current sprint) some of which are assigned and some unassigned + +**When**: The user runs standup view with unassigned items enabled (e.g. default, or `--unassigned`, or `--show-pending`) + +**Then**: The system shows assigned items (e.g. in a "My / assigned" table or section) and unassigned items (e.g. in a separate "Pending / open for commitment" table or section) so both can be discussed + +**And**: Unassigned items use the same scope (state, iteration/sprint if applied) so they are relevant to the current iteration + +**Acceptance Criteria**: + +- Unassigned items are clearly labeled (e.g. separate table title or column) +- Option to show only unassigned (e.g. `--unassigned-only`) is available for teams that want to run "pick up" separately +- When no unassigned items exist in scope, the unassigned section is omitted or shows "None" + +### Requirement: Blockers and time-critical prominence + +The system SHALL support optionally sorting or surfacing items with non-empty blockers so time-critical issues are visible at a glance (e.g. sort rows with blockers first, or a `--blockers-first` flag). + +**Rationale**: Daily standups need to surface blockers early so the team can address time-critical issues during the current iteration. + +#### Scenario: Standup view with blockers first + +**Given**: Backlog items in the standup view, some with non-empty standup blockers text + +**When**: The user runs standup view with blockers-first enabled (e.g. `--blockers-first` or default sort) + +**Then**: Items with non-empty blockers are listed first (or in a dedicated order) so blockers are easy to spot + +**Acceptance Criteria**: + +- When supported, items with non-empty blockers may be listed first (e.g. sort or `--blockers-first`), so blockers are easy to spot + +### Requirement: Optional priority/value in standup view + +The system SHALL support optionally showing priority or business value in the standup view when available on BacklogItem and enabled by config, so value-driven (e.g. SAFe) teams can focus on the right features. + +**Rationale**: Value-driven prioritization (WSJF, priority) helps teams deliver the right features during the iteration. + +#### Scenario: Standup view shows priority or value when enabled + +**Given**: Backlog items have priority or business_value (or value_points) and standup config enables showing priority/value + +**When**: The user runs standup view with priority/value display enabled (e.g. config or option) + +**Then**: The standup table includes a priority or value column (or equivalent) when the data is present on items + +**Acceptance Criteria**: + +- When priority or business value is available on BacklogItem and enabled by config, the standup view may display it (e.g. optional column) + +### Requirement: Post standup comment to linked issue + +The system SHALL support posting a standup summary as a comment on the linked issue (e.g. GitHub issue comment) when the user opts in and the adapter supports it. + +**Rationale**: Standup updates should be visible in the DevOps backend (GitHub, ADO) so the team sees progress where they work. + +#### Scenario: Post standup comment via GitHub adapter + +**Given**: A change proposal with Source Tracking linking to a GitHub issue (e.g. nold-ai/specfact-cli#N) + +**And**: The user has provided standup text (yesterday/today/blockers format) and opts to post (e.g. `specfact backlog daily --post` or equivalent) + +**When**: The user runs `specfact backlog daily --post` (or equivalent) and GitHub adapter is configured + +**Then**: The system adds a comment to the linked GitHub issue with the standup text (format: Yesterday / Today / Blockers or team-defined format) + +**And**: The comment is clearly identifiable (e.g. "Standup YYYY-MM-DD" or configurable prefix) + +**Acceptance Criteria**: + +- Comment is posted only when user opts in and adapter supports comments +- Format is configurable or follows a simple standard (yesterday, today, blockers) +- Failure to post (e.g. auth, rate limit) is reported clearly; no silent swallow + +#### Scenario: Post standup when adapter does not support comments + +**Given**: An adapter that does not support posting comments (e.g. read-only or no comment API) + +**When**: The user runs `specfact backlog daily --post` (or equivalent) + +**Then**: The system reports that posting is not supported for this adapter and does not attempt to post + +**Acceptance Criteria**: + +- Clear message; no crash or undefined behavior + +### Requirement: Interactive step-by-step review + +The system SHALL support an interactive step-by-step review of backlog items in the same scope as the standup view (state, iteration/sprint, assignee, limit, unassigned), using arrow-key selection (e.g. questionary) so the user can walk through each story and see full details including progress and existing comments. The feature is complementary to the backlog; it does not replace the backlog or board. + +**Rationale**: Teams need to review each story in detail during standup (blocked items, which to pick next) with minimal context switching; interactive selection and refine-like detail support informed feedback and decision-making. + +#### Scenario: Interactive selection presents items and shows detail on choice + +**Given**: Backlog items in the current scope (e.g. current iteration/sprint, active/blocked/todo) and the user runs `specfact backlog daily --interactive` (or equivalent) + +**When**: The interactive mode starts + +**Then**: The system presents a list of choices (one per backlog item), selectable by arrow keys (e.g. questionary), with each choice showing a summary line (e.g. id, title, status, assignee) + +**And**: When the user selects an item, the system displays full details comparable to `specfact backlog refine` for that item: ID, title, status, assignees, last updated, description/body, acceptance criteria, story points, business value, priority when available, standup fields (yesterday/today/blockers), and **existing comments annotated to that issue** (fetched via adapter when supported, e.g. GitHub issue comments, ADO work item discussion) + +**And**: Items with non-empty blockers are clearly indicated (e.g. blocked status highlighted) + +**Acceptance Criteria**: + +- Selection UI is consistent with existing questionary usage (e.g. template field mapping) +- Detail view reuses or aligns with refine output (description, acceptance criteria, **comments**) +- **Comments**: The system SHALL show comments annotated to the selected issue when the adapter supports fetching comments (e.g. get_comments); omitted or empty when adapter does not support or returns none + +#### Scenario: Interactive navigation (next / previous / back / exit) + +**Given**: User is in interactive mode and has just viewed detail for one item + +**When**: The system presents navigation choices (e.g. "Next story", "Previous story", "Back to list", "Exit") + +**Then**: "Next story" shows detail for the next item in the current ordered list without re-opening the full item menu + +**And**: "Previous story" shows detail for the previous item + +**And**: "Back to list" returns to the item selector menu + +**And**: "Exit" ends the command + +**Acceptance Criteria**: + +- Next/Previous wrap or stop at list boundaries (documented behavior) +- No re-fetch of list when moving next/previous; use already-fetched items + +#### Scenario: Optional next-best-item suggestion and sprint goal hint + +**Given**: BacklogItem or adapter provides story_points, business_value, and priority for some items; optionally sprint goal is provided by adapter or config + +**When**: Interactive mode is run with suggestion enabled (e.g. config or `--suggest-next`) + +**Then**: The system may show a "Suggested next: <id> - <title>" (or value score) for pending (e.g. todo/unassigned) items using a value score (e.g. business_value / max(1, story_points * priority)) so higher value per effort is suggested + +**And**: When sprint goal is available (adapter or config), the interactive view may display an optional hint (e.g. "Sprint goal: …") so the user can align; sprint goal is not edited by the system + +**Acceptance Criteria**: + +- Value score is omitted when required fields are missing; no fake values +- Suggestion is optional (config or flag); default can be off to avoid noise +- Sprint goal hint is optional and read-only + +### Requirement: Export to file for Copilot + +The system SHALL support exporting a summarized view of each backlog item in the current standup scope (current iteration/sprint, active/blocked/todo) to a file, formatted for use with Copilot slash-command interactive review during standup (e.g. summarize progress and next steps per story for team discussion). The export is complementary to the backlog; it does not replace the backlog or board. + +**Rationale**: Teams using Copilot during standup need a concise, file-based summary of each story so Copilot can assist with next steps and current progress; the file is for paste or reference in slash commands. + +#### Scenario: Copilot export writes summarized items to file + +**Given**: Backlog items in the current scope (same as standup: state, iteration/sprint, assignee, limit) and the user runs `specfact backlog daily --copilot-export <path>` (or equivalent) + +**When**: The command runs (with or without `--interactive`; export uses the same fetched list) + +**Then**: The system writes a file at the given path with one section per backlog item in scope + +**And**: Each section includes at least: ID, title, status, assignee(s), last updated, short progress summary (standup fields if present), blockers; optionally value score, priority, story points when available + +**And**: Format is Markdown with clear headings (e.g. `## <id> - <title>`) and bullet points for quick scanning and Copilot use + +**Acceptance Criteria**: + +- File is overwritten (idempotent write) or behavior is configurable +- Export builder has @icontract and @beartype where applicable +- When both `--interactive` and `--copilot-export` are given, export runs on the same fetched list (no requirement to re-fetch) + +### Requirement: Slash-command prompt for daily standup (specfact.backlog-daily) + +The system SHALL provide a prompt file (e.g. `resources/prompts/specfact.backlog-daily.md`) analogous to `specfact.backlog-refine.md`, so teams can run the daily standup flow interactively with the DevOps team via a slash command (e.g. `specfact.daily` or `specfact.backlog-daily`). The prompt SHALL instruct the AI to walk through stories story-by-story, explain and highlight current focus, surface found issues or open questions, and allow adding discussion notes as additional annotation comments on the issue (realistic daily standup scope). + +**Rationale**: Teams need a single, reusable prompt for IDE/Copilot that drives a structured standup review (refinement-style walkthrough with option to add discussion notes as comments), without duplicating instructions in each session. + +#### Scenario: Slash command invokes daily standup prompt + +**Given**: The user invokes the slash command (e.g. `/specfact.daily` or `/specfact.backlog-daily`) with optional adapter and filter arguments + +**When**: The prompt file is loaded and combined with the current context (e.g. CLI output or `--summarize` output) + +**Then**: The AI follows the prompt to present items story-by-story, highlight focus, issues, and open questions, and may suggest or add discussion notes as comments when the user approves + +**Acceptance Criteria**: + +- Prompt file exists under `resources/prompts/` and is documented (e.g. in tutorial and devops-adapter-integration) +- Prompt content aligns with interactive daily flow: story-by-story review, current focus, issues/open questions, discussion notes as comments +- Prompt can be used with `specfact backlog daily` output (e.g. `--copilot-export` or `--summarize`) as input context + +### Requirement: Standup summary prompt (--summarize) + +The system SHALL support a `--summarize` flag on `specfact backlog daily` that produces a **prompt** (instructions plus applied filters and filtered standup output) suitable for use in an interactive slash command (e.g. `specfact.daily`) or copy-paste to Copilot, so an LLM can generate a meaningful **summary of the daily standup status**. + +**Rationale**: Teams want one command that dumps the current standup view into a prompt-ready format, so Copilot or a slash command can then produce a short narrative summary (e.g. "Today's standup: 3 in progress, 1 blocked, 2 pending commitment …") without manually re-typing filters or data. + +#### Scenario: --summarize outputs prompt with filters and data + +**Given**: Backlog items in the current scope (same as standup: state, iteration/sprint, assignee, limit) and the user runs `specfact backlog daily --summarize` (stdout) or `--summarize-to <path>` (write to file) + +**When**: The command runs with the same filters as the standup view + +**Then**: The system outputs (to stdout or to the given path) a prompt that includes: (1) brief instruction that the following data is the current standup view and the LLM should generate a concise standup summary; (2) the applied filter context (adapter, state, sprint, assignee, limit); (3) per-item data including **body (description)** and **comments (annotations)** when available, plus ID, title, status, assignees, last updated, progress, blockers, optional value score, so the LLM can produce a **meaningful** summary + +**And**: The output is formatted so it can be pasted into Copilot or used as input to a slash command (e.g. `specfact.daily`) to produce a standup summary + +**Acceptance Criteria**: + +- `--summarize` uses the same fetched list and filters as the standup view (and as `--copilot-export`) +- Output includes filter context and per-item data; **per-item data SHALL include body (description)** and **comments (annotations)** when the adapter supports fetching comments, so the LLM can create a meaningful summary +- Format is prompt-ready (e.g. Markdown with clear "Generate a standup summary from the following" instruction) +- When `--summarize` or `--summarize-to` is used, the command outputs **only** the prompt (no standup tables) and then exits +- When `--summarize-to <path>` is given, write to file; when `--summarize` only is given, output to stdout +- When both `--summarize` and `--copilot-export` are given, both outputs can be produced from the same fetched list + +### Requirement: Project backlog context (no secrets) + +The system SHALL support storing project-level backlog context (org, project per adapter) in the repo so users do not have to pass adapter context (e.g. `--repo-owner`, `--repo-name`, `--ado-org`, `--ado-project`) every time after authenticating once. Context SHALL be stored in `.specfact/backlog.yaml` (or equivalent) and SHALL contain only non-sensitive identifiers (no tokens, no user names). Resolution order SHALL be: explicit CLI args > environment variables (e.g. `SPECFACT_GITHUB_REPO_OWNER`) > file. Tokens SHALL never be read from file. + +**Rationale**: After one-time authentication (tokens in env or keychain), teams want to set org/project once per repo so all backlog commands (daily, refine, sync bridge) work without repeating adapter options. + +#### Scenario: Adapter context from project config when not passed + +**Given**: A repo with `.specfact/backlog.yaml` containing e.g. `github.repo_owner`, `github.repo_name` (or `ado.org`, `ado.project`, `ado.team`) + +**When**: The user runs a backlog command (e.g. `specfact backlog daily github`) without passing `--repo-owner` or `--repo-name` + +**Then**: The system uses repo_owner and repo_name from the project config (or env) so the command succeeds without explicit options + +**And**: Explicit CLI options override config and env + +**Acceptance Criteria**: + +- File format supports per-adapter keys (e.g. `github.repo_owner`, `github.repo_name`; `ado.org`, `ado.project`, `ado.team`) +- Env overrides file (e.g. `SPECFACT_GITHUB_REPO_OWNER`, `SPECFACT_ADO_ORG`) +- Tokens are never read from file; only from CLI or env +- Config is loaded from `SPECFACT_CONFIG_DIR` or `.specfact/` in cwd; first found wins +- When org/repo or org/project are still missing after CLI, env, and file, the system MAY infer from `git remote get-url origin` when run from a clone (GitHub or Azure DevOps URL formats); supported ADO formats: HTTPS, SSH with keys (`git@ssh.dev.azure.com:v3/...`), SSH without keys (`user@dev.azure.com:v3/...`). If inference fails or not in a clone, the system SHALL report a clear error with guidance (CLI, env, or `.specfact/backlog.yaml`). + diff --git a/pyproject.toml b/pyproject.toml index 37c8f005..168a1cb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "specfact-cli" -version = "0.26.15" +version = "0.26.16" description = "Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts. Automate legacy code documentation and prevent modernization regressions." readme = "README.md" requires-python = ">=3.11" @@ -272,9 +272,9 @@ TOKENIZERS_PARALLELISM = "false" # Good practice if using HuggingFace tokenizers [tool.hatch.envs.hatch-test.scripts] # Adapted from template, adjust JUnit path if necessary -run = "pytest --timeout=5 --junitxml=logs/tests/junit/test-results.xml {args}" # Removed --auto-capture-workflow if not used -run-cov = "coverage run -m pytest --timeout=5 --junitxml=logs/tests/junit/test-results.xml {args}" -cov = "coverage run -m pytest --timeout=5 --junitxml=logs/tests/junit/test-results.xml {args}" +run = "pytest --timeout=30 --junitxml=logs/tests/junit/test-results.xml {args}" # 30s for code analysis/semgrep/graph tests under xdist +run-cov = "coverage run -m pytest --timeout=30 --junitxml=logs/tests/junit/test-results.xml {args}" +cov = "coverage run -m pytest --timeout=30 --junitxml=logs/tests/junit/test-results.xml {args}" cov-combine = "coverage combine --quiet" xml = "coverage xml -o logs/tests/coverage/coverage.xml" run-html = "coverage html --data-file=logs/tests/coverage/.coverage -d logs/tests/coverage/html" @@ -334,6 +334,10 @@ output = "logs/tests/coverage/coverage.xml" mode = "strict" default_fixture_loop_scope = "function" +[tool.pytest-timeout] +timeout = 30 +method = "thread" + [tool.hatch.build] dev-mode-dirs = ["src","tools"] diff --git a/resources/prompts/specfact.backlog-daily.md b/resources/prompts/specfact.backlog-daily.md new file mode 100644 index 00000000..5fb26a6e --- /dev/null +++ b/resources/prompts/specfact.backlog-daily.md @@ -0,0 +1,109 @@ +--- +description: "Daily standup and sprint review with story-by-story walkthrough" +--- + +# SpecFact Daily Standup Command + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Purpose + +Run a daily standup view and optional interactive walkthrough of backlog items (GitHub Issues, Azure DevOps work items) with the DevOps team: list items in scope, review story-by-story, highlight current focus, surface issues or open questions, and allow adding discussion notes as annotation comments on the issue. + +**When to use:** Daily standup, sprint review, quick status sync with the team. + +**Quick:** `/specfact.daily` or `/specfact.backlog-daily` with optional adapter and filters. From a clone, org/repo or org/project are auto-detected from git remote. + +## Parameters + +### Required + +- `ADAPTER` - Backlog adapter name (github, ado, etc.) + +### Adapter Configuration (same as backlog-refine) + +**GitHub:** `--repo-owner`, `--repo-name`, `--github-token` (optional). +**Azure DevOps:** `--ado-org`, `--ado-project`, `--ado-team` (optional), `--ado-base-url`, `--ado-token` (optional). + +When run from a **clone**, org/repo or org/project are inferred from `git remote get-url origin`; no need to pass them unless overriding. + +### Filters + +- `--state STATE` - Filter by state (e.g. open, Active) +- `--assignee USERNAME` or `--assignee me` - Filter by assignee +- `--sprint SPRINT` / `--iteration PATH` - Filter by sprint/iteration (e.g. `current`) +- `--limit N` - Max items (default 20) +- `--blockers-first` - Sort items with blockers first +- `--show-unassigned` / `--unassigned-only` - Include or show only unassigned items + +### Daily-Specific Options + +- `--interactive` - Step-by-step review: select items with arrow keys, view full detail (refine-like) and **existing comments** on each issue +- `--copilot-export PATH` - Write summarized progress per story to a file for Copilot slash-command use +- `--summarize` - Output a prompt (instruction + filter context + standup data) to **stdout** for Copilot or slash command to generate a standup summary +- `--summarize-to PATH` - Write the same summarize prompt to a **file** +- `--suggest-next` - In interactive mode, show suggested next item by value score +- `--post` with `--yesterday`, `--today`, `--blockers` - Post a standup comment to the first item's issue (when adapter supports comments) + +## Workflow + +### Step 1: Run Daily Standup + +Execute the CLI with adapter and optional filters: + +```bash +specfact backlog daily $ADAPTER [--state open] [--sprint current] [--assignee me] [--limit 20] +``` + +Or use the slash command with arguments: `/specfact.backlog-daily --adapter ado --sprint current` + +**What you see:** A standup table (assigned items) and a "Pending / open for commitment" table (unassigned items in scope). Each row shows ID, title, status, last updated, and optional yesterday/today/blockers from the item body. + +### Step 2: Interactive Story-by-Story Review (with DevOps team) + +When the user runs **`--interactive`** (or the slash command drives an interactive flow): + +1. **For each story** (one at a time): + - **Present** the item: ID, title, status, assignees, last updated, description, acceptance criteria, standup fields (yesterday/today/blockers), and **existing comments** annotated to that issue (when the adapter supports fetching comments). + - **Highlight current focus**: What is the team member working on? What is the next intended step? + - **Surface issues or open questions**: Blockers, ambiguities, dependencies, or decisions needed. + - **Allow discussion notes**: If the team agrees, suggest or add a **comment** on the issue (e.g. "Standup YYYY-MM-DD: …" or "Discussion: …") so the discussion is captured as an annotation. Only add comments when the user explicitly approves (e.g. "add that as a comment"). + - **Move to next** only when the team is done with this story (e.g. "next", "done"). + +2. **Rules**: + - Do not update the backlog item body or title unless the user asks for a refinement (use `specfact backlog refine` for that). + - Comments are for **discussion notes** and standup updates; keep them short and actionable. + - If the adapter does not support comments, report that clearly and skip adding comments. + +3. **Navigation**: After each story, offer "Next story", "Previous story", "Back to list", "Exit" (or equivalent) so the team can move through the list without re-running the command. + +### Step 3: Generate Standup Summary (optional) + +When the user has run `specfact backlog daily ... --summarize` or `--summarize-to PATH`, the output is a **prompt** containing: + +- A short instruction: generate a concise daily standup summary from the following data. +- Filter context (adapter, state, sprint, assignee, limit). +- Per-item data (same as `--copilot-export`: ID, title, status, assignees, last updated, progress, blockers). + +**Use this output** by pasting it into Copilot or invoking the slash command `specfact.daily` with this context, so the AI can produce a short narrative summary (e.g. "Today's standup: 3 in progress, 1 blocked, 2 pending commitment …"). + +## Comments on Issues + +- **Interactive detail view** shows **existing comments** on each issue (GitHub issue comments, ADO work item discussion) when the adapter supports it. Use them to understand prior discussion and avoid repeating questions. +- **Adding comments**: When the team agrees to record a discussion note or standup update, add it as a comment on the issue (via `--post` for standup lines, or by guiding the user to run the CLI/post manually). Do not invent comments; only suggest or add when the user approves. + +## CLI Enforcement + +- Execute `specfact backlog daily` (or equivalent) first; use its output as context. +- Use `--interactive` for story-by-story walkthrough; use `--summarize` or `--summarize-to` when a standup summary prompt is needed. +- Use `--copilot-export` when you need a file of item summaries for reference during standup. + +## Context + +{ARGS} diff --git a/setup.py b/setup.py index a3e04d7f..3e64b21e 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ if __name__ == "__main__": _setup = setup( name="specfact-cli", - version="0.26.15", + version="0.26.16", description="SpecFact CLI - Spec -> Contract -> Sentinel tool for contract-driven development", packages=find_packages(where="src"), package_dir={"": "src"}, diff --git a/src/__init__.py b/src/__init__.py index 5d4202ed..2fc0e6a2 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -3,4 +3,4 @@ """ # Package version: keep in sync with pyproject.toml, setup.py, src/specfact_cli/__init__.py -__version__ = "0.26.15" +__version__ = "0.26.16" diff --git a/src/specfact_cli/__init__.py b/src/specfact_cli/__init__.py index 0c49b21b..4debe5aa 100644 --- a/src/specfact_cli/__init__.py +++ b/src/specfact_cli/__init__.py @@ -9,6 +9,6 @@ - Validating reproducibility """ -__version__ = "0.26.15" +__version__ = "0.26.16" __all__ = ["__version__"] diff --git a/src/specfact_cli/adapters/ado.py b/src/specfact_cli/adapters/ado.py index a850a891..0c94c293 100644 --- a/src/specfact_cli/adapters/ado.py +++ b/src/specfact_cli/adapters/ado.py @@ -3107,6 +3107,11 @@ def fetch_backlog_items(self, filters: BacklogFilters) -> list[BacklogItem]: return filtered_items + @beartype + def supports_add_comment(self) -> bool: + """Whether this adapter can add comments (requires token, org, project).""" + return bool(self.api_token and self.org and self.project) + @beartype def add_comment(self, item: BacklogItem, comment: str) -> bool: """ diff --git a/src/specfact_cli/adapters/github.py b/src/specfact_cli/adapters/github.py index 01377d9a..42eec208 100644 --- a/src/specfact_cli/adapters/github.py +++ b/src/specfact_cli/adapters/github.py @@ -2644,6 +2644,11 @@ def fetch_backlog_items(self, filters: BacklogFilters) -> list[BacklogItem]: lambda result, item: result.id == item.id and result.provider == item.provider, "Updated item must preserve id and provider", ) + @beartype + def supports_add_comment(self) -> bool: + """Whether this adapter can add comments (requires token and repo).""" + return bool(self.api_token and self.repo_owner and self.repo_name) + @beartype def add_comment(self, item: BacklogItem, comment: str) -> bool: """ @@ -2681,6 +2686,31 @@ def add_comment(self, item: BacklogItem, comment: str) -> bool: except Exception: return False + @beartype + def get_comments(self, item: BacklogItem) -> list[str]: + """ + Fetch comments for a GitHub issue. + + Args: + item: BacklogItem to fetch comments for + + Returns: + List of comment body strings, or empty list on error + """ + if not self.repo_owner or not self.repo_name: + return [] + issue_number: int | None = None + if item.id.isdigit(): + issue_number = int(item.id) + elif item.url: + match = re.search(r"/issues/(\d+)", item.url) + if match: + issue_number = int(match.group(1)) + if not issue_number: + return [] + raw = self._get_issue_comments(self.repo_owner, self.repo_name, issue_number) + return [str(c.get("body", "")).strip() for c in raw if isinstance(c, dict)] + def update_backlog_item(self, item: BacklogItem, update_fields: list[str] | None = None) -> BacklogItem: """ Update a GitHub issue. diff --git a/src/specfact_cli/backlog/adapters/base.py b/src/specfact_cli/backlog/adapters/base.py index 2b6627b1..f03b8444 100644 --- a/src/specfact_cli/backlog/adapters/base.py +++ b/src/specfact_cli/backlog/adapters/base.py @@ -147,6 +147,20 @@ def create_backlog_item_from_spec(self) -> BacklogItem | None: """ return None + @beartype + @ensure(lambda result: isinstance(result, bool), "Must return boolean") + def supports_add_comment(self) -> bool: + """ + Whether this adapter supports adding comments to backlog items. + + Returns: + True if add_comment will attempt to post, False otherwise + + Note: + Default is False. Override in adapters that support issue comments. + """ + return False + @beartype def add_comment(self, item: BacklogItem, comment: str) -> bool: """ @@ -164,3 +178,20 @@ def add_comment(self, item: BacklogItem, comment: str) -> bool: can use the default False implementation. """ return False + + @beartype + def get_comments(self, item: BacklogItem) -> list[str]: + """ + Fetch comments for a backlog item (optional). + + Args: + item: BacklogItem to fetch comments for + + Returns: + List of comment body strings, or empty list if not supported or on error + + Note: + This is an optional method. Adapters that don't support fetching comments + can use the default empty list implementation. + """ + return [] diff --git a/src/specfact_cli/commands/backlog_commands.py b/src/specfact_cli/commands/backlog_commands.py index 2228a9fd..0f6462cf 100644 --- a/src/specfact_cli/commands/backlog_commands.py +++ b/src/specfact_cli/commands/backlog_commands.py @@ -13,22 +13,26 @@ from __future__ import annotations +import contextlib import os import re +import subprocess import sys import tempfile -from datetime import datetime +from datetime import date, datetime from pathlib import Path from typing import Any +from urllib.parse import urlparse import typer import yaml from beartype import beartype -from icontract import require +from icontract import ensure, require from rich.console import Console from rich.panel import Panel from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn from rich.prompt import Confirm +from rich.table import Table from specfact_cli.adapters.registry import AdapterRegistry from specfact_cli.backlog.adapters.base import BacklogAdapter @@ -131,6 +135,456 @@ def _apply_filters( return filtered +def _parse_standup_from_body(body: str) -> tuple[str | None, str | None, str | None]: + """Extract yesterday/today/blockers lines from body (standup format).""" + yesterday: str | None = None + today: str | None = None + blockers: str | None = None + if not body: + return yesterday, today, blockers + for line in body.splitlines(): + line_stripped = line.strip() + if re.match(r"^\*\*[Yy]esterday(?:\*\*|:)\s*\*\*\s*", line_stripped): + yesterday = re.sub(r"^\*\*[Yy]esterday(?:\*\*|:)\s*\*\*\s*", "", line_stripped).strip() + elif re.match(r"^\*\*[Tt]oday(?:\*\*|:)\s*\*\*\s*", line_stripped): + today = re.sub(r"^\*\*[Tt]oday(?:\*\*|:)\s*\*\*\s*", "", line_stripped).strip() + elif re.match(r"^\*\*[Bb]lockers?(?:\*\*|:)\s*\*\*\s*", line_stripped): + blockers = re.sub(r"^\*\*[Bb]lockers?(?:\*\*|:)\s*\*\*\s*", "", line_stripped).strip() + return yesterday, today, blockers + + +def _load_standup_config() -> dict[str, Any]: + """Load standup config from env and optional .specfact/standup.yaml. Env overrides file.""" + config: dict[str, Any] = {} + config_dir = os.environ.get("SPECFACT_CONFIG_DIR") + search_paths: list[Path] = [] + if config_dir: + search_paths.append(Path(config_dir)) + search_paths.append(Path.cwd() / ".specfact") + for base in search_paths: + path = base / "standup.yaml" + if path.is_file(): + try: + with open(path, encoding="utf-8") as f: + data = yaml.safe_load(f) or {} + config = dict(data.get("standup", data)) + except Exception: + pass + break + if os.environ.get("SPECFACT_STANDUP_STATE"): + config["default_state"] = os.environ["SPECFACT_STANDUP_STATE"] + if os.environ.get("SPECFACT_STANDUP_LIMIT"): + with contextlib.suppress(ValueError): + config["limit"] = int(os.environ["SPECFACT_STANDUP_LIMIT"]) + if os.environ.get("SPECFACT_STANDUP_ASSIGNEE"): + config["default_assignee"] = os.environ["SPECFACT_STANDUP_ASSIGNEE"] + return config + + +def _load_backlog_config() -> dict[str, Any]: + """Load project backlog context from .specfact/backlog.yaml (no secrets). + Same search path as standup: SPECFACT_CONFIG_DIR then .specfact in cwd. + When file has top-level 'backlog' key, that nested structure is returned. + """ + config: dict[str, Any] = {} + config_dir = os.environ.get("SPECFACT_CONFIG_DIR") + search_paths: list[Path] = [] + if config_dir: + search_paths.append(Path(config_dir)) + search_paths.append(Path.cwd() / ".specfact") + for base in search_paths: + path = base / "backlog.yaml" + if path.is_file(): + try: + with open(path, encoding="utf-8") as f: + data = yaml.safe_load(f) or {} + if isinstance(data, dict) and "backlog" in data: + nested = data["backlog"] + config = dict(nested) if isinstance(nested, dict) else {} + else: + config = dict(data) if isinstance(data, dict) else {} + except Exception: + pass + break + return config + + +@beartype +def _resolve_standup_options( + cli_state: str | None, + cli_limit: int | None, + cli_assignee: str | None, + config: dict[str, Any] | None, +) -> tuple[str, int, str | None]: + """ + Resolve effective state, limit, assignee from CLI options and config. + CLI options override config; config overrides built-in defaults. + Returns (state, limit, assignee). + """ + cfg = config or _load_standup_config() + default_state = str(cfg.get("default_state", "open")) + default_limit = int(cfg.get("limit", 20)) if cfg.get("limit") is not None else 20 + default_assignee = cfg.get("default_assignee") + if default_assignee is not None: + default_assignee = str(default_assignee) + state = cli_state if cli_state is not None else default_state + limit = cli_limit if cli_limit is not None else default_limit + assignee = cli_assignee if cli_assignee is not None else default_assignee + return (state, limit, assignee) + + +@beartype +def _split_assigned_unassigned(items: list[BacklogItem]) -> tuple[list[BacklogItem], list[BacklogItem]]: + """Split items into assigned and unassigned (assignees empty or None).""" + assigned: list[BacklogItem] = [] + unassigned: list[BacklogItem] = [] + for item in items: + if item.assignees: + assigned.append(item) + else: + unassigned.append(item) + return (assigned, unassigned) + + +def _format_sprint_end_header(end_date: date) -> str: + """Format sprint end date as 'Sprint ends: YYYY-MM-DD (N days)'.""" + today = date.today() + delta = (end_date - today).days + return f"Sprint ends: {end_date.isoformat()} ({delta} days)" + + +@beartype +def _sort_standup_rows_blockers_first(rows: list[dict[str, Any]]) -> list[dict[str, Any]]: + """Sort standup rows so items with non-empty blockers appear first.""" + with_blockers = [r for r in rows if (r.get("blockers") or "").strip()] + without = [r for r in rows if not (r.get("blockers") or "").strip()] + return with_blockers + without + + +@beartype +def _build_standup_rows( + items: list[BacklogItem], + include_priority: bool = False, +) -> list[dict[str, Any]]: + """ + Build standup view rows from backlog items (id, title, status, last_updated, optional yesterday/today/blockers). + When include_priority is True and item has priority/business_value, add to row. + """ + rows: list[dict[str, Any]] = [] + for item in items: + yesterday, today, blockers = _parse_standup_from_body(item.body_markdown or "") + row: dict[str, Any] = { + "id": item.id, + "title": item.title, + "status": item.state, + "last_updated": item.updated_at, + "yesterday": yesterday or "", + "today": today or "", + "blockers": blockers or "", + } + if include_priority and item.priority is not None: + row["priority"] = item.priority + elif include_priority and item.business_value is not None: + row["priority"] = item.business_value + rows.append(row) + return rows + + +@beartype +def _format_standup_comment(yesterday: str, today: str, blockers: str) -> str: + """Format standup text as a comment (Yesterday / Today / Blockers) with date prefix.""" + prefix = f"Standup {date.today().isoformat()}" + parts = [prefix, ""] + if yesterday: + parts.append(f"**Yesterday:** {yesterday}") + if today: + parts.append(f"**Today:** {today}") + if blockers: + parts.append(f"**Blockers:** {blockers}") + return "\n".join(parts).strip() + + +@beartype +def _post_standup_comment_supported(adapter: BacklogAdapter, item: BacklogItem) -> bool: + """Return True if the adapter supports adding comments (e.g. for standup post).""" + return adapter.supports_add_comment() + + +@beartype +def _post_standup_to_item(adapter: BacklogAdapter, item: BacklogItem, body: str) -> bool: + """Post standup comment to the linked issue via adapter. Returns True on success.""" + return adapter.add_comment(item, body) + + +@beartype +@ensure( + lambda result: result is None or (isinstance(result, (int, float)) and result >= 0), + "Value score is non-negative when present", +) +def _compute_value_score(item: BacklogItem) -> float | None: + """ + Compute value score for next-best suggestion: business_value / max(1, story_points * priority). + + Returns None when any of story_points, business_value, or priority is missing. + """ + if item.story_points is None or item.business_value is None or item.priority is None: + return None + denom = max(1, (item.story_points or 0) * (item.priority or 1)) + return item.business_value / denom + + +@beartype +def _format_daily_item_detail(item: BacklogItem, comments: list[str]) -> str: + """ + Format a single backlog item for interactive detail view (refine-like). + + Includes ID, title, status, assignees, last updated, description, acceptance criteria, + standup fields (yesterday/today/blockers), and comments when provided. + """ + parts: list[str] = [] + parts.append(f"## {item.id} - {item.title}") + parts.append(f"- **Status:** {item.state}") + assignee_str = ", ".join(item.assignees) if item.assignees else "—" + parts.append(f"- **Assignees:** {assignee_str}") + updated = ( + item.updated_at.strftime("%Y-%m-%d %H:%M") if hasattr(item.updated_at, "strftime") else str(item.updated_at) + ) + parts.append(f"- **Last updated:** {updated}") + if item.body_markdown: + parts.append("\n**Description:**") + parts.append(item.body_markdown.strip()) + if item.acceptance_criteria: + parts.append("\n**Acceptance criteria:**") + parts.append(item.acceptance_criteria.strip()) + yesterday, today, blockers = _parse_standup_from_body(item.body_markdown or "") + if yesterday or today or blockers: + parts.append("\n**Standup:**") + if yesterday: + parts.append(f"- Yesterday: {yesterday}") + if today: + parts.append(f"- Today: {today}") + if blockers: + parts.append(f"- Blockers: {blockers}") + if item.story_points is not None: + parts.append(f"\n- **Story points:** {item.story_points}") + if item.business_value is not None: + parts.append(f"- **Business value:** {item.business_value}") + if item.priority is not None: + parts.append(f"- **Priority:** {item.priority}") + if comments: + parts.append("\n**Comments:**") + for c in comments: + parts.append(f"- {c}") + return "\n".join(parts) + + +@beartype +def _build_copilot_export_content( + items: list[BacklogItem], + include_value_score: bool = False, +) -> str: + """ + Build Markdown content for Copilot export: one section per item. + + Per item: ID, title, status, assignees, last updated, progress summary (standup fields), + blockers, and optionally value score. + """ + lines: list[str] = [] + lines.append("# Daily standup – Copilot export") + lines.append("") + for item in items: + lines.append(f"## {item.id} - {item.title}") + lines.append("") + lines.append(f"- **Status:** {item.state}") + assignee_str = ", ".join(item.assignees) if item.assignees else "—" + lines.append(f"- **Assignees:** {assignee_str}") + updated = ( + item.updated_at.strftime("%Y-%m-%d %H:%M") if hasattr(item.updated_at, "strftime") else str(item.updated_at) + ) + lines.append(f"- **Last updated:** {updated}") + yesterday, today, blockers = _parse_standup_from_body(item.body_markdown or "") + if yesterday or today: + lines.append(f"- **Progress:** Yesterday: {yesterday or '—'}; Today: {today or '—'}") + if blockers: + lines.append(f"- **Blockers:** {blockers}") + if item.story_points is not None: + lines.append(f"- **Story points:** {item.story_points}") + if item.priority is not None: + lines.append(f"- **Priority:** {item.priority}") + if include_value_score: + score = _compute_value_score(item) + if score is not None: + lines.append(f"- **Value score:** {score:.2f}") + lines.append("") + return "\n".join(lines).strip() + + +_SUMMARIZE_BODY_TRUNCATE = 1200 + + +@beartype +def _build_summarize_prompt_content( + items: list[BacklogItem], + filter_context: dict[str, Any], + include_value_score: bool = False, + comments_by_item_id: dict[str, list[str]] | None = None, +) -> str: + """ + Build prompt content for standup summary: instruction + filter context + per-item data. + + Includes body (description) and annotations (comments) per item so an LLM can produce + a meaningful summary. For use with slash command (e.g. specfact.daily) or copy-paste to Copilot. + """ + lines: list[str] = [] + lines.append("--- BEGIN STANDUP PROMPT ---") + lines.append("Generate a concise daily standup summary from the following data.") + lines.append( + "Include: current focus, blockers, and pending items. Use each item's description and comments for context. Keep it short and actionable." + ) + lines.append("") + lines.append("## Filter context") + lines.append(f"- Adapter: {filter_context.get('adapter', '—')}") + lines.append(f"- State: {filter_context.get('state', '—')}") + lines.append(f"- Sprint: {filter_context.get('sprint', '—')}") + lines.append(f"- Assignee: {filter_context.get('assignee', '—')}") + lines.append(f"- Limit: {filter_context.get('limit', '—')}") + lines.append("") + lines.append("## Standup data (with description and comments)") + lines.append("") + comments_map = comments_by_item_id or {} + for item in items: + lines.append(f"## {item.id} - {item.title}") + lines.append("") + lines.append(f"- **Status:** {item.state}") + assignee_str = ", ".join(item.assignees) if item.assignees else "—" + lines.append(f"- **Assignees:** {assignee_str}") + updated = ( + item.updated_at.strftime("%Y-%m-%d %H:%M") if hasattr(item.updated_at, "strftime") else str(item.updated_at) + ) + lines.append(f"- **Last updated:** {updated}") + body = (item.body_markdown or "").strip() + if body: + snippet = body[:_SUMMARIZE_BODY_TRUNCATE] + if len(body) > _SUMMARIZE_BODY_TRUNCATE: + snippet += "\n..." + lines.append("- **Description:**") + lines.append(snippet) + lines.append("") + yesterday, today, blockers = _parse_standup_from_body(item.body_markdown or "") + if yesterday or today: + lines.append(f"- **Progress:** Yesterday: {yesterday or '—'}; Today: {today or '—'}") + if blockers: + lines.append(f"- **Blockers:** {blockers}") + item_comments = comments_map.get(item.id, []) + if item_comments: + lines.append("- **Comments (annotations):**") + for c in item_comments: + lines.append(f" - {c}") + if item.story_points is not None: + lines.append(f"- **Story points:** {item.story_points}") + if item.priority is not None: + lines.append(f"- **Priority:** {item.priority}") + if include_value_score: + score = _compute_value_score(item) + if score is not None: + lines.append(f"- **Value score:** {score:.2f}") + lines.append("") + lines.append("--- END STANDUP PROMPT ---") + return "\n".join(lines).strip() + + +def _run_interactive_daily( + items: list[BacklogItem], + standup_config: dict[str, Any], + suggest_next: bool, + adapter: str, + repo_owner: str | None, + repo_name: str | None, + github_token: str | None, + ado_org: str | None, + ado_project: str | None, + ado_token: str | None, +) -> None: + """ + Run interactive step-by-step review: questionary selection, detail view, next/previous/back/exit. + """ + try: + import questionary # type: ignore[reportMissingImports] + except ImportError: + console.print( + "[red]Interactive mode requires the 'questionary' package. Install with: pip install questionary[/red]" + ) + raise typer.Exit(1) from None + + adapter_kwargs = _build_adapter_kwargs( + adapter, + repo_owner=repo_owner, + repo_name=repo_name, + github_token=github_token, + ado_org=ado_org, + ado_project=ado_project, + ado_token=ado_token, + ) + registry = AdapterRegistry() + adapter_instance = registry.get_adapter(adapter, **adapter_kwargs) + get_comments_fn = getattr(adapter_instance, "get_comments", lambda _: []) + + n = len(items) + choices = [ + f"{item.id} - {item.title[:50]}{'...' if len(item.title) > 50 else ''} [{item.state}] ({', '.join(item.assignees) or '—'})" + for item in items + ] + choices.append("Exit") + + while True: + selected = questionary.select("Select a story to review (or Exit)", choices=choices).ask() + if selected is None or selected == "Exit": + return + try: + idx = choices.index(selected) + except ValueError: + return + if idx >= n: + return + + current_idx = idx + while True: + item = items[current_idx] + comments: list[str] = [] + if callable(get_comments_fn): + with contextlib.suppress(Exception): + raw = get_comments_fn(item) + comments = list(raw) if isinstance(raw, list) else [] + detail = _format_daily_item_detail(item, comments) + console.print(Panel(detail, title=f"Story: {item.id}", border_style="cyan")) + + if suggest_next and n > 1: + pending = [i for i in items if not i.assignees or item.story_points is not None] + if pending: + best: BacklogItem | None = None + best_score: float = -1.0 + for i in pending: + s = _compute_value_score(i) + if s is not None and s > best_score: + best_score = s + best = i + if best is not None: + console.print( + f"[dim]Suggested next (value score {best_score:.2f}): {best.id} - {best.title}[/dim]" + ) + + nav_choices = ["Next story", "Previous story", "Back to list", "Exit"] + nav = questionary.select("Navigation", choices=nav_choices).ask() + if nav is None or nav == "Exit": + return + if nav == "Back to list": + break + if nav == "Next story": + current_idx = (current_idx + 1) % n + elif nav == "Previous story": + current_idx = (current_idx - 1) % n + + def _extract_openspec_change_id(body: str) -> str | None: """ Extract OpenSpec change proposal ID from issue body. @@ -160,6 +614,88 @@ def _extract_openspec_change_id(body: str) -> str | None: return None +def _infer_github_repo_from_cwd() -> tuple[str | None, str | None]: + """ + Infer repo_owner and repo_name from git remote origin when run inside a GitHub clone. + Returns (owner, repo) or (None, None) if not a GitHub remote or git unavailable. + """ + try: + result = subprocess.run( + ["git", "remote", "get-url", "origin"], + cwd=Path.cwd(), + capture_output=True, + text=True, + timeout=5, + check=False, + ) + if result.returncode != 0 or not result.stdout or not result.stdout.strip(): + return (None, None) + url = result.stdout.strip() + owner, repo = None, None + if url.startswith("git@"): + part = url.split(":", 1)[-1].strip() + if part.endswith(".git"): + part = part[:-4] + segments = part.split("/") + if len(segments) >= 2 and "github" in url.lower(): + owner, repo = segments[-2], segments[-1] + else: + parsed = urlparse(url) + if parsed.hostname and "github" in parsed.hostname.lower() and parsed.path: + path = parsed.path.strip("/") + if path.endswith(".git"): + path = path[:-4] + segments = path.split("/") + if len(segments) >= 2: + owner, repo = segments[-2], segments[-1] + return (owner or None, repo or None) + except Exception: + return (None, None) + + +def _infer_ado_context_from_cwd() -> tuple[str | None, str | None]: + """ + Infer org and project from git remote origin when run inside an Azure DevOps clone. + Returns (org, project) or (None, None) if not an ADO remote or git unavailable. + Supports: + - HTTPS: https://dev.azure.com/org/project/_git/repo + - SSH (keys): git@ssh.dev.azure.com:v3/<org>/<project>/<repo> + - SSH (other): <user>@dev.azure.com:v3/<org>/<project>/<repo> (no ssh. subdomain) + """ + try: + result = subprocess.run( + ["git", "remote", "get-url", "origin"], + cwd=Path.cwd(), + capture_output=True, + text=True, + timeout=5, + check=False, + ) + if result.returncode != 0 or not result.stdout or not result.stdout.strip(): + return (None, None) + url = result.stdout.strip() + org, project = None, None + if "dev.azure.com" not in url.lower(): + return (None, None) + if ":" in url and "v3/" in url: + idx = url.find("v3/") + if idx != -1: + part = url[idx + 3 :].strip() + segments = part.split("/") + if len(segments) >= 2: + org, project = segments[0], segments[1] + else: + parsed = urlparse(url) + if parsed.path: + path = parsed.path.strip("/") + segments = path.split("/") + if len(segments) >= 2: + org, project = segments[0], segments[1] + return (org or None, project or None) + except Exception: + return (None, None) + + def _build_adapter_kwargs( adapter: str, repo_owner: str | None = None, @@ -171,35 +707,43 @@ def _build_adapter_kwargs( ado_token: str | None = None, ) -> dict[str, Any]: """ - Build adapter kwargs based on adapter type and provided configuration. - - Args: - adapter: Adapter name (github, ado, etc.) - repo_owner: GitHub repository owner - repo_name: GitHub repository name - github_token: GitHub API token - ado_org: Azure DevOps organization - ado_project: Azure DevOps project - ado_token: Azure DevOps PAT - - Returns: - Dictionary of adapter kwargs + Build adapter kwargs from CLI args, then env, then .specfact/backlog.yaml. + Resolution order: explicit arg > env (SPECFACT_GITHUB_REPO_OWNER, etc.) > config. + Tokens are never read from config; only from explicit args (env handled by caller). """ + cfg = _load_backlog_config() kwargs: dict[str, Any] = {} if adapter.lower() == "github": - if repo_owner: - kwargs["repo_owner"] = repo_owner - if repo_name: - kwargs["repo_name"] = repo_name + owner = ( + repo_owner or os.environ.get("SPECFACT_GITHUB_REPO_OWNER") or (cfg.get("github") or {}).get("repo_owner") + ) + name = repo_name or os.environ.get("SPECFACT_GITHUB_REPO_NAME") or (cfg.get("github") or {}).get("repo_name") + if not owner or not name: + inferred_owner, inferred_name = _infer_github_repo_from_cwd() + if inferred_owner and inferred_name: + owner = owner or inferred_owner + name = name or inferred_name + if owner: + kwargs["repo_owner"] = owner + if name: + kwargs["repo_name"] = name if github_token: kwargs["api_token"] = github_token elif adapter.lower() == "ado": - if ado_org: - kwargs["org"] = ado_org - if ado_project: - kwargs["project"] = ado_project - if ado_team: - kwargs["team"] = ado_team + org = ado_org or os.environ.get("SPECFACT_ADO_ORG") or (cfg.get("ado") or {}).get("org") + project = ado_project or os.environ.get("SPECFACT_ADO_PROJECT") or (cfg.get("ado") or {}).get("project") + team = ado_team or os.environ.get("SPECFACT_ADO_TEAM") or (cfg.get("ado") or {}).get("team") + if not org or not project: + inferred_org, inferred_project = _infer_ado_context_from_cwd() + if inferred_org and inferred_project: + org = org or inferred_org + project = project or inferred_project + if org: + kwargs["org"] = org + if project: + kwargs["project"] = project + if team: + kwargs["team"] = team if ado_token: kwargs["api_token"] = ado_token return kwargs @@ -385,6 +929,27 @@ def _fetch_backlog_items( ado_token=ado_token, ) + if adapter_name.lower() == "github" and ( + not adapter_kwargs.get("repo_owner") or not adapter_kwargs.get("repo_name") + ): + console.print("[red]repo_owner and repo_name required for GitHub.[/red]") + console.print( + "Set via: [cyan]--repo-owner[/cyan]/[cyan]--repo-name[/cyan], " + "env [cyan]SPECFACT_GITHUB_REPO_OWNER[/cyan]/[cyan]SPECFACT_GITHUB_REPO_NAME[/cyan], " + "or [cyan].specfact/backlog.yaml[/cyan] (see docs/guides/devops-adapter-integration.md). " + "When run from a GitHub clone, org/repo are auto-detected from git remote." + ) + raise typer.Exit(1) + if adapter_name.lower() == "ado" and (not adapter_kwargs.get("org") or not adapter_kwargs.get("project")): + console.print("[red]ado_org and ado_project required for Azure DevOps.[/red]") + console.print( + "Set via: [cyan]--ado-org[/cyan]/[cyan]--ado-project[/cyan], " + "env [cyan]SPECFACT_ADO_ORG[/cyan]/[cyan]SPECFACT_ADO_PROJECT[/cyan], " + "or [cyan].specfact/backlog.yaml[/cyan]. " + "When run from an ADO clone, org/project are auto-detected from git remote." + ) + raise typer.Exit(1) + adapter = registry.get_adapter(adapter_name, **adapter_kwargs) # Check if adapter implements BacklogAdapter interface @@ -414,6 +979,322 @@ def _fetch_backlog_items( return items +@beartype +@app.command() +@require( + lambda adapter: isinstance(adapter, str) and len(adapter) > 0, + "Adapter must be non-empty string", +) +def daily( + adapter: str = typer.Argument(..., help="Backlog adapter name (github, ado, etc.)"), + assignee: str | None = typer.Option( + None, + "--assignee", + help="Filter by assignee (e.g. 'me' or username). Only matching items are listed.", + ), + state: str | None = typer.Option(None, "--state", help="Filter by state (e.g. open, closed, Active)"), + labels: list[str] | None = typer.Option(None, "--labels", "--tags", help="Filter by labels/tags"), + limit: int | None = typer.Option(None, "--limit", help="Maximum number of items to show"), + iteration: str | None = typer.Option( + None, + "--iteration", + help="Filter by iteration (e.g. 'current' or literal path). ADO: full path; adapter must support.", + ), + sprint: str | None = typer.Option( + None, + "--sprint", + help="Filter by sprint (e.g. 'current' or name). Adapter must support iteration/sprint.", + ), + show_unassigned: bool = typer.Option( + True, + "--show-unassigned/--no-show-unassigned", + help="Show unassigned/pending items in a second table (default: true).", + ), + unassigned_only: bool = typer.Option( + False, + "--unassigned-only", + help="Show only unassigned items (single table).", + ), + blockers_first: bool = typer.Option( + False, + "--blockers-first", + help="Sort so items with non-empty blockers appear first.", + ), + interactive: bool = typer.Option( + False, + "--interactive", + help="Step-by-step review: select items with arrow keys and view full detail (refine-like) and comments.", + ), + copilot_export: str | None = typer.Option( + None, + "--copilot-export", + help="Write summarized progress per story to a file for Copilot slash-command use during standup.", + ), + summarize: bool = typer.Option( + False, + "--summarize", + help="Output a prompt (instruction + filter context + standup data) for slash command or Copilot to generate a standup summary (prints to stdout).", + ), + summarize_to: str | None = typer.Option( + None, + "--summarize-to", + help="Write the summarize prompt to this file (alternative to --summarize stdout).", + ), + suggest_next: bool = typer.Option( + False, + "--suggest-next", + help="In interactive mode, show suggested next item by value score (business value / (story points * priority)).", + ), + post: bool = typer.Option( + False, + "--post", + help="Post standup comment to the first item's issue. Requires at least one of --yesterday, --today, --blockers with a value (adapter must support comments).", + ), + yesterday: str | None = typer.Option( + None, + "--yesterday", + help='Standup: what was done yesterday (used when posting with --post; pass a value e.g. --yesterday "Worked on X").', + ), + today: str | None = typer.Option( + None, + "--today", + help='Standup: what will be done today (used when posting with --post; pass a value e.g. --today "Will do Y").', + ), + blockers: str | None = typer.Option( + None, + "--blockers", + help='Standup: blockers (used when posting with --post; pass a value e.g. --blockers "None").', + ), + repo_owner: str | None = typer.Option(None, "--repo-owner", help="GitHub repository owner"), + repo_name: str | None = typer.Option(None, "--repo-name", help="GitHub repository name"), + github_token: str | None = typer.Option(None, "--github-token", help="GitHub API token"), + ado_org: str | None = typer.Option(None, "--ado-org", help="Azure DevOps organization"), + ado_project: str | None = typer.Option(None, "--ado-project", help="Azure DevOps project"), + ado_team: str | None = typer.Option( + None, "--ado-team", help="ADO team for current iteration (when --sprint current)" + ), + ado_token: str | None = typer.Option(None, "--ado-token", help="Azure DevOps PAT"), +) -> None: + """ + Show daily standup view: list my/filtered backlog items with status and last activity. + + Optional standup summary lines (yesterday/today/blockers) are shown when present in item body. + Use --post with --yesterday, --today, --blockers to post a standup comment to the first item's linked issue + (only when the adapter supports comments, e.g. GitHub). + Default scope: state=open, limit=20 (overridable via SPECFACT_STANDUP_* env or .specfact/standup.yaml). + """ + standup_config = _load_standup_config() + effective_state, effective_limit, effective_assignee = _resolve_standup_options( + state, limit, assignee, standup_config + ) + items = _fetch_backlog_items( + adapter, + state=effective_state, + assignee=effective_assignee, + labels=labels, + limit=effective_limit, + iteration=iteration, + sprint=sprint, + repo_owner=repo_owner, + repo_name=repo_name, + github_token=github_token, + ado_org=ado_org, + ado_project=ado_project, + ado_team=ado_team, + ado_token=ado_token, + ) + filtered = _apply_filters( + items, + labels=labels, + state=effective_state, + assignee=effective_assignee, + iteration=iteration, + sprint=sprint, + ) + if len(filtered) > effective_limit: + filtered = filtered[:effective_limit] + + if not filtered: + console.print("[yellow]No backlog items found.[/yellow]") + return + + if copilot_export is not None: + include_score = suggest_next or bool(standup_config.get("suggest_next")) + export_path = Path(copilot_export) + content = _build_copilot_export_content(filtered, include_value_score=include_score) + export_path.write_text(content, encoding="utf-8") + console.print(f"[dim]Exported {len(filtered)} item(s) to {export_path}[/dim]") + + if summarize or summarize_to is not None: + include_score = suggest_next or bool(standup_config.get("suggest_next")) + filter_ctx: dict[str, Any] = { + "adapter": adapter, + "state": effective_state or "—", + "sprint": sprint or iteration or "—", + "assignee": effective_assignee or "—", + "limit": effective_limit, + } + comments_by_item_id: dict[str, list[str]] = {} + try: + adapter_kwargs_sum = _build_adapter_kwargs( + adapter, + repo_owner=repo_owner, + repo_name=repo_name, + github_token=github_token, + ado_org=ado_org, + ado_project=ado_project, + ado_token=ado_token, + ) + registry_sum = AdapterRegistry() + adapter_instance_sum = registry_sum.get_adapter(adapter, **adapter_kwargs_sum) + get_comments_fn = getattr(adapter_instance_sum, "get_comments", None) + if callable(get_comments_fn): + for it in filtered: + with contextlib.suppress(Exception): + raw = get_comments_fn(it) + comments_by_item_id[it.id] = list(raw) if isinstance(raw, list) else [] + except Exception: + pass + content = _build_summarize_prompt_content( + filtered, + filter_context=filter_ctx, + include_value_score=include_score, + comments_by_item_id=comments_by_item_id or None, + ) + if summarize_to: + Path(summarize_to).write_text(content, encoding="utf-8") + console.print(f"[dim]Summarize prompt written to {summarize_to} ({len(filtered)} item(s))[/dim]") + else: + console.print(content) + return + + if interactive: + _run_interactive_daily( + filtered, + standup_config=standup_config, + suggest_next=suggest_next, + adapter=adapter, + repo_owner=repo_owner, + repo_name=repo_name, + github_token=github_token, + ado_org=ado_org, + ado_project=ado_project, + ado_token=ado_token, + ) + return + + first_item = filtered[0] + include_priority = bool(standup_config.get("show_priority") or standup_config.get("show_value")) + rows_unassigned: list[dict[str, Any]] = [] + if unassigned_only: + _, filtered = _split_assigned_unassigned(filtered) + if not filtered: + console.print("[yellow]No unassigned items in scope.[/yellow]") + return + rows = _build_standup_rows(filtered, include_priority=include_priority) + if blockers_first: + rows = _sort_standup_rows_blockers_first(rows) + else: + assigned, unassigned = _split_assigned_unassigned(filtered) + rows = _build_standup_rows(assigned, include_priority=include_priority) + if blockers_first: + rows = _sort_standup_rows_blockers_first(rows) + if show_unassigned and unassigned: + rows_unassigned = _build_standup_rows(unassigned, include_priority=include_priority) + + if post: + y = (yesterday or "").strip() + t = (today or "").strip() + b = (blockers or "").strip() + if not y and not t and not b: + console.print("[yellow]Use --yesterday, --today, and/or --blockers with values when using --post.[/yellow]") + console.print('[dim]Example: --yesterday "Worked on X" --today "Will do Y" --blockers "None" --post[/dim]') + return + body = _format_standup_comment(y, t, b) + item = first_item + registry = AdapterRegistry() + adapter_kwargs = _build_adapter_kwargs( + adapter, + repo_owner=repo_owner, + repo_name=repo_name, + github_token=github_token, + ado_org=ado_org, + ado_project=ado_project, + ado_token=ado_token, + ) + adapter_instance = registry.get_adapter(adapter, **adapter_kwargs) + if not isinstance(adapter_instance, BacklogAdapter): + console.print("[red]Adapter does not implement BacklogAdapter.[/red]") + raise typer.Exit(1) + if not _post_standup_comment_supported(adapter_instance, item): + console.print("[yellow]Posting comments is not supported for this adapter.[/yellow]") + return + ok = _post_standup_to_item(adapter_instance, item, body) + if ok: + console.print(f"[green]✓ Standup comment posted to {item.url}[/green]") + else: + console.print("[red]Failed to post standup comment.[/red]") + raise typer.Exit(1) + return + + sprint_end = standup_config.get("sprint_end_date") or os.environ.get("SPECFACT_STANDUP_SPRINT_END") + if sprint_end and (sprint or iteration): + try: + from datetime import datetime as dt + + end_date = dt.strptime(str(sprint_end)[:10], "%Y-%m-%d").date() + console.print(f"[dim]{_format_sprint_end_header(end_date)}[/dim]") + except (ValueError, TypeError): + pass + + def _add_standup_rows_to_table(tbl: Table, row_list: list[dict[str, Any]], include_pri: bool) -> None: + for r in row_list: + cells: list[Any] = [ + str(r["id"]), + str(r["title"])[:50], + str(r["status"]), + r["last_updated"].strftime("%Y-%m-%d %H:%M") + if hasattr(r["last_updated"], "strftime") + else str(r["last_updated"]), + (r.get("yesterday") or "")[:30], + (r.get("today") or "")[:30], + (r.get("blockers") or "")[:20], + ] + if include_pri and "priority" in r: + cells.append(str(r["priority"])) + tbl.add_row(*cells) + + table = Table(title="Daily standup", show_header=True, header_style="bold cyan") + table.add_column("ID", style="dim") + table.add_column("Title") + table.add_column("Status") + table.add_column("Last updated") + table.add_column("Yesterday", style="dim", max_width=30) + table.add_column("Today", style="dim", max_width=30) + table.add_column("Blockers", style="dim", max_width=20) + if include_priority: + table.add_column("Priority", style="dim") + _add_standup_rows_to_table(table, rows, include_priority) + console.print(table) + if not unassigned_only and show_unassigned and rows_unassigned: + table_pending = Table( + title="Pending / open for commitment", + show_header=True, + header_style="bold cyan", + ) + table_pending.add_column("ID", style="dim") + table_pending.add_column("Title") + table_pending.add_column("Status") + table_pending.add_column("Last updated") + table_pending.add_column("Yesterday", style="dim", max_width=30) + table_pending.add_column("Today", style="dim", max_width=30) + table_pending.add_column("Blockers", style="dim", max_width=20) + if include_priority: + table_pending.add_column("Priority", style="dim") + _add_standup_rows_to_table(table_pending, rows_unassigned, include_priority) + console.print(table_pending) + + @beartype @app.command() @require( @@ -652,25 +1533,36 @@ def refine( normalized_framework = framework.lower() if framework else None normalized_persona = persona.lower() if persona else None - # Validate adapter-specific required parameters + # Validate adapter-specific required parameters (use same resolution as daily: CLI > env > config > git) validate_task = init_progress.add_task("[cyan]Validating adapter configuration...[/cyan]", total=None) - if normalized_adapter == "github" and (not repo_owner or not repo_name): + writeback_kwargs = _build_adapter_kwargs( + adapter, + repo_owner=repo_owner, + repo_name=repo_name, + github_token=github_token, + ado_org=ado_org, + ado_project=ado_project, + ado_team=ado_team, + ado_token=ado_token, + ) + if normalized_adapter == "github" and ( + not writeback_kwargs.get("repo_owner") or not writeback_kwargs.get("repo_name") + ): init_progress.stop() - console.print("[red]Error:[/red] GitHub adapter requires both --repo-owner and --repo-name options") + console.print("[red]repo_owner and repo_name required for GitHub.[/red]") console.print( - "[yellow]Example:[/yellow] specfact backlog refine github " - "--repo-owner 'nold-ai' --repo-name 'specfact-cli' --state open" + "Set via: [cyan]--repo-owner[/cyan]/[cyan]--repo-name[/cyan], " + "env [cyan]SPECFACT_GITHUB_REPO_OWNER[/cyan]/[cyan]SPECFACT_GITHUB_REPO_NAME[/cyan], " + "or [cyan].specfact/backlog.yaml[/cyan] (see docs/guides/devops-adapter-integration.md)." ) - sys.exit(1) - if normalized_adapter == "ado" and (not ado_org or not ado_project): + raise typer.Exit(1) + if normalized_adapter == "ado" and (not writeback_kwargs.get("org") or not writeback_kwargs.get("project")): init_progress.stop() console.print( - "[red]Error:[/red] Azure DevOps adapter requires both --ado-org and --ado-project options" + "[red]ado_org and ado_project required for Azure DevOps.[/red] " + "Set via --ado-org/--ado-project, env SPECFACT_ADO_ORG/SPECFACT_ADO_PROJECT, or .specfact/backlog.yaml." ) - console.print( - "[yellow]Example:[/yellow] specfact backlog refine ado --ado-org 'my-org' --ado-project 'my-project' --state Active" - ) - sys.exit(1) + raise typer.Exit(1) # Validate and set custom field mapping (if provided) if custom_field_mapping: diff --git a/src/specfact_cli/common/logger_setup.py b/src/specfact_cli/common/logger_setup.py index bea8e829..d95eb474 100644 --- a/src/specfact_cli/common/logger_setup.py +++ b/src/specfact_cli/common/logger_setup.py @@ -19,6 +19,14 @@ # Add TRACE level (5) - more detailed than DEBUG (10) logging.addLevelName(5, "TRACE") + +def _safe_console_stream() -> Any: + """Return a stream for console logging that is not closed by pytest/CliRunner capture.""" + if os.environ.get("TEST_MODE") == "true" or os.environ.get("PYTEST_CURRENT_TEST"): + return sys.__stdout__ + return sys.stdout + + # Circular dependency protection flag # Note: Platform base infrastructure removed for lean CLI # The logger setup is now standalone without agent-system dependencies @@ -340,7 +348,7 @@ def create_agent_flow_logger(cls, session_id: str | None = None) -> logging.Logg file_handler.setFormatter(formatter) file_handler.setLevel(logging.INFO) # Also stream to console so run_local.sh can colorize per agent - console_handler = logging.StreamHandler(sys.stdout) + console_handler = logging.StreamHandler(_safe_console_stream()) console_handler.setFormatter(formatter) console_handler.setLevel(logging.INFO) @@ -507,7 +515,7 @@ def create_logger( log_queue = Queue(-1) cls._log_queues[logger_name] = log_queue - console_handler = logging.StreamHandler(sys.stdout) + console_handler = logging.StreamHandler(_safe_console_stream()) console_handler.setFormatter(log_format) console_handler.setLevel(level) @@ -520,7 +528,7 @@ def create_logger( # Add a console handler for non-test environments or when no file is specified if "pytest" not in sys.modules and not any(isinstance(h, logging.StreamHandler) for h in logger.handlers): - console_handler = logging.StreamHandler(sys.stdout) + console_handler = logging.StreamHandler(_safe_console_stream()) console_handler.setFormatter(log_format) console_handler.setLevel(level) logger.addHandler(console_handler) diff --git a/src/specfact_cli/runtime.py b/src/specfact_cli/runtime.py index c215cb3a..5c3d80bd 100644 --- a/src/specfact_cli/runtime.py +++ b/src/specfact_cli/runtime.py @@ -170,6 +170,11 @@ def get_terminal_mode() -> TerminalMode: return TerminalMode.BASIC +def _is_test_env() -> bool: + """True when running under pytest or TEST_MODE (avoids caching Console with closed streams).""" + return os.environ.get("TEST_MODE") == "true" or os.environ.get("PYTEST_CURRENT_TEST") is not None + + @beartype @ensure(lambda result: isinstance(result, Console), "Must return Console") def get_configured_console() -> Console: @@ -177,12 +182,15 @@ def get_configured_console() -> Console: Get or create configured Console instance based on terminal capabilities. Caches Console instance per terminal mode to avoid repeated detection. - - Returns: - Configured Rich Console instance + In test mode, never caches so we never hold a reference to a closed stream + (e.g. CliRunner's captured stdout after invoke() ends). """ mode = get_terminal_mode() + if _is_test_env(): + config = get_console_config() + return Console(**config) + if mode not in _console_cache: config = get_console_config() _console_cache[mode] = Console(**config) diff --git a/tests/e2e/test_terminal_output_modes.py b/tests/e2e/test_terminal_output_modes.py index b97e3e5b..14553cce 100644 --- a/tests/e2e/test_terminal_output_modes.py +++ b/tests/e2e/test_terminal_output_modes.py @@ -55,8 +55,9 @@ def test_minimal_terminal_mode_test(self, tmp_path: Path) -> None: assert mode == TerminalMode.MINIMAL def test_console_consistency(self, tmp_path: Path) -> None: - """Test that get_configured_console returns consistent instances.""" + """Test that get_configured_console returns Console with consistent config.""" console1 = get_configured_console() console2 = get_configured_console() - # Should return the same instance (cached) - assert console1 is console2 + # In test mode we do not cache (avoids closed-stream refs); config should be consistent. + assert console1 is not None and console2 is not None + assert console1.width == console2.width diff --git a/tests/unit/commands/test_backlog_config.py b/tests/unit/commands/test_backlog_config.py new file mode 100644 index 00000000..d38d942e --- /dev/null +++ b/tests/unit/commands/test_backlog_config.py @@ -0,0 +1,229 @@ +""" +Unit tests for project backlog context (.specfact/backlog.yaml). + +Scenarios from openspec/changes/daily-standup-progress-support/specs/daily-standup/spec.md: +- Project backlog context: adapter context (org, project per adapter) from file when not passed +- Resolution order: CLI > env > file; tokens never from file +""" + +from __future__ import annotations + +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from specfact_cli.commands.backlog_commands import ( + _build_adapter_kwargs, + _infer_ado_context_from_cwd, + _load_backlog_config, +) + + +class TestLoadBacklogConfig: + """_load_backlog_config: read .specfact/backlog.yaml (no secrets).""" + + def test_returns_empty_dict_when_no_file(self, monkeypatch: pytest.MonkeyPatch) -> None: + """When no backlog.yaml exists, return empty dict.""" + monkeypatch.delenv("SPECFACT_CONFIG_DIR", raising=False) + monkeypatch.chdir(Path(__file__).resolve().parents[3]) + assert _load_backlog_config() == {} + + def test_loads_github_section_from_file(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """When .specfact/backlog.yaml has github.repo_owner and repo_name, return them.""" + monkeypatch.setenv("SPECFACT_CONFIG_DIR", str(tmp_path)) + (tmp_path / "backlog.yaml").write_text( + "github:\n repo_owner: myorg\n repo_name: myrepo\n", + encoding="utf-8", + ) + cfg = _load_backlog_config() + assert cfg.get("github", {}).get("repo_owner") == "myorg" + assert cfg.get("github", {}).get("repo_name") == "myrepo" + + def test_loads_ado_section_from_file(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """When .specfact/backlog.yaml has ado org, project, team, return them.""" + monkeypatch.setenv("SPECFACT_CONFIG_DIR", str(tmp_path)) + (tmp_path / "backlog.yaml").write_text( + "ado:\n org: myorg\n project: MyProject\n team: My Team\n", + encoding="utf-8", + ) + cfg = _load_backlog_config() + assert cfg.get("ado", {}).get("org") == "myorg" + assert cfg.get("ado", {}).get("project") == "MyProject" + assert cfg.get("ado", {}).get("team") == "My Team" + + def test_uses_top_level_backlog_key_when_present(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """When file has top-level 'backlog' key, nested structure is used.""" + monkeypatch.setenv("SPECFACT_CONFIG_DIR", str(tmp_path)) + (tmp_path / "backlog.yaml").write_text( + "backlog:\n github:\n repo_owner: foo\n repo_name: bar\n", + encoding="utf-8", + ) + cfg = _load_backlog_config() + assert cfg.get("github", {}).get("repo_owner") == "foo" + assert cfg.get("github", {}).get("repo_name") == "bar" + + +class TestBuildAdapterKwargsWithConfig: + """_build_adapter_kwargs: merge CLI > env > config; tokens never from config.""" + + def test_github_uses_explicit_args_over_config(self) -> None: + """When repo_owner/repo_name passed, they are used; config ignored for those.""" + with patch( + "specfact_cli.commands.backlog_commands._load_backlog_config", + return_value={"github": {"repo_owner": "fromfile", "repo_name": "fromfile"}}, + ): + kwargs = _build_adapter_kwargs( + "github", + repo_owner="fromcli", + repo_name="fromcli", + ) + assert kwargs["repo_owner"] == "fromcli" + assert kwargs["repo_name"] == "fromcli" + + def test_github_uses_config_when_args_none(self) -> None: + """When repo_owner/repo_name not passed, values from config are used.""" + with patch( + "specfact_cli.commands.backlog_commands._load_backlog_config", + return_value={"github": {"repo_owner": "myorg", "repo_name": "myrepo"}}, + ): + kwargs = _build_adapter_kwargs("github", repo_owner=None, repo_name=None) + assert kwargs.get("repo_owner") == "myorg" + assert kwargs.get("repo_name") == "myrepo" + + def test_github_env_overrides_config(self, monkeypatch: pytest.MonkeyPatch) -> None: + """When env SPECFACT_GITHUB_REPO_OWNER is set, it overrides config.""" + monkeypatch.setenv("SPECFACT_GITHUB_REPO_OWNER", "fromenv") + monkeypatch.setenv("SPECFACT_GITHUB_REPO_NAME", "fromenv") + with patch( + "specfact_cli.commands.backlog_commands._load_backlog_config", + return_value={"github": {"repo_owner": "fromfile", "repo_name": "fromfile"}}, + ): + kwargs = _build_adapter_kwargs("github", repo_owner=None, repo_name=None) + assert kwargs.get("repo_owner") == "fromenv" + assert kwargs.get("repo_name") == "fromenv" + + def test_ado_uses_config_when_args_none(self) -> None: + """When ado_org/ado_project not passed, values from config are used.""" + with patch( + "specfact_cli.commands.backlog_commands._load_backlog_config", + return_value={ + "ado": {"org": "myorg", "project": "MyProject", "team": "My Team"}, + }, + ): + kwargs = _build_adapter_kwargs( + "ado", + ado_org=None, + ado_project=None, + ado_team=None, + ) + assert kwargs.get("org") == "myorg" + assert kwargs.get("project") == "MyProject" + assert kwargs.get("team") == "My Team" + + def test_tokens_never_from_config(self) -> None: + """Tokens (api_token) are only from explicit args; config is not used for tokens.""" + with patch( + "specfact_cli.commands.backlog_commands._load_backlog_config", + return_value={ + "github": {"repo_owner": "o", "repo_name": "r", "api_token": "never"}, + }, + ): + kwargs = _build_adapter_kwargs( + "github", + repo_owner=None, + repo_name=None, + github_token="fromcli", + ) + assert kwargs.get("api_token") == "fromcli" + assert "never" not in str(kwargs) + + +class TestInferAdoContextFromCwd: + """_infer_ado_context_from_cwd: infer org/project from git remote when run in ADO clone.""" + + def test_returns_org_project_from_https_url(self) -> None: + """HTTPS dev.azure.com/org/project/_git/repo returns (org, project).""" + with patch( + "specfact_cli.commands.backlog_commands.subprocess.run", + return_value=MagicMock( + returncode=0, + stdout="https://dev.azure.com/myorg/MyProject/_git/myrepo\n", + ), + ): + org, project = _infer_ado_context_from_cwd() + assert org == "myorg" + assert project == "MyProject" + + def test_returns_org_project_from_ssh_url(self) -> None: + """SSH git@ssh.dev.azure.com:v3/org/project/repo returns (org, project).""" + with patch( + "specfact_cli.commands.backlog_commands.subprocess.run", + return_value=MagicMock( + returncode=0, + stdout="git@ssh.dev.azure.com:v3/myorg/MyProject/myrepo\n", + ), + ): + org, project = _infer_ado_context_from_cwd() + assert org == "myorg" + assert project == "MyProject" + + def test_returns_org_project_from_ssh_url_with_user(self) -> None: + """SSH user@ssh.dev.azure.com:v3/org/project/repo (as in .git/config) returns (org, project).""" + with patch( + "specfact_cli.commands.backlog_commands.subprocess.run", + return_value=MagicMock( + returncode=0, + stdout="user@ssh.dev.azure.com:v3/myorg/MyProject/myrepo\n", + ), + ): + org, project = _infer_ado_context_from_cwd() + assert org == "myorg" + assert project == "MyProject" + + def test_returns_org_project_from_ssh_url_dev_azure_no_ssh_subdomain(self) -> None: + """SSH user@dev.azure.com:v3/org/project/repo (no ssh. subdomain, as in some .git/config) returns (org, project).""" + with patch( + "specfact_cli.commands.backlog_commands.subprocess.run", + return_value=MagicMock( + returncode=0, + stdout="user@dev.azure.com:v3/myorg/MyProject/myrepo\n", + ), + ): + org, project = _infer_ado_context_from_cwd() + assert org == "myorg" + assert project == "MyProject" + + def test_returns_none_when_not_ado_remote(self) -> None: + """GitHub remote returns (None, None).""" + with patch( + "specfact_cli.commands.backlog_commands.subprocess.run", + return_value=MagicMock( + returncode=0, + stdout="https://github.com/owner/repo\n", + ), + ): + org, project = _infer_ado_context_from_cwd() + assert org is None + assert project is None + + def test_ado_uses_inferred_when_args_none(self) -> None: + """When ado_org/ado_project not passed, inferred from git is used.""" + with ( + patch( + "specfact_cli.commands.backlog_commands._load_backlog_config", + return_value={}, + ), + patch( + "specfact_cli.commands.backlog_commands._infer_ado_context_from_cwd", + return_value=("inferred-org", "inferred-project"), + ), + ): + kwargs = _build_adapter_kwargs( + "ado", + ado_org=None, + ado_project=None, + ado_team=None, + ) + assert kwargs.get("org") == "inferred-org" + assert kwargs.get("project") == "inferred-project" diff --git a/tests/unit/commands/test_backlog_daily.py b/tests/unit/commands/test_backlog_daily.py new file mode 100644 index 00000000..923e7d9b --- /dev/null +++ b/tests/unit/commands/test_backlog_daily.py @@ -0,0 +1,522 @@ +""" +Unit tests for specfact backlog daily (standup view and optional comment). + +Scenarios from openspec/changes/daily-standup-progress-support/specs/daily-standup/spec.md: +- Standup view lists items with status and last-updated; optional standup summary lines +- Assignee filter +- Post standup comment (mock adapter) +- Adapter without comment support reports clearly +- Default standup scope (state/limit when not passed) +- Current iteration/sprint focus +- Unassigned/pending items view +- Sprint/iteration end date display +- Blockers-first and optional priority +- Interactive step-by-step review (--interactive, detail view, navigation) +- Export to file for Copilot (--copilot-export <path>) +- Optional value score and next-best suggestion +- Summarize prompt (--summarize [path]) for slash command / Copilot standup summary +- specfact.backlog-daily prompt file for interactive team walkthrough +""" + +from __future__ import annotations + +from datetime import UTC, datetime +from pathlib import Path +from unittest.mock import MagicMock + +from typer.testing import CliRunner + +from specfact_cli.backlog.adapters.base import BacklogAdapter +from specfact_cli.cli import app +from specfact_cli.commands.backlog_commands import ( + _apply_filters, + _build_copilot_export_content, + _build_standup_rows, + _build_summarize_prompt_content, + _compute_value_score, + _format_daily_item_detail, + _format_standup_comment, + _post_standup_comment_supported, +) +from specfact_cli.models.backlog_item import BacklogItem + + +runner = CliRunner() + + +def _item( + id_: str = "1", + title: str = "Item", + state: str = "open", + updated_at: datetime | None = None, + assignees: list[str] | None = None, + body_markdown: str = "", + iteration: str | None = None, + sprint: str | None = None, + priority: int | None = None, + business_value: int | None = None, + story_points: int | None = None, + acceptance_criteria: str | None = None, +) -> BacklogItem: + return BacklogItem( + id=id_, + provider="github", + url=f"https://github.com/o/r/issues/{id_}", + title=title, + body_markdown=body_markdown, + state=state, + assignees=assignees or [], + updated_at=updated_at or datetime.now(UTC), + iteration=iteration, + sprint=sprint, + priority=priority, + business_value=business_value, + story_points=story_points, + acceptance_criteria=acceptance_criteria, + ) + + +class TestBuildStandupRows: + """Scenario: List my items with status and last activity.""" + + def test_lists_items_with_id_title_status_last_updated(self) -> None: + """Standup view lists items with id, title, status, last-updated.""" + items = [ + _item("1", "First", "open", datetime(2025, 2, 1, 10, 0, tzinfo=UTC)), + _item("2", "Second", "closed", datetime(2025, 2, 2, 11, 0, tzinfo=UTC)), + ] + rows = _build_standup_rows(items) + assert len(rows) == 2 + assert rows[0]["id"] == "1" and rows[0]["title"] == "First" and rows[0]["status"] == "open" + assert rows[0]["last_updated"] == datetime(2025, 2, 1, 10, 0, tzinfo=UTC) + assert rows[1]["id"] == "2" and rows[1]["title"] == "Second" and rows[1]["status"] == "closed" + assert rows[1]["last_updated"] == datetime(2025, 2, 2, 11, 0, tzinfo=UTC) + + def test_optional_standup_summary_lines_when_in_body(self) -> None: + """Optional standup summary lines (yesterday/today/blockers) shown when in body.""" + body = "Description\n\n**Yesterday:** Did X.\n**Today:** Will do Y.\n**Blockers:** None." + items = [_item("1", "Task", body_markdown=body)] + rows = _build_standup_rows(items) + assert len(rows) == 1 + assert "Yesterday" in (rows[0].get("yesterday") or "") or "Did X" in (rows[0].get("yesterday") or "") + assert "Today" in (rows[0].get("today") or "") or "Will do Y" in (rows[0].get("today") or "") + assert "Blockers" in (rows[0].get("blockers") or "") or "None" in (rows[0].get("blockers") or "") + + def test_assignee_filter_applied_by_caller(self) -> None: + """Assignee filter is applied by caller via _apply_filters; rows reflect filtered items.""" + items = [ + _item("1", "Mine", assignees=["me"]), + _item("2", "Other", assignees=["other"]), + ] + rows = _build_standup_rows(items) + assert len(rows) == 2 + rows_me = _build_standup_rows([items[0]]) + assert len(rows_me) == 1 and rows_me[0]["title"] == "Mine" + + +class TestFormatStandupComment: + """Format standup comment for posting (Yesterday / Today / Blockers).""" + + def test_formats_standup_comment_with_prefix(self) -> None: + """Comment is clearly identifiable (e.g. Standup YYYY-MM-DD).""" + from datetime import date + + text = _format_standup_comment("Did X", "Will Y", "None") + today = date.today().isoformat() + assert "Standup" in text or today in text + assert "Yesterday" in text or "Did X" in text + assert "Today" in text or "Will Y" in text + assert "Blockers" in text or "None" in text + + +class TestPostStandupCommentSupported: + """Scenario: Adapter does not support comments -> report clearly.""" + + def test_adapter_without_comment_support_returns_false(self) -> None: + """When adapter does not support comments, report that posting is not supported.""" + mock = MagicMock(spec=BacklogAdapter) + mock.supports_add_comment.return_value = False + item = _item("1", "Task") + supported = _post_standup_comment_supported(mock, item) + assert supported is False + + def test_adapter_with_comment_support_returns_true(self) -> None: + """When adapter supports comments (supports_add_comment returns True), posting is supported.""" + mock = MagicMock(spec=BacklogAdapter) + mock.supports_add_comment.return_value = True + item = _item("1", "Task") + supported = _post_standup_comment_supported(mock, item) + assert supported is True + + +class TestPostStandupCommentViaAdapter: + """Scenario: Post standup comment via GitHub adapter (mock).""" + + def test_post_standup_comment_calls_adapter_add_comment(self) -> None: + """When user opts in and adapter supports comments, add_comment is called.""" + from specfact_cli.commands.backlog_commands import _post_standup_to_item + + mock = MagicMock(spec=BacklogAdapter) + mock.add_comment.return_value = True + item = _item("1", "Task") + body = _format_standup_comment("X", "Y", "Z") + ok = _post_standup_to_item(mock, item, body) + assert ok is True + mock.add_comment.assert_called_once_with(item, body) + + def test_post_standup_comment_failure_reported(self) -> None: + """When add_comment returns False, success is False.""" + from specfact_cli.commands.backlog_commands import _post_standup_to_item + + mock = MagicMock(spec=BacklogAdapter) + mock.add_comment.return_value = False + item = _item("1", "Task") + ok = _post_standup_to_item(mock, item, "Standup text") + assert ok is False + + +class TestBacklogDailyCli: + """CLI: specfact backlog daily.""" + + def test_daily_help(self) -> None: + """Backlog daily subcommand exists and shows help.""" + result = runner.invoke(app, ["backlog", "daily", "--help"]) + assert result.exit_code == 0 + assert "daily" in result.output.lower() + + def test_daily_accepts_sprint_and_iteration_options(self) -> None: + """Backlog daily has --sprint and --iteration options.""" + result = runner.invoke(app, ["backlog", "daily", "--help"]) + assert result.exit_code == 0 + # Help may include ANSI codes (e.g. on CI); check option names as substrings + assert "sprint" in result.output.lower() + assert "iteration" in result.output.lower() + + def test_daily_accepts_show_unassigned_and_unassigned_only(self) -> None: + """Backlog daily has --show-unassigned and --unassigned-only options.""" + result = runner.invoke(app, ["backlog", "daily", "--help"]) + assert result.exit_code == 0 + assert "unassigned" in result.output.lower() + + def test_daily_accepts_blockers_first(self) -> None: + """Backlog daily has --blockers-first option.""" + result = runner.invoke(app, ["backlog", "daily", "--help"]) + assert result.exit_code == 0 + assert "blockers-first" in result.output or "blockers" in result.output.lower() + + +class TestDefaultStandupScope: + """Scenario: Standup view uses default scope when no filters given (6.1).""" + + def test_resolve_standup_options_uses_defaults_when_none(self) -> None: + """When state/limit/assignee not passed, effective state is open and limit is 20.""" + from specfact_cli.commands.backlog_commands import _resolve_standup_options + + state, limit, assignee = _resolve_standup_options(None, None, None, None) + assert state == "open" + assert limit == 20 + assert assignee is None + + def test_resolve_standup_options_explicit_overrides_defaults(self) -> None: + """Explicit --state and --limit override defaults.""" + from specfact_cli.commands.backlog_commands import _resolve_standup_options + + state, limit, assignee = _resolve_standup_options("closed", 10, None, None) + assert state == "closed" + assert limit == 10 + assert assignee is None + + def test_apply_filters_with_state_open_excludes_closed(self) -> None: + """Default state 'open' excludes closed items.""" + items = [ + _item("1", "Open", state="open"), + _item("2", "Closed", state="closed"), + ] + filtered = _apply_filters(items, state="open") + assert len(filtered) == 1 + assert filtered[0].state == "open" + + +class TestCurrentIterationSprint: + """Scenario: Standup view filtered to current iteration/sprint (6.2).""" + + def test_apply_filters_by_iteration(self) -> None: + """When --iteration is used, only items in that iteration are listed.""" + items = [ + _item("1", "In Sprint 1", iteration="Project\\Sprint 1"), + _item("2", "In Sprint 2", iteration="Project\\Sprint 2"), + ] + filtered = _apply_filters(items, iteration="Project\\Sprint 1") + assert len(filtered) == 1 + assert filtered[0].iteration == "Project\\Sprint 1" + + def test_apply_filters_by_sprint(self) -> None: + """When --sprint is used, only items in that sprint are listed.""" + items = [ + _item("1", "Sprint A", sprint="Sprint A"), + _item("2", "Sprint B", sprint="Sprint B"), + ] + filtered = _apply_filters(items, sprint="Sprint A") + assert len(filtered) == 1 + assert filtered[0].sprint == "Sprint A" + + def test_apply_filters_iteration_none_keeps_all_when_no_filter(self) -> None: + """When iteration/sprint not passed, all items pass (no crash).""" + items = [ + _item("1", "A", iteration="S1"), + _item("2", "B", iteration="S2"), + ] + filtered = _apply_filters(items) + assert len(filtered) == 2 + + +class TestUnassignedItems: + """Scenario: Unassigned items in separate table/section (6.3).""" + + def test_split_assigned_vs_unassigned(self) -> None: + """Standup view splits items into assigned and unassigned.""" + from specfact_cli.commands.backlog_commands import _split_assigned_unassigned + + items = [ + _item("1", "Mine", assignees=["me"]), + _item("2", "Unassigned", assignees=[]), + _item("3", "Other", assignees=["other"]), + ] + assigned, unassigned = _split_assigned_unassigned(items) + assert len(assigned) == 2 + assert len(unassigned) == 1 + assert unassigned[0].title == "Unassigned" + + def test_unassigned_only_filters_to_unassigned(self) -> None: + """When unassigned_only, only unassigned items in scope.""" + from specfact_cli.commands.backlog_commands import _split_assigned_unassigned + + items = [ + _item("1", "A", assignees=["me"]), + _item("2", "B", assignees=[]), + ] + _, unassigned = _split_assigned_unassigned(items) + assert len(unassigned) == 1 + assert unassigned[0].assignees == [] + + +class TestSprintIterationEndDate: + """Scenario: Sprint/iteration end date displayed when available (6.4).""" + + def test_format_sprint_end_header(self) -> None: + """When sprint end date provided, format as 'Sprint ends: YYYY-MM-DD (N days)'.""" + from datetime import date + + from specfact_cli.commands.backlog_commands import _format_sprint_end_header + + end = date(2025, 2, 15) + header = _format_sprint_end_header(end) + assert "Sprint ends" in header or "2025-02-15" in header + assert "days" in header.lower() or "15" in header + + +class TestBlockersFirstAndOptionalPriority: + """Scenario: Blockers first and optional priority column (6.5).""" + + def test_standup_rows_blockers_first(self) -> None: + """When blockers-first, items with non-empty blockers appear first.""" + from specfact_cli.commands.backlog_commands import _build_standup_rows, _sort_standup_rows_blockers_first + + body_no = "Description only." + body_yes = "**Blockers:** Waiting on API." + items = [ + _item("1", "No blocker", body_markdown=body_no), + _item("2", "Has blocker", body_markdown=body_yes), + ] + rows = _build_standup_rows(items) + sorted_rows = _sort_standup_rows_blockers_first(rows) + assert len(sorted_rows) == 2 + first_blockers = (sorted_rows[0].get("blockers") or "").strip() + assert "Waiting" in first_blockers or "API" in first_blockers + + def test_standup_rows_include_priority_when_enabled(self) -> None: + """When config enables priority and BacklogItem has priority, row has priority.""" + from specfact_cli.commands.backlog_commands import _build_standup_rows + + items = [_item("1", "P1 item", priority=1)] + rows = _build_standup_rows(items, include_priority=True) + assert len(rows) == 1 + assert rows[0].get("priority") is not None + assert rows[0]["priority"] == 1 + + +class TestComputeValueScore: + """Scenario: Optional value score for next-best suggestion (13.3).""" + + def test_value_score_computed_when_all_present(self) -> None: + """When story_points, business_value, priority are available, value_score = business_value / max(1, story_points * priority).""" + item = _item("1", "Story", story_points=5, business_value=20, priority=2) + score = _compute_value_score(item) + assert score is not None + assert score == 2.0 # 20 / (5 * 2) + + def test_value_score_omitted_when_data_missing(self) -> None: + """When any of story_points, business_value, priority is missing, score is None.""" + assert _compute_value_score(_item("1", "A")) is None + assert _compute_value_score(_item("1", "A", story_points=1)) is None + assert _compute_value_score(_item("1", "A", business_value=10)) is None + assert ( + _compute_value_score(_item("1", "A", story_points=0, business_value=10, priority=1)) is not None + ) # max(1,0)=1 + + +class TestBuildCopilotExportContent: + """Scenario: Copilot export writes summarized items (13.2).""" + + def test_copilot_export_has_section_per_item(self) -> None: + """When building Copilot export, content has one Markdown section per item with ID, title, status.""" + items = [ + _item("1", "First story", state="open", assignees=["alice"]), + _item("2", "Second story", state="Active", assignees=[]), + ] + content = _build_copilot_export_content(items, include_value_score=False) + assert "1" in content and "First story" in content and "open" in content + assert "2" in content and "Second story" in content and "Active" in content + assert "## " in content + assert content.count("## ") >= 2 + + def test_copilot_export_idempotent_format(self) -> None: + """Export format is Markdown with headings and bullets for Copilot use.""" + items = [_item("1", "Title", body_markdown="**Yesterday:** X.")] + content = _build_copilot_export_content(items, include_value_score=False) + assert "## " in content + assert "Title" in content + assert "- " in content or "* " in content or "\n" in content + + +class TestFormatDailyItemDetail: + """Scenario: Interactive detail view refine-like (13.1).""" + + def test_format_daily_item_detail_includes_title_body_status(self) -> None: + """Detail view includes ID, title, status, description/body.""" + item = _item("1", "My story", body_markdown="Description here.", acceptance_criteria="AC1") + detail = _format_daily_item_detail(item, comments=[]) + assert "1" in detail and "My story" in detail + assert "Description" in detail or "here" in detail + assert "open" in detail.lower() or "status" in detail.lower() + + def test_format_daily_item_detail_includes_comments_when_provided(self) -> None: + """When comments are provided, they appear in the detail string.""" + item = _item("1", "Story") + detail = _format_daily_item_detail(item, comments=["Comment one", "Comment two"]) + assert "Comment one" in detail or "Comment" in detail + assert "Comment two" in detail or "two" in detail + + +class TestBacklogDailyInteractiveAndExportOptions: + """CLI: --interactive and --copilot-export options (13.1, 13.2).""" + + def test_daily_help_shows_interactive(self) -> None: + """Backlog daily has --interactive option.""" + result = runner.invoke(app, ["backlog", "daily", "--help-advanced"]) + assert result.exit_code == 0 + assert "--interactive" in result.output or "interactive" in result.output.lower() + + def test_daily_help_shows_copilot_export(self) -> None: + """Backlog daily has --copilot-export option.""" + result = runner.invoke(app, ["backlog", "daily", "--help-advanced"]) + assert result.exit_code == 0 + assert "copilot-export" in result.output or "copilot" in result.output.lower() + + def test_daily_help_shows_summarize(self) -> None: + """Backlog daily has --summarize and --summarize-to options.""" + result = runner.invoke(app, ["backlog", "daily", "--help-advanced"]) + assert result.exit_code == 0 + assert "summarize" in result.output.lower() + + +class TestBuildSummarizePromptContent: + """Scenario: --summarize outputs prompt with filter context and per-item data (22.1).""" + + def test_summarize_prompt_contains_instruction_and_filter_context(self) -> None: + """Summarize prompt contains instruction to generate standup summary and filter context.""" + items = [_item("1", "First", state="open", assignees=["alice"])] + filter_ctx = { + "adapter": "github", + "state": "open", + "sprint": "current", + "assignee": "me", + "limit": 20, + } + content = _build_summarize_prompt_content(items, filter_context=filter_ctx, include_value_score=False) + assert "Generate" in content or "summary" in content.lower() + assert "Filter context" in content or "filter" in content.lower() + assert "github" in content + assert "open" in content + assert "current" in content + assert "20" in content + + def test_summarize_prompt_contains_per_item_data(self) -> None: + """Summarize prompt contains same per-item data as copilot export (ID, title, status).""" + items = [ + _item("1", "First story", state="open", assignees=["alice"]), + _item("2", "Second story", state="Active"), + ] + content = _build_summarize_prompt_content( + items, + filter_context={"adapter": "ado", "state": "—", "sprint": "—", "assignee": "—", "limit": 10}, + include_value_score=False, + ) + assert "1" in content and "First story" in content + assert "2" in content and "Second story" in content + assert "## " in content + + def test_summarize_prompt_includes_body_and_comments_when_provided(self) -> None: + """Summarize prompt includes description (body) and comments so LLM can create meaningful summary.""" + items = [ + _item( + "1", + "Story one", + state="open", + body_markdown="This is the issue description and context.", + ), + ] + comments_by_id = {"1": ["Comment from Alice: In progress.", "Comment from Bob: Blocked on API."]} + content = _build_summarize_prompt_content( + items, + filter_context={"adapter": "github", "state": "open", "sprint": "—", "assignee": "—", "limit": 20}, + include_value_score=False, + comments_by_item_id=comments_by_id, + ) + assert "Description" in content and "issue description" in content + assert "Comments" in content or "annotations" in content + assert "In progress" in content and "Blocked on API" in content + + def test_summarize_prompt_has_start_end_markers(self) -> None: + """Summarize prompt is wrapped in BEGIN/END markers for extraction or emphasis.""" + items = [_item("1", "Story", state="open")] + content = _build_summarize_prompt_content( + items, + filter_context={"adapter": "github", "state": "—", "sprint": "—", "assignee": "—", "limit": 20}, + include_value_score=False, + ) + assert "--- BEGIN STANDUP PROMPT ---" in content + assert "--- END STANDUP PROMPT ---" in content + assert content.strip().startswith("--- BEGIN STANDUP PROMPT ---") + assert content.strip().endswith("--- END STANDUP PROMPT ---") + + +class TestBacklogDailyPromptFile: + """Prompt file specfact.backlog-daily.md exists and has expected sections (22.2).""" + + def test_backlog_daily_prompt_file_exists(self) -> None: + """resources/prompts/specfact.backlog-daily.md exists.""" + repo_root = Path(__file__).resolve().parent.parent.parent.parent + prompt_path = repo_root / "resources" / "prompts" / "specfact.backlog-daily.md" + assert prompt_path.is_file(), f"Expected prompt file at {prompt_path}" + + def test_backlog_daily_prompt_contains_expected_sections(self) -> None: + """Prompt file contains purpose, story-by-story, discussion notes as comments.""" + repo_root = Path(__file__).resolve().parent.parent.parent.parent + prompt_path = repo_root / "resources" / "prompts" / "specfact.backlog-daily.md" + if not prompt_path.is_file(): + return + text = prompt_path.read_text(encoding="utf-8") + assert "daily" in text.lower() or "standup" in text.lower() + assert "story" in text.lower() or "item" in text.lower() + assert "comment" in text.lower() or "discussion" in text.lower() diff --git a/tests/unit/test_runtime.py b/tests/unit/test_runtime.py index b5220670..4ebda4cd 100644 --- a/tests/unit/test_runtime.py +++ b/tests/unit/test_runtime.py @@ -103,19 +103,26 @@ def test_get_configured_console_creates_console(self) -> None: assert isinstance(console, Console) def test_get_configured_console_caches(self) -> None: - """Test that get_configured_console caches Console instances.""" + """Test that get_configured_console returns Console instances (no cache in test mode).""" console1 = get_configured_console() console2 = get_configured_console() - # Should return the same instance (cached) - assert console1 is console2 + # In test mode we do not cache, to avoid holding a reference to a closed stream. + assert console1 is not None and console2 is not None + from rich.console import Console + + assert isinstance(console1, Console) and isinstance(console2, Console) + assert console1.width == console2.width def test_get_configured_console_different_modes(self) -> None: - """Test that different terminal modes create different Console instances.""" - # This test verifies caching works per mode - # In practice, mode doesn't change during execution, so we test caching + """Test that get_configured_console returns Console with consistent config.""" console1 = get_configured_console() console2 = get_configured_console() - assert console1 is console2 + # In test mode we do not cache; both should be Console with consistent config. + assert console1 is not None and console2 is not None + from rich.console import Console + + assert isinstance(console1, Console) and isinstance(console2, Console) + assert console1.width == console2.width class TestDebugMode: From be98de9bd541bf757d0fd7c49bfdf3f086834c75 Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Tue, 3 Feb 2026 10:37:47 +0000 Subject: [PATCH 2/6] chore: wrap openspec config for yamllint Co-authored-by: dominikus.nold <dominikus.nold@web.de> --- openspec/config.yaml | 100 +++++++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 33 deletions(-) diff --git a/openspec/config.yaml b/openspec/config.yaml index d827b4a4..db12cae3 100644 --- a/openspec/config.yaml +++ b/openspec/config.yaml @@ -8,7 +8,8 @@ context: | Testing: pytest, CrossHair (symbolic execution), Hypothesis (property-based) Distribution: uvx, PyPI, Docker - Philosophy: Brownfield-first legacy modernization tool. Reverse-engineer contracts from existing code, then enforce to prevent regressions. Offline-first, no vendor lock-in, no cloud dependencies. + Philosophy: Brownfield-first legacy modernization tool. Reverse-engineer contracts from existing code, + then enforce to prevent regressions. Offline-first, no vendor lock-in, no cloud dependencies. Architecture patterns: - Bridge Adapter: Tool-agnostic adapters (GitHub, GitLab, Linear, Jira, Spec-Kit, OpenSpec) @@ -29,20 +30,32 @@ context: | Logging: Use common.logger_setup.get_logger() (avoid print()) Naming: snake_case (files/modules/functions), PascalCase (classes), UPPER_SNAKE_CASE (constants) - Documentation (critical for every change): User-facing docs are published at https://docs.specfact.io (GitHub Pages). Source: docs/ with Jekyll front-matter (layout, title, permalink, description), docs/index.md as landing, docs/_layouts/default.html for sidebar/menu navigation. README.md is the repo entry point. + Documentation (critical for every change): User-facing docs are published at https://docs.specfact.io + (GitHub Pages). Source: docs/ with Jekyll front-matter (layout, title, permalink, description), + docs/index.md as landing, docs/_layouts/default.html for sidebar/menu navigation. README.md is the repo + entry point. - Every change must include documentation research and review: - - (1) Identifies affected documentation: docs/ (reference, guides, adapters, getting-started), README.md, docs/index.md. - - (2) Updates or adds content so docs remain a great resource for new and existing users (learn, adopt, understand). - - (3) If adding or moving pages: ensure front-matter (layout, title, permalink, description) is correct and update docs/_layouts/default.html sidebar navigation so the new or moved page appears in the menu. + - (1) Identifies affected documentation: docs/ (reference, guides, adapters, getting-started), + README.md, docs/index.md. + - (2) Updates or adds content so docs remain a great resource for new and existing users + (learn, adopt, understand). + - (3) If adding or moving pages: ensure front-matter (layout, title, permalink, description) is + correct and update docs/_layouts/default.html sidebar navigation so the new or moved page appears + in the menu. - Docs are published at https://docs.specfact.io (GitHub Pages). - Contract requirements: ALL public APIs MUST have @icontract (@require/@ensure) and @beartype decorators - Testing: Contract-first (primary), minimum 80% coverage, unit/integration/E2E tests required for all changes + Contract requirements: ALL public APIs MUST have @icontract (@require/@ensure) and @beartype + decorators + Testing: Contract-first (primary), minimum 80% coverage, unit/integration/E2E tests required for all + changes - Development discipline (SDD + TDD): SpecFact CLI adds the validation layer; we develop SpecFact itself using the same discipline to prove it works. Order is strict: + Development discipline (SDD + TDD): SpecFact CLI adds the validation layer; we develop SpecFact itself + using the same discipline to prove it works. Order is strict: - (1) Specs first—spec deltas define behavior (Given/When/Then). - - (2) Tests second—write unit/integration tests from spec scenarios (one or more tests per scenario); run tests and expect failure. - - (3) Code last—implement until tests pass and behavior satisfies the spec. Code must batch (satisfy) both (a) spec scenarios and (b) tests. + - (2) Tests second—write unit/integration tests from spec scenarios (one or more tests per scenario); + run tests and expect failure. + - (3) Code last—implement until tests pass and behavior satisfies the spec. Code must batch (satisfy) + both (a) spec scenarios and (b) tests. - If the pattern does not work in practice, adjust the process until it does. # Per-artifact rules (only injected into matching artifacts) @@ -55,10 +68,18 @@ rules: - Address offline-first constraint (no cloud dependencies) - Include rollback plan for risky changes - Check for conflicts with existing bridge adapters or plugin registry - - "Documentation impact (required for every change): Consider effect on docs published at https://docs.specfact.io. Identify affected areas: docs/ (reference, guides, adapters, getting-started), docs/index.md, README.md, docs/_layouts/default.html (sidebar/navigation). - - If the change is user-facing or alters API/CLI behavior, state in Impact which docs will be updated or added so new and existing users can learn, adopt, and understand the change." - - "For public-facing changes, include Source Tracking section with GitHub issue reference (format: ## Source Tracking with - **GitHub Issue**: #<number>, - **Issue URL**: <url>, - **Repository**: <owner>/<name>, - **Last Synced Status**: <status>). - - After creation, update proposal.md Source Tracking section with issue number, URL, repository, and status." + - |- + Documentation impact (required for every change): Consider effect on docs published at + https://docs.specfact.io. Identify affected areas: docs/ (reference, guides, adapters, + getting-started), docs/index.md, README.md, docs/_layouts/default.html (sidebar/navigation). + - If the change is user-facing or alters API/CLI behavior, state in Impact which docs will be + updated or added so new and existing users can learn, adopt, and understand the change. + - |- + For public-facing changes, include Source Tracking section with GitHub issue reference + (format: ## Source Tracking with - **GitHub Issue**: #<number>, - **Issue URL**: <url>, + - **Repository**: <owner>/<name>, - **Last Synced Status**: <status>). + - After creation, update proposal.md Source Tracking section with issue number, URL, repository, + and status. - Source tracking: Only track public repos (specfact-cli, platform-frontend). Skip for internal repos (specfact-cli-internal) specs: @@ -77,29 +98,42 @@ rules: - Include fallback strategies for offline scenarios tasks: - - Enforce SDD+TDD order: - - (1) Branch creation (first). - - (2) Write/add spec deltas if not already done. - - (3) Write tests from spec scenarios—translate each Given/When/Then scenario into test cases; run tests and expect failure (no implementation yet). - - (4) Implement code until tests pass and behavior satisfies the spec; code must batch (satisfy) both (a) spec scenarios and (b) tests. - - (5) Quality gates (format, lint, type-check). - - (6) Documentation research and review (see below). - - (7) PR creation (last). - - "Documentation research and review (required for every change): Include a task that: - - (1) Identifies affected documentation: docs/ (reference, guides, adapters, getting-started), README.md, docs/index.md. - - (2) Updates or adds content so docs remain a great resource for new and existing users (learn, adopt, understand). - - (3) If adding or moving pages: ensure front-matter (layout, title, permalink, description) is correct and update docs/_layouts/default.html sidebar navigation so the new or moved page appears in the menu. - - Docs are published at https://docs.specfact.io (GitHub Pages)." + - Enforce SDD+TDD order: + - (1) Branch creation (first). + - (2) Write/add spec deltas if not already done. + - (3) Write tests from spec scenarios—translate each Given/When/Then scenario into test cases; + run tests and expect failure (no implementation yet). + - (4) Implement code until tests pass and behavior satisfies the spec; code must batch (satisfy) + both (a) spec scenarios and (b) tests. + - (5) Quality gates (format, lint, type-check). + - (6) Documentation research and review (see below). + - (7) PR creation (last). + - |- + Documentation research and review (required for every change): Include a task that: + - (1) Identifies affected documentation: docs/ (reference, guides, adapters, getting-started), + README.md, docs/index.md. + - (2) Updates or adds content so docs remain a great resource for new and existing users + (learn, adopt, understand). + - (3) If adding or moving pages: ensure front-matter (layout, title, permalink, description) is + correct and update docs/_layouts/default.html sidebar navigation so the new or moved page + appears in the menu. + - Docs are published at https://docs.specfact.io (GitHub Pages). - Break into 2-hour maximum chunks - Include contract decorator tasks (@icontract, @beartype) for all public APIs - - Test tasks MUST come before implementation tasks: write tests derived from specs first, then implement. Do not implement before tests exist for the changed behavior. + - >- + Test tasks MUST come before implementation tasks: write tests derived from specs first, then + implement. Do not implement before tests exist for the changed behavior. - Include quality gate tasks: format, lint, type-check, test coverage - Reference existing test patterns in tests/unit/, tests/integration/, tests/e2e/ - - "Version and changelog (required before PR): Include a task that - - (1) bumps patch version when the change is a fix, or minor/major per semver when adding features or breaking; - - (2) syncs version in pyproject.toml, setup.py, src/__init__.py, src/specfact_cli/__init__.py; - - (3) adds a CHANGELOG.md entry under a new [X.Y.Z] - YYYY-MM-DD section with Fixed/Added/Changed as appropriate. - - Place this task after quality gates and documentation, before PR creation." + - |- + Version and changelog (required before PR): Include a task that + - (1) bumps patch version when the change is a fix, or minor/major per semver when adding + features or breaking; + - (2) syncs version in pyproject.toml, setup.py, src/__init__.py, + src/specfact_cli/__init__.py; + - (3) adds a CHANGELOG.md entry under a new [X.Y.Z] - YYYY-MM-DD section with Fixed/Added/Changed + as appropriate. + - Place this task after quality gates and documentation, before PR creation. - Include git workflow tasks: branch creation (first task), PR creation (last task) - For public-facing changes in public repos (specfact-cli, platform-frontend): - Include GitHub issue creation task with format: From 9a0d4822c4d860cb2d40e605566b4b7bdaa4079b Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Tue, 3 Feb 2026 10:38:55 +0000 Subject: [PATCH 3/6] fix: bump version to 0.26.17 Co-authored-by: dominikus.nold <dominikus.nold@web.de> --- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- setup.py | 2 +- src/__init__.py | 2 +- src/specfact_cli/__init__.py | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0652953..36b09e79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ All notable changes to this project will be documented in this file. --- +## [0.26.17] - 2026-02-03 + +### Changed (0.26.17) + +- **Version**: Bumped to 0.26.17 for issue [#179](https://github.com/nold-ai/specfact-cli/issues/179) + +--- + ## [0.26.16] - 2026-02-02 ### Added (0.26.16) diff --git a/pyproject.toml b/pyproject.toml index 168a1cb7..5eec8f8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "specfact-cli" -version = "0.26.16" +version = "0.26.17" description = "Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts. Automate legacy code documentation and prevent modernization regressions." readme = "README.md" requires-python = ">=3.11" diff --git a/setup.py b/setup.py index 3e64b21e..8d7568ee 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ if __name__ == "__main__": _setup = setup( name="specfact-cli", - version="0.26.16", + version="0.26.17", description="SpecFact CLI - Spec -> Contract -> Sentinel tool for contract-driven development", packages=find_packages(where="src"), package_dir={"": "src"}, diff --git a/src/__init__.py b/src/__init__.py index 2fc0e6a2..13b7517a 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -3,4 +3,4 @@ """ # Package version: keep in sync with pyproject.toml, setup.py, src/specfact_cli/__init__.py -__version__ = "0.26.16" +__version__ = "0.26.17" diff --git a/src/specfact_cli/__init__.py b/src/specfact_cli/__init__.py index 4debe5aa..65cecab6 100644 --- a/src/specfact_cli/__init__.py +++ b/src/specfact_cli/__init__.py @@ -9,6 +9,6 @@ - Validating reproducibility """ -__version__ = "0.26.16" +__version__ = "0.26.17" __all__ = ["__version__"] From ac3d77360f22c5f13beef509eb4c9d615396f36b Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Tue, 3 Feb 2026 11:02:49 +0000 Subject: [PATCH 4/6] fix: add comment annotations for standup exports Co-authored-by: dominikus.nold <dominikus.nold@web.de> --- CHANGELOG.md | 7 ++ .../tutorial-daily-standup-sprint-review.md | 29 +++-- docs/guides/agile-scrum-workflows.md | 36 +++++- docs/guides/devops-adapter-integration.md | 21 +++- src/specfact_cli/commands/backlog_commands.py | 110 ++++++++++++++---- tests/unit/commands/test_backlog_daily.py | 27 +++++ 6 files changed, 193 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36b09e79..b509600c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ All notable changes to this project will be documented in this file. ## [0.26.17] - 2026-02-03 +### Fixed (0.26.17) + +- **Daily standup exports: comment annotations** (fixes [#179](https://github.com/nold-ai/specfact-cli/issues/179)) + - **`--comments` / `--annotations`**: Include item descriptions and comment annotations in `--copilot-export` and + `--summarize`/`--summarize-to` outputs when the adapter supports `get_comments` (GitHub). + - **Docs**: Updated daily standup tutorial and guides to document the new flags and outputs. + ### Changed (0.26.17) - **Version**: Bumped to 0.26.17 for issue [#179](https://github.com/nold-ai/specfact-cli/issues/179) diff --git a/docs/getting-started/tutorial-daily-standup-sprint-review.md b/docs/getting-started/tutorial-daily-standup-sprint-review.md index edd6e502..d5da5853 100644 --- a/docs/getting-started/tutorial-daily-standup-sprint-review.md +++ b/docs/getting-started/tutorial-daily-standup-sprint-review.md @@ -20,8 +20,12 @@ This tutorial walks you through a complete **daily standup and sprint review** w - Use **`.specfact/backlog.yaml`** or environment variables when you're not in the repo (e.g. CI) or to override - **Post a standup comment** to the first (or selected) item with `--yesterday`, `--today`, `--blockers` and `--post` - Use **`--interactive`** for step-by-step story review (arrow-key selection, full detail, **existing comments on each issue** when the adapter supports them) -- Use **`--copilot-export <path>`** to write a Markdown summary for Copilot slash-command during standup -- Use **`--summarize`** or **`--summarize-to <path>`** to output a **prompt** (instruction + filter context + standup data) for a slash command (e.g. `specfact.daily`) or copy-paste to Copilot to **generate a standup summary** +- Use **`--copilot-export <path>`** to write a Markdown summary for Copilot slash-command during standup; + add **`--comments`** (alias **`--annotations`**) to include descriptions and comment annotations when + the adapter supports fetching comments +- Use **`--summarize`** or **`--summarize-to <path>`** to output a **prompt** (instruction + filter context + + standup data) for a slash command (e.g. `specfact.daily`) or copy-paste to Copilot to **generate a + standup summary**; add **`--comments`**/**`--annotations`** to include comment annotations in the prompt - Use the **`specfact.backlog-daily`** (or `specfact.daily`) slash prompt for interactive walkthrough with the DevOps team story-by-story (focus, issues, open questions, discussion notes as comments) - Filter by **`--assignee`**, **`--sprint`** / **`--iteration`**, **`--blockers-first`**, and optional **`--suggest-next`** @@ -121,10 +125,13 @@ Use **`--suggest-next`** to show a suggested next item by value score (business To feed a **summary file** into your AI IDE (e.g. for a Copilot slash-command during standup): ```bash -specfact backlog daily github --copilot-export ./standup-summary.md +specfact backlog daily github --copilot-export ./standup-summary.md --comments ``` -The file contains one section per item (ID, title, status, assignees, last updated, progress, blockers). You can open it in your IDE and use it with Copilot. Same scope as the standup table (state, assignee, limit, etc.). +The file contains one section per item (ID, title, status, assignees, last updated, progress, blockers). +With `--comments`/`--annotations`, it also includes the item description and comment annotations when the +adapter supports fetching comments. You can open it in your IDE and use it with Copilot. Same scope as +the standup table (state, assignee, limit, etc.). --- @@ -134,13 +141,17 @@ To get a **prompt** you can paste into Copilot or feed to a slash command (e.g. ```bash # Print prompt to stdout (copy-paste to Copilot) -specfact backlog daily github --summarize +specfact backlog daily github --summarize --comments # Write prompt to a file (e.g. for slash command) -specfact backlog daily github --summarize-to ./standup-prompt.md +specfact backlog daily github --summarize-to ./standup-prompt.md --comments ``` -The output includes an instruction to generate a standup summary, the applied filter context (adapter, state, sprint, assignee, limit), and the same per-item data as `--copilot-export`. Use it with the **`specfact.backlog-daily`** slash prompt for interactive team walkthrough (story-by-story, current focus, issues/open questions, discussion notes as comments). +The output includes an instruction to generate a standup summary, the applied filter context (adapter, +state, sprint, assignee, limit), and the same per-item data as `--copilot-export`. With +`--comments`/`--annotations`, the prompt includes comment annotations when supported. Use it with the +**`specfact.backlog-daily`** slash prompt for interactive team walkthrough (story-by-story, current focus, +issues/open questions, discussion notes as comments). --- @@ -187,8 +198,8 @@ The output includes an instruction to generate a standup summary, the applied fi | Override or use outside repo | Use `.specfact/backlog.yaml`, env vars (`SPECFACT_GITHUB_REPO_OWNER`, etc.), or CLI `--repo-owner`/`--repo-name` or `--ado-org`/`--ado-project`. | | Post standup to first item | Use `--yesterday "..."` `--today "..."` `--blockers "..."` and `--post` (values required). | | Step through stories with full detail (including issue comments) | Use `--interactive`; optionally `--suggest-next`. | -| Feed standup into Copilot | Use `--copilot-export <path>`. | -| Generate standup summary via AI (slash command or Copilot) | Use `--summarize` (stdout) or `--summarize-to <path>`; use with `specfact.backlog-daily` slash prompt. | +| Feed standup into Copilot | Use `--copilot-export <path>`; add `--comments`/`--annotations` for comment annotations. | +| Generate standup summary via AI (slash command or Copilot) | Use `--summarize` (stdout) or `--summarize-to <path>`; add `--comments`/`--annotations` for comment annotations; use with `specfact.backlog-daily` slash prompt. | --- diff --git a/docs/guides/agile-scrum-workflows.md b/docs/guides/agile-scrum-workflows.md index 0a6f2139..e70940d4 100644 --- a/docs/guides/agile-scrum-workflows.md +++ b/docs/guides/agile-scrum-workflows.md @@ -14,7 +14,29 @@ SpecFact CLI supports real-world agile/scrum practices through: - **Definition of Ready (DoR)**: Automatic validation of story readiness for sprint planning - **Backlog Refinement** 🆕: AI-assisted template-driven refinement for standardizing work items from DevOps backlogs -- **Daily Standup**: Use `specfact backlog daily <adapter>` to list my/filtered items with status and last activity. Default scope (state=open, limit=20, optional assignee=me) is applied when not overridden; configure via `SPECFACT_STANDUP_STATE`, `SPECFACT_STANDUP_LIMIT`, `SPECFACT_STANDUP_ASSIGNEE` or `.specfact/standup.yaml`. Use `--iteration` / `--sprint` (e.g. `--sprint current`) to focus on current iteration when the adapter supports it; sprint/iteration end date is shown when provided by adapter or config (`standup.sprint_end_date`). A second table **Pending / open for commitment** lists unassigned items (same scope); use `--show-unassigned`/`--no-show-unassigned` or `--unassigned-only`. Use `--blockers-first` to sort items with blockers first; enable `show_priority` or `show_value` in standup config for optional priority/value column (value-driven/SAFe). Optional standup summary (yesterday/today/blockers) from item body; optionally post standup comment to linked issue via `--post` when the adapter supports comments (e.g. GitHub). **Interactive step-by-step review**: Use `--interactive` to select stories with arrow keys (questionary) and view full detail (refine-like: description, acceptance criteria, standup fields, comments when adapter supports); navigate with Next/Previous/Back to list/Exit. Use `--suggest-next` to show suggested next item by value score (business_value / (story_points × priority)). **Copilot export**: Use `--copilot-export <path>` to write a summarized Markdown file of each story for use with Copilot slash-command during standup (complementary aid, not replacement for backlog). **Kanban**: omit iteration/sprint and use state + limit; unassigned = pullable work. **Scrum/SAFe**: use `--sprint current` and optional priority/value. **Out of scope**: Sprint goal is in your board/sprint settings (not displayed by CLI). Stale/at-risk flags (e.g. "no update in N days") are not in scope—use last updated + blockers. Structured "blocked by" (link to another issue) is not in scope; only free-text blockers are supported. +- **Daily Standup**: Use `specfact backlog daily <adapter>` to list my/filtered items with status and last activity. + Default scope (state=open, limit=20, optional assignee=me) is applied when not overridden; configure via + `SPECFACT_STANDUP_STATE`, `SPECFACT_STANDUP_LIMIT`, `SPECFACT_STANDUP_ASSIGNEE` or + `.specfact/standup.yaml`. Use `--iteration` / `--sprint` (e.g. `--sprint current`) to focus on current + iteration when the adapter supports it; sprint/iteration end date is shown when provided by adapter or + config (`standup.sprint_end_date`). A second table **Pending / open for commitment** lists unassigned + items (same scope); use `--show-unassigned`/`--no-show-unassigned` or `--unassigned-only`. Use + `--blockers-first` to sort items with blockers first; enable `show_priority` or `show_value` in standup + config for optional priority/value column (value-driven/SAFe). Optional standup summary + (yesterday/today/blockers) from item body; optionally post standup comment to linked issue via `--post` + when the adapter supports comments (e.g. GitHub). + **Interactive step-by-step review**: Use `--interactive` to select stories with arrow keys (questionary) + and view full detail (refine-like: description, acceptance criteria, standup fields, comments when adapter + supports); navigate with Next/Previous/Back to list/Exit. Use `--suggest-next` to show suggested next + item by value score (business_value / (story_points × priority)). + **Copilot export**: Use `--copilot-export <path>` to write a summarized Markdown file of each story for + Copilot. Add `--comments` (alias `--annotations`) to include descriptions and comment annotations in + `--copilot-export` and `--summarize` outputs when the adapter supports `get_comments` (GitHub). + **Kanban**: omit iteration/sprint and use state + limit; unassigned = pullable work. **Scrum/SAFe**: use + `--sprint current` and optional priority/value. **Out of scope**: Sprint goal is in your board/sprint + settings (not displayed by CLI). Stale/at-risk flags (e.g. "no update in N days") are not in scope—use + last updated + blockers. Structured "blocked by" (link to another issue) is not in scope; only free-text + blockers are supported. - **Dependency Management**: Track story-to-story and feature-to-feature dependencies - **Prioritization**: Priority levels, ranking, and business value scoring - **Sprint Planning**: Target sprint/release assignment and story point tracking @@ -56,13 +78,19 @@ specfact backlog daily github \ # 4. Optional: interactive step-through, Copilot export, or standup summary prompt specfact backlog daily github --interactive # step-through; detail view shows existing comments on each issue # or -specfact backlog daily github --copilot-export ./standup.md +specfact backlog daily github --copilot-export ./standup.md --comments # or -specfact backlog daily github --summarize # prompt to stdout for AI to generate standup summary +specfact backlog daily github --summarize --comments # prompt to stdout for AI to generate standup summary specfact backlog daily github --summarize-to ./standup-prompt.md ``` -Use the **`specfact.backlog-daily`** (or `specfact.daily`) slash prompt for interactive walkthrough with the DevOps team story-by-story (current focus, issues/open questions, discussion notes as comments). Default scope: **state=open**, **limit=20**; configure via `SPECFACT_STANDUP_*` or `.specfact/standup.yaml`. Use `--assignee me`, `--sprint current`, `--blockers-first`, `--interactive`, `--suggest-next`, `--copilot-export <path>`, `--summarize`, and `--summarize-to <path>` as needed. See [Tutorial: Daily Standup and Sprint Review](../getting-started/tutorial-daily-standup-sprint-review.md) for the full walkthrough. +Use the **`specfact.backlog-daily`** (or `specfact.daily`) slash prompt for interactive walkthrough with the +DevOps team story-by-story (current focus, issues/open questions, discussion notes as comments). Default +scope: **state=open**, **limit=20**; configure via `SPECFACT_STANDUP_*` or `.specfact/standup.yaml`. Use +`--assignee me`, `--sprint current`, `--blockers-first`, `--interactive`, `--suggest-next`, +`--copilot-export <path>`, `--summarize`, `--summarize-to <path>`, and `--comments`/`--annotations` as +needed. See [Tutorial: Daily Standup and Sprint Review](../getting-started/tutorial-daily-standup-sprint-review.md) +for the full walkthrough. ## Persona-Based Workflows diff --git a/docs/guides/devops-adapter-integration.md b/docs/guides/devops-adapter-integration.md index e29dbfaf..63bf15a3 100644 --- a/docs/guides/devops-adapter-integration.md +++ b/docs/guides/devops-adapter-integration.md @@ -19,7 +19,26 @@ SpecFact CLI supports **bidirectional synchronization** between OpenSpec change - **Issue Creation**: Export OpenSpec change proposals as GitHub Issues (or other DevOps backlog items) - **Progress Tracking**: Automatically detect code changes and add progress comments to issues -- **Standup Comments**: Use `specfact backlog daily --post` with `--yesterday`, `--today`, `--blockers` to post a standup summary as a comment on the linked issue (GitHub/ADO adapters that support comments). Standup config: set defaults via env (`SPECFACT_STANDUP_STATE`, `SPECFACT_STANDUP_LIMIT`, `SPECFACT_STANDUP_ASSIGNEE`, `SPECFACT_STANDUP_SPRINT_END`) or optional `.specfact/standup.yaml` (e.g. `default_state`, `limit`, `sprint`, `show_priority`, `suggest_next`). Iteration/sprint and sprint end date support depend on the adapter (ADO supports current iteration and iteration path; see adapter docs). Use `--blockers-first` and config `show_priority`/`show_value` for time-critical and value-driven standups. **Interactive review** (`--interactive`): step-through stories with arrow-key selection; detail view shows **existing comments annotated to each issue** when the adapter implements `get_comments(item)` (GitHub adapter supports it). **Value score / suggested next**: when BacklogItem has `story_points`, `business_value`, and `priority`, use `--suggest-next` or config `suggest_next` to show suggested next item (business_value / (story_points × priority)). **Standup summary prompt** (`--summarize` or `--summarize-to PATH`): output a prompt (instruction + filter context + standup data) for slash command or Copilot to generate a standup summary. **Slash prompt** `specfact.backlog-daily` (or `specfact.daily`): use with IDE/Copilot for interactive team walkthrough story-by-story (current focus, issues/open questions, discussion notes as comments); prompt file at `resources/prompts/specfact.backlog-daily.md`. **Sprint goal** is stored in your board/sprint settings and is not displayed or edited by the CLI. +- **Standup Comments**: Use `specfact backlog daily --post` with `--yesterday`, `--today`, `--blockers` to post + a standup summary as a comment on the linked issue (GitHub/ADO adapters that support comments). Standup + config: set defaults via env (`SPECFACT_STANDUP_STATE`, `SPECFACT_STANDUP_LIMIT`, + `SPECFACT_STANDUP_ASSIGNEE`, `SPECFACT_STANDUP_SPRINT_END`) or optional `.specfact/standup.yaml` + (e.g. `default_state`, `limit`, `sprint`, `show_priority`, `suggest_next`). Iteration/sprint and sprint + end date support depend on the adapter (ADO supports current iteration and iteration path; see adapter + docs). Use `--blockers-first` and config `show_priority`/`show_value` for time-critical and value-driven + standups. **Interactive review** (`--interactive`): step-through stories with arrow-key selection; detail + view shows **existing comments annotated to each issue** when the adapter implements `get_comments(item)` + (GitHub adapter supports it). **Comment annotations in exports**: add `--comments` (alias + `--annotations`) to include descriptions and comment annotations in `--copilot-export` and + `--summarize`/`--summarize-to` outputs when the adapter supports fetching comments. **Value score / + suggested next**: when BacklogItem has `story_points`, `business_value`, and `priority`, use + `--suggest-next` or config `suggest_next` to show suggested next item (business_value / (story_points × + priority)). **Standup summary prompt** (`--summarize` or `--summarize-to PATH`): output a prompt + (instruction + filter context + standup data) for slash command or Copilot to generate a standup summary. + **Slash prompt** `specfact.backlog-daily` (or `specfact.daily`): use with IDE/Copilot for interactive + team walkthrough story-by-story (current focus, issues/open questions, discussion notes as comments); + prompt file at `resources/prompts/specfact.backlog-daily.md`. **Sprint goal** is stored in your + board/sprint settings and is not displayed or edited by the CLI. - **Content Sanitization**: Protect internal information when syncing to public repositories - **Separate Repository Support**: Handle cases where OpenSpec proposals and source code are in different repositories diff --git a/src/specfact_cli/commands/backlog_commands.py b/src/specfact_cli/commands/backlog_commands.py index 0f6462cf..27471c5a 100644 --- a/src/specfact_cli/commands/backlog_commands.py +++ b/src/specfact_cli/commands/backlog_commands.py @@ -378,20 +378,66 @@ def _format_daily_item_detail(item: BacklogItem, comments: list[str]) -> str: return "\n".join(parts) +def _collect_comment_annotations( + adapter: str, + items: list[BacklogItem], + *, + repo_owner: str | None, + repo_name: str | None, + github_token: str | None, + ado_org: str | None, + ado_project: str | None, + ado_token: str | None, +) -> dict[str, list[str]]: + """ + Collect comment annotations for backlog items when the adapter supports get_comments(). + + Returns a mapping of item ID -> list of comment strings. Returns empty dict if not supported. + """ + comments_by_item_id: dict[str, list[str]] = {} + try: + adapter_kwargs = _build_adapter_kwargs( + adapter, + repo_owner=repo_owner, + repo_name=repo_name, + github_token=github_token, + ado_org=ado_org, + ado_project=ado_project, + ado_token=ado_token, + ) + registry = AdapterRegistry() + adapter_instance = registry.get_adapter(adapter, **adapter_kwargs) + if not isinstance(adapter_instance, BacklogAdapter): + return comments_by_item_id + get_comments_fn = getattr(adapter_instance, "get_comments", None) + if not callable(get_comments_fn): + return comments_by_item_id + for item in items: + with contextlib.suppress(Exception): + raw = get_comments_fn(item) + comments_by_item_id[item.id] = list(raw) if isinstance(raw, list) else [] + except Exception: + return comments_by_item_id + return comments_by_item_id + + @beartype def _build_copilot_export_content( items: list[BacklogItem], include_value_score: bool = False, + include_comments: bool = False, + comments_by_item_id: dict[str, list[str]] | None = None, ) -> str: """ Build Markdown content for Copilot export: one section per item. Per item: ID, title, status, assignees, last updated, progress summary (standup fields), - blockers, and optionally value score. + blockers, optional value score, and optionally description/comments when enabled. """ lines: list[str] = [] lines.append("# Daily standup – Copilot export") lines.append("") + comments_map = comments_by_item_id or {} for item in items: lines.append(f"## {item.id} - {item.title}") lines.append("") @@ -402,11 +448,26 @@ def _build_copilot_export_content( item.updated_at.strftime("%Y-%m-%d %H:%M") if hasattr(item.updated_at, "strftime") else str(item.updated_at) ) lines.append(f"- **Last updated:** {updated}") + if include_comments: + body = (item.body_markdown or "").strip() + if body: + snippet = body[:_SUMMARIZE_BODY_TRUNCATE] + if len(body) > _SUMMARIZE_BODY_TRUNCATE: + snippet += "\n..." + lines.append("- **Description:**") + for line in snippet.splitlines(): + lines.append(f" {line}" if line else " ") yesterday, today, blockers = _parse_standup_from_body(item.body_markdown or "") if yesterday or today: lines.append(f"- **Progress:** Yesterday: {yesterday or '—'}; Today: {today or '—'}") if blockers: lines.append(f"- **Blockers:** {blockers}") + if include_comments: + item_comments = comments_map.get(item.id, []) + if item_comments: + lines.append("- **Comments (annotations):**") + for c in item_comments: + lines.append(f" - {c}") if item.story_points is not None: lines.append(f"- **Story points:** {item.story_points}") if item.priority is not None: @@ -1030,6 +1091,12 @@ def daily( "--copilot-export", help="Write summarized progress per story to a file for Copilot slash-command use during standup.", ), + include_comments: bool = typer.Option( + False, + "--comments", + "--annotations", + help="Include item comments/annotations in summarize/copilot export (adapter must support get_comments).", + ), summarize: bool = typer.Option( False, "--summarize", @@ -1118,10 +1185,28 @@ def daily( console.print("[yellow]No backlog items found.[/yellow]") return + comments_by_item_id: dict[str, list[str]] = {} + if include_comments and (copilot_export is not None or summarize or summarize_to is not None): + comments_by_item_id = _collect_comment_annotations( + adapter, + filtered, + repo_owner=repo_owner, + repo_name=repo_name, + github_token=github_token, + ado_org=ado_org, + ado_project=ado_project, + ado_token=ado_token, + ) + if copilot_export is not None: include_score = suggest_next or bool(standup_config.get("suggest_next")) export_path = Path(copilot_export) - content = _build_copilot_export_content(filtered, include_value_score=include_score) + content = _build_copilot_export_content( + filtered, + include_value_score=include_score, + include_comments=include_comments, + comments_by_item_id=comments_by_item_id or None, + ) export_path.write_text(content, encoding="utf-8") console.print(f"[dim]Exported {len(filtered)} item(s) to {export_path}[/dim]") @@ -1134,27 +1219,6 @@ def daily( "assignee": effective_assignee or "—", "limit": effective_limit, } - comments_by_item_id: dict[str, list[str]] = {} - try: - adapter_kwargs_sum = _build_adapter_kwargs( - adapter, - repo_owner=repo_owner, - repo_name=repo_name, - github_token=github_token, - ado_org=ado_org, - ado_project=ado_project, - ado_token=ado_token, - ) - registry_sum = AdapterRegistry() - adapter_instance_sum = registry_sum.get_adapter(adapter, **adapter_kwargs_sum) - get_comments_fn = getattr(adapter_instance_sum, "get_comments", None) - if callable(get_comments_fn): - for it in filtered: - with contextlib.suppress(Exception): - raw = get_comments_fn(it) - comments_by_item_id[it.id] = list(raw) if isinstance(raw, list) else [] - except Exception: - pass content = _build_summarize_prompt_content( filtered, filter_context=filter_ctx, diff --git a/tests/unit/commands/test_backlog_daily.py b/tests/unit/commands/test_backlog_daily.py index 923e7d9b..ee5f4aa1 100644 --- a/tests/unit/commands/test_backlog_daily.py +++ b/tests/unit/commands/test_backlog_daily.py @@ -388,6 +388,27 @@ def test_copilot_export_idempotent_format(self) -> None: assert "Title" in content assert "- " in content or "* " in content or "\n" in content + def test_copilot_export_includes_description_and_comments_when_enabled(self) -> None: + """When enabled, Copilot export includes description and comment annotations.""" + items = [ + _item( + "1", + "Story one", + state="open", + body_markdown="This is the issue description and context.", + ), + ] + comments_by_id = {"1": ["Comment from Alice: In progress.", "Comment from Bob: Blocked on API."]} + content = _build_copilot_export_content( + items, + include_value_score=False, + include_comments=True, + comments_by_item_id=comments_by_id, + ) + assert "Description" in content and "issue description" in content + assert "Comments" in content or "annotations" in content + assert "In progress" in content and "Blocked on API" in content + class TestFormatDailyItemDetail: """Scenario: Interactive detail view refine-like (13.1).""" @@ -429,6 +450,12 @@ def test_daily_help_shows_summarize(self) -> None: assert result.exit_code == 0 assert "summarize" in result.output.lower() + def test_daily_help_shows_comment_annotations(self) -> None: + """Backlog daily has --comments/--annotations option for exports.""" + result = runner.invoke(app, ["backlog", "daily", "--help-advanced"]) + assert result.exit_code == 0 + assert "--comments" in result.output or "--annotations" in result.output + class TestBuildSummarizePromptContent: """Scenario: --summarize outputs prompt with filter context and per-item data (22.1).""" From e112789a2e6f909e01741d78d80f56568450b997 Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Tue, 3 Feb 2026 12:11:55 +0000 Subject: [PATCH 5/6] test: strip ansi for help output Co-authored-by: dominikus.nold <dominikus.nold@web.de> --- tests/unit/commands/test_backlog_daily.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/unit/commands/test_backlog_daily.py b/tests/unit/commands/test_backlog_daily.py index ee5f4aa1..74a04474 100644 --- a/tests/unit/commands/test_backlog_daily.py +++ b/tests/unit/commands/test_backlog_daily.py @@ -22,6 +22,7 @@ from datetime import UTC, datetime from pathlib import Path +import re from unittest.mock import MagicMock from typer.testing import CliRunner @@ -44,6 +45,12 @@ runner = CliRunner() +def _strip_ansi(text: str) -> str: + """Remove ANSI escape codes from CLI output.""" + ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") + return ansi_escape.sub("", text) + + def _item( id_: str = "1", title: str = "Item", @@ -454,7 +461,8 @@ def test_daily_help_shows_comment_annotations(self) -> None: """Backlog daily has --comments/--annotations option for exports.""" result = runner.invoke(app, ["backlog", "daily", "--help-advanced"]) assert result.exit_code == 0 - assert "--comments" in result.output or "--annotations" in result.output + output = _strip_ansi(result.output) + assert "--comments" in output or "--annotations" in output class TestBuildSummarizePromptContent: From 41b9598c443b0ca7241927dc2bdda4f343b482bc Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Tue, 3 Feb 2026 12:47:43 +0000 Subject: [PATCH 6/6] fix: use per-item story points for suggest-next Co-authored-by: dominikus.nold <dominikus.nold@web.de> --- src/specfact_cli/commands/backlog_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/specfact_cli/commands/backlog_commands.py b/src/specfact_cli/commands/backlog_commands.py index 27471c5a..d29d1e5a 100644 --- a/src/specfact_cli/commands/backlog_commands.py +++ b/src/specfact_cli/commands/backlog_commands.py @@ -620,7 +620,7 @@ def _run_interactive_daily( console.print(Panel(detail, title=f"Story: {item.id}", border_style="cyan")) if suggest_next and n > 1: - pending = [i for i in items if not i.assignees or item.story_points is not None] + pending = [i for i in items if not i.assignees or i.story_points is not None] if pending: best: BacklogItem | None = None best_score: float = -1.0