From a329f5e124243eb63884cb8632fb78ebbe18916e Mon Sep 17 00:00:00 2001 From: "james-in-a-box[bot]" <2365503+james-in-a-box[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:18:54 +0000 Subject: [PATCH 01/12] Initialize SDLC contract for issue #262 --- .egg-state/contracts/262.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .egg-state/contracts/262.json diff --git a/.egg-state/contracts/262.json b/.egg-state/contracts/262.json new file mode 100644 index 0000000000..d5ef1be4d9 --- /dev/null +++ b/.egg-state/contracts/262.json @@ -0,0 +1,18 @@ +{ + "schemaVersion": "1.0", + "issue": { + "number": 262, + "title": "Refine the SDLC human plan review workflow", + "url": "https://github.com/jwbron/egg/issues/262" + }, + "current_phase": "refine", + "acceptance_criteria": [], + "phases": [], + "decisions": [], + "circuit_breaker": { + "total_cycles": 0, + "max_total_cycles": 10, + "status": "closed" + }, + "audit_log": [] +} From c307dc00dee9b0e2673cf99f2b8491ea2b63105a Mon Sep 17 00:00:00 2001 From: "james-in-a-box[bot]" <2365503+james-in-a-box[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:28:18 +0000 Subject: [PATCH 02/12] Approve and advance to plan phase for issue #262 --- .egg-state/contracts/262.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.egg-state/contracts/262.json b/.egg-state/contracts/262.json index d5ef1be4d9..1d2cb2fb79 100644 --- a/.egg-state/contracts/262.json +++ b/.egg-state/contracts/262.json @@ -5,7 +5,7 @@ "title": "Refine the SDLC human plan review workflow", "url": "https://github.com/jwbron/egg/issues/262" }, - "current_phase": "refine", + "current_phase": "plan", "acceptance_criteria": [], "phases": [], "decisions": [], @@ -14,5 +14,16 @@ "max_total_cycles": 10, "status": "closed" }, - "audit_log": [] + "audit_log": [ + { + "timestamp": "2026-02-08T07:28:18Z", + "actor": "jwbron", + "role": "human", + "action": "transition", + "field_path": "current_phase", + "old_value": "refine", + "new_value": "plan", + "reason": "Phase approved by human" + } + ] } From d848b61d375dc0d34a11cb1971c12ebbc85e5ec4 Mon Sep 17 00:00:00 2001 From: "james-in-a-box[bot]" <2365503+james-in-a-box[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:31:17 +0000 Subject: [PATCH 03/12] Populate contract from plan for issue #262 --- .egg-state/contracts/262.json | 263 +++++++++++++++++++++++++++++++++- 1 file changed, 261 insertions(+), 2 deletions(-) diff --git a/.egg-state/contracts/262.json b/.egg-state/contracts/262.json index 1d2cb2fb79..f5c637a85b 100644 --- a/.egg-state/contracts/262.json +++ b/.egg-state/contracts/262.json @@ -6,8 +6,267 @@ "url": "https://github.com/jwbron/egg/issues/262" }, "current_phase": "plan", - "acceptance_criteria": [], - "phases": [], + "acceptance_criteria": [ + { + "id": "ac-1", + "description": "[TASK-1-1] CLI outputs properly formatted markdown block with HITL markers", + "verified": false + }, + { + "id": "ac-2", + "description": "[TASK-1-2] Any add-decision with --options includes \"Other\" as last option", + "verified": false + }, + { + "id": "ac-3", + "description": "[TASK-1-3] Tests cover markdown output, auto-appended Other, and edge cases", + "verified": false + }, + { + "id": "ac-4", + "description": "[TASK-2-1] Template has guidance on formal HITL decisions, no informal checkboxes", + "verified": false + }, + { + "id": "ac-5", + "description": "[TASK-2-2] Template includes approval checkbox with correct HTML marker", + "verified": false + }, + { + "id": "ac-6", + "description": "[TASK-2-3] Template mentions approval should use phase-completion format", + "verified": false + }, + { + "id": "ac-7", + "description": "[TASK-3-1] Prompt instructs agent to include decision markdown and approval section", + "verified": false + }, + { + "id": "ac-8", + "description": "[TASK-3-2] Prompt includes approval section instructions", + "verified": false + }, + { + "id": "ac-9", + "description": "[TASK-3-3] Consistent approval format across all prompts", + "verified": false + }, + { + "id": "ac-10", + "description": "[TASK-4-1] Test verifies workflow regex patterns match CLI output format", + "verified": false + }, + { + "id": "ac-11", + "description": "[TASK-4-2] Documentation explains informal vs formal decisions and approval flow", + "verified": false + } + ], + "phases": [ + { + "id": "phase-1", + "name": "Enhance Contract CLI Decision Output", + "status": "pending", + "tasks": [ + { + "id": "task-1-1", + "description": "Add --format markdown flag to add-decision command", + "status": "pending", + "acceptance_criteria": "CLI outputs properly formatted markdown block with HITL markers", + "files_affected": [ + "sandbox/egg_lib/contract_cli.py" + ], + "commit": null, + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "notes": "" + }, + { + "id": "task-1-2", + "description": "Auto-append \"Other (explain in reply)\" option", + "status": "pending", + "acceptance_criteria": "Any add-decision with --options includes \"Other\" as last option", + "files_affected": [ + "sandbox/egg_lib/contract_cli.py" + ], + "commit": null, + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "notes": "" + }, + { + "id": "task-1-3", + "description": "Add unit tests for new add-decision output format", + "status": "pending", + "acceptance_criteria": "Tests cover markdown output, auto-appended Other, and edge cases", + "files_affected": [ + "tests/sandbox/test_contract_cli.py" + ], + "commit": null, + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "notes": "" + } + ], + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "escalation_reason": null, + "review_feedback": [] + }, + { + "id": "phase-2", + "name": "Update Templates with Approval Section", + "status": "pending", + "tasks": [ + { + "id": "task-2-1", + "description": "Update analysis.md template", + "status": "pending", + "acceptance_criteria": "Template has guidance on formal HITL decisions, no informal checkboxes", + "files_affected": [ + "docs/templates/analysis.md" + ], + "commit": null, + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "notes": "" + }, + { + "id": "task-2-2", + "description": "Create phase-completion.md template with approval marker", + "status": "pending", + "acceptance_criteria": "Template includes approval checkbox with correct HTML marker", + "files_affected": [ + "docs/templates/phase-completion.md" + ], + "commit": null, + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "notes": "" + }, + { + "id": "task-2-3", + "description": "Update plan.md to reference phase completion format", + "status": "pending", + "acceptance_criteria": "Template mentions approval should use phase-completion format", + "files_affected": [ + "docs/templates/plan.md" + ], + "commit": null, + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "notes": "" + } + ], + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "escalation_reason": null, + "review_feedback": [] + }, + { + "id": "phase-3", + "name": "Update Phase Prompts", + "status": "pending", + "tasks": [ + { + "id": "task-3-1", + "description": "Update build_refine_prompt() with decision and approval instructions", + "status": "pending", + "acceptance_criteria": "Prompt instructs agent to include decision markdown and approval section", + "files_affected": [ + "action/build-sdlc-prompt.sh" + ], + "commit": null, + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "notes": "" + }, + { + "id": "task-3-2", + "description": "Update build_plan_prompt() with approval instructions", + "status": "pending", + "acceptance_criteria": "Prompt includes approval section instructions", + "files_affected": [ + "action/build-sdlc-prompt.sh" + ], + "commit": null, + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "notes": "" + }, + { + "id": "task-3-3", + "description": "Add helper text for consistent approval format", + "status": "pending", + "acceptance_criteria": "Consistent approval format across all prompts", + "files_affected": [ + "action/build-sdlc-prompt.sh" + ], + "commit": null, + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "notes": "" + } + ], + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "escalation_reason": null, + "review_feedback": [] + }, + { + "id": "phase-4", + "name": "Documentation and Testing", + "status": "pending", + "tasks": [ + { + "id": "task-4-1", + "description": "Add integration test for decision markdown parsing", + "status": "pending", + "acceptance_criteria": "Test verifies workflow regex patterns match CLI output format", + "files_affected": [ + "tests/workflows/test_hitl_integration.py" + ], + "commit": null, + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "notes": "" + }, + { + "id": "task-4-2", + "description": "Document HITL decision workflow", + "status": "pending", + "acceptance_criteria": "Documentation explains informal vs formal decisions and approval flow", + "files_affected": [ + "docs/hitl-decisions.md" + ], + "commit": null, + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "notes": "" + } + ], + "review_cycles": 0, + "max_cycles": 3, + "escalated": false, + "escalation_reason": null, + "review_feedback": [] + } + ], "decisions": [], "circuit_breaker": { "total_cycles": 0, From 001350c6e8e5269d12558aaa9bf92dde1463878a Mon Sep 17 00:00:00 2001 From: "james-in-a-box[bot]" <2365503+james-in-a-box[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:33:06 +0000 Subject: [PATCH 04/12] Approve and advance to implement phase for issue #262 --- .egg-state/contracts/262.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.egg-state/contracts/262.json b/.egg-state/contracts/262.json index f5c637a85b..b513e46341 100644 --- a/.egg-state/contracts/262.json +++ b/.egg-state/contracts/262.json @@ -5,7 +5,7 @@ "title": "Refine the SDLC human plan review workflow", "url": "https://github.com/jwbron/egg/issues/262" }, - "current_phase": "plan", + "current_phase": "implement", "acceptance_criteria": [ { "id": "ac-1", @@ -283,6 +283,16 @@ "old_value": "refine", "new_value": "plan", "reason": "Phase approved by human" + }, + { + "timestamp": "2026-02-08T07:33:06Z", + "actor": "jwbron", + "role": "human", + "action": "transition", + "field_path": "current_phase", + "old_value": "plan", + "new_value": "implement", + "reason": "Phase approved by human" } ] } From d1e2997649669fcd8a74bef35afd618c7d26caf7 Mon Sep 17 00:00:00 2001 From: "james-in-a-box[bot]" <2365503+james-in-a-box[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:36:34 +0000 Subject: [PATCH 05/12] Enhance add-decision CLI with markdown output and auto-append Other option - Add --format flag to add-decision command (choices: json, markdown) - Auto-append 'Other (explain in reply)' option when options are provided - Add format_decision_markdown() function to generate HITL-compatible markdown - Add comprehensive unit tests for new functionality The markdown format includes the marker required by sdlc-hitl.yml for decision detection. --- sandbox/egg_lib/contract_cli.py | 53 +++++++- tests/sandbox/test_contract_cli.py | 203 +++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+), 2 deletions(-) diff --git a/sandbox/egg_lib/contract_cli.py b/sandbox/egg_lib/contract_cli.py index 689f52d33e..ff9c5193aa 100755 --- a/sandbox/egg_lib/contract_cli.py +++ b/sandbox/egg_lib/contract_cli.py @@ -438,6 +438,34 @@ def cmd_mark_phase(args: argparse.Namespace) -> int: return 1 +def format_decision_markdown(decision_id: str, question: str, options: list[dict]) -> str: + """Format a HITL decision as markdown with proper markers. + + The output format matches what sdlc-hitl.yml expects: + - HTML comment marker with decision ID for detection + - Checkbox list for options + + Args: + decision_id: The decision ID (e.g., "decision-1") + question: The decision question + options: List of option dicts with 'label' keys + + Returns: + Formatted markdown string ready for GitHub comment + """ + lines = [ + f"", + "", + f"**{question}**", + "", + ] + + for opt in options: + lines.append(f"- [ ] {opt['label']}") + + return "\n".join(lines) + + def cmd_add_decision(args: argparse.Namespace) -> int: """Create a HITL decision point. @@ -481,12 +509,17 @@ def cmd_add_decision(args: argparse.Namespace) -> int: "debounce_until": None, } - # Parse options if provided + # Parse options if provided, and auto-append "Other" option if args.options: for i, opt in enumerate(args.options): new_decision["options"].append( {"id": f"opt-{i + 1}", "label": opt, "description": None} ) + # Auto-append "Other (explain in reply)" as the last option + other_idx = len(args.options) + 1 + new_decision["options"].append( + {"id": f"opt-{other_idx}", "label": "Other (explain in reply)", "description": None} + ) # Add the decision to the array result = make_gateway_request( @@ -503,7 +536,17 @@ def cmd_add_decision(args: argparse.Namespace) -> int: ) if result.get("success"): - print(f"Created decision {new_decision['id']}: {args.question}") + # Output based on format + output_format = getattr(args, "format", "json") + if output_format == "markdown": + markdown = format_decision_markdown( + new_decision["id"], + args.question, + new_decision["options"], + ) + print(markdown) + else: + print(f"Created decision {new_decision['id']}: {args.question}") return 0 else: print(f"Error: {result.get('message')}", file=sys.stderr) @@ -580,6 +623,12 @@ def create_parser() -> argparse.ArgumentParser: nargs="*", help="Optional: decision options", ) + decision_parser.add_argument( + "--format", + choices=["json", "markdown"], + default="json", + help="Output format: json (default) or markdown (for GitHub comments)", + ) decision_parser.set_defaults(func=cmd_add_decision) return parser diff --git a/tests/sandbox/test_contract_cli.py b/tests/sandbox/test_contract_cli.py index 94de953057..fec9f0f33b 100644 --- a/tests/sandbox/test_contract_cli.py +++ b/tests/sandbox/test_contract_cli.py @@ -16,6 +16,7 @@ from egg_lib.contract_cli import ( create_parser, + format_decision_markdown, get_gateway_url, get_issue_number, get_repo_path, @@ -120,6 +121,26 @@ def test_add_decision_with_options(self): ) assert args.options == ["Option A", "Option B", "Option C"] + def test_add_decision_with_format_markdown(self): + """Test parsing add-decision with --format markdown.""" + parser = create_parser() + args = parser.parse_args( + [ + "add-decision", + "--question", + "Which approach?", + "--format", + "markdown", + ] + ) + assert args.format == "markdown" + + def test_add_decision_format_default_json(self): + """Test that add-decision defaults to json format.""" + parser = create_parser() + args = parser.parse_args(["add-decision", "--question", "Which approach?"]) + assert args.format == "json" + class TestEnvironmentHelpers: """Tests for environment variable helpers.""" @@ -499,3 +520,185 @@ def test_invalid_with_spaces(self): """Test SHA with spaces.""" with pytest.raises(ValueError, match="Invalid commit SHA"): validate_commit_sha("abc 123") + + +class TestDecisionMarkdownFormat: + """Tests for format_decision_markdown function.""" + + def test_format_decision_markdown_basic(self): + """Test basic markdown formatting with options.""" + options = [ + {"id": "opt-1", "label": "Option A"}, + {"id": "opt-2", "label": "Option B"}, + ] + result = format_decision_markdown("decision-1", "Which approach?", options) + + assert "" in result + assert "**Which approach?**" in result + assert "- [ ] Option A" in result + assert "- [ ] Option B" in result + + def test_format_decision_markdown_includes_other(self): + """Test that Other option is included when present.""" + options = [ + {"id": "opt-1", "label": "Option A"}, + {"id": "opt-2", "label": "Other (explain in reply)"}, + ] + result = format_decision_markdown("decision-2", "Pick one?", options) + + assert "- [ ] Other (explain in reply)" in result + + def test_format_decision_markdown_no_options(self): + """Test markdown formatting with no options.""" + result = format_decision_markdown("decision-3", "Thoughts?", []) + + assert "" in result + assert "**Thoughts?**" in result + assert "- [ ]" not in result + + def test_format_decision_markdown_special_characters(self): + """Test markdown formatting handles special characters in question.""" + options = [{"id": "opt-1", "label": "Yes"}] + result = format_decision_markdown("decision-4", "Is this a `code` example?", options) + + assert "**Is this a `code` example?**" in result + + +class TestAddDecisionWithMockGateway: + """Tests for add-decision command with mock gateway.""" + + @pytest.fixture + def mock_gateway_factory(self): + """Factory fixture for creating mock gateway servers.""" + servers = [] + + def create_gateway(responses: dict) -> str: + handler_class = create_mock_gateway_handler(responses) + server = HTTPServer(("127.0.0.1", 0), handler_class) + port = server.server_address[1] + # Need to handle two requests: GET contract + POST mutate + thread1 = Thread(target=server.handle_request) + thread1.daemon = True + thread1.start() + thread2 = Thread(target=server.handle_request) + thread2.daemon = True + thread2.start() + servers.append(server) + return f"http://127.0.0.1:{port}" + + yield create_gateway + + for server in servers: + server.server_close() + + def test_add_decision_auto_appends_other_option(self, mock_gateway_factory, capsys): + """Test that add-decision auto-appends Other option when options provided.""" + responses = { + ("GET", "/api/v1/contract/123"): { + "success": True, + "data": { + "issue": {"number": 123, "title": "Test"}, + "current_phase": "refine", + "phases": [], + "decisions": [], + }, + }, + ("POST", "/api/v1/contract/mutate"): { + "success": True, + "message": "Mutation applied", + }, + } + mock_gateway = mock_gateway_factory(responses) + + with patch.dict( + "os.environ", + {"GATEWAY_URL": mock_gateway, "EGG_ISSUE_NUMBER": "123"}, + ): + result = main([ + "add-decision", + "--question", + "Which approach?", + "--options", + "Option A", + "Option B", + ]) + + assert result == 0 + + def test_add_decision_markdown_format(self, mock_gateway_factory, capsys): + """Test add-decision with --format markdown outputs correct format.""" + responses = { + ("GET", "/api/v1/contract/123"): { + "success": True, + "data": { + "issue": {"number": 123, "title": "Test"}, + "current_phase": "refine", + "phases": [], + "decisions": [], + }, + }, + ("POST", "/api/v1/contract/mutate"): { + "success": True, + "message": "Mutation applied", + }, + } + mock_gateway = mock_gateway_factory(responses) + + with patch.dict( + "os.environ", + {"GATEWAY_URL": mock_gateway, "EGG_ISSUE_NUMBER": "123"}, + ): + result = main([ + "add-decision", + "--question", + "Which approach?", + "--options", + "Option A", + "Option B", + "--format", + "markdown", + ]) + + assert result == 0 + captured = capsys.readouterr() + assert "" in captured.out + assert "**Which approach?**" in captured.out + assert "- [ ] Option A" in captured.out + assert "- [ ] Option B" in captured.out + assert "- [ ] Other (explain in reply)" in captured.out + + def test_add_decision_no_options_no_other(self, mock_gateway_factory, capsys): + """Test that add-decision without options doesn't add Other.""" + responses = { + ("GET", "/api/v1/contract/123"): { + "success": True, + "data": { + "issue": {"number": 123, "title": "Test"}, + "current_phase": "refine", + "phases": [], + "decisions": [], + }, + }, + ("POST", "/api/v1/contract/mutate"): { + "success": True, + "message": "Mutation applied", + }, + } + mock_gateway = mock_gateway_factory(responses) + + with patch.dict( + "os.environ", + {"GATEWAY_URL": mock_gateway, "EGG_ISSUE_NUMBER": "123"}, + ): + result = main([ + "add-decision", + "--question", + "Open-ended question?", + "--format", + "markdown", + ]) + + assert result == 0 + captured = capsys.readouterr() + assert "" in captured.out + assert "Other" not in captured.out From 0036949abbbbca817da29418693da86b7fd3633e Mon Sep 17 00:00:00 2001 From: "james-in-a-box[bot]" <2365503+james-in-a-box[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:37:19 +0000 Subject: [PATCH 06/12] Update templates with formal HITL decision and approval guidance - analysis.md: Replace informal checkboxes with guidance on using formal HITL decisions via egg-contract add-decision --format markdown - plan.md: Add phase approval section with marker - phase-completion.md: New template for phase completion comments with approval checkbox format This ensures the workflow can detect checkbox edits via sdlc-hitl.yml. --- docs/templates/analysis.md | 18 ++++++++-- docs/templates/phase-completion.md | 58 ++++++++++++++++++++++++++++++ docs/templates/plan.md | 17 +++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 docs/templates/phase-completion.md diff --git a/docs/templates/analysis.md b/docs/templates/analysis.md index 640aa89304..3150a6605f 100644 --- a/docs/templates/analysis.md +++ b/docs/templates/analysis.md @@ -48,8 +48,22 @@ ## Open Questions -- [ ] [Question 1 that needs human input] -- [ ] [Question 2 that needs human input] +For questions that require human input before proceeding, use formal HITL decisions. +The agent will generate these using `egg-contract add-decision --format markdown`. + +**Multiple-choice questions** (when you need the human to pick from options): +``` +egg-contract add-decision --question "Which caching strategy should we use?" \ + --options "Redis" "In-memory LRU" "File-based" --format markdown +``` +This outputs markdown with checkboxes that the human can interact with directly. + +**Open-ended questions** (when you need free-form input): +Include these as plain text in your analysis. The human will respond via comment. + +Example open-ended questions: +- What is the expected request volume for this feature? +- Are there any constraints on third-party dependencies? --- diff --git a/docs/templates/phase-completion.md b/docs/templates/phase-completion.md new file mode 100644 index 0000000000..41fe3e1584 --- /dev/null +++ b/docs/templates/phase-completion.md @@ -0,0 +1,58 @@ +# Phase Completion Comment Template + +Use this format when posting a phase completion comment to GitHub. +The approval checkbox uses the `` marker +which triggers the sdlc-hitl.yml workflow when edited. + +## Template + +```markdown +## [Phase Name] Complete + +[Brief summary of what was accomplished in this phase] + +### Deliverables + +- [Deliverable 1] +- [Deliverable 2] + +### Ready for Review + + +- [ ] Approve and advance to next phase + +--- + +*Authored-by: egg* +``` + +## Notes + +- The `` marker must appear on the line immediately before the approval checkbox +- When the human checks the `[x] Approve` checkbox, the workflow detects the edit and advances to the next phase +- Keep the approval checkbox as a single option to avoid confusion +- The phase name in the heading should match the current SDLC phase (refine, plan, implement) + +## Example: Refine Phase Complete + +```markdown +## Refine Phase Complete + +Analysis of issue #123 is complete. The recommended approach is to implement +Option B (Redis caching) based on the constraints identified. + +### Deliverables + +- Problem statement and constraints documented +- Three options analyzed with pros/cons +- Recommendation provided with justification + +### Ready for Review + + +- [ ] Approve and advance to plan phase + +--- + +*Authored-by: egg* +``` diff --git a/docs/templates/plan.md b/docs/templates/plan.md index 367f66758c..6c212b56d2 100644 --- a/docs/templates/plan.md +++ b/docs/templates/plan.md @@ -90,4 +90,21 @@ phases: --- +## Phase Approval + +When posting this plan as a GitHub comment, include an approval section at the end. +Use the phase-completion template format with the `` marker: + +```markdown +### Ready for Review + + +- [ ] Approve and advance to implement phase +``` + +This allows the human to approve by checking the checkbox, which triggers the +workflow to advance to the next phase. + +--- + *Authored-by: egg* From 4f7b24d7cb38cf6039db0a7a3c2a5eb981952793 Mon Sep 17 00:00:00 2001 From: "james-in-a-box[bot]" <2365503+james-in-a-box[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:38:05 +0000 Subject: [PATCH 07/12] Update phase prompts with HITL decision and approval instructions - build_refine_prompt(): Add HITL decision instructions showing how to use egg-contract add-decision --format markdown for multiple-choice questions - build_plan_prompt(): Add similar HITL decision instructions - Both prompts: Add phase completion section with marker format for approval checkboxes - Consistent approval format across refine and plan phases --- action/build-sdlc-prompt.sh | 74 +++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/action/build-sdlc-prompt.sh b/action/build-sdlc-prompt.sh index e5ebeab6a5..6c0a258184 100755 --- a/action/build-sdlc-prompt.sh +++ b/action/build-sdlc-prompt.sh @@ -160,15 +160,47 @@ In the refine phase: - You CANNOT push code (git push) - You CANNOT create PRs (gh pr create) +## HITL Decisions + +For questions that require human input before proceeding: + +**Multiple-choice questions** (use formal HITL decisions): +\`\`\`bash +egg-contract add-decision --question "Which approach should we use?" \\ + --options "Option A" "Option B" "Option C" --format markdown +\`\`\` +Copy the markdown output into your analysis comment. The human can check a checkbox +to select an option. An "Other (explain in reply)" option is auto-appended. + +**Open-ended questions** (no predefined options): +List these as plain text in your analysis. The human will respond via comment. + +## Phase Completion + +When your analysis is complete, post a completion comment with an approval section: + +\`\`\`markdown +## Refine Phase Complete + +[Summary of analysis and recommendation] + +### Ready for Review + + +- [ ] Approve and advance to plan phase + +--- + +*Authored-by: egg* +\`\`\` + ## Next Steps -When your analysis is complete: 1. Write the analysis document to a file: \`/tmp/analysis.md\` 2. Post via file to avoid shell escaping issues: \`gh issue comment ${issue_number} --body-file /tmp/analysis.md\` -3. If you have open questions, use \`egg-contract add-decision --question "..."\` -4. Wait for human approval to advance to the plan phase +3. Wait for human approval (they check the approval checkbox) -**IMPORTANT**: Always use \`--body-file\` instead of \`--body\` when posting analysis. +**IMPORTANT**: Always use \`--body-file\` instead of \`--body\` when posting. Content containing \`\${{ }}\` expressions or other shell metacharacters will corrupt the comment if passed inline via \`--body\`. EOF @@ -268,13 +300,41 @@ In the plan phase: - You CANNOT push code (git push) - You CANNOT create PRs (gh pr create) +## HITL Decisions + +For questions that require human input before proceeding: + +**Multiple-choice questions** (use formal HITL decisions): +\`\`\`bash +egg-contract add-decision --question "Which architecture pattern?" \\ + --options "Microservices" "Monolith" "Hybrid" --format markdown +\`\`\` +Copy the markdown output into your plan comment. The human can check a checkbox +to select an option. An "Other (explain in reply)" option is auto-appended. + +**Open-ended questions** (no predefined options): +List these as plain text. The human will respond via comment. + +## Phase Completion + +When your plan is complete, include an approval section at the end: + +\`\`\`markdown +### Ready for Review + + +- [ ] Approve and advance to implement phase + +--- + +*Authored-by: egg* +\`\`\` + ## Next Steps -When your plan is complete: 1. Write the plan document to a file: \`/tmp/plan.md\` 2. Post via file to avoid shell escaping issues: \`gh issue comment ${issue_number} --body-file /tmp/plan.md\` -3. If you have open questions, use \`egg-contract add-decision --question "..."\` -4. Wait for human approval to advance to the implement phase +3. Wait for human approval (they check the approval checkbox) **IMPORTANT**: Always use \`--body-file\` instead of \`--body\` when posting the plan. Content containing \`\${{ }}\` expressions or other shell metacharacters will corrupt From dd4425b3324271347e19fb696153170196d4a9a8 Mon Sep 17 00:00:00 2001 From: "james-in-a-box[bot]" <2365503+james-in-a-box[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:39:31 +0000 Subject: [PATCH 08/12] Add HITL integration tests and documentation - tests/workflows/test_hitl_integration.py: Tests verifying CLI output format matches the regex patterns used in sdlc-hitl.yml workflow - docs/hitl-decisions.md: Comprehensive documentation explaining: - Formal HITL decisions vs phase approval - How to create decisions with egg-contract add-decision - Workflow detection mechanism - Best practices and troubleshooting --- docs/hitl-decisions.md | 137 +++++++++++++++++++++ tests/workflows/__init__.py | 1 + tests/workflows/test_hitl_integration.py | 148 +++++++++++++++++++++++ 3 files changed, 286 insertions(+) create mode 100644 docs/hitl-decisions.md create mode 100644 tests/workflows/__init__.py create mode 100644 tests/workflows/test_hitl_integration.py diff --git a/docs/hitl-decisions.md b/docs/hitl-decisions.md new file mode 100644 index 0000000000..1b036afcd8 --- /dev/null +++ b/docs/hitl-decisions.md @@ -0,0 +1,137 @@ +# HITL (Human-In-The-Loop) Decision Workflow + +This document explains how human decisions are captured and processed in the SDLC pipeline. + +## Overview + +The SDLC pipeline includes phases where human input is required before proceeding: +- **Refine phase**: Human approves the analysis before planning +- **Plan phase**: Human approves the implementation plan before coding + +Two mechanisms exist for gathering human input: +1. **Formal HITL decisions** — Multiple-choice questions with checkboxes +2. **Phase approval** — Single checkbox to approve and advance to the next phase + +## Formal HITL Decisions + +Use formal decisions when you need the human to choose between predefined options. + +### Creating a Decision + +```bash +egg-contract add-decision \ + --question "Which caching strategy should we use?" \ + --options "Redis" "In-memory LRU" "File-based" \ + --format markdown +``` + +Output: +```markdown + + +**Which caching strategy should we use?** + +- [ ] Redis +- [ ] In-memory LRU +- [ ] File-based +- [ ] Other (explain in reply) +``` + +### How It Works + +1. The agent includes this markdown in a GitHub comment +2. The `` marker identifies the decision +3. When the human checks a checkbox, GitHub triggers an `issue_comment.edited` event +4. The `sdlc-hitl.yml` workflow detects the change and updates the contract +5. If this was the last pending decision, the workflow advances to the next phase + +### Auto-appended "Other" Option + +When you provide `--options`, an "Other (explain in reply)" option is automatically +appended. If the human selects this, they can explain their preference in a follow-up +comment, which the agent will parse. + +### Open-ended Questions + +For questions without predefined options, don't use the `--options` flag: +- Simply list the question as plain text in your comment +- The human will respond via a new comment +- No special workflow processing occurs; the agent reads the reply + +## Phase Approval + +Phase approval is a simpler mechanism for advancing the pipeline. + +### Format + +```markdown +### Ready for Review + + +- [ ] Approve and advance to plan phase + +--- + +*Authored-by: egg* +``` + +### How It Works + +1. The agent includes this at the end of phase completion comments +2. The `` marker identifies the approval section +3. When the human checks the `[x] Approve` checkbox, GitHub triggers an edit event +4. The `sdlc-hitl.yml` workflow's `handle-approval` job detects this +5. The workflow updates the contract phase and triggers the next pipeline run + +### Key Differences from Decisions + +| Aspect | Formal Decisions | Phase Approval | +|--------|-----------------|----------------| +| Marker | `` | `` | +| Purpose | Choose between options | Advance to next phase | +| Multiple options | Yes (with "Other") | No (single checkbox) | +| Workflow job | `handle-decision` | `handle-approval` | + +## Workflow Detection + +The `sdlc-hitl.yml` workflow triggers on `issue_comment.edited` events. It checks: + +1. **For decisions**: Comment contains `` marker is present +- The marker is on the line immediately before the checkbox +- The checkbox format is exactly `- [ ] Approve...` (spaces matter) +- The comment was edited (not a new comment) + +### "Decision not detected" + +Check that: +- The `` marker is present +- The decision ID uses only lowercase letters, numbers, and hyphens +- The checkbox format is standard markdown: `- [ ] Option` or `- [x] Option` + +## Related Files + +- `.github/workflows/sdlc-hitl.yml` — Workflow handling decisions and approvals +- `sandbox/egg_lib/contract_cli.py` — CLI for creating decisions +- `docs/templates/analysis.md` — Template showing decision usage +- `docs/templates/phase-completion.md` — Template for approval format diff --git a/tests/workflows/__init__.py b/tests/workflows/__init__.py new file mode 100644 index 0000000000..b764448aa7 --- /dev/null +++ b/tests/workflows/__init__.py @@ -0,0 +1 @@ +"""Tests for GitHub workflow integration.""" diff --git a/tests/workflows/test_hitl_integration.py b/tests/workflows/test_hitl_integration.py new file mode 100644 index 0000000000..93a4a82b17 --- /dev/null +++ b/tests/workflows/test_hitl_integration.py @@ -0,0 +1,148 @@ +"""Tests verifying CLI output format matches workflow regex patterns. + +The sdlc-hitl.yml workflow uses specific regex patterns to detect HITL decisions +and phase approvals. These tests ensure the CLI output format matches what the +workflow expects. +""" + +import re +import sys +from pathlib import Path + +# Add sandbox to path for import +sys.path.insert(0, str(Path(__file__).parent.parent.parent / "sandbox")) + +from egg_lib.contract_cli import format_decision_markdown + + +class TestWorkflowRegexPatterns: + """Tests that CLI output matches workflow regex patterns. + + These patterns are extracted from .github/workflows/sdlc-hitl.yml + """ + + # Pattern from sdlc-hitl.yml line 57: + # grep -oP ' +- [ ] Approve and advance to plan phase + +--- + +*Authored-by: egg* +""" + + # Workflow condition 1: contains ' +- [ ] Approve and advance to plan phase""" + + decision = format_decision_markdown( + "decision-1", "Question?", [{"id": "opt-1", "label": "Yes"}] + ) + + # Decision has its own marker + assert "", "", diff --git a/tests/sandbox/test_contract_cli.py b/tests/sandbox/test_contract_cli.py index 082566029d..f4a6a3443b 100644 --- a/tests/sandbox/test_contract_cli.py +++ b/tests/sandbox/test_contract_cli.py @@ -24,6 +24,7 @@ parse_phase_id, parse_task_id, validate_commit_sha, + validate_decision_id, ) @@ -563,6 +564,69 @@ def test_format_decision_markdown_special_characters(self): assert "**Is this a `code` example?**" in result + def test_format_decision_markdown_rejects_invalid_id(self): + """Test that format_decision_markdown rejects invalid decision IDs.""" + options = [{"id": "opt-1", "label": "Yes"}] + + with pytest.raises(ValueError, match="Invalid decision_id"): + format_decision_markdown("Decision-1", "Question?", options) + + with pytest.raises(ValueError, match="Invalid decision_id"): + format_decision_markdown("decision_1", "Question?", options) + + with pytest.raises(ValueError, match="Invalid decision_id"): + format_decision_markdown("decision 1", "Question?", options) + + +class TestValidateDecisionId: + """Tests for validate_decision_id function.""" + + def test_valid_decision_ids(self): + """Test that valid decision IDs pass validation.""" + valid_ids = [ + "decision-1", + "decision-123", + "my-decision", + "abc123", + "a", + "1", + "a-b-c-1-2-3", + ] + for decision_id in valid_ids: + validate_decision_id(decision_id) # Should not raise + + def test_invalid_uppercase(self): + """Test that uppercase letters are rejected.""" + with pytest.raises(ValueError, match="Invalid decision_id"): + validate_decision_id("Decision-1") + + def test_invalid_underscore(self): + """Test that underscores are rejected.""" + with pytest.raises(ValueError, match="Invalid decision_id"): + validate_decision_id("decision_1") + + def test_invalid_spaces(self): + """Test that spaces are rejected.""" + with pytest.raises(ValueError, match="Invalid decision_id"): + validate_decision_id("decision 1") + + def test_invalid_special_chars(self): + """Test that special characters are rejected.""" + invalid_ids = [ + "decision-->", + "decision<1", + "decision!", + "decision@1", + ] + for decision_id in invalid_ids: + with pytest.raises(ValueError, match="Invalid decision_id"): + validate_decision_id(decision_id) + + def test_empty_string(self): + """Test that empty string is rejected.""" + with pytest.raises(ValueError, match="Invalid decision_id"): + validate_decision_id("") + class TestAddDecisionWithMockGateway: """Tests for add-decision command with mock gateway.""" diff --git a/tests/workflows/test_hitl_integration.py b/tests/workflows/test_hitl_integration.py index 93a4a82b17..9a4b8173f5 100644 --- a/tests/workflows/test_hitl_integration.py +++ b/tests/workflows/test_hitl_integration.py @@ -21,9 +21,10 @@ class TestWorkflowRegexPatterns: These patterns are extracted from .github/workflows/sdlc-hitl.yml """ - # Pattern from sdlc-hitl.yml line 57: + # Pattern from sdlc-hitl.yml step "Parse decision changes": # grep -oP '