from __future__ import annotations


def enterprise_safety_rule() -> str:
    return """---
alwaysApply: true
---

# 企业内网 Trae 工作区安全规则

这些规则适用于企业内网项目中的 Trae IDE 使用场景，是所有角色 pack 的基础约束。

## 数据与密钥边界

- 禁止读取、复制或总结 `.env`、私钥、Token、Cookie、数据库连接配置、Webhook、证书和其他敏感凭据。
- 禁止将内部代码、日志、客户数据、业务数据、缺陷详情、生产拓扑或未公开计划发送到企业边界之外。
- 如需引用敏感上下文，只能使用脱敏摘要，并说明脱敏方式。

## 变更与审批边界

- 默认只读或 dry-run；不删除、不移动、不重启、不修改配置。
- 涉及生产变更、数据修改、服务重启、权限变更、批量操作或破坏性动作时，必须先输出计划、风险、回滚方式和验证命令，并等待人工确认。
- 不得把 Trae Rules、Skills 或 Agent prompt 描述成操作系统级安全边界；它们是工作流护栏，不是沙箱。

## 脚本与路径边界

- 生成脚本必须验证路径限制在用户指定 root 内。
- Python 脚本禁止 `shell=True`，优先使用结构化参数和超时。
- 输出必须说明观察结果、计划动作、跳过原因和下一步人工确认点。
"""


def ops_safety_rule() -> str:
    return """---
alwaysApply: true
---
<!-- 来源说明：[fact] Trae 官方文档 · [extracted] 文章提炼 · [extended] 实践扩展 -->
<!-- [fact] alwaysApply: true 为 Trae Rules 官方 frontmatter 键，规则在每次请求中自动注入，无需手动引用 -->

# 运维脚本安全规则

这些规则用于 Trae IDE 项目中的运维脚本自动化任务。

## 固定边界

<!-- [extracted] 只读/dry-run 优先、等待人工确认的原则，提炼自「AI 生成脚本应当可检视后再执行」核心理念 -->
- 默认只读或 dry-run；不删除、不移动、不重启、不修改配置。
- 涉及文件删除、目录归档、服务重启、K8s、数据库或生产配置时，先输出计划、风险、回滚方式，等待人工确认。
<!-- [extended] 以下条目为 Python 安全实践与运维经验扩展，非文章原文，可按团队规范调整 -->
- Python 脚本禁止 `shell=True`，优先使用 `subprocess.run([...], timeout=...)`。
- 不读取 `.env`、私钥、Token、Cookie、数据库配置等敏感文件。
- 所有脚本必须有路径 allowlist 或 root containment 校验。
- 输出必须说明观察到什么、准备做什么、哪些动作被跳过。
- 退出码约定：`0=正常`，`1=参数/路径错误`，`2=发现告警`。

## 工作方式

<!-- [extracted] 观察→dry-run 计划→人工审查→执行的四步流，提炼自文章安全自动化原则 -->
1. 先写只读检查。
2. 再写 dry-run 计划输出。
3. 人工审查输出后，才允许讨论真实执行模式。
<!-- [extended] 测试覆盖要求为实践扩展 -->
4. 测试必须覆盖正常、告警、错误输入和排除目录。
"""


def ops_skill() -> str:
    return """---
name: ops-maintenance-automation
description: Use when creating or reviewing safe operations maintenance scripts in Trae IDE; focuses on dry-run, path validation, readable output, and human-confirmed execution.
---
<!-- 来源说明：[fact] Trae 官方文档 · [extracted] 文章提炼 · [extended] 实践扩展 -->
<!-- [fact] name/description 为 Trae Skill 官方 frontmatter 键；文件须位于 .trae/skills/{name}/SKILL.md，由 @ 按需加载 -->

# Ops Maintenance Automation

## Description
<!-- [extracted] Skill 定位提炼自核心理念：AI 生成的脚本应当可检视、可回滚后再允许执行 -->

This skill turns maintenance-script requests into safe, inspectable automation. It follows the article-derived principle: AI-generated maintenance scripts should be easy to inspect before they are allowed to act.

## When to use
<!-- [extended] 触发场景为实践扩展，覆盖常见运维脚本请求模式，可按需增删 -->

Use this skill when the user asks to:

- create Linux / home-lab / project maintenance scripts;
- check backups, logs, Docker Compose files, stale directories, service status;
- add dry-run, guardrails, exit codes, or review workflow to scripts;
- convert a vague operations automation idea into a safe implementation plan.

## Instructions
<!-- [extracted] 步骤 1–5 提炼自「先观察、后计划、等人工确认、再执行」安全流程 -->
<!-- [extended] 步骤 6–9 为 Python 类型注解、测试覆盖和报告规范实践扩展，可按团队规范调整 -->

1. Rewrite vague requests into explicit scope, non-scope, allowed paths, forbidden actions, and acceptance criteria.
2. Start with read-only inspection. Do not generate destructive behavior first.
3. Require dry-run for anything that could move, delete, restart, rewrite, deploy, or mutate configuration.
4. Validate paths before touching them. Keep all generated paths inside the user-provided root.
5. Separate observation from remediation:
   - observation: read status and produce report;
   - candidate remediation: print planned actions only;
   - execution: requires a separate explicit human approval.
6. Use typed Python functions and dataclasses for structured results.
7. Avoid `shell=True`, hardcoded secrets, broad globs from `/`, and silent exception swallowing.
8. Run tests or a local sample before declaring the script usable.
9. Report exact commands, outputs, changed files, and known limitations.

## Example prompt
<!-- [extended] 示例 prompt 为实践扩展，展示 Skill 的推荐调用方式 -->

```text
Use ops-maintenance-automation.
Create a Python maintenance checker for this project.
Scope: only inspect ./backups, ./logs, and ./projects under the provided --root.
Forbidden: delete, move, restart services, read secrets, or modify configs.
Required: dry-run output, path validation, exit codes 0/1/2, tests for OK/WARN/error.
```
"""


def agent_prompt() -> str:
    return """# Trae 自定义 Agent Prompt：Ops Maintenance Builder
<!-- 来源说明：[fact] Trae 官方文档 · [extracted] 文章提炼 · [extended] 实践扩展 -->

将下面内容粘贴到 Trae 的自定义 Agent Prompt 中。

```text
你是 Ops Maintenance Builder，一个用于生成和审查运维维护脚本的 Trae IDE 智能体。

职责：
- 把模糊的运维自动化请求改写成可执行边界；
- 优先生成只读检查脚本和 dry-run 输出；
- 帮用户审查脚本是否安全、可读、可回滚；
- 在真实执行前输出计划、风险、验证命令和人工确认点。

必须遵守：
- 默认不删除、不移动、不重启、不修改配置；
- 不使用 shell=True；
- 不读取 .env、私钥、Token、Cookie、数据库配置等敏感文件；
- 所有路径必须限制在用户指定 --root 内；
- 退出码约定：0=正常，1=参数/路径错误，2=发现告警；
- 遇到数据库、K8s、生产服务、批量删除、真实归档，先停下来请求人工确认；
- 每次生成代码后都要给出验证命令和预期输出。

推荐工具：
- Read：读取项目文件、AGENTS.md、CLAUDE.md、已有脚本；
- Edit：仅编辑用户确认范围内的脚本、测试、文档；
- Terminal：运行测试和 dry-run 样例；
- Web search：仅用于查官方文档或错误信息。

工作流：
1. 先复述改写后的请求，列出范围、非目标和验收标准。
2. 搜索/读取项目现有规则和脚本。
3. 先写测试或样例验证，再写脚本。
4. 第一版只做 read-only / dry-run。
5. 运行验证命令。
6. 汇报改动文件、命令输出、风险和下一步人工确认点。
```
<!-- [fact] 以上工具（Read/Edit/Terminal/Web search）为 Trae 自定义 Agent 官方内置工具，需在 Agent 设置中勾选开启 -->
<!-- [extracted] 职责和「必须遵守」中的只读/dry-run/人工确认原则，提炼自「AI 脚本应当可检视后再执行」核心理念 -->
<!-- [extended] 工作流六步和代码生成后给出验证命令的要求为实践扩展，可按团队习惯调整步骤顺序 -->

## 建议开启的 Trae 工具
<!-- [fact] Trae 自定义 Agent 支持按需开关以下内置工具 -->

- Read
- Edit
- Terminal
- Web search（可选）

## 建议关联的项目 Skill
<!-- [fact] Trae Skill 通过 @ 引用后按需加载，与 Agent 配合使用 -->

- `ops-maintenance-automation`
"""


def workspace_readme() -> str:
    return """# Trae 运维脚本自动化工作区
<!-- 来源说明：[fact] Trae 官方文档 · [extracted] 文章提炼 · [extended] 实践扩展 -->

本目录由 `trae-workspace-tool` 的 `ops` pack 生成。

## 文件说明
<!-- [fact] .trae/rules/ 和 .trae/skills/ 为 Trae 官方约定的项目级配置目录 -->

- `.trae/rules/enterprise-safety.md`：企业内网通用安全规则，由 `internal-basic` profile 生成。
- `.trae/rules/ops-safety.md`：Trae 项目规则，长期约束 AI 运维行为。
- `.trae/skills/ops-maintenance-automation/SKILL.md`：Trae Skill，按需加载的运维脚本 SOP。
- `docs/trae-agents/ops-maintenance-agent-prompt.md`：用于创建 Trae 自定义 Agent 的 Prompt。
<!-- [extended] maintenance_check.py 为本工具生成的实践扩展，不属于 Trae 官方文件 -->
- `scripts/maintenance_check.py`：默认 dry-run 的维护检查脚本。

## 快速验证
<!-- [extended] 以下验证命令为实践扩展 -->

```bash
python3 scripts/maintenance_check.py --root /tmp/some-root
```

如果目标目录不存在，应返回 `exit=1` 并输出 `[ERROR] validation`。

## Trae 设置步骤
<!-- [fact] 步骤 1–3 对应 Trae 官方 Settings 面板中的 Rules 和 Skills & Commands 入口 -->
<!-- [fact] 步骤 4 对应 Trae 官方「@ -> Create Agent」功能 -->

1. 打开 Trae IDE 并加载本项目。
2. Settings > Rules：确认项目规则被识别。
3. Settings > Skills & Commands：确认项目 skill 被识别。
4. 在聊天输入框输入 `@` 创建自定义 Agent，并粘贴 `docs/trae-agents/ops-maintenance-agent-prompt.md` 中的 Prompt。
"""


def maintenance_script() -> str:
    return """#!/usr/bin/env python3
from __future__ import annotations

import argparse
import time
from dataclasses import dataclass
from pathlib import Path


EXCLUDED_DIRS: set[str] = set()


@dataclass(frozen=True)
class CheckResult:
    name: str
    status: str
    detail: str


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Safe maintenance checker for Trae-generated ops scripts")
    parser.add_argument("--root", required=True, help="Allowed root directory")
    parser.add_argument("--dry-run", action="store_true", help="Compatibility flag; dry-run is always the default")
    parser.add_argument("--max-log-mb", type=int, default=1, help="Warn when log files exceed this size")
    parser.add_argument("--stale-days", type=int, default=30, help="Warn when project dirs are older than this")
    parser.add_argument("--exclude", action="append", default=[], metavar="DIR", help="Directory names to skip archival (repeatable)")
    parser.add_argument("--backup-dir", default="backups", metavar="DIR", help="Subdirectory to check for backups (default: backups)")
    parser.add_argument("--logs-dir", default="logs", metavar="DIR", help="Subdirectory to scan for oversized log files (default: logs)")
    parser.add_argument("--projects-dir", default="projects", metavar="DIR", help="Subdirectory to scan for stale project folders (default: projects)")
    return parser.parse_args()


def resolve_root(raw_root: str) -> Path:
    root = Path(raw_root).expanduser().resolve()
    if not root.exists() or not root.is_dir():
        raise ValueError(f"root is not a directory: {root}")
    return root


def ensure_inside_root(root: Path, candidate: Path) -> Path:
    resolved = candidate.resolve()
    if root not in [resolved, *resolved.parents]:
        raise ValueError(f"path escapes allowed root: {resolved}")
    return resolved


def check_backup_dir(root: Path, backup_dir: str = "backups") -> CheckResult:
    target = ensure_inside_root(root, root / backup_dir)
    if target.exists():
        return CheckResult("backup_dir", "OK", f"found {target}")
    return CheckResult("backup_dir", "WARN", f"missing {target}")


def check_large_logs(root: Path, max_log_mb: int, logs_dir: str = "logs") -> list[CheckResult]:
    log_dir = ensure_inside_root(root, root / logs_dir)
    if not log_dir.exists():
        return [CheckResult("large_logs", "WARN", f"missing {log_dir}")]
    limit_bytes = max_log_mb * 1024 * 1024
    results: list[CheckResult] = []
    for path in sorted(log_dir.glob("*.log")):
        try:
            resolved_path = ensure_inside_root(root, path)
        except ValueError:
            results.append(CheckResult("large_logs", "WARN", f"skipped path outside root: {path.name}"))
            continue
        if not resolved_path.is_file():
            continue
        size = resolved_path.stat().st_size
        if size > limit_bytes:
            results.append(CheckResult("large_logs", "WARN", f"{path.name} is {size} bytes"))
    if not results:
        results.append(CheckResult("large_logs", "OK", "no oversized log files"))
    return results


def check_stale_projects(
    root: Path,
    stale_days: int,
    exclude: set[str] | None = None,
    projects_dir: str = "projects",
) -> list[CheckResult]:
    effective_exclude = EXCLUDED_DIRS | (exclude or set())
    projects_dir_path = ensure_inside_root(root, root / projects_dir)
    if not projects_dir_path.exists():
        return [CheckResult("stale_projects", "WARN", f"missing {projects_dir_path}")]
    threshold_seconds = stale_days * 24 * 60 * 60
    now = time.time()
    results: list[CheckResult] = []
    for path in sorted(projects_dir_path.iterdir()):
        if path.name in effective_exclude:
            results.append(CheckResult("stale_projects", "SKIP", f"excluded {path.name}"))
            continue
        try:
            resolved_path = ensure_inside_root(root, path)
        except ValueError:
            results.append(CheckResult("stale_projects", "WARN", f"skipped path outside root: {path.name}"))
            continue
        if not resolved_path.is_dir():
            continue
        age_seconds = now - resolved_path.stat().st_mtime
        if age_seconds >= threshold_seconds:
            planned_target = ensure_inside_root(root, root / "archive" / path.name)
            results.append(
                CheckResult("stale_projects", "DRY-RUN", f"would archive {path} to {planned_target}")
            )
    if not results:
        results.append(CheckResult("stale_projects", "OK", "no stale project folders"))
    return results


def print_results(results: list[CheckResult]) -> None:
    for item in results:
        print(f"[{item.status}] {item.name}: {item.detail}")


def main() -> int:
    args = parse_args()
    try:
        root = resolve_root(args.root)
        results = [check_backup_dir(root, backup_dir=args.backup_dir)]
        results.extend(check_large_logs(root, args.max_log_mb, logs_dir=args.logs_dir))
        results.extend(
            check_stale_projects(
                root,
                args.stale_days,
                exclude=set(args.exclude),
                projects_dir=args.projects_dir,
            )
        )
        print_results(results)
        if any(item.status == "WARN" for item in results):
            return 2
        return 0
    except ValueError as exc:
        print(f"[ERROR] validation: {exc}")
        return 1


if __name__ == "__main__":
    raise SystemExit(main())
"""
