@pennyfarthing/core 11.2.2 → 11.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +1 -1
- package/packages/core/dist/cli/commands/doctor-legacy.test.js +2 -2
- package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor.d.ts +63 -0
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +280 -43
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/packages/core/dist/cli/commands/init.d.ts +12 -0
- package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/init.js +45 -0
- package/packages/core/dist/cli/commands/init.js.map +1 -1
- package/packages/core/dist/cli/commands/pyproject-install.test.d.ts +19 -0
- package/packages/core/dist/cli/commands/pyproject-install.test.d.ts.map +1 -0
- package/packages/core/dist/cli/commands/pyproject-install.test.js +261 -0
- package/packages/core/dist/cli/commands/pyproject-install.test.js.map +1 -0
- package/packages/core/dist/cli/commands/update-consolidation.test.js +14 -6
- package/packages/core/dist/cli/commands/update-consolidation.test.js.map +1 -1
- package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/update.js +5 -1
- package/packages/core/dist/cli/commands/update.js.map +1 -1
- package/packages/core/dist/cli/index.js +2 -0
- package/packages/core/dist/cli/index.js.map +1 -1
- package/packages/core/dist/cli/utils/python.d.ts +1 -0
- package/packages/core/dist/cli/utils/python.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/python.js +22 -1
- package/packages/core/dist/cli/utils/python.js.map +1 -1
- package/packages/core/dist/cli/utils/settings-hook-migration.test.d.ts +17 -0
- package/packages/core/dist/cli/utils/settings-hook-migration.test.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/settings-hook-migration.test.js +382 -0
- package/packages/core/dist/cli/utils/settings-hook-migration.test.js.map +1 -0
- package/packages/core/dist/cli/utils/settings-pf-wrapper.test.d.ts +16 -0
- package/packages/core/dist/cli/utils/settings-pf-wrapper.test.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/settings-pf-wrapper.test.js +377 -0
- package/packages/core/dist/cli/utils/settings-pf-wrapper.test.js.map +1 -0
- package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/settings.js +15 -2
- package/packages/core/dist/cli/utils/settings.js.map +1 -1
- package/packages/core/dist/server/paths.d.ts.map +1 -1
- package/packages/core/dist/server/paths.js +6 -0
- package/packages/core/dist/server/paths.js.map +1 -1
- package/packages/core/dist/server/settings.d.ts.map +1 -1
- package/packages/core/dist/server/settings.js +5 -0
- package/packages/core/dist/server/settings.js.map +1 -1
- package/packages/core/dist/workflow/tandem-workflow-templates.test.js +7 -5
- package/packages/core/dist/workflow/tandem-workflow-templates.test.js.map +1 -1
- package/packages/core/dist/workflow/workflow-graph-validation.d.ts +65 -0
- package/packages/core/dist/workflow/workflow-graph-validation.d.ts.map +1 -0
- package/packages/core/dist/workflow/workflow-graph-validation.js +190 -0
- package/packages/core/dist/workflow/workflow-graph-validation.js.map +1 -0
- package/packages/core/dist/workflow/workflow-graph-validation.test.d.ts +18 -0
- package/packages/core/dist/workflow/workflow-graph-validation.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/workflow-graph-validation.test.js +706 -0
- package/packages/core/dist/workflow/workflow-graph-validation.test.js.map +1 -0
- package/packages/core/dist/workflow/workflow-migration.test.js +6 -5
- package/packages/core/dist/workflow/workflow-migration.test.js.map +1 -1
- package/pennyfarthing-dist/agents/dev.md +4 -2
- package/pennyfarthing-dist/agents/devops.md +2 -10
- package/pennyfarthing-dist/agents/reviewer-preflight.md +4 -5
- package/pennyfarthing-dist/agents/sm.md +4 -17
- package/pennyfarthing-dist/commands/pf-health-check.md +30 -11
- package/pennyfarthing-dist/gates/{confidence-sm.md → confidence.md} +16 -17
- package/pennyfarthing-dist/gates/dev-exit.md +75 -0
- package/pennyfarthing-dist/gates/merge-ready.md +49 -0
- package/pennyfarthing-dist/gates/release-ready.md +95 -0
- package/pennyfarthing-dist/gates/reviewer-preflight-check.md +90 -0
- package/pennyfarthing-dist/gates/sm-setup-exit.md +82 -0
- package/pennyfarthing-dist/guides/agent-behavior.md +88 -30
- package/pennyfarthing-dist/guides/gates.md +7 -2
- package/pennyfarthing-dist/scripts/lib/find-root.sh +5 -0
- package/pennyfarthing-dist/scripts/lib/run-pf.sh +7 -0
- package/pennyfarthing-dist/skills/pf-settings/skill.md +42 -0
- package/pennyfarthing-dist/skills/skill-registry.yaml +15 -0
- package/pennyfarthing-dist/templates/pyproject.toml +27 -0
- package/pennyfarthing-dist/workflows/bdd-tandem.yaml +7 -3
- package/pennyfarthing-dist/workflows/bdd.yaml +7 -3
- package/pennyfarthing-dist/workflows/installation-check/steps/step-01-foundation.md +77 -0
- package/pennyfarthing-dist/workflows/installation-check/steps/step-02-commands.md +82 -0
- package/pennyfarthing-dist/workflows/installation-check/steps/step-03-hooks.md +121 -0
- package/pennyfarthing-dist/workflows/installation-check/steps/step-04-scripts.md +83 -0
- package/pennyfarthing-dist/workflows/installation-check/steps/step-05-layout.md +81 -0
- package/pennyfarthing-dist/workflows/installation-check/steps/step-06-legacy.md +94 -0
- package/pennyfarthing-dist/workflows/installation-check/steps/step-07-tools.md +80 -0
- package/pennyfarthing-dist/workflows/installation-check/steps/step-08-summary.md +99 -0
- package/pennyfarthing-dist/workflows/installation-check/workflow.yaml +47 -0
- package/pennyfarthing-dist/workflows/tdd-tandem.yaml +7 -3
- package/pennyfarthing-dist/workflows/tdd.yaml +7 -3
- package/pennyfarthing-dist/workflows/trivial.yaml +7 -3
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/context.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/focus.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/split.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/cli.py +21 -0
- package/pennyfarthing_scripts/bc/focus.py +1 -0
- package/pennyfarthing_scripts/bc/split.py +52 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/audit_log_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/context_meter_footer.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/events.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/context_meter_footer.py +53 -3
- package/pennyfarthing_scripts/bikerack/tui.py +202 -8
- package/pennyfarthing_scripts/bmad/__init__.py +1 -0
- package/pennyfarthing_scripts/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bmad/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bmad/__pycache__/parser.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bmad/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bmad/__pycache__/test_parser.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/bmad/__pycache__/test_sync.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/bmad/cli.py +197 -0
- package/pennyfarthing_scripts/bmad/importer.py +200 -0
- package/pennyfarthing_scripts/bmad/parser.py +233 -0
- package/pennyfarthing_scripts/bmad/sync.py +464 -0
- package/pennyfarthing_scripts/bmad/test_parser.py +253 -0
- package/pennyfarthing_scripts/bmad/test_sync.py +223 -0
- package/pennyfarthing_scripts/cli.py +10 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/gate_file.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/gate_runner.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/marker.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/settings/__init__.py +0 -0
- package/pennyfarthing_scripts/settings/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/settings/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/settings/__pycache__/settings.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/settings/cli.py +55 -0
- package/pennyfarthing_scripts/settings/settings.py +98 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_evaluation.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_gate_file_resolution.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_resolve_gate_file_field.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_list_team.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/test_confidence_sm_gate.py +17 -16
- package/pennyfarthing_scripts/tests/test_resolve_gate_file_field.py +45 -47
- package/pennyfarthing_scripts/tests/test_workflow_list_team.py +0 -4
- package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Settings logic for reading and writing .pennyfarthing/config.local.yaml.
|
|
3
|
+
|
|
4
|
+
Provides dot-path traversal for get/set operations with type coercion.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from pennyfarthing_scripts.common.config import get_project_root, load_pennyfarthing_config
|
|
12
|
+
|
|
13
|
+
# Top-level keys to show in `pf settings show` (skip layout/panel blobs)
|
|
14
|
+
SHOW_KEYS = ("theme", "workflow", "display", "split", "last_panel")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _coerce_value(value: str) -> Any:
|
|
18
|
+
"""Coerce a string value to bool, int, or leave as str."""
|
|
19
|
+
lower = value.lower()
|
|
20
|
+
if lower == "true":
|
|
21
|
+
return True
|
|
22
|
+
if lower == "false":
|
|
23
|
+
return False
|
|
24
|
+
try:
|
|
25
|
+
return int(value)
|
|
26
|
+
except ValueError:
|
|
27
|
+
return value
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_by_path(data: dict, key: str) -> Any:
|
|
31
|
+
"""Traverse a dict by dot-separated path.
|
|
32
|
+
|
|
33
|
+
Returns the value at the path, or raises KeyError if not found.
|
|
34
|
+
"""
|
|
35
|
+
parts = key.split(".")
|
|
36
|
+
current: Any = data
|
|
37
|
+
for part in parts:
|
|
38
|
+
if not isinstance(current, dict) or part not in current:
|
|
39
|
+
raise KeyError(key)
|
|
40
|
+
current = current[part]
|
|
41
|
+
return current
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _set_by_path(data: dict, key: str, value: Any) -> None:
|
|
45
|
+
"""Set a value in a dict by dot-separated path, creating intermediates."""
|
|
46
|
+
parts = key.split(".")
|
|
47
|
+
current = data
|
|
48
|
+
for part in parts[:-1]:
|
|
49
|
+
if part not in current or not isinstance(current[part], dict):
|
|
50
|
+
current[part] = {}
|
|
51
|
+
current = current[part]
|
|
52
|
+
current[parts[-1]] = value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_setting(key: str) -> Any:
|
|
56
|
+
"""Get a setting value by dot-path."""
|
|
57
|
+
config = load_pennyfarthing_config()
|
|
58
|
+
return _get_by_path(config, key)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def set_setting(key: str, value: str) -> dict:
|
|
62
|
+
"""Set a setting value by dot-path. Returns the updated config."""
|
|
63
|
+
root = get_project_root()
|
|
64
|
+
config_path = root / ".pennyfarthing" / "config.local.yaml"
|
|
65
|
+
|
|
66
|
+
config = load_pennyfarthing_config(root)
|
|
67
|
+
coerced = _coerce_value(value)
|
|
68
|
+
_set_by_path(config, key, coerced)
|
|
69
|
+
|
|
70
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
with open(config_path, "w") as f:
|
|
72
|
+
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
|
73
|
+
|
|
74
|
+
return config
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def show_settings() -> str:
|
|
78
|
+
"""Format interesting top-level settings for display."""
|
|
79
|
+
config = load_pennyfarthing_config()
|
|
80
|
+
lines: list[str] = []
|
|
81
|
+
|
|
82
|
+
for key in SHOW_KEYS:
|
|
83
|
+
if key not in config:
|
|
84
|
+
continue
|
|
85
|
+
val = config[key]
|
|
86
|
+
if isinstance(val, dict):
|
|
87
|
+
lines.append(f"{key}:")
|
|
88
|
+
for k, v in val.items():
|
|
89
|
+
if isinstance(v, dict):
|
|
90
|
+
nested = yaml.dump({k: v}, default_flow_style=False, sort_keys=False)
|
|
91
|
+
for line in nested.rstrip().split("\n"):
|
|
92
|
+
lines.append(f" {line}")
|
|
93
|
+
else:
|
|
94
|
+
lines.append(f" {k}: {v}")
|
|
95
|
+
else:
|
|
96
|
+
lines.append(f"{key}: {val}")
|
|
97
|
+
|
|
98
|
+
return "\n".join(lines) if lines else "(no settings found)"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/pennyfarthing_scripts/tests/__pycache__/test_confidence_sm_gate.cpython-314-pytest-9.0.2.pyc
ADDED
|
Binary file
|
|
Binary file
|
package/pennyfarthing_scripts/tests/__pycache__/test_gate_runner.cpython-314-pytest-9.0.2.pyc
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
"""Tests for
|
|
1
|
+
"""Tests for confidence gate file — Story 90-2 (generalized).
|
|
2
2
|
|
|
3
3
|
Epic: 90 (Confidence Circuit Breaker via Gate)
|
|
4
|
-
Story: 90-2 — Implement
|
|
4
|
+
Story: 90-2 — Implement confidence gate file
|
|
5
5
|
|
|
6
|
-
Tests the confidence
|
|
7
|
-
|
|
6
|
+
Tests the confidence gate file that checks whether an instruction to any
|
|
7
|
+
agent is ambiguous. If ambiguous, <fail> returns clarifying options. If
|
|
8
8
|
unambiguous, <pass> lets the agent proceed.
|
|
9
9
|
|
|
10
|
+
Originally SM-specific (confidence-sm), generalized to agent-agnostic (confidence).
|
|
11
|
+
|
|
10
12
|
Acceptance Criteria:
|
|
11
13
|
- [AC1] Gate file exists in pennyfarthing-dist/gates/ following Gate PRD schema
|
|
12
14
|
- [AC2] Gate has <gate>, <purpose>, <pass>, <fail> blocks
|
|
13
|
-
- [AC3] Gate checks whether
|
|
15
|
+
- [AC3] Gate checks whether instruction is ambiguous
|
|
14
16
|
- [AC4] <fail> block returns clarifying options when ambiguous
|
|
15
17
|
- [AC5] <pass> block lets the agent proceed when unambiguous
|
|
16
18
|
- [AC6] Gate uses model="haiku"
|
|
@@ -30,7 +32,7 @@ from pennyfarthing_scripts.handoff.gate_runner import parse_gate_file
|
|
|
30
32
|
# Fixtures
|
|
31
33
|
# ---------------------------------------------------------------------------
|
|
32
34
|
|
|
33
|
-
GATE_NAME = "confidence
|
|
35
|
+
GATE_NAME = "confidence"
|
|
34
36
|
|
|
35
37
|
# The gate file lives in pennyfarthing-dist/gates/ relative to the framework root
|
|
36
38
|
# In the dogfooding context, the project root is the orchestrator, so we need
|
|
@@ -43,7 +45,7 @@ _GATE_FILE = _FRAMEWORK_ROOT / "pennyfarthing-dist" / "gates" / f"{GATE_NAME}.md
|
|
|
43
45
|
|
|
44
46
|
@pytest.fixture
|
|
45
47
|
def gate_path() -> Path:
|
|
46
|
-
"""Return the expected path to the confidence
|
|
48
|
+
"""Return the expected path to the confidence gate file."""
|
|
47
49
|
return _GATE_FILE
|
|
48
50
|
|
|
49
51
|
|
|
@@ -69,7 +71,7 @@ class TestGateFileExists:
|
|
|
69
71
|
"""AC1: Gate file exists at the expected location."""
|
|
70
72
|
|
|
71
73
|
def test_gate_file_exists(self, gate_path: Path) -> None:
|
|
72
|
-
"""AC1: confidence
|
|
74
|
+
"""AC1: confidence.md exists in pennyfarthing-dist/gates/."""
|
|
73
75
|
assert gate_path.is_file(), f"Gate file not found: {gate_path}"
|
|
74
76
|
|
|
75
77
|
def test_gate_file_not_empty(self, gate_path: Path) -> None:
|
|
@@ -138,11 +140,10 @@ class TestGateSchemaStructure:
|
|
|
138
140
|
|
|
139
141
|
|
|
140
142
|
class TestGateAmbiguityDetection:
|
|
141
|
-
"""AC3: Gate content describes checking for ambiguous
|
|
143
|
+
"""AC3: Gate content describes checking for ambiguous instructions."""
|
|
142
144
|
|
|
143
145
|
def test_purpose_mentions_ambiguity(self, gate_content: str) -> None:
|
|
144
146
|
"""AC3: Purpose section references ambiguity or unclear instructions."""
|
|
145
|
-
# Extract purpose content between tags
|
|
146
147
|
import re
|
|
147
148
|
|
|
148
149
|
purpose_match = re.search(
|
|
@@ -155,17 +156,17 @@ class TestGateAmbiguityDetection:
|
|
|
155
156
|
for term in ["ambig", "unclear", "vague", "confidence", "clarif"]
|
|
156
157
|
), f"Purpose doesn't reference ambiguity: {purpose}"
|
|
157
158
|
|
|
158
|
-
def
|
|
159
|
-
"""AC3: Gate name is 'confidence
|
|
159
|
+
def test_gate_name_is_confidence(self, parsed_gate: dict) -> None:
|
|
160
|
+
"""AC3: Gate name is 'confidence'."""
|
|
160
161
|
assert parsed_gate["name"] == GATE_NAME
|
|
161
162
|
|
|
162
|
-
def
|
|
163
|
-
"""AC3: Gate content
|
|
163
|
+
def test_content_is_agent_agnostic(self, gate_content: str) -> None:
|
|
164
|
+
"""AC3: Gate content is agent-agnostic (not SM-specific)."""
|
|
164
165
|
content_lower = gate_content.lower()
|
|
165
166
|
assert any(
|
|
166
167
|
term in content_lower
|
|
167
|
-
for term in ["
|
|
168
|
-
), "Gate
|
|
168
|
+
for term in ["current agent", "the agent", "any agent"]
|
|
169
|
+
), "Gate should be agent-agnostic"
|
|
169
170
|
|
|
170
171
|
|
|
171
172
|
# ===========================================================================
|
|
@@ -6,7 +6,7 @@ Story: 106-3 — Workflow YAML gate.file integration
|
|
|
6
6
|
Tests the gate.file field support in resolve_gate():
|
|
7
7
|
- Schema extension: gate.file extracted from workflow YAML
|
|
8
8
|
- Backward compatibility: gate.type-only workflows unchanged
|
|
9
|
-
- TDD workflow migration:
|
|
9
|
+
- TDD workflow migration: all phases now have gate.file fields
|
|
10
10
|
|
|
11
11
|
Acceptance Criteria:
|
|
12
12
|
- [AC1] resolve-gate.py reads gate.file field from workflow YAML phases
|
|
@@ -15,9 +15,9 @@ Acceptance Criteria:
|
|
|
15
15
|
- [AC2] Workflows with only gate.type continue to work unchanged
|
|
16
16
|
- [AC2] Existing gate type logic remains functional
|
|
17
17
|
- [AC2] No breaking changes to resolve-gate API
|
|
18
|
-
- [AC3] Green phase in tdd.yaml has file: gates/
|
|
19
|
-
- [AC3]
|
|
20
|
-
- [AC3] File
|
|
18
|
+
- [AC3] Green phase in tdd.yaml has file: gates/dev-exit and type: dev_exit
|
|
19
|
+
- [AC3] All phases have gate.file fields (full migration complete)
|
|
20
|
+
- [AC3] File paths are relative (not absolute)
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
from __future__ import annotations
|
|
@@ -202,21 +202,19 @@ class TestResolveGateFileOnly:
|
|
|
202
202
|
)
|
|
203
203
|
assert result["gate_type"] is None
|
|
204
204
|
|
|
205
|
-
def
|
|
205
|
+
def test_status_ready_when_no_type_and_file_only(
|
|
206
206
|
self, project: Path
|
|
207
207
|
) -> None:
|
|
208
|
-
"""AC1: gate with file-only and no type → status
|
|
208
|
+
"""AC1: gate with file-only and no type → status is 'ready'.
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
So file-only gates (no type) currently get 'skip' status.
|
|
214
|
-
This is expected during migration — consumers check gate_file separately.
|
|
210
|
+
File-only gates (no type) are resolvable — the gate runner uses
|
|
211
|
+
gate_file to locate and execute the gate. Status is 'ready' when
|
|
212
|
+
an assessment exists.
|
|
215
213
|
"""
|
|
216
214
|
result = resolve_gate(
|
|
217
215
|
"106-3", "file-only", "green", project_root=project
|
|
218
216
|
)
|
|
219
|
-
assert result["status"] == "
|
|
217
|
+
assert result["status"] == "ready"
|
|
220
218
|
|
|
221
219
|
|
|
222
220
|
# ===========================================================================
|
|
@@ -315,11 +313,10 @@ class TestResolveGateBackwardCompat:
|
|
|
315
313
|
|
|
316
314
|
|
|
317
315
|
class TestTddWorkflowMigration:
|
|
318
|
-
"""AC3: TDD workflow
|
|
316
|
+
"""AC3: TDD workflow phases have gate.file fields after full migration.
|
|
319
317
|
|
|
320
318
|
These tests read the ACTUAL tdd.yaml from the project to verify
|
|
321
|
-
the migration has been applied.
|
|
322
|
-
updates tdd.yaml.
|
|
319
|
+
the migration has been applied.
|
|
323
320
|
"""
|
|
324
321
|
|
|
325
322
|
@pytest.fixture
|
|
@@ -342,54 +339,55 @@ class TestTddWorkflowMigration:
|
|
|
342
339
|
raise ValueError(f"Phase '{name}' not found")
|
|
343
340
|
|
|
344
341
|
def test_green_phase_has_gate_file(self, tdd_yaml: dict) -> None:
|
|
345
|
-
"""AC3: Green phase should have gate.file = 'gates/
|
|
342
|
+
"""AC3: Green phase should have gate.file = 'gates/dev-exit'."""
|
|
346
343
|
green = self._get_phase(tdd_yaml, "green")
|
|
347
344
|
gate = green.get("gate", {})
|
|
348
|
-
assert gate.get("file") == "gates/
|
|
349
|
-
f"Expected gate.file='gates/
|
|
345
|
+
assert gate.get("file") == "gates/dev-exit", (
|
|
346
|
+
f"Expected gate.file='gates/dev-exit', got gate={gate}"
|
|
350
347
|
)
|
|
351
348
|
|
|
352
|
-
def
|
|
353
|
-
"""AC3: Green phase should
|
|
349
|
+
def test_green_phase_has_dev_exit_type(self, tdd_yaml: dict) -> None:
|
|
350
|
+
"""AC3: Green phase should have gate.type = 'dev_exit'."""
|
|
354
351
|
green = self._get_phase(tdd_yaml, "green")
|
|
355
352
|
gate = green.get("gate", {})
|
|
356
|
-
assert gate.get("type") == "
|
|
357
|
-
f"Expected gate.type='
|
|
353
|
+
assert gate.get("type") == "dev_exit", (
|
|
354
|
+
f"Expected gate.type='dev_exit', got gate={gate}"
|
|
358
355
|
)
|
|
359
356
|
|
|
360
357
|
def test_green_phase_file_is_relative(self, tdd_yaml: dict) -> None:
|
|
361
|
-
"""AC3: File path should be relative (gates/
|
|
358
|
+
"""AC3: File path should be relative (gates/dev-exit), not absolute."""
|
|
362
359
|
green = self._get_phase(tdd_yaml, "green")
|
|
363
360
|
gate = green.get("gate", {})
|
|
364
361
|
file_path = gate.get("file", "")
|
|
365
362
|
assert not file_path.startswith("/"), (
|
|
366
363
|
f"gate.file should be relative, got: {file_path}"
|
|
367
364
|
)
|
|
368
|
-
assert file_path == "gates/
|
|
369
|
-
f"Expected 'gates/
|
|
365
|
+
assert file_path == "gates/dev-exit", (
|
|
366
|
+
f"Expected 'gates/dev-exit', got: {file_path}"
|
|
370
367
|
)
|
|
371
368
|
|
|
372
|
-
def
|
|
373
|
-
"""AC3: Red phase should
|
|
369
|
+
def test_red_phase_has_gate_file(self, tdd_yaml: dict) -> None:
|
|
370
|
+
"""AC3: Red phase should have gate.file = 'gates/tests-fail'."""
|
|
374
371
|
red = self._get_phase(tdd_yaml, "red")
|
|
375
372
|
gate = red.get("gate", {})
|
|
376
|
-
assert "file"
|
|
377
|
-
f"
|
|
373
|
+
assert gate.get("file") == "gates/tests-fail", (
|
|
374
|
+
f"Expected gate.file='gates/tests-fail', got gate={gate}"
|
|
378
375
|
)
|
|
379
376
|
|
|
380
|
-
def
|
|
381
|
-
"""AC3: Review phase should
|
|
377
|
+
def test_review_phase_has_gate_file(self, tdd_yaml: dict) -> None:
|
|
378
|
+
"""AC3: Review phase should have gate.file = 'gates/approval'."""
|
|
382
379
|
review = self._get_phase(tdd_yaml, "review")
|
|
383
380
|
gate = review.get("gate", {})
|
|
384
|
-
assert "file"
|
|
385
|
-
f"
|
|
381
|
+
assert gate.get("file") == "gates/approval", (
|
|
382
|
+
f"Expected gate.file='gates/approval', got gate={gate}"
|
|
386
383
|
)
|
|
387
384
|
|
|
388
|
-
def
|
|
389
|
-
"""AC3: Setup phase should
|
|
385
|
+
def test_setup_phase_has_gate(self, tdd_yaml: dict) -> None:
|
|
386
|
+
"""AC3: Setup phase should have gate.file = 'gates/sm-setup-exit'."""
|
|
390
387
|
setup = self._get_phase(tdd_yaml, "setup")
|
|
391
|
-
|
|
392
|
-
|
|
388
|
+
gate = setup.get("gate", {})
|
|
389
|
+
assert gate.get("file") == "gates/sm-setup-exit", (
|
|
390
|
+
f"Expected gate.file='gates/sm-setup-exit', got gate={gate}"
|
|
393
391
|
)
|
|
394
392
|
|
|
395
393
|
def test_finish_phase_unchanged(self, tdd_yaml: dict) -> None:
|
|
@@ -435,30 +433,30 @@ class TestResolveGateWithRealTddYaml:
|
|
|
435
433
|
def test_green_phase_returns_gate_file(
|
|
436
434
|
self, real_project: Path
|
|
437
435
|
) -> None:
|
|
438
|
-
"""AC3: resolve_gate for tdd/green should return gate_file='gates/
|
|
436
|
+
"""AC3: resolve_gate for tdd/green should return gate_file='gates/dev-exit'."""
|
|
439
437
|
result = resolve_gate(
|
|
440
438
|
"106-3", "tdd", "green", project_root=real_project
|
|
441
439
|
)
|
|
442
|
-
assert result["gate_file"] == "gates/
|
|
443
|
-
f"Expected gate_file='gates/
|
|
440
|
+
assert result["gate_file"] == "gates/dev-exit", (
|
|
441
|
+
f"Expected gate_file='gates/dev-exit', got: {result}"
|
|
444
442
|
)
|
|
445
443
|
|
|
446
|
-
def
|
|
444
|
+
def test_green_phase_returns_dev_exit_type(
|
|
447
445
|
self, real_project: Path
|
|
448
446
|
) -> None:
|
|
449
|
-
"""AC3: resolve_gate for tdd/green should
|
|
447
|
+
"""AC3: resolve_gate for tdd/green should return gate_type='dev_exit'."""
|
|
450
448
|
result = resolve_gate(
|
|
451
449
|
"106-3", "tdd", "green", project_root=real_project
|
|
452
450
|
)
|
|
453
|
-
assert result["gate_type"] == "
|
|
454
|
-
f"Expected gate_type='
|
|
451
|
+
assert result["gate_type"] == "dev_exit", (
|
|
452
|
+
f"Expected gate_type='dev_exit', got: {result}"
|
|
455
453
|
)
|
|
456
454
|
|
|
457
|
-
def
|
|
455
|
+
def test_red_phase_gate_file_is_tests_fail(
|
|
458
456
|
self, real_project: Path
|
|
459
457
|
) -> None:
|
|
460
|
-
"""AC3: resolve_gate for tdd/red should have gate_file=
|
|
458
|
+
"""AC3: resolve_gate for tdd/red should have gate_file='gates/tests-fail'."""
|
|
461
459
|
result = resolve_gate(
|
|
462
460
|
"106-3", "tdd", "red", project_root=real_project
|
|
463
461
|
)
|
|
464
|
-
assert result["gate_file"]
|
|
462
|
+
assert result["gate_file"] == "gates/tests-fail"
|
|
@@ -17,11 +17,7 @@ workflow list team indicator are implemented.
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
from unittest.mock import patch
|
|
22
|
-
|
|
23
20
|
import pytest
|
|
24
|
-
import yaml
|
|
25
21
|
from click.testing import CliRunner
|
|
26
22
|
|
|
27
23
|
from pennyfarthing_scripts.cli import cli
|
|
Binary file
|
|
Binary file
|