ai-dev-workflow 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/PKG-INFO +47 -8
  2. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/README.md +46 -7
  3. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/pyproject.toml +8 -1
  4. ai_dev_workflow-0.3.0/tests/test_active_work_context.py +48 -0
  5. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_ai_workflow_cli.py +30 -3
  6. ai_dev_workflow-0.3.0/tests/test_codex_cloud_executor.py +206 -0
  7. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_composite_work_item.py +29 -1
  8. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_connection_and_mcp.py +24 -4
  9. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_general_init.py +5 -0
  10. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_issue_publisher.py +29 -4
  11. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_queue_dispatcher.py +82 -25
  12. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_setup_wizard.py +21 -4
  13. ai_dev_workflow-0.3.0/tests/test_work_breakdown.py +66 -0
  14. ai_dev_workflow-0.3.0/tests/test_work_claim.py +64 -0
  15. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_worker_registry.py +24 -0
  16. ai_dev_workflow-0.3.0/tests/test_workflow_handoff.py +88 -0
  17. ai_dev_workflow-0.3.0/tests/test_workflow_runtime.py +147 -0
  18. ai_dev_workflow-0.3.0/tools/active_work_context.py +128 -0
  19. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/ado_provider.py +15 -0
  20. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/ai_dev_workflow.egg-info/PKG-INFO +47 -8
  21. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/ai_dev_workflow.egg-info/SOURCES.txt +12 -0
  22. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/ai_dev_workflow.egg-info/top_level.txt +7 -0
  23. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/ai_workflow_cli.py +379 -10
  24. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/composite_work_item.py +33 -0
  25. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/github_work_item_provider.py +28 -1
  26. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/issue_publisher.py +36 -4
  27. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/setup_wizard.py +78 -18
  28. ai_dev_workflow-0.3.0/tools/skill_registry.py +43 -0
  29. ai_dev_workflow-0.3.0/tools/source_connection.py +101 -0
  30. ai_dev_workflow-0.3.0/tools/work_breakdown.py +115 -0
  31. ai_dev_workflow-0.3.0/tools/work_claim.py +246 -0
  32. ai_dev_workflow-0.3.0/tools/worker_identity.py +95 -0
  33. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/worker_registry.py +45 -3
  34. ai_dev_workflow-0.3.0/tools/workflow_handoff.py +121 -0
  35. ai_dev_workflow-0.3.0/tools/workflow_runtime.py +151 -0
  36. ai_dev_workflow-0.2.0/tests/test_codex_cloud_executor.py +0 -82
  37. ai_dev_workflow-0.2.0/tools/source_connection.py +0 -21
  38. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/setup.cfg +0 -0
  39. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_ado_provider.py +0 -0
  40. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_ai_provider.py +0 -0
  41. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_bootstrap_project.py +0 -0
  42. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_codespaces_environment.py +0 -0
  43. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_framework_update.py +0 -0
  44. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_instruction_adapter.py +0 -0
  45. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_package_install.py +0 -0
  46. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tests/test_planning_context.py +0 -0
  47. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/ado_mcp.py +0 -0
  48. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/ai_dev_workflow.egg-info/dependency_links.txt +0 -0
  49. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/ai_dev_workflow.egg-info/entry_points.txt +0 -0
  50. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/ai_dev_workflow.egg-info/requires.txt +0 -0
  51. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/ai_provider.py +0 -0
  52. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/framework_update.py +0 -0
  53. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/instruction_adapter.py +0 -0
  54. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/planning_context.py +0 -0
  55. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/setup_context.py +0 -0
  56. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/work_item_provider.py +0 -0
  57. {ai_dev_workflow-0.2.0 → ai_dev_workflow-0.3.0}/tools/work_item_registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-dev-workflow
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Provider-neutral AI-assisted development workflow and CLI
5
5
  Author: vuthethienlong
6
6
  Project-URL: Homepage, https://github.com/vuthethienlong/ai-dev-workflow
@@ -16,7 +16,7 @@ Requires-Dist: PyYAML>=6.0
16
16
 
17
17
  Reusable, provider-neutral workflow for AI-assisted software development.
18
18
 
19
- This repository starts from the workflow proven in the Dragon's Dogma 2 project and keeps Superpowers-style artifacts as first-class inputs: brainstorming, spec, ADR, implementation plan, tests, PR evidence and runtime verification.
19
+ This repository provides a reusable, provider-neutral AI development workflow with first-class brainstorming, spec, ADR, implementation plan, tests, PR evidence and runtime verification artifacts.
20
20
 
21
21
  ## Default lifecycle
22
22
 
@@ -30,7 +30,7 @@ Brainstorm
30
30
  -> RED
31
31
  -> ImplementerAgent implements
32
32
  -> GREEN + regression
33
- -> ReviewerAgent (optional/manual by default)
33
+ -> ReviewerAgent (automatic independent review by default)
34
34
  -> Integration
35
35
  -> Runtime verification batch when required
36
36
  -> Promotion / Done
@@ -41,7 +41,7 @@ Brainstorm
41
41
  - `ArchitectAgent`: brainstorm, spec, ADR and acceptance criteria.
42
42
  - `TesterAgent`: derives tests from the frozen contract and proves RED before implementation.
43
43
  - `ImplementerAgent`: implements without changing the frozen test contract; must reach GREEN and run applicable regression checks.
44
- - `ReviewerAgent`: optional semantic/architecture reviewer. Disabled/manual by default in v1.
44
+ - `ReviewerAgent`: automatic semantic/architecture reviewer by default; must use a different worker_id from the Implementer. Separate credential/identity is optional.
45
45
  - `RuntimeVerifierAgent`: optional, project-specific runtime verification role.
46
46
 
47
47
  A role is not a model. GPT, Codex, Claude, local models or future providers are replaceable adapters under these contracts.
@@ -52,7 +52,7 @@ A role is not a model. GPT, Codex, Claude, local models or future providers are
52
52
  - `Ready` is the commitment/freeze boundary.
53
53
  - Test design happens before implementation.
54
54
  - Implementer may report a bad test/spec as blocked, but must not rewrite expectations merely to make tests pass.
55
- - Deterministic CI gates run before optional semantic AI review.
55
+ - Deterministic CI gates run before automatic independent semantic review.
56
56
  - Runtime verification is separate from offline/CI verification.
57
57
  - Projects consume versioned reusable workflows such as `@v1`; they do not copy the engine.
58
58
 
@@ -70,6 +70,8 @@ project tests/runtime rules
70
70
 
71
71
  Its caller workflow will reference this repository's reusable workflow.
72
72
 
73
+ The framework does **not** install its own CI/check workflow into consumer repositories, does not configure required status checks, and does not modify branch protection. Existing consumer-repository build, test, lint, security, approval, and merge policies remain authoritative. The generated `.github/workflows/ai-workflow.yml` is orchestration only.
74
+
73
75
  See:
74
76
  - `AGENTS.md`
75
77
  - `docs/lifecycle.md`
@@ -278,7 +280,7 @@ Local stdio MCP remains available with `--ado-mcp-mode local`. Supported MCP aut
278
280
 
279
281
  ## Worker registration
280
282
 
281
- Setup can register local, Codespaces, or cloud workers separately from AI provider configuration.
283
+ Setup can register local, Codespaces, or cloud workers separately from AI provider configuration. Worker IDs are independent from credential identities, so multiple workers may share one credential reference.
282
284
 
283
285
  Example local hybrid worker:
284
286
 
@@ -326,14 +328,28 @@ Use `./bin/ai-workflow update --check` to inspect the configured framework ref,
326
328
 
327
329
  ## Install from PyPI
328
330
 
329
- Once a release is published to PyPI, consumers do not need to clone this repository:
331
+ Consumers do not need to clone this repository:
330
332
 
331
333
  ```bash
332
334
  python -m pip install --upgrade ai-dev-workflow
333
- ai-workflow --help
335
+ ```
336
+
337
+ After installation, start with the built-in guide:
338
+
339
+ ```bash
340
+ ai-workflow guide
341
+ ```
342
+
343
+ Typical first-run flow:
344
+
345
+ ```bash
346
+ cd /path/to/project
334
347
  ai-workflow setup
348
+ ai-workflow doctor
335
349
  ```
336
350
 
351
+ The setup wizard detects the containing Git repository by default, shows the generated plan/configuration, and prints the next required steps after apply. Use `ai-workflow setup --help` for setup options and `ai-workflow guide` at any time for the end-to-end usage flow.
352
+
337
353
  The GitHub repository may remain private because normal consumers install the built distribution from PyPI rather than cloning the source repository.
338
354
 
339
355
  On another machine, install Python 3.10+ and run the same `pip install ai-dev-workflow` command. Project-specific configuration remains in each consumer repository.
@@ -346,3 +362,26 @@ ai-workflow update
346
362
  ```
347
363
 
348
364
  The first command upgrades the installed CLI package. The second migrates/updates the workflow installation in the current project.
365
+
366
+
367
+ ## Active work and claims
368
+
369
+ A selected task is not considered started until its worker/provider acknowledges it:
370
+
371
+ ```text
372
+ ready/queued
373
+ -> reserved
374
+ -> offered
375
+ -> accepted by worker/provider
376
+ -> claim committed
377
+ -> active status
378
+ ```
379
+
380
+ Local agents acknowledge with the registered worker identity (for example through `ai-workflow claim`). Phase completion can be recorded with `ai-workflow complete-phase`, which releases the current claim and reserves an eligible worker for the next role. Automatic review always excludes the implementation worker_id; identity separation is enforced only when the optional strict-review policy is enabled.
381
+
382
+
383
+ ## Worker credentials and identity
384
+
385
+ Worker IDs and credentials are separate concepts. Several workers may intentionally use the same credential reference and therefore operate as the same GitHub/ADO actor. This is valid for normal planning, testing, implementation, and runtime work.
386
+
387
+ The workflow verifies effective actor identity where possible. A distinct credential is not required merely because workers are different. By default, ReviewerAgent only needs a different worker_id from the Implementer. Projects that require stronger separation may enable `reviewer.require_distinct_identity: true`.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Reusable, provider-neutral workflow for AI-assisted software development.
4
4
 
5
- This repository starts from the workflow proven in the Dragon's Dogma 2 project and keeps Superpowers-style artifacts as first-class inputs: brainstorming, spec, ADR, implementation plan, tests, PR evidence and runtime verification.
5
+ This repository provides a reusable, provider-neutral AI development workflow with first-class brainstorming, spec, ADR, implementation plan, tests, PR evidence and runtime verification artifacts.
6
6
 
7
7
  ## Default lifecycle
8
8
 
@@ -16,7 +16,7 @@ Brainstorm
16
16
  -> RED
17
17
  -> ImplementerAgent implements
18
18
  -> GREEN + regression
19
- -> ReviewerAgent (optional/manual by default)
19
+ -> ReviewerAgent (automatic independent review by default)
20
20
  -> Integration
21
21
  -> Runtime verification batch when required
22
22
  -> Promotion / Done
@@ -27,7 +27,7 @@ Brainstorm
27
27
  - `ArchitectAgent`: brainstorm, spec, ADR and acceptance criteria.
28
28
  - `TesterAgent`: derives tests from the frozen contract and proves RED before implementation.
29
29
  - `ImplementerAgent`: implements without changing the frozen test contract; must reach GREEN and run applicable regression checks.
30
- - `ReviewerAgent`: optional semantic/architecture reviewer. Disabled/manual by default in v1.
30
+ - `ReviewerAgent`: automatic semantic/architecture reviewer by default; must use a different worker_id from the Implementer. Separate credential/identity is optional.
31
31
  - `RuntimeVerifierAgent`: optional, project-specific runtime verification role.
32
32
 
33
33
  A role is not a model. GPT, Codex, Claude, local models or future providers are replaceable adapters under these contracts.
@@ -38,7 +38,7 @@ A role is not a model. GPT, Codex, Claude, local models or future providers are
38
38
  - `Ready` is the commitment/freeze boundary.
39
39
  - Test design happens before implementation.
40
40
  - Implementer may report a bad test/spec as blocked, but must not rewrite expectations merely to make tests pass.
41
- - Deterministic CI gates run before optional semantic AI review.
41
+ - Deterministic CI gates run before automatic independent semantic review.
42
42
  - Runtime verification is separate from offline/CI verification.
43
43
  - Projects consume versioned reusable workflows such as `@v1`; they do not copy the engine.
44
44
 
@@ -56,6 +56,8 @@ project tests/runtime rules
56
56
 
57
57
  Its caller workflow will reference this repository's reusable workflow.
58
58
 
59
+ The framework does **not** install its own CI/check workflow into consumer repositories, does not configure required status checks, and does not modify branch protection. Existing consumer-repository build, test, lint, security, approval, and merge policies remain authoritative. The generated `.github/workflows/ai-workflow.yml` is orchestration only.
60
+
59
61
  See:
60
62
  - `AGENTS.md`
61
63
  - `docs/lifecycle.md`
@@ -264,7 +266,7 @@ Local stdio MCP remains available with `--ado-mcp-mode local`. Supported MCP aut
264
266
 
265
267
  ## Worker registration
266
268
 
267
- Setup can register local, Codespaces, or cloud workers separately from AI provider configuration.
269
+ Setup can register local, Codespaces, or cloud workers separately from AI provider configuration. Worker IDs are independent from credential identities, so multiple workers may share one credential reference.
268
270
 
269
271
  Example local hybrid worker:
270
272
 
@@ -312,14 +314,28 @@ Use `./bin/ai-workflow update --check` to inspect the configured framework ref,
312
314
 
313
315
  ## Install from PyPI
314
316
 
315
- Once a release is published to PyPI, consumers do not need to clone this repository:
317
+ Consumers do not need to clone this repository:
316
318
 
317
319
  ```bash
318
320
  python -m pip install --upgrade ai-dev-workflow
319
- ai-workflow --help
321
+ ```
322
+
323
+ After installation, start with the built-in guide:
324
+
325
+ ```bash
326
+ ai-workflow guide
327
+ ```
328
+
329
+ Typical first-run flow:
330
+
331
+ ```bash
332
+ cd /path/to/project
320
333
  ai-workflow setup
334
+ ai-workflow doctor
321
335
  ```
322
336
 
337
+ The setup wizard detects the containing Git repository by default, shows the generated plan/configuration, and prints the next required steps after apply. Use `ai-workflow setup --help` for setup options and `ai-workflow guide` at any time for the end-to-end usage flow.
338
+
323
339
  The GitHub repository may remain private because normal consumers install the built distribution from PyPI rather than cloning the source repository.
324
340
 
325
341
  On another machine, install Python 3.10+ and run the same `pip install ai-dev-workflow` command. Project-specific configuration remains in each consumer repository.
@@ -332,3 +348,26 @@ ai-workflow update
332
348
  ```
333
349
 
334
350
  The first command upgrades the installed CLI package. The second migrates/updates the workflow installation in the current project.
351
+
352
+
353
+ ## Active work and claims
354
+
355
+ A selected task is not considered started until its worker/provider acknowledges it:
356
+
357
+ ```text
358
+ ready/queued
359
+ -> reserved
360
+ -> offered
361
+ -> accepted by worker/provider
362
+ -> claim committed
363
+ -> active status
364
+ ```
365
+
366
+ Local agents acknowledge with the registered worker identity (for example through `ai-workflow claim`). Phase completion can be recorded with `ai-workflow complete-phase`, which releases the current claim and reserves an eligible worker for the next role. Automatic review always excludes the implementation worker_id; identity separation is enforced only when the optional strict-review policy is enabled.
367
+
368
+
369
+ ## Worker credentials and identity
370
+
371
+ Worker IDs and credentials are separate concepts. Several workers may intentionally use the same credential reference and therefore operate as the same GitHub/ADO actor. This is valid for normal planning, testing, implementation, and runtime work.
372
+
373
+ The workflow verifies effective actor identity where possible. A distinct credential is not required merely because workers are different. By default, ReviewerAgent only needs a different worker_id from the Implementer. Projects that require stronger separation may enable `reviewer.require_distinct_identity: true`.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ai-dev-workflow"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Provider-neutral AI-assisted development workflow and CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -31,6 +31,10 @@ ai-workflow = "ai_workflow_cli:main"
31
31
  package-dir = {"" = "tools"}
32
32
  py-modules = [
33
33
  "ado_mcp",
34
+ "workflow_handoff",
35
+ "workflow_runtime",
36
+ "work_claim",
37
+ "active_work_context",
34
38
  "ado_provider",
35
39
  "ai_provider",
36
40
  "ai_workflow_cli",
@@ -43,7 +47,10 @@ py-modules = [
43
47
  "setup_context",
44
48
  "setup_wizard",
45
49
  "source_connection",
50
+ "skill_registry",
51
+ "work_breakdown",
46
52
  "work_item_provider",
47
53
  "work_item_registry",
54
+ "worker_identity",
48
55
  "worker_registry",
49
56
  ]
@@ -0,0 +1,48 @@
1
+ import sys, unittest
2
+ from pathlib import Path
3
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
4
+
5
+ from active_work_context import (
6
+ build_active_work_context, validate_context, role_for_phase,
7
+ queued_status_for_phase, active_status_for_phase,
8
+ )
9
+
10
+
11
+ class Tests(unittest.TestCase):
12
+ def test_context_binds_worker_issue_phase_and_role(self):
13
+ worker={
14
+ "worker_id":"local-1","provider":"chatgpt","environment":"local",
15
+ "roles":["TesterAgent","ImplementerAgent"],
16
+ "identity":{"github":{"actor":"devuser"}},
17
+ }
18
+ ctx=build_active_work_context(
19
+ worker=worker,
20
+ phase="test-design",
21
+ github_issue={"repository":"o/r","number":85,"url":"https://example/85"},
22
+ ado_work_item={"id":1234},
23
+ epic={"id":1200},
24
+ session_id="session-1",
25
+ claim={"state":"reserved","dispatch_id":"d1"},
26
+ )
27
+ self.assertEqual(ctx["required_role"],"TesterAgent")
28
+ self.assertEqual(ctx["queued_status"],"ready")
29
+ self.assertEqual(ctx["active_status"],"in-progress")
30
+ self.assertEqual(ctx["ado_work_item"]["id"],1234)
31
+ self.assertEqual(validate_context(ctx),[])
32
+
33
+ def test_worker_must_support_phase_role(self):
34
+ with self.assertRaisesRegex(ValueError,"does not support"):
35
+ build_active_work_context(
36
+ worker={"worker_id":"w","roles":["ImplementerAgent"]},
37
+ phase="code-review",
38
+ github_issue={"repository":"o/r","number":1},
39
+ )
40
+
41
+ def test_phase_maps(self):
42
+ self.assertEqual(role_for_phase("implementing"),"ImplementerAgent")
43
+ self.assertEqual(queued_status_for_phase("code-review"),"in-review")
44
+ self.assertEqual(active_status_for_phase("runtime-verify"),"runtime-pending")
45
+ self.assertIsNone(role_for_phase("done"))
46
+
47
+
48
+ if __name__=="__main__": unittest.main()
@@ -1,7 +1,9 @@
1
- import sys, unittest
1
+ import io, sys, unittest
2
2
  from pathlib import Path
3
+ from contextlib import redirect_stdout
4
+ from unittest.mock import patch
3
5
  sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
4
- from ai_workflow_cli import build_ado_config, doctor, parse_role_provider, parse_ado_field_mapping, PACKAGE_VERSION
6
+ from ai_workflow_cli import build_ado_config, doctor, parse_role_provider, parse_ado_field_mapping, onboarding_guide, setup_next_steps, main, PACKAGE_VERSION
5
7
 
6
8
  DISCOVERY={
7
9
  "organization":"org","project":"proj","project_id":"p","process_id":"proc",
@@ -16,7 +18,32 @@ DISCOVERY={
16
18
 
17
19
  class Tests(unittest.TestCase):
18
20
  def test_package_version(self):
19
- self.assertEqual(PACKAGE_VERSION, "0.2.0")
21
+ self.assertEqual(PACKAGE_VERSION, "0.3.0")
22
+
23
+ def test_guide_command_is_exposed_by_cli(self):
24
+ output=io.StringIO()
25
+ with patch.object(sys,"argv",["ai-workflow","guide"]), redirect_stdout(output):
26
+ self.assertEqual(main(),0)
27
+ text=output.getvalue()
28
+ self.assertIn("AI Dev Workflow - Getting Started",text)
29
+ self.assertIn("ai-workflow setup",text)
30
+
31
+ def test_onboarding_guide_covers_install_setup_claim_handoff_and_update(self):
32
+ text=onboarding_guide()
33
+ self.assertIn("ai-workflow setup",text)
34
+ self.assertIn("ai-workflow doctor",text)
35
+ self.assertIn("ai-workflow claim",text)
36
+ self.assertIn("ai-workflow complete-phase",text)
37
+ self.assertIn("ai-workflow update --check",text)
38
+ self.assertIn("Local worker credentials stay on the local machine",text)
39
+
40
+ def test_setup_next_steps_distinguishes_plan_only_and_applied_setup(self):
41
+ planned=setup_next_steps(plan_only=True)
42
+ applied=setup_next_steps()
43
+ self.assertIn("Re-run ai-workflow setup without --plan-only",planned)
44
+ self.assertIn("Setup completed.",applied)
45
+ self.assertIn("ai-workflow doctor",applied)
46
+ self.assertIn("ai-workflow guide",applied)
20
47
 
21
48
  def test_init_generates_explicit_mapping_and_agents(self):
22
49
  cfg=build_ado_config(DISCOVERY)
@@ -0,0 +1,206 @@
1
+ """Tests for the Codex Cloud GitHub executor."""
2
+ import sys
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ sys.path.insert(
7
+ 0,
8
+ str(
9
+ Path(__file__).resolve().parents[1]
10
+ / '.github'
11
+ / 'actions'
12
+ / 'codex-cloud-executor'
13
+ ),
14
+ )
15
+ from codex_cloud_executor import (
16
+ MARKER,
17
+ DeliveryError,
18
+ GitHubAPIError,
19
+ deliver,
20
+ prompt,
21
+ )
22
+
23
+
24
+ def payload():
25
+ return {
26
+ 'issue_number': 42,
27
+ 'dispatch_id': 'dispatch-42',
28
+ 'suggested_branch': 'issue-42-example',
29
+ 'base_branch': 'integration',
30
+ 'execution_type': 'implementation',
31
+ 'workflow_phase': 'implementing',
32
+ 'required_role': 'ImplementerAgent',
33
+ }
34
+
35
+
36
+ class FakeGitHub:
37
+ def __init__(self):
38
+ self.prs = {}
39
+ self.notes = {}
40
+ self.created_refs = 0
41
+ self.deleted_refs = 0
42
+ self.created_prs = 0
43
+ self.branch_exists = False
44
+ self.permissions = {'push': True}
45
+ self.fail_create_pr = None
46
+ self.fail_comment = None
47
+ self.create_ref_conflict = False
48
+
49
+ def repository_info(self):
50
+ return {'permissions': dict(self.permissions)}
51
+
52
+ def find_pr(self, branch):
53
+ return self.prs.get(branch)
54
+
55
+ def get_ref(self, branch):
56
+ if branch == 'integration':
57
+ return {'object': {'sha': 'base-sha'}}
58
+ if branch == 'issue-42-example' and self.branch_exists:
59
+ return {'object': {'sha': 'bootstrap-sha'}}
60
+ raise GitHubAPIError(404, 'GET', f'/git/ref/heads/{branch}', 'Not Found')
61
+
62
+ def create_commit_from(self, base_sha, message):
63
+ return {'sha': 'bootstrap-sha'}
64
+
65
+ def create_ref(self, branch, sha):
66
+ if self.create_ref_conflict:
67
+ self.branch_exists = True
68
+ raise GitHubAPIError(
69
+ 422, 'POST', '/git/refs', 'Reference already exists'
70
+ )
71
+ self.created_refs += 1
72
+ self.branch_exists = True
73
+ return {'ref': f'refs/heads/{branch}'}
74
+
75
+ def delete_ref(self, branch):
76
+ self.deleted_refs += 1
77
+ self.branch_exists = False
78
+ return {}
79
+
80
+ def create_pr(self, title, body, head, base):
81
+ if self.fail_create_pr:
82
+ status = self.fail_create_pr
83
+ raise GitHubAPIError(
84
+ status,
85
+ 'POST',
86
+ '/pulls',
87
+ 'Resource not accessible by integration',
88
+ )
89
+ self.created_prs += 1
90
+ pr = {'number': 7, 'head': {'ref': head}, 'base': {'ref': base}}
91
+ self.prs[head] = pr
92
+ return pr
93
+
94
+ def comments(self, number):
95
+ return list(self.notes.get(number, []))
96
+
97
+ def comment(self, number, body):
98
+ if self.fail_comment:
99
+ status = self.fail_comment
100
+ raise GitHubAPIError(
101
+ status,
102
+ 'POST',
103
+ f'/issues/{number}/comments',
104
+ 'Resource not accessible by integration',
105
+ )
106
+ self.notes.setdefault(number, []).append({'body': body})
107
+ return {'body': body}
108
+
109
+
110
+ class CodexCloudExecutorTests(unittest.TestCase):
111
+ def test_prompt_contains_codex_trigger_and_frozen_contract_rules(self):
112
+ text = prompt(payload())
113
+ self.assertIn('@codex Implement Issue #42', text)
114
+ self.assertIn('AGENTS.md', text)
115
+ self.assertIn('TDD', text)
116
+ self.assertIn('Do not merge', text)
117
+ self.assertIn('dispatch-42', text)
118
+ self.assertIn(MARKER, text)
119
+
120
+ def test_tester_prompt_stays_in_test_design_role(self):
121
+ p = payload()
122
+ p["workflow_phase"] = "test-design"
123
+ p["required_role"] = "TesterAgent"
124
+ text = prompt(p)
125
+ self.assertIn("@codex Design tests for Issue #42", text)
126
+ self.assertIn("Act as TesterAgent", text)
127
+ self.assertIn("prove the expected RED", text)
128
+ self.assertIn("Do not implement production behavior", text)
129
+
130
+ def test_delivery_is_idempotent_for_same_dispatch(self):
131
+ api = FakeGitHub()
132
+ first = deliver(api, payload())
133
+ second = deliver(api, payload())
134
+ self.assertEqual(first['number'], second['number'])
135
+ self.assertEqual(api.created_refs, 1)
136
+ self.assertEqual(api.created_prs, 1)
137
+ self.assertEqual(len(api.notes[7]), 1)
138
+
139
+ def test_existing_pr_gets_only_missing_codex_comment(self):
140
+ api = FakeGitHub()
141
+ api.prs['issue-42-example'] = {'number': 9}
142
+ deliver(api, payload())
143
+ self.assertEqual(api.created_refs, 0)
144
+ self.assertEqual(api.created_prs, 0)
145
+ self.assertEqual(len(api.notes[9]), 1)
146
+
147
+ def test_preflight_denies_missing_contents_write_before_artifacts(self):
148
+ api = FakeGitHub()
149
+ api.permissions['push'] = False
150
+ with self.assertRaises(DeliveryError) as caught:
151
+ deliver(api, payload())
152
+ self.assertEqual(caught.exception.code, 'github-contents-write-required')
153
+ self.assertEqual(api.created_refs, 0)
154
+ self.assertEqual(api.created_prs, 0)
155
+
156
+ def test_pr_create_403_deletes_branch_created_by_this_attempt(self):
157
+ api = FakeGitHub()
158
+ api.fail_create_pr = 403
159
+ with self.assertRaises(DeliveryError) as caught:
160
+ deliver(api, payload())
161
+ error = caught.exception
162
+ self.assertEqual(error.code, 'github-pr-create-denied')
163
+ self.assertEqual(error.status, 403)
164
+ self.assertIn('pull-request write permission', str(error))
165
+ self.assertEqual(error.recovery, 'new-bootstrap-branch-deleted')
166
+ self.assertEqual(api.created_refs, 1)
167
+ self.assertEqual(api.deleted_refs, 1)
168
+ self.assertFalse(api.branch_exists)
169
+ self.assertEqual(api.created_prs, 0)
170
+
171
+ def test_existing_orphan_branch_is_reused_after_previous_partial_attempt(self):
172
+ api = FakeGitHub()
173
+ api.create_ref_conflict = True
174
+ api.branch_exists = True
175
+ pr = deliver(api, payload())
176
+ self.assertEqual(pr['number'], 7)
177
+ self.assertEqual(api.created_refs, 0)
178
+ self.assertEqual(api.deleted_refs, 0)
179
+ self.assertEqual(api.created_prs, 1)
180
+ self.assertEqual(len(api.notes[7]), 1)
181
+
182
+ def test_comment_403_preserves_pr_and_retry_adds_one_trigger(self):
183
+ api = FakeGitHub()
184
+ api.fail_comment = 403
185
+ with self.assertRaises(DeliveryError) as caught:
186
+ deliver(api, payload())
187
+ error = caught.exception
188
+ self.assertEqual(error.code, 'github-codex-trigger-comment-denied')
189
+ self.assertEqual(
190
+ error.recovery, 'keep-existing-pr-and-retry-comment'
191
+ )
192
+ self.assertEqual(api.created_refs, 1)
193
+ self.assertEqual(api.created_prs, 1)
194
+ self.assertEqual(api.deleted_refs, 0)
195
+ self.assertIn('issue-42-example', api.prs)
196
+
197
+ api.fail_comment = None
198
+ retry = deliver(api, payload())
199
+ self.assertEqual(retry['number'], 7)
200
+ self.assertEqual(api.created_refs, 1)
201
+ self.assertEqual(api.created_prs, 1)
202
+ self.assertEqual(len(api.notes[7]), 1)
203
+
204
+
205
+ if __name__ == '__main__':
206
+ unittest.main()
@@ -2,7 +2,7 @@ import sys, unittest
2
2
  from pathlib import Path
3
3
  sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
4
4
 
5
- from composite_work_item import build_composite_config, sync_projection
5
+ from composite_work_item import build_composite_config, sync_projection, sync_active_work_context
6
6
  from ado_provider import validate_field_mapping, defaults_for_wit, execution_field_updates
7
7
 
8
8
  DISCOVERY={
@@ -64,6 +64,34 @@ class Tests(unittest.TestCase):
64
64
  self.assertEqual(projected["fields"]["Custom.AIPhase"],"code-review")
65
65
  self.assertEqual(projected["fields"]["Custom.GitHubIssue"],"o/r#7")
66
66
 
67
+ def test_active_work_context_projects_worker_role_and_issue(self):
68
+ mapping={
69
+ "Custom.UserStory":{
70
+ "workflow_phase":{"field":"Custom.AIPhase"},
71
+ "github_issue":{"field":"Custom.GitHubIssue"},
72
+ "worker":{"field":"Custom.Worker"},
73
+ "agent_role":{"field":"Custom.AgentRole"},
74
+ }
75
+ }
76
+ cfg=build_composite_config(
77
+ ado={},github={},field_mapping=mapping
78
+ )
79
+ ctx={
80
+ "phase":"implementing",
81
+ "required_role":"ImplementerAgent",
82
+ "worker":{"worker_id":"local-1"},
83
+ "github_issue":{"repository":"o/r","number":85},
84
+ }
85
+ projected=sync_active_work_context(
86
+ cfg,wit_ref="Custom.UserStory",context=ctx,
87
+ values={"pull_request":"o/r#91"},
88
+ )
89
+ self.assertEqual(projected["state"],"Active")
90
+ self.assertEqual(projected["fields"]["Custom.AIPhase"],"implementing")
91
+ self.assertEqual(projected["fields"]["Custom.GitHubIssue"],"o/r#85")
92
+ self.assertEqual(projected["fields"]["Custom.Worker"],"local-1")
93
+ self.assertEqual(projected["fields"]["Custom.AgentRole"],"ImplementerAgent")
94
+
67
95
  def test_done_projects_to_done(self):
68
96
  cfg=build_composite_config(
69
97
  ado={},github={},field_mapping=MAPPING
@@ -3,7 +3,7 @@ from pathlib import Path
3
3
  from unittest.mock import patch, Mock
4
4
  sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
5
5
 
6
- from source_connection import resolve_github_token
6
+ from source_connection import resolve_github_token, parse_github_repository, detect_repository_context
7
7
  from work_item_registry import validate_work_item_provider
8
8
  from ado_mcp import build_ado_mcp, ado_mcp_credentials
9
9
  from setup_wizard import build_setup_plan
@@ -19,13 +19,33 @@ class Tests(unittest.TestCase):
19
19
  if old is None: os.environ.pop("GH_TOKEN",None)
20
20
  else: os.environ["GH_TOKEN"]=old
21
21
 
22
- @patch("source_connection.subprocess.run")
22
+ @patch("source_connection._run", return_value="oauth-token")
23
23
  def test_github_cli_oauth_session(self, run):
24
- run.return_value=Mock(stdout="oauth-token\n")
25
24
  token, meta=resolve_github_token("gh-cli")
26
25
  self.assertEqual(token,"oauth-token")
27
26
  self.assertEqual(meta["type"],"gh-cli-oauth")
28
- run.assert_called_once()
27
+ run.assert_called_once_with(["gh","auth","token"])
28
+
29
+ def test_parse_github_repository_remote_formats(self):
30
+ self.assertEqual(parse_github_repository("git@github.com:owner/repo.git"), "owner/repo")
31
+ self.assertEqual(parse_github_repository("https://github.com/owner/repo.git"), "owner/repo")
32
+ self.assertIsNone(parse_github_repository("https://gitlab.com/owner/repo.git"))
33
+
34
+ @patch("source_connection._run")
35
+ def test_detect_repository_context_uses_git_root_and_origin(self, run):
36
+ run.side_effect = ["/work/repo", "git@github.com:owner/repo.git"]
37
+ out = detect_repository_context("/work/repo/subdir")
38
+ self.assertEqual(out["root"], "/work/repo")
39
+ self.assertEqual(out["github_repository"], "owner/repo")
40
+
41
+ @patch("source_connection._run", return_value="oauth-token")
42
+ @patch("source_connection.subprocess.run")
43
+ def test_github_browser_auth_returns_non_persisted_cli_session(self, subprocess_run, run):
44
+ token, meta = resolve_github_token("gh-browser")
45
+ self.assertEqual(token, "oauth-token")
46
+ self.assertEqual(meta["type"], "gh-cli-oauth")
47
+ self.assertEqual(meta["login"], "browser")
48
+ subprocess_run.assert_called_once_with(["gh", "auth", "login", "--web"], check=True)
29
49
 
30
50
  def test_work_item_provider_registry(self):
31
51
  self.assertEqual(validate_work_item_provider("github-issues"),[])
@@ -48,6 +48,11 @@ class Tests(unittest.TestCase):
48
48
  self.assertTrue(Path("CURRENT.md").exists())
49
49
  self.assertTrue(Path(".devcontainer/devcontainer.json").exists())
50
50
  self.assertTrue(Path(".github/workflows/ai-workflow.yml").exists())
51
+ self.assertFalse(Path(".github/workflows/queue-dispatcher-checks.yml").exists())
52
+ self.assertFalse(Path(".github/workflows/framework-checks.yml").exists())
53
+ generated_workflow=Path(".github/workflows/ai-workflow.yml").read_text(encoding="utf-8")
54
+ self.assertNotIn("Framework Queue Dispatcher Checks",generated_workflow)
55
+ self.assertNotIn("required-check",generated_workflow)
51
56
  Path("AGENTS.md").write_text("custom",encoding="utf-8")
52
57
  perform_init(args)
53
58
  self.assertEqual(Path("AGENTS.md").read_text(encoding="utf-8"),"custom")