norn-cli 0.7.2__tar.gz → 0.7.3__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.
- {norn_cli-0.7.2 → norn_cli-0.7.3}/PKG-INFO +2 -2
- {norn_cli-0.7.2 → norn_cli-0.7.3}/README.md +1 -1
- {norn_cli-0.7.2 → norn_cli-0.7.3}/pyproject.toml +1 -1
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/__init__.py +1 -1
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/pipeline.py +62 -45
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/providers/grok.py +14 -1
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norn_cli.egg-info/PKG-INFO +2 -2
- {norn_cli-0.7.2 → norn_cli-0.7.3}/tests/test_cli.py +1 -1
- {norn_cli-0.7.2 → norn_cli-0.7.3}/tests/test_pipeline.py +46 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/tests/test_providers.py +6 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/LICENSE +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/setup.cfg +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/cli.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/config.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/doctor.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/e2e_evidence.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/gate.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/models.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/project_gates.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/providers/__init__.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/providers/base.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/providers/claude.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/providers/codex.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/providers/command.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/providers/registry.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/providers/router.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/reporting.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/resources/default.yaml +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/resources/skills/norn/SKILL.md +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/resources/skills/norn/agents/openai.yaml +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/resources/skills/norn/references/cli.md +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/run_store.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/skill_install.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norf_harness/worktree.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norn_cli.egg-info/SOURCES.txt +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norn_cli.egg-info/dependency_links.txt +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norn_cli.egg-info/entry_points.txt +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norn_cli.egg-info/requires.txt +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/src/norn_cli.egg-info/top_level.txt +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/tests/test_config.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/tests/test_e2e_evidence.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/tests/test_gate.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/tests/test_project_gates.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/tests/test_run_store.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/tests/test_skill_install.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.7.3}/tests/test_worktree.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: norn-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
4
4
|
Summary: Norn: a provider-neutral multi-agent development harness
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Repository, https://github.com/uruca-kk/norn
|
|
@@ -135,7 +135,7 @@ ruff format --check .
|
|
|
135
135
|
ruff check .
|
|
136
136
|
python3 -m unittest discover -v
|
|
137
137
|
python3 -m build
|
|
138
|
-
python3 scripts/package_smoke.py dist/norn_cli-0.7.
|
|
138
|
+
python3 scripts/package_smoke.py dist/norn_cli-0.7.3-py3-none-any.whl
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
CI runs the quality suite on Python 3.12, 3.13, and 3.14, then installs the
|
|
@@ -110,7 +110,7 @@ ruff format --check .
|
|
|
110
110
|
ruff check .
|
|
111
111
|
python3 -m unittest discover -v
|
|
112
112
|
python3 -m build
|
|
113
|
-
python3 scripts/package_smoke.py dist/norn_cli-0.7.
|
|
113
|
+
python3 scripts/package_smoke.py dist/norn_cli-0.7.3-py3-none-any.whl
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
CI runs the quality suite on Python 3.12, 3.13, and 3.14, then installs the
|
|
@@ -669,53 +669,70 @@ class _PipelineRunner:
|
|
|
669
669
|
ui_required: bool,
|
|
670
670
|
) -> list[tuple[str, dict[str, Any]]]:
|
|
671
671
|
self._phase("testing")
|
|
672
|
-
results
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
),
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
672
|
+
results = await asyncio.gather(
|
|
673
|
+
*(
|
|
674
|
+
self._run_tester(role, plan, implementation, local, ui_required)
|
|
675
|
+
for role in _TESTER_ROLES
|
|
676
|
+
),
|
|
677
|
+
return_exceptions=True,
|
|
678
|
+
)
|
|
679
|
+
for result in results:
|
|
680
|
+
if isinstance(result, BaseException):
|
|
681
|
+
raise result
|
|
682
|
+
return results
|
|
683
|
+
|
|
684
|
+
async def _run_tester(
|
|
685
|
+
self,
|
|
686
|
+
role: str,
|
|
687
|
+
plan: dict[str, Any],
|
|
688
|
+
implementation: dict[str, Any],
|
|
689
|
+
local: ProjectGateResult,
|
|
690
|
+
ui_required: bool,
|
|
691
|
+
) -> tuple[str, dict[str, Any]]:
|
|
692
|
+
evidence_dir = None
|
|
693
|
+
if ui_required:
|
|
694
|
+
try:
|
|
695
|
+
evidence_dir = prepare_evidence_directory(
|
|
696
|
+
self.config.project_root,
|
|
697
|
+
self.manifest.run_id,
|
|
698
|
+
self.manifest.rounds.implementation,
|
|
699
|
+
role,
|
|
700
|
+
)
|
|
701
|
+
except EvidenceError as exc:
|
|
702
|
+
raise PipelineStop(
|
|
703
|
+
StopReason.E2E_UNAVAILABLE,
|
|
704
|
+
ExitCode.TEST_INFRA_FAILURE,
|
|
705
|
+
f"E2E証跡ディレクトリを安全に準備できません: {exc}",
|
|
706
|
+
("worktree内の.norf pathとsymlinkを確認する",),
|
|
707
|
+
) from exc
|
|
708
|
+
payload = await self._invoke(
|
|
709
|
+
role,
|
|
710
|
+
{
|
|
711
|
+
"request": self.manifest.request,
|
|
712
|
+
"plan": plan,
|
|
713
|
+
"implementation": implementation,
|
|
714
|
+
"local_gate_results": [item.to_dict() for item in local.results],
|
|
715
|
+
"e2e_required": ui_required,
|
|
716
|
+
"e2e_evidence_dir": str(evidence_dir) if evidence_dir else "",
|
|
717
|
+
"e2e_instruction": (
|
|
718
|
+
"UI変更があるため、このtester自身がE2Eを実行すること。実行したargv、"
|
|
719
|
+
"exit code、具体的な結果を返し、ログ・スクリーンショット・trace等の"
|
|
720
|
+
"少なくとも1つの非空ファイルを指定されたe2e_evidence_dir内に作成し、"
|
|
721
|
+
"artifactsへそのpathを列挙すること。他testerの証跡を再利用しないこと。"
|
|
712
722
|
if ui_required
|
|
713
|
-
else
|
|
723
|
+
else "UI変更がない場合はnot_requiredを返すこと。"
|
|
714
724
|
),
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
725
|
+
},
|
|
726
|
+
_TESTER_SCHEMA,
|
|
727
|
+
SandboxPolicy.TEST,
|
|
728
|
+
timeout=(
|
|
729
|
+
self.config.limits.e2e_timeout_seconds
|
|
730
|
+
if ui_required
|
|
731
|
+
else self.config.limits.role_timeout_seconds
|
|
732
|
+
),
|
|
733
|
+
)
|
|
734
|
+
payload["_e2e_evidence_dir"] = str(evidence_dir) if evidence_dir else ""
|
|
735
|
+
return role, payload
|
|
719
736
|
|
|
720
737
|
async def _triage(
|
|
721
738
|
self,
|
|
@@ -23,6 +23,15 @@ from .base import (
|
|
|
23
23
|
run_process,
|
|
24
24
|
)
|
|
25
25
|
|
|
26
|
+
_TOOL_IDS = {
|
|
27
|
+
"Read": "read_file",
|
|
28
|
+
"Glob": "list_dir",
|
|
29
|
+
"Grep": "grep",
|
|
30
|
+
"Edit": "search_replace",
|
|
31
|
+
"Write": "write",
|
|
32
|
+
"Bash": "run_terminal_cmd",
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
|
|
27
36
|
class GrokAdapter(ProviderAdapter):
|
|
28
37
|
name = "grok"
|
|
@@ -61,7 +70,7 @@ class GrokAdapter(ProviderAdapter):
|
|
|
61
70
|
if request.schema is not None:
|
|
62
71
|
args.extend(["--json-schema", json.dumps(request.schema, ensure_ascii=False)])
|
|
63
72
|
if request.tools:
|
|
64
|
-
args.extend(["--tools", ",".join(request.tools)])
|
|
73
|
+
args.extend(["--tools", ",".join(_grok_tools(request.tools))])
|
|
65
74
|
process = await run_process(
|
|
66
75
|
args,
|
|
67
76
|
stdin="",
|
|
@@ -113,6 +122,10 @@ def _sandbox(policy: SandboxPolicy) -> str:
|
|
|
113
122
|
return "workspace" if policy in writable else "read-only"
|
|
114
123
|
|
|
115
124
|
|
|
125
|
+
def _grok_tools(tools: tuple[str, ...]) -> tuple[str, ...]:
|
|
126
|
+
return tuple(dict.fromkeys(_TOOL_IDS.get(tool, tool) for tool in tools))
|
|
127
|
+
|
|
128
|
+
|
|
116
129
|
def _text(envelope: dict[str, Any]) -> str:
|
|
117
130
|
for key in ("result", "text", "output_text", "content"):
|
|
118
131
|
value = envelope.get(key)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: norn-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
4
4
|
Summary: Norn: a provider-neutral multi-agent development harness
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Repository, https://github.com/uruca-kk/norn
|
|
@@ -135,7 +135,7 @@ ruff format --check .
|
|
|
135
135
|
ruff check .
|
|
136
136
|
python3 -m unittest discover -v
|
|
137
137
|
python3 -m build
|
|
138
|
-
python3 scripts/package_smoke.py dist/norn_cli-0.7.
|
|
138
|
+
python3 scripts/package_smoke.py dist/norn_cli-0.7.3-py3-none-any.whl
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
CI runs the quality suite on Python 3.12, 3.13, and 3.14, then installs the
|
|
@@ -23,7 +23,7 @@ class CliTests(unittest.TestCase):
|
|
|
23
23
|
main(["--version"])
|
|
24
24
|
|
|
25
25
|
self.assertEqual(raised.exception.code, 0)
|
|
26
|
-
self.assertEqual(output.getvalue().strip(), "norn 0.7.
|
|
26
|
+
self.assertEqual(output.getvalue().strip(), "norn 0.7.3")
|
|
27
27
|
|
|
28
28
|
def test_init_and_validate(self) -> None:
|
|
29
29
|
with tempfile.TemporaryDirectory() as tmp:
|
|
@@ -128,6 +128,24 @@ class ScriptedRouter:
|
|
|
128
128
|
return RoutedResult(result, (attempt,), False)
|
|
129
129
|
|
|
130
130
|
|
|
131
|
+
class ConcurrentTesterRouter(ScriptedRouter):
|
|
132
|
+
def __init__(self, **responses: list[dict[str, object]]):
|
|
133
|
+
super().__init__(**responses)
|
|
134
|
+
self.active_testers = 0
|
|
135
|
+
self.max_active_testers = 0
|
|
136
|
+
|
|
137
|
+
async def invoke_profile(self, profile_name, request):
|
|
138
|
+
if not request.role.endswith("tester"):
|
|
139
|
+
return await super().invoke_profile(profile_name, request)
|
|
140
|
+
self.active_testers += 1
|
|
141
|
+
self.max_active_testers = max(self.max_active_testers, self.active_testers)
|
|
142
|
+
try:
|
|
143
|
+
await asyncio.sleep(0.01)
|
|
144
|
+
return await super().invoke_profile(profile_name, request)
|
|
145
|
+
finally:
|
|
146
|
+
self.active_testers -= 1
|
|
147
|
+
|
|
148
|
+
|
|
131
149
|
class PipelineTests(unittest.TestCase):
|
|
132
150
|
def config(self, root: Path, **overrides):
|
|
133
151
|
values = {"gates.enabled": False, "git.worktree": False, **overrides}
|
|
@@ -177,6 +195,34 @@ class PipelineTests(unittest.TestCase):
|
|
|
177
195
|
self.assertIn('argv=["node", "e2e.mjs"]', report)
|
|
178
196
|
self.assertIn("artifacts=", report)
|
|
179
197
|
|
|
198
|
+
def test_run_executes_tester_roles_concurrently(self) -> None:
|
|
199
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
200
|
+
root = Path(tmp)
|
|
201
|
+
clean = [tester()]
|
|
202
|
+
router = ConcurrentTesterRouter(
|
|
203
|
+
orchestrator=[orchestration()],
|
|
204
|
+
planner=[plan()],
|
|
205
|
+
plan_reviewer=[review()],
|
|
206
|
+
implementer=[implementation()],
|
|
207
|
+
spec_tester=list(clean),
|
|
208
|
+
clean_tester=list(clean),
|
|
209
|
+
security_tester=list(clean),
|
|
210
|
+
chaos_tester=list(clean),
|
|
211
|
+
final_gate=[final()],
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
outcome = asyncio.run(
|
|
215
|
+
execute_pipeline(self.config(root), "parallel testers", RunMode.RUN, router=router)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
self.assertEqual(outcome.exit_code, 0)
|
|
219
|
+
self.assertEqual(router.max_active_testers, 4)
|
|
220
|
+
tester_calls = [call.role for call in router.calls if call.role.endswith("tester")]
|
|
221
|
+
self.assertEqual(
|
|
222
|
+
tester_calls,
|
|
223
|
+
["spec_tester", "clean_tester", "security_tester", "chaos_tester"],
|
|
224
|
+
)
|
|
225
|
+
|
|
180
226
|
def test_quality_failure_is_triaged_then_reimplemented(self) -> None:
|
|
181
227
|
with tempfile.TemporaryDirectory() as tmp:
|
|
182
228
|
root = Path(tmp)
|
|
@@ -182,6 +182,7 @@ class AdapterIntegrationTests(unittest.IsolatedAsyncioTestCase):
|
|
|
182
182
|
"implement",
|
|
183
183
|
root,
|
|
184
184
|
schema=_schema(),
|
|
185
|
+
tools=("Read", "Glob", "Grep", "Edit", "Write", "Bash"),
|
|
185
186
|
sandbox=SandboxPolicy.WORKSPACE_WRITE,
|
|
186
187
|
),
|
|
187
188
|
)
|
|
@@ -191,6 +192,11 @@ class AdapterIntegrationTests(unittest.IsolatedAsyncioTestCase):
|
|
|
191
192
|
self.assertNotIn("implement", result.command_summary)
|
|
192
193
|
sandbox_index = result.command_summary.index("--sandbox")
|
|
193
194
|
self.assertEqual(result.command_summary[sandbox_index + 1], "workspace")
|
|
195
|
+
tools_index = result.command_summary.index("--tools")
|
|
196
|
+
self.assertEqual(
|
|
197
|
+
result.command_summary[tools_index + 1],
|
|
198
|
+
"read_file,list_dir,grep,search_replace,write,run_terminal_cmd",
|
|
199
|
+
)
|
|
194
200
|
|
|
195
201
|
async def test_generic_adapter_passes_prompt_over_stdin_without_shell(self) -> None:
|
|
196
202
|
with tempfile.TemporaryDirectory() as tmp:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|