mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-08-28 05:15:00 +00:00
Add local machine context snapshots
This commit is contained in:
parent
2e8e378a8c
commit
c26a2cf23e
14 changed files with 622 additions and 6 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -30,6 +30,7 @@ build/
|
|||
|
||||
# MCP files
|
||||
openspace/config/config_mcp.json
|
||||
.mcp.json
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
|
|
@ -46,6 +47,7 @@ showcase/.openspace/*
|
|||
# GDPVal benchmark cache
|
||||
gdpval_bench/.openspace/*
|
||||
!gdpval_bench/.openspace/*.db
|
||||
gdpval_bench/results/
|
||||
|
||||
# Embedding cache
|
||||
embedding_cache/
|
||||
|
|
@ -73,3 +75,6 @@ openspace/skills/*
|
|||
node_modules/
|
||||
# Frontend local dependency link
|
||||
frontend/node_modules
|
||||
|
||||
# Local scratch
|
||||
tmp/
|
||||
|
|
|
|||
28
context/local-machine/admin-macos/README.md
Normal file
28
context/local-machine/admin-macos/README.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
## Admin macOS Local Context
|
||||
|
||||
This directory keeps machine-specific snapshots that are useful for future
|
||||
deployment, migration, and debugging on other machines, while avoiding noise in
|
||||
the repo root.
|
||||
|
||||
Included here:
|
||||
|
||||
- `mcp/repo-local.mcp.json`
|
||||
- snapshot of the repo-local MCP wiring that was used during local debugging
|
||||
- `gdpval_bench/...`
|
||||
- selected benchmark result snapshots that were useful during local call-rate
|
||||
and provider-path investigation
|
||||
|
||||
Intentional choices:
|
||||
|
||||
- absolute local paths are preserved because they are part of the context
|
||||
- localhost API base values are preserved because they document the local stack
|
||||
- secrets are not preserved
|
||||
- any benchmark config copied here has API keys redacted
|
||||
|
||||
Intentionally omitted from this snapshot:
|
||||
|
||||
- SQLite/WAL benchmark databases
|
||||
- raw recording directories
|
||||
- ad hoc `tmp/` scratch files
|
||||
|
||||
Those source locations remain local-only and are ignored via `.gitignore`.
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"clawwork_root": "/tmp/openspace-bench-mp4o3U",
|
||||
"gdpval_path": null,
|
||||
"model": "gpt-5.4",
|
||||
"max_iterations": 20,
|
||||
"backend_scope": [
|
||||
"shell"
|
||||
],
|
||||
"use_clawwork_productivity": false,
|
||||
"run_name": "codex_callrate_smoke",
|
||||
"max_tasks": 3,
|
||||
"per_occupation": null,
|
||||
"sectors": null,
|
||||
"occupations": null,
|
||||
"task_ids": null,
|
||||
"record_call_details": true,
|
||||
"enable_evaluation": false,
|
||||
"concurrency": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"clawwork_root": "/tmp/openspace-bench-noref-JwyV4l",
|
||||
"gdpval_path": null,
|
||||
"model": "gpt-5.4",
|
||||
"max_iterations": 20,
|
||||
"backend_scope": [
|
||||
"shell"
|
||||
],
|
||||
"use_clawwork_productivity": false,
|
||||
"run_name": "codex_callrate_smoke_noref",
|
||||
"max_tasks": 3,
|
||||
"per_occupation": null,
|
||||
"sectors": null,
|
||||
"occupations": null,
|
||||
"task_ids": null,
|
||||
"record_call_details": true,
|
||||
"enable_evaluation": false,
|
||||
"concurrency": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{"task_id": "0112fc9b-c3b2-4084-8993-5a4abb1f54f1", "phase": "phase1", "occupation": "Nurse Practitioners", "sector": "Health Care and Social Assistance", "task_value_usd": 0.0, "status": "error", "tokens": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0, "llm_calls": 0, "cost_usd": 0.0, "wall_time_sec": 0.01, "agent_prompt_tokens": 0, "agent_completion_tokens": 0, "agent_total_tokens": 0, "agent_llm_calls": 0, "call_details": []}, "execution": {"iterations": 0, "tool_calls": 0, "time_sec": 0.01}, "skills": {"before": 2, "after": 2, "new_this_task": 0, "evolved": [], "used": []}, "evaluation": {"has_evaluation": false}, "timestamp": "2026-04-12T02:37:44.273256"}
|
||||
{"task_id": "02314fc6-a24e-42f4-a8cd-362cae0f0ec1", "phase": "phase1", "occupation": "General and Operations Managers", "sector": "Retail Trade", "task_value_usd": 0.0, "status": "error", "tokens": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0, "llm_calls": 0, "cost_usd": 0.0, "wall_time_sec": 0.0, "agent_prompt_tokens": 0, "agent_completion_tokens": 0, "agent_total_tokens": 0, "agent_llm_calls": 0, "call_details": []}, "execution": {"iterations": 0, "tool_calls": 0, "time_sec": 0.0}, "skills": {"before": 2, "after": 2, "new_this_task": 0, "evolved": [], "used": []}, "evaluation": {"has_evaluation": false}, "timestamp": "2026-04-12T02:37:44.278249"}
|
||||
{"task_id": "02aa1805-c658-4069-8a6a-02dec146063a", "phase": "phase1", "occupation": "Project Management Specialists", "sector": "Professional, Scientific, and Technical Services", "task_value_usd": 0.0, "status": "error", "tokens": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0, "llm_calls": 0, "cost_usd": 0.0, "wall_time_sec": 0.0, "agent_prompt_tokens": 0, "agent_completion_tokens": 0, "agent_total_tokens": 0, "agent_llm_calls": 0, "call_details": []}, "execution": {"iterations": 0, "tool_calls": 0, "time_sec": 0.0}, "skills": {"before": 2, "after": 2, "new_this_task": 0, "evolved": [], "used": []}, "evaluation": {"has_evaluation": false}, "timestamp": "2026-04-12T02:37:44.282846"}
|
||||
{"task_id": "0112fc9b-c3b2-4084-8993-5a4abb1f54f1", "phase": "phase1", "occupation": "Nurse Practitioners", "sector": "Health Care and Social Assistance", "task_value_usd": 0.0, "status": "error", "tokens": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0, "llm_calls": 0, "cost_usd": 0.0, "wall_time_sec": 0.0, "agent_prompt_tokens": 0, "agent_completion_tokens": 0, "agent_total_tokens": 0, "agent_llm_calls": 0, "call_details": []}, "execution": {"iterations": 0, "tool_calls": 0, "time_sec": 0.0}, "skills": {"before": 2, "after": 2, "new_this_task": 0, "evolved": [], "used": []}, "evaluation": {"has_evaluation": false}, "timestamp": "2026-04-12T02:38:19.523517"}
|
||||
{"task_id": "02314fc6-a24e-42f4-a8cd-362cae0f0ec1", "phase": "phase1", "occupation": "General and Operations Managers", "sector": "Retail Trade", "task_value_usd": 0.0, "status": "error", "tokens": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0, "llm_calls": 0, "cost_usd": 0.0, "wall_time_sec": 0.0, "agent_prompt_tokens": 0, "agent_completion_tokens": 0, "agent_total_tokens": 0, "agent_llm_calls": 0, "call_details": []}, "execution": {"iterations": 0, "tool_calls": 0, "time_sec": 0.0}, "skills": {"before": 2, "after": 2, "new_this_task": 0, "evolved": [], "used": []}, "evaluation": {"has_evaluation": false}, "timestamp": "2026-04-12T02:38:19.526964"}
|
||||
{"task_id": "02aa1805-c658-4069-8a6a-02dec146063a", "phase": "phase1", "occupation": "Project Management Specialists", "sector": "Professional, Scientific, and Technical Services", "task_value_usd": 0.0, "status": "error", "tokens": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0, "llm_calls": 0, "cost_usd": 0.0, "wall_time_sec": 0.0, "agent_prompt_tokens": 0, "agent_completion_tokens": 0, "agent_total_tokens": 0, "agent_llm_calls": 0, "call_details": []}, "execution": {"iterations": 0, "tool_calls": 0, "time_sec": 0.0}, "skills": {"before": 2, "after": 2, "new_this_task": 0, "evolved": [], "used": []}, "evaluation": {"has_evaluation": false}, "timestamp": "2026-04-12T02:38:19.529894"}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"clawwork_root": "/tmp/openspace-bench-noref-JwyV4l",
|
||||
"gdpval_path": null,
|
||||
"model": "gpt-5.4",
|
||||
"max_iterations": 20,
|
||||
"backend_scope": [
|
||||
"shell"
|
||||
],
|
||||
"use_clawwork_productivity": false,
|
||||
"run_name": "codex_callrate_smoke_noref_fix",
|
||||
"max_tasks": 2,
|
||||
"per_occupation": null,
|
||||
"sectors": null,
|
||||
"occupations": null,
|
||||
"task_ids": null,
|
||||
"record_call_details": true,
|
||||
"llm_kwargs": {
|
||||
"api_key": "REDACTED",
|
||||
"api_base": "http://127.0.0.1:8080/v1"
|
||||
},
|
||||
"enable_evaluation": false,
|
||||
"concurrency": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
{"task_id": "0112fc9b-c3b2-4084-8993-5a4abb1f54f1", "phase": "phase1", "occupation": "Nurse Practitioners", "sector": "Health Care and Social Assistance", "task_value_usd": 0.0, "status": "success", "tokens": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0, "llm_calls": 0, "cost_usd": 0.0, "wall_time_sec": 93.54, "agent_prompt_tokens": 0, "agent_completion_tokens": 0, "agent_total_tokens": 0, "agent_llm_calls": 0, "call_details": []}, "execution": {"iterations": 1, "tool_calls": 0, "time_sec": 93.54}, "skills": {"before": 3, "after": 4, "new_this_task": 1, "evolved": [{"name": "clinical-soap-note-from-narrative-enhanced", "origin": "derived", "path": "/Users/admin/PycharmProjects/openspace/openspace/skills/clinical-soap-note-from-narrative-enhanced/SKILL.md"}], "used": ["clinical-soap-note-from-narrative__v0_9ff620af"]}, "evaluation": {"has_evaluation": false}, "timestamp": "2026-04-12T02:50:48.493407"}
|
||||
{"task_id": "02314fc6-a24e-42f4-a8cd-362cae0f0ec1", "phase": "phase1", "occupation": "General and Operations Managers", "sector": "Retail Trade", "task_value_usd": 0.0, "status": "success", "tokens": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0, "llm_calls": 0, "cost_usd": 0.0, "wall_time_sec": 269.94, "agent_prompt_tokens": 0, "agent_completion_tokens": 0, "agent_total_tokens": 0, "agent_llm_calls": 0, "call_details": []}, "execution": {"iterations": 10, "tool_calls": 10, "time_sec": 269.94}, "skills": {"before": 4, "after": 6, "new_this_task": 2, "evolved": [{"name": "adaptive-document-output", "origin": "captured", "path": "/Users/admin/PycharmProjects/openspace/openspace/skills/adaptive-document-output/SKILL.md"}, {"name": "verify-build-artifacts", "origin": "captured", "path": "/Users/admin/PycharmProjects/openspace/openspace/skills/verify-build-artifacts/SKILL.md"}], "used": []}, "evaluation": {"has_evaluation": false}, "timestamp": "2026-04-12T02:55:18.445041"}
|
||||
File diff suppressed because one or more lines are too long
23
context/local-machine/admin-macos/mcp/repo-local.mcp.json
Normal file
23
context/local-machine/admin-macos/mcp/repo-local.mcp.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"mcpServers": {
|
||||
"openspace_evolution": {
|
||||
"type": "stdio",
|
||||
"command": "/Users/admin/PycharmProjects/openspace/.venv/bin/python",
|
||||
"args": [
|
||||
"-m",
|
||||
"openspace.evolution_mcp_server",
|
||||
"--transport",
|
||||
"stdio"
|
||||
],
|
||||
"env": {
|
||||
"OPENSPACE_WORKSPACE": "/Users/admin/PycharmProjects/openspace",
|
||||
"OPENSPACE_HOST_SKILL_DIRS": "/Users/admin/.claude/projects/openspace/skills,/Users/admin/.claude/skills",
|
||||
"OPENSPACE_MODEL": "gpt-5.4",
|
||||
"OPENSPACE_LLM_API_BASE": "http://127.0.0.1:8080/v1",
|
||||
"OPENSPACE_LLM_OPENAI_STREAM_COMPAT": "true",
|
||||
"OPENSPACE_ENABLE_RECORDING": "false",
|
||||
"OPENSPACE_BACKEND_SCOPE": "shell,system"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -388,6 +388,7 @@ def _make_config(cfg: Dict, phase: str, worker_id: int = 0):
|
|||
|
||||
return OpenSpaceConfig(
|
||||
llm_model=cfg["model"],
|
||||
llm_kwargs=cfg.get("llm_kwargs", {}),
|
||||
workspace_dir=str(rd / "workspace" / phase),
|
||||
recording_log_dir=rec_dir,
|
||||
recording_backends=cfg.get("backend_scope", ["shell", "web"]),
|
||||
|
|
@ -1164,6 +1165,16 @@ async def main(args: argparse.Namespace) -> None:
|
|||
if getattr(args, "use_clawwork_productivity", False):
|
||||
cfg["use_clawwork_productivity"] = True
|
||||
|
||||
# Match CLI / MCP entrypoints: resolve provider credentials once and pass
|
||||
# them into OpenSpaceConfig.llm_kwargs so benchmark runs work with custom
|
||||
# OPENSPACE_LLM_* endpoints too.
|
||||
from openspace.host_detection import build_llm_kwargs, load_runtime_env
|
||||
|
||||
load_runtime_env()
|
||||
resolved_model, llm_kwargs = build_llm_kwargs(cfg.get("model", ""))
|
||||
cfg["model"] = resolved_model
|
||||
cfg["llm_kwargs"] = llm_kwargs
|
||||
|
||||
# When using ClawWork productivity tools, ensure livebench is importable
|
||||
# before OpenSpace.initialize() (ShellSession loads productivity_tools which imports livebench)
|
||||
if cfg.get("use_clawwork_productivity"):
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ from .patch import (
|
|||
create_skill,
|
||||
fix_skill,
|
||||
derive_skill,
|
||||
parse_multi_file_full,
|
||||
SKILL_FILENAME,
|
||||
)
|
||||
from .skill_utils import (
|
||||
|
|
@ -71,6 +72,7 @@ EVOLUTION_FAILED = SkillEnginePrompts.EVOLUTION_FAILED
|
|||
|
||||
_SKILL_CONTENT_MAX_CHARS = 12_000 # Max chars of SKILL.md in evolution prompt
|
||||
_MAX_SKILL_NAME_LENGTH = 50 # Max chars for a skill name (directory name)
|
||||
_MULTI_FILE_FULL_HEADER_RE = re.compile(r"(?m)^\*\*\*\s*File:\s*")
|
||||
|
||||
|
||||
def _sanitize_skill_name(name: str) -> str:
|
||||
|
|
@ -95,6 +97,88 @@ def _sanitize_skill_name(name: str) -> str:
|
|||
truncated = truncated[:last_hyphen]
|
||||
return truncated.strip("-")
|
||||
|
||||
|
||||
def _extract_generated_frontmatter_field(content: str, field_name: str) -> Optional[str]:
|
||||
"""Extract frontmatter fields from single-file or multi-file FULL outputs."""
|
||||
direct = _extract_frontmatter_field(content, field_name)
|
||||
if direct:
|
||||
return direct
|
||||
|
||||
if _MULTI_FILE_FULL_HEADER_RE.search(content):
|
||||
try:
|
||||
skill_md = parse_multi_file_full(content).get(SKILL_FILENAME, "")
|
||||
except Exception:
|
||||
skill_md = ""
|
||||
if skill_md:
|
||||
nested = _extract_frontmatter_field(skill_md, field_name)
|
||||
if nested:
|
||||
return nested
|
||||
|
||||
# Fall back to the top of the generated text to tolerate minor format drift.
|
||||
head = "\n".join(content.splitlines()[:40])
|
||||
match = re.search(
|
||||
rf"(?im)^\s*{re.escape(field_name)}\s*:\s*(.+?)\s*$",
|
||||
head,
|
||||
)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
value = match.group(1).strip().strip("\"'")
|
||||
return value or None
|
||||
|
||||
|
||||
def _serialize_multi_file_full(files: Dict[str, str]) -> str:
|
||||
"""Serialize parsed FULL files back into the ``*** Begin Files`` format."""
|
||||
ordered_paths = [SKILL_FILENAME]
|
||||
ordered_paths.extend(path for path in sorted(files) if path != SKILL_FILENAME)
|
||||
|
||||
lines = ["*** Begin Files"]
|
||||
for path in ordered_paths:
|
||||
if path not in files:
|
||||
continue
|
||||
lines.append(f"*** File: {path}")
|
||||
body = files[path].rstrip("\n")
|
||||
if body:
|
||||
lines.append(body)
|
||||
lines.append("*** End Files")
|
||||
return "\n".join(lines).strip() + "\n"
|
||||
|
||||
|
||||
def _set_generated_frontmatter_field(content: str, field_name: str, value: str) -> str:
|
||||
"""Inject or update frontmatter fields in single-file or multi-file FULL outputs."""
|
||||
if _MULTI_FILE_FULL_HEADER_RE.search(content):
|
||||
files = parse_multi_file_full(content)
|
||||
files[SKILL_FILENAME] = _set_frontmatter_field(
|
||||
files.get(SKILL_FILENAME, ""),
|
||||
field_name,
|
||||
value,
|
||||
)
|
||||
return _serialize_multi_file_full(files)
|
||||
|
||||
return _set_frontmatter_field(content, field_name, value)
|
||||
|
||||
|
||||
def _fallback_captured_skill_name(
|
||||
direction: str,
|
||||
category: Optional[SkillCategory],
|
||||
) -> str:
|
||||
"""Generate a deterministic fallback skill name from the capture direction."""
|
||||
hint = (direction or "").strip()
|
||||
hint = re.split(r"[:.;\n]", hint, maxsplit=1)[0].strip()
|
||||
hint = re.sub(r"(?i)^capture\s+", "", hint)
|
||||
hint = re.sub(r"(?i)^a\s+reusable\s+", "", hint)
|
||||
hint = re.sub(r"(?i)^reusable\s+", "", hint)
|
||||
hint = re.sub(r"(?i)^(workflow|reference|tool[- ]guide)\s+for\s+", "", hint)
|
||||
hint = re.sub(r"(?i)^(adding|creating|capturing|building)\s+", "", hint)
|
||||
hint = re.sub(r"(?i)^(a|an|the)\s+", "", hint)
|
||||
|
||||
fallback = _sanitize_skill_name(hint)
|
||||
if fallback:
|
||||
return fallback
|
||||
|
||||
prefix = (category.value if category else "captured-skill").replace("_", "-")
|
||||
return f"{prefix}-{uuid.uuid4().hex[:6]}"
|
||||
|
||||
_ANALYSIS_CONTEXT_MAX = 5 # Max recent analyses to include in prompt
|
||||
_ANALYSIS_NOTE_MAX_CHARS = 500 # Per-analysis note truncation
|
||||
|
||||
|
|
@ -937,15 +1021,26 @@ class SkillEvolver:
|
|||
new_content, change_summary = _extract_change_summary(new_content)
|
||||
|
||||
# Extract name/description from the generated content
|
||||
new_name = _extract_frontmatter_field(new_content, "name")
|
||||
new_desc = _extract_frontmatter_field(new_content, "description")
|
||||
new_name = _extract_generated_frontmatter_field(new_content, "name")
|
||||
new_desc = _extract_generated_frontmatter_field(new_content, "description")
|
||||
if not new_name:
|
||||
logger.warning("CAPTURED: LLM did not produce a valid skill name")
|
||||
return None
|
||||
new_name = _fallback_captured_skill_name(
|
||||
ctx.suggestion.direction,
|
||||
ctx.suggestion.category,
|
||||
)
|
||||
logger.warning(
|
||||
"CAPTURED: LLM did not produce a valid skill name; using fallback '%s'",
|
||||
new_name,
|
||||
)
|
||||
|
||||
# Sanitize name (enforce length limit + valid chars)
|
||||
new_name = _sanitize_skill_name(new_name)
|
||||
new_content = _set_frontmatter_field(new_content, "name", new_name)
|
||||
if not new_name:
|
||||
new_name = _fallback_captured_skill_name(
|
||||
ctx.suggestion.direction,
|
||||
ctx.suggestion.category,
|
||||
)
|
||||
new_content = _set_generated_frontmatter_field(new_content, "name", new_name)
|
||||
|
||||
# Create new skill directory via create_skill (handles multi-file FULL)
|
||||
skill_dirs = self._registry._skill_dirs
|
||||
|
|
@ -1528,4 +1623,4 @@ class SkillEvolver:
|
|||
f"better error handling or alternative approaches."
|
||||
)
|
||||
|
||||
return None, ""
|
||||
return None, ""
|
||||
|
|
|
|||
|
|
@ -357,6 +357,7 @@ class OpenSpace:
|
|||
self._task_done.clear()
|
||||
self._last_evolved_skills = [] # Reset per-execution tracking
|
||||
start_time = asyncio.get_event_loop().time()
|
||||
execution_time = 0.0
|
||||
# Use external task_id if provided, otherwise generate one
|
||||
if task_id is None:
|
||||
task_id = f"task_{uuid.uuid4().hex[:12]}"
|
||||
|
|
@ -555,6 +556,9 @@ class OpenSpace:
|
|||
}
|
||||
|
||||
finally:
|
||||
if execution_time <= 0:
|
||||
execution_time = asyncio.get_event_loop().time() - start_time
|
||||
|
||||
recording_dir = None
|
||||
if self._recording_manager and self._recording_manager.recording_status:
|
||||
recording_dir = self._recording_manager.trajectory_dir
|
||||
|
|
|
|||
79
tests/test_evolver_captured_names.py
Normal file
79
tests/test_evolver_captured_names.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
from openspace.skill_engine.evolver import (
|
||||
_extract_generated_frontmatter_field,
|
||||
_fallback_captured_skill_name,
|
||||
_set_generated_frontmatter_field,
|
||||
)
|
||||
|
||||
|
||||
def test_extract_generated_frontmatter_field_from_single_file() -> None:
|
||||
content = """---
|
||||
name: safe-file-write
|
||||
description: Write files with validation.
|
||||
---
|
||||
|
||||
# Safe File Write
|
||||
"""
|
||||
|
||||
assert _extract_generated_frontmatter_field(content, "name") == "safe-file-write"
|
||||
assert (
|
||||
_extract_generated_frontmatter_field(content, "description")
|
||||
== "Write files with validation."
|
||||
)
|
||||
|
||||
|
||||
def test_extract_generated_frontmatter_field_from_multi_file_full() -> None:
|
||||
content = """*** Begin Files
|
||||
*** File: SKILL.md
|
||||
---
|
||||
name: local-acceptance-entry
|
||||
description: Run a repo's backend and frontend together for review.
|
||||
---
|
||||
|
||||
# Local Acceptance Entry
|
||||
*** File: examples/start.sh
|
||||
#!/usr/bin/env bash
|
||||
echo start
|
||||
*** End Files
|
||||
"""
|
||||
|
||||
assert (
|
||||
_extract_generated_frontmatter_field(content, "name")
|
||||
== "local-acceptance-entry"
|
||||
)
|
||||
assert (
|
||||
_extract_generated_frontmatter_field(content, "description")
|
||||
== "Run a repo's backend and frontend together for review."
|
||||
)
|
||||
|
||||
|
||||
def test_set_generated_frontmatter_field_updates_skill_md_in_multi_file_full() -> None:
|
||||
content = """*** Begin Files
|
||||
*** File: SKILL.md
|
||||
---
|
||||
description: Run a repo's backend and frontend together for review.
|
||||
---
|
||||
|
||||
# Local Acceptance Entry
|
||||
*** File: examples/start.sh
|
||||
#!/usr/bin/env bash
|
||||
echo start
|
||||
*** End Files
|
||||
"""
|
||||
|
||||
updated = _set_generated_frontmatter_field(content, "name", "local-acceptance-entry")
|
||||
|
||||
assert "*** File: SKILL.md" in updated
|
||||
assert "name: local-acceptance-entry" in updated
|
||||
assert "*** File: examples/start.sh" in updated
|
||||
|
||||
|
||||
def test_fallback_captured_skill_name_uses_direction_signal() -> None:
|
||||
direction = (
|
||||
"Capture a reusable workflow for adding a local acceptance entry in "
|
||||
"split backend/frontend repos: create one canonical foreground acceptance script."
|
||||
)
|
||||
|
||||
fallback = _fallback_captured_skill_name(direction, None)
|
||||
|
||||
assert fallback.startswith("local-acceptance-entry-in-split-backend")
|
||||
assert len(fallback) <= 50
|
||||
Loading…
Add table
Reference in a new issue