forgexa-cli 1.10.7__tar.gz → 1.11.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.
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/PKG-INFO +1 -1
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/forgexa_cli/daemon.py +120 -1
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/pyproject.toml +1 -1
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/README.md +0 -0
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.10.7 → forgexa_cli-1.11.0}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.11.0"
|
|
@@ -404,7 +404,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
404
404
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
405
405
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
406
406
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
407
|
-
DAEMON_VERSION = "1.
|
|
407
|
+
DAEMON_VERSION = "1.11.0"
|
|
408
408
|
|
|
409
409
|
|
|
410
410
|
def _detect_client_type() -> str:
|
|
@@ -2248,6 +2248,17 @@ class ProcessManager:
|
|
|
2248
2248
|
)
|
|
2249
2249
|
if not has_token_usage and ProcessManager._should_scan_short_success_stdout(stdout, signals):
|
|
2250
2250
|
error_channels = "\n".join(filter(None, [error_channels, stdout.strip()]))
|
|
2251
|
+
# Always include structured error messages parsed from JSONL stream events
|
|
2252
|
+
# (result events with is_error=True, error events, turn.failed payloads).
|
|
2253
|
+
# These are safe to scan unconditionally because they are structured — NOT
|
|
2254
|
+
# free-form agent work text. This catches mid-session model overload errors
|
|
2255
|
+
# (e.g. 529 overloaded_error) that appear as structured events AFTER some
|
|
2256
|
+
# assistant turns have already been emitted. Without this, such errors are
|
|
2257
|
+
# invisible to the stdout-scan guard (has_assistant_events=True → no scan)
|
|
2258
|
+
# yet still signal that the underlying model call failed.
|
|
2259
|
+
structured_errors = signals.get("error_messages", [])
|
|
2260
|
+
if structured_errors:
|
|
2261
|
+
error_channels = "\n".join(filter(None, [error_channels, "\n".join(structured_errors)]))
|
|
2251
2262
|
return error_channels
|
|
2252
2263
|
|
|
2253
2264
|
@staticmethod
|
|
@@ -5793,6 +5804,106 @@ class RuntimeDaemon:
|
|
|
5793
5804
|
except Exception as e:
|
|
5794
5805
|
logger.warning("Failed to read design artifact: %s", e)
|
|
5795
5806
|
|
|
5807
|
+
async def _remove_root_scratch_files(
|
|
5808
|
+
self, workspace_path: Path, task: TaskInfo, git_status_output: str
|
|
5809
|
+
) -> None:
|
|
5810
|
+
"""Remove unauthorized scratch/intermediate files created in the repo root.
|
|
5811
|
+
|
|
5812
|
+
Agents sometimes create working notes (findings.md, progress.md,
|
|
5813
|
+
task_plan.md, notes.md, etc.) directly in the repository root instead of
|
|
5814
|
+
inside the designated docs/requirements/<key>/ output directory. These
|
|
5815
|
+
files:
|
|
5816
|
+
1. Pollute the root directory with unstructured content.
|
|
5817
|
+
2. May survive into subsequent agent runs and cause cross-requirement
|
|
5818
|
+
context contamination.
|
|
5819
|
+
3. Can cause confusion when multiple requirements share a worktree.
|
|
5820
|
+
|
|
5821
|
+
This method removes any NEW (untracked or staged-as-add) markdown file
|
|
5822
|
+
found at the repository root that is not a recognised project-level doc,
|
|
5823
|
+
and logs a warning so the prompt can be improved.
|
|
5824
|
+
|
|
5825
|
+
NOTE: This only catches files that the agent has NOT yet committed.
|
|
5826
|
+
Files the agent self-committed (e.g. via `git add -A && git commit`)
|
|
5827
|
+
are prevented by the prompt-level prohibitions in CRITICAL REQUIREMENT
|
|
5828
|
+
and the type-specific IMPORTANT constraints.
|
|
5829
|
+
"""
|
|
5830
|
+
# Well-known root-level markdown files that must never be removed
|
|
5831
|
+
_PROTECTED = frozenset({
|
|
5832
|
+
"README.md", "readme.md",
|
|
5833
|
+
"CONTRIBUTING.md", "contributing.md",
|
|
5834
|
+
"CHANGELOG.md", "changelog.md",
|
|
5835
|
+
"LICENSE.md", "license.md",
|
|
5836
|
+
"SECURITY.md", "security.md",
|
|
5837
|
+
"CODE_OF_CONDUCT.md",
|
|
5838
|
+
"CONVENTIONS.md",
|
|
5839
|
+
"WORKFLOW.md",
|
|
5840
|
+
})
|
|
5841
|
+
|
|
5842
|
+
removed: list[str] = []
|
|
5843
|
+
for raw_line in git_status_output.splitlines():
|
|
5844
|
+
if len(raw_line) < 4:
|
|
5845
|
+
continue
|
|
5846
|
+
index_status = raw_line[0]
|
|
5847
|
+
worktree_status = raw_line[1]
|
|
5848
|
+
file_path = raw_line[3:].strip().strip('"')
|
|
5849
|
+
|
|
5850
|
+
# Only new files: untracked (??) or staged as addition (A)
|
|
5851
|
+
is_new_untracked = (index_status == "?" and worktree_status == "?")
|
|
5852
|
+
is_staged_new = (index_status == "A")
|
|
5853
|
+
if not (is_new_untracked or is_staged_new):
|
|
5854
|
+
continue
|
|
5855
|
+
|
|
5856
|
+
# Only root-level files (no path separator)
|
|
5857
|
+
if "/" in file_path or "\\" in file_path:
|
|
5858
|
+
continue
|
|
5859
|
+
|
|
5860
|
+
# Only markdown files
|
|
5861
|
+
if not file_path.lower().endswith(".md"):
|
|
5862
|
+
continue
|
|
5863
|
+
|
|
5864
|
+
# Skip protected project-level docs
|
|
5865
|
+
if file_path in _PROTECTED:
|
|
5866
|
+
continue
|
|
5867
|
+
|
|
5868
|
+
# Unstage if it was already staged
|
|
5869
|
+
if is_staged_new:
|
|
5870
|
+
try:
|
|
5871
|
+
proc = await asyncio.create_subprocess_exec(
|
|
5872
|
+
"git", "restore", "--staged", file_path,
|
|
5873
|
+
cwd=str(workspace_path),
|
|
5874
|
+
stdout=asyncio.subprocess.PIPE,
|
|
5875
|
+
stderr=asyncio.subprocess.PIPE,
|
|
5876
|
+
)
|
|
5877
|
+
await proc.communicate()
|
|
5878
|
+
except Exception as exc:
|
|
5879
|
+
logger.warning(
|
|
5880
|
+
"Failed to unstage root scratch file '%s': %s", file_path, exc
|
|
5881
|
+
)
|
|
5882
|
+
|
|
5883
|
+
# Delete the file
|
|
5884
|
+
target = workspace_path / file_path
|
|
5885
|
+
if target.exists():
|
|
5886
|
+
try:
|
|
5887
|
+
target.unlink()
|
|
5888
|
+
removed.append(file_path)
|
|
5889
|
+
except Exception as exc:
|
|
5890
|
+
logger.warning(
|
|
5891
|
+
"Failed to remove root scratch file '%s': %s", file_path, exc
|
|
5892
|
+
)
|
|
5893
|
+
|
|
5894
|
+
if removed:
|
|
5895
|
+
logger.warning(
|
|
5896
|
+
"Task %s (req: %s, node_type: %s): removed %d unauthorized root-level "
|
|
5897
|
+
"markdown file(s) before commit: [%s]. "
|
|
5898
|
+
"All documentation must be placed inside docs/requirements/<key>/. "
|
|
5899
|
+
"Check agent prompt for STRICT FILE PLACEMENT RULES compliance.",
|
|
5900
|
+
task.task_id,
|
|
5901
|
+
task.requirement_key or "?",
|
|
5902
|
+
task.node_type,
|
|
5903
|
+
len(removed),
|
|
5904
|
+
", ".join(removed),
|
|
5905
|
+
)
|
|
5906
|
+
|
|
5796
5907
|
async def _auto_commit(self, workspace_path: Path, task: TaskInfo) -> dict:
|
|
5797
5908
|
"""Auto-commit and push agent changes.
|
|
5798
5909
|
|
|
@@ -5824,6 +5935,14 @@ class RuntimeDaemon:
|
|
|
5824
5935
|
stdout, _ = await proc.communicate()
|
|
5825
5936
|
|
|
5826
5937
|
if stdout.strip():
|
|
5938
|
+
# Safety net: remove any scratch/intermediate .md files the agent
|
|
5939
|
+
# created directly in the repository root before staging everything.
|
|
5940
|
+
# Agents sometimes write working notes (findings.md, progress.md,
|
|
5941
|
+
# task_plan.md, etc.) to cwd instead of the designated output dir.
|
|
5942
|
+
# These files pollute the root and cause cross-requirement context
|
|
5943
|
+
# contamination on subsequent agent runs.
|
|
5944
|
+
await self._remove_root_scratch_files(workspace_path, task, stdout.decode())
|
|
5945
|
+
|
|
5827
5946
|
# Stage all changes
|
|
5828
5947
|
proc = await asyncio.create_subprocess_exec(
|
|
5829
5948
|
"git", "add", "-A",
|
|
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
|