diff --git a/docs/design/Design_Reconstructability_Traceability_Matrix.md b/docs/design/Design_Reconstructability_Traceability_Matrix.md index fc6b33ce..8615f5af 100644 --- a/docs/design/Design_Reconstructability_Traceability_Matrix.md +++ b/docs/design/Design_Reconstructability_Traceability_Matrix.md @@ -1,3 +1,12 @@ +--- +change_ids: ["enhance-doc-governance-traceability"] +doc_kind: design +topics: ["design-reconstructability", "traceability", "governance"] +created: 2026-02-27 +updated: 2026-03-04 +status: active +--- + # 设计可重建性追踪矩阵(Doc -> Code -> Test) > 更新时间:2026-02-27 diff --git a/docs/features/enhance-doc-governance-traceability.md b/docs/features/enhance-doc-governance-traceability.md index bd661441..c540eb86 100644 --- a/docs/features/enhance-doc-governance-traceability.md +++ b/docs/features/enhance-doc-governance-traceability.md @@ -36,8 +36,8 @@ Unify documentation management structure, lifecycle governance, and SOP-to-skill - `openspec validate enhance-doc-governance-traceability --type change --strict --json --no-interactive` ### Results -- `../../.venv/bin/python -m pytest -q tests/unit/test_governance_traceability_gate.py tests/unit/test_governance_evidence_truth_gate.py`: passed (`50 passed`) after extending the traceability gate regression suite to also cover stale active index entries, `Active Entries`-only membership checks, explicit checkpoint-to-skill pair rows, active/archive index path-family enforcement, README index-file exclusion for both active and archived entries, discrete `todo_ids` token matching, Claim Ledger-only TODO/change validation, same-record TODO/change validation, claim-scope range resolution, range-only claim-scope resolution without explicit todo tokens, full lifecycle checkpoint coverage, and date-prefixed archived change task discovery. -- `./scripts/ci/check_governance_traceability.sh`: passed against the real repository tree after tightening active/archive index membership to canonical sections, rejecting index entries outside the correct feature-doc path family, excluding `docs/features/README.md` and `docs/features/archive/README.md` from valid feature-entry targets, requiring explicit checkpoint-to-skill pair rows in Section 7, and resolving pilot `todo_ids` only through Claim Ledger records, including same-claim scope ranges where the TODO id is only implied by the claim range. +- `../../.venv/bin/python -m pytest -q tests/unit/test_governance_traceability_gate.py tests/unit/test_governance_evidence_truth_gate.py`: passed (`53 passed`) after extending the traceability gate regression suite to cover governance standard/design/TODO frontmatter enforcement, active-feature `## TODO Coverage` checks in tasks artifacts, master TODO reverse mapping to concrete feature/tasks targets, stale active index entries, `Active Entries`-only membership checks, explicit checkpoint-to-skill pair rows, active/archive index path-family enforcement, README index-file exclusion for both active and archived entries, discrete `todo_ids` token matching, Claim Ledger-only TODO/change validation, same-record TODO/change validation, claim-scope range resolution, range-only claim-scope resolution without explicit todo tokens, full lifecycle checkpoint coverage, and date-prefixed archived change task discovery. +- `./scripts/ci/check_governance_traceability.sh`: passed against the real repository tree after tightening active/archive index membership to canonical sections, rejecting index entries outside the correct feature-doc path family, excluding `docs/features/README.md` and `docs/features/archive/README.md` from valid feature-entry targets, enforcing baseline frontmatter on governance standard/design docs plus concrete TODO ledgers, requiring explicit checkpoint-to-skill pair rows in Section 7, requiring active features with `todo_ids` to declare `## TODO Coverage` in their tasks artifact, and resolving Claim Ledger / slice-plan change targets back to concrete feature docs and tasks artifacts while skipping `planned` placeholder claims. - `./scripts/ci/check_governance_evidence_truth.sh`: passed, confirming the new traceability assets do not break the existing evidence-first contract. - `openspec validate enhance-doc-governance-traceability --type change --strict --json --no-interactive`: passed (`1/1` change valid, `0` issues). @@ -50,6 +50,11 @@ Unify documentation management structure, lifecycle governance, and SOP-to-skill - `docs/features/templates/feature_aggregation_template.md` - `docs/features/README.md` - `docs/features/archive/README.md` +- `docs/governance/Documentation_Management_Model.md` +- `docs/guides/Documentation_First_Development_SOP.md` +- `docs/todos/project_overall_todos.md` +- `docs/todos/agentscope_domain_execution_todos.md` +- `openspec/changes/agentscope-d2-d4-thinking-transport/tasks.md` - `scripts/ci/check_governance_traceability.sh` - `tests/unit/test_governance_traceability_gate.py` - `docs/features/agentscope-d2-d4-thinking-transport.md` @@ -69,24 +74,26 @@ Unify documentation management structure, lifecycle governance, and SOP-to-skill ### Structured Review Report - Changed Module Boundaries / Public API: governance scope only; no new runtime public API added. -- New State: adds one new repository gate script, one new gate test file, and canonical docs/index/template assets under `docs/features/`. +- New State: expands the repository traceability gate, adds frontmatter to the baseline governance standard/design/TODO docs, and adds `## TODO Coverage` mapping to the pilot tasks artifact under `openspec/changes/agentscope-d2-d4-thinking-transport/`. - Concurrency / Timeout / Retry: no concurrency change; gate runs are single-process document scans with deterministic rerun behavior after fixes. - Side Effects and Idempotency: side effects are limited to CI/log output; repeated runs are idempotent against unchanged docs. -- Coverage and Residual Risk: template/index/skill-mapping/TODO-linkage checks are covered; residual risk is that broader frontmatter enforcement across `docs/guides/**` and `docs/design/**` is still pending. +- Coverage and Residual Risk: template/index/skill-mapping/frontmatter/TODO-linkage/master-TODO-change-target checks are covered for the governance baseline docs and concrete TODO ledgers; residual risk is that broader standard/design families outside this baseline set are not yet auto-enforced. ### Behavior Verification -- Happy path: the repository now has a canonical feature aggregation template, explicit active/archive feature indexes, and a green traceability gate that resolves a pilot feature doc back to its TODO ledger and owning change-id through Claim Ledger records, including scope ranges such as `D2-1~D2-4, D4-1~D4-4`, even when the concrete TODO id does not appear elsewhere in the file. -- Error/fallback path: the new gate fails deterministically when a feature doc is missing from the `## Active Entries` section, when an active/archive index entry points at the wrong doc family, when Section 7 keeps checkpoint names but drops the actual `checkpoint -> skill` mapping rows, or when `todo_ids` and `change_ids` only co-occur in detail-board/prose lines without a matching Claim Ledger record. +- Happy path: the repository now has a canonical feature aggregation template, explicit active/archive feature indexes, governance baseline docs with structured frontmatter, and a green traceability gate that resolves a pilot feature doc back to its TODO ledger, owning change-id, and tasks coverage through Claim Ledger records plus `## TODO Coverage` ranges such as `D2-1~D2-4, D4-1~D4-4`. +- Error/fallback path: the gate now fails deterministically when a governance standard/TODO doc drops frontmatter, when an active feature declares `todo_ids` but its tasks artifact omits `## TODO Coverage`, when a master TODO slice table points to a non-existent change target, when a feature doc is missing from `## Active Entries`, when an active/archive index entry points at the wrong doc family, when Section 7 keeps checkpoint names but drops the actual `checkpoint -> skill` mapping rows, or when `todo_ids` and `change_ids` only co-occur in detail-board/prose lines without a matching Claim Ledger record. ### Risks and Rollback -- Risk: `3.2-3.4` are still open, so the new gate does not yet enforce full frontmatter coverage for every governance-tracked doc family or full master-TODO/task completeness. +- Risk: frontmatter auto-enforcement is still scoped to the governance baseline standard/design docs and concrete TODO ledgers, not every standard/design file in the repository. +- Risk: `planned` Claim Ledger rows are intentionally excluded from reverse mapping checks until a concrete feature/tasks artifact exists, so future placeholder drift still depends on later activation checks. - Risk: active/archive indexes are now explicit manual ledgers, so closeout changes that forget to update them will fail the new gate. - Rollback: remove `governance-traceability` from `.github/workflows/ci-gate.yml` and revert the template/index additions if the new gate produces unexpected false positives. ### Review and Merge Gate Links - Intent PR: `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/126` -- Implementation PR: `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/137` -- Current implementation PR: `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/175` +- Baseline implementation PR: `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/137` +- Previous implementation PR: `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/175` +- Current implementation PR: `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/178` - Review request: `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/126#issuecomment-3976690386` - Key owner feedback: `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/126#issuecomment-3976707233` - Active fix threads: @@ -103,4 +110,4 @@ Unify documentation management structure, lifecycle governance, and SOP-to-skill - `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/175#discussion_r2881421738` ## Next Milestone -Implement the remaining CI depth tasks: widen frontmatter enforcement beyond feature docs and add machine-checkable TODO/task and master-TODO/change-slice consistency checks before closeout. +Complete `5.3` closeout: update TODO/archive records, archive `enhance-doc-governance-traceability`, and move the feature evidence into `docs/features/archive/`. diff --git a/docs/governance/Documentation_Management_Model.md b/docs/governance/Documentation_Management_Model.md index afe10ef8..489e2143 100644 --- a/docs/governance/Documentation_Management_Model.md +++ b/docs/governance/Documentation_Management_Model.md @@ -1,3 +1,12 @@ +--- +change_ids: ["enhance-doc-governance-traceability"] +doc_kind: standard +topics: ["documentation-governance", "traceability", "skills"] +created: 2026-02-28 +updated: 2026-03-04 +status: active +--- + # Documentation Management Model > Scope: Repository-wide documentation governance for design, analysis, feature lifecycle, standards, temporary notes, and archive. diff --git a/docs/guides/Development_Constraints.md b/docs/guides/Development_Constraints.md index eefcdc69..678b1d27 100644 --- a/docs/guides/Development_Constraints.md +++ b/docs/guides/Development_Constraints.md @@ -1,3 +1,12 @@ +--- +change_ids: ["enhance-doc-governance-traceability"] +doc_kind: standard +topics: ["development-constraints", "governance", "workflow"] +created: 2026-02-28 +updated: 2026-03-04 +status: active +--- + # DARE 开发约束(面向 AI/Human 开发者) 适用对象:在本仓库编写代码的所有开发者(含 Codex、Claude 等 AI Agent)。目标:在不破坏架构的前提下,产出简洁、可验证、可维护的代码。 diff --git a/docs/guides/Documentation_First_Development_SOP.md b/docs/guides/Documentation_First_Development_SOP.md index ea839139..d1d8b44a 100644 --- a/docs/guides/Documentation_First_Development_SOP.md +++ b/docs/guides/Documentation_First_Development_SOP.md @@ -1,3 +1,12 @@ +--- +change_ids: ["enhance-doc-governance-traceability"] +doc_kind: standard +topics: ["documentation-governance", "sop", "workflow"] +created: 2026-02-28 +updated: 2026-03-04 +status: active +--- + # 文档先行开发 SOP(强制执行) > 适用范围:本仓库所有开发活动(AI Agent + Human),包含 Bug 修复、新增 Feature、重构。 diff --git a/docs/guides/Evidence_Truth_Implementation_Strategy.md b/docs/guides/Evidence_Truth_Implementation_Strategy.md index 7fc27022..e81511b4 100644 --- a/docs/guides/Evidence_Truth_Implementation_Strategy.md +++ b/docs/guides/Evidence_Truth_Implementation_Strategy.md @@ -1,3 +1,12 @@ +--- +change_ids: ["enhance-doc-governance-traceability"] +doc_kind: standard +topics: ["evidence-truth", "governance", "review"] +created: 2026-02-28 +updated: 2026-03-04 +status: active +--- + # Evidence Truth Implementation Strategy > Scope: Governance changes that use `docs/features/*.md` as aggregation entry documents. diff --git a/docs/todos/2026-02-27_design_reconstructability_gap_analysis.md b/docs/todos/2026-02-27_design_reconstructability_gap_analysis.md index de764cb7..43c18b21 100644 --- a/docs/todos/2026-02-27_design_reconstructability_gap_analysis.md +++ b/docs/todos/2026-02-27_design_reconstructability_gap_analysis.md @@ -1,3 +1,12 @@ +--- +change_ids: [] +doc_kind: analysis +topics: ["design-reconstructability", "gap-analysis", "governance"] +created: 2026-02-27 +updated: 2026-03-04 +status: active +--- + # 2026-02-27 设计文档可重建性 Gap 分析(P0/P1,闭环回写版) > 目标:评估“仅保留 docs 资产,能否重建当前实现主能力”。 diff --git a/docs/todos/2026-02-27_design_reconstructability_gap_todo.md b/docs/todos/2026-02-27_design_reconstructability_gap_todo.md index 8ec71d98..86aa4b8c 100644 --- a/docs/todos/2026-02-27_design_reconstructability_gap_todo.md +++ b/docs/todos/2026-02-27_design_reconstructability_gap_todo.md @@ -1,3 +1,12 @@ +--- +change_ids: [] +doc_kind: todo +topics: ["design-reconstructability", "todo", "governance"] +created: 2026-02-27 +updated: 2026-03-04 +status: active +--- + # 2026-02-27 可重建性 Gap TODO(P0/P1) > 来源:`docs/todos/2026-02-27_design_reconstructability_gap_analysis.md` diff --git a/docs/todos/2026-02-27_full_design_review_gap_analysis.md b/docs/todos/2026-02-27_full_design_review_gap_analysis.md index 91d291f0..31682150 100644 --- a/docs/todos/2026-02-27_full_design_review_gap_analysis.md +++ b/docs/todos/2026-02-27_full_design_review_gap_analysis.md @@ -1,3 +1,12 @@ +--- +change_ids: [] +doc_kind: analysis +topics: ["design-review", "gap-analysis", "architecture"] +created: 2026-02-27 +updated: 2026-03-04 +status: active +--- + # 2026-02-27 全量设计文档 Review:Design/Code Gap Analysis(第二轮刷新) > 类型:完整评审(非最小补充) diff --git a/docs/todos/2026-02-27_full_design_review_gap_todo.md b/docs/todos/2026-02-27_full_design_review_gap_todo.md index 147e22b0..af4fc35d 100644 --- a/docs/todos/2026-02-27_full_design_review_gap_todo.md +++ b/docs/todos/2026-02-27_full_design_review_gap_todo.md @@ -1,3 +1,12 @@ +--- +change_ids: [] +doc_kind: todo +topics: ["design-review", "todo", "architecture"] +created: 2026-02-27 +updated: 2026-03-04 +status: active +--- + # 2026-02-27 全量设计文档 Review:TODO 清单(第二轮刷新) > 来源:`docs/todos/2026-02-27_full_design_review_gap_analysis.md` diff --git a/docs/todos/agentscope_domain_execution_todos.md b/docs/todos/agentscope_domain_execution_todos.md index 85b75a33..8051e175 100644 --- a/docs/todos/agentscope_domain_execution_todos.md +++ b/docs/todos/agentscope_domain_execution_todos.md @@ -1,3 +1,12 @@ +--- +change_ids: [] +doc_kind: todo +topics: ["agentscope", "domain-execution", "todo"] +created: 2026-03-02 +updated: 2026-03-04 +status: active +--- + # AgentScope 迁移 Domain 执行清单(详细设计输入版,4人协同) > 更新日期:2026-03-02 diff --git a/docs/todos/archive/2026-02-27_design_code_gap_analysis.md b/docs/todos/archive/2026-02-27_design_code_gap_analysis.md index fd422159..85c9fbe5 100644 --- a/docs/todos/archive/2026-02-27_design_code_gap_analysis.md +++ b/docs/todos/archive/2026-02-27_design_code_gap_analysis.md @@ -1,3 +1,12 @@ +--- +change_ids: [] +doc_kind: analysis +topics: ["design-code-gap", "analysis", "governance"] +created: 2026-02-27 +updated: 2026-03-04 +status: archived +--- + # 设计文档与代码实现 Gap 分析(基线) > 日期:2026-02-27 diff --git a/docs/todos/archive/2026-02-27_design_code_gap_todo.md b/docs/todos/archive/2026-02-27_design_code_gap_todo.md index 06d0f5fa..3a2053ec 100644 --- a/docs/todos/archive/2026-02-27_design_code_gap_todo.md +++ b/docs/todos/archive/2026-02-27_design_code_gap_todo.md @@ -1,3 +1,12 @@ +--- +change_ids: [] +doc_kind: todo +topics: ["design-code-gap", "todo", "governance"] +created: 2026-02-27 +updated: 2026-03-04 +status: archived +--- + # 设计-实现 Gap 修复 TODO(由 2026-02-27 分析生成) > 来源分析:`docs/todos/archive/2026-02-27_design_code_gap_analysis.md` diff --git a/docs/todos/project_overall_todos.md b/docs/todos/project_overall_todos.md index 46307e1d..1c9597fb 100644 --- a/docs/todos/project_overall_todos.md +++ b/docs/todos/project_overall_todos.md @@ -1,3 +1,12 @@ +--- +change_ids: [] +doc_kind: todo +topics: ["project-roadmap", "governance", "todo"] +created: 2026-02-25 +updated: 2026-03-04 +status: active +--- + # DARE Framework 项目总体 TODO > 更新时间:2026-03-02 diff --git a/openspec/changes/agentscope-d2-d4-thinking-transport/tasks.md b/openspec/changes/agentscope-d2-d4-thinking-transport/tasks.md index cc31c3d1..847754e3 100644 --- a/openspec/changes/agentscope-d2-d4-thinking-transport/tasks.md +++ b/openspec/changes/agentscope-d2-d4-thinking-transport/tasks.md @@ -1,3 +1,8 @@ +## TODO Coverage + +- D2-1~D2-4 +- D4-1~D4-4 + ## 1. Transport contract and schema alignment (D2) - [x] 1.1 Extend transport canonical event taxonomy to cover `message/tool_call/tool_result/thinking/error/status` with compatibility normalization for legacy aliases. diff --git a/openspec/changes/enhance-doc-governance-traceability/tasks.md b/openspec/changes/enhance-doc-governance-traceability/tasks.md index 85dd3b6f..c79bb444 100644 --- a/openspec/changes/enhance-doc-governance-traceability/tasks.md +++ b/openspec/changes/enhance-doc-governance-traceability/tasks.md @@ -28,9 +28,12 @@ - [x] 3.1 Implement or extend CI checks to validate aggregation entry existence when governance-scoped files change. Evidence: `scripts/ci/check_governance_traceability.sh`, `tests/unit/test_governance_traceability_gate.py`, and `.github/workflows/ci-gate.yml`. -- [ ] 3.2 Implement or extend CI checks to validate required frontmatter fields for governance-tracked docs. -- [ ] 3.3 Implement or extend CI checks to validate gap/TODO -> OpenSpec task mapping completeness. -- [ ] 3.4 Implement or extend CI checks to validate master TODO -> OpenSpec change-slice mapping consistency. +- [x] 3.2 Implement or extend CI checks to validate required frontmatter fields for governance-tracked docs. + Evidence: `scripts/ci/check_governance_traceability.sh` now validates required frontmatter on the governance baseline standard/design docs plus all concrete `docs/todos/**/*.md` records, and the corresponding docs now declare canonical frontmatter blocks. +- [x] 3.3 Implement or extend CI checks to validate gap/TODO -> OpenSpec task mapping completeness. + Evidence: `scripts/ci/check_governance_traceability.sh` now requires active feature docs with `todo_ids` to be covered by a `## TODO Coverage` section in their tasks artifact; `openspec/changes/agentscope-d2-d4-thinking-transport/tasks.md` provides the pilot coverage mapping and `tests/unit/test_governance_traceability_gate.py` locks the contract. +- [x] 3.4 Implement or extend CI checks to validate master TODO -> OpenSpec change-slice mapping consistency. + Evidence: `scripts/ci/check_governance_traceability.sh` now resolves Claim Ledger / slice-plan change targets back to concrete feature docs and tasks artifacts, while skipping `planned` placeholder claims; `tests/unit/test_governance_traceability_gate.py` covers stale slice target failures. - [x] 3.5 Implement evidence truth structural gate (`scripts/ci/check_governance_evidence_truth.sh`) and wire it into `ci-gate`. Evidence: `scripts/ci/check_governance_evidence_truth.sh`, `tests/unit/test_governance_evidence_truth_gate.py`, and `.github/workflows/ci-gate.yml`. diff --git a/scripts/ci/check_governance_traceability.sh b/scripts/ci/check_governance_traceability.sh index 6da5b8c6..aa8a2d4a 100755 --- a/scripts/ci/check_governance_traceability.sh +++ b/scripts/ci/check_governance_traceability.sh @@ -166,6 +166,17 @@ extract_markdown_section_matching() { ' "$file" } +extract_first_markdown_table_from_section_matching() { + local file="$1" + local pattern="$2" + awk -v pattern="$pattern" ' + $0 ~ /^##[[:space:]]+/ && $0 ~ pattern {in_section=1; next} + in_section && /^##[[:space:]]+/ {exit} + in_section && $0 ~ /^\|/ {in_table=1; print; next} + in_table {exit} + ' "$file" +} + escape_extended_regex() { printf '%s' "$1" | sed -E 's/[][(){}.^$*+?|\\/]/\\&/g' } @@ -275,7 +286,7 @@ claim_ledger_has_tokens_in_same_record() { if [[ -n "$scope_field" ]] && scope_contains_todo_id "$scope_field" "$first_token"; then return 0 fi - done < <(extract_markdown_section_matching "$file" "Claim Ledger") + done < <(extract_first_markdown_table_from_section_matching "$file" "Claim Ledger") return 1 } @@ -380,10 +391,173 @@ check_checkpoint_skill_mapping() { fi } +check_governance_doc_frontmatter() { + local file="$1" + local expected_doc_kind="$2" + local frontmatter key actual_doc_kind mode + + frontmatter="$(extract_frontmatter "$file")" + if [[ -z "$frontmatter" ]]; then + log "missing frontmatter in $file" + failures=$((failures + 1)) + return + fi + + for key in change_ids doc_kind topics created updated status; do + if [[ -z "$(frontmatter_scalar "$frontmatter" "$key")" ]]; then + log "missing required frontmatter field '$key' in $file" + failures=$((failures + 1)) + fi + done + + actual_doc_kind="$(trim_quotes "$(frontmatter_scalar "$frontmatter" "doc_kind")")" + if [[ -n "$expected_doc_kind" && "$actual_doc_kind" != "$expected_doc_kind" ]]; then + log "unexpected doc_kind in $file: expected $expected_doc_kind" + failures=$((failures + 1)) + fi + + mode="$(trim_quotes "$(frontmatter_scalar "$frontmatter" "mode")")" + if [[ "$mode" == "todo_fallback" && -z "$(frontmatter_scalar "$frontmatter" "topic_slug")" ]]; then + log "missing required frontmatter field 'topic_slug' in $file" + failures=$((failures + 1)) + fi +} + +check_governance_tracked_frontmatter() { + local file expected_doc_kind + + for file in \ + docs/governance/Documentation_Management_Model.md \ + docs/guides/Documentation_First_Development_SOP.md \ + docs/guides/Development_Constraints.md \ + docs/guides/Evidence_Truth_Implementation_Strategy.md; do + [[ -f "$file" ]] && check_governance_doc_frontmatter "$file" "standard" + done + + if [[ -f "docs/design/Design_Reconstructability_Traceability_Matrix.md" ]]; then + check_governance_doc_frontmatter "docs/design/Design_Reconstructability_Traceability_Matrix.md" "design" + fi + + while IFS= read -r file; do + expected_doc_kind="todo" + [[ "$file" == *"_gap_analysis.md" ]] && expected_doc_kind="analysis" + check_governance_doc_frontmatter "$file" "$expected_doc_kind" + done < <(find docs/todos -type f -name '*.md' ! -name 'README.md' ! -path 'docs/todos/templates/*' | sort) +} + +normalize_table_cell() { + local value="$1" + value="${value//\`/}" + trim_whitespace "$value" +} + +is_placeholder_change_id() { + local compact="${1//[[:space:]]/}" + case "$compact" in + ""|"N/A"|"n/a"|"pending"|"---"|"OpenSpecChange"|"PlannedOpenSpecChange"|"建议OpenSpecChange") + return 0 + ;; + esac + return 1 +} + +resolve_change_tasks_file() { + local change_id="$1" + if [[ -f "openspec/changes/$change_id/tasks.md" ]]; then + echo "openspec/changes/$change_id/tasks.md" + return 0 + fi + + if [[ ! -d "openspec/changes/archive" ]]; then + return 1 + fi + + find openspec/changes/archive -type f \ + \( -path "*/$change_id/tasks.md" -o -path "*/????-??-??-$change_id/tasks.md" \) \ + | head -n 1 +} + +resolve_feature_doc_file() { + local change_id="$1" + if [[ -f "docs/features/$change_id.md" ]]; then + echo "docs/features/$change_id.md" + return 0 + fi + if [[ -f "docs/features/archive/$change_id.md" ]]; then + echo "docs/features/archive/$change_id.md" + return 0 + fi + return 1 +} + +tasks_file_has_todo_coverage() { + local file="$1" + local todo_id="$2" + local line candidate + + while IFS= read -r line; do + candidate="$(sed -E 's/^[[:space:]]*[-|][[:space:]]*//; s/`//g' <<<"$line")" + candidate="$(trim_whitespace "$candidate")" + [[ -z "$candidate" ]] && continue + if text_has_discrete_token "$candidate" "$todo_id"; then + return 0 + fi + if scope_contains_todo_id "$candidate" "$todo_id"; then + return 0 + fi + done < <(extract_markdown_section_matching "$file" "TODO Coverage") + + return 1 +} + +check_todo_change_mapping_target() { + local source_file="$1" + local change_id="$2" + + if is_placeholder_change_id "$change_id"; then + return + fi + + if [[ -z "$(resolve_feature_doc_file "$change_id" || true)" ]]; then + log "missing feature aggregation doc for TODO change mapping in $source_file: $change_id" + failures=$((failures + 1)) + fi + + if [[ -z "$(resolve_change_tasks_file "$change_id" || true)" ]]; then + log "missing OpenSpec tasks artifact for TODO change mapping in $source_file: $change_id" + failures=$((failures + 1)) + fi +} + +check_master_todo_change_slice_mappings() { + local file line change_id status_value + + while IFS= read -r file; do + while IFS= read -r line; do + [[ "$line" == \|* ]] || continue + status_value="$(awk -F'|' 'NF >= 6 {field=$5; gsub(/^[[:space:]]+|[[:space:]]+$/, "", field); print field}' <<<"$line")" + status_value="$(normalize_table_cell "$status_value")" + if [[ "$status_value" == "planned" ]]; then + continue + fi + change_id="$(awk -F'|' 'NF >= 9 {field=$8; gsub(/^[[:space:]]+|[[:space:]]+$/, "", field); print field}' <<<"$line")" + change_id="$(normalize_table_cell "$change_id")" + check_todo_change_mapping_target "$file" "$change_id" + done < <(extract_first_markdown_table_from_section_matching "$file" "Claim Ledger") + + while IFS= read -r line; do + [[ "$line" == \|* ]] || continue + change_id="$(awk -F'|' 'NF >= 5 {field=$4; gsub(/^[[:space:]]+|[[:space:]]+$/, "", field); print field}' <<<"$line")" + change_id="$(normalize_table_cell "$change_id")" + check_todo_change_mapping_target "$file" "$change_id" + done < <(extract_first_markdown_table_from_section_matching "$file" "切片规划|Slice Plan") + done < <(find docs/todos -type f -name '*.md' ! -name 'README.md' ! -path 'docs/todos/templates/*' | sort) +} + check_feature_doc() { local file="$1" local frontmatter mode doc_kind key change_id todo_id matched candidate - local change_ids_raw todo_ids_raw + local change_ids_raw todo_ids_raw tasks_file task_checked task_covered frontmatter="$(extract_frontmatter "$file")" if [[ -z "$frontmatter" ]]; then @@ -423,8 +597,7 @@ check_feature_doc() { while IFS= read -r change_id; do [[ -z "$change_id" ]] && continue - if [[ ! -f "openspec/changes/$change_id/tasks.md" ]] && \ - ! find openspec/changes/archive -type f \( -path "*/$change_id/tasks.md" -o -path "*/????-??-??-$change_id/tasks.md" \) | grep -q .; then + if [[ -z "$(resolve_change_tasks_file "$change_id" || true)" ]]; then log "missing OpenSpec tasks artifact for change_id '$change_id' declared in $file" failures=$((failures + 1)) fi @@ -456,11 +629,31 @@ check_feature_doc() { log "missing TODO mapping for feature doc $file: $todo_id" failures=$((failures + 1)) fi + + task_checked=0 + task_covered=0 + while IFS= read -r change_id; do + [[ -z "$change_id" ]] && continue + tasks_file="$(resolve_change_tasks_file "$change_id" || true)" + [[ -z "$tasks_file" ]] && continue + task_checked=1 + if tasks_file_has_todo_coverage "$tasks_file" "$todo_id"; then + task_covered=1 + break + fi + done <<<"$change_ids_raw" + + if [[ "$task_checked" -eq 1 && "$task_covered" -eq 0 ]]; then + log "missing TODO coverage in tasks artifact for feature doc $file: $todo_id" + failures=$((failures + 1)) + fi done <<<"$todo_ids_raw" } check_feature_indexes check_checkpoint_skill_mapping +check_governance_tracked_frontmatter +check_master_todo_change_slice_mappings while IFS= read -r file; do check_feature_doc "$file" diff --git a/tests/unit/test_governance_traceability_gate.py b/tests/unit/test_governance_traceability_gate.py index 410abff9..9b0586c9 100644 --- a/tests/unit/test_governance_traceability_gate.py +++ b/tests/unit/test_governance_traceability_gate.py @@ -51,6 +51,26 @@ - Review thread: https://github.com/example/repo/pull/2#discussion_r1 """ +TODO_DOC_FRONTMATTER = """--- +change_ids: [] +doc_kind: todo +topics: ["governance", "todo"] +created: 2026-03-03 +updated: 2026-03-03 +status: active +--- +""" + +STANDARD_DOC_FRONTMATTER = """--- +change_ids: ["demo-change"] +doc_kind: standard +topics: ["governance", "traceability"] +created: 2026-03-03 +updated: 2026-03-03 +status: active +--- +""" + def _write(path: Path, content: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) @@ -107,20 +127,50 @@ def _base_tree(root: Path) -> None: _write(root / "docs" / "features" / "demo-change.md", FEATURE_DOC) _write( root / "docs" / "todos" / "demo_master_todo.md", - """# Demo TODO + """--- +change_ids: [] +doc_kind: todo +topics: ["governance", "todo"] +created: 2026-03-03 +updated: 2026-03-03 +status: active +--- + +# Demo TODO ## Claim Ledger | Claim ID | TODO Scope | Owner | Status | Declared At | Expires At | OpenSpec Change | Notes | |---|---|---|---|---|---|---|---| | CLM-DEMO | D2-1~D2-2 | demo | active | 2026-03-03 | 2026-03-10 | `demo-change` | demo | + +## 切片规划 +| Slice | 目标 | 建议 OpenSpec Change | 主要覆盖 TODO | +|---|---|---|---| +| Slice A | demo | `demo-change` | D2-1, D2-2 | """, ) _write(root / "openspec" / "changes" / "demo-change" / "proposal.md", "# proposal\n") _write(root / "openspec" / "changes" / "demo-change" / "design.md", "# design\n") - _write(root / "openspec" / "changes" / "demo-change" / "tasks.md", "- [ ] demo\n") + _write( + root / "openspec" / "changes" / "demo-change" / "tasks.md", + """## TODO Coverage +- D2-1~D2-2 + +- [ ] demo +""", + ) _write( root / "docs" / "governance" / "Documentation_Management_Model.md", - """# Documentation Management Model + """--- +change_ids: ["demo-change"] +doc_kind: standard +topics: ["governance", "traceability"] +created: 2026-03-03 +updated: 2026-03-03 +status: active +--- + +# Documentation Management Model ## 7. Checkpoint-to-Skill Mapping - kickoff -> `development-workflow` + `documentation-management` @@ -128,6 +178,62 @@ def _base_tree(root: Path) -> None: - verification -> `development-workflow` - review-merge-gate -> `development-workflow` + `documentation-management` - completion-archive -> `development-workflow` + `documentation-management` +""", + ) + _write( + root / "docs" / "guides" / "Documentation_First_Development_SOP.md", + """--- +change_ids: ["demo-change"] +doc_kind: standard +topics: ["governance", "sop"] +created: 2026-03-03 +updated: 2026-03-03 +status: active +--- + +# Demo SOP +""", + ) + _write( + root / "docs" / "guides" / "Development_Constraints.md", + """--- +change_ids: ["demo-change"] +doc_kind: standard +topics: ["governance", "constraints"] +created: 2026-03-03 +updated: 2026-03-03 +status: active +--- + +# Demo Constraints +""", + ) + _write( + root / "docs" / "guides" / "Evidence_Truth_Implementation_Strategy.md", + """--- +change_ids: ["demo-change"] +doc_kind: standard +topics: ["governance", "evidence"] +created: 2026-03-03 +updated: 2026-03-03 +status: active +--- + +# Demo Evidence Truth +""", + ) + _write( + root / "docs" / "design" / "Design_Reconstructability_Traceability_Matrix.md", + """--- +change_ids: ["demo-change"] +doc_kind: design +topics: ["governance", "traceability"] +created: 2026-03-03 +updated: 2026-03-03 +status: active +--- + +# Demo Matrix """, ) _write(root / ".codex" / "skills" / "documentation-management" / "SKILL.md", "# doc skill\n") @@ -261,10 +367,23 @@ def mutate(root: Path) -> None: self.assertNotEqual(result.returncode, 0) self.assertIn("invalid archived feature index entry path", result.stdout) + def test_gate_requires_frontmatter_for_governance_standard_and_todo_docs(self) -> None: + def mutate(root: Path) -> None: + todo_doc = root / "docs" / "todos" / "demo_master_todo.md" + todo_doc.write_text(todo_doc.read_text(encoding="utf-8").split("---\n", 2)[2], encoding="utf-8") + sop_doc = root / "docs" / "guides" / "Documentation_First_Development_SOP.md" + sop_doc.write_text(sop_doc.read_text(encoding="utf-8").split("---\n", 2)[2], encoding="utf-8") + + result = self._run_gate(mutate) + + self.assertNotEqual(result.returncode, 0) + self.assertIn("missing frontmatter in docs/todos/demo_master_todo.md", result.stdout) + self.assertIn("missing frontmatter in docs/guides/Documentation_First_Development_SOP.md", result.stdout) + def test_gate_fails_when_checkpoint_skill_mapping_is_missing(self) -> None: def mutate(root: Path) -> None: model = root / "docs" / "governance" / "Documentation_Management_Model.md" - model.write_text("# Documentation Management Model\n", encoding="utf-8") + model.write_text(f"{STANDARD_DOC_FRONTMATTER}\n# Documentation Management Model\n", encoding="utf-8") result = self._run_gate(mutate) @@ -275,7 +394,8 @@ def test_gate_requires_all_declared_lifecycle_checkpoints(self) -> None: def mutate(root: Path) -> None: model = root / "docs" / "governance" / "Documentation_Management_Model.md" model.write_text( - """# Documentation Management Model + f"""{STANDARD_DOC_FRONTMATTER} +# Documentation Management Model ## 7. Checkpoint-to-Skill Mapping - kickoff -> `development-workflow` + `documentation-management` @@ -295,7 +415,8 @@ def test_gate_requires_explicit_checkpoint_to_skill_pairs(self) -> None: def mutate(root: Path) -> None: model = root / "docs" / "governance" / "Documentation_Management_Model.md" model.write_text( - """# Documentation Management Model + f"""{STANDARD_DOC_FRONTMATTER} +# Documentation Management Model ## 7. Checkpoint-to-Skill Mapping @@ -320,7 +441,10 @@ def mutate(root: Path) -> None: def test_gate_fails_when_declared_todo_id_has_no_matching_todo_ledger(self) -> None: def mutate(root: Path) -> None: - (root / "docs" / "todos" / "demo_master_todo.md").write_text("# empty\n", encoding="utf-8") + (root / "docs" / "todos" / "demo_master_todo.md").write_text( + f"{TODO_DOC_FRONTMATTER}\n# empty\n", + encoding="utf-8", + ) result = self._run_gate(mutate) @@ -336,7 +460,8 @@ def mutate(root: Path) -> None: ) todo_doc = root / "docs" / "todos" / "demo_master_todo.md" todo_doc.write_text( - """# Demo TODO + f"""{TODO_DOC_FRONTMATTER} +# Demo TODO ## Claim Ledger | Claim ID | TODO Scope | Owner | Status | Declared At | Expires At | OpenSpec Change | Notes | @@ -360,7 +485,8 @@ def mutate(root: Path) -> None: ) todo_doc = root / "docs" / "todos" / "demo_master_todo.md" todo_doc.write_text( - """# Demo TODO + f"""{TODO_DOC_FRONTMATTER} +# Demo TODO ## Claim Ledger | Claim ID | TODO Scope | Owner | Status | Declared At | Expires At | OpenSpec Change | Notes | @@ -385,7 +511,8 @@ def mutate(root: Path) -> None: ) todo_doc = root / "docs" / "todos" / "demo_master_todo.md" todo_doc.write_text( - """# Demo TODO + f"""{TODO_DOC_FRONTMATTER} +# Demo TODO ## Claim Ledger | Claim ID | TODO Scope | Owner | Status | Declared At | Expires At | OpenSpec Change | Notes | @@ -423,7 +550,7 @@ def mutate(root: Path) -> None: active_change_dir.rmdir() _write( root / "openspec" / "changes" / "archive" / "2026-03-03-archived-change" / "tasks.md", - "- [x] archived\n", + "## TODO Coverage\n- D2-1~D2-2\n\n- [x] archived\n", ) result = self._run_gate(mutate) @@ -435,7 +562,8 @@ def test_gate_accepts_todo_ids_covered_by_claim_scope_for_same_change(self) -> N def mutate(root: Path) -> None: todo_doc = root / "docs" / "todos" / "demo_master_todo.md" todo_doc.write_text( - """# Demo TODO + f"""{TODO_DOC_FRONTMATTER} +# Demo TODO ## Claim Ledger | Claim ID | TODO Scope | Owner | Status | Declared At | Expires At | OpenSpec Change | Notes | @@ -451,6 +579,11 @@ def mutate(root: Path) -> None: """, encoding="utf-8", ) + tasks_doc = root / "openspec" / "changes" / "demo-change" / "tasks.md" + tasks_doc.write_text( + "## TODO Coverage\n- D2-1~D2-4\n- D4-1~D4-4\n\n- [ ] demo\n", + encoding="utf-8", + ) feature_doc = root / "docs" / "features" / "demo-change.md" feature_doc.write_text( feature_doc.read_text(encoding="utf-8").replace( @@ -469,7 +602,8 @@ def test_gate_accepts_claim_scope_range_without_explicit_todo_token(self) -> Non def mutate(root: Path) -> None: todo_doc = root / "docs" / "todos" / "demo_master_todo.md" todo_doc.write_text( - """# Demo TODO + f"""{TODO_DOC_FRONTMATTER} +# Demo TODO ## Claim Ledger | Claim ID | TODO Scope | Owner | Status | Declared At | Expires At | OpenSpec Change | Notes | @@ -484,6 +618,11 @@ def mutate(root: Path) -> None: """, encoding="utf-8", ) + tasks_doc = root / "openspec" / "changes" / "demo-change" / "tasks.md" + tasks_doc.write_text( + "## TODO Coverage\n- D2-1~D2-4\n- D4-1~D4-4\n\n- [ ] demo\n", + encoding="utf-8", + ) feature_doc = root / "docs" / "features" / "demo-change.md" feature_doc.write_text( feature_doc.read_text(encoding="utf-8").replace( @@ -498,6 +637,33 @@ def mutate(root: Path) -> None: self.assertEqual(result.returncode, 0) self.assertIn("passed", result.stdout) + def test_gate_requires_tasks_todo_coverage_for_active_feature_todo_ids(self) -> None: + def mutate(root: Path) -> None: + tasks_doc = root / "openspec" / "changes" / "demo-change" / "tasks.md" + tasks_doc.write_text("- [ ] demo\n", encoding="utf-8") + + result = self._run_gate(mutate) + + self.assertNotEqual(result.returncode, 0) + self.assertIn("missing TODO coverage in tasks artifact", result.stdout) + + def test_gate_requires_master_todo_slice_mapping_targets_to_exist(self) -> None: + def mutate(root: Path) -> None: + todo_doc = root / "docs" / "todos" / "demo_master_todo.md" + todo_doc.write_text( + todo_doc.read_text(encoding="utf-8").replace( + "| Slice A | demo | `demo-change` | D2-1, D2-2 |\n", + "| Slice A | demo | `demo-change` | D2-1, D2-2 |\n" + "| Slice B | drift | `missing-change` | D9-9 |\n", + ), + encoding="utf-8", + ) + + result = self._run_gate(mutate) + + self.assertNotEqual(result.returncode, 0) + self.assertIn("missing feature aggregation doc for TODO change mapping", result.stdout) + if __name__ == "__main__": unittest.main()