fix(codex): 完善独立运行与文档转换兼容性

This commit is contained in:
zhiye.sun
2026-08-31 13:18:41 +08:00
parent 485e65dfd9
commit 5d7a77b8ba
7 changed files with 53 additions and 14 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ description: 基于需求、现有后端代码和项目规范设计模块边界
## 边界 ## 边界
- 项目规则通过 `guidance` 获取;不存在的规范、框架能力和依赖接口不得猜测。 - 已安装 `guidance` 时用它获取项目规则;未安装时按“目标目录适用的 `AGENTS.md` → `.craftkit/agents/index.md` → `.craftkit/standards/index.md` → 命中正文”的顺序手工读取。不存在的规范、框架能力和依赖接口不得猜测。
- 版本从 `.craftkit/project.json`、构建文件、锁文件或源码证据确认,不默认最新版本。 - 版本从 `.craftkit/project.json`、构建文件、锁文件或源码证据确认,不默认最新版本。
- 对现有系统的设计先追踪真实调用链和数据流,区分已验证事实与建议。 - 对现有系统的设计先追踪真实调用链和数据流,区分已验证事实与建议。
- 不把接口示例、表结构草案或伪代码视为已实施行为。 - 不把接口示例、表结构草案或伪代码视为已实施行为。
@@ -10,7 +10,7 @@ description: 按当前项目真实语言、框架版本、规范和现有实现
## 工作流 ## 工作流
1. 检查 Git 状态,保留用户已有改动;确认目标、范围和不可修改项。 1. 检查 Git 状态,保留用户已有改动;确认目标、范围和不可修改项。
2. 读取 `AGENTS.md`、`.craftkit/project.json`,使用 `guidance` 获取项目规范,并从构建文件确认真实版本。 2. 读取 `AGENTS.md`、`.craftkit/project.json`,从构建文件确认真实版本。已安装 `guidance` 时用它检索规范;未安装时按“目标目录适用的 `AGENTS.md` → `.craftkit/agents/index.md` → `.craftkit/standards/index.md` → 命中正文”的顺序手工读取,不因缺少另一插件中断实现。
3. 追踪入口、调用链、数据流、测试和相邻稳定实现;设计不足时先补最小决策,不套用固定模板。 3. 追踪入口、调用链、数据流、测试和相邻稳定实现;设计不足时先补最小决策,不套用固定模板。
4. 实施最小完整变更,保持项目目录、依赖、异常、事务、日志和测试风格。 4. 实施最小完整变更,保持项目目录、依赖、异常、事务、日志和测试风格。
5. 执行项目已有的编译、静态检查和相关测试,分别报告未执行的真实环境验证。 5. 执行项目已有的编译、静态检查和相关测试,分别报告未执行的真实环境验证。
@@ -10,7 +10,7 @@ description: 按当前项目框架版本、组件契约、设计令牌和请求
## 工作流 ## 工作流
1. 检查 Git 状态并确认目标文件;从项目元数据、依赖和锁文件识别框架、构建工具及精确版本。 1. 检查 Git 状态并确认目标文件;从项目元数据、依赖和锁文件识别框架、构建工具及精确版本。
2. 使用 `guidance` 获取规范,检查相邻页面和公共封装;组件、样式、表单分别消费 `component`、`style`、`form` 的证据。 2. 已安装 `guidance` 时用它检索规范;未安装时按“目标目录适用的 `AGENTS.md` → `.craftkit/agents/index.md` → `.craftkit/standards/index.md` → 命中正文”的顺序手工读取。随后检查相邻页面和公共封装;组件、样式、表单分别消费 `component`、`style`、`form` 的证据。
3. 有设计与接口映射时消费 `design-frontend` 和 `prepare-api`;没有时只补当前实现必需的最小决策。 3. 有设计与接口映射时消费 `design-frontend` 和 `prepare-api`;没有时只补当前实现必需的最小决策。
4. 修改页面、路由、状态和 API 层,保持项目现有契约,不虚构组件、props、事件、接口或业务校验。 4. 修改页面、路由、状态和 API 层,保持项目现有契约,不虚构组件、props、事件、接口或业务校验。
5. 执行已有格式化、类型检查、测试和构建;未进行真实渲染或浏览器验证时明确说明。 5. 执行已有格式化、类型检查、测试和构建;未进行真实渲染或浏览器验证时明确说明。
+2 -1
View File
@@ -12,7 +12,8 @@ description: 将 Markdown 文档转换为 Word .docx,保留常见标题、段
- 只接受存在的 `.md` 或 `.markdown` 文件。 - 只接受存在的 `.md` 或 `.markdown` 文件。
- 默认在输入文件旁生成同名 `.docx`;文件已存在时停止,只有用户明确同意覆盖后才传入 `--force`。 - 默认在输入文件旁生成同名 `.docx`;文件已存在时停止,只有用户明确同意覆盖后才传入 `--force`。
- 用户提供 `.docx` 模板时可传入 `--template`,转换器沿用模板样式并在文档末尾追加内容,不替换模板中的占位符。 - 用户提供 `.docx` 模板时可传入 `--template`,转换器沿用模板样式并在文档末尾追加内容,不替换模板中的占位符。
- 不自动下载图片、字体或依赖;远程图片保留为文字提示,本地缺失图片产生警告。 - 转换依赖 Python 包 `python-docx`。优先使用 Codex 工作区依赖运行时;环境缺失时停止并给出提示,不自动安装依赖。
- 不自动下载图片或字体;远程图片保留为文字提示,本地缺失图片产生警告。
- 不伪造修订记录、批注或目录。需要人工审阅留痕时应使用独立的文档修订流程。 - 不伪造修订记录、批注或目录。需要人工审阅留痕时应使用独立的文档修订流程。
## 转换流程 ## 转换流程
@@ -9,10 +9,6 @@ import sys
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from docx import Document
from docx.shared import Inches, Pt
@dataclass @dataclass
class Result: class Result:
"""记录生成物与转换统计。""" """记录生成物与转换统计。"""
@@ -43,6 +39,10 @@ class Converter:
"""使用可预测的小型解析器转换常见 Markdown。""" """使用可预测的小型解析器转换常见 Markdown。"""
def __init__(self, source: Path, output: Path, template: Path | None) -> None: def __init__(self, source: Path, output: Path, template: Path | None) -> None:
# 第三方库在真正转换时才加载,使 --help、参数错误和能力探测不依赖本机预装包。
from docx import Document
self.document_type = Document
self.source = source self.source = source
self.doc = Document(template) if template else Document() self.doc = Document(template) if template else Document()
self.result = Result(output) self.result = Result(output)
@@ -61,12 +61,14 @@ class Converter:
self.add_line(lines[index]) self.add_line(lines[index])
index += 1 index += 1
self.doc.save(self.result.output) self.doc.save(self.result.output)
Document(self.result.output) self.document_type(self.result.output)
return self.result return self.result
def add_code(self, lines: list[str], start: int) -> int: def add_code(self, lines: list[str], start: int) -> int:
"""读取围栏代码块;未闭合时输出其余内容并记录警告。""" """读取围栏代码块;未闭合时输出其余内容并记录警告。"""
from docx.shared import Pt
index = start + 1 index = start + 1
content: list[str] = [] content: list[str] = []
while index < len(lines) and not lines[index].lstrip().startswith("```"): while index < len(lines) and not lines[index].lstrip().startswith("```"):
@@ -101,6 +103,8 @@ class Converter:
def add_line(self, line: str) -> None: def add_line(self, line: str) -> None:
"""识别标题、列表、引用、分隔线和普通段落。""" """识别标题、列表、引用、分隔线和普通段落。"""
from docx.shared import Inches
value = line.strip() value = line.strip()
if not value: if not value:
return return
@@ -158,6 +162,8 @@ class Converter:
def add_image(self, paragraph, alt: str, target: str) -> None: def add_image(self, paragraph, alt: str, target: str) -> None:
"""只处理本地图片,防止转换过程产生隐式网络访问。""" """只处理本地图片,防止转换过程产生隐式网络访问。"""
from docx.shared import Inches
if re.match(r"^[a-z][a-z0-9+.-]*://", target, re.I): if re.match(r"^[a-z][a-z0-9+.-]*://", target, re.I):
paragraph.add_run(f"[远程图片:{alt or target}]") paragraph.add_run(f"[远程图片:{alt or target}]")
self.result.warnings.append(f"未下载远程图片:{target}") self.result.warnings.append(f"未下载远程图片:{target}")
@@ -193,9 +199,22 @@ def main(argv: list[str] | None = None) -> int:
print("错误:输出必须是可写的 .docx;覆盖需使用 --force", file=sys.stderr); return 1 print("错误:输出必须是可写的 .docx;覆盖需使用 --force", file=sys.stderr); return 1
if template and (not template.is_file() or template.suffix.lower() != ".docx"): if template and (not template.is_file() or template.suffix.lower() != ".docx"):
print("错误:模板必须是存在的 .docx 文件", file=sys.stderr); return 2 print("错误:模板必须是存在的 .docx 文件", file=sys.stderr); return 2
try:
converter = Converter(source, output, template)
except ModuleNotFoundError as error:
if error.name == "docx":
print(
"错误:缺少 python-docx。请使用 Codex 工作区依赖运行时,"
"或在已获授权的本地 Python 环境中安装 python-docx 后重试。",
file=sys.stderr,
)
return 3
raise
except (OSError, ValueError) as error:
print(f"错误:{error}", file=sys.stderr); return 1
output.parent.mkdir(parents=True, exist_ok=True) output.parent.mkdir(parents=True, exist_ok=True)
try: try:
result = Converter(source, output, template).convert() result = converter.convert()
except (OSError, ValueError) as error: except (OSError, ValueError) as error:
print(f"错误:{error}", file=sys.stderr); return 1 print(f"错误:{error}", file=sys.stderr); return 1
print(f"DOCX:{result.output}") print(f"DOCX:{result.output}")
+2 -1
View File
@@ -13,7 +13,8 @@ description: 将 Excel .xlsx 工作簿转换为 Markdown,按工作表提取表
- 默认保留公式文本;只有用户希望读取工作簿内已有缓存值时才使用 `--values`。转换器不会计算公式。 - 默认保留公式文本;只有用户希望读取工作簿内已有缓存值时才使用 `--values`。转换器不会计算公式。
- 合并单元格默认将锚点值填充到合并区域,可用 `--merged anchor` 仅保留左上角值。 - 合并单元格默认将锚点值填充到合并区域,可用 `--merged anchor` 仅保留左上角值。
- 默认在输入文件旁生成同名 `.md`,已存在时停止;覆盖必须获得用户确认并传入 `--force`。 - 默认在输入文件旁生成同名 `.md`,已存在时停止;覆盖必须获得用户确认并传入 `--force`。
- 不提取宏、图表、批注、数据验证、条件格式或图片,也不自动安装依赖。 - 转换依赖 Python 包 `openpyxl`。优先使用 Codex 工作区依赖运行时;环境缺失时停止并给出提示,不自动安装依赖。
- 不提取宏、图表、批注、数据验证、条件格式或图片。
## 工作流 ## 工作流
@@ -5,12 +5,10 @@ from __future__ import annotations
import argparse import argparse
import datetime as dt import datetime as dt
import importlib.util
import sys import sys
from pathlib import Path from pathlib import Path
from openpyxl import load_workbook
def display(value: object) -> str: def display(value: object) -> str:
"""将单元格值转换为稳定、可读且适合表格的文本。""" """将单元格值转换为稳定、可读且适合表格的文本。"""
@@ -59,6 +57,9 @@ def render_sheet(title: str, rows: list[list[str]]) -> str:
def convert(source: Path, output: Path, values_only: bool, merged_mode: str) -> tuple[int, int, int, list[str]]: def convert(source: Path, output: Path, values_only: bool, merged_mode: str) -> tuple[int, int, int, list[str]]:
"""打开工作簿、转换全部工作表并写入 UTF-8 Markdown。""" """打开工作簿、转换全部工作表并写入 UTF-8 Markdown。"""
# 第三方库在真正读取工作簿时才加载,使 --help 和参数校验可在干净环境中执行。
from openpyxl import load_workbook
workbook = load_workbook(source, read_only=False, data_only=values_only) workbook = load_workbook(source, read_only=False, data_only=values_only)
sections = [f"# {source.stem}"] sections = [f"# {source.stem}"]
row_count = merged_count = 0 row_count = merged_count = 0
@@ -94,9 +95,26 @@ def main(argv: list[str] | None = None) -> int:
print("错误:输出文件必须是 Markdown", file=sys.stderr); return 2 print("错误:输出文件必须是 Markdown", file=sys.stderr); return 2
if output.exists() and not args.force: if output.exists() and not args.force:
print(f"错误:输出文件已存在:{output}", file=sys.stderr); return 1 print(f"错误:输出文件已存在:{output}", file=sys.stderr); return 1
# 在创建输出目录前完成依赖预检,保证缺少运行库时不会留下空目录或半成品。
if importlib.util.find_spec("openpyxl") is None:
print(
"错误:缺少 openpyxl。请使用 Codex 工作区依赖运行时,"
"或在已获授权的本地 Python 环境中安装 openpyxl 后重试。",
file=sys.stderr,
)
return 3
output.parent.mkdir(parents=True, exist_ok=True) output.parent.mkdir(parents=True, exist_ok=True)
try: try:
sheets, rows, merged, warnings = convert(source, output, args.values, args.merged) sheets, rows, merged, warnings = convert(source, output, args.values, args.merged)
except ModuleNotFoundError as error:
if error.name == "openpyxl":
print(
"错误:缺少 openpyxl。请使用 Codex 工作区依赖运行时,"
"或在已获授权的本地 Python 环境中安装 openpyxl 后重试。",
file=sys.stderr,
)
return 3
raise
except (OSError, ValueError) as error: except (OSError, ValueError) as error:
print(f"错误:{error}", file=sys.stderr); return 1 print(f"错误:{error}", file=sys.stderr); return 1
print(f"Markdown:{output}") print(f"Markdown:{output}")