diff --git a/.github/workflows/cross-platform-checkers.yml b/.github/workflows/cross-platform-checkers.yml new file mode 100644 index 0000000..83d183f --- /dev/null +++ b/.github/workflows/cross-platform-checkers.yml @@ -0,0 +1,56 @@ +name: Cross-platform Python checkers + +on: + pull_request: + push: + workflow_dispatch: + +permissions: + contents: read + +jobs: + checker-contract: + name: ${{ matrix.os }} / Python ${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - macos-latest + - windows-latest + python-version: + - "3.10" + - "3.x" + + steps: + - name: Check out repository + uses: actions/checkout@v5 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Compile canonical scripts and tests + run: python -m compileall -q scripts tests + + - name: Run native checker contract + run: >- + python -m unittest + tests.test_python_checker_contract + tests.test_root_agents_blocks + tests.test_onboarding_core_flow_coverage + tests.test_concept_foundation_trace + tests.test_adr_requirement_model_trace + tests.test_feature_archive_support + tests.test_feature_monthly_archive_scan + tests.test_feature_monthly_archive_apply + tests.test_feature_monthly_archive_restore + -v + + - name: Check Feature Monthly Archive command entrypoints + run: | + python scripts/scan-feature-monthly-archive.py --help + python scripts/check-feature-monthly-archive.py --help + python scripts/apply-feature-monthly-archive.py --help + python scripts/restore-feature-monthly-archive.py --help diff --git a/AGENTS.md b/AGENTS.md index 527d315..c6e3466 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -103,7 +103,7 @@ feat, fix, docs, refactor, test, chore Rules: - Prefer Chinese for the summary and body unless the project context requires English. -- Include the current skill version scope, for example `docs(v1.2.4): 调整 Project Entry Scan 文档结构`. +- Include the current skill version scope, for example `docs(v1.3.0): 调整 Project Entry Scan 文档结构`. - Do not use one-line-only commit messages for meaningful behavior, gate, artifact, template, reference, validation, or example changes. - Use 3-7 bullet lines in the commit body, focused on concrete changes and user/agent-facing behavior. - Use `docs` for proposals, README, Usage, and explanatory docs. diff --git a/CHANGELOG.md b/CHANGELOG.md index 7da0630..7a9af35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,61 @@ # Agent Loop Changelog -## Unreleased +## 1.3.0 — 2026-07-11 + +### Version Baseline +- Started the 1.3.0 alpha development line from the refreshed `stable-v1.2.4` baseline. +- Updated version-bearing skill metadata and human-facing version labels to 1.3.0. +- Updated the root AGENTS managed-block revision to `block-version:1.3.0-20260711` for the first 1.3.0 template revision. +- Updated version-sync, root-guidance, and human-help regression expectations for the 1.3.0 development line. + +### Core Flow Completeness +- Added Core Flow Inventory and Flow Slice Coverage so critical/important onboarding flows close across callbacks, consumers, retries, compensation, reconciliation, jobs, and business terminal states. +- Made timeline/sequence the primary per-flow narrative, kept overview/boundary and ASCII state-machine views complementary, and triggered lineage, transaction, async, decision, runtime, and troubleshooting diagrams only when their semantics apply. +- Added a Completeness Hard Gate before quality scoring, aligned coverage and batch review dimensions, and prevented missing critical slices from being averaged into `newcomer-ready`. +- Kept exactly two onboarding Human Gates and removed the default pressure to invent state diagrams for stateless content topics. + +### Concept Foundation And Requirement Product Model +- Implemented trigger-based Concept Foundation inside Requirements Discussion / Requirement Product Grill without adding a canonical stage or top-level artifact. +- Added the Human Grill Contract: evidence inspection, Concept Candidate Inventory, one recommended definition with accept/reject impact, then exactly one downstream-blocking human question. +- Added `candidate | accepted | reopened | concept-foundation-not-needed` routing and a hard stop before business-flow, state, and product-data modeling while blocking product meaning remains unresolved. +- Added requirement-local Concept IDs, definitions, relationships, role/permission, commands/events, product state, Requirement Product Model, exception/recovery, and Concept-To-Product Traceability templates. +- Made Product Brief and Feature Spec consume accepted Concept/Model references rather than redefine product semantics; clarified that ADR consumes accepted PRD meaning and kept Concept-to-technical mapping out of Phase 1/2. +- Hardened Concept Foundation validation against unconfirmed concepts, missing candidate inventory, unresolved blockers, placeholder not-needed reasons, duplicate IDs, incomplete trace coverage, and unauthorized command actors. +- Added cumulative Concept Foundation Human Review Summary and append-only post-archive reopen handling through the requirement README `Effective Concept Foundation` pointer. +- Added focused regression, behavioral artifact validation, pressure scenarios, examples, and a full-validation report for Phase 1/2 while leaving Design Skill, E2E Skill, Jam Kits, and executable schemas out of scope. + +### ADR Requirement Model Technical Landing +- Added Effective Requirement Snapshot resolution so requirement-driven ADRs freeze the current effective source, accepted Concept/Model IDs, compatibility judgment, and last check without copying product definitions. +- Added a source-wide Requirement Model Scope Inventory covering stable relationship, permission, command/event, flow, state, product-model, and exception IDs so coherent ADR scope cannot silently omit upstream models. +- Added the Requirement Model Technical Landing Trace with explicit `landed | covered-by-accepted-decision | feature-local | not-applicable` dispositions and hard coverage before ADR acceptance or dependent Feature Spec work. +- Split validation into `proposed` structural preflight and post-human-review `accepted` validation with recorded Human Review Evidence; added a reasoned trace-not-applicable branch for `concept-foundation-not-needed` sources. +- Added `Upstream Compatibility: review-required` as a blocking dependency judgment, not an ADR lifecycle status, and required superseding ADRs when accepted technical decisions no longer hold. +- Added a Decision & Design Human Review Summary for source, coverage counts, preserved semantics, operational triggers, Design Slice ownership, verification, and explicit acceptance/revision routing. +- Made Migration / Backfill, Compatibility, Rollout / Cutover, and Rollback / Reversibility operational landing conditional on concrete triggers instead of default empty sections. +- Added a domain-neutral trace validator, adversarial bypass regression, valid/invalid fixtures, focused regression, pressure scenarios, root-guidance coordination, and full-validation evidence without changing the skill version or stage model. + +### Cross-Platform Python Script Runtime +- Replaced the four checker implementations with canonical Python 3.10+ standard-library entrypoints: `scripts/check-root-agents-blocks.py`, `scripts/check-onboarding-core-flow-coverage.py`, `scripts/check-concept-foundation-trace.py`, and `scripts/check-adr-requirement-model-trace.py`. +- Added 36 native `unittest` cases for valid, invalid, adversarial, BOM/CRLF, usage/exit-code, standard-library-only, runtime guard, compatibility-launcher, current-authority, read-only, determinism, and workspace-confinement behavior so the same suite can run on macOS and Windows. +- Kept the former `.sh` / `.rb` paths as one-cycle compatibility launchers only; active tests and current guidance now call the canonical Python entrypoints directly. +- Added fail-closed runtime guidance and native macOS/Windows invocation examples; no third-party Python package, shell emulation layer, or automatic runtime installation is required. + +### Feature Monthly Archive +- Implemented directory-only monthly archival: each eligible, Human-confirmed closed feature directory moves intact into `features/YYYY-MM/`, without per-feature summaries, `historical/`, deletion, packing, or content compression. +- Added `features/archive.md` as the stable Feature ID-to-current-path locator while keeping feature artifacts, requirement sources, and accepted decisions authoritative. +- Added Python 3.10+ standard-library scan, check, apply, and restore commands with deterministic plan hashing, required `expected-plan-sha256`, conservative reference blocking, transaction journals, post-check, and verified failure recovery. +- Made ADR ownership, Feature Follow-up, recovery, templates, root guidance, and Stage Map resolve both flat and month-archived closed features while active, blocked, and paused features remain flat. +- Added a separate Human-gated rehydrate path before archived work can flow back; rehydrate moves the directory flat but does not change the closed lifecycle itself. +- Added focused archive/restore fixtures and macOS/Windows CI definitions for eligibility, no-mutation, stale plans, exact reference edits, idempotency, interrupted recovery, and locator consistency; current local evidence is `macOS-verified / Windows-test-defined`. +- Hardened recovery against self-consistent journal scope tampering, corrupt backups, and post-crash human edits; stranded transactions now block new scan/apply work, and inbound relative links into moving features are included in the exact reference plan. + +### Pre-Release Validation +- Revalidated the complete `stable-v1.2.4..v1.3.0` behavior set across all six semantic audit domains, the full Python/shell suite, pressure scenarios, version synchronization, and mechanical checks. +- Recorded successful Windows/macOS × Python 3.10/3.x GitHub Actions evidence for commit `7253461` instead of treating the configured matrix as execution proof. +- Unified all five v1.3.0 capability proposals at the Release Human Gate and added a durable release-evidence contract so proposal, implementation, platform, report, and authorization status cannot silently drift backward. +- Recorded Human approval for the v1.3.0 Release Gate and bound `stable-v1.3.0` creation to successful Windows/macOS CI for the exact release-evidence commit. + +## 1.2.4 — 2026-07-11 ### Project-Local Skills - Added Project Skill Creation / Update for durable target-project capabilities under `.agent-loop/skills//`, with INDEX-based lifecycle, `bootstrap` / `on-demand` loading, and target-path ownership that never defaults to global skill directories. @@ -14,8 +69,6 @@ - Added a feature-scoped contract test and a Project-Local Skills scoring report without making full-repository tests part of the feature score. - Kept single-feature scoring in maintainer guidance only; it does not replace mandatory full validation for control-surface changes. -## 1.2.4 — 2026-07-11 - ### Version Baseline - Started the 1.2.4 development line from the current alpha branch so new behavior changes are recorded under 1.2.4 instead of the closed 1.2.3 section. - Updated version-bearing skill metadata, human-facing docs, and root AGENTS template metadata to use 1.2.4 as the active skill version. diff --git a/README.md b/README.md index de70432..9a7ed7a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Agent Loop -**Current version:** 1.2.4 +**Current version:** 1.3.0 A reusable [Codex](https://github.com/openai/codex) / CLI-agent skill for single-person software development workflows—from goal intake to verified close. @@ -61,6 +61,8 @@ Message Intent → Chat And Requirements Discussion if needed | **Project-Local Skill** | A reusable project capability under `.agent-loop/skills//`. Creation or material update requires Gate 1; successful validation activates it, but every actual invocation still requires a bounded Execution Gate. | | **Requirement Lifecycle / Backlog** | Requirement memory for proposed, accepted, deferred, in-progress, partially implemented, implemented, superseded, rejected, or reference-only requirements without using project memory as a backlog. | | **Chat And Requirements Discussion** | `chat` answers or discusses without creating artifacts; `requirements-discussion → Brainstorm / Clarify → requirement document → requirements/` before any feature construction. | +| **Concept Foundation** | Triggered internal Requirements Discussion / Requirement Product Grill method. It stabilizes requirement-local Concept IDs and product meaning before flow, state, and product-data modeling; it is not a canonical stage or top-level artifact. | +| **Requirement Product Model** | Product-layer relationships, roles/permissions, commands/events, business flow, state, product objects/facts, invariants, and recovery derived from accepted concepts in the human-reviewed requirement document. | | **Decision & Design / ADR** | Requirement-landing bridge for shared business flow, domain/data rules, architecture, recovery, and non-functional goals. Design Readiness is required; `.agent-loop/decisions/*.md` is Human-gated and conditionally required only when shared design needs a durable record. | | **Delivery Contract** | Optional producer-consumer boundary handoff. Used only when API, event, public data, UI state/behavior, SDK/library, runtime, or explicit cross-agent/human handoff needs a stable contract. | @@ -93,6 +95,9 @@ Message Intent → Chat And Requirements Discussion if needed feedback.* # optional source file when provided notes.* # optional source file when provided features/ + archive.md # locator for archived/rehydrated Feature IDs; not product authority + YYYY-MM/ # Human-gated directory archive for eligible closed features + YYYY-MM-DD-/ # complete feature directory moved intact YYYY-MM-DD-/ product.md (optional) spec.md @@ -108,6 +113,8 @@ Message Intent → Chat And Requirements Discussion if needed contracts/ (optional contract details) ``` +Feature Monthly Archive keeps current work flat and moves only eligible, human-confirmed closed feature directories into their matching month bucket. It is location/index compaction, not content compression or deletion: the feature directory remains intact, while `features/archive.md` records the stable Feature ID and current path. Archive and rehydrate each require a read-only deterministic plan, the exact reviewed SHA-256, a separate Human Gate, transaction recovery, and post-check. + New target projects use `.agent-loop/` by default. Existing visible `agent-loop/` roots remain readable as legacy memory and should be migrated only after human confirmation. ## Quick Start @@ -148,7 +155,11 @@ For existing projects, the agent separates safe-entry memory from newcomer learn > "先帮我梳理这个需求,不要实现。" -For requirement shaping, the agent should enter Requirements Discussion instead of creating a feature workspace. Requirement/Product Grill is the clarification method for fuzzy terminology, business flows, exception paths, prior feature conflicts, source-of-truth questions, and decision signals. It asks one blocking question at a time, includes a recommended answer, and checks relevant project memory, source requirements, docs, code, tests, and prior feature artifacts before asking when those sources may already answer the question. +For requirement shaping, the agent should enter Requirements Discussion instead of creating a feature workspace. Requirement/Product Grill is the clarification method for fuzzy terminology, business flows, exception paths, prior feature conflicts, source-of-truth questions, and decision signals. + +When a complex requirement can change concept identity, lifecycle, relationship, state, ownership, terminal meaning, or product facts, the internal Concept Foundation method triggers. The Agent first checks project/domain evidence, extracts a Concept Candidate Inventory, presents one recommended definition with evidence and accept/reject impact, then asks exactly one blocking question. Until the human confirms the blocking concepts, detailed business flow, product state, and product data modeling stop. + +After acceptance, the effective requirement source derives a Requirement Product Model and traceability from stable Concept IDs. Feature `product.md` and `spec.md` record `Effective Concept Source` and reference those accepted IDs/model rows instead of redefining product semantics. If accepted meaning changes after archive, Agent Loop preserves the old source, reopens the gate, writes a human-confirmed append-only follow-up or new requirement set, and advances the requirement README pointer. Simple copy/style/config changes stay lightweight with a concrete `concept-foundation-not-needed` reason. Concept Foundation does not add a canonical stage, ADR, Design Skill, E2E Skill, or executable schema. Reviewed requirements live under `.agent-loop/requirements/-/`. If the human later asks to "落到 product.md" from chat or requirements discussion, Product Brief Source Gate applies: the agent first asks whether to create/reference a requirement set or confirm feature start. Feature-level `product.md` is written only after there is a requirement source and confirmed feature context. @@ -162,6 +173,12 @@ Requirement -> Design Readiness Check -> Decision & Design If Needed -> Feature Simple work records `design-not-needed` and can continue without a decision file. Feature-local choices stay in `spec.md` Design Decisions. Review and completion verify that implementation conforms to accepted design slices rather than checking feature stories alone. +For a requirement-driven ADR, the Agent first resolves an Effective Requirement Snapshot from the requirement README and accepted source. A source-wide Requirement Model Scope Inventory accounts for every stable relationship, permission, command/event, flow, state, product-model, and exception ID before the ADR selects its coherent scope. The ADR then gives every in-scope ID one Requirement Model Technical Landing Trace disposition. A `landed` row must name its concrete technical landing, preserved invariant, Design Slice, and verification target; incomplete inventory/coverage or `Upstream Compatibility: review-required` blocks ADR acceptance and dependent Feature Spec, Plan, and implementation work. + +The ADR remains `proposed` while structural preflight runs. A passing validator authorizes review, not acceptance. Only explicit Decision & Design human acceptance permits recorded Human Review Evidence and `Status: accepted`, followed by accepted-mode validation. A reasoned `concept-foundation-not-needed` input uses an explicit trace-not-applicable branch without fabricated models. The ADR remains a consumer of product semantics: ambiguity returns to Requirements Discussion, while an incompatible accepted technical decision is preserved and superseded after Human Review instead of being rewritten. + +Migration, compatibility, rollout, and rollback detail are operational landing concerns triggered only when the decision changes persistence representation, protocol/provider, runtime boundary, or rollout compatibility. Untriggered concerns keep one concrete reason and do not generate empty default design sections. This enhancement stays inside the existing ADR and adds no canonical stage, mapping artifact, lifecycle status, or executable schema. + ### 4. Start a Feature > "I want to add login." @@ -183,13 +200,14 @@ Evidence-Graph + DDD Onboarding is the current durable project-understanding flo It builds `.agent-loop/onboarding-db/` from verified code evidence into macro-to-micro handoff docs: - `08-review/evidence-graph.md` before formal docs -- `onboarding-spec.md` for module/flow coverage, DDD mapping, file strategy, quality gates, and batch plan +- Core Flow Inventory for business terminals, variants, owners, recovery responsibility, evidence chain, and planned/deferred selection +- `onboarding-spec.md` for module/flow coverage, Flow Slice Plan, DDD mapping, file strategy, quality gates, and batch plan - `onboarding-tasks.md` for accepted batch execution - module playbooks under `02-modules/.md` by default - flow playbooks under `03-flows/.md` by default - coverage matrix and reviewed batch records -The agent must first confirm Project Entry Scan or reliable project memory, then build an Evidence Graph and present an Onboarding Spec for human review. Spec acceptance authorizes creation of Onboarding Tasks; formal onboarding docs begin only after the completed Tasks and their separate Full Execution Gate are accepted. Module and flow docs default to single long files, not many small files. Wireframe architecture flow diagrams are the preferred main way to express every module and flow process. The old Quick / Deep / Targeted onboarding modes and directory-first legacy generation flow are not used. +The agent must first confirm Project Entry Scan or reliable project memory, then build an Evidence Graph with Core Flow Inventory and present an Onboarding Spec for human review. Spec acceptance authorizes creation of Onboarding Tasks; formal onboarding docs begin only after the completed Tasks and their separate Full Execution Gate are accepted. Critical/important flows use Flow Slice Coverage and a completeness gate before quality scoring. Timeline/sequence is the primary per-flow narrative, supported by overview/boundary and state-machine views; extra diagrams are triggered by real recovery, data, transaction, async, decision, runtime, or troubleshooting complexity. Stateless topics do not invent state diagrams. Module and flow docs default to single long files, not many small files. The old Quick / Deep / Targeted onboarding modes and directory-first legacy generation flow are not used. Existing legacy onboarding-db files may be read as evidence, but they are not trusted without checking code reality. Migration or replacement requires an accepted Onboarding Spec, Onboarding Tasks, and Full Execution Gate. diff --git a/SKILL.md b/SKILL.md index 27a8517..9464621 100644 --- a/SKILL.md +++ b/SKILL.md @@ -5,7 +5,7 @@ description: Use when starting, continuing, resuming, structuring, testing, impl # Agent Loop -Version: 1.2.4 +Version: 1.3.0 Run a single-human, CLI-agent development loop from goal intake to verified close. This skill is a controller: it decides the current stage, loads the right reference, produces or updates `agent-loop` artifacts, and stops at human gates. @@ -39,7 +39,7 @@ Also treat long-term memory indexes as claims that must be verified before relia ## Message Intent Guard -Before project-state classification, classify the latest human message intent. `chat` means ordinary discussion, rules questions, status questions, or design talk; answer or discuss only and do not create requirement sets or feature workspaces by default. Message intent is not permanent: if chat turns into demand shaping, `proposal-doc`, implementation, operational support, follow-up, deferred work, or project-skill management, reclassify and route accordingly. If the human explicitly wants discussion without documentation, keep `chat`. `requirements-discussion` means the human is shaping product needs, business goals, capability ideas, constraints, tradeoffs, or user scenarios without authorizing implementation; use Brainstorm / Clarify to produce a human-reviewed requirement document under `.agent-loop/requirements/` before feature construction. `project-skill-management` means the human asks to turn a repeatable project workflow into a project-local skill or to update, disable, or deprecate one; load `references/project-skills.md`. If unclear, ask whether the human wants ordinary discussion, requirements documentation, feature implementation, or project-skill management. +Before project-state classification, classify the latest human message intent. `chat` means ordinary discussion, rules questions, status questions, or design talk; answer or discuss only and do not create requirement sets or feature workspaces by default. Message intent is not permanent: if chat turns into demand shaping, `proposal-doc`, implementation, operational support, follow-up, deferred work, project-skill management, or explicit historical feature archive/rehydrate maintenance, reclassify and route accordingly. If the human explicitly wants discussion without documentation, keep `chat`. `requirements-discussion` means the human is shaping product needs, business goals, capability ideas, constraints, tradeoffs, or user scenarios without authorizing implementation; use Brainstorm / Clarify to produce a human-reviewed requirement document under `.agent-loop/requirements/` before feature construction. `project-skill-management` means the human asks to turn a repeatable project workflow into a project-local skill or to update, disable, or deprecate one; load `references/project-skills.md`. `feature-archive-maintenance` means the human explicitly requests Feature Monthly Archive or rehydrate for closed history; require reliable memory, a read-only scan, and the exact plan SHA-256 Batch Human Gate before mutation. If unclear, ask whether the human wants ordinary discussion, requirements documentation, feature implementation, project-skill management, or archive maintenance. ## Human Help And Version Questions @@ -77,6 +77,7 @@ Use this skill when the user wants to: - reconcile `agent-loop` documents with code reality - execute a task/story with TDD and verification - submit, pause, resume, or close a feature +- compact closed feature discovery by moving whole feature directories through Human-gated Feature Monthly Archive, or rehydrate an archived feature before follow-up execution Do not use for one-off edits when the user explicitly asks to bypass workflow and the edit does not affect feature behavior, public interfaces, data/security boundaries, project memory, submit, or close state. @@ -95,7 +96,7 @@ references/project-memory-mode.md simple vs enterprise project memory rules references/project-architecture-init.md DDD-inspired architecture and stack adapter rules references/remote-project-discovery.md local entry + remote project discovery rules references/requirement-management.md human source requirement archive rules -references/requirement-product-grill.md requirement/product clarification method for ambiguous terminology, flows, prior feature conflicts, and decision signals +references/requirement-product-grill.md requirement/product clarification plus triggered Concept Foundation and Requirement Product Model derivation references/project-decisions.md Design Readiness, Decision & Design, placement, coverage, and project-level ADR rules references/product-brief.md feature product brief and product consensus rules references/delivery-contracts.md durable producer-consumer interface handoff rules @@ -118,7 +119,15 @@ references/submit-and-integrate.md explicit git submit / commit / PR gate references/validation-scenarios.md pressure scenarios for checking this skill works references/document-templates.md inline markdown templates references/workflow-checklists.md checklist form for each stage -scripts/check-root-agents-blocks.sh read-only root AGENTS managed-block drift checker +scripts/checker_support.py shared standard-library Markdown checker support +scripts/check-root-agents-blocks.py read-only root AGENTS managed-block drift checker +scripts/check-onboarding-core-flow-coverage.py onboarding core-flow coverage checker +scripts/check-concept-foundation-trace.py accepted concept/model trace checker +scripts/check-adr-requirement-model-trace.py ADR requirement-model landing checker +scripts/scan-feature-monthly-archive.py read-only deterministic archive/rehydrate plan +scripts/check-feature-monthly-archive.py read-only pre/post archive contract checker +scripts/apply-feature-monthly-archive.py exact-hash Human-gated archive/rehydrate apply +scripts/restore-feature-monthly-archive.py exact transaction-journal restore templates/ copy-ready artifact templates examples/login-feature/ small finished feature workspace examples/complex-saas-project/ larger takeover + feature execution workspace @@ -132,7 +141,8 @@ CHANGELOG.md version-change source of truth for "what cha 1. Inspect `.agent-loop/`; if missing, also check legacy `agent-loop/`. 2. Check root `AGENTS.md` / `CLAUDE.md` as the Root Agent Bootstrap Gate; if either is missing or stale, load `references/project-guidance.md` and include the guidance repair in the recommended Project Entry action unless the human has explicitly deferred it. -3. Classify the latest message intent: `chat`, `requirements-discussion`, `project-skill-management`, `feature-request`, `operational-support`, `feature-follow-up`, `deferred-requirement`, or `unknown`. +2a. Canonical `scripts/check-*.py` validation requires Python 3.10+ and only the Python standard library. Run the `.py` entrypoints natively on macOS or Windows; if a required checker cannot run because Python is missing or unsupported, fail closed and report the capability gap instead of silently using an obsolete implementation. +3. Classify the latest message intent: `chat`, `requirements-discussion`, `project-skill-management`, `feature-archive-maintenance`, `feature-request`, `operational-support`, `feature-follow-up`, `deferred-requirement`, or `unknown`. 3a. For `chat`, answer or discuss only; do not create requirement sets, feature workspaces, tasks, tests, or plans. 3b. For `requirements-discussion`, load `references/requirement-management.md`, use Brainstorm / Clarify, produce a human-reviewed requirement document, and archive it under `.agent-loop/requirements/-/` after confirmation before any feature construction. 3c. For `project-skill-management`, load `references/project-skills.md`, require reliable Project Entry/memory, and route to Project Skill Creation / Update without creating a requirement set or feature workspace. @@ -144,15 +154,16 @@ CHANGELOG.md version-change source of truth for "what cha 7. Load `references/project-architecture-init.md` during init or Project Entry Scan, when proposing project structure, when recording architecture profile, or when a task creates durable code boundaries. 8. Load `references/remote-project-discovery.md` when the human says the project is remote, local files contain remote-entry hints, or local/remote/container execution is unclear. Do not treat an empty local directory alone as remote. 9. Load `references/requirement-management.md` before copying, moving, renaming, indexing, or referencing human source requirements. -9a. Load `references/requirement-product-grill.md` during Requirements Discussion, Product Brief, or Brainstorm / Clarify when requirements include ambiguous terminology, domain boundaries, business flows, exception paths, conflicting prior feature behavior, or decision signals. Grill questions clarify input only; they do not create PRDs, ADRs, project memory, `CONTEXT.md`, `CONTEXT-MAP.md`, or `docs/adr/`. -9b. Load `references/project-decisions.md` for Design Readiness Check and `Decision & Design If Needed`: before accepted requirements enter feature construction, when a requirement spans multiple features or needs shared business-flow/domain/data/architecture/recovery/non-functional design, when product or technical work reveals cross-feature decisions, or when drift changes durable facts. Decision Scan / Placement is an internal method. A `.agent-loop/decisions/*.md` file is globally optional, but it becomes conditionally required when shared design is required and no accepted decision already covers it; creation and acceptance remain Human-gated. +9a. Load `references/requirement-product-grill.md` during Requirements Discussion, Product Brief, or Brainstorm / Clarify when requirements include ambiguous terminology, domain boundaries, business flows, exception paths, conflicting prior feature behavior, or decision signals. When Concept Foundation triggers, inspect evidence, extract candidate concepts, recommend one definition with impact, and ask exactly one blocking question before deriving the Requirement Product Model. Grill questions clarify input only; they do not create ADRs, project memory, `CONTEXT.md`, `CONTEXT-MAP.md`, or `docs/adr/`. +9b. Load `references/project-decisions.md` for Design Readiness Check and `Decision & Design If Needed`: before accepted requirements enter feature construction, when a requirement spans multiple features or needs shared business-flow/domain/data/architecture/recovery/non-functional design, when product or technical work reveals cross-feature decisions, or when drift changes durable facts. A requirement-driven ADR resolves the Effective Requirement Snapshot, records a Requirement Model Technical Landing Trace, and passes coverage/compatibility review before acceptance. Decision Scan / Placement is an internal method. A `.agent-loop/decisions/*.md` file is globally optional, but it becomes conditionally required when shared design is required and no accepted decision already covers it; creation and acceptance remain Human-gated. 10. Load `references/product-brief.md` when a feature needs product intent, product consensus, user stories, product scope, or PRD-like synthesis. 11. Load `references/e2e-discovery.md` before designing or executing Web E2E/browser verification. 12. Load `references/delivery-contracts.md` when the human requests cross-boundary handoff/API/interface documentation, or when the agent detects a likely downstream consumer boundary such as frontend/backend, service, event, public data, SDK/library, UI state, or runtime behavior. Delivery Contracts are not created by default. 13. Load `references/project-entry-scan.md` when taking over an existing project without reliable `agent-loop` memory. This is now a Project Entry Scan only: build safe project memory, guidance status, commands, boundaries, and uncertainties. Do not create `.agent-loop/onboarding-db/`, module docs, flow docs, onboarding diagrams, or old Quick / Deep / Targeted onboarding artifacts during Project Entry Scan. -13a. Load `references/onboarding-knowledge-base.md` when the human asks for newcomer-facing docs, durable project understanding, guided learning paths, or onboarding-db construction. Run it only after Project Entry Scan or reliable project memory. Use Evidence Graph first, then accepted Onboarding Spec, Onboarding Tasks, single-file module/flow docs by default, wireframe architecture flow diagrams as the preferred flow expression, coverage scoring, and reviewed batches. +13a. Load `references/onboarding-knowledge-base.md` when the human asks for newcomer-facing docs, durable project understanding, guided learning paths, or onboarding-db construction. Run it only after Project Entry Scan or reliable project memory. Use Evidence Graph and Core Flow Inventory first, then accepted Onboarding Spec, Onboarding Tasks, Flow Slice Coverage for critical/important flows, evidence-linked diagrams, completeness gating, coverage scoring, and reviewed batches; module/flow docs remain single-file by default. 13b. During Project Entry, Resume, Re-Adopt, context recovery, and controller re-entry, check `.agent-loop/skills/INDEX.md` when present. Load only `active` project skills whose current instruction-bearing and executable files match the validation manifest, according to `bootstrap` / `on-demand`; discovery and loading never satisfy the per-invocation Execution Gate. 14. Load `references/feature-follow-up.md` when the human reports a bug, regression, post-close correction, field/schema change, algorithm change, API mismatch, screenshot issue, behavior tweak, "small tweak", test failure, or QA/user feedback that may belong to a recent feature. +14a. For `feature-archive-maintenance`, load `references/artifact-rules.md`, `references/stage-guides.md`, `references/human-review-summary.md`, and `references/feature-follow-up.md`. Feature ID is stable while location changes and root `features/archive.md` locates archived/rehydrated history. The scan is read-only; archive/rehydrate requires the expected plan SHA-256 Batch Human Gate, transaction journal, post-check, and restore. Rehydrate before reopened execution; auto modes never authorize either operation. 15. Load `references/large-projects.md` when the repo is large, old, unfamiliar, multi-package, or likely above 100k LOC. 16. Load `references/complex-artifacts.md` when story/task/test/plan complexity crosses its trigger conditions. 17. Load `references/implementation-planning.md` before writing or approving `plan.md` for a task/story. @@ -192,6 +203,8 @@ CHANGELOG.md version-change source of truth for "what cha feedback.* optional source file when provided notes.* optional source file when provided features/ + archive.md optional Feature ID locator maintained only by Feature Monthly Archive + YYYY-MM/ archived closed feature directories -/ product.md optional product brief spec.md @@ -237,16 +250,22 @@ If the local directory is only a remote-project entry point, create only thin lo - Task Done Gate: mark a task `done` only after implementation is complete, required tests or substitute verification have run fresh, evidence is recorded in `notes.md`, lightweight Spec Review is recorded, Standards Review is recorded when triggered, drift decision is recorded, and `tasks.md` links or names the evidence. - During large Project Entry Scan, recommend bounded subagent scanning when available and human-confirmed; otherwise use single-agent layered scan. - During existing-project Project Entry Scan, create or propose only safe project memory, root guidance status, commands, boundaries, capabilities, and uncertainties. Do not create onboarding-db detail docs, module docs, flow docs, onboarding diagrams, onboarding-spec, or onboarding-tasks during Project Entry Scan. -- During Evidence-Graph + DDD Onboarding, create or update onboarding-db only through `references/onboarding-knowledge-base.md`: build Evidence Graph, confirm Onboarding Spec, write Onboarding Tasks, then produce reviewed batches with single-file module/flow docs by default, wireframe architecture flow diagrams, coverage scoring, and no placeholder files. +- During Evidence-Graph + DDD Onboarding, create or update onboarding-db only through `references/onboarding-knowledge-base.md`: build Evidence Graph with Core Flow Inventory, confirm Onboarding Spec, write Onboarding Tasks, then produce reviewed batches with Flow Slice Coverage for critical/important flows, evidence-linked diagrams, completeness gating, single-file module/flow docs by default, and no placeholder files. - When Project Entry Scan discovers stable project facts missing from project memory, propose or perform project memory backfill after human confirmation. - For focused questions about one module, flow, async task, deployment path, or problem area, answer from existing docs/code as chat or operational support unless the human explicitly authorizes feature/fix work. Do not create focused onboarding-db artifacts. - When local and remote project reality are split, discover the remote environment before Project Entry Scan or initializing project memory. - Historical execution evidence belongs in `notes.md`. +- Feature Monthly Archive moves only eligible whole closed feature directories to `features/YYYY-MM//`; active / blocked / paused features stay flat. It creates no per-feature archive summary, no `historical/`, no Deep Archive, and exposes no `--force`. Original human requirement sources remain unchanged. - Web E2E capability is discovered from the real project environment. Stable E2E capability belongs in `project.md`; feature-specific E2E cases belong in `tests.md` or `tests/e2e/*`. - Human source requirements are archived as requirement set directories, not new flat files. Each requirement set groups the human's requirement, prototype, feedback, screenshots, recordings, links, and follow-up notes for one intake event or topic. - `.agent-loop/requirements/` is canonical. Do not create or maintain legacy `inputs/` archives in current-version projects. - Human source requirement archive dates mean archive date only; never infer deadlines, scope duration, or lifecycle from input paths. - Requirements created from requirements-discussion live under `.agent-loop/requirements/`; feature `product.md` and `spec.md` only derive from and link to requirement sets, and do not own requirement lifecycle. +- Concept Foundation is an internal Requirements Discussion / Requirement Product Grill method, never a canonical stage or `.agent-loop/concepts/` artifact. Triggered foundations stay `candidate` or `reopened` until the Human Grill Contract confirms blocking meanings; only `accepted` or a reasoned `concept-foundation-not-needed` path may continue to requirement-level flow, state, and product-data modeling. +- The effective human-reviewed requirement source owns the accepted Concept Foundation and Requirement Product Model. After archive, requirement README indexes the effective source/status while append-only follow-ups preserve earlier sources. Product Brief and Feature Spec reference accepted Concept IDs/model rows rather than redefining product meaning; ADR work consumes accepted product semantics only through its own later Human Gate. +- A requirement-driven ADR records an Effective Requirement Snapshot, a source-wide Requirement Model Scope Inventory, and a Requirement Model Technical Landing Trace inside the existing decision record. Inventory all stable `REL/PERM/CMD/EVT/FLOW/STATE/PM/EX` IDs before selecting scope; every in-scope accepted ID needs a disposition, and every `landed` row needs a concrete technical landing, preserved invariant, Design Slice, and verification path. +- Keep the ADR `proposed` for structural preflight. Only explicit Decision & Design human acceptance authorizes Human Review Evidence plus `Status: accepted`, followed by accepted-mode validation. A reasoned `concept-foundation-not-needed` source uses the trace-not-applicable path instead of invented models. +- `Upstream Compatibility: review-required` blocks new dependent Feature Spec, Plan, and implementation work. It is not an ADR lifecycle status. If changed upstream meaning invalidates an accepted technical decision, preserve history and create a Human-gated superseding ADR instead of rewriting accepted decision meaning. - For complex requirements, recommend requirement-level `Delivery Phases` in the requirement set `README.md` before feature construction when the human needs to confirm staged delivery. Phases express human-readable delivery slices; they are not feature workspaces, tasks, or plans. - A feature may implement one accepted Delivery Phase or a smaller slice inside one phase. It should not combine multiple phases unless the human first confirms a phase rewrite/merge. Feature `spec.md` must reference the requirement set and phase when phase mode is used, and requirement reconciliation should update phase status and feature mapping after human confirmation. - Future/deferred work, backlog items, and unimplemented planned capabilities belong in requirement set lifecycle/status records and optional `requirements/INDEX.md`, not in `project.md`. Do not edit `requirement.md` or other source files for lifecycle/status updates. @@ -303,6 +322,7 @@ Stop when: - intent, scope, product, design, architecture, security, data, approval, or public-interface decisions cannot be resolved from files - a stage would modify human original requirements - spec, product scope, or acceptance criteria would change +- a triggered Concept Foundation is still `candidate` or `reopened`, a downstream artifact would redefine an accepted Concept ID / Requirement Product Model rule, or an ADR dependency is `review-required` / missing Requirement Model coverage - project memory and code reality materially disagree - code reality conflicts with feature docs - a new dependency, migration, destructive operation, credential, external service, or long-lived boundary directory is needed @@ -316,3 +336,4 @@ Stop when: - subagents are needed but not yet approved - submit, commit, PR, merge, release, publish, pause, or close is requested - the work would require first-version exclusions +- Feature Monthly Archive or rehydrate lacks an exact reviewed plan hash, has unsupported/ambiguous references, encounters a stale plan or incomplete `.archive-txn`, or would be performed by manual directory movement diff --git a/Usage.md b/Usage.md index bdb7d54..b4f295a 100644 --- a/Usage.md +++ b/Usage.md @@ -1,6 +1,6 @@ # Agent Loop 使用指南 -**版本:** 1.2.4 +**版本:** 1.3.0 这份文档是给人类看的。你不需要记住内部阶段名,只要用自然语言说出你想做什么,Agent 应该自己判断当前状态、推荐一个下一步,并在需要你确认的地方停下来。 @@ -45,13 +45,14 @@ Project Entry Scan 不算完成,除非 root `AGENTS.md` 已存在、已创建 | “重点讲清楚支付/钱包/任务调度这块。” | 先从现有代码和文档回答;如果要沉淀长期文档,再走聚焦的 onboarding-db 更新。 | | “这个旧 onboarding-db 还能信吗?” | 把旧文档当 evidence,先和代码现实核对;不直接按旧布局刷新。 | -当前 1.2.4 使用的是 **Evidence-Graph + DDD Onboarding**,不是旧 Quick / Deep / Targeted 模式。 +当前 1.3.0 使用的是 **Evidence-Graph + DDD Onboarding**,不是旧 Quick / Deep / Targeted 模式。 推荐流程: ```text 可靠项目记忆 -> 08-review/evidence-graph.md +-> Core Flow Inventory(核心流程、业务终态、恢复责任和证据链) -> onboarding-spec.md(第一次确认) -> onboarding-tasks.md / Full Execution Gate(第二次确认) -> 02-modules/.md @@ -73,6 +74,10 @@ Agent 不应该用空目录、薄 README、planned/later 占位文件、`TBD`、 - 怎么验证、怎么排查、怎么安全修改 - 架构/边界图、ASCII 状态图、Timeline / 时序图 +核心流程完整性先于文档评分:`critical` / `important` 流程必须从触发闭合到业务成功、失败、取消、未知或人工处理终态;callback、consumer、retry、DLQ、compensation、reconciliation 和 job 不能因为被拆成其他 topic 就从流程中消失。每个关键 slice 都要连接代码证据、图和正文,缺失时不能标记 `newcomer-ready`。 + +Timeline / Sequence 是单个核心流程的主叙事;Core Flow Overview / Boundary 讲 scope、owner、branch 和 terminal;ASCII State Machine 讲状态、非法转换和恢复。数据血缘、事务并发、异步拓扑、决策树、runtime 和排障图由真实复杂度触发。非流程的 stateless 文档不强制状态图。 + 普通流程图和时序图可以优先使用 Mermaid flowchart / sequenceDiagram;状态机、复杂原理图和复杂示例图优先使用 ASCII。 ### 我只是想问问题 @@ -90,7 +95,7 @@ Agent 不应该用空目录、薄 README、planned/later 占位文件、`TBD`、 | 你可以这样说 | Agent 应该怎么做 | |---|---| -| “1.2.4 更新了什么?” | 读取 `CHANGELOG.md` 的 1.2.4 段落,按能力分类总结,不凭记忆回答。 | +| “1.3.0 更新了什么?” | 读取 `CHANGELOG.md` 的 1.3.0 段落,按能力分类总结,不凭记忆回答。 | | “和 1.2.2 比有什么变化?” | 对比 `CHANGELOG.md` 里的两个版本段落,说明新增、删除、替换和迁移影响。 | | “现在 agent-loop 怎么用?” | 基于 `Usage.md` 用人类语言介绍常见触发方式。 | | “这个功能怎么触发?” | 从 `Usage.md` 找对应说法,再说明 Agent 会进入哪个处理流。 | @@ -104,10 +109,13 @@ Agent 不应该用空目录、薄 README、planned/later 占位文件、`TBD`、 |---|---| | “先帮我梳理这个需求,不要实现。” | 进入 Requirements Discussion,问清目标、用户、范围、约束、验收方向。 | | “先按 grill-with-docs 问清这个需求。” | 先问清术语、业务流程、边界和异常场景;提问前会查已有文档、代码和相关历史 feature。 | +| “这个需求概念容易混,先把概念、关系和状态讲清楚。” | 在 Requirements Discussion 内触发 Concept Foundation:先查证据、提取候选概念、给出推荐定义及影响,再一次只确认一个真正阻塞后续模型的问题。 | +| “只是改按钮文案,不要搞复杂建模。” | 没有产品语义变化时记录 `concept-foundation-not-needed` 和理由,不生成大型概念表。 | | “把这些内容落到 product.md。” | 如果还在聊天或需求澄清阶段,先问你是要创建/引用 requirement set,还是确认进入 feature Product Brief;不会直接创建 feature `product.md`。 | | “聊需求时遇到复杂架构取舍,要不要 ADR?” | 记录 Design Readiness evidence 和 Decision Candidate;不会直接创建 ADR。requirement 被确认后、feature construction 前判断是否需要 Decision & Design。 | | “这个需求进入 feature 前先做 ADR / Decision Design。” | 先做 Design Readiness;涉及多 feature、业务闭环、共享状态/事实源、恢复或非功能目标时进入 Decision & Design,不要求先出现技术争议。 | | “这个需求会拆成多个 feature,先检查整体设计是否完整。” | 运行 Design Readiness Check;需要共享设计时先形成 Decision & Design 和 Design Slice Coverage,再创建各 feature spec。 | +| “检查 ADR 是否完整落地了 requirement model。” | 解析 Effective Requirement Snapshot,逐项检查 Requirement Model Technical Landing Trace、Design Slice 与 Verification;缺失 coverage 或 compatibility 待复核时停在 Decision & Design Human Review。 | | “这个需求比较大,先拆成几个阶段。” | 建议在 requirement README 里写 `Delivery Phases`,让你确认先做哪一段。 | | “这个先记一下,后面做。” | 作为 deferred requirement 写进 requirement set 或 optional `requirements/INDEX.md`,不写进 `project.md`。 | | “这是需求文档、原型图和反馈。” | 归档到 `.agent-loop/requirements/-/`,保留人类原始材料。 | @@ -131,12 +139,22 @@ Agent 不应该用空目录、薄 README、planned/later 占位文件、`TBD`、 `grill-with-docs` 在 agent-loop 里是需求/产品澄清方法,不是新的阶段。它会先查 project memory、需求来源、代码文档和相关过往 feature,再问人类一个阻塞问题;如果发现跨 feature、共享状态、恢复或长期取舍,只会记录 Design Readiness evidence / Decision Candidate,不会直接创建 ADR。 +Concept Foundation 是这套澄清方法在复杂需求里的前置约束,不是新的 stage。Agent 不会让你先写“领域模型”;它从成功/失败场景和项目证据提取 Concept Candidate,用 requirement-local `Concept ID` 固定定义、identity、owner、lifecycle、relationship、invariant 和 product fact meaning。每轮 Human Grill 只问一个 downstream blocker,并附推荐定义、证据以及接受/拒绝对流程、状态和产品数据的影响。 + +Concept Foundation 被确认后,effective requirement source 才推导 Concept Relationships、Role / Permission Matrix、Commands / Events、Primary Business Flow、Product State Model 和 Requirement Product Model。`product.md` 与 `spec.md` 同时记录 `Effective Concept Source` 并只引用 accepted Concept/Model IDs;如果归档后要改变产品含义,Agent 保留原 source、把 Gate 设为 `reopened`,经人类确认后追加 follow-up 或新建 requirement set,再更新 README 的 effective pointer,而不是改写历史文档或在下游文档/ADR 里重新定义。 + 如果用了 Requirement/Product Grill,requirement document 会承接术语、主流程、异常路径、事实源、历史冲突、验收场景和 Decision Candidates,而不是只写一段摘要。 Product Brief Source Gate 的意思是:从聊天或需求澄清直接说“落到 product.md”时,Agent 不能立刻创建 feature 级 `product.md`。它要先问你是要创建/引用 requirement set,还是确认开始 feature Product Brief。如果只是整理产品意图,可以先保留在 requirement artifact 或回复草稿,等 feature context 明确后再写入 `product.md`。 Decision & Design / ADR 是 requirement 和 feature 之间的需求落地层。Requirement 接受后先运行 Design Readiness Check;只要需求会拆成多个 feature,或需要共享业务流程、领域/数据规则、事实源、一致性、恢复、性能、高可用、安全或可观测性设计,就会建议先完成整体 Decision & Design,即使没有技术争议。新的 decision draft 默认是 `proposed`,只有你明确确认后才会变成 `accepted`。每项 required Design Slice 都必须映射到 owning feature 和验证路径,普通 feature 内的小取舍仍写在 `spec.md` 的 `Design Decisions`。 +Requirement-driven ADR 会先记录 Effective Requirement Snapshot,再用 Requirement Model Scope Inventory 对来源中的 `REL/PERM/CMD/EVT/FLOW/STATE/PM/EX` 稳定 ID 做完整盘点,最后用 Requirement Model Technical Landing Trace 给 scope 内每个 accepted model ID 安排 `landed`、`covered-by-accepted-decision`、`feature-local` 或 `not-applicable`。`landed` 必须明确技术落点、保留的不变量、Design Slice 和验证方向;inventory/coverage 不完整或 `Upstream Compatibility: review-required` 时,ADR 不能接受,依赖的 Feature Spec、Plan 和实现也会停止。 + +结构预检期间 ADR 保持 `proposed`;校验通过只表示可以提交人类评审。你明确接受后,Agent 才记录 Human Review Evidence、改为 `accepted` 并运行 accepted-mode validation。对于有具体理由的 `concept-foundation-not-needed`,使用 trace-not-applicable 分支,不伪造数据模型或流程。ADR 仍不能重新定义 Concept、流程、状态、不变量或事实归属;上游变化使既有技术结论失效时,必须经人类确认创建 superseding ADR。 + +Operational landing 不是每份 ADR 的默认大章节。只有持久化表示、协议/provider、runtime boundary 或上线兼容性发生变化时,才展开 Migration / Backfill、Compatibility、Rollout / Cutover 或 Rollback / Reversibility;未触发时只记录具体原因。 + 示例: ```md @@ -241,6 +259,33 @@ Feature Follow-up / Flow-back 默认看最近 30 天,但这不是硬限制。 低信息错误,比如 “500 / 白屏 / unknown error”,不应该随便归到最近 feature;Agent 应先建议 investigate-first。 +### 我想按月份归档已经关闭的 feature + +Feature Monthly Archive 只整理目录位置,不压缩或删除 feature 内容,也不会自动提交。一次正常交互是: + +```text +Human: 把 2026 年 5 月和 6 月已经关闭的 feature 按月份归档。 +Agent: 运行只读 scan,展示 plan SHA-256、eligible/blocked 行、目录移动、引用影响、不变内容和恢复范围。 +Human: 确认这个精确批次及其 plan SHA-256。 +Agent: 执行整目录移动和精确引用更新,完成 post-check,报告 transaction evidence,不自动 commit。 +``` + +只有 `closed` 且关闭证据、Archive Readiness、drift、project memory 和 open follow-up 检查完整的 feature 才能进入候选。`active`、`blocked`、`paused`、当前月或存在不安全引用的 feature 保持 flat 并被阻断。归档结果是 `.agent-loop/features/YYYY-MM//`,根级 `.agent-loop/features/archive.md` 只负责按稳定 Feature ID 定位当前位置;原 feature 文档、需求源和 accepted decisions 仍然是事实权威。 + +如果归档后的 feature 要重新进入 Follow-up / Flow-back,Agent 先运行只读 rehydrate scan 并展示新的 plan SHA-256;你必须通过一个独立的 rehydrate Human Gate。确认后 Agent 才把完整目录移回 `.agent-loop/features//` 并复验。rehydrate 不会自行把 `spec.md` 从 `closed` 改为 `active`,后续 reopen 仍归 Feature Follow-up 管理。 + +原生 Python 3.10+ 调用示例: + +```text +# macOS:只读生成计划 +python3 scripts/scan-feature-monthly-archive.py --project-root --operation archive --month 2026-05 --month 2026-06 --as-of 2026-07-14 + +# Windows PowerShell:只读生成计划 +py -3 scripts\scan-feature-monthly-archive.py --project-root --operation archive --month 2026-05 --month 2026-06 --as-of 2026-07-14 +``` + +真正 apply 还必须提供刚刚经人类确认的 `--expected-plan-sha256`;不能使用 `--force` 绕过 stale-plan、引用阻断、transaction journal、恢复或 post-check。 + ### 我想同步 AGENTS.md | 你可以这样说 | Agent 应该怎么做 | @@ -249,13 +294,17 @@ Feature Follow-up / Flow-back 默认看最近 30 天,但这不是硬限制。 | “按最新 agent-loop 刷新这个项目的 AGENTS.md 托管块。” | 先给 Human Review Summary,说明要改哪些 block、为什么、风险是什么;你确认后才写。 | | “不要覆盖我自己写的项目规则。” | 保留 managed block 外的人类/项目内容;冲突规则单独列出来让你决定。 | -如果当前 skill 提供脚本,Agent 应先运行: +如果当前 skill 提供脚本,Agent 应使用 Python 3.10+ 直接运行 canonical `.py` 入口。脚本只使用 Python 标准库;macOS 和 Windows 分别可以这样调用: ```text -scripts/check-root-agents-blocks.sh +# macOS +python3 scripts/check-root-agents-blocks.py --template /templates/root-AGENTS.md --target /AGENTS.md + +# Windows PowerShell +py -3 scripts\check-root-agents-blocks.py --template \templates\root-AGENTS.md --target \AGENTS.md ``` -这个脚本只读检查,不写文件。它会报告 missing / stale / broken managed block、`block-version` 漂移、marker 问题、unexpected section、source 缺失等。 +这个脚本只读检查,不写文件。它会报告 missing / stale / broken managed block、`block-version` 漂移、marker 问题、unexpected section、source 缺失等。如果 Python 3.10+ 不可用,Agent 应报告 capability gap 并停止依赖该 checker 的判断,不能静默退回旧 Bash/Ruby 规则实现。 注意:managed block 不等于都能模板覆盖。规则块可以按模板刷新;项目事实块必须结合 `.agent-loop/project.md` 或 enterprise memory 重新生成,并经过你确认。 diff --git a/docs/proposal/v1.3.x/adr-requirement-model-technical-landing-trace-implementation-plan.md b/docs/proposal/v1.3.x/adr-requirement-model-technical-landing-trace-implementation-plan.md new file mode 100644 index 0000000..3c810bc --- /dev/null +++ b/docs/proposal/v1.3.x/adr-requirement-model-technical-landing-trace-implementation-plan.md @@ -0,0 +1,112 @@ +# ADR Requirement Model Technical Landing Trace Implementation Plan + +> **For agentic workers:** execute inline under the Agent Loop maintainer rules. Do not create `docs/superpowers/`, target-project `.agent-loop/` artifacts, commits, pushes, PRs, tags, releases, or publications. + +**Goal:** Implement the approved ADR technical-landing trace, coverage gate, upstream compatibility review, Human Review Summary, and triggered operational landing without changing the v1.3.0 stage model or version. + +**Architecture:** Enhance the existing Decision & Design record instead of adding a mapping artifact. The effective requirement source remains product-semantics authority; the ADR records a read-only snapshot, a source-wide scope inventory, and a generic trace from accepted Requirement Model IDs to technical landing, Design Slices, and verification. A domain-neutral Ruby validator proves source resolution, source/scope equality, coverage, external owner references, preflight/accepted Human Gate order, compatibility, and acceptance gates against isolated fixtures. + +**Tech Stack:** Markdown runtime/reference/template sources, Bash focused regression contract, Ruby artifact validator. + +--- + +### Task 1: Establish RED Baseline + +**Files:** +- Create: `tests/validate-adr-requirement-model-technical-landing-trace.sh` +- Create after RED: `docs/reports/agent-loop-v1.3.0-adr-technical-landing-red-baseline-2026-07-13.md` + +- [x] Add focused assertions for the Effective Requirement Snapshot, generic Technical Landing Trace, Coverage Hard Gate, compatibility drift, Decision & Design approval summary, operational trigger routing, scope exclusions, and domain neutrality. +- [x] Run the focused test before changing runtime, design, references, or templates. +- [x] Confirm the failure is caused by missing technical-landing behavior rather than shell syntax or test infrastructure. +- [x] Preserve the pre-change `31/31 PASS` repository baseline and exact RED output. + +### Task 2: Implement Runtime And Design Authority + +**Files:** +- Modify: `SKILL.md` +- Modify: `references/runtime.md` +- Modify: `references/design.md` +- Modify: `references/project-decisions.md` + +- [x] Define Effective Requirement Snapshot resolution and keep `review-required` separate from ADR lifecycle status. +- [x] Define Requirement Model Technical Landing Trace dispositions and product-semantics ownership. +- [x] Block ADR acceptance and dependent Feature Spec / Plan / implementation when coverage or compatibility is incomplete. +- [x] Require superseding ADRs when accepted decision meaning is no longer valid; never rewrite accepted meaning in place. +- [x] Trigger operational landing detail only for changed persistence, protocol, provider, runtime-boundary, or rollout-compatibility concerns. + +### Task 3: Coordinate Stage, Gate, Human Review, And Root Guidance + +**Files:** +- Modify: `references/human-review-summary.md` +- Modify: `references/stage-guides.md` +- Modify: `references/workflow-checklists.md` +- Modify: `references/project-guidance.md` +- Modify: `templates/root-AGENTS.md` + +- [x] Add the Decision & Design Human Review Summary with source, coverage counts, preserved semantics, operational triggers, Design Slice ownership, verification, and explicit human decision. +- [x] Add compatibility-review and coverage-hard-stop behavior at Decision & Design, Feature Spec, Plan, Review, and Drift surfaces. +- [x] Refresh all root managed-block revisions to the same v1.3.0 same-day revision without adding a stage. +- [x] Keep root guidance navigational and leave detailed procedures in published references. + +### Task 4: Enhance The ADR Template Without Domain Leakage + +**Files:** +- Modify: `templates/decision.md` + +- [x] Add Effective Requirement Snapshot fields. +- [x] Add the generic Requirement Model Technical Landing Trace table and disposition rules. +- [x] Add coverage-hard-gate instructions and align Design Slice Coverage. +- [x] Add Upstream Compatibility / Drift rules and immutable accepted-decision behavior. +- [x] Add operational trigger assessment and include detail only for triggered concerns. +- [x] Do not copy fixture business nouns, actions, technical products, or landing choices into the template. + +### Task 5: Add Domain-Neutral Artifact Validation + +**Files:** +- Create: `scripts/check-adr-requirement-model-trace.rb` +- Create: `tests/fixtures/adr-technical-landing/valid/` +- Create: `tests/fixtures/adr-technical-landing/invalid-missing-coverage/` +- Create: `tests/fixtures/adr-technical-landing/invalid-empty-landing/` +- Create: `tests/fixtures/adr-technical-landing/invalid-unaccepted-source/` +- Create: `tests/fixtures/adr-technical-landing/invalid-reopened-source/` +- Create: `tests/fixtures/adr-technical-landing/invalid-review-required/` + +- [x] Resolve the README Effective Concept Foundation pointer and require source/snapshot alignment. +- [x] Reject `candidate` / `reopened`, `review-required`, missing scoped IDs, invalid dispositions, empty landed fields, missing Design Slices, and incomplete verification. +- [x] Keep parser logic identifier- and structure-based; fixture-specific business/action/technology tokens must not appear in validator or templates. +- [x] Keep all downstream simulation under `tests/fixtures/`; do not create repository-root `.agent-loop/`. + +### Task 6: Human Docs, Scenarios, GREEN, And Full Validation + +**Files:** +- Modify: `README.md` +- Modify: `Usage.md` +- Modify: `CHANGELOG.md` +- Modify: `references/validation-scenarios.md` +- Create: `docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.md` + +- [x] Add focused pressure scenarios for stale effective sources, partial coverage, semantic redefinition pressure, feature-local/not-applicable dispositions, supersede behavior, and operational trigger routing. +- [x] Run the focused contract to GREEN and all `tests/*.sh`. +- [x] Perform the required six-domain semantic audit and representative full-skill pressure suite. +- [x] Run YAML, JSON, Ruby, Shell, Markdown fence, trailing-whitespace, version, target-artifact, and `git diff --check` guards. +- [x] Save a Chinese full-validation report and stop at Human Review without submission actions. + +### Task 7: Repair Review-Discovered Gate Bypasses + +**Files:** +- Modify: `scripts/check-adr-requirement-model-trace.rb` +- Modify: `scripts/check-concept-foundation-trace.rb` +- Modify: runtime/reference/template/example surfaces from Tasks 2-6 +- Create: `tests/validate-adr-requirement-model-trace-adversarial.rb` +- Create: `tests/fixtures/adr-technical-landing/valid-not-needed/` +- Create: `docs/reports/agent-loop-v1.3.0-adr-technical-landing-review-red-2026-07-13.md` +- Create after GREEN: `docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.2.md` + +- [x] Preserve an adversarial RED proving the initial validator inverted the Human Gate, accepted placeholder/gate/ID garbage, allowed silent scope omission and fake external owners, under-validated slices/operational inventory, and rejected the valid not-needed branch. +- [x] Split `proposed` structural preflight from post-human-review `accepted` validation and require Human Review Evidence for accepted mode. +- [x] Add source-wide Requirement Model Scope Inventory and exact source/scope/trace set validation. +- [x] Add stable `PERM-*` permission-rule and `EX-*` exception-path IDs to requirement modeling, examples, downstream model tracing, and ADR validation. +- [x] Validate decision/Feature Spec paths and statuses, explicit `planned:` future paths, exact gate inventory, slice status, and operational trigger inventory/details. +- [x] Accept the reasoned `concept-foundation-not-needed` trace-not-applicable branch without fabricating product models. +- [x] Rerun focused contracts, all `tests/*.sh`, six-domain semantic audit, mechanical checks, and save the replacement `.2` full-validation report. diff --git a/docs/proposal/v1.3.x/adr-requirement-model-technical-landing-trace.md b/docs/proposal/v1.3.x/adr-requirement-model-technical-landing-trace.md new file mode 100644 index 0000000..c594bd1 --- /dev/null +++ b/docs/proposal/v1.3.x/adr-requirement-model-technical-landing-trace.md @@ -0,0 +1,325 @@ +# Proposal: ADR Requirement Model Technical Landing Trace + +状态:v1.3.0 Release Human Gate 已批准;发布目标 stable-v1.3.0 + +版本边界:v1.3.x 行为增强,不修改 skill version + +## 背景 + +Concept Foundation 与 Requirement Product Model 已经让 requirement 能够固化产品概念,并用稳定 ID 表达关系、角色/权限、命令/事件、业务流程、产品状态、产品对象/事实、不变量和异常恢复。 + +现有 Decision & Design / ADR 已明确: + +- PRD / Requirement Product Model 拥有产品语义; +- ADR 只能消费 accepted 产品语义,不能重新定义 Concept、生命周期、关系、状态、终态、不变量或 product fact ownership; +- ADR 可以在独立 Human Gate 后选择技术表示; +- Design Slice 将共享技术设计分配给 Feature。 + +但现有 ADR 模板仍有四个落地缺口: + +1. 只记录 `Source Requirements`,没有固定 requirement README 当前指向的 `Effective Concept Source`; +2. `Domain Concepts` 可以引用 Concept ID,但 Business Flow、Data Model、Interface、State、Recovery 等技术章节不要求引用对应 Requirement Product Model ID; +3. Requirement Model 到 Technical Landing、Design Slice、Verification 之间缺少完整性 Gate; +4. effective requirement source 变化后,没有明确规定 accepted ADR 何时必须进入 compatibility review、何时必须 supersede。 + +结果是 ADR 虽然原则上不能改需求,仍可能基于旧 source、遗漏某个上游模型行,或在技术章节中产生无法追踪的局部解释。 + +## 目标 + +让 Decision & Design / ADR 成为 accepted Requirement Product Model 到可开发技术设计的可追踪桥梁: + +```text +Effective Requirement Source +→ Source-Wide Requirement Model Scope Inventory +→ Accepted Requirement Model IDs +→ Technical Landing Trace +→ Design Slice Coverage +→ Feature Ownership +→ Verification Evidence +``` + +同时保持以下边界: + +- 不新增 canonical stage; +- 不新增默认中间 artifact; +- 不引入 executable schema; +- 不要求简单或 feature-local 决策创建 ADR; +- 不让 ADR 重新拥有产品语义; +- 模板和运行规则保持领域中立;讲解 Technical Landing Trace 时使用的领域内容只作为示例,不成为默认要求或验收条件。 + +## 方案比较 + +### 方案 A:增强现有 ADR(推荐) + +在 `templates/decision.md` 中增加 upstream snapshot、Requirement Model Technical Landing Trace、coverage gate 和 compatibility review。现有 Design Slice 继续承担 Feature 分配。 + +优点: + +- 一份 Decision & Design record 即可完成需求到技术的追踪; +- 不新增同步对象; +- 与当前 ADR lane、Design Slice 和 Feature Spec 规则自然衔接。 + +代价: + +- ADR 模板更严格; +- Agent 必须显式处理 scope 内的 Requirement Model IDs。 + +### 方案 B:新增独立 Requirement-to-Technical Mapping 文档 + +在 requirement 与 ADR 之间增加单独 mapping artifact。 + +优点:技术映射可以独立演进。 + +缺点:新增默认 artifact 和同步关系;mapping、ADR、Feature Spec 容易互相漂移,不符合当前轻量边界。 + +### 方案 C:使用 YAML / JSON schema 自动生成映射 + +把 Concept、State、Flow 和技术设计变成机器可执行 schema。 + +优点:可以进行更强的自动验证。 + +缺点:超出 v1.3.x 范围,会提前引入 schema 版本、生成器和迁移规则,也容易伪装业务语义真实性。 + +## 选择 + +采用方案 A。 + +## 设计 + +### 1. Effective Requirement Snapshot + +每个由 Requirement Product Model 驱动的 ADR 在头部记录: + +```text +Effective Concept Source: +Concept Foundation Status: accepted | concept-foundation-not-needed +Accepted Concept IDs: +Accepted Requirement Model IDs: +Upstream Compatibility: current | review-required +Last Compatibility Check: +Trace Applicability: required | not-applicable +Trace Not-Applicable Reason: +``` + +规则: + +- `Effective Concept Source` 必须解析 requirement README `Effective Concept Foundation` pointer;旧 requirement set 没有 pointer 时使用兼容来源; +- 触发过 Concept Foundation 的复杂 requirement 必须为 `accepted`; +- `candidate` 或 `reopened` 阻止创建或接受 ADR; +- ADR 不复制 Concept 定义或 Requirement Product Model,只记录稳定引用和必要的 unchanged meaning summary; +- `Upstream Compatibility` 不是新的 ADR lifecycle status,不改变 `proposed | accepted | superseded | deprecated`。 +- 对有具体理由的 `concept-foundation-not-needed` 来源,accepted ID 字段写 `none`,trace 写 `not-applicable` 和具体原因,不伪造产品模型。 + +### 2. Requirement Model Scope Inventory + +ADR 在选择 coherent scope 前,先盘点 effective source 中全部稳定模型 ID: + +```text +REL-* | PERM-* | CMD-* | EVT-* | FLOW-* | STATE-* | PM-* | EX-* +``` + +每个 source ID 必须有且只有一个 scope disposition: + +```text +in-scope | covered-by-accepted-decision | feature-local | proposed-decision | not-applicable +``` + +规则: + +- `in-scope` 必须指向本 ADR,并与 snapshot 的 Accepted Requirement Model IDs 完全一致; +- existing decision / Feature Spec 引用必须解析到真实文件和有效状态; +- 尚未创建的下游 owner 必须使用显式 `planned:` canonical path,不能用模糊名称伪装真实 artifact; +- `not-applicable` 必须有具体理由; +- Scope Inventory 位于现有 ADR 内,不新增 mapping artifact; +- Agent 不得通过缩小 snapshot 和 trace 来静默隐藏 source model。 + +### 3. Requirement Model Technical Landing Trace + +ADR 增加通用追踪表: + +| Requirement Model Ref | Accepted Meaning / Constraint | Disposition | Technical Landing | Preserved Invariant | Design Slice | Verification | +|---|---|---|---|---|---|---| +| `STATE-...` | link or concise unchanged meaning | landed | component / state representation / transition owner | invariant reference | `DS-...` | test / evidence target | + +允许的 `Disposition`: + +```text +landed | covered-by-accepted-decision | feature-local | not-applicable +``` + +规则: + +- ADR scope 内涉及的 `REL-*`、`PERM-*`、`CMD-*`、`EVT-*`、`FLOW-*`、`STATE-*`、`PM-*`、`EX-*` 必须各有一行; +- `landed` 必须指向具体 Technical Landing、Design Slice 和 Verification; +- `covered-by-accepted-decision` 必须引用现有 accepted decision; +- `feature-local` 必须说明下沉到哪个 Feature Spec,不得隐藏共享约束; +- `not-applicable` 必须给出具体理由,并在 ADR Human Gate 中展示; +- 表格不允许创造新的产品含义;发现含义不足时返回 Requirements Discussion。 + +### 4. Coverage Hard Gate + +ADR 不能进入 `accepted`,Feature Spec 不能继续,除非: + +- effective source 已解析且 compatibility 为 `current`; +- source-wide Scope Inventory 完整覆盖 effective source 中全部稳定模型 ID; +- scope 内所有 accepted Requirement Model IDs 都有 disposition; +- 所有 `landed` 行都有 Technical Landing、Design Slice 和 Verification; +- 没有未解决的 product-semantic blocker; +- `not-applicable`、`feature-local` 和 deferred/out-of-scope Design Slice 已由人类看到并确认; +- 每个 implementation-bearing technical rule 已进入 Design Slice Coverage。 + +“已经引用 Applicable Decision”只能证明 Feature 知道 ADR,不能替代 Requirement Model coverage 或 Design Slice ownership。 + +校验分两步: + +1. ADR 保持 `proposed`,先运行 structural preflight; +2. preflight 通过后展示 Decision & Design Human Review Summary;只有人类明确接受后,Agent 才记录 Human Review Evidence、改为 `accepted` 并运行 accepted-mode validation。 + +Validator pass 只证明可以进入人类评审,不产生 acceptance authority。 + +### 5. Upstream Compatibility And Drift + +当 requirement README 的 effective source 改变,或新 requirement evidence 改变已接受模型时: + +1. 将依赖 ADR 的当前判断置为 `Upstream Compatibility: review-required`; +2. 停止新的依赖 Feature Spec、Plan 和 implementation; +3. 比较旧/新 effective source 的 Concept IDs 与 Requirement Model IDs; +4. 如果产品语义变化但现有技术决策仍成立,在 Decision & Design Human Gate 后更新 snapshot/trace; +5. 如果技术选择、边界、数据表示、恢复或 NFR 结论不再成立,创建 superseding ADR; +6. 不原地改写 accepted ADR 的决策含义。 + +Runtime 中的 `review-required` 是依赖可用性判断,不是新的 decision status。历史 ADR 保持可审计。 + +### 6. Decision & Design Human Review Summary + +`references/human-review-summary.md` 增加专用审批表: + +| Item | Review Content | +|---|---| +| Effective Requirement Source | 当前 effective source 与 Concept Foundation status | +| Requirement Model Scope | source total / in-scope / existing-decision / feature-local / proposed-decision / not-applicable / missing | +| Requirement Model Coverage | in-scope total / landed / existing-decision / feature-local / not-applicable / missing | +| Chosen Technical Decision | 选择及主要 rejected alternatives | +| Product Semantics Preserved | yes / no;任何 blocker 必须可见 | +| Migration / Compatibility / Rollout | triggered / not-triggered,以及理由 | +| Design Slice Ownership | unassigned / planned / deferred / out-of-scope | +| Verification | 每个落地行的证明方向 | +| Human Decision | accept / revise / return to Requirements Discussion | + +ADR acceptance 仍需要显式人类确认。Human Review Summary 不替代完整 ADR。 + +accepted ADR 还必须保存 `Decision`、`Confirmed By`、`Confirmed At` 和具体 `Evidence`,使 accepted-mode validation 能证明 Human Gate 已落盘。 + +### 7. Triggered Operational Landing + +只有技术决策引入或改变持久化表示、协议、provider、runtime boundary 或上线兼容性时,ADR 才展开: + +- Migration / Backfill; +- Compatibility; +- Rollout / Cutover; +- Rollback / Reversibility。 + +未触发时记录一条具体 `not-triggered` 理由,不展开对应的 operational landing 章节。示例中的业务动作或技术落点不得被复制成默认设计。 + +### 8. ADR Scope + +一份 ADR 对应一个 coherent durable decision boundary,而不是复制整份 PRD。 + +- 一个 requirement 可以触发多个互相关联的 ADR; +- ADR 只覆盖其声明 scope 内的 Requirement Model IDs; +- scope 外模型必须在 source-wide inventory 中由另一个 accepted/proposed decision、明确 feature-local placement,或具体 not-applicable 理由处理; +- Related Decisions / Supersedes 保持决策图可追踪。 + +## Artifact Ownership + +| Artifact | Owns | Does Not Own | +|---|---|---| +| Effective requirement source | Concept、关系、角色/权限、命令/事件、业务流程、产品状态、产品事实与不变量 | 技术表示 | +| Requirement README | effective source/status pointer、Design Readiness、decision links | 完整 Concept 或技术设计 | +| ADR | accepted Requirement Model 到技术表示、边界、Design Slice 和验证的落地决策 | 产品语义重定义 | +| Feature Spec | assigned Design Slice 和 feature-local implementation behavior | shared decision 或上游产品语义 | +| Tests / Evidence | 证明产品不变量被保留且技术决策成立 | 反向改写 requirement / ADR | + +## Failure Handling + +| Failure | Required Route | +|---|---| +| Effective source 缺失或无法解析 | 返回 Requirement Archive / Requirements Discussion 修复来源 | +| Concept Foundation 为 `candidate` / `reopened` | 停止 ADR,返回 Human Grill Contract | +| Requirement Model ID 缺失或含义不清 | 返回 Requirements Discussion,不在 ADR 中补定义 | +| Trace coverage 不完整 | ADR 保持 `proposed` | +| Design Slice 未分配 | Feature Spec 保持阻塞 | +| Upstream Compatibility 为 `review-required` | 返回 Decision & Design compatibility review | +| accepted ADR 技术结论失效 | 创建 superseding ADR | + +## 验证策略 + +先新增 focused contract 并确认 RED,再实现 runtime/reference/template 协调修改。 + +Focused tests 至少断言: + +- ADR template 包含 Effective Requirement Snapshot; +- ADR template 包含通用 Requirement Model Technical Landing Trace; +- 默认模板和 validator 保持领域中立,不把讲解示例中的名词、动作或技术落点当成必填内容; +- coverage gate 阻止 missing、空 landing、空 Design Slice、空 Verification; +- source-wide inventory 阻止静默漏掉 `PERM-*`、`EX-*` 或其他 source model; +- proposed structural preflight 不得代替 Human Gate,accepted-mode 必须验证落盘的 Human Review Evidence; +- 外部 decision/spec 引用必须真实存在,未来 owner 必须显式使用 `planned:` 路径; +- reasoned `concept-foundation-not-needed` 路径不得被误拒绝或诱导生成假模型; +- `candidate` / `reopened` requirement 不能进入 ADR acceptance; +- effective source 变化使 compatibility 进入 `review-required`; +- Human Review Summary 有 Decision & Design Approval; +- Product Brief / Feature Spec / root guidance 不得把 ADR 变成产品语义 owner; +- accepted ADR 的 decision meaning 不被原地改写;不兼容时 supersede。 + +该修改触及 Decision / ADR Human Gate、dependency 和 drift 规则,因此实现后必须按 `docs/maintenance/full-validation-method.md` 执行全量语义审计、全部 `tests/*.sh` 和机械检查,并保存新的中文报告。 + +## 预计修改面 + +Runtime / design authority: + +- `SKILL.md` +- `references/runtime.md` +- `references/design.md` +- `references/project-decisions.md` + +Stage / gates / guidance: + +- `references/human-review-summary.md` +- `references/stage-guides.md` +- `references/workflow-checklists.md` +- `references/project-guidance.md` +- `templates/root-AGENTS.md` + +Artifacts / human docs: + +- `templates/decision.md` +- `templates/requirement-set-README.md` only if the current pointer fields are insufficient +- `README.md` +- `Usage.md` +- `CHANGELOG.md` + +Validation: + +- focused regression test +- generic valid/invalid ADR fixtures or validator +- `references/validation-scenarios.md` +- new full-validation report + +## 非目标 + +- 不创建默认 Delivery Contract; +- 不新增 ADR lifecycle status; +- 不创建 Requirement-level 或 Feature-level ADR 目录; +- 不实现 executable Concept / State / Flow schema; +- 不设计 Design Skill 或 E2E Skill; +- 不把讲解示例中的领域行为或技术落点固化成通用要求; +- 不 bump version,除非人类另行批准。 + +## 批准结论 + +1. 采用“增强现有 ADR、不新增 mapping artifact”的方案; +2. Technical Landing Trace 是 ADR acceptance 和依赖 Feature Spec 的 hard gate; +3. effective source 变化后先进入 compatibility review,不兼容时创建 superseding ADR; +4. 实现完成后停在 Human Review Summary,不自动提交或发布; +5. 首轮实现的 validator 绕过必须以对抗性 RED/GREEN 证据修复后才可再次判定完成。 diff --git a/docs/proposal/v1.3.x/concept-foundation-requirement-modeling-implementation-plan.md b/docs/proposal/v1.3.x/concept-foundation-requirement-modeling-implementation-plan.md new file mode 100644 index 0000000..1fc683f --- /dev/null +++ b/docs/proposal/v1.3.x/concept-foundation-requirement-modeling-implementation-plan.md @@ -0,0 +1,115 @@ +# Concept Foundation Requirement Modeling Implementation Plan + +> **For agentic workers:** execute inline under the Agent Loop maintainer rules. Do not create `docs/superpowers/`, target-project `.agent-loop/` artifacts, commits, pushes, PRs, tags, releases, or publications. + +**Goal:** Implement only approved proposal Phase 1 Requirement Concept Foundation and Phase 2 Product Model Derivation on the existing v1.3.0 development line. + +**Architecture:** Keep Concept Foundation inside Requirements Discussion / Requirement Product Grill. The human-reviewed requirement document owns accepted concepts and the Requirement Product Model; Product Brief and Feature Spec consume references without redefining product semantics. Runtime/design own ordering and gates, while templates, root guidance, scenarios, examples, and regression tests remain derived and coordinated. + +**Tech Stack:** Markdown skill sources, Bash regression contracts, Ruby artifact-trace validator. + +--- + +### Task 1: Establish RED Baseline + +**Files:** +- Create: `tests/validate-concept-foundation-requirement-modeling.sh` +- Create after RED: `docs/reports/agent-loop-v1.3.0-concept-foundation-red-baseline-2026-07-12.md` + +- [x] Add contract assertions for internal-method placement, Human Grill Contract ordering, statuses, not-needed route, accepted gate, requirement-product-model derivation, downstream references, and Phase 3/4 exclusions. +- [x] Run `bash tests/validate-concept-foundation-requirement-modeling.sh` before runtime/template edits. +- [x] Confirm failure is caused by missing Concept Foundation behavior rather than shell syntax or a typo. +- [x] Save exact RED output and current `30/30` pre-change repository baseline. + +### Task 2: Implement Phase 1 Runtime Contract + +**Files:** +- Modify: `references/design.md` +- Modify: `references/runtime.md` +- Modify: `SKILL.md` +- Modify: `references/requirement-product-grill.md` +- Modify: `references/requirement-management.md` +- Modify: `references/stage-guides.md` +- Modify: `references/workflow-checklists.md` + +- [x] Define trigger/not-needed routing and `candidate | accepted | reopened | concept-foundation-not-needed` states. +- [x] Put Concept Foundation before Business Flow, State, and Product Data modeling without adding a canonical stage. +- [x] Implement the Human Grill Contract in this exact order: inspect evidence, extract candidate concepts, present recommended definition/evidence/impact, ask one blocking question. +- [x] Block downstream product modeling while a triggered foundation remains `candidate` or `reopened`. +- [x] Keep original human source requirements immutable and keep accepted concept detail in the human-reviewed requirement document. + +### Task 3: Implement Phase 2 Product Model Derivation + +**Files:** +- Modify: `references/product-brief.md` +- Modify: `references/project-decisions.md` only to preserve the PRD ownership boundary; do not add technical mapping +- Modify: `references/stage-guides.md` +- Modify: `references/workflow-checklists.md` +- Modify: `references/document-templates.md` +- Modify: `templates/product.md` +- Modify: `templates/spec.md` + +- [x] Derive Concept Relationships, Role/Permission Matrix, State Model, Commands/Events, Business Flow, Product Data Model, invariants, failures, and recovery from accepted Concept IDs. +- [x] Add upstream-to-downstream traceability inside the requirement document. +- [x] Make Product Brief and Feature Spec cite accepted concepts/model rows instead of redefining shared semantics. +- [x] State that PRD / Requirement Product Model defines the product and ADR consumes accepted product semantics for technical landing. +- [x] Do not add Concept-to-technical-representation tables, Design Skill, E2E Skill, Jam Kits, or executable YAML/JSON schemas. + +### Task 4: Coordinate Root Guidance And Human Surfaces + +**Files:** +- Modify: `templates/root-AGENTS.md` +- Modify: `references/project-guidance.md` +- Modify: `README.md` +- Modify: `Usage.md` +- Modify: `CHANGELOG.md` + +- [x] Add Requirements Discussion signal/reference and required stop for unresolved Concept Foundation. +- [x] Increment same-version managed block revisions without changing v1.3.0. +- [x] Document human trigger wording and the one-question recommendation contract. +- [x] Convert the existing proposal-only changelog entry into implemented Phase 1/2 behavior while preserving the proposal file. + +### Task 5: Add Behavioral Example And Trace Validator + +**Files:** +- Create: `scripts/check-concept-foundation-trace.rb` +- Create: `examples/concept-foundation-refund/requirement.md` +- Create: `examples/concept-foundation-refund/product.md` +- Create: `examples/concept-foundation-refund/spec.md` +- Create: `tests/fixtures/concept-foundation/invalid-unaccepted/` +- Create: `tests/fixtures/concept-foundation/invalid-detached-model/` + +- [x] Make the valid example trace stable Concept IDs through definitions, relationships, states, actions/events, flow, product data, Product Brief, and Feature Spec. +- [x] Reject a triggered foundation that is not accepted. +- [x] Reject a model row or downstream reference detached from a defined Concept ID. +- [x] Reject flows/states/product objects that precede or bypass the accepted foundation. +- [x] Keep examples inside `examples/`; do not create a repository-root `.agent-loop/` tree. + +### Task 6: GREEN, Pressure, And Full Validation + +**Files:** +- Modify: `references/validation-scenarios.md` +- Create: `docs/reports/agent-loop-v1.3.0-full-validation-2026-07-12.md` + +- [x] Add proposal pressure scenarios: overloaded refund completion, User/Customer/Member/Tenant boundaries, Approval action vs instance, historical overdraft conflict, simple copy not-needed, no ADR over-generation, and Concept→Product/Spec trace. +- [x] Run the focused contract until GREEN. +- [x] Run the full `tests/*.sh` suite and record counts. +- [x] Perform the six-domain semantic audit and representative pressure scenarios required by `docs/maintenance/full-validation-method.md`. +- [x] Run YAML, JSON, Shell syntax, Markdown fence balance, trailing-whitespace, and `git diff --check` checks. +- [x] Verify no version-bearing file changed away from 1.3.0 and no Phase 3/4 artifact or target-project `.agent-loop/` path was introduced. + +### Task 7: Human Review Handoff + +- [x] Compare every Phase 1 and Phase 2 proposal item against concrete files/tests/evidence. +- [x] List modified/created files and preserve pre-existing proposal/CHANGELOG ownership in the summary. +- [x] Report RED/GREEN evidence, focused/full validation results, unresolved issues, scope drift, and exactly one recommended next stage. +- [x] Stop before commit, push, PR, merge, tag, release, or publish. + +### Task 8: Review Repair And Adversarial Hardening + +- [x] Reject unconfirmed downstream concepts, missing candidate inventory, unresolved blockers, placeholder not-needed reasons, duplicate IDs, incomplete trace coverage, and command actors without an explicit permission path. +- [x] Add `Effective Concept Source` to Product Brief and Feature Spec and validate both resolve the same accepted source. +- [x] Preserve archived requirement sources; record confirmed reopen changes in an append-only follow-up or a new requirement set and advance the README effective pointer. +- [x] Add the cumulative Concept Foundation Human Review Summary before acceptance. +- [x] Remove implementation-phase wording from distributed ADR authority rules while preserving proposal history. +- [x] Run fresh full validation and save the superseding 2026-07-13 report. diff --git a/docs/proposal/v1.3.x/concept-foundation-requirement-modeling.md b/docs/proposal/v1.3.x/concept-foundation-requirement-modeling.md new file mode 100644 index 0000000..8950f5a --- /dev/null +++ b/docs/proposal/v1.3.x/concept-foundation-requirement-modeling.md @@ -0,0 +1,568 @@ +# Proposal: Concept Foundation For Requirement Modeling + +状态:v1.3.0 Release Human Gate 已批准;发布目标 stable-v1.3.0 + +目标版本:v1.3.x 候选 + +创建时间:2026-07-12 + +## 摘要 + +当前 Agent Loop 已经通过 Requirement/Product Grill、Requirement Document、Product Brief 和 Decision & Design 支持术语澄清、角色、业务流程、异常路径、事实源、产品决策和跨 Feature 设计。 + +现有缺口位于所有这些模型之前:Agent 会在术语模糊时提问,却没有稳定要求复杂需求先固化核心业务概念,再沿着概念推导角色、关系、流程、状态机、产品数据模型、ADR、UI 设计输入和 E2E 场景。结果可能是每一份下游文档单独看都合理,但它们对“订单”“申请”“余额”“审核”“完成”等概念的身份、边界和生命周期理解并不一致。 + +本 proposal 建议在 Requirements Discussion / Requirement Product Grill 内增加触发式 `Concept Foundation` 方法和前置 Gate: + +```text +真实场景与目标 +→ 提取候选概念 +→ Concept Foundation +→ 角色 / 权限与概念关系 +→ 业务流程 +→ 状态机 +→ 产品数据模型 +→ 异常与恢复 +→ Design Readiness / Decision & Design +→ Design Skill / Feature Spec / E2E +``` + +`Concept Foundation` 不是新的 canonical stage,不新增顶层 artifact,也不要求人类先懂领域建模。Agent 先从人类语言、案例、现有项目记忆、代码、测试和历史 Feature 中提取名词、动作和冲突,再一次只提出一个真正阻塞后续模型的问题,并附带推荐定义。 + +## 背景与问题定义 + +### 当前已经具备的能力 + +当前 Requirement/Product Grill 已经要求: + +- 在术语、角色、权限、业务流程、状态流转、异常路径或事实源不清楚时进行针对性澄清; +- 先检查项目记忆、需求来源、代码、测试和相关历史 Feature; +- 把确认后的术语写入 requirement document 的 `Terminology / Domain Language`; +- 把长期或跨 Feature 信号送入 Design Readiness / Decision & Design; +- 避免从模糊聊天直接创建 ADR。 + +当前 requirement document 也已经包含: + +- `Roles / Operators / Permission Boundary`; +- `Terminology / Domain Language`; +- `Primary Business Flow`; +- `Exception Paths`; +- `Data / Source of Truth`; +- `Acceptance Scenarios`; +- `Decision Candidates`。 + +这些能力为 Concept Foundation 提供了可复用基础,不需要推翻现有 Requirements Discussion 或新建复杂领域建模体系。 + +### 当前结构性缺口 + +#### 1. 术语澄清是条件动作,不是复杂需求的前置方法 + +当前规则只在术语模糊或冲突时触发。一个词即使看起来清楚,也可能缺少身份、边界、所有者、生命周期和关系定义。例如“退款”可能表示请求、审核过程、资金动作或最终结果;仅记录一句自然语言定义无法支撑后续状态机和数据模型。 + +#### 2. Requirement 模板先列功能,再补术语 + +现有结构允许 Agent 先写 `Requirements`,再填写 `Terminology / Domain Language` 和 `Primary Business Flow`。这会让下游模型继承未经确认的名词,而不是从稳定概念推导行为。 + +#### 3. 没有 Concept Foundation Gate + +当前没有明确阻止 Agent 在关键概念仍有歧义时继续设计流程、状态机、数据模型、页面或 E2E。人类可能直到看到原型或代码才发现双方对“完成”“取消”“用户”“账户”等概念理解不同。 + +#### 4. 缺少概念到下游模型的推导链 + +当前 artifacts 能记录流程、数据和 Decision Candidate,但没有稳定说明: + +```text +概念名词 → 领域对象 +概念关系 → 产品数据模型 +业务动作 → Command / Event +生命周期 → 状态机 +角色与责任 → 流程泳道和权限 +不变量 → Guard 和校验规则 +失败场景 → 异常、补偿与恢复 +状态与动作 → UI 页面状态和 E2E 场景 +``` + +#### 5. 概念变化没有明确的 Drift 扇出 + +当已确认概念的含义、身份或生命周期变化时,当前规则没有要求统一回查关系、流程、状态机、数据模型、Decision、UI 设计和 E2E。局部修改可能留下多个互相矛盾的真相源。 + +## 目标 + +本 proposal 的目标是: + +1. 让复杂需求在流程和状态设计前形成可确认的 Concept Foundation。 +2. 让 Agent 从真实场景和项目证据提取概念,而不是要求人类先完成抽象领域建模。 +3. 让每个关键概念拥有足以支撑流程、状态机和产品数据模型的定义。 +4. 让概念关系、生命周期、不变量和事实源问题能够稳定进入 Design Readiness。 +5. 让 Decision & Design 引用已确认的产品概念,而不是在技术设计阶段重新发明产品语义。 +6. 给未来偏 UI / 交互的 Design Skill 提供稳定、可消费的页面设计输入。 +7. 给未来 E2E Skill 提供状态转换、业务规则和异常闭环的统一测试来源。 +8. 在概念发生变化时触发可追踪的下游 Drift 检查。 +9. 保持简单需求轻量,不让小改动被迫进入完整概念建模。 + +## 非目标 + +本轮不做以下事情: + +- 不新增 `Concept Foundation` canonical stage; +- 不新增 `.agent-loop/concepts/`、`domain-model/` 或其他顶层目录; +- 不要求所有需求创建 ADR; +- 不要求所有概念进入 `project.md Domain Language`; +- 不把 requirement document 变成数据库 schema 或技术 ERD; +- 不要求人类使用 DDD、UML 或状态机术语与 Agent 对话; +- 不让 Design Skill 自行修改业务概念、状态机或事实源; +- 不让 E2E Skill 从页面结构反推并覆盖业务真相; +- 不在第一版引入 YAML / JSON 可执行 Schema; +- 不改变原始人类需求材料不可静默重写的规则; +- 不改变 Decision、Feature、Submit、Pause、Close 等既有 Human Gate; +- 不在 proposal 阶段修改已发布 runtime、template 或 validation 行为。 + +## 方案比较 + +### 方案 A:只增强术语表 + +做法:给 `Terminology / Domain Language` 增加身份、关系和生命周期列。 + +优点:改动最小,容易落地。 + +限制:仍然没有前置 Gate、推导链和 Drift 规则;Agent 可能填写完表格后继续生成彼此割裂的流程与状态机。 + +### 方案 B:触发式 Concept Foundation + Downstream Traceability + +做法:在 Requirements Discussion 内增加 Concept Foundation 方法、触发条件、Gate、稳定 Concept ID、推导矩阵和下游 Drift 检查。Concept Foundation 仍写在 human-reviewed requirement document,不新增 stage 或目录。 + +优点:直接解决概念不稳导致的流程、状态、数据、UI 和 E2E 漂移;与现有 Requirement/Product Grill、Design Readiness 和 Decision & Design 兼容。 + +代价:正式实施时需要协调 runtime/design、stage guidance、templates、root guidance、validation scenarios 和 regression tests。 + +### 方案 C:可执行领域 Schema + +做法:使用 YAML / JSON 定义实体、关系、状态、事件、Guard 和权限,并让 Design Skill 与 E2E Skill直接读取。 + +优点:机器可读,未来可用于自动生成页面状态矩阵和测试场景。 + +限制:过早固化 Schema 会把产品澄清变成格式维护;还会显著扩大第一版解析、兼容和迁移成本。 + +### 推荐 + +采用方案 B。保留方案 C 作为 Concept Foundation 已被多个真实项目验证后的独立 proposal,不阻塞 v1.3.x。 + +## 核心设计 + +### 1. Concept Foundation 的定位 + +`Concept Foundation` 是 Requirements Discussion / Requirement Product Grill 内部的前置方法,不是新的 stage: + +```text +Requirements Discussion + → Scenario Intake + → Concept Candidate Extraction + → Concept Foundation Gate if triggered + → Business Flow / State / Product Data Modeling + → Human-reviewed Requirement Document + → Requirement Archive +``` + +它只约束产品语义形成的顺序,不改变 canonical stage order。 + +### 2. 触发条件 + +发现任一信号时,Concept Foundation 状态至少进入 `candidate`: + +| Signal | Why It Matters | +|---|---| +| 同一术语可能表示多个对象、动作或结果 | 流程和数据会把不同事物误当成一个概念 | +| 新增或改变有生命周期的业务对象 | 状态机需要稳定身份和生命周期边界 | +| 多角色、租户、运营方或外部系统参与 | 权限、所有权和流程泳道依赖概念定义 | +| 需求会拆成多个 Feature | 各 Feature 必须共享同一业务语言 | +| 涉及事实源、余额、库存、订单、审批、任务或额度 | 数据所有权和不变量通常难以逆转 | +| 现有 Domain Language、代码、测试或历史 Feature 与新说法冲突 | 必须先决定复用、覆盖还是新 scope | +| Design Skill 需要依据数据模型和状态流转设计页面 | 页面不能自行发明业务状态和允许动作 | +| E2E 需要证明跨页面或跨系统闭环 | 测试必须知道对象身份、事件和终态 | + +以下情况可以记录 `concept-foundation-not-needed`: + +- 纯文案、样式或局部布局调整; +- 不改变语义的窄 bugfix; +- 单一配置修改; +- 只复用已有 accepted Domain Language、状态机和事实源,且没有新增概念或关系; +- 没有状态、所有权、跨角色、跨 Feature 或数据语义变化的简单功能。 + +`concept-foundation-not-needed` 必须包含一句理由,不能作为跳过模糊需求澄清的默认值。 + +### 3. Scenario-first 概念提取 + +Agent 不应先问抽象问题“请定义你的领域模型”。推荐方法: + +1. 收集一个正常成功场景和必要的异常场景; +2. 从自然语言中提取名词、动作、结果和约束; +3. 对照项目 Domain Language、原始需求、代码、测试和相关历史 Feature; +4. 合并同义词,拆开一词多义,标记冲突; +5. 形成 Concept Candidate Inventory; +6. 一次只向人类确认一个会改变流程、状态或数据模型的阻塞问题; +7. 每个问题都附带 Agent 的推荐定义、依据和不采用时的影响。 + +示例: + +```text +你提到“客户提交退款,管理员审核后退回余额”。 + +我识别到“退款”可能同时表示退款申请、审核流程和资金退回结果。 +推荐定义: +- Refund Request = 客户发起、可审核的申请对象; +- Refund Review = 管理员对申请作出的审批动作; +- Refund Settlement = 实际资金退回结果。 + +这样状态机和事实源不会把申请状态与资金状态混为一体。 +是否接受这三个概念边界? +``` + +### 4. Concept Candidate Inventory + +在深入确认前,先用轻量清单展示候选概念: + +| Concept ID | Candidate Name | Kind | Evidence / Example | Ambiguity / Conflict | Status | +|---|---|---|---|---|---| +| C-REFUND-REQUEST | Refund Request | entity | human scenario | “退款”也可能指资金结果 | candidate | +| C-REFUND-REVIEW | Refund Review | action / decision | admin flow | 是否允许多次审核 | candidate | +| C-REFUND-SETTLEMENT | Refund Settlement | entity / result | payment callback | 外部支付还是内部余额为事实源 | candidate | + +Concept ID 在 requirement scope 内稳定,用于连接后续关系、流程、状态、Decision Candidate 和验收场景。第一版不要求 Concept ID 成为全项目永久编号。 + +### 5. Concept Definition + +只有影响当前需求的字段才需要填写;不适用字段写 `n/a`,不允许为了完整表格编造事实。 + +| Field | Meaning | +|---|---| +| Concept ID | requirement 内稳定引用,例如 `C-ORDER` | +| Canonical Name | 本需求中的唯一推荐名称 | +| Definition | 精确定义这个概念是什么 | +| Examples / Non-examples | 用正反例排除相邻含义 | +| Identity | 如何判断两个记录是否代表同一个对象 | +| Owner / Responsible Actor | 谁创建、管理、推进或负责该概念 | +| Lifecycle Boundary | 何时产生、何时结束、结束后能否恢复 | +| Relationships | 与其他概念的基数、依赖或包含关系 | +| Invariants | 始终必须成立的业务规则 | +| State-bearing | 是否具有业务状态和状态生命周期 | +| Source Of Truth | 已知事实源或待 Decision & Design 的候选 | +| Synonyms / Avoid | 可接受同义词和禁止混用的词 | +| Evidence | 人类确认、原始需求、代码、测试或历史 Feature | + +推荐 requirement document 结构: + +```md +## Concept Foundation + +Status: concept-foundation-not-needed | candidate | accepted | reopened + +### Concept Candidate Inventory + +### Concept Definitions + +### Concept Relationships + +### Blocking Ambiguities + +### Human Confirmation +``` + +### 6. Concept Foundation Gate + +复杂需求在进入 `Primary Business Flow`、状态机或产品数据模型前必须满足: + +| Check | Gate Rule | +|---|---| +| 关键概念拥有唯一推荐含义 | blocking | +| 一词多义、同义词和历史冲突已处理 | blocking when downstream meaning changes | +| 关键实体的身份和生命周期边界明确 | blocking | +| 角色、所有权和权限边界明确 | blocking when multiple actors participate | +| 关键概念关系明确到足以设计流程 | blocking | +| 哪些概念拥有状态已明确 | blocking | +| 关键不变量已列出 | blocking | +| 事实源已确认或明确标为 Decision Candidate | blocking only if flow cannot proceed without the choice | +| 未解决问题不会改变当前流程模型 | required | +| 人类确认 Concept Foundation | required for triggered complex requirements | + +Gate 输出: + +- `concept-foundation-not-needed`:不触发,记录理由; +- `candidate`:候选概念已提取,但存在会改变下游模型的问题; +- `accepted`:阻塞概念已由人类确认,可以继续推导流程、状态和数据; +- `reopened`:下游设计、代码现实或人类反馈改变了已接受概念,必须先处理 Drift。 + +Concept Foundation 的确认可以作为 Requirements Discussion 的一次前置 Human Gate,也可以与一组概念的 Batch Human Review 合并;它不新增 canonical stage,也不授权 Requirement Archive、ADR 创建或 Feature construction。 + +### 7. 从概念推导下游产品模型 + +Concept Foundation 接受后,Agent 按以下关系推导并检查,而不是把每个 artifact 独立生成: + +| Concept Evidence | Derived Model | Required Check | +|---|---|---| +| Canonical Name + Definition | Domain Language | 后续 artifacts 使用同一名称和含义 | +| Identity + Relationships | Conceptual Product Data Model | 对象、关系和基数不互相矛盾 | +| Owner + Responsible Actor | Role / Permission Matrix | 谁能读取、创建、推进、撤销和恢复 | +| Lifecycle Boundary + State-bearing | State Machine | 初始态、终态、恢复和禁止转换明确 | +| Human Verbs / System Actions | Commands / Events | 每个动作有 actor、precondition 和结果 | +| Invariants | Guards / Validation Rules | 状态转换和数据写入不能破坏不变量 | +| Relationships + Commands + Events | Primary Business Flow | 每一步使用已确认概念并闭合到终态 | +| Failure Scenarios | Exception / Compensation / Recovery | 每类失败有可观察结果和责任方 | +| States + Permissions + Commands | UI Design Input | 页面状态、可见动作和反馈不自行发明规则 | +| Transitions + Invariants + Terminals | E2E Scenario Matrix | 正常、禁止、失败和恢复路径可验证 | + +### 8. Product Data Model 与 Technical Data Model 分离 + +Requirement Document 只描述产品层概念模型: + +- 哪些业务对象存在; +- 对象如何识别; +- 对象之间是什么关系; +- 哪些事实和不变量必须成立; +- 谁拥有或能够改变这些事实。 + +Decision & Design 再决定技术实现: + +- 对应哪些 table、document、event、ledger 或外部 provider; +- 哪个存储是技术事实源; +- 如何实现事务、一致性、并发、幂等和恢复; +- 如何把产品状态映射为系统状态; +- 如何迁移、兼容和验证。 + +ADR 应引用 Concept ID,并增加产品概念到技术实现的映射,而不是重新定义概念: + +| Concept ID | Product Meaning | Technical Representation | Source Of Truth | Invariant Enforcement | Migration / Compatibility | +|---|---|---|---|---|---| + +### 9. 与 Decision & Design / ADR 的关系 + +不是每个概念都生成 ADR。Concept Foundation 只把以下内容标记为 Decision Candidate: + +- 跨 Feature 的共享概念定义; +- 难以逆转的数据身份或事实源; +- 多个合理边界方案之间的真实取舍; +- 会改变项目级所有权、协议、状态语义或一致性模型的选择; +- 如果不记录原因,未来维护者会问“为什么这样定义”的规则。 + +Decision & Design 负责: + +- 接受已确认 Concept Foundation 作为输入; +- 运行 Decision Scan / Placement; +- 记录需要长期保存的边界和取舍; +- 把 Concept、Flow、State、Invariant 和 Recovery 映射为 Design Slice; +- 分配 owning Feature 和验证证据。 + +它不得在没有人类确认的情况下改变 requirement 中已接受的概念语义。 + +### 10. 给 Design Skill 的输入 + +未来偏 UI / 交互的 Design Skill 消费一个由已接受 requirement、Product Brief 和 Decision & Design 派生的只读 `UI Design Input View`。第一版不新增独立 source-of-truth 文件;该 view 可以在 Design Skill 的输入摘要或 owning artifact 中生成。 + +输入至少包括: + +- Concept ID、名称和产品含义; +- 角色、权限和所有权; +- 产品对象关系和页面所需信息; +- 状态、事件、Guard 和允许操作; +- 正常、异常、恢复和人工处理路径; +- 不可被 UI 改写的业务不变量; +- Loading、Empty、Error、Success 所对应的业务语义; +- 尚未解决、禁止 Design Skill 自行假设的问题。 + +Design Skill 可以提交 `Design Feedback`: + +```text +Design Feedback +→ Agent Loop impact scan +→ reopen Concept Foundation / Product / Decision if semantics change +→ human confirmation +→ regenerate affected UI design input +``` + +Design Skill 不得静默增加业务状态、改变事实源或放宽 Guard。 + +### 11. 给 E2E Skill 的输入 + +未来 E2E Skill 使用同一模型生成或检查: + +- 每个关键状态转换的 happy path; +- Guard 不满足时的禁止转换; +- 重复事件和幂等行为; +- 失败、重试、补偿和人工恢复; +- 角色权限与页面动作可见性; +- 成功终态、失败终态和可观察证据; +- 概念不变量在跨页面、跨服务流程中的保持情况。 + +E2E Skill 不从当前页面行为反向覆盖 accepted Concept Foundation;发现不一致时进入 Drift / Design Feedback。 + +### 12. Concept Drift + +已接受概念发生以下变化时,Concept Foundation 状态转为 `reopened`: + +- Canonical Name 的含义改变,而非只改展示文案; +- Identity、Owner、Lifecycle Boundary 或 Relationship 改变; +- State-bearing 属性或业务终态改变; +- Source Of Truth 或关键 Invariant 改变; +- 一个概念被拆分、合并或废弃; +- Design Skill、E2E、实现或历史 Feature 暴露语义冲突。 + +必须执行影响扇出: + +```text +Concept Change +→ Relationship Drift +→ Business Flow Drift +→ State Machine Drift +→ Product Data Model Drift +→ Decision / Design Slice Drift +→ Feature Spec / Test Drift +→ UI Design Drift +→ E2E Drift +→ Project Domain Language update if applicable +``` + +Agent 先列影响和推荐修订,不静默重写人类原始需求或 accepted Decision。 + +## Artifact Ownership + +| Artifact / Surface | Owns | Does Not Own | +|---|---|---| +| Human source requirement | 人类原始表达 | 不被 Agent 静默改写 | +| Requirement document | requirement-local Concept Foundation、关系、流程、状态、产品数据、异常和验收 | 不拥有技术 schema 或项目长期事实 | +| Requirement README | 来源、生命周期、Delivery Phase、Feature Mapping、Design Readiness、decision links,以及归档后 `Effective Concept Foundation` 状态/来源指针 | 不复制完整 Concept Foundation;不改写历史 source | +| `project.md Domain Language` | 人类确认后需要跨 Feature 复用的稳定概念 | 不保存每个需求的局部概念 | +| `product.md` | 当前 Feature 的产品旅程、范围、体验和产品取舍 | 不重新定义 accepted shared concepts | +| `.agent-loop/decisions/*.md` | 跨 Feature、长期、难逆转的概念边界和技术实现决策 | 不成为所有术语的字典 | +| `spec.md` | Feature 行为、验收和 assigned Design Slices | 不复制整套产品模型 | +| Design Skill output | 页面、交互、组件状态和原型 | 不拥有业务概念、事实源或状态机真相 | +| E2E Skill output | 从 accepted 模型推导的端到端验证 | 不通过测试结果静默改写需求语义 | + +## Agent Interaction Contract + +触发 Concept Foundation 时,Agent 必须: + +1. 先读取可用的 Domain Language、source requirement、相关代码/测试和历史 Feature; +2. 用真实场景提取候选概念; +3. 展示推荐定义和证据; +4. 一次只问一个会改变下游模型的阻塞问题; +5. 说明接受或拒绝推荐定义分别影响哪些流程、状态或数据设计; +6. 在进入 Business Flow 前展示 Concept Foundation Human Review Summary; +7. 未获得确认时保留 `candidate`,不伪装为 accepted; +8. 简单需求不触发时记录 `concept-foundation-not-needed` 和理由。 + +推荐 Human Review Summary: + +| Concept ID | Recommended Definition | Identity / Boundary | Relationship | State / Lifecycle | Open Conflict | Decision | +|---|---|---|---|---|---|---| + +## 实施影响面 + +本 proposal 获得实施批准后,至少需要协调检查和更新: + +- `references/design.md`:加入 concept-first 的核心约束; +- `references/runtime.md`:Requirements Discussion routing、Gate 和 stop condition; +- `SKILL.md`:精简入口和对应 reference 路由; +- `references/requirement-product-grill.md`:Scenario-first、Concept Foundation、提问和输出映射; +- `references/requirement-management.md`:requirement review、archive 和 concept status; +- `references/project-decisions.md`:Concept ID、产品到技术映射和 concept drift; +- `references/product-brief.md`:复用 accepted concepts,不重新定义共享语义; +- `references/stage-guides.md`:Requirements Discussion、Requirement Archive、Decision & Design、Product Brief、Feature Spec; +- `references/workflow-checklists.md`:Concept Foundation Gate 和 downstream traceability; +- `references/document-templates.md`:Requirement Document 的 Concept Foundation、关系、状态和产品数据模型; +- `templates/decision.md`:产品概念到技术实现映射; +- `templates/product.md`、`templates/spec.md`、`templates/tests.md`:Concept / State / Design Slice 引用; +- `templates/root-AGENTS.md` 和 `references/project-guidance.md`:root Stage Map signal/reference 与 required stop; +- `README.md`、`Usage.md`、`CHANGELOG.md`:人类触发方式和版本行为; +- `references/validation-scenarios.md` 与 focused regression tests; +- `examples/`:至少一个状态型业务需求的完整 Concept → Flow → State → Decision → Feature 示例。 + +由于正式实施会改变 Requirements Discussion 的 Gate、stop rule 和跨文件 workflow invariant,必须按 `docs/maintenance/full-validation-method.md` 执行完整验证并保存中文报告。Proposal 阶段不把这些规则声明为已发布能力。 + +## 验证与压力场景 + +正式实现至少需要覆盖: + +### 1. 一词多义 + +人类说“退款完成”,但申请审批完成和资金到账完成是两个不同终态。Agent 必须拆分概念并在流程前确认。 + +### 2. 相邻概念混用 + +需求把 User、Customer、Member 和 Tenant 混用。Agent 必须检查项目 Domain Language 并提出 canonical boundaries。 + +### 3. 状态对象识别 + +“审批”既可能是动作,也可能是有生命周期的 Approval Instance。Agent 必须通过场景判断是否需要独立对象。 + +### 4. 历史冲突 + +历史 Feature 定义余额不足立即停止服务,新需求描述允许透支。Agent 必须先提出冲突和推荐选择,不能直接生成新流程。 + +### 5. 简单需求保持轻量 + +按钮文案修改且不改变权限、状态和数据语义。Agent 应记录 `concept-foundation-not-needed`,不得生成大型概念表。 + +### 6. ADR 不过度生成 + +单 Feature 内局部筛选概念不应创建 project decision;共享事实源或跨 Feature 状态语义才进入 Decision Candidate。 + +### 7. Design Feedback 回流 + +Design Skill 发现某状态下没有可恢复入口。Agent 必须判断是 UI 缺失还是业务模型缺失;后者重新打开 Concept Foundation / Decision,而不是让 Design Skill自行增加状态。 + +### 8. E2E 追踪 + +每个关键状态转换、禁止转换和恢复终态都能追踪到 acceptance / tests,不允许只验证页面存在。 + +### 9. Concept Drift 扇出 + +accepted Concept 的身份或生命周期改变时,Agent 必须列出 Flow、State、Data、Decision、Feature、UI 和 E2E 影响,并停在 Human Gate。 + +## 分阶段实施建议 + +### Phase 1: Requirement Concept Foundation + +- 加入触发条件、Concept Candidate Inventory、Definition、Gate 和 requirement template; +- 增加简单需求 not-needed 路由; +- 增加 pressure scenarios 和 focused regression test。 + +### Phase 2: Product Model Derivation + +- 增加 Concept Relationship、State Machine、Product Data Model 和 Business Flow 推导规则; +- 增加 upstream-to-downstream traceability; +- 更新 Product Brief 和 Feature Spec 引用。 + +### Phase 3: Decision & Design Mapping + +- 增加 Concept ID 到 technical representation、source of truth、invariant enforcement 的映射; +- 把 shared concepts、states、flows 和 recovery obligations 转为 Design Slices; +- 加入 Concept Drift 检查。 + +### Phase 4: Jam Kits Integration + +- 定义 Design Skill 的 UI Design Input View; +- 定义 Design Feedback 回流; +- 定义 E2E Skill 的 State / Invariant / Terminal 输入; +- 等真实项目验证稳定后,再评估可执行 Schema proposal。 + +各 Phase 只是实施切片,不改变 Human Gate。若分阶段发布,每个阶段都必须保持 artifacts 和 routing 自洽,不能让 Requirement 模板先产生无法被下游消费的字段。 + +## 批准时的决策与后续问题 + +1. Phase 1 / Phase 2 使用 requirement-local 稳定 Concept ID;项目级永久编号不属于本轮。 +2. Concept Foundation Human Gate 是 Requirements Discussion 内的产品语义确认;按 Human Grill Contract 每次只确认一个 downstream-blocking 问题,不用模糊 Batch Review 绕过。 +3. 产品层 Concept Relationship 和 State Model 以可追踪表格为 source of truth;可视化图仅作派生视图。 +4. `UI Design Input View` 的 artifact 形式保留到 Phase 4 / Jam Kits Integration 再评审。 +5. Concept Drift 的 rename 自动传播与 Human Gate 分界保留到 Phase 3 再评审。 + +## 推荐结论 + +建议接受以下方向进入后续实施规划: + +```text +Concept Foundation 是复杂 Requirements Discussion 内部的前置 Gate。 +它不新增 canonical stage 或顶层 artifact。 +Agent 从真实场景和项目证据提取概念,并由人类确认阻塞语义。 +流程、状态机、产品数据模型、ADR、UI Design 和 E2E 都沿 accepted concepts 推导。 +简单需求使用 concept-foundation-not-needed 保持轻量。 +概念变化触发显式 downstream drift,不静默改写 accepted artifacts。 +``` diff --git a/docs/proposal/v1.3.x/cross-platform-python-script-runtime-implementation-plan.md b/docs/proposal/v1.3.x/cross-platform-python-script-runtime-implementation-plan.md new file mode 100644 index 0000000..6a0da79 --- /dev/null +++ b/docs/proposal/v1.3.x/cross-platform-python-script-runtime-implementation-plan.md @@ -0,0 +1,1022 @@ +# Cross-Platform Python Script Runtime Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use `executing-plans` to implement this plan task-by-task after Plan Human Review. Use `test-driven-development` for every port, `verification-before-completion` before completion claims, and `requesting-code-review` before handoff. Subagent execution is not authorized by this plan. + +**Goal:** Replace the four current Bash/Ruby checker implementations with Python 3.10+ standard-library canonical implementations that preserve existing safety behavior and run natively on Windows and macOS. + +**Execution Status:** Implemented and verified on macOS and Windows. Commit `7253461` passed all four GitHub Actions jobs (`macos-latest` / `windows-latest` × Python 3.10 / 3.x) on 2026-07-14: . The v1.3.0 Release Human Gate was approved on 2026-07-14; the exact release-evidence commit must pass the same CI matrix before creating `stable-v1.3.0`. + +**Architecture:** A small `scripts/checker_support.py` module owns deterministic UTF-8 Markdown parsing, table parsing, path confinement, and CLI failure semantics. Four standalone `.py` entrypoints own their existing checker-specific rules. Existing `.sh` / `.rb` paths remain one-cycle compatibility launchers only; cross-platform `unittest` suites invoke the canonical scripts with `sys.executable` and run unchanged fixtures on Windows and macOS. + +**Tech Stack:** Python 3.10+ standard library (`argparse`, `dataclasses`, `datetime`, `hashlib`, `json`, `pathlib`, `re`, `shutil`, `subprocess`, `tempfile`, `unittest`), existing Markdown fixtures, optional GitHub Actions matrix for `macos-latest` and `windows-latest`. + +--- + +## Execution Boundary + +This plan implements only the approved `Cross-Platform Python Script Runtime` proposal. + +Included: + +- port four checker entrypoints to canonical Python; +- preserve valid/invalid behavior, exit-code classes, path confinement, and read-only guarantees; +- add Windows/macOS-native Python tests; +- convert old Bash/Ruby entrypoints to compatibility launchers after parity passes; +- update current authoritative script references and current-version changelog; +- save a focused validation report and run the repository-required full validation. + +Excluded: + +- Feature Monthly Compaction implementation; +- rewriting all `tests/*.sh` as Python; +- rewriting historical proposals or reports; +- generic YAML parsing; +- third-party Python dependencies; +- version bump, commit, push, tag, PR, release, or publish without a later explicit Human Gate. + +## Stage Helper Resolution + +```text +Stage: Plan Gate / Plan If Needed +Canonical Candidate: superpowers:writing-plans +Canonical Result: unavailable in current runtime +Alias Candidate: writing-plans +Resolved Helper: writing-plans +Status: loaded +Load Evidence: /Users/shaodowyd/.codex/skills/writing-plans/SKILL.md read completely on 2026-07-13 +Fallback Used: no +Agent Loop Path Override: docs/proposal/v1.3.x/cross-platform-python-script-runtime-implementation-plan.md +Human Gates Preserved: plan acceptance; commit; push; tag; PR; release; publish +``` + +## File Responsibility Map + +### New canonical runtime files + +| File | Responsibility | +|---|---| +| `scripts/checker_support.py` | Shared UTF-8 reading, metadata/section/table parsing, concrete-value checks, path confinement, stable diagnostics | +| `scripts/check-root-agents-blocks.py` | Read-only managed-block parser and root `AGENTS.md` drift report | +| `scripts/check-onboarding-core-flow-coverage.py` | Core Flow Inventory, slice, diagram, section, direction, and hard-gate coverage validation | +| `scripts/check-concept-foundation-trace.py` | Concept Foundation, Requirement Product Model, permission, action, state, exception, and downstream trace validation | +| `scripts/check-adr-requirement-model-trace.py` | Effective source, model inventory, landing trace, operational trigger, Human Review, path, and coverage validation | + +### New cross-platform tests + +| File | Responsibility | +|---|---| +| `tests/checker_test_support.py` | `sys.executable` subprocess runner, repo paths, UTF-8 fixture helpers, stable assertion helpers | +| `tests/test_python_checker_contract.py` | Python version, canonical file inventory, stdlib-only imports, help/usage and read-only contract | +| `tests/test_root_agents_blocks.py` | Native replacement for root checker behavioral fixture generation | +| `tests/test_onboarding_core_flow_coverage.py` | Valid, deferred, missing-recovery, detached-trace, CRLF, and BOM behavior | +| `tests/test_concept_foundation_trace.py` | Valid/not-needed/invalid/adversarial Concept Foundation cases | +| `tests/test_adr_requirement_model_trace.py` | Proposed/accepted/not-needed/invalid/adversarial ADR cases and workspace confinement | +| `.github/workflows/cross-platform-checkers.yml` | Run the same `unittest` command on Windows and macOS using Python 3.10 and current stable Python | + +### Compatibility and active-reference files + +| File | Planned change | +|---|---| +| `scripts/check-root-agents-blocks.sh` | Replace embedded Ruby rules with a thin Python launcher after parity passes | +| `scripts/check-onboarding-core-flow-coverage.rb` | Replace validation rules with a thin Python launcher after parity passes | +| `scripts/check-concept-foundation-trace.rb` | Replace validation rules with a thin Python launcher after parity passes | +| `scripts/check-adr-requirement-model-trace.rb` | Replace validation rules with a thin Python launcher after parity passes | +| `tests/validate-root-agents-block-checker.sh` | Invoke canonical Python checker and retain the existing POSIX contract wrapper | +| `tests/validate-root-agents-block-refresh.sh` | Expect canonical `.py` references | +| `tests/validate-onboarding-core-flow-completeness.sh` | Invoke canonical Python checker | +| `tests/validate-concept-foundation-requirement-modeling.sh` | Invoke canonical Python checker and Python adversarial suite | +| `tests/validate-adr-requirement-model-technical-landing-trace.sh` | Invoke canonical Python checker and Python adversarial suite | +| `tests/validate-concept-foundation-trace-adversarial.rb` | Preserve as historical compatibility test during this version; canonical cases move to `test_concept_foundation_trace.py` | +| `tests/validate-adr-requirement-model-trace-adversarial.rb` | Preserve as historical compatibility test during this version; canonical cases move to `test_adr_requirement_model_trace.py` | + +### Current authority and human-facing files + +| File | Planned change | +|---|---| +| `SKILL.md` | Publish the canonical `.py` root checker path and Python capability rule | +| `Usage.md` | Show native Windows/macOS invocation examples | +| `references/project-guidance.md` | Use the canonical Python root checker and fail closed when Python is unavailable | +| `references/workflow-checklists.md` | Use the canonical Python root checker in both guidance checks | +| `CHANGELOG.md` | Record the current 1.3.0 script portability behavior without a version bump | +| `docs/reports/agent-loop-v1.3.0-cross-platform-python-script-runtime-validation-2026-07-13.md` | Record RED, parity, macOS evidence, Windows CI evidence, remaining limitations, and full-validation result | + +Historical `docs/reports/*` and completed implementation plans that cite `.rb` / `.sh` remain unchanged. + +## Exit-Code Contract + +All canonical checkers use: + +```text +0 = validation passed or --help completed +1 = checked artifact violates the contract +2 = command usage, missing argument, missing input, unsupported Python, or capability error +``` + +The old scripts currently mix `abort` exit `1` with explicit usage exit `2`. Before replacing an old implementation, capture its current fixture result and lock the intended exit class above in the Python tests. Human-readable wording may improve, but every diagnostic must retain a stable error category substring used by regression tests. + +## Task 1: Establish RED Portability And Standard-Library Contract + +**Files:** + +- Create: `tests/checker_test_support.py` +- Create: `tests/test_python_checker_contract.py` +- Read: `scripts/check-root-agents-blocks.sh` +- Read: `scripts/check-onboarding-core-flow-coverage.rb` +- Read: `scripts/check-concept-foundation-trace.rb` +- Read: `scripts/check-adr-requirement-model-trace.rb` + +- [ ] **Step 1: Add the shared subprocess test helper** + +Create `tests/checker_test_support.py` with this complete interface: + +```python +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def run_checker(script: str, *args: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [sys.executable, str(ROOT / script), *map(str, args)], + cwd=str(cwd or ROOT), + check=False, + capture_output=True, + text=True, + encoding="utf-8", + ) + + +def combined_output(result: subprocess.CompletedProcess[str]) -> str: + return (result.stdout + result.stderr).strip() +``` + +- [ ] **Step 2: Write the failing canonical inventory and stdlib-only tests** + +Create `tests/test_python_checker_contract.py` with the canonical inventory below. Parse imports with `ast`, allow modules in `sys.stdlib_module_names`, and allow only the local module `checker_support` outside that set. + +```python +from __future__ import annotations + +import ast +import sys +import unittest + +from checker_test_support import ROOT, combined_output, run_checker + + +CHECKERS = ( + "scripts/check-root-agents-blocks.py", + "scripts/check-onboarding-core-flow-coverage.py", + "scripts/check-concept-foundation-trace.py", + "scripts/check-adr-requirement-model-trace.py", +) + + +class PythonCheckerContractTests(unittest.TestCase): + def test_python_runtime_is_supported(self) -> None: + self.assertGreaterEqual(sys.version_info[:2], (3, 10)) + + def test_canonical_checker_files_exist(self) -> None: + for relative in CHECKERS: + with self.subTest(relative=relative): + self.assertTrue((ROOT / relative).is_file(), relative) + + def test_canonical_checkers_use_only_stdlib_and_local_support(self) -> None: + allowed_local = {"checker_support"} + for relative in CHECKERS: + path = ROOT / relative + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + imported: set[str] = set() + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imported.update(alias.name.split(".", 1)[0] for alias in node.names) + elif isinstance(node, ast.ImportFrom) and node.module: + imported.add(node.module.split(".", 1)[0]) + external = imported - set(sys.stdlib_module_names) - allowed_local + self.assertEqual(external, set(), f"{relative}: {sorted(external)}") + + def test_missing_arguments_fail_with_usage_exit_two(self) -> None: + for relative in CHECKERS: + with self.subTest(relative=relative): + result = run_checker(relative) + self.assertEqual(result.returncode, 2, combined_output(result)) + self.assertIn("usage", combined_output(result).lower()) + + +if __name__ == "__main__": + unittest.main() +``` + +- [ ] **Step 3: Run RED and record the expected failure** + +Run: + +```text +python3 -m unittest tests/test_python_checker_contract.py -v +``` + +Expected: FAIL at `test_canonical_checker_files_exist` because the four `.py` entrypoints do not exist. Record this exact RED result in the final validation report draft; do not create the report before the implementation stage is authorized. + +- [ ] **Step 4: Capture current old-script fixture outcomes without editing them** + +Run: + +```text +bash tests/validate-root-agents-block-checker.sh +bash tests/validate-onboarding-core-flow-completeness.sh +bash tests/validate-concept-foundation-requirement-modeling.sh +bash tests/validate-adr-requirement-model-technical-landing-trace.sh +``` + +Expected: all four commands PASS before porting. Any failure is a pre-existing blocker and must route to Diagnose Failure rather than being normalized into the port. + +- [ ] **Step 5: Review checkpoint** + +Inspect `git diff -- tests/checker_test_support.py tests/test_python_checker_contract.py`. Do not commit. Agent Loop commit authorization remains absent. + +## Task 2: Build Shared Markdown Support And Port Root AGENTS Checker + +**Files:** + +- Create: `scripts/checker_support.py` +- Create: `scripts/check-root-agents-blocks.py` +- Create: `tests/test_root_agents_blocks.py` +- Reference behavior: `scripts/check-root-agents-blocks.sh:1` +- Reference fixtures: `tests/validate-root-agents-block-checker.sh:1` + +- [ ] **Step 1: Write root-checker RED tests using only Python fixture operations** + +`tests/test_root_agents_blocks.py` must copy `templates/root-AGENTS.md` into `tempfile.TemporaryDirectory()` and cover these exact outcomes: + +```text +PASS current template +FAIL missing message-intent +FAIL missing workflow-stage-map +FAIL stale block-version +FAIL broken end marker +FAIL nested block +FAIL duplicate section +FAIL unexpected managed section +FAIL missing local source +FAIL malformed bare start +FAIL malformed bare end +FAIL two markers on one line +PASS --no-source-check when only the local source is missing +``` + +Use this invocation pattern: + +```python +result = run_checker( + "scripts/check-root-agents-blocks.py", + "--template", str(template), + "--target", str(target), +) +self.assertEqual(result.returncode, expected_code, combined_output(result)) +``` + +Run: + +```text +python3 -m unittest tests/test_root_agents_blocks.py -v +``` + +Expected: FAIL because `scripts/check-root-agents-blocks.py` does not exist. + +- [ ] **Step 2: Implement shared Markdown primitives** + +Create `scripts/checker_support.py` with these concrete contracts: + +```python +from __future__ import annotations + +import re +from datetime import date +from pathlib import Path + + +class CheckFailure(RuntimeError): + pass + + +def read_text(path: str | Path) -> str: + candidate = Path(path) + if not candidate.is_file(): + raise CheckFailure(f"missing file: {candidate}") + return candidate.read_bytes().decode("utf-8-sig").replace("\r\n", "\n").replace("\r", "\n") + + +def metadata(content: str, label: str) -> str | None: + match = re.search(rf"^{re.escape(label)}:\s*(.+?)\s*$", content, re.MULTILINE) + return match.group(1).strip() if match else None + + +def section(content: str, title: str, level: int = 2, required: bool = True) -> str | None: + marker = "#" * level + match = re.search( + rf"^{re.escape(marker)} {re.escape(title)}\s*$\n(.*?)(?=^#{{1,{level}}}\s|\Z)", + content, + re.MULTILINE | re.DOTALL, + ) + if not match and required: + raise CheckFailure(f"missing section: {marker} {title}") + return match.group(1) if match else None + + +def split_row(line: str) -> list[str]: + return [cell.strip() for cell in line.strip().strip("|").split("|")] + + +def table(content: str, title: str, level: int = 2) -> list[dict[str, str]]: + body = section(content, title, level) + assert body is not None + lines = [line.strip() for line in body.splitlines()] + start = next((index for index, line in enumerate(lines) if line.startswith("|")), None) + if start is None: + raise CheckFailure(f"missing table in section: {title}") + table_lines: list[str] = [] + for line in lines[start:]: + if not line.startswith("|"): + break + table_lines.append(line) + if len(table_lines) < 3: + raise CheckFailure(f"missing table in section: {title}") + headers = split_row(table_lines[0]) + rows: list[dict[str, str]] = [] + for line in table_lines[2:]: + cells = split_row(line) + if len(cells) != len(headers): + raise CheckFailure(f"column count mismatch in section: {title}") + rows.append(dict(zip(headers, cells, strict=True))) + if not rows: + raise CheckFailure(f"empty table in section: {title}") + return rows + + +def parse_iso_date(value: str | None, context: str) -> date: + if not value: + raise CheckFailure(f"{context} is missing") + try: + return date.fromisoformat(value) + except ValueError as error: + raise CheckFailure(f"{context} must be YYYY-MM-DD") from error + + +def confined_path(root: str | Path, relative: str) -> Path: + root_path = Path(root).resolve() + candidate = (root_path / relative).resolve(strict=False) + try: + candidate.relative_to(root_path) + except ValueError as error: + raise CheckFailure(f"reference escapes workspace root: {relative}") from error + return candidate +``` + +- [ ] **Step 3: Implement the root checker as a read-only Python CLI** + +Create `scripts/check-root-agents-blocks.py` with: + +- `ManagedBlock` and `MarkerError` dataclasses; +- regexes equivalent to current `START_RE` and `END_RE`; +- `parse_blocks(path) -> tuple[dict[str, ManagedBlock], list[MarkerError]]`; +- `local_source(source) -> bool`; +- `build_findings(template, target, check_sources) -> list[list[str]]`; +- an `argparse` CLI supporting `--template`, `--target`, `--no-source-check`; +- exact PASS prefix `PASS root AGENTS managed blocks are current`; +- exact FAIL prefix `FAIL root AGENTS drift found` and the six-column Markdown table; +- exit `2` for missing arguments/files, `1` for drift, `0` for PASS/help. + +Use `Path.exists()` only; never write the template, target, or source paths. + +- [ ] **Step 4: Run GREEN and the original contract** + +Run: + +```text +python3 -m unittest tests/test_python_checker_contract.py tests/test_root_agents_blocks.py -v +bash tests/validate-root-agents-block-checker.sh +``` + +Expected: Python tests PASS; the original shell test still PASS against the old compatibility path at this stage. + +- [ ] **Step 5: Review checkpoint** + +Run `python3 -m py_compile scripts/checker_support.py scripts/check-root-agents-blocks.py tests/test_root_agents_blocks.py`. Inspect the diff. Do not commit. + +## Task 3: Port Onboarding Core Flow Coverage Checker + +**Files:** + +- Create: `scripts/check-onboarding-core-flow-coverage.py` +- Create: `tests/test_onboarding_core_flow_coverage.py` +- Reference behavior: `scripts/check-onboarding-core-flow-coverage.rb:1` +- Existing fixtures: `tests/fixtures/onboarding-core-flow/` +- Existing example: `examples/ai-meeting-minutes-backend/onboarding-db/` + +- [ ] **Step 1: Write failing native tests** + +Cover: + +```text +PASS examples/ai-meeting-minutes-backend/onboarding-db +PASS tests/fixtures/onboarding-core-flow/valid-deferred +FAIL invalid-missing-recovery with missing required slice/recovery diagnostic +FAIL invalid-detached-trace with missing diagram/section trace diagnostic +PASS a temporary copy converted to CRLF +PASS a temporary copy whose first Markdown file starts with UTF-8 BOM +FAIL missing onboarding root with exit 2 +``` + +Run `python3 -m unittest tests/test_onboarding_core_flow_coverage.py -v`. + +Expected: FAIL because the canonical checker does not exist. + +- [ ] **Step 2: Port the validator without changing its semantic surface** + +Implement a `CoreFlowCoverage` class by translating the current methods one-for-one: + +| Python method | Ruby source | Exact responsibility | +|---|---|---| +| `validate` | lines 16-55 | read five required artifacts, select critical/important rows, require planned/deferred, count both branches | +| `validate_deferred` | lines 59-63 | require `impact=`, `missing=`, and `next=` fields | +| `validate_planned` | lines 65-112 | validate slices, flow docs, placeholders, diagrams, section targets, symbol/config evidence, call/data direction, and hard gates | +| `read_required` | lines 114-119 | choose the first existing candidate and decode it with `read_text` | +| `read_flow_docs` | lines 121-127 | deterministically sort `03-flows/*.md`, otherwise use `flow.md` | +| `require_token` | lines 129-131 | reject a missing flow/slice token | +| `require_hard_gate_pass` | lines 133-136 | require the flow row to contain `PASS` | +| `require_hard_gate_before_score` | lines 138-146 | require the gate and ensure it precedes `## Score` | + +Use this complete CLI boundary around the translated class: + +```python +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Validate onboarding core-flow coverage") + parser.add_argument("onboarding_root") + args = parser.parse_args(argv) + try: + planned, deferred = CoreFlowCoverage(Path(args.onboarding_root)).validate() + except CheckFailure as error: + print(str(error), file=sys.stderr) + return 1 + print(f"PASS: core-flow coverage trace is complete ({planned} planned, {deferred} deferred)") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) +``` + +The class must preserve all checks from Ruby lines 16-146 in the same order so first-failure categories remain stable. + +CLI output remains: + +```text +PASS: core-flow coverage trace is complete (1 planned, 0 deferred) +``` + +- [ ] **Step 3: Run GREEN and focused legacy tests** + +Run: + +```text +python3 -m unittest tests/test_onboarding_core_flow_coverage.py -v +python3 scripts/check-onboarding-core-flow-coverage.py examples/ai-meeting-minutes-backend/onboarding-db +bash tests/validate-onboarding-core-flow-completeness.sh +``` + +Expected: all PASS. + +- [ ] **Step 4: Review checkpoint** + +Run `python3 -m py_compile scripts/check-onboarding-core-flow-coverage.py tests/test_onboarding_core_flow_coverage.py`. Inspect the diff. Do not commit. + +## Task 4: Port Concept Foundation Trace Checker + +**Files:** + +- Create: `scripts/check-concept-foundation-trace.py` +- Create: `tests/test_concept_foundation_trace.py` +- Reference behavior: `scripts/check-concept-foundation-trace.rb:1` +- Reference adversarial behavior: `tests/validate-concept-foundation-trace-adversarial.rb:1` +- Existing examples/fixtures: `examples/concept-foundation-refund/`, `tests/fixtures/concept-foundation/` + +- [ ] **Step 1: Write failing native tests** + +Port all current adversarial cases by creating temporary copies and transforming text with Python `str.replace`, `re.sub`, and `splitlines(keepends=True)`. The test suite must reject: + +```text +downstream use of unconfirmed concepts +missing Concept Candidate Inventory +open blocking ambiguity +non-concrete concept-foundation-not-needed reason +downstream model omitted from Concept-To-Product trace +duplicate Concept Definition ID +missing effective Concept Foundation source +command actor without target permission +invalid-unaccepted fixture +invalid-detached-model fixture +``` + +It must accept the current complete refund example and a reasoned not-needed case. Run `python3 -m unittest tests/test_concept_foundation_trace.py -v`; expected RED is missing canonical checker. + +- [ ] **Step 2: Port shared table and ID validation logic** + +Use `checker_support.metadata`, `section`, `table`, and `read_text`. Implement these checker-local helpers with Python `set` semantics: + +```python +def ids(text: str, pattern: re.Pattern[str]) -> set[str]: + return set(pattern.findall(text or "")) + + +def assert_defined(used: set[str], defined: set[str], context: str) -> None: + missing = used - defined + if missing: + raise CheckFailure(f"undefined IDs in {context}: {', '.join(sorted(missing))}") + + +def assert_unique_ids(values: list[str], context: str, pattern: re.Pattern[str]) -> None: + invalid = sorted({value for value in values if pattern.fullmatch(value) is None}) + if invalid: + raise CheckFailure(f"invalid IDs in {context}: {', '.join(invalid)}") + duplicates = sorted({value for value in values if values.count(value) > 1}) + if duplicates: + raise CheckFailure(f"duplicate IDs in {context}: {', '.join(duplicates)}") + + +def assert_confirmed(used: set[str], confirmed: set[str], context: str) -> None: + unconfirmed = used - confirmed + if unconfirmed: + raise CheckFailure(f"unconfirmed Concept IDs in {context}: {', '.join(sorted(unconfirmed))}") + + +def reject_placeholders(content: str, path: Path) -> None: + forbidden = ("", "C-EXAMPLE", "C-OTHER", "TBD", "TODO", "待补充") + found = [token for token in forbidden if token in content] + if found: + raise CheckFailure(f"placeholder content in {path}: {', '.join(found)}") +``` + +Port every current validation block in source order so the first-failure category remains stable: + +```text +status and effective-source alignment +reasoned not-needed branch +section order +candidate/definition/confirmation/ambiguity +relationships +permission matrix +commands/events and actor permission +primary flow +state model +requirement product model +exception paths +Concept-To-Product trace completeness +Product Brief / Feature Spec no-redefinition rules +downstream concept/model coverage +``` + +- [ ] **Step 3: Preserve CLI and PASS behavior** + +CLI accepts exactly `REQUIREMENT PRODUCT SPEC`; usage/missing input returns `2`, validation failures return `1`, and success returns `0` with one of: + +```text +PASS: reasoned concept-foundation-not-needed trace +PASS: accepted Concept Foundation trace is complete (4 concepts, 9 model rows) +``` + +- [ ] **Step 4: Run GREEN and original focused tests** + +Run: + +```text +python3 -m unittest tests/test_concept_foundation_trace.py -v +python3 scripts/check-concept-foundation-trace.py examples/concept-foundation-refund/requirement.md examples/concept-foundation-refund/product.md examples/concept-foundation-refund/spec.md +bash tests/validate-concept-foundation-requirement-modeling.sh +``` + +Expected: all PASS. + +- [ ] **Step 5: Review checkpoint** + +Run `python3 -m py_compile scripts/check-concept-foundation-trace.py tests/test_concept_foundation_trace.py`. Inspect the diff. Do not commit. + +## Task 5: Port ADR Requirement Model Technical Landing Checker + +**Files:** + +- Create: `scripts/check-adr-requirement-model-trace.py` +- Create: `tests/test_adr_requirement_model_trace.py` +- Reference behavior: `scripts/check-adr-requirement-model-trace.rb:1` +- Reference adversarial behavior: `tests/validate-adr-requirement-model-trace-adversarial.rb:1` +- Existing fixtures: `tests/fixtures/adr-technical-landing/` + +- [ ] **Step 1: Write failing fixture and adversarial tests** + +The Python suite must accept: + +```text +proposed structural preflight without Human Review Evidence +accepted ADR with complete Human Review Evidence +planned canonical Feature Spec owner +source model delegated to an existing proposed decision +reasoned concept-foundation-not-needed ADR +``` + +It must reject: + +```text +accepted ADR without Human Review Evidence +placeholder not-applicable reason +arbitrary or extra Coverage Hard Gate items +garbage Accepted Requirement Model ID +silently omitted source model +missing accepted-decision target +missing feature-local target +invalid Design Slice coverage status +triggered operational concern without detail +incomplete operational concern inventory +invalid-missing-coverage fixture +invalid-empty-landing fixture +invalid-unaccepted-source fixture +invalid-reopened-source fixture +invalid-review-required fixture +workspace escape using ../ +Windows-style case-collision reference where two existing paths differ only by case +``` + +Run `python3 -m unittest tests/test_adr_requirement_model_trace.py -v`; expected RED is missing canonical checker. + +- [ ] **Step 2: Port parser, value, date, and confinement helpers** + +Use shared support where behavior is identical. Implement checker-local functions matching the current Ruby contracts: + +```python +def normalized(value: str | None) -> str: + return (value or "").strip().strip("`") + + +def concrete(value: str | None) -> bool: + text = normalized(value) + if not text or re.fullmatch(r"-|none|n/a|na|not applicable|tbd|todo|unknown", text, re.IGNORECASE): + return False + return re.search(r"<[^>]+>", text) is None + + +def concrete_reason(value: str | None) -> bool: + text = normalized(value) + return concrete(text) and len(text) >= 12 + + +def assert_unique(values: list[str], context: str) -> None: + duplicates = sorted({value for value in values if values.count(value) > 1}) + if duplicates: + raise CheckFailure(f"duplicate IDs in {context}: {', '.join(duplicates)}") + + +def parse_id_list( + value: str | None, + pattern: re.Pattern[str], + context: str, + allow_none: bool = False, +) -> list[str]: + text = (value or "").strip() + if allow_none and text.casefold() == "none": + return [] + if not text: + raise CheckFailure(f"{context} is missing") + tokens = [normalized(token) for token in text.split(",")] + invalid = [token for token in tokens if pattern.fullmatch(token) is None] + if invalid: + raise CheckFailure(f"invalid values in {context}: {', '.join(invalid)}") + assert_unique(tokens, context) + return tokens + + +def markdown_path(value: str | None) -> str | None: + match = re.search(r"([A-Za-z0-9._/-]+\.md)\b", normalized(value)) + return match.group(1) if match else None +``` + +Translate the remaining complex validators one-for-one from these exact sources: + +| Python function | Ruby source | Required behavior | +|---|---|---| +| `validate_decision_reference` | lines 149-162 | planned rule, Markdown path extraction, confinement, existence, allowed decision status | +| `validate_feature_reference` | lines 164-174 | canonical Feature Spec shape, confinement, existence, proposed/accepted status | +| `validate_human_review` | lines 176-184 | proposed bypass only; accepted decision, confirmer, ISO date, concrete evidence | +| `validate_gate` | lines 186-200 | reject unchecked, duplicate, missing, and extra gate items | +| `validate_operational` | lines 202-237 | exact concern inventory, trigger reason, detail presence/absence and headings | + +Use `PurePosixPath` semantics for Markdown paths, reject backslashes in canonical Markdown references, then resolve through `confined_path`. Before accepting an existing path on Windows/macOS, build a case-folded sibling map and reject ambiguous names that differ only by case. + +- [ ] **Step 3: Port the two validation branches in source order** + +Preserve: + +```text +README effective-source resolution +source/pointer/snapshot status agreement +proposed vs accepted Human Review behavior +Upstream Compatibility current +Design Slice validation +exact Coverage Hard Gate inventory +exact Operational Landing inventory +reasoned not-needed trace-not-applicable branch +accepted Concept/Model source inventory +Scope Inventory equality and dispositions +Technical Landing Trace equality and dispositions +Design Slice references for landed rows +``` + +CLI accepts `REQUIREMENT_README REQUIREMENT_SOURCE DECISION [WORKSPACE_ROOT]`. PASS output remains one of: + +```text +PASS: reasoned concept-foundation-not-needed ADR proposed gate is complete +PASS: ADR accepted technical landing trace covers 8 in-scope requirement-model IDs with 3 landed rows +``` + +- [ ] **Step 4: Run GREEN and focused legacy tests** + +Run: + +```text +python3 -m unittest tests/test_adr_requirement_model_trace.py -v +python3 scripts/check-adr-requirement-model-trace.py tests/fixtures/adr-technical-landing/valid/README.md tests/fixtures/adr-technical-landing/valid/requirement.md tests/fixtures/adr-technical-landing/valid/decision.md +bash tests/validate-adr-requirement-model-technical-landing-trace.sh +``` + +Expected: all PASS. + +- [ ] **Step 5: Review checkpoint** + +Run `python3 -m py_compile scripts/check-adr-requirement-model-trace.py tests/test_adr_requirement_model_trace.py`. Inspect the diff. Do not commit. + +## Task 6: Switch Active Tests And Add One-Cycle Compatibility Launchers + +**Files:** + +- Modify: `scripts/check-root-agents-blocks.sh` +- Modify: `scripts/check-onboarding-core-flow-coverage.rb` +- Modify: `scripts/check-concept-foundation-trace.rb` +- Modify: `scripts/check-adr-requirement-model-trace.rb` +- Modify: `tests/validate-root-agents-block-checker.sh` +- Modify: `tests/validate-root-agents-block-refresh.sh` +- Modify: `tests/validate-onboarding-core-flow-completeness.sh` +- Modify: `tests/validate-concept-foundation-requirement-modeling.sh` +- Modify: `tests/validate-adr-requirement-model-technical-landing-trace.sh` + +- [ ] **Step 1: Change shell contract tests to invoke canonical Python** + +At the top of each affected shell test, resolve: + +```bash +python_cmd=${PYTHON:-python3} +if ! command -v "$python_cmd" >/dev/null 2>&1; then + printf 'FAIL: Python 3.10+ is required: %s\n' "$python_cmd" >&2 + exit 2 +fi +``` + +Replace every checker invocation explicitly: + +```text +scripts/check-root-agents-blocks.sh -> python3 scripts/check-root-agents-blocks.py +scripts/check-onboarding-core-flow-coverage.rb -> python3 scripts/check-onboarding-core-flow-coverage.py +scripts/check-concept-foundation-trace.rb -> python3 scripts/check-concept-foundation-trace.py +scripts/check-adr-requirement-model-trace.rb -> python3 scripts/check-adr-requirement-model-trace.py +``` + +Update file-existence and forbidden-domain assertions to target `.py`. Keep the shell tests as POSIX maintainer wrappers; Windows uses `unittest` directly. + +- [ ] **Step 2: Replace the old root shell implementation with a launcher** + +The compatibility launcher must contain no marker parsing. Resolve `python3`, then `python`, verify `>=3.10`, and `exec` the sibling `.py` with unchanged arguments. On failure print `deprecated compatibility entry requires Python 3.10+; invoke check-root-agents-blocks.py directly` and exit `2`. + +- [ ] **Step 3: Replace each old Ruby implementation with a launcher** + +Each `.rb` compatibility file must use `RbConfig::CONFIG["host_os"]` only to choose candidate commands, find its sibling `.py`, forward `ARGV`, and exit with the child status. It must contain no validation regex, tables, domain IDs, or artifact rules. + +- [ ] **Step 4: Run parity and active focused tests** + +Run: + +```text +python3 -m unittest discover -s tests -p 'test_*.py' -v +bash tests/validate-root-agents-block-checker.sh +bash tests/validate-root-agents-block-refresh.sh +bash tests/validate-onboarding-core-flow-completeness.sh +bash tests/validate-concept-foundation-requirement-modeling.sh +bash tests/validate-adr-requirement-model-technical-landing-trace.sh +``` + +Expected: all PASS. Run each old launcher once against a valid fixture and confirm it returns the same PASS category as the canonical Python entrypoint. + +- [ ] **Step 5: Review checkpoint** + +Search old files for validation leakage: + +```text +rg -n 'Concept ID|Requirement Model|managed-start|Core Flow|Coverage Hard Gate' scripts/*.rb scripts/*.sh +``` + +Expected: no business/validation rules remain in compatibility launchers. Do not commit. + +## Task 7: Switch Current Documentation And Record The Behavior Change + +**Files:** + +- Modify: `SKILL.md:121` +- Modify: `Usage.md:273` +- Modify: `references/project-guidance.md:37` +- Modify: `references/project-guidance.md:161` +- Modify: `references/project-guidance.md:178` +- Modify: `references/workflow-checklists.md:136` +- Modify: `references/workflow-checklists.md:222` +- Modify: `CHANGELOG.md` under `1.3.0` +- Preserve: `docs/reports/**` +- Preserve: completed proposal implementation plans that record old commands + +- [ ] **Step 1: Add a failing documentation-reference assertion** + +Extend `tests/test_python_checker_contract.py` so current authority files must contain `scripts/check-root-agents-blocks.py` and must not contain `scripts/check-root-agents-blocks.sh`: + +```python + def test_current_authority_uses_python_root_checker(self) -> None: + current = ( + "SKILL.md", + "Usage.md", + "references/project-guidance.md", + "references/workflow-checklists.md", + ) + for relative in current: + content = (ROOT / relative).read_text(encoding="utf-8") + self.assertIn("scripts/check-root-agents-blocks.py", content, relative) + self.assertNotIn("scripts/check-root-agents-blocks.sh", content, relative) +``` + +Run the test and expect FAIL before editing docs. + +- [ ] **Step 2: Update current authority references** + +Use the canonical path and include both invocation forms where commands are shown: + +```text +macOS: python3 "$AGENT_LOOP_SKILL/scripts/check-root-agents-blocks.py" --template "$AGENT_LOOP_SKILL/templates/root-AGENTS.md" --target "$PROJECT_ROOT/AGENTS.md" +Windows: py -3 "%AGENT_LOOP_SKILL%\scripts\check-root-agents-blocks.py" --template "%AGENT_LOOP_SKILL%\templates\root-AGENTS.md" --target "%PROJECT_ROOT%\AGENTS.md" +``` + +State that interpreter discovery is read-only, Python must be `>=3.10`, missing capability blocks the checker, and no Agent/manual fallback counts as validation. + +- [ ] **Step 3: Add a `Cross-Platform Python Script Runtime` section to the 1.3.0 changelog** + +Record: + +- four canonical Python standard-library checkers; +- Windows/macOS native fixture coverage; +- one-cycle deprecated compatibility entrypoints; +- fail-closed interpreter policy; +- historical reports/proposals unchanged; +- monthly compaction still not implemented. + +- [ ] **Step 4: Verify no current authority reference remains stale** + +Run: + +```text +python3 -m unittest tests/test_python_checker_contract.py -v +rg -n 'scripts/check-root-agents-blocks\.sh' SKILL.md Usage.md references templates README.md +``` + +Expected: unittest PASS; `rg` returns no current authority match. Matches in `docs/reports/` or completed proposals are allowed and remain unchanged. + +- [ ] **Step 5: Review checkpoint** + +Inspect the docs diff separately from code. Do not commit and do not bump `Version: 1.3.0`. + +## Task 8: Add Dual-Platform CI And Complete Verification + +**Files:** + +- Create: `.github/workflows/cross-platform-checkers.yml` +- Create: `docs/reports/agent-loop-v1.3.0-cross-platform-python-script-runtime-validation-2026-07-13.md` +- Review: `docs/maintenance/full-validation-method.md` +- Review: `docs/maintenance/feature-validation-method.md` + +- [ ] **Step 1: Add the Windows/macOS Python matrix** + +Create: + +```yaml +name: Cross-platform Python checkers + +on: + push: + branches: ["alpha/v1.3.0", "v1.3.0"] + pull_request: + +jobs: + checker-tests: + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest] + python-version: ["3.10", "3.x"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - run: python -m unittest discover -s tests -p "test_*.py" -v +``` + +This workflow validates only the cross-platform Python checker scope. It does not claim that Bash maintainer tests run natively on Windows. + +- [ ] **Step 2: Run the complete Python suite locally on macOS** + +Run: + +```text +python3 -m unittest discover -s tests -p 'test_*.py' -v +python3 -m compileall -q scripts tests +``` + +Expected: all Python tests PASS and compileall exits `0`. + +- [ ] **Step 3: Run all existing shell regressions on macOS** + +Run each file independently so failures are attributable: + +```text +for test_file in tests/*.sh; do bash "$test_file" || exit 1; done +``` + +Expected: every current shell test PASS. + +- [ ] **Step 4: Run repository-required mechanical checks** + +Run: + +```text +ruby -e 'require "yaml"; YAML.load_file("SKILL.md")' +python3 -m json.tool plugin.json >/dev/null +git diff --check +``` + +Run a Markdown fence-balance check over every changed Markdown file. Expected: all PASS. + +- [ ] **Step 5: Follow the full-validation method** + +Because current published checker references, root guidance validation, and multiple accepted artifact validators change together, follow `docs/maintenance/full-validation-method.md`: preserve RED evidence, run semantic audit, pressure tests, full regression, cross-file consistency review, and save the Chinese report at the planned report path. + +The report must distinguish: + +```text +macOS local evidence +Windows CI evidence +old/new parity evidence +stdlib-only evidence +historical-document preservation +remaining maintainer-shell limitation +monthly-compaction not implemented +``` + +- [ ] **Step 6: Verification helper and review helper gates** + +Before claiming completion, resolve and load `verification-before-completion`; run fresh commands and cite outputs. Then resolve and load `requesting-code-review`; perform Spec Review and Standards Review because this is a broad cross-platform tooling change. + +- [ ] **Step 7: Final Human Review Summary** + +Present changed files, Python test counts, shell regression counts, macOS evidence, Windows evidence availability, full-validation score/findings, drift, compatibility limitations, and the exact next action. + +Do not commit or push. If Windows CI evidence cannot exist until push, report the capability as `macOS-verified / Windows-test-defined`, keep the proposal status out of completed, and ask for the Submit/CI gate rather than claiming dual-platform completion. + +## Plan Self-Review + +### Spec coverage + +- Standard-library-only runtime: Tasks 1-5 and contract test. +- Native Windows/macOS behavior: Tasks 1-5 tests plus Task 8 matrix. +- Four current checkers: Tasks 2-5. +- One-cycle compatibility: Task 6. +- Current Markdown references: Task 7. +- Historical evidence preservation: execution boundary and Task 7. +- Fail-closed interpreter behavior: Tasks 1, 6, and 7. +- No monthly compaction implementation: execution boundary, changelog, and final report. +- No version bump/submit action: execution boundary and final gate. + +### Placeholder scan + +The plan contains no unresolved implementation marker or unnamed file/command placeholder. Dynamic output examples use concrete fixture counts. + +### Type and naming consistency + +- Canonical filenames consistently use hyphenated `.py` entrypoints. +- Shared module name is consistently `checker_support`. +- Tests invoke canonical scripts with `sys.executable` through `run_checker`. +- Markdown repository-relative paths remain `/`-separated; filesystem paths use `Path`. +- Success/failure/usage exit codes are consistently `0/1/2`. diff --git a/docs/proposal/v1.3.x/cross-platform-python-script-runtime.md b/docs/proposal/v1.3.x/cross-platform-python-script-runtime.md new file mode 100644 index 0000000..e75324f --- /dev/null +++ b/docs/proposal/v1.3.x/cross-platform-python-script-runtime.md @@ -0,0 +1,337 @@ +# Proposal: Cross-Platform Python Script Runtime + +状态:v1.3.0 Release Human Gate 已批准;发布目标 stable-v1.3.0 + +平台证据:`macOS-verified / Windows-verified` + +CI 证据:`7253461` 的四个 GitHub Actions matrix jobs 全部成功: + +目标版本:v1.3.0 候选 + +创建时间:2026-07-13 + +## 摘要 + +Agent Loop 当前的发布脚本和验证脚本混合使用 Bash 与 Ruby。它们在当前 macOS 维护环境中可用,但不能作为原生 Windows 与 macOS 目标项目的共同运行基线。 + +本 proposal 建议把当前脚本逐步迁移为 Python 3 标准库实现,并建立统一的跨平台脚本契约。迁移完成后,Feature Monthly Compaction 等新的目标项目维护能力直接使用该基线,不再新增 Ruby、Bash 或平台专属实现。 + +核心顺序: + +```text +冻结当前行为与 RED/PARITY 基线 +→ 增加 Python 等价实现 +→ 在 macOS 与 Windows 运行相同 fixture +→ 切换当前权威引用 +→ 保留一版兼容入口 +→ 后续再决定是否移除兼容入口 +``` + +## 背景与问题 + +当前 `scripts/` 包含: + +```text +scripts/check-root-agents-blocks.sh +scripts/check-adr-requirement-model-trace.rb +scripts/check-concept-foundation-trace.rb +scripts/check-onboarding-core-flow-coverage.rb +``` + +主要问题: + +1. Windows 不保证存在 Ruby、Bash 或 POSIX 工具链; +2. macOS 中可发现的 Python 也不应被视为版本合格,需要 capability scan; +3. 继续为新能力增加 Ruby/Bash 会扩大平台差异; +4. 同一安全规则若分别用 Bash、PowerShell、Ruby 实现,容易发生行为漂移; +5. 脚本迁移若同时重写历史报告,会破坏过去验证证据的时间点语义。 + +## 目标 + +1. 当前分发脚本使用一份 Python 3 源码同时支持原生 Windows 与 macOS; +2. 默认只使用 Python 标准库,不要求 `pip install`; +3. 保持现有脚本的输入、输出语义、退出码和 Human Gate 边界; +4. 通过相同 fixtures 验证 Windows/macOS 行为一致; +5. 当前权威文档只引用新的 `.py` 路径; +6. 历史 proposal 和 report 保持原样; +7. 为 Feature Monthly Compaction 的发现、执行、恢复和后检脚本建立前置基线。 + +## 非目标 + +第一阶段不做: + +- 不把全部 `tests/*.sh` 一次性改写为 Python; +- 不宣称整个维护仓库已经原生支持 Windows; +- 不自动安装 Python; +- 不引入 PyYAML、Click、Rich 或其他第三方包; +- 不实现通用 YAML parser; +- 不改变 Concept Foundation、ADR、Onboarding 或 root guidance checker 的业务规则; +- 不修改历史 `docs/reports/` 中记录的旧命令、旧路径和旧行号; +- 不因为迁移脚本而自动升级 Agent Loop 版本; +- 不在本 proposal 中实现 Feature Monthly Compaction。 + +## 方案比较 + +### 方案 A:Python 3 标准库单实现(推荐) + +优点: + +- Windows 与 macOS 使用同一源码; +- `pathlib`、`hashlib`、`json`、`tempfile`、`shutil` 足以覆盖当前需求; +- 文件系统、编码、退出码和测试行为易于统一; +- 不需要维护两套安全逻辑。 + +代价: + +- 运行前必须探测 Python; +- 缺少合格 Python 时必须停止,不能降级为手工执行; +- 维护仓库现有 Bash 测试仍需阶段性保留。 + +### 方案 B:Bash + PowerShell 双实现 + +优点:每个平台可使用本地 shell。 + +缺点:核心规则重复,路径、正则、编码、退出码和回滚逻辑容易漂移,测试成本翻倍。 + +### 方案 C:预编译跨平台二进制 + +优点:目标机不需要 Python。 + +缺点:需要维护 Windows/macOS 多架构产物、构建链、校验和与发布流程,超出当前脚本规模。 + +## 决策 + +选择方案 A。 + +```text +Canonical runtime: Python 3.10+ +Dependency policy: Python standard library only +Platform target: native Windows and macOS +Fallback: fail closed when no compatible interpreter exists +``` + +Python 不可用或版本不足时,Agent 可以报告缺失能力和安装方向,但不得: + +- 自动安装 Python; +- 自动修改 PATH; +- 回退到 Agent 手工模拟脚本; +- 跳过预检、后检或完整性校验; +- 把“无法运行检查器”解释为检查通过。 + +## 脚本迁移范围 + +| Current | Canonical Python Target | Role | +|---|---|---| +| `scripts/check-root-agents-blocks.sh` | `scripts/check-root-agents-blocks.py` | 目标项目 root guidance 只读漂移检查 | +| `scripts/check-adr-requirement-model-trace.rb` | `scripts/check-adr-requirement-model-trace.py` | ADR Requirement Model trace 校验 | +| `scripts/check-concept-foundation-trace.rb` | `scripts/check-concept-foundation-trace.py` | Concept Foundation / Product Model trace 校验 | +| `scripts/check-onboarding-core-flow-coverage.rb` | `scripts/check-onboarding-core-flow-coverage.py` | Onboarding Core Flow coverage 校验 | + +旧 `.sh` / `.rb` 入口在一个兼容周期内保留,但必须满足: + +- 标记为 deprecated compatibility entry; +- 不再拥有独立业务规则; +- 只解析兼容调用并转交 Python canonical implementation; +- 找不到合格 Python 时明确失败; +- 当前权威文档不再推荐旧入口; +- 移除旧入口需要后续单独 Human Review。 + +## Python 标准库契约 + +允许的核心标准库包括: + +```text +argparse +dataclasses +enum +hashlib +json +os +pathlib +re +shutil +subprocess +tempfile +unittest +``` + +Python canonical checker 不得调用或依赖: + +```text +grep +sed +find +bash +shasum / sha256sum +git mv +PowerShell-only cmdlets +third-party Python packages +``` + +测试代码可以用标准库 `subprocess` 以当前 `sys.executable` 启动 checker。旧 `.sh` / `.rb` 兼容入口只负责解释器探测和参数转交,不属于 canonical implementation,不得承载检查规则。 + +如未来出现标准库无法满足的需求,必须先提交依赖 proposal,并经过 Human Review;脚本不得自行执行 `pip install`。 + +## 平台兼容契约 + +### 解释器发现 + +建议探测顺序: + +```text +Windows: py -3 → python +macOS: python3 → python +``` + +调用者必须验证实际解释器版本,而不是只验证命令名存在。Python 脚本内部不得依赖调用命令名称。 + +### 路径 + +- 文件系统路径一律使用 `pathlib.Path`; +- 不手工拼接 `/` 或 `\`; +- Markdown 中的仓库相对路径统一使用 `/`; +- 路径必须限制在声明的 project/workspace root 内; +- 在执行写操作前检查 Windows 大小写不敏感冲突; +- 不依赖 symlink、POSIX executable bit 或 POSIX permission semantics; +- 文件名不得包含 Windows 禁止字符。 + +### 文本与摘要 + +- 读取 Markdown 时兼容 UTF-8 与 UTF-8 BOM; +- 兼容 CRLF 与 LF; +- 内容摘要使用二进制读取与 SHA-256; +- 稳定输出必须使用确定性排序; +- 机器输出使用 JSON,Human Review 输出使用 Markdown/text; +- JSON 中的仓库相对路径统一使用 `/`。 + +### 文件操作 + +- 只读 checker 不得产生文件副作用; +- 未来的 mutation script 必须先生成确定性计划与摘要; +- 写操作使用临时路径、冲突检查和可验证替换; +- Windows 文件占用、权限失败或部分移动必须返回非零; +- 失败回滚必须包含在已确认执行计划中; +- 不提供绕过安全检查的 `--force`。 + +## YAML 边界 + +Python 标准库没有通用 YAML parser。 + +因此: + +- 本轮四个 Markdown checker 不增加 YAML 依赖; +- `plugin.json` 使用标准库 `json` 校验; +- 若只需要检查 `SKILL.md` 当前稳定 frontmatter 字段,可建立严格、有限的字段检查器,但不得宣称支持通用 YAML; +- 当前维护侧的完整 YAML 解析命令不属于第一阶段目标项目脚本迁移范围; +- 是否替换维护侧完整 YAML parser,留给后续独立决定。 + +## 行为兼容与验证 + +每个 Python port 必须建立 parity matrix: + +| Dimension | Requirement | +|---|---| +| Valid fixture | 新旧实现都 PASS | +| Invalid fixture | 新旧实现都 FAIL | +| Exit code | 成功/失败语义一致 | +| Required diagnostics | 关键错误类别一致 | +| Read-only behavior | 不产生文件变化 | +| Workspace confinement | 拒绝越界路径 | +| Determinism | 相同输入产生稳定排序与摘要 | +| Windows/macOS | 相同 fixture 结果一致 | + +不得把旧脚本的偶然输出格式全部冻结成永久 API。实施前先区分: + +- 必须兼容的参数、退出码、错误类别和安全结论; +- 可以改进但需同步测试与文档的人类可读措辞。 + +## 文档与引用迁移 + +当前权威表面中的旧脚本路径必须切换到 Python canonical path,包括适用的: + +- `SKILL.md`; +- `Usage.md`; +- `references/project-guidance.md`; +- `references/workflow-checklists.md`; +- 当前维护指南中的现行命令; +- 对应回归测试和 fixture runner; +- `CHANGELOG.md` 当前开发版本章节。 + +以下内容保持原样: + +- 已完成的 `docs/reports/`; +- 已完成 proposal 的历史实施计划和历史命令; +- 仅用于说明过去 RED/GREEN 过程的证据。 + +历史文件引用旧路径不表示当前运行时仍推荐旧脚本。 + +## 与 Feature Monthly Compaction 的关系 + +Feature Monthly Compaction 的生产脚本必须建立在本 proposal 的 Python 基线上: + +```text +Cross-Platform Python Script Runtime accepted and implemented +→ current published checkers pass parity validation +→ Feature Monthly Compaction reader compatibility +→ compaction scan/apply/restore/post-check implementation +``` + +月度压缩不得以“Python port 还没完成”为理由回退到 Ruby、Bash 或 Agent 手工移动。 + +## 实施阶段 + +### Phase 1:Scope Lock 与 RED/PARITY 基线 + +- 固定四个脚本的有效/无效 fixture; +- 记录参数、退出码、错误类别和只读边界; +- 区分目标项目运行脚本与维护仓库测试入口; +- 证明 Windows 当前无法直接运行现有入口。 + +### Phase 2:Python Ports + +- 使用 Python 3 标准库实现四个 canonical checker; +- 为路径越界、大小写冲突、CRLF、UTF-8 BOM 和确定性排序增加测试; +- 不修改原有业务规则。 + +### Phase 3:引用切换与兼容入口 + +- 当前权威文档切换到 `.py`; +- 测试主路径调用 Python canonical checker; +- 旧 `.sh` / `.rb` 变为兼容入口; +- Changelog 记录迁移和能力边界。 + +### Phase 4:双平台验证 + +- macOS 运行全部 port parity fixtures; +- Windows 运行相同 fixtures; +- 运行仓库要求的 focused/full validation; +- 保存中文验证报告; +- Human Review 决定是否允许 Feature Monthly Compaction 进入实现。 + +## 验收标准 + +- 四个 Python canonical checker 不依赖第三方包; +- macOS 与 Windows 对相同 fixtures 给出相同安全结论; +- 旧实现与 Python 实现对有效/无效 fixtures 保持 parity; +- 当前权威文档不再推荐 `.sh` / `.rb` 入口; +- 历史报告与历史 proposal 未被重写; +- 缺少合格 Python 时 fail closed; +- 没有 `--force`、自动安装或手工降级路径; +- root guidance checker 仍保持只读; +- ADR、Concept Foundation、Onboarding checker 没有弱化任何 hard gate; +- full validation 与跨平台验证报告均通过; +- 未经人类明确批准不修改版本号、不 commit、不发布。 + +## Human Review 决策 + +在实施前,人类需要确认: + +| Decision | Recommendation | +|---|---| +| Runtime | Python 3.10+ 标准库 | +| Platform | 原生 Windows + macOS | +| Migration scope | 当前四个分发 checker | +| Existing tests | 第一阶段保留维护侧 `tests/*.sh`,增加跨平台 Python parity tests | +| Compatibility | 旧入口保留一个兼容周期 | +| Historical docs | 不修改 | +| Monthly compaction | 等本 proposal 实现并通过双平台验证后再实施 | diff --git a/docs/proposal/v1.3.x/feature-monthly-archive-implementation-plan.md b/docs/proposal/v1.3.x/feature-monthly-archive-implementation-plan.md new file mode 100644 index 0000000..c4747c9 --- /dev/null +++ b/docs/proposal/v1.3.x/feature-monthly-archive-implementation-plan.md @@ -0,0 +1,1325 @@ +# Feature Monthly Archive Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use `executing-plans` to implement this plan task-by-task after Plan Human Review. Use `test-driven-development` for every behavior change, `systematic-debugging` for unexpected failures, `verification-before-completion` before completion claims, and `requesting-code-review` before handoff. This plan is intended for a separate development Agent, but writing the plan does not authorize implementation, subagent dispatch, commit, push, tag, PR, release, or publication. + +**Goal:** Add a Human-gated, cross-platform Feature Monthly Archive capability that moves eligible closed feature directories intact into `.agent-loop/features/YYYY-MM/`, maintains `.agent-loop/features/archive.md` as the stable Feature ID locator, updates durable references, verifies the result, restores failed transactions, and rehydrates archived features before follow-up work. + +**Architecture:** Implement Reader Compatibility first through one standard-library `feature_archive_support.py` module that owns Feature ID/path resolution, archive-index parsing, deterministic plans, reference-impact discovery, hashing, path confinement, and transaction-journal primitives. Four thin Python CLIs then expose scan, check, apply, and restore operations; apply always recomputes and matches a Human-reviewed plan hash before writing. Only after reader compatibility and executable tests pass should the published runtime, root Stage Map, templates, and human docs advertise the capability. + +**Tech Stack:** Python 3.10+ standard library (`argparse`, `dataclasses`, `datetime`, `hashlib`, `json`, `os`, `pathlib`, `re`, `shutil`, `tempfile`, `typing`, `unittest`, `unicodedata`, `urllib.parse`), Markdown artifacts, existing `scripts/checker_support.py`, existing shell contract tests, GitHub Actions Windows/macOS matrix. + +--- + +## Execution Status + +```text +Proposal: accepted by Human Review on 2026-07-14 +Plan: accepted and implementation authorized by Human Review on 2026-07-14 +Implementation: Task 0-7 completed and pre-release full validation passed +Review: local Spec/Standards Review and Human feature review completed +Platform: macOS-verified / Windows-verified +Release: Human Gate approved on 2026-07-14; target tag stable-v1.3.0 +Tag condition: the exact release-evidence commit must pass the Windows/macOS CI matrix before tag creation +``` + +## Execution Boundary + +Included: + +- Feature ID and flat/month-path resolver; +- `.agent-loop/features/archive.md` template, parser, validator, and locator behavior; +- ADR Feature Spec reference compatibility for archived closed features; +- explicit `feature-archive-maintenance` message intent and triggered `Feature Monthly Archive` stage; +- read-only deterministic archive/rehydrate scan; +- read-only pre/post/restore checking; +- Human-confirmed whole-directory archive and rehydrate operations; +- persistent transaction journal and failure restore; +- durable Markdown reference updates and conservative blocker reporting; +- Windows/macOS native tests using only Python standard library; +- coordinated runtime, design, Stage Map, checklist, template, scenario, Usage, README, changelog, and report updates; +- mandatory full validation because routing, Feature Follow-up, recovery, root guidance, and feature path invariants change. + +Excluded: + +- per-feature archive summaries; +- `historical/` content reorganization; +- per-month `INDEX.md`; +- deletion, packing, compression, external storage, or Deep Archive; +- automatic scheduled archival; +- automatic archive without a Batch Human Gate; +- moving `active`, `blocked`, `paused`, current-month, incomplete-close, open-follow-up, or drifted features; +- changing original requirement sources or accepted ADR meaning; +- third-party Python packages; +- generic repository-wide link rewriting outside Markdown and Agent Loop-owned YAML metadata; +- version bump without separate Human approval. + +## Stage Helper Resolution + +```text +Stage: Plan Gate / Plan If Needed +Canonical Candidate: superpowers:writing-plans +Canonical Result: unavailable under that qualified name in the current runtime +Alias Candidate: writing-plans +Resolved Helper: writing-plans +Status: loaded +Load Evidence: /Users/shaodowyd/.codex/skills/writing-plans/SKILL.md read completely on 2026-07-14 +Fallback Used: no +Method Used: exact file map, interface-first design, bite-sized TDD steps, RED/GREEN commands, risks, rollback, and self-review +Agent Loop Path Override: docs/proposal/v1.3.x/feature-monthly-archive-implementation-plan.md +Human Gates Preserved: Plan approval; implementation start; any development-Agent/subagent dispatch; archive Batch Human Gate; rehydrate Human Gate; commit; push; tag; PR; release; publish +``` + +## Preconditions And Hard Stops + +1. Preserve the verified remote `cross-platform-checkers.yml` evidence for commit `7253461`: all four macOS/Windows × Python 3.10/3.x jobs succeeded in run . Do not replace execution evidence with matrix configuration alone. +2. Preserve unrelated dirty work. At plan time, unrelated paths include `docs/proposal/v1.3.x/onboarding-core-flow-completeness.md`, deleted v1.4 proposal files, and `docs/proposal/v2.0.x/`. Do not stage, revert, rewrite, or include them in validation claims. +3. Do not move any real target-project feature directory while developing this source repository. All mutation tests use temporary fixtures. +4. If any pre-existing full-suite test fails before the first RED, stop and diagnose it separately; do not normalize it into this feature. +5. No command exposes `--force`, skips the expected-plan hash, or silently treats an unresolved reference as historical evidence. + +## Non-Negotiable Invariants + +```text +Feature identity = YYYY-MM-DD-slug Feature ID +Feature lifecycle = draft | active | blocked | paused | closed +Archive state = archived | rehydrated; never a feature lifecycle value +Active / blocked / paused path = features// +Archived path = features/YYYY-MM// +Archive eligibility = closed + complete close evidence + no open blocker +Archive authority = original feature artifacts; archive.md is locator only +Mutation authorization = exact Human-reviewed plan SHA-256 +Failure behavior = fail closed + persistent journal + verified restore +Content policy = move intact; no deletion or content-summary substitution +``` + +## CLI Contract + +All four entrypoints use the same exit classes: + +```text +0 = scan/check/apply/restore completed successfully +1 = artifact, eligibility, stale-plan, reference, transaction, or post-check contract failed +2 = command usage, missing path, unsupported Python, invalid date/month, or capability error +``` + +Archive scan: + +```text +python scripts/scan-feature-monthly-archive.py \ + --project-root /workspace/project \ + --operation archive \ + --month 2026-05 \ + --month 2026-06 \ + --as-of 2026-07-14 +``` + +Rehydrate scan: + +```text +python scripts/scan-feature-monthly-archive.py \ + --project-root /workspace/project \ + --operation rehydrate \ + --feature-id 2026-05-08-login \ + --as-of 2026-07-14 +``` + +Apply binds to the exact scan result: + +```text +python scripts/apply-feature-monthly-archive.py \ + --project-root /workspace/project \ + --operation archive \ + --month 2026-05 \ + --month 2026-06 \ + --as-of 2026-07-14 \ + --expected-plan-sha256 64-lowercase-hex-characters +``` + +Post-check and transaction restore: + +```text +python scripts/check-feature-monthly-archive.py --project-root /workspace/project --operation archive --plan /absolute/path/plan.json +python scripts/restore-feature-monthly-archive.py --project-root /workspace/project --transaction-id 20260714T120000Z-0123456789ab +``` + +Scan prints deterministic UTF-8 JSON to stdout. The controlling Agent may save that output to an OS temporary file for apply/check; the script must not create a target-project artifact during scan. The Batch Human Review Summary displays `plan_sha256`, selected months, eligible/blocked Feature IDs, moves, reference edits, unchanged files, and restore behavior. + +## JSON Plan Contract + +The serialized plan has this exact top-level shape: + +```json +{ + "schema_version": 1, + "operation": "archive", + "as_of": "2026-07-14", + "selected_months": ["2026-05", "2026-06"], + "selected_feature_ids": [], + "candidates": [], + "moves": [], + "archive_entries": [], + "reference_edits": [], + "skipped_references": [], + "snapshots": {}, + "plan_sha256": "64-lowercase-hex-characters" +} +``` + +`plan_sha256` is SHA-256 over canonical JSON of every field except `plan_sha256`, using `sort_keys=True`, `ensure_ascii=False`, `separators=(",", ":")`, UTF-8 encoding, POSIX workspace-relative paths, sorted lists, and no current timestamp. Absolute project paths and output formatting are excluded so the same fixture produces the same hash on Windows and macOS. Content snapshots and relative paths still bind the plan to the exact project state. + +## Interface Contracts + +### `ArchiveContractError` + +Location: `scripts/feature_archive_support.py` + +Kind: exception dataclass + +Signature: + +```python +@dataclass(frozen=True) +class ArchiveContractError(Exception): + category: str + detail: str + exit_code: int = 1 +``` + +Errors: `usage`, `memory-root`, `feature-id`, `month`, `eligibility`, `path-collision`, `path-escape`, `archive-index`, `reference-impact`, `stale-plan`, `transaction`, `post-check`, `restore`. + +### `FeatureLocation` + +Location: `scripts/feature_archive_support.py` + +Kind: immutable dataclass + +Signature: + +```python +@dataclass(frozen=True) +class FeatureLocation: + feature_id: str + relative_path: str + layout: Literal["flat", "archived"] + month: str | None +``` + +Validation: flat is exactly `features/`; archived is exactly `features/YYYY-MM/` and month equals the first seven characters of Feature ID. + +### `ArchiveEntry` + +Location: `scripts/feature_archive_support.py` + +Kind: immutable dataclass + +Signature: + +```python +@dataclass(frozen=True) +class ArchiveEntry: + feature_id: str + month: str + current_path: str + archive_state: Literal["archived", "rehydrated"] + closed_at: str + delivered_summary: str + source_requirements: str + applicable_decisions: str + last_moved_at: str +``` + +### `ReferenceEdit` + +Location: `scripts/feature_archive_support.py` + +Kind: immutable dataclass + +Signature: + +```python +@dataclass(frozen=True) +class ReferenceEdit: + path: str + kind: Literal["literal-path", "relative-link", "archive-index"] + old: str + new: str + occurrences: int + before_sha256: str + after_sha256: str +``` + +### `SkippedReference` + +Location: `scripts/feature_archive_support.py` + +Kind: immutable dataclass + +Signature: + +```python +@dataclass(frozen=True) +class SkippedReference: + path: str + classification: Literal["immutable-requirement-source", "historical-evidence", "unsupported"] + matched_value: str + reason: str +``` + +`immutable-requirement-source` and concrete `historical-evidence` rows are displayed at Human Review and preserved. Any `unsupported` row blocks apply. + +### `Move` + +Location: `scripts/feature_archive_support.py` + +Kind: immutable dataclass + +Signature: + +```python +@dataclass(frozen=True) +class Move: + feature_id: str + month: str + source: str + target: str +``` + +Paths are POSIX project-relative paths. Archive moves are flat-to-month; rehydrate moves are month-to-flat. + +### `ArchiveCandidate` + +Location: `scripts/feature_archive_support.py` + +Kind: immutable dataclass + +Signature: + +```python +@dataclass(frozen=True) +class ArchiveCandidate: + feature_id: str + month: str + current_path: str + lifecycle: str + close_evidence: Literal["complete", "incomplete"] + open_follow_up: str + delivered_summary: str + source_requirements: str + applicable_decisions: str + blockers: Sequence[str] +``` + +Eligibility is `not blockers`; blocker strings are stable category/detail messages sorted lexically. + +### `ArchivePlan` + +Location: `scripts/feature_archive_support.py` + +Kind: immutable dataclass with canonical JSON serialization + +Fields: + +```python +@dataclass(frozen=True) +class ArchivePlan: + schema_version: int + operation: Literal["archive", "rehydrate"] + as_of: str + selected_months: Sequence[str] + selected_feature_ids: Sequence[str] + candidates: Sequence[ArchiveCandidate] + moves: Sequence[Move] + archive_entries: Sequence[ArchiveEntry] + reference_edits: Sequence[ReferenceEdit] + skipped_references: Sequence[SkippedReference] + snapshots: Mapping[str, str] +``` + +Import `Literal`, `Mapping`, and `Sequence` from `typing`. Store tuples and a read-only copied mapping internally even though the public annotations use collection protocols. + +Required methods and exact behavior: + +| Method | Return | Behavior | +|---|---|---| +| `to_payload(include_hash: bool = True)` | `dict[str, object]` | Serialize every dataclass collection in stable sorted order; include the computed lowercase hash only when requested | +| `canonical_bytes()` | `bytes` | Call `to_payload(include_hash=False)` and encode through `canonical_json_bytes` | +| `computed_sha256()` | `str` | Return `sha256_bytes(self.canonical_bytes())` | +| `assert_hash(expected: str)` | `None` | Reject malformed hashes as usage exit 2 and mismatches as `stale-plan` exit 1 | + +### `resolve_feature_location` + +Location: `scripts/feature_archive_support.py` + +Signature: + +```python +def resolve_feature_location(memory_root: Path, feature_id: str) -> FeatureLocation: +``` + +Behavior: + +1. Reject malformed Feature ID. +2. If exact flat directory exists, reject any simultaneous archived locator/directory collision and return flat. +3. Otherwise parse `features/archive.md`, require exactly one matching row, confine `Current Path`, require existing directory and matching Feature ID/month, then return archived or rehydrated location. +4. A `rehydrated` row must point to the flat path. + +Related exact signatures: + +```text +parse_archive_index(memory_root: Path) -> Sequence[ArchiveEntry] +render_archive_index(entries: Sequence[ArchiveEntry]) -> str +``` + +`render_archive_index` uses `templates/feature-archive.md` header text, escapes table-breaking newlines/pipes in one-line summary fields, and produces one trailing newline. + +### `build_archive_plan` + +Location: `scripts/feature_archive_support.py` + +Signature: + +```python +def build_archive_plan( + project_root: Path, + *, + operation: Literal["archive", "rehydrate"], + selected_months: Sequence[str], + selected_feature_ids: Sequence[str], + as_of: date, +) -> ArchivePlan: +``` + +Side effects: none. + +### `apply_archive_plan` + +Location: `scripts/feature_archive_support.py` + +Signature: + +```python +def apply_archive_plan( + project_root: Path, + plan: ArchivePlan, + *, + expected_plan_sha256: str, +) -> str: +``` + +Return: transaction ID after successful post-check and journal cleanup. + +Side effects: creates/removes month directories, moves whole feature directories, updates approved Markdown files and `features/archive.md`, creates a temporary transaction journal, and removes the journal only after successful post-check. + +### `restore_transaction` + +Location: `scripts/feature_archive_support.py` + +Signature: + +```python +def restore_transaction(project_root: Path, transaction_id: str) -> None: +``` + +Behavior: load the persistent journal, restore directory moves in reverse order, restore backed-up bytes atomically, run restore check, mark `restored`, then remove the journal. Any incomplete restore leaves the journal and raises `ArchiveContractError("restore", detail)` with the concrete failed path/operation in `detail`. + +## File Responsibility Map + +### New runtime and template files + +| File | Responsibility | +|---|---| +| `scripts/feature_archive_support.py` | Feature IDs, memory-root discovery, archive index, resolver, candidate eligibility, plan/hash, references, journal, apply/check/restore primitives | +| `scripts/scan-feature-monthly-archive.py` | Read-only archive/rehydrate plan CLI and deterministic JSON output | +| `scripts/check-feature-monthly-archive.py` | Read-only pre/post/restore contract validation CLI | +| `scripts/apply-feature-monthly-archive.py` | Human-confirmed expected-hash apply CLI | +| `scripts/restore-feature-monthly-archive.py` | Persistent-journal recovery CLI | +| `templates/feature-archive.md` | Copy-ready `features/archive.md` locator template | + +### New native tests + +| File | Responsibility | +|---|---| +| `tests/feature_archive_test_support.py` | Temporary target-project builder, feature/requirement/decision/project fixtures, subprocess runner, snapshots | +| `tests/test_feature_archive_support.py` | Feature ID, month, index parsing, resolver, collision, path escape, BOM/CRLF, deterministic plan/hash | +| `tests/test_feature_monthly_archive_scan.py` | Eligible/blocked classification, multi-month plan, reference impacts, no-mutation, rehydrate scan | +| `tests/test_feature_monthly_archive_apply.py` | Whole-directory moves, exact reference edits, archive index, stale-plan, idempotency, post-check | +| `tests/test_feature_monthly_archive_restore.py` | Injected failure, persistent journal, reverse restore, rehydrate, interrupted-process recovery | +| `tests/validate-feature-monthly-archive-runtime.sh` | Coordinated runtime/stage/template/human-gate/source-authority contract | + +### Existing code and CI files to modify + +| File | Planned change | +|---|---| +| `scripts/checker_support.py` | Add reusable Markdown code-span stripping, canonical JSON/hash, atomic UTF-8/bytes write, and normalized relative-path helpers without weakening existing checkers | +| `scripts/check-adr-requirement-model-trace.py` | Replace one-level Feature Spec regex with resolver-aware flat/month validation; allow `closed` only for a valid existing owner path | +| `tests/test_adr_requirement_model_trace.py` | Add archived closed Feature Spec and missing/mismatched archive-index cases | +| `tests/test_python_checker_contract.py` | Add four archive commands and `feature_archive_support` to stdlib/local-import, runtime guard, help/usage, scan/check-read-only contracts | +| `.github/workflows/cross-platform-checkers.yml` | Run all five new archive suites on Windows/macOS and Python 3.10/current | + +### Published runtime and design files to modify together + +| File | Planned change | +|---|---| +| `SKILL.md` | Add feature-archive intent/routing, script map, archived-path inspection rule, and Human Gate boundary concisely | +| `references/runtime.md` | Add `feature-archive-maintenance` intent, precedence/routing, triggered stage order, inspection of `features/archive.md`, and no-manual-move stop | +| `references/design.md` | Add directory-only archive model, locator authority boundary, archive/rehydrate state, and core flow placement | +| `references/concepts.md` | Define Feature Monthly Archive, Archive State, Feature Locator, and Rehydrate | +| `references/artifact-rules.md` | Add flat/month layouts, `features/archive.md` ownership, archived/rehydrated distinction, and stable Feature ID rules | +| `references/feature-follow-up.md` | Resolve archive rows during lookback/extended scan and require rehydrate before reopened execution | +| `references/feature-completion-check.md` | Produce a deterministic Archive Readiness record at close without auto-archiving | +| `references/stage-guides.md` | Add complete Feature Monthly Archive entry/read/write/gate/exit procedure | +| `references/workflow-checklists.md` | Add scan, Batch Human Gate, apply/post-check/restore, and rehydrate checklist | +| `references/human-review-summary.md` | Add Feature Monthly Archive Batch Review table with plan hash and restore scope | +| `references/recovery-and-backfill.md` | Treat missing/stale archive rows, stranded journals, dual flat/month locations, and missing target paths as stale memory/recovery signals | +| `references/project-decisions.md` | Allow archived closed Feature Spec owner paths without rewriting accepted decision meaning | +| `references/requirement-management.md` | Preserve Feature ID while updating `Feature Mapping` / `Implemented By` current path after archive/rehydrate | +| `references/project-memory-mode.md` | Add optional archive locator to simple/enterprise memory trees; keep history out of `project.md` | +| `references/project-guidance.md` | Clarify first-level active state versus month archive and canonical scripts | +| `references/document-templates.md` | Add inline archive-index and Batch Review derived templates | +| `templates/notes.md` | Add the Archive Readiness block consumed by scan | +| `templates/root-AGENTS.md` | Add message intent and Stage Map row; bump every managed block revision on the approved 1.3.0 version | +| `references/validation-scenarios.md` | Add archive, blocked mixed month, stale plan, interrupted restore, follow-up rehydrate, and no-content-loss pressure scenarios | + +### Human-facing and maintenance evidence files + +| File | Planned change | +|---|---| +| `README.md` | Explain directory-only historical archive and locator boundary | +| `Usage.md` | Add trigger phrases, two-stage Human Gate example, archive/rehydrate behavior, and Windows/macOS commands | +| `CHANGELOG.md` | Record implemented 1.3.0 archive capability without version bump | +| `docs/proposal/v1.3.x/feature-monthly-compaction.md` | Change status from accepted design to implemented only after verification/Human Review | +| `docs/reports/agent-loop-v1.3.0-feature-monthly-archive-validation-2026-07-14.md` | Focused RED/GREEN, platform, fixture, recovery, scope, and remaining-risk report | +| `docs/reports/agent-loop-v1.3.0-full-validation-2026-07-14.md` | Required Chinese six-domain full-validation report | +| `examples/login-feature/notes.md` | Add a complete backward-compatible Archive Readiness example for the closed login feature | + +Review but do not mechanically rewrite generic `` paths in `references/e2e-discovery.md`, `references/delivery-contracts.md`, `references/complex-artifacts.md`, `references/external-skill-adapters.md`, and `templates/subagent-brief.md`: active work is rehydrated to flat before execution, so those execution paths remain correct. Add a regression assertion preventing those surfaces from recommending execution inside an archived month directory. + +## Task 0: Establish Baseline And Phase-0 Evidence + +**Files:** + +- Read: `.github/workflows/cross-platform-checkers.yml` +- Read: `docs/reports/agent-loop-v1.3.0-cross-platform-python-script-runtime-validation-2026-07-13.md` +- Read: `AGENTS.md` +- No writes before baseline completes + +- [ ] **Step 1: Record the exact workspace boundary** + +Run: + +```text +git status --short --branch +git log -1 --oneline +``` + +Expected: branch `alpha/v1.3.0`; unrelated dirty paths remain visible and unstaged. Record them in the new focused report once implementation is authorized. + +- [ ] **Step 2: Run the existing native and shell baseline** + +Run: + +```text +python3 -m unittest discover -s tests -p 'test_*.py' -v +for test_file in tests/validate-*.sh; do bash "$test_file"; done +``` + +Expected baseline at plan creation: 36 Python tests PASS and every existing shell contract PASS. A changed count is acceptable only when every pre-existing test still passes and the report records the exact count. + +- [ ] **Step 3: Verify remote Windows evidence without guessing** + +Preferred command when GitHub CLI is authenticated: + +```text +gh run list --workflow cross-platform-checkers.yml --branch alpha/v1.3.0 --limit 10 +``` + +Expected: locate the run containing commit `e49673c`, then inspect all Windows matrix jobs. If no authenticated CLI or remote evidence is available, record `Windows evidence unavailable` and keep Phase 0 blocked; do not claim cross-platform acceptance. + +- [ ] **Step 4: Baseline review checkpoint** + +Do not edit or commit. If baseline or Windows evidence fails unexpectedly, use `systematic-debugging` before proceeding. + +## Task 1: Establish RED Archive Contracts And Fixture Builder + +**Files:** + +- Create: `tests/feature_archive_test_support.py` +- Create: `tests/test_feature_archive_support.py` +- Create: `tests/test_feature_monthly_archive_scan.py` +- Modify: `tests/test_python_checker_contract.py` + +- [ ] **Step 1: Create the temporary target-project builder** + +Create `tests/feature_archive_test_support.py` with this public interface: + +```python +from __future__ import annotations + +import hashlib +import json +import subprocess +import sys +from dataclasses import dataclass +from pathlib import Path + +from tests.checker_test_support import ROOT + + +@dataclass +class ArchiveWorkspace: + project_root: Path + + @property + def memory_root(self) -> Path: + return self.project_root / ".agent-loop" + + @property + def features_root(self) -> Path: + return self.memory_root / "features" + + def write(self, relative: str, content: str) -> Path: + path = self.project_root / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8", newline="\n") + return path + + def feature(self, feature_id: str, *, status: str = "closed", close_complete: bool = True) -> Path: + root = self.features_root / feature_id + root.mkdir(parents=True, exist_ok=True) + self.write(f".agent-loop/features/{feature_id}/spec.md", f"# Feature Spec\n\nStatus: {status}\n") + task_status = "done" if close_complete else "in-progress" + self.write(f".agent-loop/features/{feature_id}/tasks.md", f"# Tasks\n\n- Status: {task_status}\n") + close = ( + "## Feature Close Review\n\nDecision: pass\n\n" + "## Drift Check\n\nDecision: no-drift\n\n" + "## Close Record\n\nClosed At: 2026-05-20\nHuman Decision: confirmed\n\n" + f"## Archive Readiness\n\nClosed At: 2026-05-20\nDelivered Summary: completed {feature_id}\n" + "Verification: complete\nFeature Close Review: complete\nDrift: resolved\n" + "Project Memory Impact: none\nOpen Follow-up: none\n" + if close_complete else "## Close Record\n\nClosed At:\n" + ) + self.write(f".agent-loop/features/{feature_id}/notes.md", f"# Notes\n\n{close}") + self.write(f".agent-loop/features/{feature_id}/tests.md", "# Tests\n\nStatus: passing\n") + self.write(f".agent-loop/features/{feature_id}/plan.md", "# Plan\n\nStatus: closed\n") + return root + + +def run_archive_command(script: str, *args: str) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [sys.executable, str(ROOT / "scripts" / script), *map(str, args)], + cwd=str(ROOT), + check=False, + capture_output=True, + text=True, + encoding="utf-8", + ) + + +def tree_snapshot(root: Path) -> dict[str, str]: + return { + path.relative_to(root).as_posix(): hashlib.sha256(path.read_bytes()).hexdigest() + for path in sorted(root.rglob("*")) + if path.is_file() + } + + +def json_output(result: subprocess.CompletedProcess[str]) -> dict[str, object]: + return json.loads(result.stdout) +``` + +- [ ] **Step 2: Write resolver and deterministic-plan RED tests** + +Create `tests/test_feature_archive_support.py` with these exact cases and assertions: + +| Test name | Fixture | Assertion | +|---|---|---| +| `test_flat_feature_resolves_without_archive_index` | one flat closed feature | `layout == "flat"`, `month is None`, path is `features/2026-05-08-login` | +| `test_archived_feature_requires_matching_unique_index_row` | one month directory plus one matching row | `layout == "archived"`, month `2026-05`; deleting or duplicating the row raises `archive-index` | +| `test_flat_and_archived_collision_fails_closed` | same Feature ID at both paths | raises `path-collision` | +| `test_month_must_match_feature_id` | ID starts `2026-05`, directory uses `2026-06` | raises `month` | +| `test_archive_index_accepts_bom_and_crlf` | index bytes start BOM and use CRLF | row parses with exact Unicode summary | +| `test_symlink_escape_is_rejected` | archived path symlinks outside project | raises `path-escape` | +| `test_plan_hash_is_stable_across_absolute_roots` | identical relative trees under two temp roots | equal `plan_sha256` | + +Use `self.assertRaisesRegex(ArchiveContractError, "archive-index")`, `"path-collision"`, `"month"`, or `"path-escape"` according to the failure row above, and exact `FeatureLocation` equality for success. The archived fixture row must use the accepted columns from `templates/feature-archive.md`; create the same relative tree under two temporary absolute roots and assert equal `plan_sha256`. + +- [ ] **Step 3: Write multi-month and no-mutation RED tests** + +Create `tests/test_feature_monthly_archive_scan.py` with this representative test body and companion cases for current month, active, blocked, paused, incomplete close, open follow-up, path collision, and rehydrate: + +```python +def test_scan_selects_closed_features_across_two_months_and_preserves_blocked(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login") + workspace.feature("2026-05-22-import", status="paused") + workspace.feature("2026-06-12-payment") + before = tree_snapshot(workspace.project_root) + result = run_archive_command( + "scan-feature-monthly-archive.py", + "--project-root", str(workspace.project_root), + "--operation", "archive", + "--month", "2026-05", + "--month", "2026-06", + "--as-of", "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertEqual( + [move["feature_id"] for move in payload["moves"]], + ["2026-05-08-login", "2026-06-12-payment"], + ) + self.assertIn("2026-05-22-import", [item["feature_id"] for item in payload["candidates"]]) + self.assertEqual(tree_snapshot(workspace.project_root), before) +``` + +- [ ] **Step 4: Extend the Python command contract in RED** + +In `tests/test_python_checker_contract.py`, add: + +```python +ARCHIVE_COMMANDS = ( + "scripts/scan-feature-monthly-archive.py", + "scripts/check-feature-monthly-archive.py", + "scripts/apply-feature-monthly-archive.py", + "scripts/restore-feature-monthly-archive.py", +) +``` + +Update the import allowlist to include `feature_archive_support`; assert all commands exist, call `require_supported_python`, use only standard-library/local imports, and return exit `2` with `usage` when invoked without arguments. Keep the existing checker inventory assertions unchanged. + +- [ ] **Step 5: Run RED and preserve the reason** + +Run: + +```text +python3 -m unittest \ + tests.test_feature_archive_support \ + tests.test_feature_monthly_archive_scan \ + tests.test_python_checker_contract -v +``` + +Expected RED: missing `scripts/feature_archive_support.py`, the four missing CLI files, and missing `templates/feature-archive.md`. The RED must not be an import-path or fixture-construction error. + +- [ ] **Step 6: Review checkpoint** + +Inspect only the new tests and the contract-test diff. Do not commit; commit remains a separate Human Gate. + +## Task 2: Implement Feature Locator, Archive Index, Plan Hash, And ADR Reader Compatibility + +**Files:** + +- Create: `scripts/feature_archive_support.py` +- Create: `templates/feature-archive.md` +- Modify: `scripts/checker_support.py` +- Modify: `scripts/check-adr-requirement-model-trace.py` +- Modify: `tests/test_adr_requirement_model_trace.py` +- Test: `tests/test_feature_archive_support.py` + +- [ ] **Step 1: Add reusable safe-write and canonical helpers** + +Add these exact signatures to `scripts/checker_support.py`: + +```python +def strip_code_span(value: str) -> str: + cleaned = value.strip() + return cleaned[1:-1].strip() if len(cleaned) >= 2 and cleaned[0] == cleaned[-1] == "`" else cleaned + + +def sha256_bytes(value: bytes) -> str: + return hashlib.sha256(value).hexdigest() + + +def canonical_json_bytes(payload: object) -> bytes: + return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + + +def atomic_write_bytes(path: Path, content: bytes) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + handle, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent) + try: + with os.fdopen(handle, "wb") as stream: + stream.write(content) + stream.flush() + os.fsync(stream.fileno()) + os.replace(temporary, path) + except BaseException: + Path(temporary).unlink(missing_ok=True) + raise +``` + +Import only `hashlib`, `json`, `os`, and `tempfile` from the standard library. Add focused tests proving UTF-8 bytes, replacement, and no leftover temporary file after an injected `os.replace` failure. + +- [ ] **Step 2: Create the copy-ready archive index template** + +Create `templates/feature-archive.md` exactly with the explanatory authority boundary and these columns: + +```md +# Feature Archive + +This file locates archived or rehydrated features. Feature specs, tests, notes, requirement sources, and accepted decisions remain authoritative. + +| Feature ID | Month | Current Path | Archive State | Closed At | Delivered Summary | Source Requirements | Applicable Decisions | Last Moved At | +|---|---|---|---|---|---|---|---|---| +``` + +An empty template is valid before the first row; do not use the existing `table()` helper directly because it rejects empty tables. `parse_archive_index()` must parse the header and allow zero rows. + +- [ ] **Step 3: Implement immutable models and archive-index parsing** + +Create `scripts/feature_archive_support.py`. Use the interface contracts above and these constants: + +```python +FEATURE_ID_RE = re.compile(r"^(?P\d{4}-(?:0[1-9]|1[0-2]))-(?P0[1-9]|[12]\d|3[01])-[a-z0-9][a-z0-9-]*$") +MONTH_RE = re.compile(r"^\d{4}-(?:0[1-9]|1[0-2])$") +ARCHIVE_COLUMNS = ( + "Feature ID", "Month", "Current Path", "Archive State", "Closed At", + "Delivered Summary", "Source Requirements", "Applicable Decisions", "Last Moved At", +) +ARCHIVE_STATES = frozenset({"archived", "rehydrated"}) +``` + +Implement: + +```python +def discover_memory_root(project_root: Path) -> Path: + hidden = project_root / ".agent-loop" + legacy = project_root / "agent-loop" + if hidden.is_dir() and legacy.is_dir(): + raise ArchiveContractError("memory-root", "both .agent-loop and legacy agent-loop exist") + if hidden.is_dir(): + return hidden.resolve() + if legacy.is_dir(): + return legacy.resolve() + raise ArchiveContractError("memory-root", "no agent-loop memory root exists", 2) +``` + +`parse_archive_index`, `render_archive_index`, and `resolve_feature_location` must sort by `(month, feature_id)`, reject duplicate IDs, reject duplicate normalized paths, reject unknown states, verify `rehydrated` points flat, and verify `archived` points to `features/YYYY-MM/`. + +- [ ] **Step 4: Implement deterministic plan serialization** + +Implement `ArchivePlan.to_payload`, `canonical_bytes`, `computed_sha256`, and `assert_hash`. The payload used for hashing excludes `plan_sha256`; candidate, move, reference-edit, and snapshot collections are sorted by stable tuple keys. Reject expected hashes that do not match `^[0-9a-f]{64}$` with exit `2`; a valid but different hash is `stale-plan` exit `1`. + +- [ ] **Step 5: Make ADR Feature Spec references resolver-aware** + +Replace the current single-level check at `scripts/check-adr-requirement-model-trace.py` `validate_feature_reference` with: + +```python +FEATURE_SPEC_RE = re.compile( + r"(?:^|/)features/(?:(?P\d{4}-\d{2})/)?(?P[^/]+)/spec\.md$" +) +``` + +Rules: + +- `planned:` accepts flat `features//spec.md` only; +- existing flat or archived paths must exist and stay confined; +- an archived path requires a matching archive-index row when `features/archive.md` exists; +- `Status: proposed | accepted | closed` is valid for an existing Feature Spec owner; +- `closed` proves historical ownership only and does not authorize new execution; +- an archived month mismatch, missing archive row, duplicate row, or `rehydrated` row pointing to a month path fails. + +Add native tests using `features/2026-05/2026-05-08-login/spec.md` plus matching/missing/mismatched index rows. + +- [ ] **Step 6: Run GREEN for locator and ADR compatibility** + +Run: + +```text +python3 -m unittest \ + tests.test_feature_archive_support \ + tests.test_adr_requirement_model_trace \ + tests.test_python_checker_contract -v +bash tests/validate-adr-requirement-model-technical-landing-trace.sh +``` + +Expected: all selected tests PASS; existing valid/proposed/not-needed ADR fixtures retain their prior conclusions. + +- [ ] **Step 7: Review checkpoint** + +Run `python3 -m compileall -q scripts tests` and inspect the new public interfaces for name/type consistency. Do not commit. + +## Task 3: Implement Read-Only Archive/Rehydrate Scan And Check + +**Files:** + +- Create: `scripts/scan-feature-monthly-archive.py` +- Create: `scripts/check-feature-monthly-archive.py` +- Modify: `scripts/feature_archive_support.py` +- Test: `tests/test_feature_monthly_archive_scan.py` +- Create: `tests/test_feature_monthly_archive_apply.py` with pre/post-check RED cases only + +- [ ] **Step 1: Implement exact eligibility extraction** + +Add these pure helper interfaces: + +| Function | Exact signature | +|---|---| +| Inspect one candidate | `inspect_feature(memory_root: Path, feature_id: str, as_of: date) -> ArchiveCandidate` | +| Discover first-level features | `discover_flat_features(memory_root: Path) -> Sequence[FeatureLocation]` | +| Discover exact reference edits | `discover_reference_impacts(project_root: Path, moves: Sequence[Move]) -> Sequence[ReferenceEdit]` | +| Build deterministic plan | `build_archive_plan(project_root: Path, *, operation: Literal["archive", "rehydrate"], selected_months: Sequence[str], selected_feature_ids: Sequence[str], as_of: date) -> ArchivePlan` | + +`inspect_feature` reads `spec.md`, `tasks.md`, `notes.md`, and `project.md`. It requires `Status: closed`, a concrete `## Close Record`, and this exact `## Archive Readiness` metadata contract: + +```md +## Archive Readiness + +Closed At: 2026-05-20 +Delivered Summary: completed login authentication and verified failure paths +Verification: complete +Feature Close Review: complete +Drift: resolved +Project Memory Impact: complete | none +Open Follow-up: none | FU-001, FU-002 +``` + +`Open Follow-up` must be exactly `none` for eligibility. Existing closed features without the block are classified `missing-archive-readiness`; the Agent may propose a human-reviewed notes backfill from existing close evidence, but scan/apply may not infer readiness. `inspect_feature` records blockers instead of throwing so mixed eligible/blocked months produce one reviewable plan. + +Current month is `as_of.strftime("%Y-%m")`. Never call `date.today()` inside planning logic. + +- [ ] **Step 2: Implement conservative Markdown reference impact discovery** + +Scan UTF-8 Markdown files under project root while excluding `.git`, `.archive-txn`, `node_modules`, `vendor`, `.venv`, `dist`, and `build`. Reject symlinked directories and files outside project root. For files above 2 MiB, add a `skipped_references` blocker instead of reading them. + +Support two deterministic edit kinds: + +1. literal canonical paths beginning `.agent-loop/features//` or `agent-loop/features//`; +2. Markdown inline links and reference definitions inside moved feature files when the old link resolves within project root but outside the moved feature directory. + +Preserve anchors and URL query strings with `urllib.parse`; ignore `http`, `https`, `mailto`, and same-document `#anchor` links. Any unparsable candidate containing the old feature path becomes a blocker; do not guess. + +Do not rewrite original human requirement source files. Paths under `requirements//` other than `README.md` and lifecycle-owned index files are recorded as `SkippedReference(classification="immutable-requirement-source")`. Completed proposal/report text that clearly describes a past path may be classified `historical-evidence`; every such row remains visible in the Batch Human Review. Any old-path occurrence that is neither an approved edit nor one of those two preserved classes becomes `unsupported` and blocks apply. + +- [ ] **Step 3: Implement the scan CLI** + +`scripts/scan-feature-monthly-archive.py` must: + +```python +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Build a read-only Feature Monthly Archive plan") + parser.add_argument("--project-root", required=True) + parser.add_argument("--operation", choices=("archive", "rehydrate"), required=True) + parser.add_argument("--month", action="append", default=[]) + parser.add_argument("--feature-id", action="append", default=[]) + parser.add_argument("--as-of", required=True) + return parser +``` + +Archive requires at least one month and no feature IDs; rehydrate requires at least one Feature ID and no months. Print `json.dumps(plan.to_payload(), ensure_ascii=False, sort_keys=True, indent=2)` plus one newline. Catch `ArchiveContractError as error`, call `print(f"{error.category}: {error.detail}", file=sys.stderr)`, and exit with `error.exit_code`. + +- [ ] **Step 4: Implement the read-only check CLI** + +`scripts/check-feature-monthly-archive.py` accepts `--project-root`, `--operation archive|rehydrate|restore`, and `--plan`. It loads UTF-8 JSON, reconstructs/validates `ArchivePlan`, checks current paths, archive rows, snapshots, allowed reference edits, absence/presence of source/target paths appropriate to the operation, and prints a stable PASS summary. It rejects every `unsupported` skipped reference and every remaining durable old-path occurrence; immutable requirement sources and approved historical-evidence rows remain unchanged and are reported separately. It never writes. + +- [ ] **Step 5: Prove scan/check are read-only and cross-root deterministic** + +Run: + +```text +python3 -m unittest \ + tests.test_feature_archive_support \ + tests.test_feature_monthly_archive_scan \ + tests.test_feature_monthly_archive_apply -v +``` + +Expected: scan suites PASS; apply tests that require the missing apply CLI remain RED for the next task. Snapshots before/after scan and pre-check must be byte-identical. + +- [ ] **Step 6: Review checkpoint** + +Search the two read-only CLIs for `write_text`, `write_bytes`, `mkdir`, `rename`, `replace`, `move`, `unlink`, and `rmtree`; only plan-free pure support imports are permitted. Do not commit. + +## Task 4: Implement Expected-Hash Apply, Transaction Journal, Restore, And Rehydrate + +**Files:** + +- Create: `scripts/apply-feature-monthly-archive.py` +- Create: `scripts/restore-feature-monthly-archive.py` +- Modify: `scripts/feature_archive_support.py` +- Complete: `tests/test_feature_monthly_archive_apply.py` +- Create: `tests/test_feature_monthly_archive_restore.py` + +- [ ] **Step 1: Write apply and restore RED cases** + +Cover these exact tests before implementation: + +| Test name | Required assertion | +|---|---| +| `test_apply_moves_two_closed_features_intact_and_updates_archive_index` | two source dirs disappear, two month dirs exist, file bytes match except approved link edits, two sorted locator rows exist | +| `test_apply_rejects_valid_but_different_plan_hash_without_writes` | exit 1 contains `stale-plan`; full tree snapshot unchanged | +| `test_apply_rejects_state_drift_after_scan` | edit one candidate note after scan; apply exits 1; no journal or move exists | +| `test_apply_updates_only_precomputed_reference_edits` | known project/requirement/decision links change; an unrelated Markdown file is byte-identical | +| `test_apply_is_idempotent_and_never_nests_month_directory` | second scan says `already-archived`; second apply has no moves; nested month path does not exist | +| `test_injected_reference_write_failure_restores_directories_and_bytes` | subprocess exits 1; before/after full snapshots equal; restored journal removed | +| `test_interrupted_transaction_can_be_restored_by_new_process` | leave journal at `moving`, invoke restore CLI, assert original tree and no journal | +| `test_incomplete_restore_keeps_journal_and_fails_closed` | create target collision before restore; exit 1; journal remains with `restoring` state | +| `test_rehydrate_moves_archived_feature_flat_and_updates_locator` | month source disappears, flat target exists, row is `rehydrated`, original `spec.md` remains `closed` | + +Use environment variable `AGENT_LOOP_ARCHIVE_FAIL_AFTER=` only inside tests to inject a deterministic failure. Production docs must label it test-only; reject the variable unless `AGENT_LOOP_ARCHIVE_TEST_MODE=1` is also set. + +- [ ] **Step 2: Implement the journal schema and atomic backup** + +Journal path: + +```text +.agent-loop/features/.archive-txn//journal.json +.agent-loop/features/.archive-txn//backups/ +``` + +Journal JSON fields: + +```json +{ + "schema_version": 1, + "transaction_id": "20260714T120000Z-0123456789ab", + "operation": "archive", + "plan_sha256": "64-lowercase-hex-characters", + "state": "prepared", + "moves": [], + "backups": [], + "completed_operations": [] +} +``` + +Allowed states: `prepared`, `moving`, `references-updated`, `checking`, `restoring`, `restored`, `verified`. Persist journal state with `atomic_write_bytes` before and after each irreversible step. Backup only files listed in `reference_edits` plus existing `features/archive.md`; record `missing-before` for newly created index files. + +- [ ] **Step 3: Implement exact-hash revalidation and archive apply** + +The apply CLI uses the same parser fields as scan plus required `--expected-plan-sha256`. It rebuilds the plan from current state, compares the hash before creating `.archive-txn`, then: + +1. creates the journal and backups; +2. creates month directories only for confirmed moves; +3. uses `Path.rename()` for same-memory-root directory moves; +4. renders `features/archive.md` from sorted entries; +5. applies exact precomputed `ReferenceEdit` replacements only when current file hash equals `before_sha256`; +6. writes changed files atomically; +7. runs the same post-check core used by the check CLI; +8. marks verified and removes the transaction directory; +9. removes an empty month directory created by a failed transaction during restore. + +Any exception after journal creation immediately calls `restore_transaction`; report both the original failure and restore result. + +- [ ] **Step 4: Implement rehydrate through the same plan/apply path** + +For `operation=rehydrate`, require the archive row state `archived`, source month path, flat target absence, closed feature, and explicit Feature ID selection. Move the whole directory flat, set row `Archive State: rehydrated`, set `Current Path` to `features//`, update references, and post-check. Do not change `spec.md Status`; Feature Follow-up owns the later `closed -> active` transition. + +- [ ] **Step 5: Implement standalone restore CLI** + +`restore-feature-monthly-archive.py` accepts only `--project-root` and `--transaction-id`. Validate the ID against `^\d{8}T\d{6}Z-[0-9a-f]{12}$`, confine the journal path, restore moves in reverse order, restore exact backup bytes or remove `missing-before` files, verify the pre-transaction snapshots, then remove the journal. Never infer a transaction ID by choosing the newest directory. + +- [ ] **Step 6: Run GREEN including injected failures** + +Run: + +```text +python3 -m unittest \ + tests.test_feature_archive_support \ + tests.test_feature_monthly_archive_scan \ + tests.test_feature_monthly_archive_apply \ + tests.test_feature_monthly_archive_restore -v +``` + +Expected: every success, stale-plan, injected-failure, restore, idempotency, and rehydrate case PASS; no temporary `.archive-txn` remains after successful or successfully restored tests. + +- [ ] **Step 7: Review checkpoint** + +Run `python3 -m compileall -q scripts tests`, inspect mutation functions line-by-line, and confirm no delete/archive-content path exists. Do not commit. + +## Task 5: Publish Coordinated Runtime, Stage, Gate, Template, And Reader Rules + +**Files:** + +- Create: `tests/validate-feature-monthly-archive-runtime.sh` +- Modify all published runtime/design files listed in the File Responsibility Map +- Modify: `templates/root-AGENTS.md` +- Modify: `references/validation-scenarios.md` + +- [ ] **Step 1: Write the coordinated RED contract first** + +Create `tests/validate-feature-monthly-archive-runtime.sh` with `assert_contains` / `assert_not_contains` checks that require: + +```text +feature-archive-maintenance +Feature Monthly Archive +features/archive.md +Feature ID is stable +archive state is not feature lifecycle +scan is read-only +expected plan SHA-256 +Batch Human Gate +transaction journal +post-check +restore +rehydrate before reopened execution +active / blocked / paused features stay flat +no per-feature archive summary +no historical/ +no Deep Archive +no --force +``` + +Require coordinated mentions in `SKILL.md`, `references/runtime.md`, `references/design.md`, `references/artifact-rules.md`, `references/feature-follow-up.md`, `references/stage-guides.md`, `references/workflow-checklists.md`, `references/human-review-summary.md`, `references/recovery-and-backfill.md`, `templates/root-AGENTS.md`, `references/validation-scenarios.md`, and `templates/feature-archive.md`. + +Run: + +```text +bash tests/validate-feature-monthly-archive-runtime.sh +``` + +Expected RED: missing runtime intent/stage/gate/template references. + +- [ ] **Step 2: Add routing without creating an ordinary feature stage** + +Add `feature-archive-maintenance` to Message Intent. Route it after Safety Stop, Remote Discovery, and Memory Recovery, but before Active Feature Guard because it is maintenance of closed history rather than new/active feature execution. If selected candidates include current active/paused paths, eligibility blocks those candidates; it does not switch active work. + +The stage order adds `Feature Monthly Archive If Explicitly Requested` after Re-Adopt/Recovery and before feature construction stages. Auto modes never authorize archive or rehydrate; both stop at their Batch Human Gates. + +- [ ] **Step 3: Add the complete stage procedure and checklist** + +`references/stage-guides.md` section must state: + +```text +Entry: explicit archive/rehydrate request after reliable project memory +Reads: project.md, archive.md, selected feature close artifacts, requirements, decisions, Markdown references +Writes: none during scan; confirmed month moves, archive.md, approved references, temporary journal during apply +Human Gate: exact plan SHA-256 Batch Review +Exit: verified archive/rehydrate, verified restore, or one blocker stage +Next: Chat/report when complete; Ask Human for stale plan/scope; Recovery for stranded journal; Feature Follow-up after verified rehydrate +``` + +Add the matching checklist and Human Review table. The review table includes operation, plan SHA-256, selected months/IDs, eligible, blocked, moves, reference edits, unchanged content, journal/restore, platform evidence, and decision. + +Update `templates/notes.md`, `references/document-templates.md`, and `references/feature-completion-check.md` so close records write the exact `Archive Readiness` block above. This does not auto-archive and does not add a new Close Human Gate. Update `examples/login-feature/notes.md` with a concrete completed block. Regression tests must reject archive eligibility when the block is missing, contains a non-concrete summary, has any value other than the allowed terminal values, or lists an open follow-up ID. + +- [ ] **Step 4: Update Feature Follow-up and recovery rules** + +Follow-up lookup reads Active/Paused first, flat recent features second, `features/archive.md` third, archived feature artifacts fourth. A confirmed archived owner must rehydrate through its own Human-reviewed plan before Feature Follow-up changes lifecycle or starts execution. + +Recovery treats these as stale-memory/safety conditions: + +- archive row target missing; +- archived directory without row; +- duplicate flat/month Feature ID; +- `rehydrated` row pointing to month path; +- incomplete `.archive-txn`; +- old path durable references after verified apply. + +- [ ] **Step 5: Update root Stage Map and block revision** + +Add a row for explicit historical feature archive/rehydrate requests and route to `references/stage-guides.md`, `references/artifact-rules.md`, and `references/feature-follow-up.md`. Update precedence wording consistently. Bump all managed blocks from the current `block-version:1.3.0-20260713.2` to one new full revision value such as `block-version:1.3.0-20260714.1`; use the exact same full value in every managed block and update root-block regression expectations. + +- [ ] **Step 6: Add pressure scenarios** + +Add at least these scenarios to `references/validation-scenarios.md`: + +1. human selects May/June with closed and paused features; +2. plan changes between review and apply; +3. accepted ADR references a closed archived Feature Spec; +4. archived feature owns a day-45 regression and must rehydrate; +5. journal remains after process interruption; +6. duplicate flat/month path and stale archive row; +7. user says “compress” but asks to delete history—route outside this capability; +8. auto mode attempts archive without Batch Human Gate; +9. archive scan under missing controller fallback—read-only discussion only, no apply; +10. reference scanner finds an ambiguous old path and blocks. + +- [ ] **Step 7: Run GREEN coordinated contracts** + +Run: + +```text +bash tests/validate-feature-monthly-archive-runtime.sh +bash tests/validate-feature-monthly-compaction-proposal.sh +bash tests/validate-v1.2.4-root-stage-coverage.sh +bash tests/validate-root-agents-block-refresh.sh +``` + +Expected: all PASS with the new intent, Stage Map, block revision, gate, and preserved proposal boundary. + +## Task 6: Publish Human Docs, CI, Changelog, And Current Authority + +**Files:** + +- Modify: `README.md` +- Modify: `Usage.md` +- Modify: `CHANGELOG.md` +- Modify: `.github/workflows/cross-platform-checkers.yml` +- Modify: `tests/test_python_checker_contract.py` +- Modify: `docs/proposal/v1.3.x/feature-monthly-compaction.md` +- Create: `docs/reports/agent-loop-v1.3.0-feature-monthly-archive-validation-2026-07-14.md` + +- [ ] **Step 1: Add human trigger and safety examples** + +Usage must include this ordinary-language flow: + +```text +Human: 把 2026 年 5 月和 6 月已经关闭的 feature 按月份归档。 +Agent: runs read-only scan and shows plan SHA-256, eligible/blocked rows, moves, references, unchanged content, and restore scope. +Human: confirms the exact batch. +Agent: applies, post-checks, reports transaction evidence, and does not commit automatically. +``` + +Add a rehydrate example showing a separate Human Gate before flow-back. README should explain the directory shape and that archive means location/index compaction, not content deletion. + +- [ ] **Step 2: Extend native CI** + +Append these modules to the workflow unittest command: + +```text +tests.test_feature_archive_support +tests.test_feature_monthly_archive_scan +tests.test_feature_monthly_archive_apply +tests.test_feature_monthly_archive_restore +``` + +Update `test_cross_platform_ci_runs_the_native_suite` to require all four modules and all four archive command paths. Keep Python 3.10/current and Windows/macOS matrix unchanged. + +- [ ] **Step 3: Record current-version behavior** + +Update the 1.3.0 changelog section with implemented behavior, not proposal promises. Do not alter 1.2.4 historical entries and do not bump the version. + +Set proposal status only after fresh verification: + +```text +状态:v1.3.0 Release Human Gate 已批准;发布目标 stable-v1.3.0 +``` + +If Windows jobs have not actually run successfully, write `macOS-verified / Windows-test-defined` and do not mark the proposal implemented cross-platform. + +- [ ] **Step 4: Write focused validation report from real evidence** + +The report must include exact Python/shell counts, RED reasons, eligible/blocked cases, no-mutation hashes, stale-plan evidence, injected-failure restore, rehydrate, platform evidence, Critical/High/Medium findings, scope exclusions, and commit/push authorization status. Do not copy scores from an older report. + +- [ ] **Step 5: Run focused GREEN** + +Run: + +```text +python3 -m unittest \ + tests.test_python_checker_contract \ + tests.test_feature_archive_support \ + tests.test_feature_monthly_archive_scan \ + tests.test_feature_monthly_archive_apply \ + tests.test_feature_monthly_archive_restore \ + tests.test_adr_requirement_model_trace -v +bash tests/validate-feature-monthly-archive-runtime.sh +bash tests/validate-feature-monthly-compaction-proposal.sh +``` + +Expected: all focused suites PASS; scan/check no-mutation and apply/restore fixture cleanup assertions pass. + +## Task 7: Full Validation, Review, And Development-Agent Handoff + +**Files:** + +- Read and follow: `docs/maintenance/full-validation-method.md` +- Create: `docs/reports/agent-loop-v1.3.0-full-validation-2026-07-14.md` +- Review: every file in this plan's responsibility map + +- [ ] **Step 1: Run all native Python and shell tests** + +Run: + +```text +python3 -m unittest discover -s tests -p 'test_*.py' -v +for test_file in tests/validate-*.sh; do bash "$test_file"; done +``` + +Expected: zero failures. Record exact counts from output. + +- [ ] **Step 2: Run mechanical checks** + +Run: + +```text +ruby -e 'require "yaml"; YAML.load_file("SKILL.md")' +python3 -m compileall -q scripts tests +for shell_file in scripts/*.sh tests/*.sh; do bash -n "$shell_file"; done +for ruby_file in scripts/*.rb; do ruby -c "$ruby_file" >/dev/null; done +git diff --check +``` + +Run the repository Markdown-fence and JSON parsers used by the prior 1.3.0 full validation. Remove generated `__pycache__` directories after compile checks. + +- [ ] **Step 3: Perform six-domain semantic pressure review** + +Audit Logic Correctness, Autonomy, Project Entry/Onboarding, Development/Test Workflow, Memory, and Recommendation. Explicitly test routing priority for feature archive versus Active Feature Guard; Batch Human Gate non-bypass; archive state versus lifecycle; rehydrate before execution; stale memory/journal recovery; and no deletion/manual move rationalization. + +- [ ] **Step 4: Request code review** + +Use `requesting-code-review`. Run Spec Review against every proposal acceptance scenario and Standards Review because the diff changes routing, root guidance, recovery, filesystem mutation, and cross-platform tooling. Fix accepted findings through TDD and rerun all affected plus full tests. + +- [ ] **Step 5: Save the full validation report** + +Write a Chinese report with real RED/GREEN evidence, exact counts, score, severity totals, unresolved risks, Windows status, unrelated dirty-work boundary, and explicit statement that commit/push/tag/PR remain unauthorized unless the human separately confirms them. + +- [ ] **Step 6: Final implementation handoff checkpoint** + +Present a table containing proposal coverage, plan tasks completed, tests, platform evidence, review, drift, changed files, unrelated files excluded, remaining risks, and exactly one recommended next stage. Do not commit, push, tag, PR, release, publish, close, or move real project feature directories without the corresponding later Human Gate. + +## Expected Commit Decomposition After A Separate Submit Gate + +If the human later authorizes commits, prefer these reviewable commits; do not create them during plan execution without the Submit two-stage confirmation: + +1. `feat(v1.3.0): 增加 feature 归档路径解析能力` + - shared locator/index/plan model; + - ADR archived Feature Spec compatibility; + - native resolver tests. +2. `feat(v1.3.0): 实现 feature 月度归档事务命令` + - scan/check/apply/restore; + - transaction journal, stale-plan, rehydrate; + - mutation and recovery tests. +3. `docs(v1.3.0): 发布 feature 月度归档工作流` + - runtime/design/stage/root guidance/templates; + - human docs, scenarios, changelog, reports; + - full validation evidence. + +Each meaningful commit uses the repository-required Chinese multiline body. Push both `origin` and `ai-factory` only after explicit push authorization. Do not tag unless separately requested. + +## Plan Self-Review + +### Proposal coverage + +| Accepted proposal requirement | Plan coverage | +|---|---| +| Whole feature directory moves intact | Task 4 apply + file/hash assertions | +| Root `features/archive.md` locator | Task 2 template/parser/resolver | +| Stable Feature ID | Task 2 model and Task 5 artifact rules | +| Only eligible closed historical features | Task 3 eligibility and Task 5 gate | +| Mixed month allowed with blocked feature flat | Task 1/3 fixtures and runtime scenario | +| Follow-up / ADR / Requirement / Project Memory references | Task 2 ADR plus Task 5 coordinated rules | +| Read-only scan and check | Task 3 snapshots and command review | +| Exact Batch Human Gate | Task 5 Human Review and expected plan hash | +| Stale-plan protection | Task 2 hash plus Task 4 apply | +| Persistent failure restore | Task 4 journal and interrupted-process tests | +| Rehydrate before execution | Task 4 behavior plus Task 5 Follow-up routing | +| Windows/macOS standard-library behavior | Tasks 0, 6, and 7 | +| No summary/history/delete/deep archive | Boundary, Task 4 mutation review, runtime negative assertions | + +### Type and naming consistency + +- CLI operation values are exactly `archive | rehydrate`; check adds `restore` only for validation mode. +- Archive state values are exactly `archived | rehydrated`. +- Feature lifecycle remains `draft | active | blocked | paused | closed`. +- Plan hash field and CLI flag consistently use `plan_sha256` / `--expected-plan-sha256`. +- All target-project paths serialize as POSIX paths relative to project root. +- All dates supplied to deterministic behavior use explicit ISO `YYYY-MM-DD` arguments. + +### Risk review + +| Risk | Control | +|---|---| +| Reader misses month paths | Reader Compatibility completes and passes before apply exists | +| Human-approved plan drifts | apply rebuilds plan and requires exact SHA-256 | +| Process dies mid-move | journal state and backups persist before mutation | +| Restore destroys new work | file hashes and exact transaction ID gate restoration; mismatch fails closed | +| Relative links change with directory depth | deterministic Reference Impact edits plus pre/post hashes | +| Accepted ADR meaning is rewritten | only locator/path changes; decision content/status/review stays immutable | +| Active feature is archived | eligibility, project memory, lifecycle, and current-month blockers | +| Same Feature ID exists twice | flat/month collision hard failure | +| Cross-platform paths differ | POSIX serialization, explicit dates, standard library, Windows/macOS fixtures | +| Scope expands into content deletion | negative runtime/test assertions and no deletion CLI | + +### Placeholder and ambiguity result + +The placeholder scan must return no matches for any forbidden incomplete-plan pattern defined by `writing-plans`. If execution discovers a new artifact schema, file boundary, public interface, deletion need, or unsupported reference form, stop at Human Review instead of improvising beyond the accepted proposal. diff --git a/docs/proposal/v1.3.x/feature-monthly-compaction.md b/docs/proposal/v1.3.x/feature-monthly-compaction.md index de41301..879da21 100644 --- a/docs/proposal/v1.3.x/feature-monthly-compaction.md +++ b/docs/proposal/v1.3.x/feature-monthly-compaction.md @@ -1,370 +1,465 @@ -# Proposal: Feature Monthly Compaction +# Proposal: Feature Monthly Archive -状态:讨论草案 -目标版本:v1.2.4 候选 -创建时间:2026-07-01 - -## 背景 - -`agent-loop` 现在把 feature workspace 直接放在 `.agent-loop/features/` 第一层: +状态:v1.3.0 Release Human Gate 已批准;发布目标 stable-v1.3.0 -```text -.agent-loop/features/YYYY-MM-DD-/ -``` +平台证据:`macOS-verified / Windows-verified` -这个结构对当前开发很友好:路径短、Agent 易写、recent scan 易查。但 feature 越来越多以后,第一层目录会变长,人类浏览困难,Agent 做 Feature Follow-up / Flow-back、Targeted Feature Scan、Re-Adopt 时也容易扫描过多历史细节。 +CI 证据:`7253461` 的四个 GitHub Actions matrix jobs 全部成功: -问题不是“历史 feature 不重要”,而是旧 feature 的主要价值已经从施工过程转为交付摘要: +Human Review:2026-07-14,人类确认采用整目录按月归档、根 `features/archive.md` locator、统一引用更新、post-check、失败恢复与 rehydrate 设计 -```text -当前 feature 需要完整施工上下文。 -旧 feature 更需要稳定的最终事实、交付行为、验证摘要和索引关系。 -``` - -## 目标 +实施计划:`docs/proposal/v1.3.x/feature-monthly-archive-implementation-plan.md` -本 proposal 目标是定义一套安全的 feature 月度压缩规则: +目标版本:v1.3.0 候选 -1. 当前月保持 flat,不影响正在开发的 feature; -2. 上个月且整月全部完成后,才允许月度压缩; -3. 压缩后第一层目录按月份收束; -4. 旧 feature 以 `archive.md` 作为主入口; -5. 历史施工文件默认进入 `historical/`,不默认删除; -6. 明确哪些索引关系、引用路径和扫描规则会受影响; -7. 为未来实施提供安全 gate 和验证方向。 +创建时间:2026-07-01 -## 非目标 +最近更新:2026-07-14 -第一版不做以下事情: +> 文件名保留 `feature-monthly-compaction.md` 以维持历史引用;本 proposal 的能力名称和行为已经收敛为 `Feature Monthly Archive`。这里的 archive 只改变 feature 目录位置和默认发现入口,不压缩、重组或删除 feature 内容。 -- 不自动压缩当前月 feature; -- 不在月份中存在 active / paused / in-progress feature 时自动压缩整月; -- 不默认删除 `spec.md`、`tasks.md`、`tests.md`、`plan.md`、`notes.md` 等历史细节; -- 不默认做 Partial month compaction; -- 不把 `requirements/` 的原始需求材料压缩成摘要; -- 不把 feature archive 变成 project memory; -- 不替代 Feature Follow-up / Flow-back 的归属判断; -- 不破坏 human gate:移动、压缩、删除历史细节都必须经过人类确认。 +## 摘要 -## 核心观点 +人类可以选择一个或多个历史月份,例如 5 月和 6 月。Agent 先扫描这些月份对应的 flat feature,只有生命周期为 `closed` 且关闭证据完整的 feature 才能进入候选。人类确认后,Agent 把每个候选 feature 目录原样移动到各自的月份目录,并在 `.agent-loop/features/archive.md` 记录稳定 Feature ID、当前位置和一行交付摘要。 ```text -当前月保持 flat。 -上个月且整月全部完成后,才允许月度压缩。 -Slim With History 是默认压缩模式。 -Deep Archive / Summary Only 永远不是默认动作。 +Human selects 2026-05 and 2026-06 +→ read-only Feature Monthly Archive Scan +→ show eligible / blocked features and reference updates +→ one Batch Human Gate +→ move each eligible feature directory intact +→ update features/archive.md and durable references +→ post-check paths, links, indexes, and feature contents ``` -目录压缩解决的是 `.agent-loop/features/` 第一层过长的问题。内容压缩解决的是旧 feature 不应再让 Agent 默认深读完整施工过程的问题。 +这个能力解决的是 `.agent-loop/features/` 第一层过长和历史 feature 默认扫描过重的问题,不解决磁盘空间问题。 -这两个目标可以一起做,但默认策略必须保守:移动到月目录、生成摘要、保留历史细节。 +## 目标 -## 压缩模式 +1. 当前或仍在工作的 feature 继续留在 `.agent-loop/features/` 第一层; +2. 人类可以一次选择一个或多个历史月份进行归档; +3. 每个 eligible feature 目录完整移动到 `features/YYYY-MM/`,内部文件结构不变; +4. `features/archive.md` 成为 archived Feature ID 到当前位置的稳定 locator; +5. Follow-up、ADR、Requirement Mapping 和 Project Memory 使用统一引用更新逻辑; +6. 归档前只读发现,归档后强制检查,失败时恢复; +7. macOS 与 Windows 使用同一套 Python 标准库脚本和 fixture。 + +## 非目标 -| Mode | 适用对象 | 目录形态 | 内容策略 | 默认 | -|---|---|---|---|---| -| Full | 当前月 feature;active / paused / in-progress feature;有 open follow-up 的 feature | `.agent-loop/features/YYYY-MM-DD-/` | 保留完整 `product.md`、`spec.md`、`tasks.md`、`tests.md`、`plan.md`、`notes.md`、handoffs 和复杂细节目录 | 是 | -| Slim With History | 上个月或更早,且整月全部完成、无 open follow-up、索引已回填 | `.agent-loop/features/YYYY-MM/YYYY-MM-DD-/` | `archive.md` 成为主入口;旧施工文件移入 `historical/` | 是 | -| Deep Archive / Summary Only | 更早月份,且人类明确要求进一步深压缩 | `.agent-loop/features/YYYY-MM/YYYY-MM-DD-/` | 默认只保留 `README.md` 和 `archive.md`;删除、打包或外移 `historical/` 需要强 human gate | 否 | +第一版明确不做: -Deep Archive 永远不是默认动作。即使 5 月及以前的 feature 已经很旧,也只是具备“可建议深压缩”的资格,不代表 Agent 可以自动删除历史细节。 +- 不自动按日历月份运行; +- 不自动归档当前月; +- 不移动 `active`、`blocked` 或 `paused` feature; +- 不修改 feature 的业务内容或生命周期含义; +- 不生成每个 feature 的新 `archive.md`; +- 不创建 `historical/`; +- 不创建 `features/YYYY-MM/INDEX.md`; +- 不删除、打包、外移或摘要替代 `spec.md`、`tasks.md`、`tests.md`、`plan.md`、`notes.md`、`product.md`、`contracts.md` 或细节目录; +- 不压缩 `requirements/`、`.agent-loop/decisions/` 或 project memory; +- 不把 `features/archive.md` 变成产品、需求、决策或验证事实的权威来源; +- 不允许 Agent 手工移动目录来绕过 scan、Human Gate、post-check 或 restore。 -## 推荐目录 +Deep Archive、Summary Only 和历史文件删除不属于本 proposal。未来如果确实需要,应单独提案和单独评估恢复风险。 -当前月保持 flat: +## 目录模型 + +归档前: ```text .agent-loop/ features/ - 2026-07-01-wallet-recharge/ + 2026-05-08-login/ + spec.md + tasks.md + tests.md + plan.md + notes.md + 2026-06-12-payment/ product.md spec.md tasks.md tests.md plan.md notes.md + contracts.md + contracts/ + 2026-07-10-current-feature/ + spec.md + tasks.md + tests.md + plan.md + notes.md ``` -上个月整月完成后压缩为 Slim With History: +人类选择归档 2026-05 和 2026-06 后: ```text .agent-loop/ features/ - 2026-07-01-wallet-recharge/ - 2026-07-03-token-deduction/ + archive.md + 2026-05/ + 2026-05-08-login/ + spec.md + tasks.md + tests.md + plan.md + notes.md 2026-06/ - INDEX.md - 2026-06-13-login/ - README.md - archive.md - historical/ - product.md - spec.md - tasks.md - tests.md - plan.md - notes.md - handoffs/ - plans/ - tasks/ - tests/ + 2026-06-12-payment/ + product.md + spec.md + tasks.md + tests.md + plan.md + notes.md + contracts.md + contracts/ + 2026-07-10-current-feature/ + spec.md + tasks.md + tests.md + plan.md + notes.md ``` -更早月份在强确认后可进入 Deep Archive / Summary Only: +归档只增加月份目录并移动整个 feature 目录。feature 内部同目录或子目录关系保持不变;由于目录深度增加,指向 feature 外部的相对 Markdown 链接必须进入 Reference Impact Scan 并按新位置更新。 + +## 稳定 Feature ID 与路径解析 + +Feature ID 保持不变: ```text -.agent-loop/ - features/ - 2026-05/ - 2026-05-08-upload/ - README.md - archive.md +2026-05-08-login ``` -## 触发入口 +路径可以变化: -人类可以这样说: +```text +flat: .agent-loop/features/2026-05-08-login/ +archived: .agent-loop/features/2026-05/2026-05-08-login/ +``` + +规则: + +1. Feature ID 是稳定身份;目录路径只是当前位置; +2. active / blocked / paused feature 必须位于第一层; +3. archived feature 通过 `features/archive.md` 解析当前位置; +4. 新增或更新的 durable relationship 应同时记录 Feature ID; +5. 仍然保存直接路径的现有引用必须在移动时更新; +6. resolver 不得仅依赖 `features/*/spec.md` 这种单层 glob; +7. archived feature 的原 `spec.md` 仍然是 Feature Spec,归档不会把它替换为摘要。 + +## `features/archive.md` + +`features/archive.md` 是 archive locator 和移动历史账本,不是业务事实来源。它在第一次成功归档时创建,此后由 archive / rehydrate 操作维护。 + +推荐模板: + +```md +# Feature Archive + +This file locates archived or rehydrated features. The feature's own spec, tests, notes, requirement sources, and accepted decisions remain authoritative. + +| Feature ID | Month | Current Path | Archive State | Closed At | Delivered Summary | Source Requirements | Applicable Decisions | Last Moved At | +|---|---|---|---|---|---|---|---|---| +| 2026-05-08-login | 2026-05 | `.agent-loop/features/2026-05/2026-05-08-login/` | archived | 2026-05-20 | 完成登录认证与失败路径验证 | `.agent-loop/requirements/2026-05-01-login/` | `.agent-loop/decisions/ADR-001-login.md` | 2026-07-14 | +``` + +字段规则: + +- `Feature ID`:稳定且唯一; +- `Month`:来自 Feature ID 的年月,不从移动日期推导; +- `Current Path`:当前真实目录; +- `Archive State`:`archived | rehydrated`,它不是 feature lifecycle status; +- `Closed At`:来自 feature Close Record; +- `Delivered Summary`:来自已关闭 feature 的交付/关闭记录,只写一行,不创造新产品含义; +- `Source Requirements` / `Applicable Decisions`:只保存 locator; +- `Last Moved At`:最近一次 archive 或 rehydrate 日期。 + +`spec.md` 仍使用正式 lifecycle: + +```text +draft | active | blocked | paused | closed +``` + +归档资格只接受 `closed`。不要把 `implemented` 或 `archived` 写成 feature lifecycle status。 + +## 人类触发 + +示例: ```text -现在 7 月了,把 6 月份已经做完的 feature 压缩一下。 +把 2026 年 5 月和 6 月已经关闭的 feature 按月份归档。 ``` -Agent 应进入 Feature Compaction Scan,而不是直接移动文件。 +这条指令只授权进入 `Feature Monthly Archive Scan`。Agent 必须先展示扫描结果和精确变更范围,再请求一次 Batch Human Gate;不得收到月份后直接移动目录。 -## Feature Compaction Scan +## Feature Monthly Archive Scan + +Scan 是只读操作。 输入: -- 当前日期; -- `.agent-loop/features/` 第一层 flat feature; -- 已有 month bucket; -- `project.md` Active Feature / Paused Features; -- feature `spec.md`、`tasks.md`、`tests.md`、`plan.md`、`notes.md`、close 记录; +- 人类选择的月份; +- `.agent-loop/features/` 第一层 feature; +- 已有月份目录; +- `features/archive.md`(如果存在); +- `project.md` Active Feature / Paused Features / Current Work; +- feature `spec.md`、`tasks.md`、`tests.md`、`plan.md`、`notes.md`、close record; - requirement set README 和 optional `requirements/INDEX.md`; - `.agent-loop/decisions/*.md`; -- recent Feature Follow-up / Flow-back notes。 +- Follow-up / Flow-back、verification、review 和 drift evidence; +- repository 内指向候选 feature 旧路径的文本与 Markdown 链接。 输出: - Candidate Matrix; -- blocked month / blocked feature reason; -- proposed compaction mode; -- affected index relationship list; -- human confirmation request。 +- eligible 和 blocked 原因; +- old path → new path 映射; +- `features/archive.md` 预期新增/更新行; +- Reference Impact List; +- path collision、symlink/path escape 和 stale-plan 检查结果; +- post-check 与 restore 范围; +- Batch Human Review Summary。 ## Candidate Matrix -Agent 在执行压缩前必须展示类似表格: +```md +| Month | Feature ID | Current Path | Lifecycle | Close Evidence | Open Follow-up | Reference Impact | Decision | +|---|---|---|---|---|---|---:|---| +| 2026-05 | 2026-05-08-login | `.agent-loop/features/2026-05-08-login/` | closed | complete | none | 4 | eligible | +| 2026-05 | 2026-05-22-import | `.agent-loop/features/2026-05-22-import/` | paused | incomplete | none | 2 | blocked | +| 2026-06 | 2026-06-12-payment | `.agent-loop/features/2026-06-12-payment/` | closed | complete | none | 7 | eligible | +``` -| Month | Feature | Current Path | Status | Open Follow-up | Index Backfill | Proposed Mode | Decision | -|---|---|---|---|---|---|---|---| -| 2026-06 | 2026-06-13-login | `.agent-loop/features/2026-06-13-login/` | closed | none | complete | Slim With History | pending-human | -| 2026-06 | 2026-06-20-upload | `.agent-loop/features/2026-06-20-upload/` | in-progress | none | incomplete | Full | blocked | +资格按 feature 判断,不要求一个月份的所有 feature 都完成。人类可以一次批准多个 selected-month 中的 eligible feature;blocked feature 保持 flat,不影响其他 eligible feature,但 Human Review Summary 必须明确显示同月存在 flat 与 archived feature。 -如果某个月存在 blocked feature,默认不做整月压缩。 +## Eligibility And Safety Gate -## Safety Gate +每个 candidate 必须同时满足: -整月进入 Slim With History 前,必须满足: +- 人类明确选择了该月份; +- 该月份不是当前月份; +- feature `spec.md` lifecycle 是 `closed`; +- Close Record 存在; +- tasks 全部为 `done` 或经过人类确认移出范围的 `skipped`; +- fresh verification、Feature Close Review、drift decision 和 project-memory impact 已记录; +- 没有 open follow-up、unresolved drift、review blocker 或待恢复的失败操作; +- `project.md` 不把它列为 Active Feature 或 Paused Feature; +- requirement `Feature Mapping` / `Implemented By` 和相关 decision `Implemented By` 已完成关闭时回填; +- 源目录真实存在且位于 `.agent-loop/features/` 第一层; +- 目标月份与 Feature ID 年月一致; +- 目标路径不存在,不发生大小写或 Unicode 归一化碰撞; +- 源目录、目标目录和被更新引用不得通过 symlink 逃逸 workspace; +- Reference Impact List 覆盖所有已发现旧路径引用和跨 feature 边界相对链接; +- post-check 和 restore 所需的原路径、目标路径、内容快照与引用快照能够写入批次 transaction journal。 -- 该月份不是当前月; -- 该月份所有 feature 都是 terminal 状态,例如 closed / implemented / archived; -- 没有 active / paused / in-progress feature; -- 没有 open follow-up、unresolved drift、未完成 verification、未处理 review blocker; -- requirement set README 的 `Implemented By` / `Feature Mapping` 已回填; -- optional `requirements/INDEX.md` 如存在,也已更新实现状态; -- relevant `decisions/*.md` 的 `Implemented By` 已回填; -- `project.md` 不再把这些 feature 作为 Active Feature 或 Paused Features; -- archive summary 能覆盖 delivered behavior、verification evidence、drift / follow-up notes、changed files / public interfaces; -- Agent 已列出所有需要更新的索引关系; -- 人类明确确认。 +任何一项不满足都必须 fail closed。不要提供 `--force` 绕过资格和安全检查。 -## Human Gate +## Batch Human Gate -以下动作都必须 Human-gated: +一次普通归档批次只需要一个 Human Gate。确认摘要必须同时列出: -- 创建 month bucket; -- 移动 feature workspace; -- 生成或覆盖 `archive.md`; -- 移动历史细节到 `historical/`; -- 更新 requirement / decision / project memory 索引引用; -- 执行 Partial month compaction; -- 执行 Deep Archive / Summary Only; -- 删除、打包、外移 `historical/`。 +- selected months; +- eligible / blocked Feature IDs; +- 每个 old path → new path; +- 将创建的月份目录; +- `features/archive.md` 预期变化; +- Requirement、ADR、Project Memory、Follow-up 和其他引用更新; +- 不会修改或删除的 feature 内容; +- post-check 与失败 restore 行为。 -Partial month compaction 不是默认行为。只有当人类明确要求“这个月部分完成的也先压缩已完成 feature”时,Agent 才能建议,并且必须在 `features/YYYY-MM/INDEX.md` 标记该月份为 `mixed`。 +人类确认这个完整批次后,Agent 可以执行其中列出的目录创建、移动、引用更新和 post-check,不为每个机械步骤重复询问。 -## Archive Summary Template +任何新增月份、Feature ID、目标路径、引用文件、删除动作或批次范围变化都使原确认失效,必须重新 Scan 和确认。 -`archive.md` 应该成为旧 feature 的主入口: +## 引用更新逻辑 -```md -# Feature Archive: +### Feature Follow-up / Flow-back -Status: archived -Compaction Mode: Slim With History | Deep Archive / Summary Only -Original Feature ID: YYYY-MM-DD- -Current Path: -Archived At: -Archived By: +扫描顺序: -Source Requirements: -- +1. 从 `project.md` 读取 Active / Paused feature; +2. 扫描第一层 flat feature; +3. 读取 `features/archive.md`; +4. 对匹配的 archived Feature ID 读取其原有 `spec.md`、`tests.md` 和 `notes.md`; +5. 只有摘要与路径信号不足时才继续读取复杂细节目录。 -Applicable Decisions: -- +归档不会改变 owning feature 判断,也不会把 archived feature 自动重开。 -Implements Decisions: -- Decision: - - Implemented Slice: +### ADR / Decision -Delivered Behavior: -- +- `Applicable Decisions` 和 `Implements Decisions` 的语义不变; +- 直接保存 Feature Spec 路径的 ADR / decision 引用更新到 month path; +- validator 必须同时支持 flat 和 archived Feature Spec; +- 后续 durable mapping 应优先保存 Feature ID,并通过 resolver / `features/archive.md` 定位路径; +- 归档不得改写 accepted decision 的含义、状态或 Human Review Evidence。 -Key Design Decisions: -- +### Requirement Mapping -Changed Files / Public Interfaces: -- +- requirement set README 的 `Feature Mapping` / `Implemented By` 保留 Feature ID; +- 直接路径更新为当前 month path; +- optional `requirements/INDEX.md` 如保存 feature path,也同步更新; +- 原始 requirement source 文件保持不变。 -Verification Summary: -- Evidence: -- Commands: -- Result: +### Project Memory -Drift / Follow-up Notes: -- +- Active Feature 和 Paused Features 不得指向 archived feature; +- Current Work 不得把 archived feature 当作正在执行的 feature; +- Recent Feature 或历史 locator 如存在,更新为 Feature ID 加当前路径; +- 不把所有 archive rows 复制进 `project.md`,历史 inventory 由 `features/archive.md` 负责。 -Known Risks: -- +### Internal And External Links -Historical Detail Location: -- historical/ -``` +- feature 目录内部文件和子目录保持原样; +- 同目录和 feature 内部相对链接通常保持有效; +- 因目录加深而受影响的 `requirements/`、`decisions/`、project docs 等跨边界相对链接必须更新; +- workspace 内指向 old flat path 的 durable reference 必须更新; +- 历史报告中只用于描述过去命令或过去路径的纯文本证据可以保留,但必须由 Reference Impact Scan 分类为 `historical-evidence`,不能静默忽略。 -`README.md` 应很短: +## Apply、Post-Check 与失败恢复 -```md -# Archived Feature +Apply 只能执行人类确认过的 Feature ID 和路径映射。开始时必须重跑关键 preflight;如果文件、状态、引用计数或目标路径与确认时不同,判定 `stale-plan` 并停止。 -Read `archive.md` first. -Historical implementation details are under `historical/` when present. +Apply 在首次写入前创建临时 transaction journal: + +```text +.agent-loop/features/.archive-txn// ``` -## 影响的索引关系 +journal 只保存本批次操作映射、preflight 摘要,以及将被改写的 `archive.md` / Requirement / ADR / Project Memory / link 文件快照;它不复制 feature 目录。成功 post-check 后删除 journal;失败或进程中断时保留 journal,供 restore 在新的 Agent 进程中恢复。journal 是临时恢复材料,不是 feature lifecycle 或 project memory 的新权威来源。 -Feature 月度压缩会改变路径和默认阅读入口,因此会影响以下索引关系。 +推荐执行顺序: -| Index / Relationship | 当前用途 | 压缩影响 | 必须更新 | -|---|---|---|---| -| `features/INDEX.md` | 全 feature inventory、month view、状态视图 | 需要记录 feature 当前路径、month bucket、compaction mode、archive path | 是 | -| `features/YYYY-MM/INDEX.md` | 月份内 feature 列表 | 新增月度索引,记录该月是否 fully compacted / mixed / deep archived | 是 | -| requirement set README | `Implemented By`、`Delivery Phases`、`Feature Mapping` | feature 路径从 flat 改为 month bucket;phase 到 feature 的链接要改到 archive path 或 current path | 是 | -| `requirements/INDEX.md` | requirement inventory、implemented view、backlog/deferred view | 如果存在,要同步 implemented feature path 和 status | 条件是已存在或本来应更新 | -| `.agent-loop/decisions/*.md` | ADR / decision 的 `Implemented By` | feature 实现引用要改到新的 archive path;`Applicable Decisions` / `Implements Decisions` 在 archive summary 中保留 | 是 | -| `project.md` | Active Feature、Paused Features、Current Work、recent references | Active Feature 和 Paused Features 不能指向 archived feature;旧 closed feature 可指向 archive summary | 是 | -| feature internal links | `Source Requirements`、`Applicable Decisions`、`Implements Decisions` | `archive.md` 需要保留这些关系;historical 文件内的旧相对链接可保留为历史证据,但外部索引要更新 | 是 | -| Feature Follow-up / Flow-back | recent / historical feature ownership scan | 扫描规则要同时支持 flat feature 和 month bucket;archived feature 先读 `archive.md`,必要时再读 `historical/` | 是 | -| Drift Check | 关闭后行为和文档一致性 | 压缩前要确认 drift 已处理;压缩后要确认 archive summary 与 requirement/decision/project memory 一致 | 是 | -| verification evidence | 测试、构建、E2E、review evidence | `archive.md` 必须摘要 verification evidence;完整 evidence 留在 `historical/notes.md` 或 historical detail | 是 | -| scripts / validation glob | 查找 `features/*/spec.md`、`notes.md`、`tests.md` 的脚本 | 需要兼容 `features/YYYY-MM//archive.md` 和 `features/YYYY-MM//historical/spec.md` | 是 | -| recovery / re-adopt scans | 重接管项目时识别历史 feature | 不应把 archived feature 当 active feature;应优先读 archive summary | 是 | +```text +revalidate confirmed scope +→ prepare reversible operation snapshot +→ persist transaction journal +→ create selected month directories +→ move whole feature directories +→ update features/archive.md +→ update approved durable references +→ run post-check +→ keep result only when every check passes +``` -## 路径兼容规则 +Post-check 必须确认: -第一版需要同时支持两种 feature layout: +- 每个 source path 已不存在; +- 每个 target path 存在且 Feature ID 与月份匹配; +- feature 文件清单和内容哈希与移动前一致,只有已批准的跨边界链接调整除外; +- `features/archive.md` 每个 Feature ID 恰好一行且 Current Path 存在; +- 没有 durable reference 仍指向 old flat path; +- Requirement、ADR、Project Memory 和 Follow-up resolver 能解析新路径; +- blocked / unselected / current feature 没有变化; +- 重复运行 scan 得到 `already-archived`,不会再次嵌套月份目录。 -```text -.agent-loop/features/YYYY-MM-DD-/ -.agent-loop/features/YYYY-MM/YYYY-MM-DD-/ -``` +如果任一写入或 post-check 失败,操作必须恢复: -扫描顺序建议: +- 目录移回原 flat path; +- 从 transaction journal 恢复 `features/archive.md` 和引用文件快照; +- 检查恢复后文件清单、哈希和旧路径; +- 报告失败点和恢复结果; +- restore 未完全通过时进入 Safety Stop,不得报告归档成功。 -1. Active / Paused feature from `project.md`; -2. current-month flat features; -3. recent flat features in 30-day lookback; -4. month bucket `archive.md`; -5. `historical/` only when archive summary is insufficient. +## Rehydrate / Reopen -Feature ID 仍然是 `YYYY-MM-DD-`。路径可能变化,ID 不变。索引更新必须使用当前路径。 +当 Follow-up 确认 archived feature 是 owning feature,并且人类确认 flow-back / reopen 后: -## Requirements 策略 +```text +archived feature selected +→ read-only rehydrate scan +→ Human Review shows month path → flat path and reference impact +→ move the whole directory back to `.agent-loop/features//` +→ update archive row to `rehydrated` and Current Path to flat +→ update durable references +→ post-check +→ normal Feature Follow-up lifecycle may set the feature active +``` -Requirements 不做内容压缩。 +Rehydrate 只是恢复工作目录位置,不自行授权 scope change,不自行把 `closed` 改为 `active`。Feature Follow-up 的人类决定仍然控制是否重开。 -原因: +## 跨平台脚本边界 -- requirement 是人类源材料; -- 需求源材料保持原样,不应被摘要替代; -- requirement lifecycle 和 Delivery Phases 仍由 requirement set README 负责; -- 压缩 feature 不应改变原始需求。 +生产脚本使用 Python 3.10+ 标准库,并复用 cross-platform runtime 的 UTF-8、BOM/CRLF、确定性排序、path confinement 和 exit-code 约定。 -后续可以讨论 requirement month bucket: +建议入口: ```text -.agent-loop/requirements/YYYY-MM/YYYY-MM-DD-/ +scripts/scan-feature-monthly-archive.py +scripts/apply-feature-monthly-archive.py +scripts/check-feature-monthly-archive.py +scripts/restore-feature-monthly-archive.py ``` -但第一版只把 feature 压缩作为第一版默认能力。Requirement 的第一层过长问题优先靠 `requirements/INDEX.md` 和未来可选 bucket 解决,不做内容压缩。 - -## 与 Feature Follow-up / Flow-back 的关系 +契约: -Feature Follow-up / Flow-back 的默认 30 天 lookback 仍然有效,但不再假设所有 feature 都在 `.agent-loop/features/*/` 第一层。 +- scan 和 check 只读; +- apply 和 restore 只接受人类确认范围; +- Python capability 不满足时 exit 2 并 fail closed; +- 不回退到 Ruby、Bash、PowerShell 或 Agent 手工移动; +- 不自动安装 Python; +- macOS 与 Windows 对相同 fixture 给出相同候选、阻塞、路径和恢复结论。 -如果 bug/change 指向 archived feature: +## 实施顺序 -1. 先读 `archive.md`; -2. 判断 delivered behavior、changed files、public interfaces、known risks 是否匹配; -3. 不足时再读 `historical/spec.md`、`historical/tests.md`、`historical/notes.md`; -4. 如果确认为 owning feature,可以建议 flow-back; -5. 如果 archived feature 已 deep archived 且缺少 historical detail,应记录 evidence limitation。 +### Phase 0: Cross-Platform Python Runtime Acceptance -## 与 Close / Drift Check 的关系 +- canonical Python checker、macOS parity 和 CI matrix 已实现; +- `7253461` 的 Windows/macOS × Python 3.10/3.x matrix 已全部成功; +- CI 配置本身仍不能替代执行证据,发布判断引用具体 run `29320389912`; +- archive apply / restore 已实现并完成发布前全量验证,v1.3.0 Release Human Gate 已于 2026-07-14 批准,发布目标为 `stable-v1.3.0`。 -Feature Close 不直接压缩,但要让未来可压缩: +### Phase 1: Proposal Revision -- close summary 要足够支持 archive summary; -- requirement mapping 要完整; -- decision mapping 要完整; -- verification evidence 要可摘要; -- drift decision 要记录; -- project memory update 要完成或明确 none。 +- 本 proposal 收敛为整目录归档; +- 移除内容重组、`historical/`、Deep Archive 和删除路径; +- 明确 Feature ID、archive locator、Batch Human Gate、post-check 和 rehydrate。 -Feature Compaction Scan 可以视为月度 close 后维护动作。它不替代单个 feature close。 +### Phase 2: Reader Compatibility -## 迁移策略 +- 增加统一 Feature Path Resolver; +- Follow-up、Targeted Scan、Recovery / Re-Adopt 支持 flat 与 month path; +- ADR / requirement validators 支持 archived Feature Spec; +- 新建 active feature 仍只使用 flat path; +- 增加 `features/archive.md` 模板和 validation scenarios; +- 完成 reader compatibility 后仍不移动真实 feature。 -### Phase 1: Proposal +### Phase 3: Scan And Post-Check -- 只建立规则和影响面; -- 不改运行时规则; -- 不移动现有 feature。 +- 实现只读 scan 和 check; +- 固定 Candidate Matrix、Reference Impact List、exit code 和 deterministic output; +- 用 fixture 证明 active / paused / blocked、open follow-up、stale path、collision、symlink escape 和 broken link 会 fail closed。 -### Phase 2: Runtime Support +### Phase 4: Apply And Restore -- 增加 feature path resolver; -- 所有 recent / targeted / follow-up scan 支持 flat 和 month bucket; -- 增加 `archive.md` / monthly `INDEX.md` 模板; -- 增加 validation scenarios。 +- 实现人类确认后的整目录移动和引用更新; +- 实现 stale-plan stop、失败恢复、幂等和 rehydrate; +- 在 Windows 与 macOS 运行相同 fixture; +- 通过 focused validation、full validation 和 Human Review 后才允许真实项目归档。 -### Phase 3: Compaction Command +## 验收场景 -- 人类触发 Feature Compaction Scan; -- Agent 输出 Candidate Matrix; -- 人类确认后执行 Slim With History; -- 复验所有索引关系。 +至少覆盖: -### Phase 4: Optional Deep Archive +1. 人类选择 5 月和 6 月,多个 closed feature 被移动到各自月份; +2. 同月 paused feature 保持 flat,eligible closed feature仍可在明确批次中归档; +3. 当前月、active、blocked、paused、open follow-up 和 incomplete close feature 被拒绝; +4. 整目录移动前后文件清单和哈希一致; +5. 跨边界相对链接和 old flat path 引用被正确更新; +6. `features/archive.md` locator 可以解析所有 archived Feature ID; +7. ADR Feature Spec、Requirement Mapping、Project Memory 和 Follow-up 扫描能解析 month path; +8. 路径碰撞、大小写冲突、Unicode 差异、symlink escape 和 stale-plan 被拒绝; +9. 中途失败恢复全部目录和引用; +10. 重复 apply 不产生 `YYYY-MM/YYYY-MM/`; +11. rehydrate 把整个 feature 恢复到 flat path,并保持 Feature ID 与文件内容; +12. Windows 与 macOS 对相同 fixture 输出一致; +13. scan / check 不修改任何文件; +14. 任何脚本都不提供删除历史内容或 `--force` 绕过路径。 -- 仅在更早月份、人类明确要求、historical detail 已不再需要时执行; -- 必须单独列出将删除或外移的文件; -- 必须保留 `archive.md` 和必要 verification summary。 +## Proposal Boundary -## 待讨论问题 +本文件仍是 proposal,不是发布运行时权威。它不会自行改变 `SKILL.md`、`references/runtime.md`、`references/design.md`、Feature Follow-up、ADR validator 或目标项目目录。 -- `features/INDEX.md` 是否应该在第一个 month bucket 出现时自动创建? -- old flat feature 的外部人类书签会失效,是否需要额外生成迁移报告? -- Deep Archive 是否应该支持压缩包,而不是删除 `historical/`? -- requirement month bucket 是否应该和 feature compaction 同期实现,还是作为独立 proposal? -- 如果一个月份有一个 long-running paused feature,是否允许其他 closed features 做 Partial month compaction? +本实现是在 proposal 与实施计划分别获得 Human Review 授权后开始,并先完成 Phase 0、Reader Compatibility 与 RED/GREEN 契约。源码仓库中的实现与测试不会移动任何真实目标项目 feature;目标项目执行 archive 或 rehydrate 仍必须分别经过运行时 Human Gate。 diff --git a/docs/proposal/v1.3.x/onboarding-core-flow-completeness-implementation-plan.md b/docs/proposal/v1.3.x/onboarding-core-flow-completeness-implementation-plan.md new file mode 100644 index 0000000..22e5f86 --- /dev/null +++ b/docs/proposal/v1.3.x/onboarding-core-flow-completeness-implementation-plan.md @@ -0,0 +1,223 @@ +# Onboarding Core Flow Completeness Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILLS: use `writing-skills` for documentation RED/GREEN/REFACTOR, `test-driven-development` for executable validators, `verification-before-completion` before completion claims, and `requesting-code-review` before handoff. Agent Loop controls paths, Human Gates, and submit authorization. + +**Goal:** Make Evidence-Graph + DDD Onboarding prove that critical project flows, branches, terminal states, recovery paths, diagrams, narrative, and code evidence are traceably complete before `newcomer-ready`. + +**Architecture:** Extend the existing onboarding pipeline without changing its stage order or adding Human Gates. Stable Flow IDs and Slice IDs connect Evidence Graph, Onboarding Spec, Tasks, Flow docs, Coverage, and Batch Review; an executable validator checks that trace chain, while semantic pressure scenarios check that generic or incomplete docs are rejected. + +**Tech Stack:** Markdown skill sources and templates, Bash contract tests, Ruby artifact validator, repository examples, existing Agent Loop full-validation method. + +--- + +## Execution Boundary + +- Implement inside the Agent Loop skill source repository, not a target project's `.agent-loop/`. +- Preserve the existing uncommitted 1.3.0 version alignment. +- Do not create a new Human Gate; retain only Onboarding Spec Acceptance and Onboarding Tasks Full Execution Gate. +- Do not commit, push, tag, publish, or release without a separate human submit confirmation. +- Do not run repository-wide/full-skill validation without explicit human permission; default to onboarding feature-scoped validation. +- Treat `docs/proposal/` as design/history only; published behavior must be carried by `SKILL.md`, `references/`, and `templates/`. + +## Task 1: Establish RED contracts + +**Files:** + +- Create: `tests/validate-onboarding-core-flow-completeness.sh` +- Create: `tests/fixtures/onboarding-core-flow/invalid-missing-recovery/` +- Reference: `docs/proposal/v1.3.x/onboarding-core-flow-completeness.md` + +- [ ] Add contract assertions requiring `Core Flow Inventory`, stable `Flow ID`, `Slice ID`, terminal outcomes, selection/deferred reason, evidence chain, diagram-to-slice mapping, and `Completeness Hard Gate` in the owning runtime/reference/templates. +- [ ] Add a validator invocation that expects the valid reference under `examples/ai-meeting-minutes-backend/onboarding-db/` to pass and the invalid fixture to fail with `missing required slice: CF-ORDER-PAYMENT/S07`. +- [ ] Run `bash tests/validate-onboarding-core-flow-completeness.sh` before production-document edits. +- [ ] Record the exact failing assertion and confirm the failure is caused by the missing completeness capability. + +Expected RED shape: + +```text +missing text in references/onboarding-knowledge-base.md: Core Flow Inventory +``` + +## Task 2: Update controller and runtime invariants + +**Files:** + +- Modify: `SKILL.md` +- Modify: `references/design.md` +- Modify: `references/runtime.md` + +- [ ] Replace the controller's `wireframe architecture flow diagrams as the preferred flow expression` summary with a concise core-flow completeness summary. +- [ ] Add the design invariant: + +```text +Core Flow Inventory +→ accepted Flow selection +→ Flow Slice Coverage +→ Diagram + Narrative + Evidence trace +→ Completeness Hard Gate +→ Quality Score +``` + +- [ ] State that a missing critical slice cannot be averaged away by topic quality scores. +- [ ] Preserve the canonical onboarding order: + +```text +Evidence Graph -> accept Onboarding Spec -> write Onboarding Tasks -> accept Full Execution Gate -> formal docs +``` + +- [ ] Preserve exactly two onboarding Human Gates. +- [ ] Run the new focused test and confirm the first controller/runtime assertions turn GREEN while template assertions remain RED. + +## Task 3: Implement detailed onboarding behavior + +**Files:** + +- Modify: `references/onboarding-knowledge-base.md` +- Modify: `references/stage-guides.md` +- Modify: `references/workflow-checklists.md` +- Modify: `references/validation-scenarios.md` + +- [ ] Define `Core Flow Inventory` fields: Flow ID, business outcome, criticality, entry, success/failure terminals, variants, owners, state/data owners, async/jobs/callbacks, side effects, recovery responsibility, evidence chain, selection, selection reason, confidence, unknowns. +- [ ] Define discovery triangulation across entries, state writes, transactions, messages, callbacks/jobs, tests/contracts/logs/config, and human business outcomes verified against code. +- [ ] Define the Spec Acceptance checks for every `critical` / `important` flow to be planned or explicitly deferred with evidence and impact. +- [ ] Define `Flow Slice Coverage` and key-slice classification for `critical` / `important` flows; keep supporting flows lightweight unless they own core state, side effects, or recovery. +- [ ] Define the diagram contract: + +```text +Core Flow Overview / Boundary = scope, branches, owners, terminals +Timeline / Sequence = primary per-flow narrative +ASCII State Machine = states, invalid transitions, recovery +Conditional diagrams = lineage, transaction/concurrency, async topology, + decision tree, ERD, runtime, troubleshooting +``` + +- [ ] Require stable Diagram IDs and Slice ID mapping in every diagram explanation. +- [ ] Scope the fixed diagram group to core module/flow behavior; make overview/domain/jobs/infra/deploy/change-guide diagrams relevance-based so stateless topics do not invent state diagrams. +- [ ] Align stage guide and checklist wording; remove any implication that the current batch is a Human Gate. +- [ ] Add adversarial validation scenarios for missing callback/reconciliation, diagram-narrative detachment, averaged-away critical failure, simple CRUD exemptions, wallet/billing risk, gateway/runtime, and deferred critical flows. +- [ ] Run the focused test and confirm reference assertions pass while remaining template/fixture assertions stay RED. + +## Task 4: Align all onboarding templates + +**Files:** + +- Modify: `templates/onboarding-db/README.md` +- Modify: `templates/onboarding-db/evidence-graph.md` +- Modify: `templates/onboarding-db/onboarding-spec.md` +- Modify: `templates/onboarding-db/onboarding-tasks.md` +- Modify: `templates/onboarding-db/flow.md` +- Modify: `templates/onboarding-db/coverage-matrix.md` +- Modify: `templates/onboarding-db/batch-review.md` + +- [ ] Add the complete Core Flow Inventory and Evidence Readiness checks. +- [ ] Add Core Flow Selection and Flow Slice Plan to Onboarding Spec. +- [ ] Extend Diagram Plan with complexity signals and Covered Slice IDs. +- [ ] Make Onboarding Tasks list Flow IDs, Slice IDs, required and conditional diagrams, evidence, hard gate, and score target. +- [ ] Add flow identity, terminal outcomes, slice trace table, Diagram IDs, and diagram explanations to `flow.md`. +- [ ] Add `Completeness Hard Gate` before quality score in coverage and batch review. +- [ ] Use the same quality dimensions and 1-5 anchors in coverage and batch review. +- [ ] Keep module/flow single-file defaults and exactly two Human Gates. +- [ ] Run the focused test and confirm all source/template assertions pass; fixture/validator assertions remain RED. + +## Task 5: Implement artifact validation and fixtures + +**Files:** + +- Create: `scripts/check-onboarding-core-flow-coverage.rb` +- Create: `examples/ai-meeting-minutes-backend/onboarding-db/08-review/evidence-graph.md` +- Create: `examples/ai-meeting-minutes-backend/onboarding-db/onboarding-spec.md` +- Create: `examples/ai-meeting-minutes-backend/onboarding-db/onboarding-tasks.md` +- Create: `examples/ai-meeting-minutes-backend/onboarding-db/03-flows/order-payment.md` +- Create: `examples/ai-meeting-minutes-backend/onboarding-db/coverage-matrix.md` +- Create: `examples/ai-meeting-minutes-backend/onboarding-db/batch-review.md` +- Create: `tests/fixtures/onboarding-core-flow/invalid-missing-recovery/evidence-graph.md` +- Create: `tests/fixtures/onboarding-core-flow/invalid-missing-recovery/onboarding-spec.md` +- Create: `tests/fixtures/onboarding-core-flow/invalid-missing-recovery/onboarding-tasks.md` +- Create: `tests/fixtures/onboarding-core-flow/invalid-missing-recovery/flow.md` +- Create: `tests/fixtures/onboarding-core-flow/invalid-missing-recovery/coverage-matrix.md` +- Create: `tests/fixtures/onboarding-core-flow/invalid-missing-recovery/batch-review.md` + +- [ ] Implement a Ruby validator with this CLI: + +```bash +ruby scripts/check-onboarding-core-flow-coverage.rb examples/ai-meeting-minutes-backend/onboarding-db +``` + +- [ ] Parse the fixture's declared critical/important Flow IDs and required Slice IDs. +- [ ] Verify every planned flow is connected across Evidence Graph, Spec, Tasks, Flow doc, Coverage, and Review. +- [ ] Verify every required Slice ID has evidence, Diagram ID, and document-section tokens. +- [ ] Verify Completeness Hard Gate is `PASS` only when required slices are covered. +- [ ] Emit deterministic errors, including: + +```text +missing required slice: CF-ORDER-PAYMENT/S07 +``` + +- [ ] Run the focused test; valid fixture must pass and invalid fixture must fail for the expected reason. + +## Task 6: Human-facing documentation and changelog + +**Files:** + +- Modify: `Usage.md` +- Modify: `CHANGELOG.md` +- Review: `README.md` +- Review: `templates/root-AGENTS.md` + +- [ ] Explain to humans that onboarding first finds complete core flows, then writes diagram-backed flow docs. +- [ ] Explain the default diagram group and complexity-triggered diagrams without exposing unnecessary internal schema detail. +- [ ] Record the v1.3.0 behavior under the existing 1.3.0 changelog section. +- [ ] Update README only if its onboarding summary would otherwise contradict the new behavior. +- [ ] Do not change root Stage Map signals or add a new stage; update the managed block only if its existing onboarding description becomes contradictory. + +## Task 7: REFACTOR and feature-scoped verification + +**Files:** + +- Create: `docs/reports/onboarding-core-flow-completeness-feature-validation-2026-07-11.md` +- Review: all files changed by Tasks 1-6 + +- [ ] Run the focused test: + +```bash +bash tests/validate-onboarding-core-flow-completeness.sh +``` + +Expected: `PASS: onboarding core-flow completeness contract is complete`. + +- [ ] Run the existing onboarding regression: + +```bash +bash tests/validate-evidence-graph-ddd-onboarding.sh +``` + +Expected: `evidence-graph DDD onboarding validation passed`. + +- [ ] Run direct onboarding regressions only: + +```bash +bash tests/validate-onboarding-core-flow-completeness.sh +bash tests/validate-evidence-graph-ddd-onboarding.sh +bash tests/validate-project-entry-onboarding-reset.sh +bash tests/validate-v1.2.4-state-lifecycle-repairs.sh +bash tests/validate-v1.2.4-postfix-pressure-repairs.sh +bash tests/validate-v1.2.3-routing-fixes.sh +bash tests/validate-v1.2.3-medium-consistency.sh +``` + +Expected: all seven feature-boundary tests exit zero. + +- [ ] Run required structural checks: + +```bash +ruby -e 'require "yaml"; YAML.load_file("SKILL.md")' +ruby -rjson -e 'JSON.parse(File.read("plugin.json"))' +find . -name '*.sh' -type f -print0 | xargs -0 -n1 bash -n +git diff --check +``` + +- [ ] Run a Markdown fence-balance check over changed Markdown files. +- [ ] Execute the five-domain semantic audit required by `docs/maintenance/feature-validation-method.md`, separating RED baseline from GREEN results. +- [ ] Record repository-wide/full-skill validation as not authorized and not part of the feature score; run it only after explicit human permission. +- [ ] Review the final diff for unrelated edits and preserve the pre-existing 1.3.0 version-alignment changes. +- [ ] Stop before commit and request the Agent Loop Submit / Integrate Human Gate. diff --git a/docs/proposal/v1.3.x/onboarding-core-flow-completeness.md b/docs/proposal/v1.3.x/onboarding-core-flow-completeness.md new file mode 100644 index 0000000..fea697c --- /dev/null +++ b/docs/proposal/v1.3.x/onboarding-core-flow-completeness.md @@ -0,0 +1,495 @@ +# Proposal: Onboarding Core Flow Completeness + +状态:v1.3.0 Release Human Gate 已批准;发布目标 stable-v1.3.0 + +目标版本:v1.3.0 + +创建时间:2026-07-11 + +## 摘要 + +当前 Evidence-Graph + DDD Onboarding 已经对单篇 Module / Flow 文档提出较强的内容要求,包括架构/边界图、状态图、Timeline / 时序图、数据对象、状态变化、失败路径、排障和代码证据。 + +现有缺口位于更上游:Agent 可以把“已经选中的流程”写得很详细,却没有足够强的机制证明项目核心流程已经找全、关键阶段和异常分支已经拆全。若 Evidence Graph 只发现“创建订单”,但漏掉支付回调、重复回调、超时关闭、退款、补偿和对账,后续文档即使结构完整,也会在错误的范围内显得详细。 + +本 proposal 建议在现有 onboarding 顺序和两个 Human Gate 不变的前提下,引入三项能力: + +1. `Core Flow Inventory`:在 Evidence Graph 阶段建立带业务关键度、终态、变体、异步链路和恢复责任的核心流程清单。 +2. `Flow Slice Coverage`:把每个核心流程拆成可追踪的主路径、分支、失败和恢复 slice,并映射到代码证据、图和文档章节。 +3. 产物级语义验证:使用完整示例、缺陷样本和压力场景验证“有没有漏”,不再只验证模板中是否存在标题和关键词。 + +预期结果: + +```text +发现完整的核心流程 +→ 选择并解释文档范围 +→ 将流程拆成可追踪 slice +→ 用互补图组表达结构、时间、状态和恢复 +→ 按 slice 验证覆盖 +→ 才允许标记 newcomer-ready +``` + +## 背景与问题定义 + +### 当前已经做得好的部分 + +现有 Flow 文档要求已经覆盖: + +- 业务目标、触发入口、参与模块、前置状态和成功结果; +- 架构/边界图; +- ASCII 状态机/决策图; +- Timeline / 时序图; +- 阶段、数据流转和状态变化; +- 示例、失败路径、排障路径、变更指南和代码证据; +- consistency、idempotency、transaction、compensation、reconciliation 等高风险内容。 + +因此,本轮不需要推翻 Flow 模板,也不需要以增加文件数量来解决问题。 + +### 当前结构性缺口 + +#### 1. 核心流程发现没有完整性契约 + +当前 `Flow Candidates` 可以记录 trigger、participants、state changes、external dependencies、evidence 和 risk,但不能稳定回答: + +- 这是核心流程、支持流程,还是局部调用链; +- 业务成功终态和失败终态分别是什么; +- 是否存在同步、异步、回调、job 或人工恢复变体; +- 哪些外部副作用必须发生,哪些允许最终一致; +- 为什么该流程进入 Flow Plan,或为什么可以延期; +- 入口、状态写点、消息消费者和终态是否形成闭合证据链。 + +#### 2. 上游漏项会在后续阶段被继承 + +Onboarding Spec 和 Onboarding Tasks 只会规划已经进入候选清单的流程。如果 Evidence Graph 漏掉回调、补偿或对账,后续的 Diagram Plan、Flow 文档和 Coverage Matrix 都无法自动恢复该细节。 + +#### 3. Topic 级评分会掩盖 slice 级缺失 + +当前评分以 topic 为单位。某篇 Flow 文档可以在图、可读性和代码证据上得到较高平均分,同时漏掉一个业务关键失败分支。关键分支缺失不应被其他维度的高分抵消。 + +#### 4. 机械测试不能证明生成产物完整 + +现有 onboarding 测试主要检查 source、template 和 scenario 中是否存在指定文字或章节。它能够防止规则被误删,但不能证明实际生成的 onboarding: + +- 找全了核心流程; +- 覆盖了所有关键终态; +- 图和正文对应真实代码路径; +- 没有用泛化描述填满必填章节。 + +#### 5. Review 维度存在漂移 + +`coverage-matrix.md` 分开评分 architecture、state、timeline 等维度,而 `batch-review.md` 使用较粗的 `Wireframe` 列。两套评分口径不一致,会让 review 丢失图解和流程 slice 的具体缺口。 + +## 目标 + +本 proposal 的目标是: + +1. 让 Agent 在写正式文档前证明核心流程发现足够完整。 +2. 让每个核心流程的主路径、分支、失败和恢复都能追踪到代码证据。 +3. 让图解承担明确职责,避免一张泛化 flowchart 同时冒充架构、时序和状态说明。 +4. 让关键 slice 缺失成为 hard blocker,不能被平均分掩盖。 +5. 让验证覆盖真实产物和故意不完整的反例。 +6. 保留现有 Onboarding Spec Acceptance Gate 和 Full Execution Gate,不增加第三个人类 Gate。 +7. 保持 Module / Flow 默认单长文件,不通过拆文件制造详细感。 +8. 只对 `critical` / `important` 核心流程强制 Flow/Slice/Diagram 追踪;supporting flow 和非流程文档保持轻量。 + +## 非目标 + +本轮不做以下事情: + +- 不改变 Project Entry Scan 与 onboarding-db 的边界; +- 不改变 `Evidence Graph -> Onboarding Spec -> Onboarding Tasks -> formal docs` 的阶段顺序; +- 不增加每个 batch 的人类确认; +- 不要求所有流程使用所有图类型; +- 不要求 glossary、静态配置清单、纯索引或无状态主题编造状态图; +- 不要求固定数量的核心流程或固定数量的图; +- 不从目录名自动推断业务流程; +- 不要求 Agent 生成网站或图片文件;Markdown 中的 Mermaid 和 ASCII 仍是 source of truth; +- 不在本轮建设通用 LLM 自动评审平台;该能力可与未来 self-test harness 对接; +- 不把历史 proposal 或示例升级为运行时 source of truth。 + +## 方案比较 + +### 方案 A:只增强 Flow 模板 + +做法:在 `flow.md` 增加更多章节、表格和自检项。 + +优点:改动小,容易实施。 + +限制:无法解决 Evidence Graph 上游漏项;Agent 仍可能把一个不完整范围写得很详细。 + +### 方案 B:核心流程契约 + Slice Traceability + +做法:增强 Evidence Graph、Onboarding Spec、Flow 文档、Coverage 和 Review 的贯通关系;以稳定 Flow ID 和 Slice ID 建立追踪链。 + +优点:直接处理核心流程发现不完整和关键分支丢失;与现有 Evidence-Graph + DDD 模型兼容;不需要新增 Human Gate。 + +代价:需要同步多份 reference、template、scenario 和 test,并执行全量验证。 + +### 方案 C:自动生成静态调用图 + +做法:扫描 AST、调用关系、数据库访问和消息消费者,自动生成候选流程图。 + +优点:能提高代码入口和调用链发现效率。 + +限制:静态调用图通常不知道业务目标、终态、补偿责任和人工恢复语义;多语言项目还需要不同解析器。它适合作为 Evidence Graph 的辅助证据,不适合作为第一版完整性判定源。 + +### 推荐 + +采用方案 B。方案 A 可作为方案 B 中的模板改动部分;方案 C 延后为可选 Evidence Adapter,不阻塞 v1.3.0。 + +## 设计 + +### 1. Core Flow Inventory + +Evidence Graph 在普通 `Flow Candidates` 之上增加核心流程分类和选择信息。推荐表结构: + +| 字段 | 含义 | +|---|---| +| Flow ID | 稳定标识,例如 `CF-ORDER-PAYMENT` | +| Flow | 人类可读流程名 | +| Business Outcome | 流程为用户或业务产生的结果 | +| Criticality | `critical`、`important`、`supporting` | +| Trigger / Entry | 外部触发与具体代码入口 | +| Success Terminal | 成功终态和可观察结果 | +| Failure Terminals | 失败、取消、未知、人工处理等终态 | +| Variants / Branches | 同步、异步、回调、降级和业务分支 | +| Participants / Owners | 模块、服务、外部系统和真相所有者 | +| State / Data Owners | 核心状态对象和数据 owner | +| Async / Jobs / Callbacks | Topic、consumer、job、callback 关系 | +| External Side Effects | 扣款、通知、发货、配额、第三方调用等 | +| Recovery Responsibility | retry、compensation、reconciliation、manual action | +| Evidence Chain | 入口、关键写点、消息处理和终态证据 | +| Selection | `planned`、`deferred`、`not-applicable` | +| Selection Reason | 纳入或延期原因 | +| Confidence / Unknowns | 置信度和仍需确认的事实 | + +#### 发现方法 + +Agent 不应只从目录和入口函数生成 Flow Inventory。至少从以下证据方向交叉发现: + +1. API、CLI、UI action、webhook、consumer、cron/job 等入口; +2. 核心实体、订单、任务或会话的状态写入点; +3. DB transaction、Redis/Lua、outbox、MQ publish/consume 等副作用; +4. callback、retry、DLQ、compensation、reconciliation 和人工恢复入口; +5. tests、fixtures、API contracts、logs、config 和 runbook 中的真实行为; +6. 项目记忆或人类说明中的业务结果,并用代码现实复核。 + +#### Core Flow Selection Gate + +不新增独立 Human Gate。现有 Spec Acceptance Gate 增加以下检查: + +- 每个 `critical` / `important` Flow Candidate 都已进入 Flow Plan,或有具体 deferred reason; +- 每个计划内核心流程都列出成功终态、失败终态和已知变体; +- 入口、关键状态写点、异步处理和终态已形成 Evidence Chain; +- 无证据的推断保留在 Unknowns,不伪装成已确认流程。 + +人类确认 Onboarding Spec 时,同时确认这份核心流程选择结果。Full Execution Gate 仍只负责确认具体输出、证据要求和执行范围。 + +### 2. Flow Slice Coverage + +每个计划内 `critical` / `important` 核心流程在 Onboarding Spec 或 Flow 文档中建立 slice 清单。Slice 是一个可验证的主路径阶段、业务分支、失败点或恢复动作,不是文档章节数量。`supporting` flow 只有在改变核心状态、产生关键副作用或承担恢复责任时才升级为完整 slice 追踪;否则可保留轻量 Flow Candidate 和证据说明。 + +推荐结构: + +| 字段 | 含义 | +|---|---| +| Flow ID / Slice ID | 例如 `CF-ORDER-PAYMENT/S07` | +| Path Kind | `main`、`branch`、`failure`、`recovery` | +| Trigger / Precondition | 进入该 slice 的条件 | +| Owner | 执行和状态责任模块 | +| Input / Output | 输入和输出对象 | +| Action | 真实行为 | +| State Read / Written | 读取和写入的状态 | +| Transition | before、trigger、after | +| Sync / Async / External | 调用性质和外部副作用 | +| Failure Result | 本 slice 失败后的可观察结果 | +| Recovery | retry、rollback、compensation、reconciliation 或 manual action | +| Evidence | 文件、符号/配置键和调用/数据方向 | +| Diagram IDs | 该 slice 出现在哪些图中 | +| Document Section | 该 slice 在正文中的讲解位置 | +| Coverage Status | `covered`、`inferred`、`blocked` | + +追踪链必须闭合: + +```text +Core Flow Inventory + → Flow Plan + → Onboarding Task + → Flow Slice Coverage + → Diagram IDs + narrative sections + → Code Evidence + → Coverage Matrix + Batch Review +``` + +关键 slice 定义: + +- 改变核心业务状态; +- 产生不可忽略的外部副作用; +- 决定成功、失败、取消、未知或人工处理终态; +- 承担 retry、compensation、reconciliation 或安全责任; +- 涉及 transaction、lock、idempotency、quota、billing、credential 或权限边界。 + +任何关键 slice 为 `blocked` 或缺少 Evidence / Diagram / Document Section 映射时,对应 Flow 不能标记 `newcomer-ready`。 + +### 3. 图解表达规则 + +#### Critical / Important 核心 Flow 的默认图组 + +| 图 | 默认性 | 主要回答的问题 | +|---|---|---| +| Core Flow Overview / Branch Map | 必填 | 核心流程有哪些主干、分支、异步出口和终态 | +| Timeline / Sequence Diagram | 必填,正文主图 | 谁在什么时间调用谁、传什么数据、读写什么状态 | +| ASCII State Machine / Decision Diagram | 必填 | 状态如何变化、哪些转换非法、失败后如何恢复 | +| Architecture / Boundary Diagram | 必填,可与全景图合并但职责必须清楚 | 流程跨越哪些模块、服务、数据和外部系统边界 | + +架构/边界图可以与 Core Flow Overview 合并,但合并图必须同时显示: + +- 模块/系统边界; +- 数据或状态 owner; +- 主路径、关键分支和终态; +- DB、cache、MQ、gateway 和外部依赖的位置。 + +Timeline / Sequence Diagram 是单个核心流程的主要讲解图。普通 `A -> B -> C` flowchart 只可作为导航图,不能独立满足流程细节要求。 + +#### 按复杂度增加的图 + +| 复杂度信号 | 增加的图 | 需要表达的细节 | +|---|---|---| +| callback、retry、补偿、对账、最终一致性 | Failure Recovery Timeline | 时间窗口、观察点、重试和恢复终点 | +| DTO、Command、Entity、DB Record、Event 多次转换 | Data Lineage / Object Transformation | 对象 owner、字段变化、ID 和状态来源 | +| transaction、lock、Lua、outbox、并发、幂等 | Transaction / Concurrency Boundary | 事务内外、锁生命周期、幂等检查、部分成功 | +| 多 Topic、consumer、retry queue、DLQ | Async Message Topology | producer、consumer、delivery、retry、DLQ 和 owner | +| 路由、权限、provider 或策略选择复杂 | Decision Tree | 条件、优先级、fallback 和拒绝结果 | +| 实体关系影响理解 | ERD / Model Relationship | 关系、聚合边界和真相来源 | +| gateway、sidecar、环境拓扑影响行为 | Runtime / Deployment Topology | route、header、timeout、retry、upstream、logs | +| 排障入口分散 | Observability / Troubleshooting Map | request ID、logs、metrics、traces、DB/MQ 检查顺序 | + +Diagram Plan 不再只列图类型,还要记录触发该图的复杂度信号和覆盖的 Slice IDs。 + +非 Flow 内容文档按解释需要选择图,不继承核心 Flow 的固定图组: + +- system context / architecture / domain docs 使用能说明边界、关系和数据所有权的图; +- jobs / async / runtime docs 在存在时间、状态或恢复语义时使用 sequence/state/timeline; +- glossary、静态配置清单、纯索引和没有状态语义的说明不强制状态图; +- 任何豁免都不允许掩盖实际存在的状态、分支或恢复行为。 + +#### 图文绑定 + +每张图必须具有稳定 Diagram ID,并附带: + +- 图要看什么; +- 图支持什么结论; +- 覆盖哪些 Slice IDs; +- 出现哪些数据对象、状态字段、消息和配置; +- 对应哪些代码证据; +- 哪些内容属于 inferred,以及置信度和待验证点。 + +禁止用同一张泛化图复制到多个章节并声称完成不同职责。 + +### 4. Coverage 与 Review + +#### 两级判定 + +`newcomer-ready` 使用两级判定: + +1. **Completeness Hard Gate**:核心流程和关键 slice 没有未解释的缺失。 +2. **Quality Score**:通过 hard gate 后,再评价图解、证据、排障和可读性质量。 + +Hard Gate 失败时,无论平均分多高,都不能标记 `newcomer-ready`。 + +#### 统一评分维度 + +`coverage-matrix.md` 与 `batch-review.md` 使用同一组维度: + +- Core flow discovery completeness; +- Slice and branch coverage; +- Architecture / boundary clarity; +- Timeline / sequence clarity; +- State machine clarity; +- Data object / lineage completeness; +- Failure / recovery completeness; +- Evidence granularity and traceability; +- Troubleshooting / observability; +- Change guidance; +- Newcomer readability。 + +评分继续使用 1-5,但增加锚点: + +| 分数 | 锚点 | +|---:|---| +| 5 | 完整、证据闭合,新人可独立解释和排障 | +| 4 | 核心路径完整,只有不影响接手的低风险缺口 | +| 3 | 可以理解主路径,但分支、恢复或证据存在明显缺口 | +| 2 | 结构存在,内容主要是概览或泛化描述 | +| 1 | 无法依靠该文档理解或操作 | + +### 5. 任务与 Human Gate + +Onboarding Tasks 对每个核心 Flow task 必须列出: + +- Flow ID 和计划路径; +- 必须覆盖的 Slice IDs; +- 默认图组; +- 条件触发图; +- evidence required; +- Completeness Hard Gate; +- quality score target。 + +Human Gate 保持两个: + +```text +Gate 1: 接受 Onboarding Spec + - 包含 Core Flow Inventory 选择结果 + - 只授权创建 Onboarding Tasks + +Gate 2: 接受 Onboarding Tasks Full Execution Gate + - 包含具体 Flow/Slice/Diagram/Evidence 范围 + - 授权连续创建正式 onboarding 文档 +``` + +本 proposal 不引入 Core Flow Gate、Diagram Gate 或 Batch Gate 等额外人工暂停点。 + +### 6. 验证设计 + +#### RED 基线 + +实现前保留以下当前漏洞证据: + +1. 现有 onboarding source validation 可以在没有完整 onboarding 示例产物时通过; +2. Flow Candidate 没有终态、变体、恢复责任和选择理由字段; +3. topic 评分没有关键 slice hard blocker; +4. batch review 与 coverage matrix 的图解评分维度不一致。 + +#### 新增压力场景 + +至少覆盖: + +1. **只写成功路径**:缺少 callback failure 和 reconciliation,必须拒绝 newcomer-ready。 +2. **有三张图但图文脱节**:没有 Slice ID、数据对象或代码证据映射,必须判定 incomplete。 +3. **漏异步消费者**:API 到 DB 完整,但 event consumer 改变最终状态,必须回到 Evidence Graph 补 flow/slice。 +4. **关键失败被平均分掩盖**:其他维度为 5,关键 compensation slice 缺失,仍必须 hard fail。 +5. **简单 CRUD**:没有异步、事务或复杂状态时,不强制额外恢复、并发或消息拓扑图。 +6. **wallet / billing / quota**:必须触发 transaction/concurrency 和 recovery 表达。 +7. **gateway runtime**:必须触发 boundary/runtime 图和 route/header/timeout/log evidence。 +8. **延期流程**:critical / important flow 只有在记录 evidence、impact 和 deferred reason 后才可延期。 +9. **不新增 Gate**:Spec Acceptance 与 Full Execution Gate 仍是唯二 onboarding 人类确认点。 + +#### 产物级验证 + +在 `examples/` 增加一个小而完整的复杂项目 onboarding reference,至少包含: + +- API 入口; +- 多模块调用; +- DB 状态变化; +- MQ publish / consume; +- callback; +- retry / reconciliation; +- 一个 transaction / idempotency 风险; + +在 `tests/fixtures/` 单独保存一条故意遗漏关键 slice 的最小 invalid fixture,避免复制两套完整 onboarding 树。 + +可执行 validator 至少检查: + +- Flow ID 从 Inventory 贯通到 Plan、Task、Flow doc、Coverage 和 Review; +- 每个 planned critical/important flow 有终态和 Slice IDs; +- 每个关键 Slice ID 有 evidence、diagram 和 document section 映射; +- required / conditionally-triggered diagram 均存在; +- invalid fixture 因目标缺陷失败; +- placeholder、空 required row 和泛化 evidence 仍被拒绝。 + +validator 不宣称验证业务事实绝对正确;事实正确性继续由证据审计和压力场景负责。 + +## 实施影响面 + +### 运行与设计来源 + +| 文件 | 计划动作 | +|---|---| +| `SKILL.md` | 将 onboarding 摘要从 wireframe preferred 调整为 core-flow inventory、sequence-main、state/branch coverage 的简洁入口 | +| `references/design.md` | 补充核心流程完整性与 Evidence -> Slice -> Evidence 的设计不变量 | +| `references/runtime.md` | 保持阶段顺序和两个 Gate;明确 accepted spec 必须覆盖核心流程选择结果 | +| `references/onboarding-knowledge-base.md` | 承载完整 Core Flow Inventory、Slice Coverage、图选择和 hard gate 规则 | + +### 阶段与模板 + +| 文件 | 计划动作 | +|---|---| +| `references/stage-guides.md` | 对齐 onboarding 执行和 review 步骤 | +| `references/workflow-checklists.md` | 增加发现、slice、图文绑定和 hard gate 检查 | +| `templates/onboarding-db/evidence-graph.md` | 增加 Core Flow Inventory 和完整性检查 | +| `templates/onboarding-db/onboarding-spec.md` | 增加核心流程选择、Flow Slice Plan 和图触发信号 | +| `templates/onboarding-db/onboarding-tasks.md` | 任务记录 Flow/Slice/Diagram/Evidence 范围 | +| `templates/onboarding-db/flow.md` | 增加 Flow/Slice IDs、主图职责、条件图和追踪表 | +| `templates/onboarding-db/coverage-matrix.md` | 增加 Completeness Hard Gate 和统一评分锚点 | +| `templates/onboarding-db/batch-review.md` | 与 Coverage Matrix 使用同一评分维度 | +| `templates/onboarding-db/README.md` | 更新新人阅读顺序和图解职责说明 | + +### 验证与示例 + +| 文件 | 计划动作 | +|---|---| +| `references/validation-scenarios.md` | 加入缺流程、缺 slice、图文脱节和不新增 Gate 的压力场景 | +| `tests/validate-evidence-graph-ddd-onboarding.sh` | 保留 source/template contract regression | +| `tests/validate-onboarding-core-flow-completeness.sh` | 新增跨 artifact ID 和 hard-gate 校验 | +| `examples/ai-meeting-minutes-backend/onboarding-db/` | 增加一套可阅读、可验证的 valid 核心流程 onboarding reference | +| `tests/fixtures/onboarding-core-flow/` | 只保存针对性的 invalid fixture | +| `Usage.md` | 增加面向人类的核心流程图组和完整性说明 | +| `CHANGELOG.md` | 实施完成后记录 v1.3.0 行为变化 | + +`templates/root-AGENTS.md` 的 Stage Map 信号和下一阶段不因本设计改变。实施时仍需运行 root guidance validation,只有 managed block 的 onboarding 描述需要表达新不变量时才更新 block 内容和对应测试。 + +## 兼容与迁移 + +已有 Evidence-Graph + DDD onboarding-db 不自动判定失效: + +- Focused Update 可以为相关核心流程补 Inventory、Slice IDs 和图文映射; +- 全项目缺少核心流程选择证据时,标记 coverage `needs-review`,通过现有 Onboarding Spec migration/update gate 处理; +- 不因为格式升级删除旧文档;旧内容继续作为 evidence; +- 不要求一次性重写所有低风险 supporting flow;优先迁移 `critical` 和 `important` 流程。 + +## 验收标准 + +实施完成需同时满足: + +- Core Flow Inventory 能区分 critical、important 和 supporting flow; +- critical / important flow 必须 planned 或有证据级 deferred reason; +- supporting flow 只有触及核心状态、副作用或恢复责任时才要求完整 slice 追踪; +- 每个计划内核心流程有成功终态、失败终态、变体和 Evidence Chain; +- 每个关键 slice 能追踪到 evidence、diagram 和 document section; +- 每个核心 Flow 默认使用全景/边界、Timeline / Sequence、ASCII State 三类互补表达; +- 非 Flow 文档按内容相关性选图,不为无状态主题编造状态图; +- 条件复杂度可以可靠触发 recovery、lineage、transaction/concurrency、async topology、decision tree 等附加图; +- Completeness Hard Gate 失败时不能通过平均分成为 newcomer-ready; +- coverage matrix 与 batch review 使用一致的维度和评分锚点; +- valid fixture 通过,invalid fixture 因目标缺陷失败; +- Onboarding Spec Acceptance 和 Full Execution Gate 仍是唯二 onboarding Human Gate; +- 不改变 canonical stage order、Project Entry 边界、Module/Flow 单文件默认策略; +- 运行 onboarding 功能专项测试、直接相关回归、YAML/JSON/Markdown/Shell/diff 检查; +- 按 `docs/maintenance/feature-validation-method.md` 完成单功能逻辑与压力评分报告;全仓库/全技能验证只在人类明确授权时另行执行,不计入默认功能得分。 + +## 实施顺序建议 + +```text +1. 固化 RED 漏洞与 invalid fixture +2. 更新 design/runtime/controller summary +3. 更新 onboarding runtime reference +4. 更新 Evidence/Spec/Tasks/Flow/Coverage/Review templates +5. 更新 stage guide/checklist/validation scenarios/Usage/CHANGELOG +6. 实现 artifact validator +7. 运行 focused GREEN +8. 运行 onboarding 功能边界测试和单功能语义验证 +9. 形成评分报告 +10. 人类确认后再进入 commit / push +``` + +## 人类评审点 + +本 proposal 需要人类确认以下设计结论后才能进入实施计划: + +1. 采用方案 B:Core Flow Inventory + Flow Slice Traceability; +2. 不新增 onboarding Human Gate; +3. Timeline / Sequence Diagram 作为单流程正文主图; +4. 全景/边界图负责防漏,ASCII State Diagram 负责状态和恢复; +5. 额外图由复杂度信号触发,不固定图数量; +6. newcomer-ready 增加 Completeness Hard Gate; +7. v1.3.0 包含 valid/invalid fixture 和产物级 validator。 diff --git a/docs/reports/agent-loop-v1.3.0-adr-technical-landing-red-baseline-2026-07-13.md b/docs/reports/agent-loop-v1.3.0-adr-technical-landing-red-baseline-2026-07-13.md new file mode 100644 index 0000000..40d6e67 --- /dev/null +++ b/docs/reports/agent-loop-v1.3.0-adr-technical-landing-red-baseline-2026-07-13.md @@ -0,0 +1,54 @@ +# Agent Loop v1.3.0 ADR Technical Landing RED Baseline + +日期:2026-07-13 + +分支:`alpha/v1.3.0` + +审计对象:当前工作区;实施前只有人类提供的 approved proposal,未修改 runtime、design、ADR template 或 validator。 + +## 修复前仓库基线 + +在新增 focused contract 之前运行全部已有 `tests/*.sh`: + +```text +BASELINE passed=31 failed=0 +``` + +该基线证明现有仓库测试健康,但不证明 ADR 已实现 Requirement Model Technical Landing Trace。 + +## Focused Contract RED + +新增: + +```text +tests/validate-adr-requirement-model-technical-landing-trace.sh +``` + +先验证 Shell 语法,再执行: + +```text +$ bash -n tests/validate-adr-requirement-model-technical-landing-trace.sh +$ bash tests/validate-adr-requirement-model-technical-landing-trace.sh +FAIL: SKILL.md missing required text: Effective Requirement Snapshot +``` + +判定:`RED`,且失败原因正确。当前发布入口只说明 ADR 消费 accepted product semantics,尚未要求固定 effective source snapshot、模型行到技术落点的逐项 coverage、compatibility review 或相应 Human Review Summary。 + +## RED 覆盖的实际漏洞 + +- ADR 可以只链接 requirement set,但没有固定 README 当前 effective source/status。 +- `Domain Concepts` 只保护 Concept 引用,不证明 scope 内 `REL-*` / `CMD-*` / `EVT-*` / `FLOW-*` / `STATE-*` / `PM-*` 已落地。 +- `Applicable Decisions` 和既有 Design Slice Coverage 无法单独证明 Requirement Model 没有遗漏。 +- effective source 变化后,accepted ADR 可能被新 Feature 继续使用,而没有进入 `review-required`。 +- 既有 Human Review Summary 没有展示 coverage 计数、产品语义保留、operational trigger 和未分配 Design Slice。 +- Migration / Compatibility / Rollout / Rollback 容易被模板默认展开,尚无按需触发的统一判断。 + +## GREEN 必须证明 + +- effective README pointer、source status 和 ADR snapshot 一致; +- `candidate` / `reopened` / `review-required` 会阻止 ADR acceptance 和依赖工作; +- scope 内每个 accepted Requirement Model ID 都有合法 disposition; +- `landed` 行具有非空 Technical Landing、Preserved Invariant、Design Slice 和 Verification; +- accepted ADR 不得重新定义产品语义,不兼容时创建 superseding ADR; +- template 和 validator 只依赖通用字段、ID 与结构,不固化 fixture 业务名词、动作或技术落点; +- 不新增 canonical stage、默认 mapping artifact、ADR lifecycle status 或 executable schema。 diff --git a/docs/reports/agent-loop-v1.3.0-adr-technical-landing-review-red-2026-07-13.md b/docs/reports/agent-loop-v1.3.0-adr-technical-landing-review-red-2026-07-13.md new file mode 100644 index 0000000..cfc6289 --- /dev/null +++ b/docs/reports/agent-loop-v1.3.0-adr-technical-landing-review-red-2026-07-13.md @@ -0,0 +1,48 @@ +# Agent Loop v1.3.0 ADR Technical Landing 审查 RED 报告 + +日期:2026-07-13 + +分支:`alpha/v1.3.0` + +审计对象:另一个 Agent 完成但尚未提交的 ADR Requirement Model Technical Landing Trace 首轮实现。 + +## 结论 + +首轮实现方向正确,但 `98/100` 与 `0 High / Medium` 结论不成立。对 validator 做同源临时变异后,确认 10 个可绕过场景与 2 个合法输入误拒绝。修复前必须保持 RED 证据,不能把首轮 full-validation 报告当作当前验收结论。 + +## 可复现 RED + +| 场景 | 首轮结果 | 正确结果 | 风险 | +|---|---|---|---| +| `Status: proposed` 在 Human Review 前做结构预检 | 拒绝 | 接受 preflight,但不得自动 accepted | Human Gate 顺序倒置 | +| `Status: accepted` 无 Human Review Evidence | 接受 | 拒绝 | 可伪造 acceptance | +| `not-applicable` 使用 `reason: n/a` | 接受 | 拒绝 | placeholder 冒充理由 | +| Coverage Hard Gate 被一条任意 checkbox 替换 | 接受 | 拒绝 | Hard Gate 形同虚设 | +| Accepted Requirement Model IDs 追加垃圾 token | 接受 | 拒绝 | ID parser 不完整 | +| source model 同时从 snapshot/trace 删除 | 接受 | 拒绝 | Agent 可静默缩小 scope | +| `covered-by-accepted-decision` 指向不存在 ADR | 接受 | 拒绝 | 外部 owner 未验证 | +| `feature-local` 指向不存在 Feature Spec | 接受 | 拒绝 | feature owner 未验证 | +| Design Slice 使用非法 status | 接受 | 拒绝 | coverage 生命周期不可依赖 | +| triggered operational concern 删除 detail | 接受 | 拒绝 | trigger 与落地内容脱节 | +| operational concern inventory 缺 3 项 | 接受 | 拒绝 | 部分评估冒充完整评估 | +| reasoned `concept-foundation-not-needed` 且无产品模型 | 拒绝 | 接受 trace-not-applicable branch | 诱导伪造产品模型 | + +另发现上游 Requirement Role / Permission Matrix 没有稳定 `PERM-*` ID,Exception Paths 虽有 `EX-*`,但 ADR validator 未把它纳入模型集合。这会使 ADR 无法完整引用权限与异常/恢复语义。 + +## RED 执行摘要 + +初始对抗性测试输出表明:accepted fixture 可以通过;proposed preflight 与 not-needed 合法输入被拒;placeholder、任意 gate、垃圾 ID、silent omission、假 ADR、缺失 Feature Spec、非法 slice、缺失 operational detail、残缺 concern inventory 均被错误接受。 + +## 修复要求 + +1. `proposed -> structural preflight -> Human Review -> accepted + evidence -> accepted-mode validation`; +2. source-wide Requirement Model Scope Inventory 完整覆盖 `REL/PERM/CMD/EVT/FLOW/STATE/PM/EX`; +3. snapshot in-scope IDs、scope inventory 与 trace 做集合相等验证; +4. 外部 artifact 路径与状态真实可解析,未来 owner 必须显式 `planned:`; +5. Hard Gate、Design Slice、operational concern/detail 做结构化精确验证; +6. reasoned not-needed 分支不要求虚构 Concept/Model table; +7. 新增对抗性回归并重新执行全量验证,另存 `.2` 报告。 + +## 授权边界 + +本 RED 报告不授权 commit、push、tag、PR、merge、release 或 publish。 diff --git a/docs/reports/agent-loop-v1.3.0-concept-foundation-red-baseline-2026-07-12.md b/docs/reports/agent-loop-v1.3.0-concept-foundation-red-baseline-2026-07-12.md new file mode 100644 index 0000000..7acf708 --- /dev/null +++ b/docs/reports/agent-loop-v1.3.0-concept-foundation-red-baseline-2026-07-12.md @@ -0,0 +1,153 @@ +# Agent Loop v1.3.0 Concept Foundation RED Baseline + +日期:2026-07-12 + +分支:`alpha/v1.3.0` + +审计对象:当前工作区;包含人类已提供但尚未提交的 `docs/proposal/v1.3.x/concept-foundation-requirement-modeling.md` 和既有 `CHANGELOG.md` proposal 记录。 + +实施范围:仅 Phase 1 Requirement Concept Foundation 与 Phase 2 Product Model Derivation。 + +## 修复前仓库机械基线 + +在新增 focused contract 前运行全部既有 `tests/*.sh`: + +```text +BASELINE passed=30 failed=0 +``` + +这证明现有仓库测试基线健康,但不能证明 Concept Foundation 已存在。 + +## 新增 Focused Contract RED + +新增: + +```text +tests/validate-concept-foundation-requirement-modeling.sh +``` + +先验证脚本语法,再执行 contract: + +```text +$ bash -n tests/validate-concept-foundation-requirement-modeling.sh +$ bash tests/validate-concept-foundation-requirement-modeling.sh +FAIL: SKILL.md missing required text: Concept Foundation +``` + +判定:`RED`,且失败原因正确。现有发布入口没有 Concept Foundation routing;测试尚未进入后续 runtime ordering、Human Grill Contract、requirement template、product/spec trace 和 artifact-validator 检查。 + +## Contract 覆盖的不变量 + +- Concept Foundation 只能是 Requirements Discussion / Requirement Product Grill 内部方法,不能成为 canonical stage。 +- Human Grill Contract 必须按 evidence check → candidate extraction → recommended definition/evidence/impact → exactly one blocking question 的顺序运行。 +- triggered foundation 为 `candidate` 或 `reopened` 时,Business Flow、State Model 和 Product Data Model 必须停止。 +- 简单需求可使用带理由的 `concept-foundation-not-needed`。 +- requirement document 必须先完成 Concept Foundation,再推导 Concept Relationship、Role/Permission、Commands/Events、State、Business Flow 和 Requirement Product Model。 +- Product Brief 与 Feature Spec 只消费 accepted Concept / Product Model reference,不重新定义产品语义。 +- ADR 只消费 accepted PRD / Requirement Product Model;本轮不增加产品概念到技术实现映射。 +- 不增加 Design Skill、E2E Skill、Jam Kits、YAML/JSON executable schema 或 `.agent-loop/concepts/`。 + +## Skill Pressure RED + +人类明确授权了 3 个 bounded read-only subagent 场景及同场景 GREEN 复测。授权范围仅包括读取当前已发布规则、模拟下游 Agent 行为和返回原始合理化语言;禁止修改文件、提交、推送或读取本 proposal。授权在三组 GREEN 复测返回后标记为 `consumed`。 + +Stage Helper Resolution: + +```text +Stage: Subagent Execution If Approved +Canonical Candidate: superpowers:subagent-driven-development (not exposed) +Alias Candidate: subagent-driven-development (loaded) +Resolution Status: loaded +Fallback Used: no +Method: three independent read-only pressure lanes, main-agent synthesis +Artifact Override: maintainer RED report; no target-project feature/handoffs created +``` + +### RED-1:退款完成 / Human Grill Contract + +结论:`FAIL`。 + +当前规则允许谨慎 Agent 自行做出正确选择,但没有强制以下行为: + +- 枚举 Concept Candidate; +- 推荐一个定义并同时说明证据、接受影响和拒绝影响; +- 在 requirement-level Business Flow / State / Product Data Model 前执行 hard stop; +- 抵抗“人类已经说不要拆概念,所以最新说法等于 override”的合理化。 + +原始合理化摘录: + +```text +The latest human statement explicitly overrides historical behavior. +Refund remains one concept; settlement is merely an asynchronous technical side effect. +I can write a complete draft with assumptions and an open question. +Design Readiness blocks Feature Spec, not requirement drafting. +Resolve enough grill questions has no objective threshold. +Deadline and prior discussion make another question redundant. +``` + +风险:管理员审核完成、资金到账完成和客户通知触发点被压成一个终态,导致异步失败、重试、对账和验收互相矛盾。 + +### RED-2:Approval Action / Approval Instance 下游追踪 + +结论:`FAIL`。 + +当前谨慎 Agent 会因歧义停止 Product Brief,但现有模板无法证明 stable upstream-to-downstream chain;在会议时间压力下,模板允许以下漂移: + +- `Approval` 从 glossary 中的动作,分别变成 product 的 request 和 spec 的 record; +- Applicant 被重命名为 Requester; +- Reviewer 与 Admin permission 被混为一体; +- Tenant 只作为依赖出现,没有 identity/boundary; +- lifecycle 和 one-active-instance invariant 在 spec 中被下游自行发明。 + +原始漏洞摘录: + +```text +The requirement template has no concept identity, relationship, lifecycle-transition, or invariant registry. +Product terminology explicitly allows Meaning in this feature, enabling redefinition instead of citation. +Product-to-spec guidance provides no semantic-reference mechanism. +Stable IDs exist only for downstream Design Slices, not upstream domain concepts. +Self-contained even if wording differs is not rejected by an explicit semantic-drift rule. +``` + +风险:动作/实体碰撞、跨租户授权错误、非法状态转换、重复 active instance 和不兼容的下游 schema/event。 + +### RED-3:轻量路由与 ADR 所有权边界 + +场景 A(按钮文案):`PASS`。现有 `design-not-needed` 与小 UI copy 不建 project decision 的规则可以抵抗“审计需要所以建 ADR”的压力。 + +场景 B(额度/透支冲突):`FAIL`。当前 Grill 能先查历史行为并提出一个推荐问题,但 ADR surface 仍可重新拥有产品语义: + +```text +Multi-tenant credit limits share domain/data rules, so all product semantics belong in Decision & Design. +The decision template asks for Domain Concepts and Source Of Truth, so mapping concepts directly to tables is required. +The manager authorized skipping clarification, so the ADR can settle what overdraft means. +A proposed ADR is not accepted yet, so redefining product meaning inside it is harmless. +``` + +风险:ADR 变成 PRD、Concept Model 和 physical data design 的混合真相源;Concept Foundation 也可能被错误实现为隐藏的新 stage。 + +## RED 综合判定 + +```text +Mechanical focused contract: FAIL as expected +Pressure lane 1: FAIL +Pressure lane 2: FAIL +Pressure lane 3A: PASS (lightweight path already exists) +Pressure lane 3B: FAIL +``` + +GREEN 必须针对实际漏洞实现,不能把已经通过的简单需求轻量路径重写成更重流程。 + +## GREEN Closure Note + +同一授权组在更新后的发布规则上复测相同场景: + +```text +Refund Human Grill Contract: PASS +Approval Concept -> Product/Spec trace: PASS +Simple copy not-needed route: PASS +Historical overdraft / PRD-ADR ownership: PASS after reference-only template alignment +Authorization Status: consumed +``` + +GREEN 详细证据与最终评分记录在本轮 full-validation 报告;本文件继续保留修复前失败和原始合理化语言。 diff --git a/docs/reports/agent-loop-v1.3.0-cross-platform-python-script-runtime-validation-2026-07-13.md b/docs/reports/agent-loop-v1.3.0-cross-platform-python-script-runtime-validation-2026-07-13.md new file mode 100644 index 0000000..b5eddd2 --- /dev/null +++ b/docs/reports/agent-loop-v1.3.0-cross-platform-python-script-runtime-validation-2026-07-13.md @@ -0,0 +1,128 @@ +# Agent Loop v1.3.0 Cross-Platform Python Script Runtime 验证报告 + +日期:2026-07-13 + +分支:`alpha/v1.3.0` + +版本:`1.3.0`,未 bump + +审计对象:当前未提交工作区中的 Cross-Platform Python Script Runtime proposal、实施计划、四个 canonical checker、兼容入口、原生测试、现行文档引用和 CI matrix。 + +## 结论 + +| 项目 | 结果 | +|---|---| +| 实现状态 | 四个 canonical Python checker 已实现 | +| Python 依赖 | Python 3.10+;标准库 only | +| 原生 Python tests | `36/36 PASS` | +| 既有 Shell regressions | `32/32 PASS` | +| 旧入口兼容 | 4/4 launcher PASS;不再包含业务规则 | +| macOS | `macOS-verified`,Python 3.14.5 | +| Windows | `Windows-test-defined`,远端 CI 尚未运行 | +| Python 3.10 | CI matrix 已定义,本机未安装独立 `python3.10` | +| 当前权威旧入口推荐 | 0 | +| Critical / High / Medium | `0 / 0 / 0` | + +结论:本轮代码迁移、macOS parity、fail-closed、只读和路径边界均已完成;在远端 Windows CI 实际通过前,不宣称双平台最终验收完成。唯一下一阶段是 Human Review;如人类后续授权 commit/push,再读取远端 CI 结果完成 Windows execution evidence。 + +## 实现范围 + +| 旧入口 | Canonical implementation | 当前角色 | +|---|---|---| +| `scripts/check-root-agents-blocks.sh` | `scripts/check-root-agents-blocks.py` | Root AGENTS managed-block 只读漂移检查 | +| `scripts/check-onboarding-core-flow-coverage.rb` | `scripts/check-onboarding-core-flow-coverage.py` | Onboarding Core Flow coverage | +| `scripts/check-concept-foundation-trace.rb` | `scripts/check-concept-foundation-trace.py` | Concept Foundation / Product Model trace | +| `scripts/check-adr-requirement-model-trace.rb` | `scripts/check-adr-requirement-model-trace.py` | ADR Requirement Model technical landing trace | + +`scripts/checker_support.py` 统一负责 Python 3.10 guard、UTF-8 BOM/CRLF、metadata/section/table parsing 与 workspace path confinement。旧 `.sh` / `.rb` 文件只保留一周期 launcher,包含 `DEPRECATED COMPATIBILITY ENTRY` 标记并转交 canonical `.py`。 + +## RED 基线与修复 + +| RED | 修复 | GREEN evidence | +|---|---|---| +| 四个 canonical `.py` 不存在 | 建立共享支持层与四个 Python CLI | canonical inventory PASS | +| CI workflow 不存在 | 新增 macOS/Windows × Python 3.10/3.x matrix | CI contract PASS | +| Python `<3.10` 无显式 fail-closed | 四入口调用统一 runtime guard | 模拟 3.9 返回 exit 2 | +| 兼容入口未标记 deprecated | 增加标记、行数和 canonical target contract | 4/4 thin-launcher contract PASS | +| 兼容入口未发现 Windows `py -3` | 统一探测 `py -3 -> python3 -> python` | launcher discovery contract PASS | +| Root managed source 可逃逸 project root | 对 source 使用 `confined_path` | existing `../outside.md` 被拒绝 | + +最初 `unittest` RED 曾先暴露测试 helper 的模块导入路径错误;修复为 `tests.checker_test_support` 后,RED 精确命中 canonical 文件缺失,没有把测试装载错误计作生产缺陷。 + +## 行为 parity + +| Checker | Valid | Invalid / adversarial | 特殊平台文本 | 结论 | +|---|---|---|---|---| +| Root AGENTS | current template PASS | missing/stale/broken/nested/duplicate/outside source FAIL | BOM + CRLF PASS | parity PASS,越界更严格 | +| Onboarding | planned/deferred PASS | missing recovery/detached diagram FAIL | BOM + CRLF PASS | parity PASS | +| Concept Foundation | accepted/not-needed PASS | unconfirmed/open ambiguity/duplicate/missing permission 等 FAIL | BOM + CRLF PASS | parity PASS | +| ADR trace | accepted/proposed/not-needed PASS | missing review/coverage/owner/gate/operation/path 等 FAIL | BOM + CRLF PASS | parity PASS | + +所有 valid checker 均重复运行两次并比较 `returncode/stdout/stderr`,输出一致;对临时 artifact tree 做运行前后 SHA-256 snapshot,确认无文件变化。 + +## Exit Code 与安全边界 + +| Exit | 含义 | 验证 | +|---:|---|---| +| 0 | PASS 或 `--help` | PASS | +| 1 | artifact contract invalid | PASS | +| 2 | usage、missing input、unsupported Python | PASS | + +同时确认: + +- canonical scripts 的 AST imports 只包含 Python 标准库与本地 `checker_support`; +- 不调用 Bash、Ruby、grep、sed、PowerShell cmdlet 或第三方 package; +- ADR 和 Root source reference 均拒绝 workspace/project root 越界; +- 没有 `--force`、自动安装 Python、自动修改 PATH 或手工模拟降级; +- ADR、Concept Foundation 和 Onboarding hard gate 的既有 valid/invalid 结论保持不弱化。 + +## 文档与历史边界 + +当前 authority 已切换到 `.py`: + +- `SKILL.md` +- `Usage.md` +- `references/project-guidance.md` +- `references/workflow-checklists.md` +- 当前 1.3.0 `CHANGELOG.md` +- active regression runners + +旧路径仍只出现在 compatibility contract、迁移 proposal/plan、历史 changelog 或历史 report 中。历史 evidence 未被改写。 + +## 平台证据 + +`.github/workflows/cross-platform-checkers.yml` 定义四组合: + +```text +macos-latest / Python 3.10 +macos-latest / Python 3.x +windows-latest / Python 3.10 +windows-latest / Python 3.x +``` + +本地实际证据:macOS + Python 3.14.5,36/36 native tests、32/32 Shell tests、四个 compatibility launcher 和全部机械检查通过。 + +未执行证据:Windows runner 与独立 Python 3.10 runner。原因是当前没有 commit/push 授权,workflow 尚不能在远端执行;这不是 PASS,也不被伪装成双平台完成。 + +## 机械检查 + +- `SKILL.md` 和 workflow YAML:PASS +- repository JSON parse:PASS +- Markdown fence balance:PASS +- 全部 Shell syntax:PASS +- 全部 Ruby compatibility/test syntax:PASS +- Python compileall:PASS +- `git diff --check`:PASS + +## Human Gate + +| 操作 | 是否授权 | +|---|---| +| version bump | 否 | +| commit | 否 | +| push | 否 | +| tag | 否 | +| PR / merge | 否 | +| release / publish | 否 | + +本报告不授权 Feature Monthly Compaction 自动进入实现;该 proposal 仍需根据当前状态和 Human Review 单独路由。 diff --git a/docs/reports/agent-loop-v1.3.0-feature-monthly-archive-validation-2026-07-14.md b/docs/reports/agent-loop-v1.3.0-feature-monthly-archive-validation-2026-07-14.md new file mode 100644 index 0000000..8c3f3d2 --- /dev/null +++ b/docs/reports/agent-loop-v1.3.0-feature-monthly-archive-validation-2026-07-14.md @@ -0,0 +1,119 @@ +# Agent Loop v1.3.0 Feature Monthly Archive 专项验证报告 + +验证日期:2026-07-14 +验证范围:Feature Monthly Archive(目录归档、locator、Reader Compatibility、scan/check/apply/restore/rehydrate) +平台结论:`macOS-verified / Windows-verified` +版本结论:沿用 v1.3.0 开发线,未修改版本号 + +## 1. 结论 + +Feature Monthly Archive 的本地专项实现通过。归档只把经过人类确认的完整 closed Feature 目录移动到 `.agent-loop/features/YYYY-MM//`,并维护根级 `.agent-loop/features/archive.md`;没有内容压缩、per-feature summary、`historical/`、Deep Archive、删除或自动调度能力。 + +本地最终 focused 回归为 79/79 个 Python 测试通过、2/2 个专项 shell contract 通过。提交 `7253461` 的 GitHub Actions 四矩阵任务(Windows/macOS × Python 3.10/3.x)全部成功,跨平台结论更新为 `macOS-verified / Windows-verified`:。 + +## 2. RED 基线 + +| RED 阶段 | 真实结果 | 证明的缺口 | +|---|---:|---| +| 初始 archive contract | 27 个测试方法运行,产生 28 个预期失败 | support module、archive template、四个 CLI 与 Reader Compatibility 尚不存在 | +| Runtime coordinated contract | 1 个 shell contract 失败 | `SKILL.md` 尚无 Feature Monthly Archive runtime authority | +| Cross-platform CI contract | 1 个测试方法产生 8 个失败 | 四个 archive 测试模块和四个 CLI 路径尚未进入 Windows/macOS workflow | +| Proposal implementation status contract | 1 个 shell contract 失败 | proposal 仍停留在设计/计划状态,没有标记为“已实现;待最终 Human Review” | +| Responsibility-map contract | 1 个 shell contract 失败 | project decision、requirement mapping、project memory、project guidance 与 execution-path negative assertion 尚未同步 | +| Restore check contract | 1 个 Python 测试失败 | `check --operation restore` 虽出现在 CLI contract 中,但没有可到达的成功语义 | +| Journal path escape | 1 个 Python 测试失败,错误返回 0 | 篡改 journal 的 `../` move path 可以把目录移出 workspace;该 Critical 已用统一 confinement 修复 | +| Rename/journal crash window | 1 个 Python 测试失败 | rename 完成但 completion record 尚未写入时,新进程无法恢复;现按 source/target 实际状态恢复 | +| Whole-directory hash coverage | 1 个 Python 测试失败 | 大于 2 MiB 的 feature payload 未进入 snapshots;现仅 Markdown reference scan 保留 2 MiB 限制 | +| BOM/CRLF preservation | 1 个 Python 测试失败 | 引用更新会移除 UTF-8 BOM 并规范化换行;现逐字节保留编码标记和 CRLF | +| Broken relative link | 1 个 Python 测试失败 | 不存在的跨边界相对链接仍会被重写;现分类为 `unsupported` 并阻断 apply | +| Journal scope tampering | 1 个 Python 测试错误返回 0,并删除不在原计划内的 `README.md` | 外层 journal 与内嵌 plan 被一起篡改并重算自哈希时,`missing-before` 可越权;现要求 reference-edit 哈希链从原 snapshots 起步,并在任何目录移动前校验 backup bytes | +| Post-crash human drift | 1 个 Python 测试错误返回 0 | restore 会覆盖进程中断后的人类修改;现对 source/target 实际位置和全部当前字节做 preflight,发现 drift 后保留 journal 并停止 | +| Stranded transaction bypass | 2 个 Python 测试错误返回 0 | 已存在 `.archive-txn/` 时仍可生成新 plan 或开始 apply;现 scan 与 apply 都要求先按显式 transaction ID 恢复 | +| Inbound relative link blind spot | 2 个 Python 测试失败 | `.agent-loop/project.md` 中 `features//spec.md` 这类指向待移动 feature 的相对链接未进入 plan,apply 后可留下 broken link;现双向计算跨移动边界的链接目标 | + +这些 RED 都发生在对应实现或 authority 更新之前;没有通过弱化断言把失败改成 GREEN。 + +## 3. GREEN 证据 + +执行命令: + +```text +python3 -m unittest \ + tests.test_python_checker_contract \ + tests.test_feature_archive_support \ + tests.test_feature_monthly_archive_scan \ + tests.test_feature_monthly_archive_apply \ + tests.test_feature_monthly_archive_restore \ + tests.test_adr_requirement_model_trace -v +``` + +Task 6 首次运行结果为 `Ran 64 tests`,`OK`。首轮 Standards Review 后为 `Ran 72 tests`,`OK`;本轮 Human Review 针对四个问题补强 7 条回归并完成修复后,最终为 `Ran 79 tests`,`OK`。 + +执行命令: + +```text +bash tests/validate-feature-monthly-archive-runtime.sh +bash tests/validate-feature-monthly-compaction-proposal.sh +``` + +结果:2/2 PASS。 + +## 4. 逻辑与压力场景 + +### Eligibility 与阻断 + +- 两个月 fixture 中,`2026-05-08-login` 与 `2026-06-12-payment` eligible;同批 `2026-05-22-import` 为 paused,保留在 flat path。 +- active、blocked、paused、current-month、incomplete close、缺少 Archive Readiness、非终态 readiness、placeholder summary、open follow-up、project memory active/paused 均被阻断。 +- flat/month collision、symlink escape、大于 2 MiB 且含旧路径的 Markdown、不确定旧路径引用均 fail closed。 +- 原始 requirement source 被分类为 `immutable-requirement-source`,不被重写;历史报告只作为可见 preserved evidence。 + +### Read-only 与哈希证据 + +- `scan`、pre-check、post-check、current-month blocker、collision 与 stale-plan 场景都在操作前后比较完整文件树 SHA-256 map;结果完全相等,没有新增、删除或字节漂移。 +- deterministic plan 测试在两个不同绝对根目录生成相同 canonical JSON SHA-256,绝对路径不进入 plan hash。 +- valid-but-different `expected-plan-sha256` 与 scan 后状态漂移均以 `stale-plan` 拒绝,且完整 SHA-256 map 保持不变;状态漂移拒绝时不会创建 transaction journal。 + +### Apply、恢复与 rehydrate + +- 两个 closed Feature 被整目录移动到各自月份;移动前后的完整文件 SHA-256 map 相同,binary payload 字节保持不变,archive locator 按 Feature ID 排序。 +- 只执行 plan 中预计算的 project、requirement README 与 ADR 引用更新;人类原始 requirement source 和无关 Markdown 字节保持不变。 +- 重复 apply 不创建 `YYYY-MM/YYYY-MM/`,无 move 的重复计划保持幂等。 +- test-only 注入引用写失败后,目录、引用和 locator 由 transaction journal 恢复到完整的事前 SHA-256 map;非 test mode 使用注入变量会被拒绝。 +- 新进程可以从显式 transaction ID 恢复中断事务;发生源路径碰撞时 fail closed,并保留 `restoring` journal 供人工处理。 +- restore 在任何 feature move 或引用回写前校验 journal/plan/snapshot scope、reference-edit 哈希链、备份字节和当前工作区字节;整体篡改 journal、损坏 backup 或崩溃后出现人类修改时都不会覆盖项目内容。 +- 任一 stranded `.archive-txn/` 会同时阻断新 scan 和 apply;项目记忆中指向待移动 feature 的相对 Markdown link 会进入确定性 plan 并随 apply 更新。 +- rehydrate 把完整目录移回 flat path,将 locator 标记为 `rehydrated`,但 `spec.md` 仍保持 `closed`;后续 reopen 仍需独立 Feature Follow-up Human Gate。 + +## 5. 平台证据 + +| 平台 | 状态 | 证据 | +|---|---|---| +| macOS | verified | Python 3.10+ 本地 79/79 focused tests 与 2/2 shell contracts 通过;远端 `macos-latest` × Python 3.10/3.x 成功 | +| Windows | verified | GitHub Actions `windows-latest` × Python 3.10/3.x 在 commit `7253461` 成功;run `29320389912` | + +实现只使用 Python 3.10+ 标准库。文档同时给出 macOS `python3` 与 Windows PowerShell `py -3` 调用方式。 + +## 6. 严重度审计 + +| 严重度 | 数量 | 结论 | +|---|---:|---| +| Critical | 0 | 未发现删除历史内容、绕过 Human Gate、跳过 expected hash 或越界写入能力 | +| High | 0 | 未发现 stale-plan、恢复、locator 或 lifecycle/archive-state 混淆缺口 | +| Medium | 0 | Windows/macOS 执行证据、恢复安全和跨文件契约均已闭合 | + +专项评分:100/100。跨平台结论引用具体 workflow run,不把 matrix configuration 单独视为执行证据。 + +## 7. 明确排除范围 + +- 不创建 per-feature archive summary 或 per-month index; +- 不创建 `historical/`、Deep Archive、Summary Only; +- 不删除、打包、压缩、外移或替代历史 feature 内容; +- 不自动定时归档,不自动归档当前月; +- 不移动 active、blocked、paused 或证据不完整的 feature; +- 不改写原始人类需求文件或 accepted ADR 产品语义; +- 不提供 `--force`; +- 不在技能源码仓库创建真实目标项目 `.agent-loop/features/`。 + +## 8. 授权与边界 + +本报告覆盖实现、focused validation 与跨平台 CI。功能提交 `7253461` 已推送到 `origin/alpha/v1.3.0` 和 `ai-factory/alpha/v1.3.0`。v1.3.0 Release Human Gate 已于 2026-07-14 批准,发布目标为 `stable-v1.3.0`;tag 只能在精确 release-evidence commit 的 Windows/macOS CI 全部成功后创建。发布复验在隔离 worktree 中进行,没有吸收主工作区的 onboarding proposal 修改、v1.4 proposal 删除或 `docs/proposal/v2.0.x/` 内容。 diff --git a/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-12.md b/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-12.md new file mode 100644 index 0000000..1f3870c --- /dev/null +++ b/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-12.md @@ -0,0 +1,143 @@ +# Agent Loop v1.3.0 全量验证报告 + +日期:2026-07-12 + +分支:`alpha/v1.3.0` + +版本:`1.3.0` + +审计对象:当前未提交工作区,包含已批准 proposal、Phase 1 / Phase 2 实现、回归测试、行为示例与验证证据。不将历史报告当作 runtime authority。 + +## 总体结论 + +| 项目 | 结果 | +|---|---| +| 总分 | **97 / 100** | +| 等级 | **STRONG** | +| 修复前仓库基线 | `30/30 PASS` | +| Focused Concept Foundation contract | `PASS` | +| 修复后全部 `tests/*.sh` | `31/31 PASS` | +| Critical / High / Medium | `0 / 0 / 0` | +| 当前风险 | 2 项 Low,均不绕过 Gate | + +结论:Phase 1 Requirement Concept Foundation 和 Phase 2 Product Model Derivation 已形成跨 runtime、design、requirement、product/spec、root guidance、example 和测试的一致闭环。`Concept Foundation` 仍为 Requirements Discussion / Requirement Product Grill 内部方法,未新增 canonical stage。 + +## 六域评分 + +| 审计域 | 权重 | 结果 | 评分 | 主要证据 | +|---|---:|---|---:|---| +| Logic Correctness | 20% | PASS | 97 | `references/runtime.md:59-88` 定义路由、状态、hard stop 和 Human Grill Contract;`references/requirement-product-grill.md:22-113` 定义 Gate 与推导。 | +| Autonomy | 15% | PASS | 96 | Agent 先查证据、提取候选、推荐定义与影响,再仅询问一个 downstream-blocking 问题;简单路径可记录 not-needed 理由。 | +| Project Entry / Evidence Graph + DDD Onboarding | 15% | PASS | 97 | 未修改这些阶段的语义或顺序;root Stage Map 仅在 Requirements Discussion 行增加导航,全量回归保持 GREEN。 | +| Development / Test Workflow | 20% | PASS | 99 | 保留 RED,新增 focused contract、真实 Markdown artifact parser、无效 fixtures、同场景 GREEN 压测,并重跑全量测试。 | +| Memory | 15% | PASS | 96 | 原始人类需求保持不可篡改;accepted Concept Foundation 和 Requirement Product Model 归属 human-reviewed requirement document,root guidance 与下游引用一致。 | +| Recommendation | 15% | PASS | 98 | `candidate` / `reopened` 有唯一回退路径;`accepted` / `concept-foundation-not-needed` 后才继续;Human Gate 的问题粒度和停止条件明确。 | + +加权得分为 97.25,按报告精度记为 97。 + +## RED 基线与 GREEN 证据 + +修复前,全部已有测试为 `30/30 PASS`,说明仓库健康但现有测试未覆盖本漏洞。新增 `tests/validate-concept-foundation-requirement-modeling.sh` 后,在修改 runtime/template 之前产生预期 RED: + +```text +FAIL: SKILL.md missing required text: Concept Foundation +``` + +三组修复前语义压测暴露了真实合理化路径:退款终态被压缩,Approval 动作/实例在下游漂移,ADR 重新拥有产品语义。完整证据保存于 `docs/reports/agent-loop-v1.3.0-concept-foundation-red-baseline-2026-07-12.md`。 + +修复后,同一组授权场景复测为: + +```text +Refund Human Grill Contract: PASS +Approval Concept -> Product/Spec trace: PASS +Simple copy not-needed route: PASS +Historical overdraft / PRD-ADR ownership: PASS +``` + +Focused contract 还通过 Ruby validator 实际解析示例与无效 fixture,证明: + +- accepted 示例的 5 个 Concept 和 20 个 Product Model row 可追踪; +- `candidate` 需求不能继续模型化; +- 未定义 Concept ID 或脱离上游的 downstream reference 会被拒绝; +- Product Brief / Feature Spec 不能在本地重新定义 accepted 产品语义。 + +## 已通过的核心不变量 + +- `SKILL.md` 保持简洁入口,详细方法仍属于 `references/`。 +- `Concept Foundation` 没有进入 `references/runtime.md` canonical Stage Order。 +- Human Grill Contract 固定为 evidence inspection → candidate extraction → recommendation/evidence/impact → exactly one blocking question。 +- `candidate` 和 `reopened` 会阻止 Business Flow、State Model 和 Product Data Model。 +- 简单文案/样式/配置变更可使用带具体理由的 `concept-foundation-not-needed`。 +- accepted Concept Foundation 与 Requirement Product Model 只由 human-reviewed requirement document 拥有。 +- Product Brief 和 Feature Spec 只引用 accepted Concept / Model ID,不重新定义产品语义。 +- ADR 仅消费 accepted PRD 语义;本轮未建立 Concept-ID-to-table/store/event/provider 映射。 +- Delivery Contract、TDD、Active Feature、Pause/Resume/Close/Reopen、Submit/Integrate 和 Tag 的原有 Gate 继续由全量回归保护。 +- 没有在技能源仓库中创建目标项目 `.agent-loop/` artifact。 + +## 代表性压力场景 + +| 场景 | 结果 | 结论 | +|---|---|---| +| 退款审核完成与资金到账完成被要求合并 | PASS | Agent 先展示候选概念和推荐定义,再只问一个阻断问题。 | +| User / Customer / Member / Tenant 边界 | PASS | 候选库要求 identity、owner、lifecycle 和 relationship,不得仅做同义词表。 | +| Approval action / instance 冲突 | PASS | stable Concept IDs 追踪到 Product Brief 和 Feature Spec。 | +| 历史允许透支与新规则冲突 | PASS | PRD 负责产品定义,ADR 无权用技术结构重新定义。 | +| 按钮文案变更 | PASS | 记录 `concept-foundation-not-needed` 理由,不强制建模或 ADR。 | +| 复杂 Requirement 后续 Decision Scan | PASS | accepted PRD 语义可被后续技术决策消费,本轮未提前实现 Phase 3。 | +| 既有全流程压力集 | PASS | Product Source、Delivery Contract Gate、TDD、Active Feature、Phase 汇总、ADR drift、Follow-up、Submit、stale-memory、Chat 路径由 `31/31` 全量回归保持通过。 | + +## 当前问题 + +### Low-1:触发分类仍需 Agent 语义判断 + +证据:`references/runtime.md:61-71`、`references/requirement-product-grill.md:28-41`。 + +风险:边界案例中,Agent 可能需要在“复用已接受概念”和“新关系已改变产品语义”之间做判断。 + +处理:已有正/反触发信号、具体 not-needed 理由和 human stop;不会绕过 Gate,故为 Low。 + +### Low-2:Markdown validator 证明结构引用,不证明业务证据为真 + +证据:`scripts/check-concept-foundation-trace.rb`。 + +风险:自动验证可拒绝未接受状态、未定义 ID、脱钩 model row 与下游重定义,但无法自动判断人类提供的业务事实是否真实。 + +处理:这是 Human Grill Contract 与 Human Gate 的职责,不应在本轮用可执行 schema 伪造业务真实性,故为 Low。 + +## 未采纳或降级意见 + +- 未把 Concept Foundation 升级为 canonical stage:proposal 明确否定,且会与轻量需求路径冲突。 +- 未强制所有需求建立概念模型:保留带理由的 `concept-foundation-not-needed`。 +- 未让 ADR 拥有 Domain Concept 定义:ADR 仅保留 accepted PRD 引用与技术决策职责。 +- 未增加 Concept-to-technical mapping、Design Skill、E2E Skill、Jam Kits 或 YAML/JSON 可执行 schema:均属 Phase 3/4 或明确排除范围。 + +## 范围漂移检查 + +- Phase 1:已实现 trigger、not-needed、status lifecycle、evidence-first candidate extraction、recommended definition/evidence/impact、单一 blocking question 和 hard stop。 +- Phase 2:已实现 Concept Relationships、Role/Permission、Commands/Events、Business Flow、State Model、Product Data/Fact/Invariant、Exception/Recovery 和 Concept-to-Product trace。 +- 未实现 Phase 3 / Phase 4,未新增 Design Skill、E2E Skill、Jam Kits 或 executable schema。 +- 未修改技能版本;`SKILL.md`、`plugin.json`、`README.md` 与 `Usage.md` 仍为 `1.3.0`,root managed block 仅做同版修订号更新。 +- 未在仓库根目录创建 `.agent-loop/`;下游 artifact 只存在于 `templates/`、`examples/` 和 tests fixtures。 + +## 结构与机械检查 + +- `SKILL.md` YAML:PASS +- `plugin.json` JSON:PASS +- `scripts/check-concept-foundation-trace.rb` Ruby 语法:PASS +- 全部 Shell 语法:PASS +- Markdown fence 平衡:PASS +- 本轮 diff 新增/修改行尾随空白:PASS;被触及模板内仍有历史 `- ` 占位行,不在本轮 diff 中,未做无关格式清理 +- `git diff --check`:PASS + +## 发布与授权判断 + +当前实现可进入人类审查,但本报告不构成提交或发布授权。 + +| 操作 | 是否授权 | +|---|---| +| commit | 否 | +| push | 否 | +| PR / merge | 否 | +| tag / release / publish | 否 | + +推荐的唯一下一阶段:**Human Review**。人类确认本报告与 Phase 1 / Phase 2 范围后,再单独决定是否进入 Submit / Integrate。 diff --git a/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.1.md b/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.1.md new file mode 100644 index 0000000..92c9dc7 --- /dev/null +++ b/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.1.md @@ -0,0 +1,166 @@ +# Agent Loop v1.3.0 全量验证报告(ADR Technical Landing) + +状态:**已被后续审查否定并由 `.2` 修复验证取代**。本文件保留首轮实现的历史证据,不代表当前工作区结论。后续审查发现 proposed/accepted Human Gate 顺序、source-wide scope、外部引用、Hard Gate、Design Slice、operational inventory 和 not-needed 分支存在可复现漏洞;详见 `agent-loop-v1.3.0-adr-technical-landing-review-red-2026-07-13.md`。 + +日期:2026-07-13 + +分支:`alpha/v1.3.0` + +版本:`1.3.0` + +审计对象:当前未提交工作区,包含 approved proposal、Effective Requirement Snapshot、Requirement Model Technical Landing Trace、Coverage Hard Gate、Upstream Compatibility / Drift、Decision & Design Human Review Summary、triggered operational landing、root guidance、领域中立 validator 和回归证据。 + +同日已有 `docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.md` 属于 Concept Foundation 历史审计,本轮不覆盖该证据,使用 `.1` 保存新报告。 + +## 总体结论 + +| 项目 | 结果 | +|---|---| +| 总分 | **98 / 100** | +| 等级 | **STRONG** | +| 修改前全部测试 | `31/31 PASS` | +| Focused contract | PASS | +| 修改后全部 `tests/*.sh` | `32/32 PASS` | +| Validator valid trace | 6 Requirement Model IDs:3 landed / 1 existing-decision / 1 feature-local / 1 not-applicable | +| Validator invalid fixtures | 5/5 被拒绝 | +| Critical / High / Medium | `0 / 0 / 0` | +| Low | 2 | + +结论:ADR 已能作为 accepted Requirement Product Model 到技术落地的可追踪桥梁,且仍是产品语义的消费者而非拥有者。本轮没有新增 canonical stage、默认 mapping artifact、ADR lifecycle status 或 executable schema。 + +## 六域评分 + +| 审计域 | 权重 | 结果 | 评分 | 主要证据 | +|---|---:|---|---:|---| +| Logic Correctness | 20% | PASS | 98 | runtime 固定 snapshot、trace、coverage、compatibility 和 supersede 顺序;validator 拒绝缺失 coverage、空 landing、未接受/重开 source 和 `review-required`。 | +| Autonomy | 15% | PASS | 97 | Agent 先解析 effective pointer 和模型 scope,自主计算 coverage,只将产品语义、非落地 disposition、compatibility 和 supersede 真正决策交给人类。 | +| Project Entry / Evidence Graph + DDD Onboarding | 15% | PASS | 98 | canonical Stage Order 未改;root Stage Map 仍仅导航到 Decision & Design,只增加必要 stop/completion/artifact 提示。 | +| Development / Test Workflow | 20% | PASS | 99 | 保留正确 RED,实现结构 parser 而非只做关键词断言,混合 disposition valid fixture 与 5 类 invalid fixture 全部按预期运行。 | +| Memory | 15% | PASS | 98 | README effective pointer 是当前 source 索引;accepted ADR 历史不被改写,不兼容技术决策通过 superseding ADR 保留审计链。 | +| Recommendation | 15% | PASS | 98 | 缺 source 回 Requirement Archive / Requirements Discussion,产品语义 blocker 回 Human Grill,coverage 缺失保持 proposed,compatibility 回 Decision & Design,技术结论失效进 supersede。 | + +加权得分为 98.05,按整数记为 98。 + +## RED 基线与 GREEN 证据 + +修改 runtime/template 前,全部已有测试为: + +```text +BASELINE passed=31 failed=0 +``` + +新增 focused contract 后首次运行: + +```text +FAIL: SKILL.md missing required text: Effective Requirement Snapshot +``` + +该 RED 因现有发布入口没有 snapshot/trace contract 而失败,不是 Shell 语法或拼写错误。完整证据保存于 `docs/reports/agent-loop-v1.3.0-adr-technical-landing-red-baseline-2026-07-13.md`。 + +GREEN 输出: + +```text +PASS: ADR technical landing trace covers 6 requirement-model IDs with 3 landed rows +PASS: ADR Requirement Model Technical Landing Trace contract is complete +FULL_SUITE passed=32 failed=0 +``` + +领域中立 validator 同时证明: + +- `landed`、`covered-by-accepted-decision`、`feature-local` 和 `not-applicable` 都有可执行规则; +- snapshot 内每个 Requirement Model ID 必须恰好有一个 trace row; +- `landed` 必须有 Technical Landing、Preserved Invariant、Design Slice 和 Verification; +- existing-decision 必须指向 ADR reference,feature-local 必须指向 `spec.md`,not-applicable 必须有具体 reason; +- `candidate`、`reopened`、`review-required`、missing coverage 和 empty landing 均无法进入 acceptance。 + +## 已通过的跨文件不变量 + +- `SKILL.md` 仍是简洁入口,详细合同在 runtime/design/project-decisions。 +- Effective Requirement Snapshot 和 Technical Landing Trace 是 Decision & Design 内部方法/记录部分,不是 stage。 +- requirement README effective pointer、effective source status 和 ADR snapshot 必须一致。 +- PRD / Requirement Product Model 继续拥有 Concept、relationship、role/permission、command/event、flow、state、invariant、recovery 和 fact ownership。 +- ADR 只记录 unchanged meaning reference 和技术落地,含义不足时回 Requirements Discussion。 +- Coverage Hard Gate 同时阻止 ADR acceptance 和依赖的 Feature Spec、Plan、implementation。 +- `review-required` 仅是 dependency availability judgment;`Allowed Status` 仍为 `proposed | accepted | superseded | deprecated`。 +- accepted ADR 技术结论失效时建立 superseding ADR,不原地改写 decision meaning。 +- Operational landing 先分类 `triggered | not-triggered`;只有 triggered concern 产生对应详细章节。 +- Human Review Summary 显示 source、coverage 数量、技术决策、语义保留、operational trigger、Design Slice、verification 和人类决定。 +- root guidance 修订为 `block-version:1.3.0-20260713.1`,全部 13 个 managed blocks 一致。 +- 没有创建仓库根 `.agent-loop/`、mapping directory、YAML/JSON schema 或新的 ADR status。 + +## 代表性压力场景 + +| 场景 | 结果 | 结论 | +|---|---|---| +| README pointer 已换源,ADR 仍引用旧 source | PASS | 进入 `review-required`,停止新依赖工作。 | +| 部分 State / Recovery model row 没有落点 | PASS | ADR 保持 proposed,Feature Spec 被阻止。 | +| 人类要求在 ADR 里补产品定义 | PASS | 回 Requirements Discussion / Human Grill,ADR 不填补。 | +| accepted ADR 的一致性边界已失效 | PASS | 保留原记录,Human Review 后新建 superseding ADR。 | +| 用 feature-local / not-applicable 隐藏共享约束 | PASS | 必须给 owner/spec、verification 或具体 reason,并在 Human Review 中显示。 | +| 无迁移/兼容/上线变化仍要写满 operational 章节 | PASS | 只记录 not-triggered 理由,不展开空章节。 | +| 简单单 Feature 需求 | PASS | 既有 `design-not-needed` 路径不受影响,不强制 ADR。 | +| Product Brief Source Gate / Delivery Contract Gate / TDD / Active Feature / Pause-Resume-Close-Reopen / multi-phase roll-up / Follow-up / Submit / stale-memory / Chat | PASS | 原有控制面在 `32/32` 全量回归中继续通过。 | + +## 当前问题 + +### Low-1:Validator 信任 ADR 声明的 coherent scope + +证据:`references/project-decisions.md:170`、`scripts/check-adr-requirement-model-trace.rb:124-152`。 + +风险:Validator 能证明 snapshot 声明的所有 model ID 都有 trace,但无法仅从 Markdown 自动判断 Agent 是否为了避免落地而错误缩小 ADR scope。 + +处理:scope 外模型必须由 accepted decision、feature-local placement、其他 decision 或具体 not-applicable 说明,并在 Decision & Design Human Review 展示。这是人类决策而非 schema 能证明的业务真实性,故为 Low。 + +### Low-2:Validator 证明引用形状,不单独证明外部 artifact 当前真实状态 + +证据:`scripts/check-adr-requirement-model-trace.rb:181-187`。 + +风险:Parser 要求 existing-decision 具有 ADR reference、feature-local 具有 `spec.md` 引用,但不仅凭单个 ADR 文件确认被引用 decision 当前必然 accepted 或 Feature Spec 已获人类确认。 + +处理:runtime 要求读取已链接 decision/spec 和状态,Human Review Summary 展示 owner/verification;不在本轮引入 executable schema 或跨文件生成器,故为 Low。 + +## 未采纳或降级意见 + +- 未新建 requirement-to-technical mapping artifact:会引入第二个同步对象,与 approved 方案 A 冲突。 +- 未把 Effective Snapshot、Coverage Review 或 Compatibility Review 升级为 canonical stage:它们是 Decision & Design 内部方法和 gate。 +- 未新增 `review-required` ADR status:它仅表示依赖可用性。 +- 未把业务名词、业务动作、vendor、store、protocol 或 rollout topology 写入通用 template/validator。 +- 未默认生成 Migration / Rollout / Rollback 章节:只有 trigger assessment 为 triggered 时生成对应详细。 +- 未引入 YAML / JSON executable schema:当前 Ruby validator 仅验证 Markdown 结构与可追踪性。 + +## 范围漂移检查 + +- 已实现 proposal 要求的 Effective Requirement Snapshot、Technical Landing Trace、Coverage Hard Gate、Upstream Compatibility / Drift、Decision & Design Human Review Summary 和 triggered operational landing。 +- 已协调 `SKILL.md`、runtime、design、project-decisions、stage guides、checklists、human review、decision template、root guidance、README、Usage、CHANGELOG、scenarios 和 tests。 +- 未修改 `templates/requirement-set-README.md`:现有 `Effective Concept Foundation` pointer 已能支持 snapshot 解析,无需增加字段。 +- 未新增 canonical stage、mapping artifact、lifecycle status、executable schema 或目标项目 `.agent-loop/`。 +- 版本仍为 `1.3.0`;root managed blocks 只从同日首次修订提升到 `1.3.0-20260713.1`。 + +## 结构与机械检查 + +- `SKILL.md` YAML:PASS +- `plugin.json` JSON:PASS +- `scripts/check-adr-requirement-model-trace.rb` Ruby 语法:PASS +- `scripts/check-concept-foundation-trace.rb` Ruby 语法:PASS +- 全部 Shell 语法:PASS +- 变更/新增 Markdown fence:PASS +- 本轮 diff 和 untracked 文件尾随空白:PASS +- `git diff --check`:PASS +- `SKILL.md` / `plugin.json` / `README.md` / `Usage.md` 版本一致:PASS(`1.3.0`) +- root managed block revision:PASS(13/13 为 `1.3.0-20260713.1`) +- target-project `.agent-loop/` guard:PASS +- fixture-specific domain/action/technology token 未进入 template/validator:PASS + +## 发布与授权判断 + +当前实现可进入 Human Review,但报告不构成提交或发布授权。 + +| 操作 | 是否授权 | +|---|---| +| commit | 否 | +| push | 否 | +| tag | 否 | +| PR / merge | 否 | +| release / publish | 否 | + +推荐的唯一下一阶段:**Human Review**。 diff --git a/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.2.md b/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.2.md new file mode 100644 index 0000000..ca2b187 --- /dev/null +++ b/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.2.md @@ -0,0 +1,168 @@ +# Agent Loop v1.3.0 全量验证报告(ADR Technical Landing 审查修复) + +日期:2026-07-13 + +分支:`alpha/v1.3.0` + +版本:`1.3.0` + +审计对象:当前未提交工作区;包含 ADR Requirement Model Technical Landing Trace 首轮实现、审查 RED、Human Gate / Scope Inventory / validator 修复、Requirement Product Model `PERM-*` / `EX-*` 增强及协调文档。 + +本报告取代同日 `.1` 报告的当前结论;`.1` 仅保留首轮实现的历史证据。 + +## 总体结论 + +| 项目 | 结果 | +|---|---| +| 总分 | **99 / 100** | +| 等级 | **STRONG** | +| 全部 `tests/*.sh` | `32/32 PASS` | +| ADR focused contract | PASS | +| 对抗性 validator contract | 16/16 场景符合预期 | +| Valid accepted ADR | 8 个 in-scope Requirement Model IDs,5 个 `landed` | +| Valid not-needed ADR | proposed preflight PASS,无伪造产品模型 | +| Concept Foundation trace | 5 Concepts,26 个 model rows | +| Root managed blocks | 13/13 为 `1.3.0-20260713.2` | +| Critical / High / Medium | `0 / 0 / 0` | +| Low | 2 个明确边界,不阻塞 Human Review | + +结论:修复后的 ADR lane 能把 accepted Requirement Product Model 可靠落到 source-wide scope、技术落点、Design Slice 和 Verification,同时保持 PRD 的产品语义所有权与 ADR 的独立 Human Gate。首轮评审发现的 Gate 绕过已具备 RED/GREEN 回归,当前没有 Critical、High 或 Medium 问题。 + +## 六域评分 + +| 审计域 | 权重 | 结果 | 评分 | 主要证据 | +|---|---:|---|---:|---| +| Logic Correctness | 20% | PASS | 99 | `proposed -> preflight -> Human Review -> accepted + evidence -> accepted validation` 顺序一致;source/scope/snapshot/trace 做集合校验;状态、引用、Hard Gate、operational detail 均结构化验证。 | +| Autonomy | 15% | PASS | 99 | Agent 自动解析 effective source、抽取 `REL/PERM/CMD/EVT/FLOW/STATE/PM/EX`、计算 scope/coverage、验证 artifact;只把产品语义、scope 裁决和 ADR acceptance 留给人类。 | +| Project Entry / Evidence Graph + DDD Onboarding | 15% | PASS | 98 | canonical Stage Order 未变;root guidance 只增加 stop/completion/artifact 导航,13 个 managed block revision 同步。 | +| Development / Test Workflow | 20% | PASS | 100 | 保留首轮 RED 与审查 RED;16 场景对抗 contract、focused tests、32/32 全量回归和机械检查全部通过。 | +| Memory | 15% | PASS | 99 | requirement README effective pointer 继续定位产品语义;source-wide inventory 防静默丢失;accepted ADR 不兼容时 supersede 而非改写历史。 | +| Recommendation | 15% | PASS | 99 | 缺模型意义回 Requirements Discussion;缺 coverage 保持 proposed;compatibility 回 Decision & Design;accepted 结论失效进入 Human-gated supersede。 | + +加权分为 99.05,按整数记为 99。 + +## 首轮审查 RED + +首轮实现的机械 suite 虽为 `32/32 PASS`,但临时同源变异证明了 10 个错误接受和 2 个合法输入误拒绝: + +- accepted ADR 无 Human Review Evidence 仍通过; +- placeholder `reason: n/a`、任意 Hard Gate、垃圾 ID、silent scope omission 仍通过; +- 不存在的 accepted decision / Feature Spec、非法 Design Slice status 仍通过; +- triggered detail 缺失和 operational concern inventory 残缺仍通过; +- proposed preflight 和 reasoned `concept-foundation-not-needed` 合法分支被误拒绝。 + +完整 RED 证据见 `agent-loop-v1.3.0-adr-technical-landing-review-red-2026-07-13.md`。因此 `.1` 的 `98/100` 与 `0 High / Medium` 结论已被明确标记为失效历史结论。 + +## GREEN 修复证据 + +### Human Gate 顺序 + +- `scripts/check-adr-requirement-model-trace.rb:176-184`:`proposed` 只做结构预检;`accepted` 必须有 `Decision: accepted`、确认人、ISO 日期和具体证据。 +- `references/runtime.md:123-127`:validator pass 只允许请求评审,不授予 acceptance;reasoned not-needed 使用独立分支。 +- `templates/decision.md:209-236`:完整 Hard Gate、preflight 指引与 Human Review Evidence 写入同一 ADR。 + +### Source-Wide Scope 与模型完整性 + +- `scripts/check-adr-requirement-model-trace.rb:325-342`:source model 集合覆盖 `REL/PERM/CMD/EVT/FLOW/STATE/PM/EX`。 +- `scripts/check-adr-requirement-model-trace.rb:364-413`:Scope Inventory 必须与 source 集合相等,in-scope 必须与 snapshot/trace 集合相等。 +- `scripts/check-concept-foundation-trace.rb:179-190`:Role / Permission Matrix 使用稳定 `PERM-*` ID。 +- `scripts/check-concept-foundation-trace.rb:260-288`:Exception Paths 使用稳定 `EX-*`,并校验 Concept / State / Action 引用。 + +### 外部引用、Hard Gate 与 Operational Landing + +- `scripts/check-adr-requirement-model-trace.rb:145-174`:decision/spec 路径限制在 workspace;existing artifact 校验 status;未来 owner 必须显式 `planned:`。 +- `scripts/check-adr-requirement-model-trace.rb:186-200`:Hard Gate 必须与规范清单精确相等,缺项、未勾选、重复或额外伪检查均拒绝。 +- `scripts/check-adr-requirement-model-trace.rb:202-236`:四项 operational concern 必须完整;trigger 与 detail heading 一致;全部未触发时禁止空 detail section。 +- `scripts/check-adr-requirement-model-trace.rb:276-297`:Design Slice ID、owner、verification 和状态均受验证。 + +## 对抗性回归结果 + +| 类别 | 场景 | 结果 | +|---|---|---| +| 正向 | proposed ADR 在 Human Review 前 structural preflight | PASS | +| 正向 | accepted ADR 带 Human Review Evidence | PASS | +| 反向 | accepted ADR 缺 Human Review Evidence | 正确拒绝 | +| 正向 | 显式 `planned:features//spec.md` owner | PASS | +| 正向 | source model 明确委派给 existing proposed decision | PASS | +| 反向 | placeholder not-applicable reason | 正确拒绝 | +| 反向 | Hard Gate 被任意 checkbox 替换 | 正确拒绝 | +| 反向 | Hard Gate 夹带额外伪检查 | 正确拒绝 | +| 反向 | Accepted Requirement Model IDs 含垃圾 token | 正确拒绝 | +| 反向 | source model 从 snapshot/trace 静默删除 | 正确拒绝 | +| 反向 | 不存在的 accepted decision | 正确拒绝 | +| 反向 | 不存在的 Feature Spec | 正确拒绝 | +| 反向 | 非法 Design Slice status | 正确拒绝 | +| 反向 | triggered concern 缺 detail | 正确拒绝 | +| 反向 | operational concern inventory 不完整 | 正确拒绝 | +| 正向 | reasoned not-needed ADR 无产品模型 | PASS | + +## 已通过的跨文件不变量 + +- `SKILL.md` 保持简洁入口;详细规则位于 runtime/design/project-decisions/stage/checklist/template。 +- Effective Requirement Snapshot、Scope Inventory、Technical Landing Trace 和 preflight 是 Decision & Design 内部方法,不是 canonical stage。 +- ADR status 仍只有 `proposed | accepted | superseded | deprecated`;`review-required` 只表示 dependency availability。 +- effective requirement source 继续拥有 Concept、relationship、permission、action、flow、state、product fact、invariant 与 exception/recovery 产品意义。 +- ADR 不创建或重定义产品语义;含义缺失回 Requirements Discussion / Human Grill。 +- Scope Inventory 位于现有 ADR 内,没有新增默认 mapping artifact 或 YAML/JSON executable schema。 +- `Applicable Decisions` 只证明 awareness,不替代 scope/trace/slice/verification coverage。 +- Operational landing 仍为 trigger-based,不默认引入退款、对账、迁移或任何示例业务能力。 +- accepted ADR 不兼容时创建 superseding ADR,原 accepted decision 保持可审计。 +- root guidance revision 为 `1.3.0-20260713.2`,13/13 managed blocks 一致。 +- 没有创建仓库根 `.agent-loop/`,没有修改 skill version。 +- Delivery Contract、Submit、Commit、PR、Merge、Release、Publish 与 Tag 的 Human Gate 均未被改动或绕过。 + +## 代表性压力场景 + +| 场景 | 结果 | 路由 | +|---|---|---| +| source 中 permission / exception 被从 ADR 静默删除 | PASS | Scope Inventory mismatch,ADR 保持 proposed。 | +| validator 通过后要求自动 accepted | PASS | 停在 Human Review;无 evidence 不能 accepted-mode PASS。 | +| existing-decision / spec 路径伪造 | PASS | 文件或 status 校验失败。 | +| 下游 Feature Spec 尚未创建 | PASS | 只能写显式 canonical `planned:` path,并在 Human Review 可见。 | +| Concept Foundation not-needed 但共享技术约束仍需 ADR | PASS | trace-not-applicable,不伪造产品模型。 | +| requirement effective source 变化 | PASS | `review-required`,停止新的依赖工作并复核/supersede。 | +| Product Brief Source Gate / Delivery Contract / TDD / Active Feature / Pause-Resume-Close-Reopen / multi-phase / Follow-up / Submit / stale-memory / Chat | PASS | 原有 32 项全量 suite 继续通过。 | + +## 当前边界 + +### Low-1:Markdown 证据不能证明人类身份真实性 + +证据:`scripts/check-adr-requirement-model-trace.rb:176-184` 能证明 accepted ADR 记录了完整 Human Review Evidence,但不能从 Markdown 单独证明确认人身份或 UI 交互真实性。 + +处理:运行规则明确要求先展示 Human Review Summary 并等待显式人类接受;validator 只做落盘一致性检查,永不授予 acceptance。该边界不能通过伪造自动化审批来“修复”。 + +### Low-2:显式 planned owner 只证明未来路径清晰 + +证据:`scripts/check-adr-requirement-model-trace.rb:149-174` 对 `planned:` 路径做 canonical path 与 workspace confinement 验证,但按设计不要求尚未创建的 Feature Spec 文件存在。 + +处理:planned owner 必须在 Human Review 可见;Feature Spec 创建后,Feature Spec / Drift / Completion 继续验证 Applicable Decision、Design Slice 与证据。没有 `planned:` 前缀的引用必须立即解析到真实 artifact 和有效 status。 + +## 结构与机械检查 + +- `SKILL.md` YAML:PASS +- `plugin.json` JSON:PASS +- 全部 Ruby 语法:PASS +- 全部 Shell 语法:PASS +- 全仓 Markdown fence balance:PASS +- tracked diff 与 untracked 文件尾随空白:PASS +- `git diff --check`:PASS +- 版本同步:`SKILL.md` / `plugin.json` / `README.md` / `Usage.md` 均为 `1.3.0` +- root managed block revision:13/13 为 `1.3.0-20260713.2` +- canonical Stage Order:未新增 stage +- target-project `.agent-loop/` guard:PASS +- 默认 mapping artifact / executable schema guard:PASS + +## 范围与授权 + +- 未 bump version; +- 未创建 target-project artifacts; +- 未提交、推送、打 tag、创建 PR、merge、release 或 publish; +- 当前唯一下一阶段:Human Review。 + +| 操作 | 是否授权 | +|---|---| +| commit | 否 | +| push | 否 | +| tag | 否 | +| PR / merge | 否 | +| release / publish | 否 | diff --git a/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.3.md b/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.3.md new file mode 100644 index 0000000..59c48cb --- /dev/null +++ b/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.3.md @@ -0,0 +1,192 @@ +# Agent Loop v1.3.0 全量验证报告(Cross-Platform Python Script Runtime) + +日期:2026-07-13 + +分支:`alpha/v1.3.0` + +版本:`1.3.0` + +审计对象:当前未提交工作区中的 Cross-Platform Python Script Runtime 实现及其对发布表面、既有 workflow contract 和 repository validation 的影响。 + +范围排除:共享工作区中另一个 Agent 的 `docs/proposal/v1.4.x/` 删除与 `docs/proposal/v2.0.x/` 新增,以及先前独立任务对 `onboarding-core-flow-completeness.md` 的状态修正。它们未被本轮修改、审查或视为本功能提交范围。 + +## 总体结论 + +| 项目 | 结果 | +|---|---| +| 总分 | **99 / 100** | +| 等级 | **STRONG** | +| 全部 `tests/*.sh` | `32/32 PASS` | +| Native Python suite | `36/36 PASS` | +| Compatibility launchers | 4/4 PASS | +| Current authority legacy recommendations | 0 | +| Critical / High / Medium | `0 / 0 / 0` | +| 平台证据 | `macOS-verified / Windows-test-defined` | + +结论:脚本 runtime 迁移没有改变 canonical stage、routing、Human Gate、artifact ownership 或 lifecycle。四个 checker 的 hard gate 保持等价或更严格,当前权威入口与测试已切换,repository full suite 全绿。Windows execution evidence 仍需 commit/push 后的远端 CI;因此本报告支持进入 Human Review,不支持声称双平台最终验收或直接发布。 + +## 六域评分 + +| 审计域 | 权重 | 结果 | 评分 | 主要证据 | +|---|---:|---|---:|---| +| Logic Correctness | 20% | PASS | 99 | valid/invalid parity、exit code、runtime guard、workspace confinement 与 hard gate regression 全部闭合。 | +| Autonomy | 15% | PASS | 99 | Agent 有明确 canonical entry、capability failure 和唯一后续动作;缺 Python 时 fail closed,不手工模拟。 | +| Project Entry / Evidence Graph + DDD Onboarding | 15% | PASS | 99 | Root checker 与 Onboarding checker 语义保持;root Stage Map 和 onboarding Human Gates 未改变。 | +| Development / Test Workflow | 20% | PASS | 100 | TDD RED/GREEN、36 native tests、32 shell tests、兼容入口、机械检查与 review RED 全部有证据。 | +| Memory | 15% | PASS | 99 | requirement pointer、ADR snapshot/scope/trace、Active Feature、Delivery Phase 与 root guidance ownership 均未改变。 | +| Recommendation | 15% | PASS | 99 | 当前唯一下一阶段为 Human Review;commit/push/Windows CI evidence 仍保持独立 Human Gate。 | + +加权分为 99.2,按整数记为 99。 + +## 当前问题 + +没有 Critical、High 或 Medium 逻辑问题。 + +### 验证边界:Windows execution evidence 待远端 CI + +CI 已定义 `windows-latest` × Python 3.10/3.x,但当前未 commit/push,未产生真实 Windows runner 结果。报告使用 `Windows-test-defined`,不把 matrix configuration 等同于 execution PASS。 + +处理:Human Review 后若另行授权 commit/push,读取远端 CI;任何 Windows failure 返回 Diagnose Failure,不得将 proposal 标记为双平台最终完成。 + +### Review helper 边界 + +`requesting-code-review` helper 建议派发 reviewer subagent,但批准实施计划明确写明 Subagent execution 未授权。本轮没有越权派发,改由主 Agent 对完整 diff、旧规则、proposal contract、路径边界和回归结果进行自审。 + +处理:该降级不改变实现结论;若人类需要独立 reviewer,可单独授权 subagent review。 + +## RED 基线 + +实施前四个旧 checker focused suites 全部 PASS,证明迁移基线有效: + +- Root AGENTS checker:PASS +- Onboarding Core Flow:PASS +- Concept Foundation:PASS +- ADR Technical Landing:PASS + +新增 RED 依次证明: + +- canonical `.py` inventory 缺失; +- cross-platform workflow 缺失; +- unsupported Python guard 缺失; +- deprecated/thin compatibility contract 缺失; +- Windows `py -3` launcher discovery 缺失; +- Root source 可在外部文件存在时逃逸 project root。 + +所有 RED 均先失败于目标缺口,随后才修改生产实现。 + +## GREEN 结果 + +### Native checker contracts + +36 项 `unittest` 覆盖: + +- canonical inventory 与 standard-library-only imports; +- usage/missing input/unsupported runtime exit 2; +- deprecated thin launcher 与 `py -3` discovery; +- valid runs 的只读 SHA-256 snapshot 与 deterministic output; +- Root missing/stale/broken/nested/duplicate/source escape; +- Onboarding planned/deferred/recovery/diagram; +- Concept accepted/not-needed/unconfirmed/ambiguity/permission/trace; +- ADR proposed/accepted/Human Review/scope/trace/owner/operational/path; +- 全部 checker 的 BOM/CRLF 行为; +- macOS/Windows CI matrix contract。 + +### Repository regressions + +全部 32 个 `tests/*.sh` PASS,包括 requirement/chat、Decision & Design、Product Brief Source Gate、Delivery Contract、Project Entry、project-local skill gates、routing/lifecycle、root Stage Map、full-validation guidance 和 monthly compaction proposal contract。 + +## 六域语义审计 + +### Logic Correctness + +- Requirement 仍拥有产品语义;ADR 只消费 accepted meaning 并负责技术落地。 +- Decision / ADR 仍 globally optional、conditionally required,创建与接受保持 Human-gated。 +- ADR proposed/accepted/not-needed、Coverage Hard Gate、Operational Landing 和 Design Slice 结论未弱化。 +- Root source 与 ADR owner/reference 路径均限制在声明 root 内。 +- 没有新增 canonical stage、status 或自动 acceptance。 + +### Autonomy + +- `SKILL.md` 发布四个 canonical `.py` 路径和共享支持层。 +- macOS/Windows 有明确 native invocation;兼容入口不再拥有规则。 +- Python 不可用或版本不足时 exit 2,并报告 capability gap。 +- Agent 不自动安装 Python、不改 PATH、不手工模拟 checker。 + +### Project Entry / Evidence Graph + DDD Onboarding + +- Root AGENTS checker 继续只读,并增加 source confinement。 +- Onboarding valid planned/deferred fixtures 和缺 recovery/detached diagram 反例保持原结论。 +- Project Entry 与 Evidence Graph + DDD Onboarding 的阶段边界、Spec/Tasks gate 和 exactly two Human Gates 未改变。 + +### Development / Test Workflow + +- 行为改动遵循 test-first RED/GREEN。 +- Active shell tests 已调用 canonical Python;旧 Ruby adversarial 文件保留为 compatibility evidence,但不再是 active主路径。 +- full validation、review、drift 与 Submit / Integrate gate 未改变。 +- `requesting-code-review` 的 subagent 默认未覆盖实施计划中的 no-subagent Human Gate。 + +### Memory + +- Requirement lifecycle、Delivery Phase、Feature Mapping、Active Feature 与 project memory 无 schema/routing 修改。 +- Effective Concept Foundation pointer、ADR Effective Requirement Snapshot、scope inventory 与 technical landing trace 保持既有 ownership。 +- 历史 report/proposal 命令保持原样,避免把当前 runtime 反写为过去证据。 + +### Recommendation + +- 当前 authority 只推荐 canonical `.py`。 +- 缺 Python、Windows CI failure 或 checker failure 都有明确 Diagnose/Capability stop。 +- Human Review 之后仍需独立 commit/push 授权;远端 CI 结果不能预先假定。 + +## 代表性压力场景 + +| 场景 | 结果 | +|---|---| +| 复杂 Requirement -> Decision Scan -> ADR -> Feature Spec | PASS;既有 scope/trace/Human Review contract 保持。 | +| 无需 ADR 的简单需求 | PASS;ADR 仍 globally optional。 | +| Product Brief Source Gate | PASS。 | +| Delivery Contract Human Gate | PASS。 | +| TDD RED / 非行为型 N/A | PASS。 | +| Active Feature / Pause / Resume / Close / Reopen | PASS。 | +| 多 Phase `partially-implemented` | PASS。 | +| accepted ADR 与实现 drift | PASS;仍回 Drift Check / Decision & Design。 | +| Follow-up investigate-first | PASS。 | +| Submit / Integrate blocker 与顺序 | PASS。 | +| stale-memory / root guidance / Project Entry | PASS。 | +| 普通 Chat | PASS;不创建 workflow artifacts。 | +| Python 3.9 | 正确 exit 2。 | +| Root source 指向已存在的 `../outside.md` | 正确拒绝。 | +| Windows runner 尚未执行 | 不误报 PASS,保持 CI pending。 | + +## 机械检查 + +- `SKILL.md` YAML:PASS +- `.github/workflows/cross-platform-checkers.yml` YAML:PASS +- repository JSON:PASS +- Python compileall:PASS +- 全部 Shell syntax:PASS +- 全部 Ruby syntax:PASS +- Markdown fence balance:PASS +- `git diff --check`:PASS +- target-project `.agent-loop/` guard:PASS +- version:保持 `1.3.0` + +## 未采纳或降级意见 + +- 未把全部 `tests/*.sh` 改写为 Python:不在批准范围,维护侧 shell tests 继续作为 repository regression。 +- 未改写历史报告和已完成实施计划中的 `.rb/.sh`:历史证据必须保留时间点语义。 +- 未引入 PyYAML 或通用 YAML parser:Python 标准库边界不支持,且本轮四个 checker 只解析 Markdown。 +- 未自动安装 Python 或提供 `--force`:与 fail-closed 决策冲突。 +- 未执行 reviewer subagent:实施计划未授权 subagent。 + +## 发布与授权判断 + +当前可进入 Human Review;在 Windows CI 实际通过前,不建议宣称跨平台最终验收或发布 Feature Monthly Compaction 的 mutation runtime。 + +| 操作 | 是否授权 | +|---|---| +| version bump | 否 | +| commit | 否 | +| push | 否 | +| tag | 否 | +| PR / merge | 否 | +| release / publish | 否 | diff --git a/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.md b/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.md new file mode 100644 index 0000000..85f2427 --- /dev/null +++ b/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-13.md @@ -0,0 +1,120 @@ +# Agent Loop v1.3.0 全量验证报告 + +日期:2026-07-13 + +分支:`alpha/v1.3.0` + +版本:`1.3.0` + +审计对象:当前未提交工作区。范围包括 Concept Foundation / Requirement Product Model 初始实现、2026-07-13 review repair、示例、validator、root guidance 与全部回归测试;历史 proposal 和旧报告不作为 runtime authority。 + +## 总体结论 + +| 项目 | 结果 | +|---|---| +| 总分 | **98 / 100** | +| 等级 | **STRONG** | +| 修复后全部 `tests/*.sh` | **31 / 31 PASS** | +| Concept Foundation 对抗用例 | **8 / 8 被拒绝** | +| Critical / High / Medium | **0 / 0 / 0** | +| Low | **1** | + +结论:Concept Foundation 已能在详细流程、状态和产品数据建模前固定人类确认的概念,并把 Requirement Product Model 作为 Product Brief、Feature Spec 与后续 Decision / ADR 的产品语义输入。归档后语义变化采用 append-only follow-up 或新 requirement set,通过 README effective pointer 切换当前来源,不再要求改写已归档需求。 + +## 六域评分 + +| 审计域 | 权重 | 结果 | 评分 | 主要证据 | +|---|---:|---|---:|---| +| Logic Correctness | 20% | PASS | 99 | `references/runtime.md` 固定 hard stop、Human Gate 和归档后 reopen;validator 拒绝未确认概念、未闭合 trace 和无权限 command actor。 | +| Autonomy | 15% | PASS | 98 | Agent 先查证据、形成 candidate inventory、给出推荐与影响,再一次只问一个 blocker;非触发路径必须给具体理由。 | +| Project Entry / Evidence Graph + DDD Onboarding | 15% | PASS | 98 | canonical stage 未变化;root Stage Map 只增加 Requirements Discussion 导航和 required stop,并使用同版修订号 `1.3.0-20260713`。 | +| Development / Test Workflow | 20% | PASS | 99 | 保留 review RED 证据,新增对抗回归,修复后 focused 与全量测试均 GREEN。 | +| Memory | 15% | PASS | 98 | 历史 requirement source 不可原地重写;README 仅保存 effective/previous pointer,定义和模型留在被引用 source。 | +| Recommendation | 15% | PASS | 98 | `candidate` / `reopened` 均有明确停止与回退路径;`accepted` 前必须展示累计 Human Review Summary。 | + +加权结果为 98.4,按整数记为 98。 + +## RED 基线与 GREEN 修复 + +修复前既有 `31/31` 测试为 GREEN,但人工审查证明 validator 仍可接受下列错误输入,属于真实覆盖缺口: + +- downstream 使用未获人类确认的 Concept; +- 缺少 Concept Candidate Inventory; +- Blocking Ambiguity 仍为 open; +- `concept-foundation-not-needed` 使用 `n/a` 等占位理由; +- Concept-To-Product Traceability 漏掉已定义 model row; +- 重复 Concept ID; +- Product Brief / Feature Spec 没有共同的 effective source; +- Commands / Events 中 actor 对 target 没有 Role / Permission 路径。 + +此外,已归档 requirement 的 `reopened` 状态原本与“source immutable”发生冲突,Concept Foundation acceptance 也没有接入统一 Human Review Summary。 + +GREEN 后: + +- `tests/validate-concept-foundation-trace-adversarial.rb` 的 8 个反例全部被拒绝; +- valid refund example 的 5 个 Concept、20 个 model row 完整通过,并补齐管理员 reconciliation permission; +- Product Brief 与 Feature Spec 必须声明同一个 `Effective Concept Source`; +- reopen 通过 Requirement Conflict Review、append-only follow-up / new requirement set 和 README pointer 完成; +- root managed-block revision 更新后,曾出现 4 个旧测试常量失败,断言同步后全量恢复为 `31/31 PASS`。 + +## 已通过的跨文件不变量 + +- `SKILL.md` 仍是简洁入口;详细规则归属 `references/`。 +- `Concept Foundation` 仍是 Requirements Discussion / Requirement Product Grill 内部方法,不是 canonical stage。 +- 触发后的顺序是 evidence inspection → candidate inventory → recommendation/evidence/impact → one blocking question → cumulative Human Review Summary → human acceptance。 +- `candidate` 或 `reopened` 时不能进入 Business Flow、State Model、Product Data Model、Product Brief 或 Feature Spec。 +- Requirement Product Model 定义产品语义;ADR 只在后续 Decision & Design Human Gate 后选择技术落地,不重定义 Concept identity、lifecycle、relationship、state、invariant 或 fact ownership。 +- 已归档 source 保持不可变;README `Effective Concept Foundation` block 只维护 current/previous source 与状态。 +- Product Brief、Feature Spec、examples、root guidance、stage guides、checklists 和 validation scenarios 使用同一 effective-source 规则。 +- Delivery Contract、TDD、Active Feature、Pause / Resume / Close / Reopen、Submit / Integrate 和发布 Gate 的原有行为继续通过全量回归。 +- 没有创建目标项目根级 `.agent-loop/` artifact,也没有引入 Design Skill、E2E Skill、Jam Kits 或 executable schema。 + +## 代表性压力场景 + +| 场景 | 结果 | 结论 | +|---|---|---| +| “退款完成”同时表示审批完成与到账完成 | PASS | 先分离 Concept 与 lifecycle,再等待人类确认 terminal meaning。 | +| User / Customer / Member / Tenant 被强制合并 | PASS | identity、ownership、membership、permission 改变下游时不可当同义词处理。 | +| 已归档退款定义被新回调证据推翻 | PASS | 保留旧 source,进入 `reopened`,确认后追加 source 并推进 README pointer。 | +| downstream 自行改名或漏引用 model row | PASS | validator 拒绝 detached / incomplete trace。 | +| command actor 没有 target permission | PASS | validator 拒绝缺失 Role / Permission pair。 | +| 简单按钮文案变更 | PASS | 使用具体的 `concept-foundation-not-needed` 理由,不强制建模或 ADR。 | +| ADR 试图重新定义产品概念和 fact owner | PASS | 回到 Requirement / Concept Foundation;技术表示留给后续 Decision & Design。 | +| 既有全流程压力集 | PASS | Product Source、Delivery Contract、TDD、Feature lifecycle、Phase 汇总、ADR drift、Follow-up、Submit、stale-memory 与 Chat 路径均由 `31/31` 回归覆盖。 | + +## 当前问题 + +### Low:结构 validator 不证明业务证据真实性 + +`scripts/check-concept-foundation-trace.rb` 可以验证结构、ID、确认范围、permission、trace 和 effective source,但无法判断人类提供的业务事实是否真实。真实性仍由 evidence inspection、Human Grill 与 Human Gate 负责;将其伪装为可执行 schema 会越过本轮边界,因此不作为阻断项。 + +## 未采纳或降级意见 + +- 未把 Concept Foundation 升为 canonical stage,避免简单需求被强制建模。 +- 未允许直接修改 archived requirement,避免历史语义丢失。 +- 未让 ADR 承担 PRD / Concept 定义,避免产品语义在技术设计中漂移。 +- 未加入 Concept-to-table/store/event/provider mapping、Design Skill、E2E Skill、Jam Kits 或 executable schema;这些不属于当前已批准实现范围。 + +## 机械验证 + +- `SKILL.md` YAML:PASS +- `plugin.json` JSON:PASS +- 全部 Shell syntax:PASS +- 全部 Ruby syntax:PASS +- Markdown fence balance:PASS +- `git diff --check`:PASS +- skill version:保持 `1.3.0`,未 bump +- root managed block:统一为 `block-version:1.3.0-20260713` + +## 发布与授权 + +当前工作区可进入 Human Review;本报告不授权任何外部写操作。 + +| 操作 | 是否授权 | +|---|---| +| commit | 否 | +| push | 否 | +| PR / merge | 否 | +| tag / release / publish | 否 | + +推荐的唯一下一阶段:**Human Review**。确认 Concept Foundation、append-only reopen 和 ADR ownership 边界后,再单独决定是否进入 Submit / Integrate。 diff --git a/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-14.md b/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-14.md new file mode 100644 index 0000000..386cafb --- /dev/null +++ b/docs/reports/agent-loop-v1.3.0-full-validation-2026-07-14.md @@ -0,0 +1,204 @@ +# Agent Loop v1.3.0 发布前全量验证报告 + +验证日期:2026-07-14 +验证分支:`codex/v1.3.0-pre-release-validation` +版本:`1.3.0` +审计对象:v1.3.0 release candidate,行为基线 commit `7253461`,以及隔离 worktree 中仅用于刷新发布证据的文档/contract 变更 +比较基线:`stable-v1.2.4`(`a3c8408`) +平台状态:`macOS-verified / Windows-verified` +远端 CI: + +## 1. 发布判断 + +总分:**99/100** +等级:**STRONG** +最终测试:**98/98 Python tests PASS;34/34 `tests/*.sh` PASS** +当前严重度:**Critical 0 / High 0 / Medium 0 / Low 0** + +v1.3.0 的累计行为已经形成一致、可执行、可恢复的闭环,并已通过正式 Release Human Gate。发布范围包括:Onboarding Core Flow Completeness、Concept Foundation / Requirement Product Model、ADR Requirement Model Technical Landing、Cross-Platform Python Script Runtime、Feature Monthly Archive。 + +Release Human Gate:**已批准**。授权动作:提交并推送 release-evidence commit、推进两个 `v1.3.0` branch、创建并推送 `stable-v1.3.0` tag。为保证 tag 与验证证据绑定,只有精确 release-evidence commit 的 Windows/macOS CI 全部成功后才执行 branch/tag 发布。 + +## 2. 版本范围与提交边界 + +`stable-v1.2.4..7253461` 共五个行为提交: + +| Commit | 能力 | 发布前结论 | +|---|---|---| +| `27f4f18` | Onboarding Core Flow Completeness | Critical flow、slice、terminal、recovery、diagram/evidence trace 完整;仍保持两个 Onboarding Human Gates | +| `e43f745` | Concept Foundation / Requirement Product Model | 复杂需求先固化 Concept IDs,再推导 flow/state/product facts;简单需求有 reasoned not-needed 分支 | +| `81b55c7` | ADR Requirement Model Technical Landing | accepted product meaning 全量进入 scope inventory、landing trace、Design Slice 和 verification;ADR 不重定义 PRD 语义 | +| `e49673c` | Cross-Platform Python Script Runtime | 四个 canonical checker 使用 Python 3.10+ 标准库;旧 Bash/Ruby 入口仅兼容一周期 | +| `7253461` | Feature Monthly Archive | 只读 scan、确定性 plan、Human Gate、事务 apply/post-check/restore、rehydrate 和 locator 闭环 | + +发布证据刷新不改变 runtime、stage order、Human Gate、artifact schema 或 feature behavior。 + +## 3. 六域语义审计 + +| 审计域 | 结果 | 分数 | 已通过的不变量 | +|---|---|---:|---| +| Logic Correctness | PASS | 99 | first-match precedence 唯一;Concept/Requirement/ADR/Feature 所有权不冲突;archive state 不混入 lifecycle;无 Gate 绕过 | +| Autonomy | PASS | 99 | Agent 负责调查、分类和唯一下一阶段;blocked matrix 可执行;helper fallback 不接管 task/memory/gate authority | +| Project Entry / Onboarding | PASS | 99 | remote-entry 优先;stale memory 先恢复;Onboarding 仍只有 Spec Acceptance 与 Tasks Full Execution 两个 Human Gates | +| Development / Test Workflow | PASS | 99 | Plan Gate、Analyze Consistency、TDD、Verify、Review、Drift、Task Done、Completion 和 Follow-up 顺序闭合 | +| Memory | PASS | 99 | 单 Active Feature、Delivery Phase roll-up、Effective Concept pointer、ADR compatibility、archive locator 与 code-reality-wins 一致 | +| Recommendation | PASS | 99 | Chat 不创建产物;阻塞只推荐一个 unblock stage;Submit/Close/Release/Publish 分别保留 Human Gate | + +加权结果为 99。没有未解释的 Critical、High 或 Medium。 + +## 4. RED 基线 + +### 4.1 可执行与机械基线 + +在干净 worktree、精确 HEAD `7253461` 上先运行既有验证: + +- `python3 -m unittest discover -s tests -p 'test_*.py' -v`:98/98 PASS; +- `for test_file in tests/*.sh; do bash "$test_file"; done`:33/33 PASS; +- YAML、JSON、Python compile、Shell/Ruby syntax、Markdown fence、`git diff --check`:PASS。 + +因此本轮没有把既有失败混入 release 修复。 + +### 4.2 发布证据状态 RED + +语义审计发现一个 Medium 级发布证据漂移,不是 runtime 逻辑缺陷:代码、commit/push 和 Windows/macOS CI 已完成,但五组 Proposal、Archive 实施计划和 7 月 14 日报告仍声明 `Windows-test-defined`、未 commit/push 或等待旧 Human Review。 + +新增 `tests/validate-v1.3.0-release-readiness.sh`,并先更新 Archive proposal contract 的期望状态。修复前两个 contract 都真实退出 1: + +```text +FAIL: docs/proposal/v1.3.x/onboarding-core-flow-completeness.md missing release evidence: 状态:已实现并通过发布前全量验证;待 v1.3.0 Release Human Gate +FAIL: feature monthly archive proposal missing required text: 状态:已实现并通过发布前全量验证;待 v1.3.0 Release Human Gate +release-evidence exit=1 archive-proposal exit=1 +``` + +该 RED 证明最终 GREEN 不是通过保留旧状态或弱化断言获得。 + +## 5. GREEN 结果 + +发布证据修复包括: + +- 五组 v1.3.0 Proposal 统一进入 `待 v1.3.0 Release Human Gate`; +- Cross-Platform Runtime 与 Feature Monthly Archive 记录具体 commit `7253461` 和 workflow run `29320389912`; +- Archive 实施计划记录 implementation/review/commit/push 已完成,同时保留 tag/release/publish 未授权; +- Archive focused report 更新为 Windows/macOS verified; +- 本报告从单一 Archive 审计扩展为 `stable-v1.2.4..7253461` 的累计 release-candidate 审计; +- 新增持久 release-evidence contract,防止同一版本再次退回旧发布状态。 + +最终复跑结果: + +```text +python3 -m unittest discover -s tests -p 'test_*.py' -v +Ran 98 tests +OK + +for test_file in tests/*.sh; do bash "$test_file"; done +34/34 PASS +``` + +## 6. 关键压力场景 + +### Requirement、Product 与 ADR + +- 复杂 Requirement:Concept Foundation accepted 后才能推导 Requirement Product Model;Design Readiness 触发 Decision & Design;每个 accepted model ID 必须有 scope/landing/Design Slice/verification disposition。 +- 简单 Requirement:使用有具体理由的 `concept-foundation-not-needed`,不伪造概念、状态机或 ADR。 +- Product Brief / Feature Spec:必须引用有效 requirement source 或明确 feature-start evidence,不重新定义 accepted Concept/Product Model。 +- accepted ADR 与上游或实现 drift:进入 compatibility review / Decision Scan / Drift Check;旧 accepted ADR 被 supersede,不原地改写。 + +### Human Gate、执行与完成 + +- Delivery Contract:不是默认产物;创建、接受和 breaking change 都必须单独 Human Gate。 +- 行为变更:不能因人类催促跳过 TDD RED;非行为工作只允许带理由的 `not-applicable`。 +- Task 完成:测试通过或 review approval 单独都不能标记 `done`;必须满足 evidence、review、drift 和 Task Done Gate。 +- Submit / Release:`commit this`、`publish release` 只进入 Submit / Integrate 检查;真正 commit、tag、release、publish 仍需后续明确确认。 + +### 状态、记忆与恢复 + +- 同一时间只允许一个 Active Feature;新 feature 前必须 close 或 pause 当前 feature并记录 resume point。 +- 多 Delivery Phase:一部分实现时 requirement 只能是 `partially-implemented`,不能由单个 feature 错误关闭整体。 +- stale-memory / outside-loop:先 Reconcile / Recovery,code reality 是 agent-maintained facts 的当前基线,human requirement source 保持不可变。 +- Feature Follow-up:先 investigate owner;archived owner 必须先独立 rehydrate Gate,再决定 `closed -> active`。 + +### Feature Monthly Archive + +- stale plan、flat/month collision、symlink/path escape、broken/ambiguous reference、stranded transaction 全部 fail closed; +- journal scope、backup bytes、post-crash human edits在任何恢复写入前校验; +- scan/check 只读,apply 只执行 Human-reviewed SHA-256 plan; +- 归档只移动完整目录并维护 locator,不删除、压缩、summary-only 或创建 `historical/`。 + +### Project Entry 与 Onboarding + +- remote-entry 在 existing-project 之前;普通 Chat 不创建 requirement/feature/onboarding 产物; +- Project Entry 只建立安全接管记忆,不偷跑 Evidence-Graph onboarding; +- Critical/Important flow 缺 slice、terminal、recovery 或 detached diagram 时 Completeness Hard Gate 失败; +- Completeness 是 Agent quality gate,不增加第三个 Onboarding Human Gate。 + +## 7. 关键跨文件不变量 + +- `SKILL.md` 保持简洁 controller;`references/design.md` 与 `references/runtime.md` 分别拥有模型/约束和可执行路由。 +- root `templates/root-AGENTS.md` 只做导航;13 个 managed block 均为 `1.3.0-20260714.1`。 +- runtime 与 root Stage Map 的 first-match 顺序一致:Safety Stop -> Remote Discovery -> Memory Recovery -> Feature Archive Maintenance -> Active Feature Guard -> Blocker Resolution -> Intent Routing -> Normal Stage Continuation。 +- Requirement 拥有产品目标/语义;ADR 是可选技术落地桥梁;Feature Spec 消费已接受输入。 +- Delivery Contract、Task Done、Submit、Close、Release、Publish 和 Tag Gate 没有被 Auto Mode、external skill 或 subagent 绕过。 +- `features/archive.md` 只是 locator/ledger,不替代 requirement、ADR、feature artifacts 或 lifecycle authority。 + +## 8. 版本、平台与机械证据 + +版本同步: + +- `SKILL.md`: `Version: 1.3.0`; +- `plugin.json`: `1.3.0`; +- `README.md`: Current version `1.3.0`; +- `Usage.md`: `1.3.0`; +- `CHANGELOG.md`: `1.3.0` section; +- `templates/root-AGENTS.md`: 13/13 managed blocks 使用 `1.3.0-20260714.1`。 + +跨平台 CI: + +| Job | 结果 | +|---|---| +| `windows-latest / Python 3.10` | success | +| `windows-latest / Python 3.x` | success | +| `macos-latest / Python 3.10` | success | +| `macos-latest / Python 3.x` | success | + +四个 job 都属于 commit `7253461` 的 run `29320389912`。 + +机械检查: + +| 检查 | 结果 | +|---|---| +| `SKILL.md` + repository YAML parse | PASS(2 YAML files) | +| repository JSON parse | PASS(1 file) | +| `python3 -m compileall -q scripts tests` | PASS | +| Shell `bash -n` | PASS(35 files) | +| Ruby `ruby -c` | PASS(5 files) | +| Markdown fence balance | PASS(199 files) | +| `git diff --check` | PASS | + +## 9. Review 与未采纳意见 + +主 Agent 逐项完成 Spec/Standards Review、六域交叉取证和 release diff 审计。当前协作约束未授权 reviewer subagent,因此没有把主 Agent review 冒充独立 reviewer 证据;这一限制不构成 runtime 缺陷。 + +未采纳: + +- 不新增 canonical stage、Human Gate、Delivery Contract 默认产物或 executable schema; +- 不为发布证据更新修改 skill version 或 root block revision; +- 不把历史 `.1` 等报告改写成当前权威;其中已标记 superseded 的报告继续保留 RED/审查历史; +- 不在发布前验证中创建 tag、GitHub/GitLab Release 或执行 publish。 + +## 10. 工作区隔离与授权 + +验证 worktree:`/Users/shaodowyd/.config/superpowers/worktrees/agent-loop/v1.3.0-pre-release-validation` + +主工作区原有以下无关改动未被读取为发布内容、未被复制、暂存或修改: + +- `docs/proposal/v1.3.x/onboarding-core-flow-completeness.md` 的主工作区未提交版本; +- 删除中的两个 v1.4 proposal; +- `docs/proposal/v2.0.x/`。 + +行为提交 `7253461` 已推送到 `origin/alpha/v1.3.0` 与 `ai-factory/alpha/v1.3.0`。两个远端 `v1.3.0` branch 在审计开始时也指向 `7253461`。本轮 release-evidence 变更尚未 commit 或 push。 + +## 11. 下一阶段 + +当前推荐下一阶段:**执行已批准的 v1.3.0 正式发布**。 + +顺序固定为:提交 release-evidence commit;推送到两个 `alpha/v1.3.0`;等待该精确 commit 的 Windows/macOS CI;CI 全绿后推进两个 `v1.3.0` branch,并创建、推送 `stable-v1.3.0` tag。任何 CI 失败都必须停止 branch/tag 发布。 diff --git a/docs/reports/agent-loop-v1.3.0-onboarding-core-flow-red-baseline-2026-07-11.md b/docs/reports/agent-loop-v1.3.0-onboarding-core-flow-red-baseline-2026-07-11.md new file mode 100644 index 0000000..ed3af7d --- /dev/null +++ b/docs/reports/agent-loop-v1.3.0-onboarding-core-flow-red-baseline-2026-07-11.md @@ -0,0 +1,185 @@ +# Agent Loop v1.3.0 Onboarding Core Flow RED Baseline + +**日期:** 2026-07-11 +**分支:** `alpha/v1.3.0` +**审计对象:** 修复前当前工作区中的已发布 Agent Loop 规则;proposal 和新 RED 测试不作为下游 Agent 输入 +**方法:** `writing-skills` + `test-driven-development`,三个只读下游 Agent 压力场景,仓库机械基线 + +## 调度授权 + +人类于 2026-07-11 明确授权三个只读压力 Agent。 + +边界: + +- 只读当前 `SKILL.md`、`references/` 和 `templates/onboarding-db/`; +- 不读取本轮 proposal、实施计划或新 RED test; +- 不修改文件、不提交; +- 每个 Agent 只执行一个场景,返回遗漏、合理化理由和 PASS/FAIL 后停止; +- 主 Agent 负责复核、归并和后续修改。 + +## 机械 RED + +新增专项测试: + +```text +tests/validate-onboarding-core-flow-completeness.sh +``` + +修复前运行结果: + +```text +missing text in SKILL.md: Core Flow Inventory +``` + +全部仓库测试基线: + +```text +baseline tests: passed=29 failed=1 +``` + +唯一失败是本轮新增 RED 测试;原有 29 个测试全部通过。这证明当前缺口能在既有机械测试全绿时继续存在。 + +## RED-1:通过缩窄 Flow 边界遗漏业务闭环 + +### 场景 + +虚拟订单支付包含: + +- `CreateOrder` 与 `orders=PENDING`; +- Payment Provider 同步 `PROCESSING`; +- webhook `SUCCESS/FAILED`; +- `provider_event_id` 幂等; +- retry topic、DLQ; +- `PAYMENT_UNKNOWN` 与 `ReconcileJob`; +- `OrderPaidEvent` 补发; +- 30 分钟未支付取消; +- `REFUND_PENDING -> REFUNDED`。 + +压力:今晚交付,先写创建订单主流程,三张图齐就算新人可读。 + +### Agent 行为 + +Agent 能发现六个候选流程,但把正式 Flow Plan 缩窄为“创建订单与发起支付”,止于同步 `PROCESSING`。webhook、retry/DLQ、reconciliation、cancel 和 refund 被拆为后续 topic。 + +### 遗漏 + +- 真正成功/失败终态; +- webhook 和重复 callback; +- retry topic、DLQ; +- `PAYMENT_UNKNOWN` 进入和恢复; +- 对账补写与补发事件; +- webhook/reconcile/cancel 并发状态所有权; +- DB 状态与事件发布的事务/幂等关系; +- 取消和退款生命周期。 + +### 合理化原话 + +> “当前 batch”只交创建订单同步主流程,其他作为 “next batch”。 + +> webhook、retry、reconcile 被归为 “jobs / async / callback”,不是当前 Flow。 + +> refund 是 “单独入口”,因此不是创建订单主流程。 + +> 将同步返回 `PROCESSING` 解释为当前文档的“成功结果”,不继续追踪业务终态。 + +### 规则缺口 + +旧规则能要求候选、async inventory、失败路径和三类图,但没有 flow closure / terminal-state completeness gate。它不能阻止 Agent 把关键闭环拆成可延期 topic,也不能阻止窄范围 topic 独立得到 `newcomer-ready`。 + +**结论:FAIL** + +## RED-2:三图齐全但图文与真实闭环脱节 + +### 场景 + +待审订单支付文档有架构图、状态图和时序图,章节齐全,但只覆盖 API 到 PaymentClient 的同步成功路径。作者自评 4/5,准备标记 `newcomer-ready`。 + +### Agent 行为 + +审查 Agent 能依据当前规则拒绝该文档,因为订单类流程应包含幂等、失败、重试、补偿、对账和细粒度证据。 + +但 Agent 同时确认当前规则缺少不可平均的追踪契约,作者仍能通过缩小 use case 定义、主观评分和较粗的 batch review 列规避。 + +### 容易被主观给高分的维度 + +- Required diagram set present; +- Architecture diagram clarity; +- 同步 Timeline clarity; +- 被缩窄后的 Use case completeness; +- 被缩窄后的 State transition clarity; +- 只有目录路径的 Code evidence; +- Newcomer readability; +- 粗粒度 Overall。 + +### 合理化原话 + +> “三种必填图都已经有了,规则要求的是图存在,不是每张图都画全量异常。” + +> “核心流程就是下单到支付调用成功;webhook、DLQ、对账属于异步支线,后续 focused update 再补。” + +> “状态图和时序图对已描述范围是清楚的,所以可以给 4/5。” + +> “每节都有代码路径,symbol 和调用方向只是更高质量要求,不影响 newcomer-ready。” + +> “新人先掌握 happy path 更容易,异常细节一次放进去反而影响可读性。” + +### 规则缺口 + +缺少从 Evidence Graph 到正文、图、symbol/config、调用/数据方向和 coverage 状态的逐项追踪。关键恢复 slice 缺失没有独立 hard fail,可以被总体评分掩盖。 + +**结论:FAIL** + +## RED-3:Focused Scope 与无意义状态图 + +### 场景 + +虚拟项目有 8 个服务和 4 条核心流程。余额扣减涉及 transaction、Redis lock、outbox、Kafka、幂等、补偿和对账;API key 涉及完整 credential 生命周期;gateway 涉及 route/header/auth/rate-limit/timeout/retry/logs。 + +压力:今天完成 onboarding,不新增 Gate,每个 Flow 画三张图即可。 + +### Agent 行为 + +Agent 可以把范围解释为 4 篇核心 Flow 文档,把 8 个服务的 module、非核心 async、infra、deploy 和 change-guide 留在 discovered/planned/deferred。范围内形式合规,但整个项目仍不可接手。 + +### 合理化空隙 + +- whole project / focused area 没有默认范围判定; +- information architecture 是 planning target,不是全量硬清单; +- Completion Gate 要求记录 discovered topics,但不要求全部达到新人可接手; +- Deferred Topics 可以长期容纳核心相关内容; +- API key 完整安全清单只明确绑定 module doc,可通过不写 module doc 绕开; +- 三张图存在后,复杂机制图可以被省略。 + +### 无意义图风险 + +旧规则默认要求所有 content-bearing 正式文档至少包含架构/边界图和 ASCII 状态图。glossary、code organization、dependencies、config、observability、environments、runbooks 等主题可能没有自然状态机,Agent 会被诱导生成形式化但无信息量的状态图。 + +### Gate 结论 + +不应新增第三个 Gate。Onboarding Spec Acceptance 和 Onboarding Tasks Full Execution Gate 是唯二 onboarding Human Gate;batch 不是 Gate。 + +**结论:FAIL** + +## 共同根因 + +三个场景共同确认: + +1. 当前规则约束“被选中的文档怎么写”,没有可靠约束“核心流程是否闭合到业务终态”。 +2. async、callback、job、compensation 和 reconciliation 可以被重新分类为后续 topic,从核心流程中移走。 +3. topic 级平均分不能表达关键 slice 的不可缺失性。 +4. Coverage Matrix 与 Batch Review 的评分粒度不一致。 +5. 所有正式文档默认状态图会产生无意义图。 + +## GREEN 设计约束 + +本轮最小修复应: + +- 只对 `critical` / `important` 核心流程要求 Flow/Slice/Diagram/Evidence 追踪; +- 要求核心流程闭合到成功、失败、取消、未知或人工处理终态; +- 不允许通过把 callback/retry/reconciliation 改名为后续 topic 绕开关键 slice; +- 用 Completeness Hard Gate 阻止关键 slice 被平均分掩盖; +- supporting flow 只有承担核心状态、副作用或恢复责任时才升级完整追踪; +- 非 Flow 内容文档按相关性选图,不为 stateless topic 编造状态图; +- 保留恰好两个 onboarding Human Gate; +- 以 valid reference + targeted invalid fixture 验证真实 artifact trace; +- 明确 validator 验证结构追踪,不宣称自动证明业务事实正确。 diff --git a/docs/reports/onboarding-core-flow-completeness-feature-validation-2026-07-11.md b/docs/reports/onboarding-core-flow-completeness-feature-validation-2026-07-11.md new file mode 100644 index 0000000..432da8b --- /dev/null +++ b/docs/reports/onboarding-core-flow-completeness-feature-validation-2026-07-11.md @@ -0,0 +1,158 @@ +# Onboarding Core Flow Completeness 单功能验证报告 + +**日期:** 2026-07-11 +**分支:** `alpha/v1.3.0` +**版本:** 1.3.0 +**审计对象:** 当前工作区中的 Onboarding Core Flow Completeness 功能改动 +**验证方法:** `docs/maintenance/feature-validation-method.md` +**全量验证状态:** 未在最终 Scope Lock 后执行;人类明确要求默认只做单功能全量验证,全仓库/全技能验证不计入本报告得分 + +## 结论 + +| 项目 | 结果 | +|---|---| +| 总分 | 96 / 100 | +| 等级 | `STRONG` | +| Critical | 0 | +| High | 0 | +| Medium | 0 | +| Low | 2 | +| GREEN 压力场景 | 3 / 3 PASS | +| 功能边界测试 | 7 / 7 PASS | +| 提交状态 | 未 commit、未 push、未 tag | + +功能在已确认范围内形成闭环,可以进入提交前的人类审查。该结论只覆盖 onboarding-core-flow 功能,不代表整个 Agent Loop 已完成全量验证或发布验收。 + +## Scope Lock + +### 目标行为 + +- Evidence Graph 在正式文档前发现 `critical` / `important` 核心流程、业务终态、变体、owner、副作用、恢复责任和 Evidence Chain。 +- 核心流程不能停在 `accepted` / `pending` / `processing` 等非终态响应。 +- callback、consumer、retry、DLQ、compensation、reconciliation 和 job 只要承担核心状态、副作用或恢复责任,就不能被拆成 future topic 来逃避闭环。 +- critical/important flow 使用 Flow Slice Coverage,把关键行为映射到代码 evidence、Diagram IDs 和正文 section。 +- Completeness Hard Gate 先于质量评分;missing/blocked critical slice 不能被平均掉。 +- 核心流程默认使用 Overview/Boundary、Timeline/Sequence、ASCII State Machine;额外图由真实 complexity signal 触发。 +- supporting flow 保持轻量,除非承担核心状态、副作用或恢复责任。 +- stateless glossary、静态配置清单和纯索引不强制状态图。 +- 仍只有 Onboarding Spec Acceptance 和 Onboarding Tasks Full Execution Gate 两个人类 Gate。 + +### 非目标 + +- 不改变 Project Entry Scan 边界或 onboarding 阶段顺序。 +- 不增加 batch、diagram、slice 或 newcomer-ready Human Gate。 +- 不以固定文件数、图数量或文档长度证明完整。 +- validator 不宣称自动证明业务事实正确,只验证 artifact trace 和明确的结构不变量。 +- 本报告不审计 Agent Loop 的无关 feature、submit、memory、ADR、contracts 或 project-local skills。 + +## 五域评分 + +| Domain | Weight | Score | Result | 扣分原因 | +|---|---:|---:|---|---| +| Requirement And Scope Fidelity | 15 | 15 | PASS | 无 | +| Logic, State, And Human Gates | 30 | 29 | PASS | critical/supporting 初始分类仍需要 evidence-based Agent judgment | +| Cross-Surface Consistency | 20 | 20 | PASS | 无 | +| Pressure Resistance | 25 | 24 | PASS | 合理 focused scope 与错误缩窄仍需语义审查区分 | +| Evidence And Maintainability | 10 | 8 | PASS | validator 只能证明 trace 结构;全仓库验证未获授权且不计入功能得分 | +| **Total** | **100** | **96** | **STRONG** | 0 Critical / 0 High / 0 Medium | + +## RED Baseline + +详细原话和证据见: + +```text +docs/reports/agent-loop-v1.3.0-onboarding-core-flow-red-baseline-2026-07-11.md +``` + +修复前机械基线:原有测试通过,但新专项 contract 因缺少 `Core Flow Inventory` 失败。 + +三个只读 RED 场景全部 FAIL: + +| Scenario | RED 行为 | 根因 | +|---|---|---| +| RED-1 | 把订单支付缩窄到同步 `PROCESSING`,callback/retry/reconcile/cancel 延期 | 没有业务终态闭合和关键 slice hard gate | +| RED-2 | 三图和章节齐全即可主观自评 4/5 | 缺少不可平均的 evidence/diagram/section trace | +| RED-3 | focused scope 可冒充 whole-project 完成;所有正式文档被迫画状态图 | scope claim 和 diagram relevance 规则不足 | + +## GREEN 压力复测 + +| Scenario | Result | 修复后行为 | +|---|---|---| +| GREEN-1:同步 `PROCESSING` 截断 | PASS | `PROCESSING` 明确为非终态;webhook、retry、DLQ、reconcile、cancel 保留在同一核心闭环;缺失 slice 直接 FAIL | +| GREEN-2:三图齐但图文脱节 | PASS | Hard Gate 先于评分;目录级 evidence、缺 Diagram/Section 映射和缺 recovery slice 均不能 newcomer-ready | +| GREEN-3:8 服务复杂项目时间压力 | PASS | focused scope 不得冒充全项目;transaction/async/recovery/decision/runtime 图按信号触发;stateless 文档不编造状态图 | + +三个 Agent 均只读修复后的发布规则,没有把 proposal 当运行时权威,也没有修改文件。 + +## Review Findings And Closure + +最终只读 reviewer 首轮给出 `NOT READY`:1 High、4 Medium、1 Low。主 Agent 逐项验证并通过专项 RED/GREEN 关闭: + +| Severity | Finding | Closure | +|---|---|---| +| High | validator 只验证任意 `D-*` / `§N` / 路径字符串,缺条件图和图文脱节仍可能通过 | 新增 detached-trace invalid fixture;validator 验证 Diagram 定义、真实 section、symbol/config evidence、call/data direction、placeholder、Hard Gate 顺序 | +| Medium | validator 错误拒绝合法 deferred core flow | 新增 valid-deferred fixture;planned 与 deferred 分支分别验证,deferred 要求 impact/missing/next | +| Medium | supporting flow 仍被 Flow 模板强制完整图组 | flow/spec/checklist 明确 critical/important 与 supporting 边界,supporting 承担核心责任时才升级 | +| Medium | Flow 模板缺 ERD、独立 runtime 和 troubleshooting 图 | 补齐 ERD / Model Relationship、Runtime / Deployment Topology、Observability / Troubleshooting Map 及自检 | +| Medium | reference 与 coverage/batch 评分维度漂移 | 统一 Failure / recovery、Troubleshooting、Evidence granularity、Consistency / gateway risk 等维度 | +| Low | `Review Gate`、`Human Review Status` 可能暗示额外 Gate | 改为 `Agent Review Check` 和 `Optional Human Review Notes` | + +关闭这些 finding 后,专项 contract、相关回归和结构检查重新运行通过。 + +## Feature-Scoped Test Evidence + +实际执行并通过: + +```text +PASS tests/validate-onboarding-core-flow-completeness.sh +PASS tests/validate-evidence-graph-ddd-onboarding.sh +PASS tests/validate-project-entry-onboarding-reset.sh +PASS tests/validate-v1.2.4-state-lifecycle-repairs.sh +PASS tests/validate-v1.2.4-postfix-pressure-repairs.sh +PASS tests/validate-v1.2.3-routing-fixes.sh +PASS tests/validate-v1.2.3-medium-consistency.sh +feature tests: passed=7 failed=0 +``` + +结构检查: + +```text +SKILL YAML: PASS +plugin JSON: PASS +Ruby validator syntax: PASS +Focused Shell syntax: PASS +Changed Markdown fence balance: PASS +git diff --check: PASS +``` + +Artifact validator evidence: + +```text +valid reference: PASS (1 planned, 0 deferred) +valid deferred fixture: PASS (0 planned, 1 deferred) +invalid missing recovery slice: expected FAIL +invalid detached diagram/section/evidence trace: expected FAIL +``` + +## Unexecuted Validation + +- 最终 Scope Lock 后未运行 `for test_file in tests/*.sh` 全仓库测试。 +- 未运行 `docs/maintenance/full-validation-method.md` 的六域全技能验证。 +- 人类已明确规定:默认执行单功能全量验证;全仓库/全技能验证只有在明确允许后才执行。 +- 本轮早期在该规则提出前曾运行过仓库测试基线;该历史输出不计入本报告得分,也不作为已授权的 full validation。 + +## Remaining Risks + +### Low-1: Core-flow classification remains judgment-based + +`critical` / `important` / `supporting` 初始分类仍依赖 Agent 阅读业务结果、state owner、副作用和恢复证据。规则通过“承担核心责任必须升级”缩小了空隙,但无法完全机械替代语义判断。 + +### Low-2: Validator proves trace, not truth + +validator 可以拒绝缺 Slice、缺 Diagram 定义、悬空 section、泛化 evidence 和错误 Hard Gate 顺序,但不能证明代码路径陈述与真实业务绝对一致。真实正确性仍需 Evidence Graph 调查和 reviewer 审计。 + +## Submission Judgment + +在 onboarding-core-flow 单功能范围内,当前结果为 `STRONG`,可进入提交前人类审查。 + +当前没有 commit、push、tag、release 或 publish 授权。全仓库/全技能验证也没有授权,不应把本报告描述为 release acceptance。 diff --git a/examples/ai-meeting-minutes-backend/onboarding-db/03-flows/order-payment.md b/examples/ai-meeting-minutes-backend/onboarding-db/03-flows/order-payment.md new file mode 100644 index 0000000..f26f8af --- /dev/null +++ b/examples/ai-meeting-minutes-backend/onboarding-db/03-flows/order-payment.md @@ -0,0 +1,154 @@ +# Flow: 订单支付闭环 + +## 1. Flow Identity And Outcomes + +- Flow ID: CF-ORDER-PAYMENT +- Criticality: critical +- Business outcome: 订单从创建进入可确认的支付业务终态。 +- Success terminal: `PAID` with `OrderPaidEvent` recorded for delivery. +- Failure terminals: `FAILED`, `CANCELLED`, `PAYMENT_UNKNOWN`, `MANUAL_REVIEW`. + +## 2. Flow Slice Coverage + +| Flow ID | Slice ID | Path Kind | Action | Transition / Terminal | Evidence | Diagram IDs | Document Section | Coverage Status | +|---|---|---|---|---|---|---|---|---| +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S01 | main | create order and persist pending | new -> PENDING | `internal/order/handler.go#Create` | D-BOUNDARY, D-SEQUENCE | §3 | covered | +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S02 | branch | provider accepts asynchronous payment | PENDING -> PROCESSING | `internal/payment/client.go#CreatePayment` | D-SEQUENCE, D-STATE | §4 | covered | +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S03 | main | webhook applies provider success/failure | PROCESSING -> PAID/FAILED | `internal/payment/webhook.go#Handle` | D-SEQUENCE, D-STATE, D-ASYNC | §5 | covered | +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S04 | branch | duplicate webhook returns existing result | terminal unchanged | `internal/payment/webhook.go#provider_event_id` | D-STATE, D-ASYNC | §6 | covered | +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S05 | failure | callback retry exhausts into DLQ | PROCESSING -> PAYMENT_UNKNOWN | `config/kafka.yaml#payment_retry` | D-RECOVERY, D-ASYNC | §7 | covered | +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S06 | recovery | reconciler queries provider and repairs state/event | PAYMENT_UNKNOWN -> PAID/MANUAL_REVIEW | `internal/order/reconcile.go#Run` | D-SEQUENCE, D-RECOVERY | §8 | covered | +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S07 | branch | conditional cancel competes with late webhook | PROCESSING -> CANCELLED or PAID | `internal/order/cancel.go#Run` | D-STATE, D-RECOVERY | §9 | covered | + +## 3. Core Flow Overview / Boundary — D-BOUNDARY + +```mermaid +flowchart LR + API[Order API] --> Order[Order Service] + Order --> DB[(orders/outbox)] + Order --> Provider[Payment Provider] + Provider --> Webhook[Webhook Consumer] + Webhook --> DB + Webhook --> MQ[OrderPaidEvent] + Retry[Retry/DLQ] --> Reconcile[ReconcileJob] + Reconcile --> Provider + Reconcile --> DB +``` + +Covered Slice IDs: S01-S07. `OrderService` owns order truth; provider results are evidence, not direct DB ownership. + +## 4. ASCII State Machine — D-STATE + +```text +[PENDING] -> [PROCESSING] -> [PAID] + | ^ + +-> [FAILED] | + +-> [PAYMENT_UNKNOWN] -> reconcile + +-> [CANCELLED] +late success may move PROCESSING/PAYMENT_UNKNOWN to PAID only through conditional update +terminal PAID/FAILED/CANCELLED cannot be overwritten by duplicate callbacks +``` + +Covered Slice IDs: S02-S07. The conditional update and provider-event idempotency protect terminal ownership. + +## 5. Timeline / Sequence — D-SEQUENCE + +```mermaid +sequenceDiagram + participant API + participant Order + participant Provider + participant Webhook + participant DB + participant Reconcile + API->>Order: CreateOrder + Order->>DB: PENDING + outbox + Order->>Provider: CreatePayment + Provider-->>Order: PROCESSING + Provider->>Webhook: SUCCESS/FAILED callback + Webhook->>DB: idempotent terminal update + Reconcile->>Provider: query unknown payment + Reconcile->>DB: repair state and event +``` + +Covered Slice IDs: S01-S06. `PROCESSING` is not a business terminal; webhook or reconciliation closes the flow. + +## 6. Failure Recovery Timeline — D-RECOVERY + +```text +T1 callback delivery fails +T2 retry topic retries with provider_event_id +T3 retries exhausted -> DLQ + PAYMENT_UNKNOWN +T4 ReconcileJob queries Provider +T5 conditional update -> PAID or MANUAL_REVIEW +T6 missing OrderPaidEvent is republished from durable state +``` + +Covered Slice IDs: S05-S07. + +## 7. Async Message Topology — D-ASYNC + +```text +Provider webhook -> consumer -> orders/outbox -> OrderPaidEvent + | + +-> retry topic -> DLQ -> ReconcileJob +``` + +Covered Slice IDs: S03-S06. Retry and DLQ are part of the payment closure, not an unrelated future topic. + +## 8. 阶段与数据流转 + +| Stage | Owner | Input | Action | State Read / Written | Output | Evidence | +|---|---|---|---|---|---|---| +| create | OrderService | CreateOrder command | persist order and outbox intent | write `orders=PENDING` | order id | `internal/order/handler.go#Create` | +| provider request | Payment Adapter | order id / amount | create provider payment | read order, write provider reference | PROCESSING | `internal/payment/client.go#CreatePayment` | +| callback | Webhook Consumer | provider event | idempotent conditional terminal update | read event id, write PAID/FAILED | outbox event | `internal/payment/webhook.go#Handle` | +| recovery | ReconcileJob | PAYMENT_UNKNOWN order | query provider and repair state/event | write PAID/MANUAL_REVIEW | reconciliation record | `internal/order/reconcile.go#Run` | + +## 9. 状态变化 + +| State Object | Before | Trigger | After | Persistence / Guard | Evidence | +|---|---|---|---|---|---| +| order | PENDING | provider accepts | PROCESSING | conditional version update | `internal/payment/client.go#CreatePayment` | +| order | PROCESSING | unique SUCCESS callback | PAID | `provider_event_id` + terminal guard | `internal/payment/webhook.go#Handle` | +| order | PROCESSING | retries exhausted | PAYMENT_UNKNOWN | retry metadata and DLQ record | `config/kafka.yaml#payment_retry` | +| order | PAYMENT_UNKNOWN | reconcile success | PAID | conditional terminal guard | `internal/order/reconcile.go#Run` | +| order | PROCESSING | cancel deadline | CANCELLED | update fails if late callback already wrote PAID | `internal/order/cancel.go#Run` | + +## 10. 示例 + +Example trace: `order_id=ord-42`, `provider_event_id=evt-9`. The provider returns PROCESSING, the first callback delivery fails, retry succeeds, and the webhook writes PAID plus the durable paid event. This example is constructed from the synthetic evidence paths listed above. + +## 11. 失败路径 + +| Failure Point | Symptom | Cause | Retry / Compensation / Degradation | User Visible? | Evidence | +|---|---|---|---|---|---| +| provider request | order remains PENDING | timeout before provider reference | retry only with idempotency key | pending result | `internal/payment/client.go#CreatePayment` | +| webhook delivery | PROCESSING does not close | callback delivery failure | retry topic then DLQ | delayed | `config/kafka.yaml#payment_retry` | +| reconciliation | PAYMENT_UNKNOWN remains | provider unavailable or contradictory | MANUAL_REVIEW | support action | `internal/order/reconcile.go#Run` | +| late callback/cancel | competing terminal writes | timing race | conditional update, no terminal overwrite | one final terminal | `internal/order/cancel.go#Run` | + +## 12. 排障路径 + +Trace by `order_id`, `provider_event_id`, and outbox event id. Verify terminal-state conditional update, retry topic attempts, DLQ record, reconciliation result, and event delivery together. Do not inspect only the synchronous API response. + +## 13. 变更指南 + +Changing callback semantics must update provider-event idempotency, terminal-state guards, reconciliation, outbox behavior, all five diagrams, and S03-S07 evidence. + +## 14. 代码证据 + +| Claim | File / Symbol | Direction | +|---|---|---| +| order creation establishes pending truth | `internal/order/handler.go#Create` | API -> OrderService -> orders/outbox | +| callback owns normal terminal transition | `internal/payment/webhook.go#Handle` | Provider -> Consumer -> orders/outbox | +| reconciliation owns unknown-state recovery | `internal/order/reconcile.go#Run` | Job -> Provider -> orders/outbox | +| cancel cannot overwrite terminal payment | `internal/order/cancel.go#Run` | Job -> conditional order update | + +## 15. 自检 + +- Completeness Hard Gate: PASS. +- Required Slice IDs: S01-S07 covered. +- Required Diagram IDs: D-BOUNDARY, D-STATE, D-SEQUENCE covered. +- Complexity-triggered Diagram IDs: D-RECOVERY, D-ASYNC covered. +- Known production-trace gap remains recorded without changing the structural reference result. diff --git a/examples/ai-meeting-minutes-backend/onboarding-db/08-review/evidence-graph.md b/examples/ai-meeting-minutes-backend/onboarding-db/08-review/evidence-graph.md new file mode 100644 index 0000000..087d1e9 --- /dev/null +++ b/examples/ai-meeting-minutes-backend/onboarding-db/08-review/evidence-graph.md @@ -0,0 +1,22 @@ +# Evidence Graph + +This reference uses synthetic code paths to demonstrate the onboarding artifact contract. + +## Core Flow Inventory + +| Flow ID | Flow | Business Outcome | Criticality | Trigger / Entry | Success Terminal | Failure Terminals | Variants / Branches | Participants / Owners | State / Data Owners | Async / Jobs / Callbacks | External Side Effects | Recovery Responsibility | Evidence Chain | Selection | Selection Reason | Confidence / Unknowns | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| CF-ORDER-PAYMENT | 订单支付闭环 | 已创建订单最终得到可确认的支付结果 | critical | `POST /orders` -> `OrderHandler.Create` | `PAID` + `OrderPaidEvent` | `FAILED` / `CANCELLED` / `PAYMENT_UNKNOWN` / `MANUAL_REVIEW` | sync processing / webhook / retry / reconcile / cancel | Order API / Payment Adapter / Webhook Consumer / Reconciler | `orders.status` owned by OrderService | `PaymentWebhookConsumer` / retry topic / DLQ / `ReconcileJob` | provider charge + paid event | idempotent callback, retry, reconcile, manual review | `internal/order/handler.go#Create` -> `internal/payment/client.go#CreatePayment` -> `internal/payment/webhook.go#Handle` -> `internal/order/reconcile.go#Run` | planned | 新人必须理解同步非终态与异步恢复闭环 | high; cancel/reconcile race requires conditional update | + +## Async / Job / Callback Inventory + +| Name | Trigger | Consumer / Handler | State Changed | Retry / Compensation | Evidence | +|---|---|---|---|---|---| +| payment webhook | provider callback | `PaymentWebhookConsumer.Handle` | `PROCESSING -> PAID/FAILED` | provider event idempotency | `internal/payment/webhook.go#Handle` | +| payment reconcile | 10-minute schedule | `ReconcileJob.Run` | `PAYMENT_UNKNOWN -> PAID/MANUAL_REVIEW` | query provider and republish | `internal/order/reconcile.go#Run` | + +## Unknowns + +| Question | Why It Matters | Evidence Missing | Human Needed? | +|---|---|---|---| +| cancel and late webhook ordering | prevents illegal terminal overwrite | production race trace | yes | diff --git a/examples/ai-meeting-minutes-backend/onboarding-db/batch-review.md b/examples/ai-meeting-minutes-backend/onboarding-db/batch-review.md new file mode 100644 index 0000000..50cd910 --- /dev/null +++ b/examples/ai-meeting-minutes-backend/onboarding-db/batch-review.md @@ -0,0 +1,17 @@ +# Onboarding Batch Review + +## Completeness Hard Gate + +| Flow ID | Criticality | Business Terminals Closed? | Required Slice IDs | Missing / Blocked Slice IDs | Evidence + Diagram + Section Trace Complete? | Result | Next Action | +|---|---|---|---|---|---|---|---| +| CF-ORDER-PAYMENT | critical | yes | CF-ORDER-PAYMENT/S01, CF-ORDER-PAYMENT/S02, CF-ORDER-PAYMENT/S03, CF-ORDER-PAYMENT/S04, CF-ORDER-PAYMENT/S05, CF-ORDER-PAYMENT/S06, CF-ORDER-PAYMENT/S07 | none | yes | PASS | retain newcomer-ready | + +## Score + +| Topic | Core flow discovery completeness | Slice and branch coverage | Required diagram set present | Architecture diagram clarity | State diagram clarity | Timeline / sequence clarity | Use case completeness | Data object completeness | State transition clarity | Code evidence | Evidence granularity | Example authenticity | Failure / recovery | Troubleshooting | Consistency / gateway risk | Change guidance | Newcomer readability | Overall | +|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| CF-ORDER-PAYMENT | 5 | 5 | 5 | 4 | 5 | 5 | 4 | 4 | 5 | 4 | 4 | 4 | 5 | 4 | 5 | 4 | 4 | 4.5 | + +## Gaps / Unknowns + +Cancel and late-webhook race needs a production trace; current conditional-update claim is code-backed and marked for focused refresh. diff --git a/examples/ai-meeting-minutes-backend/onboarding-db/coverage-matrix.md b/examples/ai-meeting-minutes-backend/onboarding-db/coverage-matrix.md new file mode 100644 index 0000000..ec1bb82 --- /dev/null +++ b/examples/ai-meeting-minutes-backend/onboarding-db/coverage-matrix.md @@ -0,0 +1,11 @@ +# Coverage Matrix + +## Completeness Hard Gate + +| Flow ID | Criticality | Business Terminals Closed? | Required Slice IDs | Missing / Blocked Slice IDs | Evidence + Diagram + Section Trace Complete? | Result | Next Action | +|---|---|---|---|---|---|---|---| +| CF-ORDER-PAYMENT | critical | yes | CF-ORDER-PAYMENT/S01, CF-ORDER-PAYMENT/S02, CF-ORDER-PAYMENT/S03, CF-ORDER-PAYMENT/S04, CF-ORDER-PAYMENT/S05, CF-ORDER-PAYMENT/S06, CF-ORDER-PAYMENT/S07 | none | yes | PASS | keep evidence current | + +| Topic | Type | Doc Path | Score | Status | Missing Evidence | Next Action | +|---|---|---|---:|---|---|---| +| CF-ORDER-PAYMENT | flow | `03-flows/order-payment.md` | 4.5 | newcomer-ready | production race trace | focused update after trace capture | diff --git a/examples/ai-meeting-minutes-backend/onboarding-db/onboarding-spec.md b/examples/ai-meeting-minutes-backend/onboarding-db/onboarding-spec.md new file mode 100644 index 0000000..bc5caeb --- /dev/null +++ b/examples/ai-meeting-minutes-backend/onboarding-db/onboarding-spec.md @@ -0,0 +1,19 @@ +# Onboarding Spec + +## Core Flow Selection + +| Flow ID | Criticality | Business Outcome | Success / Failure Terminals | Variants / Recovery | Selection | Selection Reason / Impact | Evidence Chain | +|---|---|---|---|---|---|---|---| +| CF-ORDER-PAYMENT | critical | 订单得到最终支付结果 | PAID / FAILED / CANCELLED / PAYMENT_UNKNOWN / MANUAL_REVIEW | webhook / retry / DLQ / reconciliation / cancel | planned | 决定收入状态和用户可见结果 | handler -> payment client -> webhook -> reconciler | + +## Flow Slice Plan + +| Flow ID | Required Slice IDs | Main / Branch / Failure / Recovery Scope | State / Side Effect / Terminal Owned | Required Evidence | Planned Document | +|---|---|---|---|---|---| +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S01, CF-ORDER-PAYMENT/S02, CF-ORDER-PAYMENT/S03, CF-ORDER-PAYMENT/S04, CF-ORDER-PAYMENT/S05, CF-ORDER-PAYMENT/S06, CF-ORDER-PAYMENT/S07 | create / processing / webhook / duplicate / retry-DLQ / reconcile / cancel race | order terminals and OrderPaidEvent | file + symbol + direction | `03-flows/order-payment.md` | + +## Diagram Plan + +| Topic / Flow ID | Planned Path | Content Doc? | Complexity Signals | Required Architecture/Boundary Diagram | Required State Diagram | Required Timeline / Sequence | Conditional Diagram Types | Covered Slice IDs | Exemption / Reason | +|---|---|---|---|---|---|---|---|---|---| +| CF-ORDER-PAYMENT | `03-flows/order-payment.md` | yes | callback/retry/reconcile/idempotency/async | D-BOUNDARY | D-STATE | D-SEQUENCE | D-RECOVERY, D-ASYNC | S01-S07 | none | diff --git a/examples/ai-meeting-minutes-backend/onboarding-db/onboarding-tasks.md b/examples/ai-meeting-minutes-backend/onboarding-db/onboarding-tasks.md new file mode 100644 index 0000000..74abe17 --- /dev/null +++ b/examples/ai-meeting-minutes-backend/onboarding-db/onboarding-tasks.md @@ -0,0 +1,10 @@ +# Onboarding Tasks + +| Task | Output | Flow ID | Required Slice IDs | Required / Conditional Diagram IDs | Evidence Required | Completeness Hard Gate | Quality Score Target | Status | +|---|---|---|---|---|---|---|---|---| +| Document order-payment closure | `03-flows/order-payment.md` | CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S01, CF-ORDER-PAYMENT/S02, CF-ORDER-PAYMENT/S03, CF-ORDER-PAYMENT/S04, CF-ORDER-PAYMENT/S05, CF-ORDER-PAYMENT/S06, CF-ORDER-PAYMENT/S07 | D-BOUNDARY, D-STATE, D-SEQUENCE, D-RECOVERY, D-ASYNC | handler/client/webhook/reconcile symbols and direction | must PASS before score | >=4 | complete | + +## Full Execution Gate + +- Status: accepted +- Scope: CF-ORDER-PAYMENT/S01-S07 and Diagram IDs listed above diff --git a/examples/concept-foundation-refund/product.md b/examples/concept-foundation-refund/product.md new file mode 100644 index 0000000..18e6bb6 --- /dev/null +++ b/examples/concept-foundation-refund/product.md @@ -0,0 +1,99 @@ +# Product Brief: Refund Completion + +Created: 2026-07-12 +Updated: 2026-07-12 +Status: draft + +Source Requirements: +- Requirement: requirement.md + +Summary: +- Separate request approval from actual funds settlement in customer-visible behavior. + +## Applicable Decisions + +- none; technical settlement design remains a candidate + +## Accepted Concept References + +Concept Foundation Status: accepted +Source Requirement: requirement.md +Effective Concept Source: requirement.md +Not-Needed Reason: n/a + +| Concept ID | Canonical Name | Product Brief Use | Source Definition / Trace | +|---|---|---|---| +| C-CUSTOMER | Customer | submits and receives outcome | requirement.md#concept-definitions / TRACE-01 | +| C-REFUND-ADMIN | Refund Administrator | reviews and recovers | requirement.md#concept-definitions / TRACE-02 | +| C-PAYMENT-PROVIDER | Payment Provider | reports funds outcome | requirement.md#concept-definitions / TRACE-03 | +| C-REFUND-REQUEST | Refund Request | request/review journey | requirement.md#concept-definitions / TRACE-01 / TRACE-02 | +| C-REFUND-SETTLEMENT | Refund Settlement | terminal customer outcome | requirement.md#concept-definitions / TRACE-02 / TRACE-03 | + +## Requirement Product Model Coverage + +| Requirement Model ID | Concept IDs | Feature Product Journey / Story | Coverage | Notes | +|---|---|---|---|---| +| PM-01 / PERM-CUSTOMER-REQUEST / PERM-ADMIN-REQUEST / FLOW-01 / FLOW-02 / STATE-REQUEST-01 / STATE-REQUEST-02 | C-CUSTOMER, C-REFUND-ADMIN, C-REFUND-REQUEST | submit and review | in-scope | approval is not funds success | +| PM-02 / PERM-ADMIN-SETTLEMENT / PERM-PROVIDER-SETTLEMENT / FLOW-03 / FLOW-04 / STATE-SETTLEMENT-01 / STATE-SETTLEMENT-02 / STATE-SETTLEMENT-03 / STATE-SETTLEMENT-04 / EX-01 / EX-02 | C-PAYMENT-PROVIDER, C-REFUND-SETTLEMENT | observe and recover settlement | in-scope | success notification uses settlement terminal | + +## Problem Statement + +Customers currently cannot distinguish “approved” from “funds returned.” + +## Target Users / Actors + +- C-CUSTOMER +- C-REFUND-ADMIN +- C-PAYMENT-PROVIDER + +## Solution Summary + +Show request review and settlement outcome as separate accepted product facts. + +## Primary User Journey + +1. Customer submits C-REFUND-REQUEST. +2. Refund Administrator approves it and starts C-REFUND-SETTLEMENT. +3. Customer sees success only after settlement succeeds. + +## User Stories + +### US1: Customer submits a request + +As a customer, I want to submit an eligible request, so that refund review can begin. + +Acceptance Direction: FLOW-01 and STATE-REQUEST-01 remain traceable. + +### US2: Administrator reviews without claiming funds success + +As a refund administrator, I want approval to start settlement, so that review and funds outcomes remain distinct. + +Acceptance Direction: FLOW-02 and STATE-SETTLEMENT-01 remain traceable. + +### US3: Customer receives an observable settlement outcome + +As a customer, I want success/failure/manual outcomes, so that no approval is misreported as funds returned. + +Acceptance Direction: FLOW-03 / FLOW-04 and settlement states remain traceable. + +## Product Scope + +- Request submission and review status. +- Settlement success/failure/unknown/manual outcome. + +## Out Of Scope + +- Technical storage and provider adapter choices. + +## Terminology + +- `funds returned`: + - Accepted Concept ID: C-REFUND-SETTLEMENT + - Feature display wording / alias: Refund completed + - Promote to project Domain Language: pending + +## Open Product Questions + +- Question: provider latency target + - Recommended answer: route to later technical design + - Blocks: none diff --git a/examples/concept-foundation-refund/requirement.md b/examples/concept-foundation-refund/requirement.md new file mode 100644 index 0000000..730ad19 --- /dev/null +++ b/examples/concept-foundation-refund/requirement.md @@ -0,0 +1,210 @@ +# Requirement: Refund Completion + +Status: accepted +Created: 2026-07-12 +Source: conversation + payment callback evidence + +## Background + +Customers request refunds; administrators review them; a later payment callback confirms whether funds actually returned. + +## Problem + +The word “refund” previously mixed the review request with the settlement result, so “completed” had two incompatible terminals. + +## Users / Operators + +- Customer +- Refund administrator +- Payment provider + +## Goals + +- Make review completion and funds-settlement completion independently observable. +- Notify the customer only from the accepted settlement terminal. + +## Concept Foundation + +Concept Foundation Status: accepted +Not-Needed Reason: n/a because identity, lifecycle, actor, and terminal meaning change the downstream model + +### Evidence And Scenarios + +- Success scenario: customer submits, administrator approves, provider later confirms settlement. +- Failure scenario: administrator approves, but provider callback reports failure or remains unknown. +- Evidence: payment callback test owns the final funds result; historical product notes separate request and settlement. + +## Concept Candidate Inventory + +| Concept ID | Candidate Name | Kind | Evidence / Example | Ambiguity / Conflict | Status | +|---|---|---|---|---|---| +| C-CUSTOMER | Customer | role | source scenario | none | accepted | +| C-REFUND-ADMIN | Refund Administrator | role | review flow | “admin” must stay refund-scoped | accepted | +| C-PAYMENT-PROVIDER | Payment Provider | external actor | callback contract | provider does not own request review | accepted | +| C-REFUND-REQUEST | Refund Request | entity | submit/review records | previously called “refund” | accepted | +| C-REFUND-SETTLEMENT | Refund Settlement | entity/result | callback and reconciliation | previously called “refund completed” | accepted | + +## Concept Definitions + +| Concept ID | Canonical Name | Definition / Non-example | Identity | Owner | Lifecycle Boundary / State-bearing | Invariants | Product Fact Owner | Evidence | +|---|---|---|---|---|---|---|---|---| +| C-CUSTOMER | Customer | requester receiving the product outcome; not the payment provider | customer ID | customer account | n/a: role concept | may act only on own request | customer account | source scenario | +| C-REFUND-ADMIN | Refund Administrator | operator reviewing a request; not the settlement actor | operator ID + refund permission | refund operations | n/a: role concept | cannot mark funds settled | refund operations | permission rule | +| C-PAYMENT-PROVIDER | Payment Provider | external actor reporting funds result | provider account | payment boundary | n/a: actor concept | callback result cannot approve request | provider callback | callback contract | +| C-REFUND-REQUEST | Refund Request | reviewable customer request; not proof that funds arrived | request ID | refund operations | submitted to rejected/cancelled/approved | one review terminal per request | refund operations | request model/test | +| C-REFUND-SETTLEMENT | Refund Settlement | actual refund funds attempt/result; not administrator approval | provider reference per request | payment boundary | pending to succeeded/failed/unknown/manual-review | succeeded only from verified provider/reconciliation evidence | payment result | callback/reconcile tests | + +## Concept Relationships + +| Relationship ID | From Concept ID | Relationship | To Concept ID | Cardinality / Boundary | Invariant | Evidence | +|---|---|---|---|---|---|---| +| REL-01 | C-CUSTOMER | submits | C-REFUND-REQUEST | one customer to many requests | customer owns request visibility | source scenario | +| REL-02 | C-REFUND-REQUEST | authorizes creation of | C-REFUND-SETTLEMENT | one approved request to one active settlement | rejected/cancelled requests have no settlement | review/callback tests | +| REL-03 | C-PAYMENT-PROVIDER | reports result for | C-REFUND-SETTLEMENT | one provider result stream per settlement | duplicate callbacks preserve one terminal | callback contract | + +### Blocking Ambiguities + +| Blocking Question | Recommended Definition | Evidence | Accept Impact | Reject Impact | Status | +|---|---|---|---|---|---| +| Does “refund completed” mean review approval or funds settlement? | funds settlement succeeded | callback owns actual funds result | notification and recovery use observable terminal | post-success failure remains possible | resolved | + +### Human Confirmation + +- Confirmed Concept IDs: C-CUSTOMER, C-REFUND-ADMIN, C-PAYMENT-PROVIDER, C-REFUND-REQUEST, C-REFUND-SETTLEMENT +- Human decision: request approval and settlement success are separate product terminals +- Confirmed at: 2026-07-12 +- Remaining non-blocking unknowns: provider-specific settlement latency + +## Requirements + +- A customer can submit one refund request for an eligible purchase. +- Administrator approval creates the settlement attempt but does not notify success. +- Settlement success, failure, unknown, and manual-review outcomes remain observable. + +## Terminology / Domain Language + +| Concept ID | Canonical Term | Allowed Alias / Display Wording | Avoid / Ambiguity | Promote To Project Domain Language | +|---|---|---|---|---| +| C-REFUND-REQUEST | Refund Request | refund application | “refund completed” | pending | +| C-REFUND-SETTLEMENT | Refund Settlement | funds returned | “approved means paid” | pending | + +## Role / Permission Matrix + +| Permission Rule ID | Role Concept ID | Product Object Concept ID | Read | Create | Advance / Decide | Cancel / Withdraw | Recover | Boundary / Evidence | +|---|---|---|---|---|---|---|---|---| +| PERM-CUSTOMER-REQUEST | C-CUSTOMER | C-REFUND-REQUEST | own | yes | no | before approval | no | source scenario | +| PERM-ADMIN-REQUEST | C-REFUND-ADMIN | C-REFUND-REQUEST | permitted scope | no | approve/reject | no | manual review | permission rule | +| PERM-ADMIN-SETTLEMENT | C-REFUND-ADMIN | C-REFUND-SETTLEMENT | permitted scope | no | reconcile unknown/failed | no | retry/reconcile/manual review | reconciliation responsibility | +| PERM-PROVIDER-SETTLEMENT | C-PAYMENT-PROVIDER | C-REFUND-SETTLEMENT | callback reference | result only | report result | no | no | callback contract | + +## Commands / Events + +| Action ID | Type | Name | Actor / Producer Concept ID | Target Concept ID | Preconditions / Guard | Result / Event | Evidence | +|---|---|---|---|---|---|---|---| +| CMD-SUBMIT | command | Submit Refund Request | C-CUSTOMER | C-REFUND-REQUEST | eligible purchase and no active request | request submitted | request test | +| CMD-APPROVE | command | Approve Refund Request | C-REFUND-ADMIN | C-REFUND-REQUEST | submitted request | settlement pending | review test | +| EVT-SETTLED | event | Settlement Succeeded | C-PAYMENT-PROVIDER | C-REFUND-SETTLEMENT | matching provider reference | customer success notification | callback test | +| EVT-FAILED | event | Settlement Failed | C-PAYMENT-PROVIDER | C-REFUND-SETTLEMENT | matching provider reference | failure visible for recovery | callback test | +| CMD-RECONCILE | command | Reconcile Unknown Settlement | C-REFUND-ADMIN | C-REFUND-SETTLEMENT | unknown or timed out | terminal or manual-review result | reconciliation test | + +## Primary Business Flow + +| Flow Step ID | Actor Concept ID | Action ID | Input / Target Concept IDs | Product State Change | Result / Next Step | +|---|---|---|---|---|---| +| FLOW-01 | C-CUSTOMER | CMD-SUBMIT | C-REFUND-REQUEST | none to submitted | await review | +| FLOW-02 | C-REFUND-ADMIN | CMD-APPROVE | C-REFUND-REQUEST, C-REFUND-SETTLEMENT | request approved; settlement pending | await provider | +| FLOW-03 | C-PAYMENT-PROVIDER | EVT-SETTLED | C-REFUND-SETTLEMENT | pending to succeeded | notify customer success | +| FLOW-04 | C-REFUND-ADMIN | CMD-RECONCILE | C-REFUND-SETTLEMENT | unknown to terminal/manual-review | close observable outcome | + +## Product State Model + +| State Model ID | State-bearing Concept ID | From | Action / Event ID | Guard / Invariant | To | Terminal / Recovery | Forbidden Transition | +|---|---|---|---|---|---|---|---| +| STATE-REQUEST-01 | C-REFUND-REQUEST | none | CMD-SUBMIT | eligible purchase | submitted | no | none to approved | +| STATE-REQUEST-02 | C-REFUND-REQUEST | submitted | CMD-APPROVE | refund administrator | approved | request terminal | approved to submitted | +| STATE-SETTLEMENT-01 | C-REFUND-SETTLEMENT | none | CMD-APPROVE | request approved | pending | no | none to succeeded | +| STATE-SETTLEMENT-02 | C-REFUND-SETTLEMENT | pending | EVT-SETTLED | provider result verified | succeeded | success terminal | succeeded to failed | +| STATE-SETTLEMENT-03 | C-REFUND-SETTLEMENT | pending | EVT-FAILED | provider result verified | failed | recovery allowed | failed to succeeded without reconcile | +| STATE-SETTLEMENT-04 | C-REFUND-SETTLEMENT | unknown | CMD-RECONCILE | evidence checked | manual-review | manual terminal | manual-review to pending | + +## Requirement Product Model + +| Product Model ID | Product Object / Fact | Concept IDs | Identity / Relationship | Owner / Allowed Changer | Product Invariant | Product Fact Meaning | +|---|---|---|---|---|---|---| +| PM-01 | refund request fact | C-REFUND-REQUEST, C-CUSTOMER, C-REFUND-ADMIN | request ID; submitted by customer; reviewed by admin | refund operations | approval is not proof of funds | customer intent and review terminal | +| PM-02 | settlement result fact | C-REFUND-SETTLEMENT, C-PAYMENT-PROVIDER | provider reference linked by REL-02 | provider result/reconciliation | one active settlement; succeeded is immutable | observed funds outcome | + +## Exception Paths + +| Scenario ID | Concept / State / Action IDs | Trigger | Expected Handling | Recovery / Responsible Actor | Observable Result | Acceptance Direction | +|---|---|---|---|---|---|---| +| EX-01 | C-REFUND-SETTLEMENT / EVT-FAILED | provider failure | keep request approved and settlement failed | retry/reconcile by C-REFUND-ADMIN | failure visible | no success notification | +| EX-02 | C-REFUND-SETTLEMENT / CMD-RECONCILE | missing callback | mark unknown then investigate | C-REFUND-ADMIN | terminal or manual-review | no false completion | + +## Data / Source of Truth + +| Product Model ID | Product Fact | Product Fact Owner | Who Can Change It | Timing / Consistency Need | Decision Candidate | +|---|---|---|---|---|---| +| PM-01 | request/review terminal | C-REFUND-REQUEST | C-REFUND-ADMIN | synchronous review | none | +| PM-02 | actual funds outcome | C-REFUND-SETTLEMENT | C-PAYMENT-PROVIDER or reconciliation | asynchronous | technical storage mapping deferred | + +## Concept-To-Product Traceability + +| Trace ID | Accepted Concept IDs | Derived Model IDs / Sections | Product Rule / Meaning | Downstream Product Brief / Feature Spec Use | +|---|---|---|---|---| +| TRACE-01 | C-CUSTOMER, C-REFUND-REQUEST | REL-01 / PERM-CUSTOMER-REQUEST / CMD-SUBMIT / FLOW-01 / STATE-REQUEST-01 / PM-01 | customer owns request submission | refund journey and US1 | +| TRACE-02 | C-REFUND-ADMIN, C-REFUND-REQUEST, C-REFUND-SETTLEMENT | REL-02 / PERM-ADMIN-REQUEST / PERM-ADMIN-SETTLEMENT / CMD-APPROVE / FLOW-02 / STATE-REQUEST-02 / STATE-SETTLEMENT-01 / PM-01 / PM-02 / EX-02 | approval creates settlement but is not funds success | admin journey and US2 | +| TRACE-03 | C-PAYMENT-PROVIDER, C-REFUND-SETTLEMENT | REL-03 / PERM-PROVIDER-SETTLEMENT / EVT-SETTLED / EVT-FAILED / CMD-RECONCILE / FLOW-03 / FLOW-04 / STATE-SETTLEMENT-02 / STATE-SETTLEMENT-03 / STATE-SETTLEMENT-04 / PM-02 / EX-01 | notification follows observed settlement terminal | callback behavior and US3 | + +## Historical Behavior / Prior Conflicts + +| Prior Source | Existing Rule / Behavior | Current Requirement Says | Human Decision | +|---|---|---|---| +| prior refund notes | “approved” was sometimes called completed | distinguish review and settlement | override ambiguous wording | + +## Acceptance Scenarios + +| Scenario | Given | When | Then | Notes | +|---|---|---|---|---| +| settlement success | approved request with pending settlement | verified success callback arrives | settlement succeeds and customer is notified | approval alone does not notify | +| settlement unknown | approved request with no callback | reconciliation runs | terminal or manual-review result is visible | no false completion | + +## Decision Candidates + +| Candidate | Why It Matters | Signal | Suggested Destination | Status | +|---|---|---|---|---| +| technical settlement storage/consistency | later implementation must preserve PM-02 | shared-design | Decision & Design | proposed | + +## Non-goals + +- No technical table, topic, provider adapter, or migration mapping. + +## Out Of Scope And Why + +| Out Of Scope | Why | Revisit Trigger | +|---|---|---| +| provider-specific retries | implementation design | Decision & Design | + +## Constraints / Assumptions + +- Provider callbacks can be delayed or duplicated. + +## Acceptance Direction + +- Product artifacts and features preserve TRACE-01 through TRACE-03. + +## Open Questions + +- Provider latency target is non-blocking for this product model. + +## Product / Feature Mapping + +| Downstream Artifact | Mapping Direction | Status | +|---|---|---| +| product.md | TRACE-01..03 | created | +| spec.md | PM-01 / PM-02 and state/action IDs | created | +| Design Readiness | storage/consistency candidate | candidate | + +## Source Conversation Summary + +- Human confirmed that “refund completed” means observed settlement success. diff --git a/examples/concept-foundation-refund/spec.md b/examples/concept-foundation-refund/spec.md new file mode 100644 index 0000000..fc30dfe --- /dev/null +++ b/examples/concept-foundation-refund/spec.md @@ -0,0 +1,80 @@ +# Feature Spec: Refund Completion + +Created: 2026-07-12 +Updated: 2026-07-12 +Status: draft +Feature Type: normal + +Source Requirements: +- Requirement: requirement.md + +Product Brief: product.md + +Summary: +- Implement the accepted separation between refund request review and settlement outcome. + +## Problem / Goal + +Preserve the accepted product terminals in feature behavior and acceptance. + +## Applicable Decisions + +- none accepted + +## Accepted Concept References + +Concept Foundation Status: accepted +Source Requirement: requirement.md +Effective Concept Source: requirement.md + +| Concept ID | Canonical Name | Feature Use | Source Definition / Trace | +|---|---|---|---| +| C-CUSTOMER | Customer | submits and observes | requirement.md#concept-definitions / TRACE-01 | +| C-REFUND-ADMIN | Refund Administrator | reviews and reconciles | requirement.md#concept-definitions / TRACE-02 | +| C-PAYMENT-PROVIDER | Payment Provider | reports settlement | requirement.md#concept-definitions / TRACE-03 | +| C-REFUND-REQUEST | Refund Request | request lifecycle | requirement.md#concept-definitions / TRACE-01 / TRACE-02 | +| C-REFUND-SETTLEMENT | Refund Settlement | settlement lifecycle | requirement.md#concept-definitions / TRACE-02 / TRACE-03 | + +## Requirement Product Model Trace + +| Requirement Model ID | Concept / Action / Flow / State IDs | Feature Behavior / Story | Acceptance / Verification Direction | Coverage | +|---|---|---|---|---| +| PM-01 | C-CUSTOMER / C-REFUND-ADMIN / C-REFUND-REQUEST / PERM-CUSTOMER-REQUEST / PERM-ADMIN-REQUEST / CMD-SUBMIT / CMD-APPROVE / FLOW-01 / FLOW-02 / STATE-REQUEST-01 / STATE-REQUEST-02 | US1 / US2 | approval never claims funds success | covered | +| PM-02 | C-PAYMENT-PROVIDER / C-REFUND-SETTLEMENT / PERM-ADMIN-SETTLEMENT / PERM-PROVIDER-SETTLEMENT / EVT-SETTLED / EVT-FAILED / CMD-RECONCILE / FLOW-03 / FLOW-04 / STATE-SETTLEMENT-01 / STATE-SETTLEMENT-02 / STATE-SETTLEMENT-03 / STATE-SETTLEMENT-04 / EX-01 / EX-02 | US3 | settlement terminals and recovery are observable | covered | + +## Scope + +- Request/review and settlement outcomes remain separate. +- Customer success notification follows STATE-SETTLEMENT-02 only. + +## Stories + +### US1: Submit refund request + +Acceptance scenarios: +- Given eligibility, when CMD-SUBMIT runs, then STATE-REQUEST-01 is observable. + +### US2: Approve without false completion + +Acceptance scenarios: +- Given a submitted request, when CMD-APPROVE runs, then request is approved and settlement is pending. + +### US3: Observe settlement terminal + +Acceptance scenarios: +- Given a pending settlement, when EVT-SETTLED arrives, then success is observable and customer is notified. +- Given missing/failure evidence, when CMD-RECONCILE runs, then failure or manual-review is observable. + +## Acceptance Criteria + +- Accepted Concept IDs and PM-01/PM-02 meanings are unchanged. +- Approval alone never emits customer success. +- Duplicate or delayed provider results cannot create conflicting terminals. + +## Out of Scope + +- Concept-to-table/store/event/provider mapping. + +## Open Questions + +- none at product-semantics level diff --git a/examples/login-feature/notes.md b/examples/login-feature/notes.md index 9e641d6..234362d 100644 --- a/examples/login-feature/notes.md +++ b/examples/login-feature/notes.md @@ -41,5 +41,15 @@ Result: passed ## Close Record -Closed: 2026-05-26 +Closed At: 2026-05-26 Human confirmation: required in real use; example assumes confirmed. + +## Archive Readiness + +Closed At: 2026-05-26 +Delivered Summary: completed login authentication, inline failure states, and browser verification +Verification: complete +Feature Close Review: complete +Drift: resolved +Project Memory Impact: complete +Open Follow-up: none diff --git a/plugin.json b/plugin.json index 3e80bb5..ea2a99e 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "agent-loop", - "version": "1.2.4", + "version": "1.3.0", "description": "Agent-guided single-human development loop. The agent controls workflow mechanics—stage classification, reference loading, artifact production, and human gates. Human controls goals and decisions. TDD by default.", "author": { "name": "Shadow-linux", diff --git a/references/artifact-rules.md b/references/artifact-rules.md index d9cf008..ce7b76f 100644 --- a/references/artifact-rules.md +++ b/references/artifact-rules.md @@ -51,6 +51,7 @@ New human source material should be archived inside a requirement set directory. | `plans/*` | dated plan cycles when complex mode is triggered | current-state summary | | `handoffs/*` | subagent briefs and returned summaries when subagent mode is triggered | authoritative task status | | `contracts/*` | optional confirmed durable producer-consumer contract details when interface detail is needed | temporary task logs | +| `features/archive.md` | Feature Monthly Archive locator and move ledger: stable Feature ID, current path, archive state, close date, one-line delivery locator, source/decision locators, last move | feature lifecycle, product meaning, requirement meaning, decision content, verification evidence | ## Status Values @@ -111,6 +112,22 @@ Feature Auto-Loop Task Auto-Run ``` +## Feature Monthly Archive Layout + +Feature ID is stable while its location may be flat or archived: + +```text +features// active, blocked, paused, recent closed, or rehydrated +features/YYYY-MM// Human-reviewed archived closed history +features/archive.md root locator and move ledger +``` + +Archive state is `archived | rehydrated`; archive state is not feature lifecycle. Lifecycle remains `draft | active | blocked | paused | closed`, and active / blocked / paused features stay flat. + +Feature Monthly Archive moves the complete eligible directory without content compression. The scan is read-only. Apply requires the expected plan SHA-256 Batch Human Gate, transaction journal, exact precomputed reference edits, post-check, and restore. Original human requirement source files are never rewritten. + +Scope boundaries are explicit: no per-feature archive summary, no historical/ directory, no Deep Archive, no deletion/packing/scheduled archive, and No `--force`. A closed archived feature must rehydrate before reopened execution. + Record the active gate mode in `project.md` Current Work or the active feature `notes.md` checkpoint. If scope changes, switch back to Strict Mode unless the human renews the auto-mode grant. Slice type: diff --git a/references/concepts.md b/references/concepts.md index 87ede21..2d340b0 100644 --- a/references/concepts.md +++ b/references/concepts.md @@ -35,6 +35,14 @@ First version excludes: **Feature**: One behavior-changing work area under `.agent-loop/features//`. A feature can contain many stories and many tasks. +**Feature Monthly Archive**: Explicit closed-history maintenance that moves an eligible whole feature directory intact to `.agent-loop/features/YYYY-MM//`, maintains root `features/archive.md`, updates only approved references, and uses a deterministic plan hash, Batch Human Gate, transaction journal, post-check, and restore. + +**Feature Locator**: The `features/archive.md` row that maps a stable Feature ID to its current flat or archived path. It is not the authority for feature behavior or delivery evidence. + +**Archive State**: `archived | rehydrated`; archive state is not feature lifecycle. + +**Rehydrate**: Human-gated movement of an archived closed feature back to its flat path before Feature Follow-up may reopen it for execution. + **Feature Type**: Feature work can be `normal`, `maintenance-fix`, or `follow-up`. The file layout stays the same for all types. **Maintenance Fix**: A narrow feature used when a bugfix or internal correction has no clear owning recent feature and does not create a new user capability. It is not a workflow bypass and not a separate directory system. It still uses `.agent-loop/features/YYYY-MM-DD-fix-/` with `spec.md`, `tasks.md`, `tests.md`, `plan.md`, `notes.md`, verification, review, drift check, project memory update when needed, and close. diff --git a/references/design.md b/references/design.md index 21499fc..523ad06 100644 --- a/references/design.md +++ b/references/design.md @@ -22,10 +22,16 @@ The core constraints are: - optional `.agent-loop/skills/` owns Human-gated project-local reusable capabilities; `INDEX.md` owns lifecycle and discovery metadata - stable Web E2E capability belongs in `project.md`; feature-specific E2E cases belong in feature `tests.md` or `tests/e2e/*` - `requirements/` stores human source material packages and requirement lifecycle/backlog records as requirement set directories: requirements, prototypes, feedback, screenshots, recordings, links, follow-up notes, status, and optional `requirements/INDEX.md` +- Concept Foundation is an internal Requirements Discussion / Requirement Product Grill method, not a canonical stage; when triggered, it stabilizes requirement-local product concepts before business-flow, state, and product-data modeling +- the effective human-reviewed requirement source owns accepted Concept Foundation and Requirement Product Model semantics; after archive, requirement README indexes the effective source/status without copying details, and Product Brief / Feature Spec consume those meanings by reference +- requirement-driven ADRs freeze an Effective Requirement Snapshot, inventory every source Requirement Model ID, and trace every in-scope accepted ID to a disposition, technical landing, Design Slice, and verification without taking ownership of product semantics +- upstream requirement changes invalidate dependency availability until compatibility review; `review-required` is not a decision lifecycle status, and incompatible accepted decisions are superseded rather than rewritten - requirement-set dates mean archive date only, not deadlines or feature lifecycle dates - future/deferred work belongs in requirement sets and optional `requirements/INDEX.md`, not in `project.md` - `product.md` is optional feature-level product understanding when needed - each feature has stable `spec.md`, `tasks.md`, `tests.md`, `plan.md`, `notes.md`; `contracts.md` is added only after human confirmation when producer-consumer boundaries need explicit handoff +- Feature Monthly Archive is explicit closed-history maintenance: Feature ID is stable, eligible whole directories move to `features/YYYY-MM//`, and root `features/archive.md` is only the locator/ledger +- archive state is not feature lifecycle; active / blocked / paused features stay flat, and archived closed features rehydrate before reopened execution - feature type may be `normal`, `maintenance-fix`, or `follow-up`; all use the same feature workspace model - maintenance fixes are narrow feature workspaces under `.agent-loop/features/YYYY-MM-DD-fix-/`, not naked code edits and not a separate `.agent-loop/maintenance/` tree - stories live in `spec.md`; optional `tasks/USn/` or `tests/USn/` folders are detail grouping, not separate story workspaces @@ -48,6 +54,7 @@ Human Goal → Execute / Verify → Drift Check → Feature Follow-up / Flow-back when post-close bug/change appears +→ Feature Monthly Archive when the human explicitly asks to compact closed-history discovery → Project Memory Update → Submit / Integrate if requested → Resume / Pause / Close @@ -73,10 +80,28 @@ Behavior Intent **Requirement**: human-provided need, goal, document, or natural-language request. +**Concept Foundation**: a triggered method inside Requirements Discussion / Requirement Product Grill that derives requirement-local stable Concept IDs, definitions, identity, lifecycle boundaries, relationships, owners, state-bearing classification, invariants, and product fact-source questions from scenarios and evidence. It is not a stage or top-level artifact. + +**Requirement Product Model**: the product-layer derivation owned by the effective human-reviewed requirement source. It traces accepted concepts into relationships, roles/permissions, commands/events, business flow, product state, product data objects, invariants, and exception/recovery behavior without choosing tables, stores, protocols, or other technical representations. After archive, append-only follow-ups or a linked replacement set preserve prior sources while README indexes the effective source. + +**Effective Requirement Snapshot**: the read-only ADR header that resolves the requirement README's current Effective Concept Foundation pointer and records the accepted source, Concept Foundation status, accepted Concept IDs, accepted Requirement Model IDs, compatibility judgment, and last compatibility check. It does not copy or redefine product meaning. + +**Requirement Model Scope Inventory**: the source-wide ADR section that accounts for every stable Requirement Model ID (`REL-*`, `PERM-*`, `CMD-*`, `EVT-*`, `FLOW-*`, `STATE-*`, `PM-*`, and `EX-*`) before declaring the coherent ADR scope. It prevents silent omissions and records external, proposed, feature-local, or reasoned not-applicable ownership without becoming a separate artifact. + +**Requirement Model Technical Landing Trace**: the table inside an existing Decision & Design record that gives every in-scope accepted Requirement Model ID one disposition and, when landed by this ADR, connects it to a concrete technical landing, preserved invariant, Design Slice, and verification path. It is not a separate artifact or executable schema. + **Prototype**: human-provided design artifact, screenshot, wireframe, or interaction reference. **Feature**: one behavior-changing work area under `.agent-loop/features//`. +**Feature Monthly Archive**: An explicit, Human-gated maintenance capability that moves an eligible closed feature directory intact to `.agent-loop/features/YYYY-MM//`, updates `features/archive.md` and approved references, post-checks, and restores on failure. The scan is read-only and apply requires the exact expected plan SHA-256 Batch Human Gate plus transaction journal. It creates no per-feature archive summary, no `historical/`, no Deep Archive, and no `--force`. + +**Feature Locator**: The root `features/archive.md` mapping from stable Feature ID to current flat or month path. It locates history but does not own product, requirement, decision, lifecycle, test, or delivery facts. + +**Archive State**: `archived | rehydrated`. Archive state is not feature lifecycle; lifecycle remains `draft | active | blocked | paused | closed`. + +**Rehydrate**: The separately Human-reviewed move from a month path back to the flat feature path. Rehydrate before reopened execution; Feature Follow-up decides any later `closed -> active` lifecycle transition. + **Stories**: user-perspective slices inside a feature. They live in `spec.md`. Use labels such as `US1`, `US2` in `tasks.md`; complex artifact mode may group detail files under `tasks/USn/` or `tests/USn/` without making stories separate workspaces. **Task**: default executable engineering unit. Keep tasks small, verifiable, and tied to a story when possible. @@ -246,6 +271,25 @@ Do not create or refresh onboarding-db through the removed legacy flow. If onboarding-db is missing but project memory or root guidance claims it should exist, route to stale-memory recovery and ask before correcting `project.md` or root guidance. Do not recreate onboarding-db through the removed legacy flow. +#### Core Flow Completeness Invariant + +Evidence-Graph + DDD Onboarding must preserve this trace for every `critical` / `important` core flow: + +```text +Core Flow Inventory +-> accepted Core Flow selection +-> Flow Slice Coverage +-> Diagram + narrative + code-evidence trace +-> Completeness Hard Gate +-> Quality Score +``` + +A core flow is not closed merely because a synchronous call returned. It must trace to its business success, failure, cancellation, unknown, or manual-handling terminals and include any callback, consumer, retry, compensation, reconciliation, or job that owns a required transition, side effect, or recovery responsibility. Reclassifying those required slices as separate future topics does not remove them from the core flow. + +A missing critical slice cannot be averaged away by diagram presence, readability, or other topic scores. `supporting` flows remain lightweight unless they own core state, an externally visible side effect, or recovery. Stateless overview, glossary, configuration, and index topics use only diagrams that explain real semantics; they do not invent state machines to satisfy a file-wide quota. + +The invariant does not add a Human Gate. Onboarding has exactly two onboarding Human Gates: Onboarding Spec Acceptance, followed later by Onboarding Tasks Full Execution Gate. Completeness is an Agent quality gate inside the accepted scope. + ### Feature Follow-up And Flow-back Project Entry and memory bootstrap have priority over Feature Follow-up. @@ -265,6 +309,26 @@ Inspect Active / Paused / Closed features and recent feature docs. Use 30 days as the default lookback, not a hard boundary. Present Candidate Match Matrix. Recommend flow-back, linked new feature, maintenance-fix, or investigate-first. +Resolve Active/Paused first, flat recent features second, `features/archive.md` third, and archived artifacts fourth. Rehydrate a confirmed archived owner before lifecycle change or execution. +``` + +### Feature Monthly Archive + +Condition: + +```text +Human explicitly requests archive or rehydrate +Project memory and feature close evidence are reliable +``` + +Action: + +```text +Run read-only scan and show one deterministic Batch Review. +Wait for confirmation of the exact plan SHA-256. +Apply only eligible moves through the transaction journal. +Update the root locator and approved references, then post-check. +Restore on failure; route a stranded journal to Recovery. ``` ### Active Feature Continuation @@ -323,10 +387,37 @@ Require the Execution Gate for every invocation. ## Main Flow +Within Requirements Discussion, triggered complex requirements use this internal semantic order before the canonical stage flow continues: + +```text +Scenario / Evidence +→ Concept Candidate Inventory +→ Concept Foundation Human Confirmation +→ Requirement Product Model +→ human-reviewed Requirement Document +``` + +Simple requirements record `concept-foundation-not-needed` with a reason and remain lightweight. `candidate` and `reopened` are blocking; only `accepted` or a reasoned not-needed result may continue into requirement product modeling. + +Within Decision & Design, a requirement-driven ADR uses this internal order without adding a canonical stage or default mapping artifact: + +```text +Effective Requirement Source +→ Effective Requirement Snapshot +→ Requirement Model Scope Inventory +→ Requirement Model Technical Landing Trace +→ proposed structural preflight +→ Decision & Design Human Review +→ accepted-mode validation and assigned Design Slices +``` + +The trace consumes accepted product semantics. Product ambiguity returns to Requirements Discussion; technical incompatibility with an accepted ADR creates a superseding decision after Human Review. + ```text Project Entry → Remote Project Discovery if Needed → Re-Adopt Agent Loop Project if Needed +→ Feature Monthly Archive If Explicitly Requested → Code-Guided Operational Support if Needed → Project Skill Creation / Update if Needed → Requirement Archive diff --git a/references/document-templates.md b/references/document-templates.md index e85e820..dbf421a 100644 --- a/references/document-templates.md +++ b/references/document-templates.md @@ -26,6 +26,7 @@ CLAUDE.md -> AGENTS.md -/ README.md requirement.md + YYYY-MM-DD-concept-foundation-.md optional append-only semantic follow-up prototype.png feedback.md design-link.md @@ -114,6 +115,8 @@ Requirement source files such as `requirement.md` are immutable by default. Life When requirements change, add a new file to the requirement set or create a new requirement set. Do not overwrite the old source requirement material. +After archive, requirement set README `Effective Concept Foundation` points to the current human-reviewed semantic source. It may record effective status, source, previous source, confirmation date, and reopen trigger, but it must not duplicate definitions or the Requirement Product Model. Later semantic changes use an append-only Concept Foundation follow-up or a linked/superseding requirement set after Requirement Conflict Review and human confirmation. + Future/deferred work and backlog items belong in requirement sets and optional `requirements/INDEX.md`, not in `project.md`. Use `Delivery Phases` in requirement set `README.md` when the requirement is too large for one feature, has MVP/later scope, crosses multiple boundaries, or needs staged human delivery confirmation: @@ -129,6 +132,14 @@ Use `Delivery Phases` in requirement set `README.md` when the requirement is too Before an accepted requirement enters feature construction, add or update this requirement README summary: ```md +## Effective Concept Foundation + +Status: not-recorded | candidate | accepted | reopened | concept-foundation-not-needed +Effective Source: requirement.md | YYYY-MM-DD-concept-foundation-.md | none +Previous Source: none +Last Confirmed: YYYY-MM-DD | none +Reason / Reopen Trigger: + ## Design Readiness Status: not-scanned | design-not-needed | candidate | required | completed @@ -167,41 +178,120 @@ Source: conversation | file | link | prototype | mixed ## Users / Operators -## Roles / Operators / Permission Boundary +## Goals -| Role / Operator | What They Do | Permission / Boundary | Notes | -|---|---|---|---| -| | | | | +## Concept Foundation -## Goals +Concept Foundation Status: candidate | accepted | reopened | concept-foundation-not-needed + +Not-Needed Reason: + +### Evidence And Scenarios + +- Success scenario: +- Failure / cancel / recovery scenario: +- Project Domain Language / source / code / test / historical feature evidence: + +## Concept Candidate Inventory + +| Concept ID | Candidate Name | Kind | Evidence / Example | Ambiguity / Conflict | Status | +|---|---|---|---|---|---| +| C-EXAMPLE | | entity / action / result / role / value | | | candidate / accepted / rejected | + +## Concept Definitions + +Use only fields that affect the current requirement. Write `n/a` plus a reason instead of inventing facts. + +| Concept ID | Canonical Name | Definition / Non-example | Identity | Owner | Lifecycle Boundary / State-bearing | Invariants | Product Fact Owner | Evidence | +|---|---|---|---|---|---|---|---|---| +| C-EXAMPLE | | | | | | | | | + +## Concept Relationships + +| Relationship ID | From Concept ID | Relationship | To Concept ID | Cardinality / Boundary | Invariant | Evidence | +|---|---|---|---|---|---|---| +| REL-01 | C-EXAMPLE | | C-OTHER | | | | + +### Blocking Ambiguities + +| Blocking Question | Recommended Definition | Evidence | Accept Impact | Reject Impact | Status | +|---|---|---|---|---|---| +| | | | | | open / resolved | + +### Human Confirmation + +- Confirmed Concept IDs: +- Human decision: +- Confirmed at: +- Remaining non-blocking unknowns: + +Do not continue to the derived product sections while status is `candidate` or `reopened`. ## Requirements ## Terminology / Domain Language -Use this when terms are ambiguous, overloaded, project-specific, or clarified through Requirement/Product Grill. +Derive canonical terms from accepted Concept IDs. This section may record display wording or aliases, but must not redefine accepted meaning. -| Term | Meaning In This Requirement | Avoid / Ambiguity | Promote To Project Domain Language | -|---|---|---|---| -| | | | yes / no / pending | +| Concept ID | Canonical Term | Allowed Alias / Display Wording | Avoid / Ambiguity | Promote To Project Domain Language | +|---|---|---|---|---| +| C-EXAMPLE | | | | yes / no / pending | + +## Role / Permission Matrix + +| Permission Rule ID | Role Concept ID | Product Object Concept ID | Read | Create | Advance / Decide | Cancel / Withdraw | Recover | Boundary / Evidence | +|---|---|---|---|---|---|---|---|---| +| PERM-01 | C-ACTOR | C-EXAMPLE | yes / no | yes / no | | | | | + +## Commands / Events + +| Action ID | Type | Name | Actor / Producer Concept ID | Target Concept ID | Preconditions / Guard | Result / Event | Evidence | +|---|---|---|---|---|---|---|---| +| CMD-01 | command / event | | C-ACTOR | C-EXAMPLE | | EVT-01 | | ## Primary Business Flow -Describe the normal business path in human-readable steps. Prefer concrete actor/system/state wording over abstract summaries. +Describe the normal business path in human-readable steps. Every step cites Action IDs and accepted Concept IDs and closes to a defined product terminal. + +| Flow Step ID | Actor Concept ID | Action ID | Input / Target Concept IDs | Product State Change | Result / Next Step | +|---|---|---|---|---|---| +| FLOW-01 | C-ACTOR | CMD-01 | C-EXAMPLE | | | + +## Product State Model + +| State Model ID | State-bearing Concept ID | From | Action / Event ID | Guard / Invariant | To | Terminal / Recovery | Forbidden Transition | +|---|---|---|---|---|---|---|---|---| +| STATE-01 | C-EXAMPLE | initial | CMD-01 | | active | no | | + +## Requirement Product Model + +This is a conceptual product model, not a table, document, topic, ledger, provider, or technical schema design. -1. +| Product Model ID | Product Object / Fact | Concept IDs | Identity / Relationship | Owner / Allowed Changer | Product Invariant | Product Fact Meaning | +|---|---|---|---|---|---|---| +| PM-01 | | C-EXAMPLE | | | | | ## Exception Paths -| Scenario | Trigger | Expected Handling | Recovery / Operator Action | Acceptance Direction | -|---|---|---|---|---| -| | | | | | +| Scenario ID | Concept / State / Action IDs | Trigger | Expected Handling | Recovery / Responsible Actor | Observable Result | Acceptance Direction | +|---|---|---|---|---|---|---| +| EX-01 | | | | | | | ## Data / Source of Truth -| Data / Fact | Source Of Truth | Who Can Change It | Consistency / Timing Notes | -|---|---|---|---| -| | | | | +Record product fact ownership or a Decision Candidate. Do not select a table/store/provider here. + +| Product Model ID | Product Fact | Product Fact Owner | Who Can Change It | Timing / Consistency Need | Decision Candidate | +|---|---|---|---|---|---| +| PM-01 | | | | | none / candidate | + +## Concept-To-Product Traceability + +Every derived row must cite accepted Concept IDs. Product Brief and Feature Spec consume these IDs instead of creating replacement definitions. + +| Trace ID | Accepted Concept IDs | Derived Model IDs / Sections | Product Rule / Meaning | Downstream Product Brief / Feature Spec Use | +|---|---|---|---|---| +| TRACE-01 | C-EXAMPLE | REL-01 / PERM-01 / CMD-01 / FLOW-01 / STATE-01 / PM-01 / EX-01 | | | ## Historical Behavior / Prior Conflicts @@ -260,7 +350,7 @@ Use only when staged delivery has been discussed and human-reviewed. If used, mi ## Source Conversation Summary ``` -When Requirement/Product Grill was used, do not leave these sections as empty headings. Fill the applicable sections with concrete terms, flows, exceptions, data facts, conflicts, scenarios, and candidates; for non-applicable sections, write `Not applicable` plus a short reason. +When Requirement/Product Grill was used, do not leave these sections as empty headings. Fill the applicable sections with concrete concepts, relationships, roles, actions/events, flows, states, product-model facts, exceptions, trace rows, historical conflicts, scenarios, and candidates; for non-applicable sections, write `Not applicable` plus a short reason. A triggered Concept Foundation must be `accepted` before derived product sections are written. A simple requirement uses `concept-foundation-not-needed` and a concrete reason instead of a large empty model. Write the human-reviewed document as `.agent-loop/requirements/-/requirement.md` after the human confirms the document should be recorded. Do not write it directly into a feature workspace. Feature `product.md` and `spec.md` derive from this source and link back to the requirement set. @@ -1111,4 +1201,17 @@ Status: active ## Pause / Resume Point ## Close Record + +Closed At: +Human Decision: + +## Archive Readiness + +Closed At: +Delivered Summary: +Verification: complete +Feature Close Review: complete +Drift: resolved +Project Memory Impact: complete | none +Open Follow-up: none | ``` diff --git a/references/feature-completion-check.md b/references/feature-completion-check.md index f5f2361..8c375d8 100644 --- a/references/feature-completion-check.md +++ b/references/feature-completion-check.md @@ -178,3 +178,19 @@ Record the check in `notes.md`: ``` If the feature is closed, also write the final `Close Record`. + +The same close update writes this deterministic Feature Monthly Archive readiness block in `notes.md`; it does not auto-archive and does not add a new Close Human Gate: + +```md +## Archive Readiness + +Closed At: +Delivered Summary: +Verification: complete +Feature Close Review: complete +Drift: resolved +Project Memory Impact: complete | none +Open Follow-up: none | +``` + +Only `Open Follow-up: none` is eligible for Feature Monthly Archive. Missing blocks, placeholder summaries, non-terminal values, or listed follow-up IDs remain blocked until a human-reviewed close-note correction; the archive scan never infers or rewrites them. diff --git a/references/feature-follow-up.md b/references/feature-follow-up.md index 2915925..9410d87 100644 --- a/references/feature-follow-up.md +++ b/references/feature-follow-up.md @@ -31,10 +31,9 @@ Default recent window: **30 calendar days** from the current date. Inspect: - `project.md` Active Feature, Paused Features, and recent feature references -- `.agent-loop/features/*/spec.md` -- `.agent-loop/features/*/tasks.md` -- `.agent-loop/features/*/tests.md` -- `.agent-loop/features/*/notes.md` +- flat recent `.agent-loop/features//spec.md`, `tasks.md`, `tests.md`, and `notes.md` +- root `.agent-loop/features/archive.md` after Active/Paused and flat recent candidates +- archived `.agent-loop/features/YYYY-MM//` artifacts only after the locator row resolves uniquely - close records, submit records, verification evidence, and drift notes - code paths, tests, APIs, data models, or UI routes mentioned by the bug/change - screenshot text, visible UI labels, error messages, stack traces, request/response samples, logs, test names, and file paths attached to the report @@ -45,6 +44,14 @@ The 30-day window is a default scan window, not a hard ownership boundary. If th Do not use day 31 as a reason to stop flow-back analysis. If evidence is weak after the extended scan, classify as `unclear` and recommend `investigate-first` rather than guessing. +## Archived Feature Owners + +Feature Monthly Archive changes location, not ownership. Lookup order is fixed: Active/Paused first, flat recent features second, `features/archive.md` third, archived feature artifacts fourth. + +When an archived closed feature is the confirmed owner, rehydrate before reopened execution. First run a read-only rehydrate scan, show the exact plan SHA-256 Batch Human Gate, then use the transaction journal, reference updates, post-check, and restore rules. Only after verified rehydrate may Feature Follow-up ask to change lifecycle from `closed` to `active` or start tasks. Archive state is not feature lifecycle. + +If the archive row target is missing, a month directory lacks its row, the same Feature ID exists flat and archived, a `rehydrated` row points to a month path, or `.archive-txn` is incomplete, stop and route to Recovery instead of guessing ownership. + ## Low-information Reports Some reports are too generic to identify feature ownership by themselves: diff --git a/references/human-review-summary.md b/references/human-review-summary.md index 33bae22..cfe3601 100644 --- a/references/human-review-summary.md +++ b/references/human-review-summary.md @@ -17,6 +17,8 @@ risk / blocker / human decision = never hidden Use before human confirmation for: +- Concept Foundation acceptance +- Decision & Design record creation, acceptance, compatibility update, or superseding decision - Project Entry / Project Entry Scan - legacy onboarding-db reference cleanup - Remote Project Discovery @@ -90,6 +92,55 @@ High-confidence rows can be drafted, but cannot become reviewed or written as ac ## Stage Table Patterns +### Concept Foundation Approval + +Use after the one-question-per-turn Human Grill Contract has resolved each blocker and before changing a triggered foundation to `accepted`. + +| Concept ID | Recommended Definition | Identity / Lifecycle Boundary | Relationship / State Impact | Evidence | Open Conflict | Human Decision | +|---|---|---|---|---|---|---| +| | | | | | none / blocking | accept / revise / keep candidate | + +Add: + +```text +Effective Concept Source: +Requirement Product Model derivation authorized: yes | no +Artifacts to write/update: +Recommended next stage: continue Requirements Discussion | Requirement Archive +``` + +This summary is cumulative confirmation of the current concept baseline. It does not replace the strict one-question-per-turn Grill used to resolve blocking meanings, and it does not accept implementation, create an ADR, archive files, or start a feature. + +### Decision & Design Approval + +Use before creating, accepting, superseding, or materially updating a project / cross-feature Decision & Design record. + +| Item | Review Content | +|---|---| +| Effective Requirement Source | effective source path, Concept Foundation status, Last Compatibility Check, and `current` / `review-required` | +| Requirement Model Scope | source total / in-scope / existing-decision / feature-local / proposed-decision / not-applicable / missing | +| Requirement Model Coverage | in-scope total / landed / existing-decision / feature-local / not-applicable / missing | +| Chosen Technical Decision | chosen option and the main rejected alternatives | +| Product Semantics Preserved | yes / no; list any product blocker that must return to Requirements Discussion | +| Migration / Compatibility / Rollout | triggered / not-triggered for each concern, with reason or linked section | +| Design Slice Ownership | planned / unassigned / deferred / out-of-scope, including affected Feature Specs | +| Verification | proof direction for every landed row and accepted-decision dependency | +| Human Decision | accept / revise / return to Requirements Discussion / require superseding ADR | + +Add: + +```text +ADR path: +Effective Concept Source: +Upstream Compatibility: current | review-required +Artifacts to write/update: +Recommended next stage: Decision & Design If Needed | Feature Spec | Requirements Discussion +``` + +The summary must expose `not-applicable`, `feature-local`, deferred, out-of-scope, missing, and compatibility-review items. ADR acceptance still requires explicit human confirmation; the summary does not replace the complete decision record or authorize a changed accepted decision to be rewritten in place. + +Before showing this summary, keep the ADR `proposed` and run structural preflight. If the human accepts, record `Decision: accepted`, `Confirmed By`, `Confirmed At`, and concrete `Evidence` in the ADR Human Review Evidence section, then rerun accepted-mode validation. Never infer acceptance from a validator pass or from the summary being displayed. + ### Feature Spec Approval | Item | Summary | @@ -183,6 +234,24 @@ If subagent scan results conflict, include: | Conflict | Finding A | Finding B | Evidence A | Evidence B | Current Judgment | Action | |---|---|---|---|---|---|---| +### Feature Monthly Archive Batch Human Review + +| Item | Summary | Evidence / Exact Value | Human Decision | +|---|---|---|---| +| Operation | archive / rehydrate | | confirm / revise | +| Plan SHA-256 | expected exact hash | | confirm exact batch | +| Selected Months / Feature IDs | explicit scope | | confirm / revise | +| Eligible | closed and complete | | include / exclude | +| Blocked | candidate plus blocker | | acknowledge / resolve | +| Moves | source -> target | | confirm | +| Reference Edits | path, old/new, occurrence, before/after hash | | confirm | +| Unchanged Content | whole feature contents and immutable requirement sources | | acknowledge | +| Transaction Journal / Restore | journal path, backups, reverse moves, post-check | | acknowledge | +| Platform evidence | macOS / Windows actual or test-defined | | acknowledge | +| Decision | apply exact hash / revise / stop | | human only | + +The scan is read-only. The Batch Human Gate authorizes only the displayed expected plan SHA-256; a stale plan requires a new scan and review. Feature Monthly Archive maintains `features/archive.md`; rehydrate before reopened execution. + ### Feature Completion Check | Check | Status | Evidence | Issue | diff --git a/references/onboarding-knowledge-base.md b/references/onboarding-knowledge-base.md index bea01ab..9ad313d 100644 --- a/references/onboarding-knowledge-base.md +++ b/references/onboarding-knowledge-base.md @@ -28,8 +28,9 @@ If existing `.agent-loop/onboarding-db/` files use an old layout, treat them as - Split a module/flow into a directory only when justified by size, independent subdomains, frequent separate updates, or explicit human request. - Use Mermaid flowchart / sequenceDiagram for normal flow and sequence diagrams; use ASCII 文本图 / 纯文本线框图 for state machines, complex principle diagrams, and complex examples. - When older docs say ASCII wireframe, interpret it as “choose the diagram format that best explains the boundary, state, and timing,” not as a generic stacked box. -- 状态图优先。每个正式 onboarding 文档至少包含架构/边界图和 ASCII 状态图 / 状态机图,用来讲清楚结构边界和状态怎么变化。 -- 模块和流程文档默认还必须包含 Timeline / 时序图,用来讲清楚流程怎么跑,并在流程讲解中引入相关数据模型。 +- `critical` / `important` 核心流程必须闭合到业务终态,并用 Core Flow Overview / Boundary、Timeline / Sequence、ASCII State Machine 三类互补表达;不能停在同步 `PROCESSING` 一类非终态结果。 +- 模块文档在具有真实状态、时间顺序或数据移动时使用架构/边界、状态、Timeline / 时序图;没有对应语义时不要编造图。 +- overview、domain、jobs/async、infra、deploy、runtime、change-guide 等内容文档按实际解释需要选图。glossary、静态配置清单、纯索引和其他 stateless topic 不强制状态图。 - Do not default complex flows to a stacked box diagram. stacked box diagram / 阶段堆叠图 is only acceptable for simple layer summaries. - Mermaid flowchart / sequenceDiagram can be the main diagram for normal flow and timing; ASCII remains preferred for state-machine / decision diagrams and complex examples. - Default narrative language is Chinese; preserve code symbols, file paths, commands, API paths, env vars, config keys, error messages, and third-party product names. @@ -122,6 +123,7 @@ Evidence Graph is a code-backed inventory, not a summary. It must identify: - bounded-context candidates; - module candidates; - flow candidates; +- core flow inventory and selection; - data object inventory; - async / job / callback inventory; - infra / dependency inventory; @@ -136,6 +138,42 @@ Evidence quality gate: - claims without concrete evidence stay in `Unknowns` and must not enter the formal Module Plan or Flow Plan; - examples must come from real tests, fixtures, API contracts, logs, configs, or code-constructed objects; if no real example exists, mark it as unknown instead of inventing one. +## Core Flow Inventory + +Evidence Graph must classify the discovered end-to-end business flows before Onboarding Spec. A core flow is defined by a business outcome and terminal states, not by one convenient synchronous call chain. + +Required fields for `critical` / `important` flows: + +| Field | Required meaning | +|---|---| +| Flow ID | stable ID used across Evidence Graph, Spec, Tasks, Flow doc, Coverage, and Review | +| Business Outcome | user/business result the flow exists to produce | +| Criticality | `critical`, `important`, or `supporting` | +| Trigger / Entry | external trigger plus concrete entry evidence | +| Success Terminal | observable business success terminal | +| Failure Terminals | failure, cancellation, unknown, or manual-handling terminals | +| Variants / Branches | synchronous, asynchronous, callback, fallback, or business variants | +| Participants / Owners | modules, services, external systems, and truth owners | +| State / Data Owners | state objects and source-of-truth owners | +| Async / Jobs / Callbacks | producers, consumers, jobs, callbacks, retries, and DLQ paths | +| External Side Effects | charge, quota, notification, delivery, event, or other visible effect | +| Recovery Responsibility | retry, rollback, compensation, reconciliation, or manual action | +| Evidence Chain | entry, state-write, async-handler, recovery, and terminal evidence with direction | +| Selection | `planned`, `deferred`, or `not-applicable` | +| Selection Reason | evidence-backed inclusion or deferral reason and impact | +| Confidence / Unknowns | confidence plus unresolved facts that block claims | + +Discovery must triangulate API/CLI/UI/webhook/consumer/job entries, core state writes, transaction/cache/message side effects, callback/retry/compensation/reconciliation paths, tests/contracts/logs/config/runbooks, and human-described business outcomes checked against code. + +Rules: + +- a non-terminal response such as `accepted`, `pending`, or `processing` does not close a core flow when later code owns the business terminal; +- callbacks, consumers, retry queues, DLQs, compensators, reconcilers, and jobs remain required slices when they own a core transition, side effect, or recovery responsibility, even if documented elsewhere too; +- every `critical` / `important` candidate must be `planned` or carry a concrete deferred reason, impact, missing evidence, and next action; +- a whole-project onboarding cannot be called complete while a discovered `critical` / `important` flow is merely deferred; focused onboarding must label its boundary and cannot claim whole-project readiness; +- a `supporting` flow stays lightweight unless it owns core state, an externally visible side effect, or recovery, in which case it is promoted into the applicable core flow's slice coverage; +- no evidence means Unknowns, not invented flow closure. + Minimum sections: ```md @@ -153,6 +191,9 @@ Minimum sections: ## Flow Candidates | Flow | Trigger | Participants | State Changes | External Dependencies | Evidence | Risk | +## Core Flow Inventory +| Flow ID | Flow | Business Outcome | Criticality | Trigger / Entry | Success Terminal | Failure Terminals | Variants / Branches | Participants / Owners | State / Data Owners | Async / Jobs / Callbacks | External Side Effects | Recovery Responsibility | Evidence Chain | Selection | Selection Reason | Confidence / Unknowns | + ## Relationship Wireframe ```text ┌──────────┐ ┌──────────┐ ┌──────────┐ @@ -197,6 +238,8 @@ This is the production spec for future Agents. It must define: - scope: whole project or focused area; - module plan; - flow plan; +- Core Flow Inventory selection for the accepted scope; +- Flow Slice Plan for every planned `critical` / `important` flow; - DDD bounded-context plan; - jobs / async / callback plan; - infra / deploy plan; @@ -209,6 +252,8 @@ This is the production spec for future Agents. It must define: - non-goals; - human confirmation questions. +Spec acceptance must confirm Core Flow Inventory selection. Every `critical` / `important` candidate is planned or explicitly deferred with evidence and impact; every planned core flow lists its success/failure terminals, known variants, required Slice IDs, and evidence chain. This is part of the existing Spec Acceptance Gate, not a new gate. + Use this gate sequence: ```text @@ -238,6 +283,7 @@ Rules: - split tasks by batch, not by full tree generation; - each batch covers a coherent set of topics; batch size is an execution/review pacing tool, not a human approval boundary; - each task lists output path, evidence required, and quality gate; +- each core-flow task lists Flow ID, required Slice IDs, default and complexity-triggered diagrams, Diagram IDs, evidence requirements, Completeness Hard Gate, and quality-score target; - present the completed task ledger and Full Execution Gate for explicit human acceptance before formal-doc execution; - after the Full Execution Gate is accepted, create and complete all planned docs that can be written with meaningful evidence-backed content; - do not create thin planned files for topics that are still unknown; track those topics in `coverage-matrix.md` and `onboarding-tasks.md`; @@ -276,9 +322,9 @@ Required sections: Minimum content: -- at least one Architecture / Boundary Diagram, using Mermaid flowchart or ASCII as appropriate; -- at least one ASCII state diagram / state-machine diagram; -- at least one Timeline / sequence diagram that explains how the module flow runs over time; +- an Architecture / Boundary Diagram when module boundaries, ownership, or dependencies need a visual explanation; +- an ASCII state diagram / state-machine diagram when the module owns a real lifecycle, decision, or recovery state; +- a Timeline / sequence diagram when ordered calls, state phases, async interactions, or data movement exist; - a process narrative that introduces referenced data models while explaining the flow; - 工作原理与示例 section with 关键机制, principle / example diagram, and evidence-backed examples; - example trace with diagram when the module has non-trivial state or data movement; @@ -306,35 +352,77 @@ Required sections: ```md # Flow: -## 1. 用例 -## 2. 架构/边界图 -## 3. ASCII 状态图 / 状态机图 -## 4. Timeline / 时序图 -## 5. 阶段说明 -## 6. 数据流转 -## 7. 状态变化 -## 8. 示例 -## 9. 失败路径 -## 10. 排障路径 -## 11. 变更指南 -## 12. 代码证据 +## 1. Flow Identity And Outcomes +## 2. Flow Slice Coverage +## 3. Core Flow Overview / Boundary +## 4. ASCII State Machine / Decision +## 5. Timeline / Sequence +## 6. Diagram Explanations / Complexity-Triggered Diagrams +## 7. 阶段说明 +## 8. 数据流转 +## 9. 状态变化 +## 10. 示例 +## 11. 失败路径 +## 12. 排障路径 +## 13. 变更指南 +## 14. 代码证据 +## 15. 自检 +``` + +Every `critical` / `important` flow must include business terminals, required Slice IDs, trigger, participants, Architecture / Boundary Diagram, ASCII state diagram / state-machine diagram, Timeline / sequence diagram, phase explanation, data transfer, state change, success path, failure path, retry/compensation/degradation, example request/object, troubleshooting path, change guide, and code evidence. Supporting flows may stay lighter unless they own core state, external side effects, or recovery. 流程讲解时顺带解释涉及的数据模型 so the reader learns objects through the running flow instead of from a detached list. + +## Flow Slice Coverage + +Every planned `critical` / `important` flow must trace its required main, branch, failure, and recovery slices. A slice is a real behavior unit, not a document heading. + +```md +| Flow ID | Slice ID | Path Kind | Trigger / Precondition | Owner | Input / Output | Action | State Read / Written | Transition | Sync / Async / External | Failure Result | Recovery | Evidence | Diagram IDs | Document Section | Coverage Status | ``` -Every flow must include trigger, participants, ASCII Architecture / Boundary Diagram, ASCII state diagram / state-machine diagram, Timeline / sequence diagram, phase explanation, data transfer, state change, success path, failure path, retry/compensation/degradation, example request/object, troubleshooting path, change guide, and code evidence. 流程讲解时顺带解释涉及的数据模型 so the reader learns objects through the running flow instead of from a detached list. +`Path Kind` is `main`, `branch`, `failure`, or `recovery`. `Coverage Status` is `covered`, `inferred`, or `blocked`. + +A slice is critical when it changes core business state, causes an externally visible side effect, determines a business terminal, owns retry/compensation/reconciliation/manual recovery, or crosses transaction, lock, idempotency, billing, quota, credential, permission, or security boundaries. Each critical Slice ID must map to concrete evidence, at least one Diagram ID, and a narrative section. A `blocked` or missing critical slice makes the flow incomplete. Examples must be evidence-backed. Prefer real tests, fixtures, API contracts, logs, configs, or code-constructed request/event objects. If an example is inferred, label it as inferred and explain the evidence gap. For billing, wallet, quota, apikey, order, balance-related, or message-retry flows, include consistency / idempotency / compensation details: idempotency key, transaction boundary, retry behavior, reconciliation path, and how to inspect mismatched state. +For apikey / token / credential modules or flows, include generation, hash/encrypted storage, masked display, permission scope, expiry/rotation/revocation, leak response, and audit logs. Do not evade the security lifecycle by documenting only a flow and omitting its module doc. + For gateway/runtime flows involving Nginx, OpenResty, ingress, API gateway, reverse proxy, sidecar, or runtime routing scripts, include route matching, header propagation, auth/rate-limit behavior, timeout/retry/upstream behavior, and access/error log fields. ## Diagram Rules -Any content-bearing onboarding-db document must include the required diagram set for its document type. Content-bearing means formal onboarding docs that teach project behavior, such as overview, architecture, code organization, domain, module, flow, jobs/async, infra, deploy, runtime, and change-guide docs. +Any content-bearing onboarding-db document must use diagrams that explain real semantics for its document type. Core flow docs have a fixed default diagram set. Module and other content docs use relevant diagrams when they have real boundary, state, timing, data, decision, or recovery semantics; stateless docs do not invent diagrams to satisfy a quota. + +Explicitly exempted docs are control/review artifacts such as `onboarding-spec.md`, `onboarding-tasks.md`, `coverage-matrix.md`, `batch-review.md`, `08-review/evidence-graph.md`, `08-review/open-questions.md`, and `08-review/human-review-summary.md`. For other docs, Diagram Plan records the real semantics and selected view rather than forcing an irrelevant diagram. + +## Core Flow Diagram Set -Explicitly exempted docs are control/review artifacts such as `onboarding-spec.md`, `onboarding-tasks.md`, `coverage-matrix.md`, `batch-review.md`, `08-review/evidence-graph.md`, `08-review/open-questions.md`, and `08-review/human-review-summary.md`. If a planned content doc cannot reasonably include the required diagram set, record the explicit exemption and reason in the Diagram Plan before writing that doc. +Every `critical` / `important` core flow uses these complementary views: -状态图优先;每个正式 onboarding 文档至少包含架构/边界图和 ASCII 状态图 / 状态机图。模块文档默认必须包含架构/边界图、ASCII 状态图、Timeline / 时序图。流程文档默认必须包含架构/边界图、ASCII 状态图、Timeline / 时序图。Timeline / 时序图 is required by default for module and flow docs because onboarding must explain how the process runs over time. Sequence and flow diagrams may use Mermaid because normal Markdown Mermaid is easier to read for calls and flowcharts. State machine diagrams and complex example diagrams should use ASCII because they often need compact annotations, recovery branches, and data-object notes. List state diagrams and timeline/sequence diagrams before optional supporting diagrams in planning and review so Agent does not default to detached static inventories. Choose extra diagram types by need; do not force every topic into one stacked box. +- Core Flow Overview / Architecture / Boundary: scope, owners, main/branch paths, data boundaries, and terminals; overview and boundary may be one diagram when all responsibilities remain visible; +- Timeline / Sequence Diagram is the primary per-flow narrative for ordered calls, data objects, state reads/writes, messages, and external effects; +- ASCII State Machine / Decision: state transitions, invalid transitions, failures, retries, compensation, and recovery. + +Each diagram has a stable Diagram ID and explanation: what to inspect, conclusion, Covered Slice IDs, data/state/message/config objects, code evidence, and any inferred content. A generic A→B→C flowchart or one copied diagram cannot satisfy multiple responsibilities without showing the required semantics. + +## Complexity-Triggered Diagrams + +Add diagrams only when the signal exists: + +| Signal | Diagram | +|---|---| +| callback, retry, compensation, reconciliation, eventual consistency | Failure Recovery Timeline | +| DTO/command/entity/DB/event transformations | Data Lineage / Object Transformation | +| transaction, lock, Lua, outbox, concurrency, idempotency | Transaction / Concurrency Boundary | +| multiple topics, consumers, retry queues, DLQ | Async Message Topology | +| routing, permission, provider, or policy decisions | Decision Tree | +| entity relations affect understanding | ERD / Model Relationship | +| gateway, sidecar, environment topology affects behavior | Runtime / Deployment Topology | +| troubleshooting entry points are distributed | Observability / Troubleshooting Map | + +Diagram Plan records the complexity signal and Covered Slice IDs. Overview/domain/jobs/infra/deploy/runtime/change-guide docs select relevant views; glossary, static config lists, pure indexes, and other stateless topics do not require state diagrams. An exemption cannot hide state, branch, timing, or recovery semantics that really exist. Every diagram must have an explanation. 每张图必须带讲解,否则读者只能猜图在表达什么。The explanation must state what to look at, what conclusion the diagram supports, which data objects / state fields / messages / configs appear, and the concrete code evidence behind the diagram. @@ -344,7 +432,7 @@ Recommended diagram types: |---|---|---| | Architecture / Boundary Diagram | 架构/边界图 / ASCII 架构图 / Mermaid flowchart | module location, boundaries, inbound/outbound, ownership, component relations, Redis key layout, wallet type structure, gateway / DB / MQ dependency | | ASCII State Machine / Decision Diagram | ASCII 状态机图 / 状态机/决策图 | state transition, validation decision, lock/Lua/Kafka/Rollback paths, retry/compensation | -| Timeline / Sequence Diagram | Timeline / 时序图 / Mermaid sequenceDiagram / 纯文本时序图 | required by default for module and flow docs; ordered interactions, request/response, async publish/consume, retry windows, delayed consistency | +| Timeline / Sequence Diagram | Timeline / 时序图 / Mermaid sequenceDiagram / 纯文本时序图 | primary for critical/important flows; used for modules when ordered interactions, request/response, async publish/consume, retry windows, or delayed consistency exist | | ASCII Swimlane Diagram | ASCII 泳道图 | optional supporting diagram when roles/systems are important and the reader must see who owns each action | | Timeline Diagram | 时间线图 | incident recovery, retry windows, delayed consistency, callback/reconciliation history | @@ -352,7 +440,7 @@ Required diagram set: 1. 架构/边界图:先讲模块在哪、结构边界、组件关系、数据/依赖位置;可用 Mermaid flowchart 或 ASCII,选择更清楚的格式。 2. ASCII 状态图 / 状态机/决策图:再讲状态怎么变、异常怎么恢复。 -3. Timeline / 时序图:对 module / flow 默认必填,讲流程按时间怎么跑、谁先读写什么、哪个阶段引入哪些数据模型;优先用 Mermaid sequenceDiagram,必要时可用纯文本时序图。 +3. Timeline / 时序图:对 critical/important flow 默认必填;module 在存在时间顺序或数据移动时使用。讲流程按时间怎么跑、谁先读写什么、哪个阶段引入哪些数据模型;优先用 Mermaid sequenceDiagram,必要时可用纯文本时序图。 Optional supporting diagrams: @@ -566,12 +654,29 @@ Topic types: - deploy; - change-guide. +## Completeness Hard Gate + +Completeness is evaluated before quality score. For every planned `critical` / `important` flow: + +A missing critical slice cannot be averaged away by diagram presence, readability, or any other quality score. + +- business success and failure/cancellation/unknown/manual terminals are identified; +- every required critical Slice ID appears in the accepted plan/task and Flow Slice Coverage; +- each critical slice maps to evidence, Diagram IDs, and a narrative section; +- required callbacks, consumers, retries, DLQs, compensators, reconcilers, and jobs cannot be removed by renaming them as future topics; +- complexity-triggered diagrams exist when their signal applies; +- `blocked`, missing, or unjustifiably deferred critical slices force `FAIL` and prevent `newcomer-ready`. + +The hard gate is an Agent quality decision inside the accepted scope, not a Human Gate. A focused scope may pass for its explicitly named boundary, but cannot be represented as whole-project readiness. + ## Review Score Every batch must score changed topics: | Dimension | Meaning | |---|---| +| Core flow discovery completeness | critical/important outcomes, terminals, variants, owners, side effects, and recovery are discovered or explicitly blocked | +| Slice and branch coverage | main, branch, failure, and recovery slices are traceably covered | | Required diagram set present | required architecture/boundary + state + timeline/sequence set is present unless explicitly exempted | | Architecture diagram clarity | boundaries, dependency direction, state ownership, and infra/data boundaries are visible | | State diagram clarity | state changes, decisions, failures, retries, and recovery paths are understandable | @@ -582,13 +687,15 @@ Every batch must score changed topics: | Code evidence | concrete files/symbols/behavior support claims | | Evidence granularity | key claims include file path, symbol/config key, and direction of call/data flow | | Example authenticity | examples come from tests, fixtures, API contracts, logs, configs, or code-constructed objects | -| Failure troubleshooting | failures are actionable | +| Failure / recovery | failures, retries, compensation, reconciliation, and recovery terminals are complete | +| Troubleshooting | symptoms, identifiers, logs, fields, and inspection order are actionable | | Consistency / gateway risk | idempotency, compensation, reconciliation, gateway routing, timeout, retry, and log fields are covered where applicable | | Change guidance | future changes have safe reading/verification path | | Newcomer readability | a newcomer can read continuously | Rules: +- Completeness Hard Gate must pass before scoring; - below 4/5 cannot be `newcomer-ready`; - below 3/5 must enter next batch or be `blocked-by-unknown`; - review must record gaps and next action. @@ -620,14 +727,17 @@ Do not call onboarding complete unless: - Project Entry Scan or reliable project memory exists; - Evidence Graph exists; +- Core Flow Inventory covers the accepted whole-project or focused scope; - Onboarding Spec was accepted; - Onboarding Tasks exist for completed and next batches; +- the Full Execution Gate was accepted; +- every planned `critical` / `important` flow passes the Completeness Hard Gate; - current batch files contain architecture/boundary diagrams, ASCII state diagrams, Timeline/sequence diagrams for module/flow docs, use cases, data objects, state transitions, failure modes, code evidence, and verification/troubleshooting where applicable; - coverage matrix records all discovered core topics; - changed topics were scored; - low score / low-score topics are not marked newcomer-ready; - batch review records evidence read, gaps, scores, and next batch; - no unresolved placeholders, empty required rows, `TBD`, `TODO`, `待补充`, or vague “see code / 看代码” evidence remain in submitted batch files; -- human has reviewed or explicitly paused the current batch. +- batch review records current status and gaps; batch does not add a Human Gate. Exit with exactly one recommended next action: next onboarding batch, focused update, Project Memory Update, Code-Guided Operational Support, Decision & Design If Needed, Product Brief If Needed, Feature Spec, Pause, or Close Onboarding Work. diff --git a/references/product-brief.md b/references/product-brief.md index 0711143..45162ac 100644 --- a/references/product-brief.md +++ b/references/product-brief.md @@ -14,6 +14,21 @@ project.md Domain Language = durable business terminology `product.md` belongs to one feature. Long-term product consensus must be proposed for `project.md` during Project Memory Update. +## Accepted Concept Foundation + +Product Brief consumes the accepted Requirement Product Model from its source requirement. It does not define a competing product language. + +Before synthesis: + +1. resolve requirement README `Effective Concept Foundation` when present, otherwise use the backward-compatible status in the human-reviewed requirement document; +2. require `accepted`, or a reasoned `concept-foundation-not-needed` for a simple requirement; +3. load the effective human-reviewed source and the Concept IDs / Requirement Product Model row IDs relevant to this feature; +4. record the effective source plus those IDs under `Accepted Concept References` and `Requirement Product Model Coverage` in `product.md`. + +If status is `candidate` or `reopened`, return to Requirements Discussion and the Human Grill Contract. Do not use Open Product Questions to carry an unresolved product-semantic blocker into Product Brief. + +Product Brief may select a feature slice, journey, scope, and product tradeoff from accepted concepts. It must not redefine an accepted Concept ID's canonical name, identity, owner, relationship, lifecycle, invariant, state, terminal meaning, or product fact. A semantic change reopens the source Concept Foundation. + ## Product Brief Source Gate If the latest human message comes from `chat` or `requirements-discussion` and asks to write `product.md`, create a Product Brief, or “落到 product.md”, do not create feature `product.md` directly. @@ -61,6 +76,7 @@ Include: - historical compatibility - open product questions - terminology used in this feature +- accepted Concept IDs and Requirement Product Model coverage - long-term consensus candidates Product Decisions must record status, evidence/source, human gate, and Decision & Design routing when applicable. @@ -128,3 +144,5 @@ Product Brief: product.md - testing implications Do not duplicate all product prose in `spec.md`. + +Do not duplicate Concept Foundation definitions either. `product.md` and `spec.md` cite accepted Concept IDs and model rows; the effective human-reviewed requirement source remains product-semantics authority. diff --git a/references/project-decisions.md b/references/project-decisions.md index 6a6bce5..3a0444e 100644 --- a/references/project-decisions.md +++ b/references/project-decisions.md @@ -8,6 +8,10 @@ Requirement -> Design Readiness Check -> Decision & Design If Needed -> Feature Requirement explains what humans want and how success is recognized. Decision & Design explains how the complete requirement can work across features and which shared rules every feature must preserve. Feature artifacts implement and verify assigned design slices. +PRD / Requirement Product Model owns product meaning. ADR consumes accepted product semantics and is responsible for later technical landing; it must not redefine accepted Concept IDs, identity, lifecycle, relationships, invariants, product states, terminal meaning, or product fact ownership. + +Concept Foundation and Requirement Product Model do not choose tables, stores, event topics, providers, or other technical representations. Decision & Design may select those representations only after requirement acceptance and through its own Human Gate, while preserving accepted product meaning. + Design Readiness Check is a required method at Requirement Archive, Product Brief, and Feature Spec boundaries; it is not a standalone stage. Decision Scan / Placement remains a lightweight method inside Decision & Design. It decides where a decision belongs; it is not the whole design stage. @@ -35,6 +39,8 @@ Before Decision & Design, Product Brief, or Feature Spec: 5. Do not create a duplicate decision merely because an existing accepted decision was not linked from the current feature yet; propose the missing reference instead. 6. Before writing Product Brief or Feature Spec, present missing Applicable Decision references for human confirmation and backfill the approved links. +Feature Monthly Archive changes the path of historical ownership, not accepted decision meaning. An ADR `feature-local` or Design Slice owner may resolve an archived closed Feature Spec through the stable Feature ID plus `features/archive.md`; the locator row and month path must agree, and `closed` is historical coverage only. New work or reopened execution must rehydrate the owner to a flat path first. Archive/rehydrate may update only the approved locator/path reference and must not rewrite accepted ADR content, status, rationale, Human Review Evidence, or product semantics. + ## Timing Design Readiness starts during requirement shaping as soon as the agent can see cross-feature or end-to-end design needs, but a decision file is not created from the first fuzzy idea. @@ -132,6 +138,114 @@ The agent must not: The decision file status cannot become `accepted` without explicit human confirmation. +## Effective Requirement Snapshot + +Every ADR driven by a Requirement Product Model resolves the requirement README `Effective Concept Foundation` block before technical design. If the block exists, follow `Effective Source`; older requirement sets may use the reviewed requirement document as a backward-compatible source. + +Record this snapshot near the ADR header: + +```text +Effective Concept Source: +Concept Foundation Status: accepted | concept-foundation-not-needed +Accepted Concept IDs: +Accepted Requirement Model IDs: +Upstream Compatibility: current | review-required +Last Compatibility Check: +Trace Applicability: required | not-applicable +Trace Not-Applicable Reason: +``` + +Rules: + +- a triggered Concept Foundation must be `accepted`; `candidate` or `reopened` returns to the Human Grill Contract before ADR work continues +- list only accepted Concept IDs and the accepted Requirement Model IDs declared inside this ADR's coherent decision scope +- cite or concisely summarize unchanged accepted meaning; do not copy a new definition into the decision +- resolve the effective source again before ADR acceptance and before a dependent Feature Spec, Plan, or implementation begins +- `Upstream Compatibility` is a dependency judgment, not a new decision status +- when the effective source is reasoned `concept-foundation-not-needed`, record both accepted-ID fields as `none`, set trace applicability to `not-applicable`, give a concrete reason, and do not invent product-model tables + +## Requirement Model Scope Inventory + +Before choosing the ADR scope, inventory every stable Requirement Model ID in the effective source: relationship (`REL-*`), permission (`PERM-*`), command/event (`CMD-*` / `EVT-*`), flow (`FLOW-*`), state (`STATE-*`), product model (`PM-*`), and exception/recovery (`EX-*`). Give each source ID exactly one scope disposition: + +```text +in-scope | covered-by-accepted-decision | feature-local | proposed-decision | not-applicable +``` + +- `in-scope` names this ADR; these IDs exactly match `Accepted Requirement Model IDs` and the Technical Landing Trace rows +- `covered-by-accepted-decision` names an existing accepted decision Markdown path +- `feature-local` names an existing Feature Spec path, or an explicit canonical future path prefixed with `planned:` +- `proposed-decision` names an existing decision draft, or an explicit canonical future path prefixed with `planned:` +- `not-applicable` begins with `reason:` and gives a concrete scope reason + +The inventory is a section of the existing ADR, not a new mapping artifact. It makes out-of-scope ownership visible and prevents an Agent from shrinking the snapshot until an inconvenient source model disappears. + +## Requirement Model Technical Landing Trace + +Use one generic trace table inside the ADR: + +| Requirement Model Ref | Accepted Meaning / Constraint | Disposition | Technical Landing | Preserved Invariant | Design Slice | Verification | +|---|---|---|---|---|---|---| +| `` | link or concise unchanged meaning | landed / covered-by-accepted-decision / feature-local / not-applicable | | | | | + +Every in-scope accepted relationship, permission rule, command, event, flow step, state rule, product-model row, and exception/recovery row needs exactly one disposition. Do not require an ADR to copy the whole PRD; the Scope Inventory accounts for the complete stable-ID set, while the snapshot and trace declare the coherent scope handled or deliberately delegated by this ADR. + +Disposition rules: + +- `landed`: name a concrete technical landing, preserved invariant, Design Slice, and verification target +- `covered-by-accepted-decision`: reference the existing accepted decision Markdown path that owns the landing; do not duplicate its technical reasoning +- `feature-local`: name an existing Feature Spec path or an explicit `planned:features//spec.md` path plus verification direction; shared constraints cannot be hidden as feature-local +- `not-applicable`: state a concrete reason and show it in the Decision & Design Human Review Summary + +ADR must not create, rename, split, merge, or redefine a Concept, relationship, role/permission, command/event, business flow, product state, invariant, exception/recovery meaning, or product fact ownership. If accepted meaning is missing or insufficient, return to Requirements Discussion rather than filling the gap in technical design. + +## Coverage Hard Gate + +A decision cannot become `accepted` while coverage is missing or Upstream Compatibility is `review-required`. + +The Agent first runs structural preflight while the ADR remains `proposed`. Only after preflight succeeds does it present the Decision & Design Human Review Summary. Explicit human acceptance authorizes the Agent to record Human Review Evidence, change the status to `accepted`, and rerun accepted-mode validation. A validator pass never grants acceptance by itself. + +Before asking for ADR acceptance or allowing a dependent Feature Spec: + +1. resolve the effective source and confirm compatibility is `current`; +2. confirm every stable Requirement Model ID in the effective source has one Scope Inventory row; +3. confirm the in-scope inventory IDs exactly equal the snapshot IDs and trace rows; +4. confirm every `landed` row has a concrete Technical Landing, Preserved Invariant, Design Slice, and Verification target; +5. confirm accepted-decision paths exist and are accepted; confirm feature-local paths either exist or use an explicit canonical `planned:` path; +6. present every `not-applicable`, `feature-local`, proposed-decision, deferred, and out-of-scope item to the human; +7. confirm every implementation-bearing technical rule is represented in Design Slice Coverage and no required slice remains `unassigned`; +8. confirm no unresolved product-semantic blocker remains. + +An `Applicable Decisions` reference proves awareness only. It cannot replace Requirement Model coverage, Design Slice ownership, or verification. + +## Upstream Compatibility And Drift + +Re-run compatibility review when the requirement README effective source changes or newly accepted requirement evidence changes Concept IDs, Requirement Model IDs, or their accepted meaning. + +1. set the dependency judgment to `Upstream Compatibility: review-required`; +2. stop new dependent Feature Spec, Plan, and implementation work; +3. compare old/new effective sources, Concept IDs, Requirement Model IDs, and affected trace rows; +4. if accepted product meaning changed but the existing technical decision remains valid, update only the snapshot and trace after Decision & Design Human Review; +5. if the chosen technical boundary, representation, recovery, compatibility, NFR conclusion, or accepted decision meaning no longer holds, create a Human-gated superseding ADR; +6. preserve the accepted ADR for audit; do not rewrite its decision meaning in place. + +Compatibility review may add references or current evidence to an accepted record only when repository policy permits append-only metadata. It must not use a metadata update to disguise a changed decision. + +## Triggered Operational Landing + +Operational landing detail is conditional. Assess these concerns before the ADR Human Gate: + +| Concern | Trigger | Required Result | +|---|---|---| +| Migration / Backfill | persistence representation or existing durable data changes | expand migration/backfill design | +| Compatibility | protocol, consumer, provider, or version compatibility changes | expand compatibility design | +| Rollout / Cutover | runtime boundary, traffic path, or staged activation changes | expand rollout/cutover design | +| Rollback / Reversibility | safe reversal needs technical work or data repair | expand rollback/reversibility design | + +For each concern, record `triggered` with its ADR section or `not-triggered` with one concrete reason. Do not add empty operational sections by default, and do not copy a domain-specific action, vendor, storage choice, protocol, or rollout topology from examples into a new decision. + +Before creating or accepting the ADR, load `references/human-review-summary.md` and present the Decision & Design Human Review Summary. The summary exposes effective source, source-wide scope inventory, coverage counts, chosen decision, preserved product semantics, operational triggers, Design Slice ownership, verification direction, blockers, and the explicit human decision. It does not replace the ADR. An accepted ADR records `Decision`, `Confirmed By`, `Confirmed At`, and concrete evidence in its Human Review Evidence section. + ## Relationship Model Use these relationship fields consistently: @@ -147,7 +261,7 @@ Use these relationship fields consistently: Requirement README owns lifecycle, phase mapping, triggered decisions, and implemented-by tracking. -Decision files own the design reasoning: goals, domain concepts, business flow, chosen option, architecture design, consequences, and verification closure. +Decision files own technical design reasoning: goals, accepted product-concept references, business-flow landing, chosen option, architecture design, consequences, and verification closure. The source PRD / Requirement Product Model continues to own product definitions. Feature `product.md` and `spec.md` reference applicable decisions and state which part of a decision they implement. They do not restate the whole decision. diff --git a/references/project-guidance.md b/references/project-guidance.md index dc207f1..f8948f0 100644 --- a/references/project-guidance.md +++ b/references/project-guidance.md @@ -10,6 +10,8 @@ AGENTS.md / CLAUDE.md = agent startup guidance .agent-loop/project/* = optional enterprise project memory details .agent-loop/remote.md = local entry pointer for remote projects .agent-loop/features/* = feature execution state +.agent-loop/features/archive.md = Feature Monthly Archive locator for stable Feature IDs +.agent-loop/features/YYYY-MM/* = Human-gated closed-history month archive; never an execution path .agent-loop/requirements/-/* = human source material package .agent-loop/skills/INDEX.md = optional project-skill lifecycle and discovery index .agent-loop/skills//* = optional human-confirmed project-local capability @@ -19,6 +21,8 @@ Default memory root is `.agent-loop/` because it is workflow metadata, not produ Do not use `AGENTS.md` as a task log. Do not use `project.md` as the startup instruction file for every agent. +Root guidance may navigate a user Agent to the Feature Monthly Archive procedure, but it must not teach manual directory movement. Active/blocked/paused work remains at the first level. Archive and rehydrate use the canonical Python scan/check/apply/restore commands, an exact plan SHA-256 Human Gate, transaction recovery, and post-check; execution resumes only after rehydrate restores the flat feature path. + ## Root Agent Bootstrap Gate Root `AGENTS.md` is the bootstrap node that teaches future agents how to enter `agent-loop`. It is not optional project decoration. @@ -34,7 +38,7 @@ Every time `agent-loop` is used inside a target project, check root guidance bef 4. Apply the Bootstrap Protocol skill-loading step when the runtime exposes the agent-loop skill. 5. Check whether AGENTS.md contains the required bootstrap sections. 6. If AGENTS.md uses agent-loop managed blocks, compare each managed block `section` and `block-version` with the current root AGENTS template. -7. If `scripts/check-root-agents-blocks.sh` is available in the local `agent-loop` skill package, run it against the current `templates/root-AGENTS.md` and target root `AGENTS.md`; use its report as the managed-block drift evidence. +7. If `scripts/check-root-agents-blocks.py` is available in the local `agent-loop` skill package, run it with Python 3.10+ against the current `templates/root-AGENTS.md` and target root `AGENTS.md`; use its report as the managed-block drift evidence. Use `python3` on macOS or `py -3` / `python` on Windows. If Python 3.10+ is unavailable, fail closed and report the capability gap instead of using an obsolete checker implementation. 8. If `.agent-loop/skills/INDEX.md` exists, read its metadata, verify each referenced `active` path and exact INDEX row plus instruction-bearing/executable files against the SHA-256 manifest, and exclude missing, mismatched, `proposed`, `disabled`, and `deprecated` skills from normal routing. 9. Record or update guidance status in project.md. 10. If missing or stale, propose a repair through Human Review Summary. @@ -44,13 +48,13 @@ Every time `agent-loop` is used inside a target project, check root guidance bef - project uses `agent-loop` - Bootstrap Protocol is missing skill-loading/fallback rules: root guidance must say root `AGENTS.md` is a bootstrap cache rather than a replacement for the `agent-loop` skill, must load/use the available skill before agent-loop workflow decisions, and must say Stage Helper Capability Scan happens only after the controller is active or unavailable/load-failed -- Message Intent Guard: distinguish `chat`, `requirements-discussion`, `project-skill-management`, and `feature-request` before project-state routing; chat answers only, requirements discussion uses Brainstorm / Clarify to produce requirement documents under `.agent-loop/requirements/`, and project-skill management routes to Project Skill Creation / Update +- Message Intent Guard: distinguish `chat`, `requirements-discussion`, `project-skill-management`, and `feature-request` before project-state routing; chat answers only, requirements discussion uses Brainstorm / Clarify to produce requirement documents under `.agent-loop/requirements/`, applies the internal Concept Foundation Gate before downstream product modeling when triggered, and project-skill management routes to Project Skill Creation / Update - Workflow Stage Map: route common human/project signals to exactly one next stage and its matching detailed references; root guidance is navigation only, not the detailed stage procedure - Bootstrap Protocol: inspect `.agent-loop/`, classify the stage, and recommend exactly one next action - Agent Ownership: agents steer the loop instead of waiting for the human to name every step - Stage Helper Capability Scan: agents actively check available skills/plugins/helpers before fallback stage guidance - Gate Modes: Strict Mode, Feature Auto-Loop, Task Auto-Run, and their explicit human enablement rules -- Required Stops: unclear scope, risky changes, Delivery Contract gates, Project Skill Gate 1, per-invocation Project Skill Execution Gate, subagent dispatch, submit, close, commit, PR, merge, release, publish +- Required Stops: unclear scope, unresolved Concept Foundation Gate, downstream redefinition of accepted product semantics, unresolved ADR Effective Requirement Snapshot, incomplete source-wide Requirement Model Scope Inventory / Technical Landing Trace, `Upstream Compatibility: review-required`, risky changes, Delivery Contract gates, Project Skill Gate 1, per-invocation Project Skill Execution Gate, subagent dispatch, submit, close, commit, PR, merge, release, publish - Completion Rules: fresh verification, review, drift check, project memory update, Feature Completion Check, Feature Close Review - Feature Follow-up / Flow-back: bugs, regressions, screenshots, QA feedback, API mismatches, and small tweaks are checked against active/paused/closed recent features before new feature creation or code edits, but only after Project Entry has established or verified agent-loop memory - Submit And Commit Rules: submit/commit/PR/merge/release/publish require explicit confirmation and only intended files are included @@ -139,7 +143,7 @@ Rules: - Managed block maintenance rules belong here and in refresh tooling; do not require the target root `AGENTS.md` to include a separate Managed Block Rule prose section. - If an existing `AGENTS.md` has no managed blocks, propose adding the minimal needed managed blocks instead of replacing the whole file. - If a block-version is missing or older than the current template, treat that block as stale. -- Treat bare skill-version-only block revisions such as `block-version:1.2.4` as stale because they cannot distinguish same-version template revisions. +- Treat bare skill-version-only block revisions such as `block-version:1.3.0` as stale because they cannot distinguish same-version template revisions. - If a managed block exists in the current template but is missing from root AGENTS.md, treat it as a missing managed block and propose adding it. - If a managed block source is missing, stale, or contradictory, classify the block as stale and propose either source correction or block refresh through Human Review Summary. - If marker pairs are broken, duplicated, nested, or ambiguous, stop and ask before editing. @@ -158,7 +162,7 @@ Managed block detection checklist: 8. Check whether each `source` path exists or is intentionally external/deferred before relying on it. 9. If any check fails, classify root guidance as `stale-marker` and stop before editing `AGENTS.md`. -If `scripts/check-root-agents-blocks.sh` is available, use it as the first read-only managed-block drift check. The script validates section presence, marker integrity, per-section `block-version`, unexpected managed sections, and local `source` paths. Its output is evidence for the Human Review Summary; it must not be treated as approval to write. +If `scripts/check-root-agents-blocks.py` is available, run it with Python 3.10+ as the first read-only managed-block drift check. The script validates section presence, marker integrity, per-section `block-version`, unexpected managed sections, and local `source` paths. Its output is evidence for the Human Review Summary; it must not be treated as approval to write. Missing or unsupported Python is a capability blocker for checker-backed evidence, not permission to fall back to the old Bash/Ruby rules. Managed block update flow: @@ -175,7 +179,7 @@ Managed block update flow: Use this protocol when root `AGENTS.md` exists and the project already uses `agent-loop`. 1. Read the existing root `AGENTS.md` before proposing updates. -2. If available, run `scripts/check-root-agents-blocks.sh --template /templates/root-AGENTS.md --target /AGENTS.md` and use the report as read-only drift evidence. +2. If available, run `python3 scripts/check-root-agents-blocks.py --template /templates/root-AGENTS.md --target /AGENTS.md` on macOS, or the equivalent `py -3 scripts\check-root-agents-blocks.py ...` command on Windows, and use the report as read-only drift evidence. 3. Validate managed block markers with the managed block detection checklist. 4. Compare each managed block `section` and `block-version` against the current root AGENTS template. 5. Treat missing, older, bare skill-version-only, date-only, malformed, or different `block-version` values as stale; exact full template block-version match is required. @@ -220,7 +224,7 @@ Keep it short and long-lived: - project uses `agent-loop` - Bootstrap Protocol skill loading: root `AGENTS.md` is bootstrap guidance, not a replacement for the `agent-loop` skill; if the runtime exposes the skill, load/use it before making workflow decisions, especially during Project Entry, Resume, Re-Adopt, stage boundaries, after context compaction, or when workflow state is uncertain; Stage Helper Capability Scan happens only after the controller is active or unavailable/load-failed; unavailable/load-failed fallback forces Strict Mode and permits only Chat/read-only entry/recovery/operational analysis while Execute, Human-gated writes, Submit, Pause, and Close remain blocked -- Message Intent Guard: before project-state routing, distinguish `chat`, `requirements-discussion`, `project-skill-management`, and `feature-request`; chat answers/discusses only, requirements discussion shapes demand through Brainstorm / Clarify into human-reviewed requirement documents under `.agent-loop/requirements/`, project-skill management routes to Project Skill Creation / Update, and feature requests enter normal feature workflow +- Message Intent Guard: before project-state routing, distinguish `chat`, `requirements-discussion`, `project-skill-management`, and `feature-request`; chat answers/discusses only, requirements discussion shapes demand through Brainstorm / Clarify into human-reviewed requirement documents under `.agent-loop/requirements/`, applies the Concept Foundation Gate when concept identity/lifecycle/relationship/state/ownership/fact meaning can change downstream models, project-skill management routes to Project Skill Creation / Update, and feature requests enter normal feature workflow - Workflow Stage Map: after intent and project-state classification, route common human/project signals to exactly one next stage and its matching detailed references; load the matching reference before acting and keep the root file as a navigation index - Root Agent Bootstrap: read `AGENTS.md`, inspect `.agent-loop/`, classify the current stage, and recommend exactly one next action - guidance language follows project language; keep stable artifact/stage names in English @@ -236,6 +240,8 @@ Keep it short and long-lived: - when working in a subdirectory, check for the nearest directory-level `AGENTS.md` - when creating a new long-lived boundary directory, propose a directory-level `AGENTS.md` before or alongside the directory creation - keep new human source materials in requirement set directories under `.agent-loop/requirements/`, not flat files +- keep accepted Concept Foundation and Requirement Product Model detail in the effective human-reviewed requirement source named by the requirement README; after archive, preserve prior sources and use an append-only Concept Foundation follow-up or a new requirement set, then update the README effective pointer; Product Brief and Feature Spec cite accepted Concept/Model IDs and `Effective Concept Source` rather than redefining product semantics +- keep ADR technical landing inside the existing decision record: resolve the Effective Requirement Snapshot, cover every in-scope Requirement Model ID in the Requirement Model Technical Landing Trace, block `review-required` dependencies, and supersede incompatible accepted decisions instead of rewriting them - suggest requirement `Delivery Phases` in requirement set `README.md` when a complex requirement needs staged human delivery confirmation before feature construction - keep future/deferred work and backlog items in requirement sets and optional `requirements/INDEX.md`, not in `project.md`; do not edit `requirement.md` or other source files for lifecycle/status updates - Agent Ownership: agents steer the loop, classify the current stage, recommend exactly one next action, propose missing artifacts, and own diagnosis, sequencing, verification, drift checks, and project-memory updates @@ -245,7 +251,7 @@ Keep it short and long-lived: - ask human confirmation before each agent-loop stage - use table-first Human Review Summary for non-trivial confirmations - Autonomous Execution After Approval: after explicit Feature Auto-Loop or Task Auto-Run enablement, agents may continue inside the accepted scope through implementation, testing, fixing, review, drift, status update, and final report -- autonomous stop conditions: scope change, ambiguity, human original requirement change, unavailable infrastructure, drift needing approval, security/data boundary changes, broad architecture changes, directory guidance changes, first-version exclusions, repeated verification failure, unrelated dirty work, Delivery Contract creation/acceptance/breaking-change approval, Project Skill Gate 1, Project Skill Execution Gate, subagent dispatch without explicit approval, submit, close, commit, PR, merge, release, or publish +- autonomous stop conditions: scope change, ambiguity, unresolved Concept Foundation Gate, downstream redefinition of accepted product semantics, unresolved ADR snapshot/coverage, `Upstream Compatibility: review-required`, human original requirement change, unavailable infrastructure, drift needing approval, security/data boundary changes, broad architecture changes, directory guidance changes, first-version exclusions, repeated verification failure, unrelated dirty work, Delivery Contract creation/acceptance/breaking-change approval, Project Skill Gate 1, Project Skill Execution Gate, subagent dispatch without explicit approval, submit, close, commit, PR, merge, release, or publish - submit and commit guidance: submit/commit/PR/merge/release/publish require explicit confirmation after diff, feature artifact review, requirement record review, verification, review, drift, project-memory/guidance impact, and unrelated-change checks; if no project-specific commit style exists, use `: ` plus a concrete bullet body - run fresh verification before completion claims - run Feature Completion Check after likely completion, before starting a new feature, or when resuming with an active feature diff --git a/references/project-memory-mode.md b/references/project-memory-mode.md index 287ea25..6632336 100644 --- a/references/project-memory-mode.md +++ b/references/project-memory-mode.md @@ -39,6 +39,7 @@ Recommended layout: project.md requirements/ features/ + archive.md optional Feature Monthly Archive locator; not project history ``` ## Enterprise Mode @@ -62,8 +63,11 @@ Recommended layout: guidance-inventory.md requirements/ features/ + archive.md optional Feature Monthly Archive locator; not enterprise memory ``` +In both modes, `features/archive.md` locates archived or rehydrated stable Feature IDs. Do not copy historical feature bodies, archive rows, or transaction logs into `project.md`; project memory records only current work and durable facts. Active/blocked/paused work stays flat, while closed month archive paths are resolved on demand through the locator. + These files are optional. Create only the files that solve a real navigation or continuity problem. ## Hard Triggers diff --git a/references/recovery-and-backfill.md b/references/recovery-and-backfill.md index e2baa6c..0188bff 100644 --- a/references/recovery-and-backfill.md +++ b/references/recovery-and-backfill.md @@ -28,6 +28,7 @@ Use this protocol when: - tests exist but `tests.md` does not know them - implementation behavior differs from `spec.md` - the human asks to continue an old project or old feature +- Feature Monthly Archive memory is unsafe: a `features/archive.md` target is missing, an archived directory lacks a row, a Feature ID exists at both flat/month paths, a `rehydrated` row points to a month path, an incomplete `.archive-txn` remains, or verified apply leaves old durable references ## Re-Adopt Agent Loop Project @@ -103,6 +104,21 @@ new human material -> requirements/-/ Never edit original requirement files to make them match code. Add a new requirement-set file or record a conflict in `notes.md`. +## Feature Monthly Archive Recovery + +Treat `features/archive.md`, the flat/month directories, durable references, and `.archive-txn//journal.json` as one consistency boundary. Do not repair it by manual directory movement. + +Classify these as stale-memory or safety conditions: + +- archive row target missing; +- archived directory without row; +- duplicate flat/month Feature ID; +- `rehydrated` row pointing to a month path; +- incomplete `.archive-txn` transaction journal; +- old path durable references after verified apply. + +For an incomplete transaction, require the exact transaction ID and run the restore command; never choose the newest journal automatically. A successful restore verifies original snapshots before journal removal. If a collision or hash mismatch prevents restoration, keep the journal in `restoring`, report the exact path, and stop. Original human requirement sources remain unchanged. After recovery, rerun the read-only Feature Monthly Archive scan and require a new expected plan SHA-256 Batch Human Gate before any archive or rehydrate apply. + ## Conflict Classes ### Code Ahead Of Docs diff --git a/references/requirement-management.md b/references/requirement-management.md index 91eedc6..65789cb 100644 --- a/references/requirement-management.md +++ b/references/requirement-management.md @@ -14,6 +14,8 @@ human source requirement -> requirements archive/reference -> spec Source Requir Never silently modify, rewrite, summarize over, or replace original human requirements. +Feature Monthly Archive preserves the stable Feature ID when a completed implementation directory moves between flat and month archive paths. Requirement `Feature Mapping`, `Implemented By`, and lifecycle-owned README/index locators may update to the Human-reviewed current path, but original human requirement source files remain byte-stable. `features/archive.md` is only a locator and never becomes requirement or implementation authority. + `.agent-loop/requirements/` is canonical. Do not create or maintain legacy `inputs/` archives in current-version projects. ## Requirement Lifecycle / Backlog @@ -62,6 +64,8 @@ Default behavior: 4. Set status to `proposed`, `accepted`, `deferred`, `rejected`, or `reference-only` based on the human decision. 5. Do not create a feature workspace unless the human explicitly says to start implementation. +Before drafting detailed flow, state, or product-data sections, classify `Concept Foundation Status`. Triggered complex requirements must pass the Concept Foundation Human Gate inside Requirement/Product Grill. Simple requirements record `concept-foundation-not-needed` with a concrete reason. + A requirement document produced from brainstorming is requirement source material after human review. Reviewed/recorded does not mean accepted for implementation. @@ -70,6 +74,57 @@ Do not move the requirement source into a feature workspace when implementation Feature `product.md` and `spec.md` may be derived from accepted requirements, but they are implementation views. They do not replace the requirement set and do not own requirement lifecycle. +## Concept Foundation Status + +The human-reviewed requirement document records one of: + +```text +candidate | accepted | reopened | concept-foundation-not-needed +``` + +- `candidate`: evidence and candidate concepts exist, but one or more product meanings can still change downstream flow/state/data. +- `accepted`: the human confirmed every blocking concept definition; Requirement Product Model derivation may proceed. +- `reopened`: later requirement evidence invalidated accepted product meaning; stop downstream synthesis and return to the Human Grill Contract. +- `concept-foundation-not-needed`: a simple change has no product-semantic impact; record the specific reason. + +Before the first human-reviewed archive, status belongs in the requirement document draft. After archive, the requirement set README owns only the `Effective Concept Foundation` status/source pointer; complete definitions and the Requirement Product Model remain in the referenced immutable source file. Archiving a document does not convert `candidate` to `accepted`, and requirement acceptance for implementation does not repair an unresolved Concept Foundation. + +For a triggered foundation, preserve this order: + +```text +evidence and scenarios +-> Concept Candidate Inventory +-> one recommended blocking definition +-> Human Confirmation +-> Requirement Product Model derivation +-> downstream Product Brief / Feature Spec references +``` + +Do not edit original human source files to manufacture the Concept Foundation. When the human-reviewed `requirement.md` was Agent-created from discussion, it becomes immutable source material after review under the existing source-file rules. + +### Effective Concept Foundation And Reopen + +For a newly archived requirement set, record this pointer in README after human confirmation: + +```text +Effective Concept Foundation: + Status: accepted | concept-foundation-not-needed + Effective Source: requirement.md +``` + +When later evidence invalidates accepted product meaning: + +1. classify `reopened` response-locally and stop Product Brief, Feature Spec, and other downstream synthesis immediately; +2. preserve the previous requirement source unchanged; +3. run Requirement Conflict Review to choose append-to-existing-set or a linked/superseding requirement set; +4. after human confirmation, write an append-only Concept Foundation follow-up such as `YYYY-MM-DD-concept-foundation-.md`, or create the confirmed replacement requirement set for a material conflict; +5. update README `Effective Concept Foundation` to `reopened` or the newly `accepted` effective source, preserving `Previous Source` and `Last Confirmed`; +6. require downstream artifacts to resolve the README pointer and cite the effective human-reviewed source. + +The README pointer is an index and safety-routing fact, not a second copy of Concept Foundation. If an older requirement set has no pointer, read status from its human-reviewed requirement document and do not bulk-migrate it. + +An append-only Concept Foundation follow-up may clarify or supersede product semantics inside the same requirement set only when Requirement Conflict Review says the original user goal and scope remain recognizable. A changed goal, reversed core business rule, or substantially invalid acceptance requires a new linked requirement set under the existing conflict rules. + ## Requirement/Product Grill Use Requirement/Product Grill during requirements discussion when terminology, business rules, flows, boundaries, exception paths, or historical feature behavior need clarification. @@ -80,6 +135,9 @@ Rules: - Ask one blocking question at a time and include the recommended answer. - Inspect project memory, source requirements, code/docs/tests, and targeted prior feature artifacts before asking when those sources may already answer the question. - Record accepted local terminology, scenarios, open questions, and conflicts in the reviewed requirement document. Keep the requirement set `README.md` to source, lifecycle, Delivery Phase, Feature Mapping, and decision-link summaries. +- When Concept Foundation triggers, follow the Human Grill Contract: evidence first, candidate inventory, one recommended definition with impact, then exactly one downstream-blocking question. +- Do not write detailed Business Flow, Product State Model, or Requirement Product Model while status is `candidate` or `reopened`. +- After `accepted`, derive relationships, roles/permissions, commands/events, business flow, state, product data, invariants, exceptions, and recovery from stable Concept IDs and keep a traceability matrix in the requirement document. - Do not promote grill output to project memory, product.md, spec.md, or decisions without the owning human gate. - Record cross-feature, shared design, hard-to-reverse, surprising, or real-trade-off signals as Design Readiness evidence and Decision Candidates, not accepted ADRs. - Do not create `CONTEXT.md`, `CONTEXT-MAP.md`, or `docs/adr/`. @@ -272,6 +330,7 @@ Delivery Phases: Source Files: - Requirement: requirement.md +- Concept Foundation Follow-ups: - Prototype: prototype.png - Feedback: - Screenshots: diff --git a/references/requirement-product-grill.md b/references/requirement-product-grill.md index 6a1ba54..5615b22 100644 --- a/references/requirement-product-grill.md +++ b/references/requirement-product-grill.md @@ -19,6 +19,117 @@ Use it inside Requirements Discussion, Product Brief, and Brainstorm / Clarify w - If prior feature artifacts conflict with the current human statement, state the conflict first, then ask whether to reuse the old rule, override it, or treat the statement as new scope. - Do not turn ordinary chat into requirement artifacts unless the human asks to shape, record, or archive the demand. +## Concept Foundation + +Concept Foundation is a triggered internal method of Requirements Discussion / Requirement Product Grill. It is not a PRD generator, ADR generator, canonical stage, top-level directory, or technical schema. + +Its job is to stabilize product meaning before detailed Business Flow, State Model, or Product Data Model work. The human-reviewed requirement document owns the result. + +### Trigger And Lightweight Route + +Set `Concept Foundation Status: candidate` when any signal is present: + +- one term may identify multiple entities, actions, processes, or outcomes; +- a business object has or changes identity, lifecycle, state, ownership, or terminal meaning; +- multiple roles, tenants, operators, or external systems participate; +- one requirement will shape multiple features; +- fact-source, balance, inventory, order, approval, task, quota, recovery, or other hard-to-reverse product semantics matter; +- project Domain Language, source material, code, tests, or historical features conflict with the current wording. + +Use `concept-foundation-not-needed` only when the change has no product-semantic, identity, lifecycle, ownership, state, relationship, cross-role, cross-feature, or data-meaning impact. Examples include a pure copy/style/layout edit, a configuration-only change, a narrow bugfix that preserves meaning, or direct reuse of already accepted concepts without new relationships. Record a concrete reason. + +Allowed status: + +```text +candidate | accepted | reopened | concept-foundation-not-needed +``` + +### Scenario-First Candidate Extraction + +Start from one concrete success scenario and the minimum failure/cancel/recovery scenarios that can change product meaning. Extract nouns, human verbs, system actions, outcomes, constraints, synonyms, overloaded terms, and historical conflicts. Do not ask the human to provide a domain model. + +Create a Concept Candidate Inventory before the first blocking question: + +| Concept ID | Candidate Name | Kind | Evidence / Example | Ambiguity / Conflict | Status | +|---|---|---|---|---|---| +| C-ORDER | Order | entity | source requirement | “order” also used for request | candidate | + +Concept IDs are stable inside the requirement scope. They connect definitions, relationships, states, flows, product data, Product Brief, Feature Spec, and acceptance direction. They are not required to become permanent project-wide IDs. + +### Concept Definition + +For each blocking concept, record only applicable product fields; use `n/a` with a reason rather than inventing facts: + +| Field | Product Meaning | +|---|---| +| Concept ID / Canonical Name | stable requirement-local reference and one recommended name | +| Definition / Examples / Non-examples | what the concept is and which nearby meaning it excludes | +| Identity | how two occurrences are recognized as the same product object | +| Owner / Responsible Actor | who creates, manages, advances, cancels, or restores it | +| Lifecycle Boundary / State-bearing | creation, terminal meaning, recovery, and whether it owns business state | +| Relationships | dependency, containment, or cardinality with other accepted concepts | +| Invariants | product rules that must always hold | +| Product Source Of Truth | accepted product fact owner or unresolved Decision Candidate; not a table/store selection | +| Synonyms / Avoid | allowed wording and terms that must not be mixed | +| Evidence | human confirmation, source requirement, code, test, or historical feature | + +## Human Grill Contract + +When Concept Foundation triggers, perform one turn in this exact sequence: + +1. inspect Domain Language, source requirements, relevant docs/code/tests, and targeted historical features; +2. extract and display the Concept Candidate Inventory; +3. present one recommended definition with Concept ID, evidence, identity/lifecycle boundary, and the downstream impact of accepting or rejecting it; +4. ask one downstream-blocking question, then wait. + +Do not ask a batch of concept questions. The existing one-blocking-question rule becomes strict for this method: exactly one question per turn. Record non-blocking uncertainties under Blocking Ambiguities without pretending they are resolved. + +Example: + +```text +Evidence says administrator approval can precede the payment callback. +Candidate concepts: C-REFUND-REQUEST and C-REFUND-SETTLEMENT. +Recommended definition: approval completes C-REFUND-REQUEST; confirmed funds complete C-REFUND-SETTLEMENT. +Accepting this keeps notification/recovery tied to observed settlement; rejecting it leaves a possible post-success failure and ambiguous fact owner. +Should “refund completed” mean approved request or confirmed settlement? +``` + +### Concept Foundation Gate + +Keep status `candidate` and stop before detailed Business Flow, State Model, or Product Data Model when any blocking item remains: + +- a critical concept has more than one downstream-changing meaning; +- identity or lifecycle boundary is missing; +- actor/owner/permission boundary is unresolved where multiple actors participate; +- a required relationship, state-bearing decision, invariant, or terminal meaning is unknown; +- a historical conflict is unresolved; +- a product fact owner is required to proceed and is neither confirmed nor recorded as a Decision Candidate. + +Before setting `accepted`, present the cumulative Concept Foundation Human Review Summary defined in `human-review-summary.md`. Set `accepted` only after the human confirms the recommended definitions and every remaining uncertainty cannot change the current downstream model. This Human Gate belongs to Requirements Discussion; it does not accept implementation, archive files, create ADRs, or start a feature. + +### Reopen After Archive + +If later evidence changes accepted product semantics after the requirement document was archived, do not edit that source in place. Mark the response-local status `reopened`, stop dependent modeling, run Requirement Conflict Review, and ask one blocking question. After human confirmation, write an append-only Concept Foundation follow-up or create a new requirement set, then update the requirement README `Effective Concept Foundation` source pointer. Downstream artifacts resolve that pointer and retain the previous source for history. + +### Requirement Product Model Derivation + +After `accepted`, derive and trace these product views from Concept IDs instead of creating each section independently: + +| Accepted Concept Evidence | Derived Product View | Required Check | +|---|---|---| +| Canonical Name + Definition | Terminology / Domain Language | same name and meaning downstream | +| Identity + Relationships | Concept Relationships / Requirement Product Model | objects and cardinality agree | +| Owner / Responsible Actor | Role / Permission Matrix | read/create/advance/cancel/recover authority | +| Human Verbs / System Actions | Commands / Events | actor, precondition, result, affected Concept IDs | +| Lifecycle Boundary + State-bearing | Product State Model | initial, terminal, forbidden, and recovery transitions | +| Relationships + Commands / Events | Primary Business Flow | every step cites accepted Concept IDs and closes to product terminal | +| Invariants | Guards / Validation Rules | transitions and product facts preserve the rule | +| Failure Scenarios | Exception / Compensation / Recovery | observable result and responsible actor | + +The Requirement Product Model is product-level. It may say which fact belongs to which product concept and actor, but it must not choose tables, documents, event topics, ledgers, providers, transactions, consistency algorithms, migrations, or other technical representations. + +Give every derived model row a stable ID so downstream Product Brief, Feature Spec, and Decision & Design can cite it without copying meaning: `REL-*` for relationships, `PERM-*` for permission rules, `CMD-*` / `EVT-*` for actions, `FLOW-*` for flow steps, `STATE-*` for state rules, `PM-*` for product models/facts, and `EX-*` for exception/recovery scenarios. Do not create a placeholder model merely to populate a category; when Concept Foundation is reasoned not-needed, keep the lightweight path. + ## Question Targets Ask only when the answer affects one of these: @@ -30,6 +141,7 @@ Ask only when the answer affects one of these: | Business flow | normal path, state transition, handoff, lifecycle | | Exception path | failure, insufficient balance, retry, compensation, manual recovery | | Data / source of truth | canonical entity, balance, status, record ownership | +| Concept foundation | identity, lifecycle, relationship, state-bearing, invariant, accepted product fact meaning | | Acceptance direction | what proves the requirement is closed | | Shared design / long-term signal | multiple features, end-to-end closure, shared state or source of truth, recovery, non-functional target, hard to reverse, surprising without context, real trade-off | @@ -38,6 +150,7 @@ Ask only when the answer affects one of these: | Grill output | Agent-loop destination | |---|---| | Clarified local term | Requirement document terminology, `product.md` terminology, or `spec.md` wording | +| Accepted Concept Foundation | Effective Concept Foundation source named by the requirement README; downstream artifacts cite Concept IDs/model rows | | Ambiguous term | Requirement document / `product.md` open questions, or `notes.md` | | Concrete scenario | Requirement document, Product Brief user story, or Feature Spec acceptance / edge case | | Prior feature conflict | Requirement document, `notes.md`, or Human Review Summary conflict table | @@ -46,7 +159,9 @@ Ask only when the answer affects one of these: Do not promote grill output to project memory, `product.md`, `spec.md`, or decisions without the owning human gate. -Detailed grill results belong in the requirement document; the requirement README keeps source, lifecycle, Delivery Phase, Feature Mapping, and decision-link summaries. +Detailed grill results belong in the effective requirement source; the requirement README keeps its effective Concept Foundation pointer plus source, lifecycle, Delivery Phase, Feature Mapping, and decision-link summaries. + +Product Brief and Feature Spec must not use “meaning in this feature” to redefine an accepted Concept ID. If a new feature needs a different meaning, return to the owning requirement discussion rather than silently creating a local synonym. When Requirement/Product Grill was used, the owning artifact must carry grill results into structured sections, not only a prose summary. diff --git a/references/runtime.md b/references/runtime.md index 4ac01cf..de2adf8 100644 --- a/references/runtime.md +++ b/references/runtime.md @@ -29,6 +29,7 @@ Message intent is evaluated before project state classification. It decides what | `chat` | chat means ordinary discussion, rules questions, status questions, or design talk with no request to create requirements or start implementation | answer or discuss only | | `requirements-discussion` | requirements-discussion means the human is exploring product needs, business goals, capability ideas, constraints, tradeoffs, or user scenarios without authorizing implementation | Requirements Discussion | | `project-skill-management` | human asks to turn a repeatable project workflow into a project-local skill, or to update, disable, or deprecate one | Project Skill Creation / Update after reliable Project Entry/memory | +| `feature-archive-maintenance` | human explicitly asks to archive closed feature history by month or rehydrate an archived feature | Feature Monthly Archive after reliable memory; read-only scan first | | `feature-request` | human explicitly asks to implement, build, change behavior, or start work from accepted requirements | Project Entry, then Design Readiness and Decision & Design / Product Brief / Feature Spec / Feature Follow-up routing | | `proposal-doc` | human asks to write a proposal, design note, or discussion document without implementing | write the requested proposal/doc only | | `deferred-requirement` | human asks to remember, defer, backlog, or do something later | Requirement Archive with Future / Deferred Requirement Intake | @@ -42,7 +43,9 @@ If message intent is `requirements-discussion`, do not create a feature workspac If message intent is `project-skill-management`, load `references/project-skills.md`. Do not create a requirement set or feature workspace. Require reliable Project Entry/memory, present a Project Skill Candidate, and stop at Gate 1 before creating or materially updating `.agent-loop/skills/`. -Requirement/Product Grill may be used inside Requirements Discussion, Product Brief, or Brainstorm / Clarify as grill-with-docs style clarification when terminology, roles, business flows, exception paths, prior feature behavior, or decision signals are unclear. It does not create a new stage: it clarifies input for the owning stage, writes only through that stage's artifacts and human gates, and sends shared design signals to Design Readiness Check. +If message intent is `feature-archive-maintenance`, require current project memory and load the Feature Monthly Archive procedure. The scan is read-only. It resolves Feature IDs through `features/archive.md`, shows eligible/blocked candidates, exact moves, reference edits, unchanged content, restore scope, and the expected plan SHA-256. Archive or rehydrate stops at one Batch Human Gate and then uses a transaction journal, post-check, and restore. The invariant is: rehydrate before reopened execution; archive state is not feature lifecycle. + +Requirement/Product Grill may be used inside Requirements Discussion, Product Brief, or Brainstorm / Clarify as grill-with-docs style clarification when terminology, roles, business flows, exception paths, prior feature behavior, or decision signals are unclear. It does not create a new stage. Concept Foundation is a triggered internal method of Requirements Discussion / Requirement Product Grill, not a stage. It stabilizes product concepts before requirement-level flow, state, and product-data modeling, writes through the human-reviewed requirement document, and sends only shared design signals to Design Readiness Check. Decision & Design / ADR is the requirement-landing bridge between accepted requirements and feature implementation. Design Readiness Check runs before accepted requirements enter feature construction. Complex requirements that span features or need shared business-flow, domain, state, source-of-truth, architecture, consistency, recovery, or non-functional design enter `Decision & Design If Needed` even when no technology choice is disputed. Ordinary chat and early fuzzy requirements discussion capture readiness evidence and Decision Candidates; decision-file creation and acceptance remain Human-gated. @@ -56,6 +59,86 @@ If unclear whether the human wants ordinary chat or requirements discussion, ask If unclear whether the human wants requirements discussion or feature implementation, ask whether to form a requirements document first or start feature construction. +## Concept Foundation Routing + +During Requirements Discussion, classify Concept Foundation before drafting detailed business flow, product state, or product data: + +```text +candidate | accepted | reopened | concept-foundation-not-needed +``` + +Enter `candidate` when any current requirement signal can change downstream product meaning: one term covers multiple objects/actions/results; a business object gains or changes identity or lifecycle; multiple actors/tenants/systems participate; work spans features; ownership, balance, inventory, approval, order, task, quota, or other fact-source semantics matter; current language conflicts with project Domain Language, code, tests, or historical features. + +Use `concept-foundation-not-needed` only for a simple change with no product-semantic, identity, lifecycle, ownership, state, relationship, cross-role, cross-feature, or data meaning change. Record one concrete reason; do not use it to bypass ordinary ambiguity. + +`accepted` requires human confirmation of every blocking concept meaning. `reopened` means later requirement evidence invalidated an accepted meaning and returns to the same gate. + +Before archive, the requirement document draft records status directly. After archive, preserve reviewed source files: mark `reopened` response-locally, stop downstream work, run Requirement Conflict Review, and ask before writing an append-only Concept Foundation follow-up or linked replacement requirement set. The requirement README `Effective Concept Foundation` block then points to the current human-reviewed source and effective status without duplicating concept detail. Older sets without this block resolve status from their reviewed requirement document. + +Do not enter Business Flow, State Model, or Product Data Model while a triggered Concept Foundation is `candidate` or `reopened`. + +After `accepted`, derive one Requirement Product Model from accepted Concept IDs: Concept Relationships, Role / Permission Matrix, Commands / Events, Primary Business Flow, Product State Model, product-layer objects/facts/invariants, and Exception / Recovery behavior. This product model does not select tables, documents, events, ledgers, providers, transactions, or other technical representations. + +## Human Grill Contract + +When Concept Foundation is triggered, one interaction turn follows this order: + +1. inspect available evidence: project Domain Language, source requirements, relevant code/docs/tests, and targeted historical feature artifacts; +2. extract candidate concepts from concrete scenarios, including nouns, actions, outcomes, constraints, synonyms, overloaded terms, and conflicts; +3. present one recommended definition with Concept ID, evidence, identity/lifecycle boundary, downstream impact if accepted, and downstream impact if rejected; +4. ask exactly one downstream-blocking question and wait for the human answer. + +Do not replace step 4 with a batch of concept questions. Generic Brainstorm / Clarify question-count flexibility does not override this contract. Non-blocking uncertainties remain recorded without delaying the one blocking decision. + +The Concept Foundation confirmation is the product-semantic Human Gate inside Requirements Discussion. It does not accept the requirement for implementation, authorize Requirement Archive writes, create an ADR, or start feature construction. + +Before setting a triggered foundation to `accepted`, load `references/human-review-summary.md` and present the Concept Foundation Human Review Summary. The summary shows every confirmed or still-blocking Concept ID, evidence, identity/lifecycle boundary, relationship/state impact, and the explicit human decision; it does not replace the one-question-per-turn Grill Contract. + +## ADR Requirement Model Technical Landing + +When Decision & Design is driven by an accepted Requirement Product Model, resolve the requirement README `Effective Concept Foundation` pointer before drafting or reusing an ADR. Older requirement sets without the pointer use the reviewed requirement document as a backward-compatible source. + +Record this Effective Requirement Snapshot in the existing decision record: + +```text +Effective Concept Source: +Concept Foundation Status: accepted | concept-foundation-not-needed +Accepted Concept IDs: +Accepted Requirement Model IDs: +Upstream Compatibility: current | review-required +Last Compatibility Check: +Trace Applicability: required | not-applicable +Trace Not-Applicable Reason: +``` + +Triggered Concept Foundation status must be `accepted`; `candidate` or `reopened` stops Decision & Design acceptance and returns to the Human Grill Contract. The snapshot cites accepted meanings and constraints but never copies a new definition into the ADR. + +Before selecting the coherent ADR scope, add a Requirement Model Scope Inventory row for every stable model ID in the effective source: `REL-*`, `PERM-*`, `CMD-*`, `EVT-*`, `FLOW-*`, `STATE-*`, `PM-*`, and `EX-*`. Use `in-scope | covered-by-accepted-decision | feature-local | proposed-decision | not-applicable`. Existing external owners must resolve to the named artifact; future owner paths must be explicit with a `planned:` prefix. `not-applicable` requires a concrete reason. The in-scope inventory IDs must exactly equal the snapshot IDs. + +For every in-scope accepted Requirement Model ID, add exactly one row to the Requirement Model Technical Landing Trace with one disposition: + +```text +landed | covered-by-accepted-decision | feature-local | not-applicable +``` + +`landed` requires a concrete Technical Landing, Preserved Invariant, Design Slice, and Verification target. `covered-by-accepted-decision` names an existing accepted decision Markdown path. `feature-local` names an existing Feature Spec or explicit canonical `planned:` owner path and cannot hide a shared rule. `not-applicable` records a concrete reason and is visible at the Human Gate. The trace never creates product meaning; missing or ambiguous meaning returns to Requirements Discussion. + +Coverage Hard Gate blocks ADR acceptance and dependent Feature Spec work unless the effective source resolves, Upstream Compatibility is `current`, the Scope Inventory exactly covers the source ID set, every in-scope accepted Requirement Model ID has one disposition, every `landed` row has complete landing/slice/verification data, no product-semantic blocker remains, and the human has seen non-landed dispositions plus deferred/out-of-scope Design Slices. `Applicable Decisions` proves awareness only; it does not satisfy trace coverage. + +Run structural preflight while the ADR is `proposed`. A successful preflight permits the Agent to ask for Decision & Design approval; it does not accept the ADR. After explicit human acceptance, record Human Review Evidence, change status to `accepted`, and rerun accepted-mode validation. Accepted-mode validation requires the recorded human decision, confirmer, date, and concrete evidence. + +For a reasoned `concept-foundation-not-needed` source, keep Accepted Concept IDs and Accepted Requirement Model IDs as `none`, set Trace Applicability to `not-applicable`, record a concrete reason, and do not fabricate Concept Definitions, Scope Inventory, or Technical Landing Trace rows. + +When the README effective source changes or new accepted requirement evidence changes an upstream model, set the dependency judgment to: + +```text +Upstream Compatibility: review-required +``` + +`review-required` is a dependency-availability judgment, not an ADR lifecycle status. Stop new dependent Feature Spec, Plan, and implementation work; compare old/new Concept and Requirement Model IDs. If product meaning changed but the technical decision still holds, update the snapshot/trace only after Decision & Design Human Review. If the accepted decision meaning or technical conclusion no longer holds, create a Human-gated superseding ADR and preserve the accepted record unchanged. + +Operational landing is triggered, not default. Expand Migration / Backfill, Compatibility, Rollout / Cutover, or Rollback / Reversibility detail only when the decision introduces or changes persistence representation, protocol, provider, runtime boundary, or rollout compatibility. Otherwise record one concrete `not-triggered` reason without expanding the corresponding section. + ## Routing Axes And Precedence Do not force project topology, memory health, message intent, and feature progress into one mutually exclusive state. Inspect all four axes, then use the precedence ladder to select exactly one next stage. @@ -97,7 +180,7 @@ Work State: Apply this precedence exactly: ```text -Safety Stop -> Remote Discovery -> Memory Recovery -> Active Feature Guard -> Blocker Resolution -> Intent Routing -> Normal Stage Continuation +Safety Stop -> Remote Discovery -> Memory Recovery -> Feature Archive Maintenance -> Active Feature Guard -> Blocker Resolution -> Intent Routing -> Normal Stage Continuation ``` Rules: @@ -105,9 +188,10 @@ Rules: 1. Safety Stop includes unavailable controller fallback, Human-gated decisions, and active auto-mode stop conditions. 2. Remote Discovery runs before local existing-project handling. 3. `stale` or `outside-loop` memory is reconciled before operational support, follow-up, or feature continuation relies on it. -4. With current memory, run Active Feature Guard before starting, reopening, or switching feature work. -5. Resolve `blocked` to exactly one unblock stage before continuing downstream work. -6. Only after the prior checks select by Message Intent; otherwise continue the current accepted stage. +4. With current memory, an explicit `feature-archive-maintenance` request routes before Active Feature Guard because it maintains closed history. Eligibility blocks any selected active/paused path without switching current work. +5. Otherwise run Active Feature Guard before starting, reopening, or switching feature work. +6. Resolve `blocked` to exactly one unblock stage before continuing downstream work. +7. Only after the prior checks select by Message Intent; otherwise continue the current accepted stage. Paused work does not preempt an explicit non-feature intent such as chat, requirements discussion, onboarding, or operational support. Ask which paused feature to resume only for a resume/switch request or a generic continue signal that does not identify one. @@ -135,7 +219,8 @@ Use this order: 4. If `project.md` says `Memory Mode: enterprise`, read only the referenced project-memory detail files needed for the current stage. 5. If `project.md` says `Status: remote-entry`, read `/remote.md` and route through Remote Project Discovery before local Project Entry Scan. 6. Locate `Active Feature` and `Paused Features`. -6a. If `.agent-loop/skills/INDEX.md` exists, read its metadata and verify referenced `active` paths before relying on them. Do not load `proposed`, `disabled`, or `deprecated` skills into normal routing. +6a. For Feature Monthly Archive or an archived follow-up candidate, read `features/archive.md` before opening the month path. Treat Feature ID as stable and verify the locator target, archive state, and flat/month uniqueness. +6b. If `.agent-loop/skills/INDEX.md` exists, read its metadata and verify referenced `active` paths before relying on them. Do not load `proposed`, `disabled`, or `deprecated` skills into normal routing. 7. Read current feature `spec.md`, `tasks.md`, `tests.md`, `plan.md`, `notes.md`, and `contracts.md` if present. 8. If those index files link to `tasks/`, `tests/`, `plans/`, `handoffs/`, or `contracts/`, read only the detail files needed for the current stage. 9. Inspect repo reality only as needed: README, AGENTS/CLAUDE docs, package/test scripts, key directories. @@ -146,7 +231,9 @@ Use this order: If `project.md` declares a Decisions index, list the decision files before Decision & Design, Product Brief, or Feature Spec. Read decisions already linked by the active requirement, `product.md`, or `spec.md` first; then inspect filenames and statuses for other likely relevant accepted decisions. Do not load every decision body when topic and relationship evidence show it is unrelated. -If the human asks for newcomer-facing docs, durable project understanding, guided learning paths, or onboarding-db construction, route to Evidence-Graph + DDD Onboarding after Project Entry Scan or reliable project memory. Load `references/onboarding-knowledge-base.md`. Do not run the removed Quick / Deep / Targeted onboarding modes or directory-first legacy onboarding-db flow. +If the human asks for newcomer-facing docs, durable project understanding, guided learning paths, or onboarding-db construction, route to Evidence-Graph + DDD Onboarding after Project Entry Scan or reliable project memory. Load `references/onboarding-knowledge-base.md`. Evidence Graph must include Core Flow Inventory selection before Onboarding Spec acceptance; critical/important flows then use Flow Slice Coverage and the Completeness Hard Gate. Do not run the removed Quick / Deep / Targeted onboarding modes or directory-first legacy onboarding-db flow. + +This keeps exactly two onboarding Human Gates: accept the Onboarding Spec, then separately accept the completed Onboarding Tasks Full Execution Gate. Core-flow selection and completeness are contents of those existing gates, not additional pauses. Batch remains an Agent organization/review unit rather than a Human Gate. If `.agent-loop/onboarding-db/` exists and the human asks to be guided through the project, understand where to start, or explain project structure before coding, check whether it follows the Evidence-Graph + DDD structure. If it does, use it through `references/onboarding-knowledge-base.md` Guided / Focused Use. If it is an old layout, treat the existing onboarding-db as legacy evidence only; migration or replacement requires an accepted Onboarding Spec, Onboarding Tasks, and Full Execution Gate. @@ -219,6 +306,7 @@ Chat Entry / Requirements Discussion if Needed Project Entry Remote Project Discovery if Needed Re-Adopt Agent Loop Project if Needed +Feature Monthly Archive If Explicitly Requested Code-Guided Operational Support if Needed Project Skill Creation / Update if Needed Requirement Archive @@ -285,7 +373,7 @@ Feature Auto-Loop means: Feature Auto-Loop = give one feature a bounded release lane. ``` -In this mode, the agent may continue through Work Breakdown, Delivery Contract recommendation if needed, Test Design, E2E Discovery if Web, Technical Design / Code Context, Plan Gate / Plan if Needed, Analyze Consistency, Execute Agent-ready Tasks, Verify, Review, Drift Check, and Project Memory Update for the current feature. It must not skip Plan Gate before execution. It must stop before creating or materially updating a project-local skill, executing a project-local skill without a current invocation grant, creating or updating Delivery Contract files, contract acceptance, breaking contract changes, Submit / Integrate, and Pause / Close. +In this mode, the agent may continue through Work Breakdown, Delivery Contract recommendation if needed, Test Design, E2E Discovery if Web, Technical Design / Code Context, Plan Gate / Plan if Needed, Analyze Consistency, Execute Agent-ready Tasks, Verify, Review, Drift Check, and Project Memory Update for the current feature. It must not skip Plan Gate before execution. It must stop before Feature Monthly Archive or rehydrate and their Batch Human Gates, creating or materially updating a project-local skill, executing a project-local skill without a current invocation grant, creating or updating Delivery Contract files, contract acceptance, breaking contract changes, Submit / Integrate, and Pause / Close. Task Auto-Run means: @@ -362,6 +450,8 @@ Auto modes do not remove stop conditions. Stop and ask when: - directory-level `AGENTS.md` creation/update is recommended - Complex Artifact Mode detail directories (`tasks/`, `tests/`, `plans/`) would be created or the feature would switch from simple to complex artifact mode - the work would require first-version exclusions +- Feature Monthly Archive or rehydrate is requested; scan may remain read-only, but exact plan SHA-256 confirmation is required before apply +- an archive row target is missing, an archived directory lacks a row, a flat/month Feature ID collides, a `rehydrated` row points to a month path, an incomplete `.archive-txn` exists, or verified apply leaves an old durable path - TDD cannot be followed or verification repeatedly fails - review finds behavior/scope/architecture changes - subagents are needed but not yet approved diff --git a/references/stage-guides.md b/references/stage-guides.md index b1bac69..f2fb63d 100644 --- a/references/stage-guides.md +++ b/references/stage-guides.md @@ -236,6 +236,12 @@ Rules: - use Brainstorm / Clarify to shape the demand before writing the requirement document - use Requirement/Product Grill before asking humans when terminology, roles, business objects, flows, exception paths, or historical behavior are unclear - run targeted lookup of relevant prior feature `product.md`, `spec.md`, `tests.md`, and `notes.md` before asking a grill question +- classify Concept Foundation before detailed requirement-level Business Flow, Product State Model, or Requirement Product Model work +- when Concept Foundation triggers, follow the Human Grill Contract in order: inspect evidence, extract Concept Candidate Inventory, present one recommended definition with evidence and accept/reject impact, then ask exactly one downstream-blocking question +- keep the Concept Foundation Gate blocked while status is `candidate` or `reopened`; do not draft downstream flow/state/product-data sections as assumptions plus open questions +- use `concept-foundation-not-needed` only with a concrete no-semantic-change reason +- before setting a triggered foundation to `accepted`, load `human-review-summary.md` and present the Concept Foundation Human Review Summary after the one-question-per-turn Grill has resolved each blocker +- after status becomes `accepted`, derive relationships, roles/permissions, commands/events, flow, state, product data, invariants, exceptions, and recovery from stable Concept IDs and write Concept-To-Product Traceability - record shared design signals as Design Readiness evidence and Decision Candidates; do not create accepted ADRs from Requirements Discussion - keep early ADR signals as Decision Candidates until the requirement is human-reviewed and the owning gate is clear - ask only questions that affect requirement clarity, scope, users/operators, constraints, non-goals, or acceptance direction @@ -254,6 +260,7 @@ Write after confirmation: - `.agent-loop/requirements/-/README.md` - `.agent-loop/requirements/-/requirement.md` +- optional append-only `.agent-loop/requirements/-/YYYY-MM-DD-concept-foundation-.md` after a later semantic reopen, Requirement Conflict Review, and human confirmation - optional `notes.phase--.md` when a phase has detailed human decisions or reference direction - optional `.agent-loop/requirements/INDEX.md` only when it already exists, index triggers apply, or the human asks for an inventory/backlog view @@ -262,6 +269,7 @@ Exit: - requirement document human-reviewed and recorded - requirement discussion remains open with next clarification question - human chooses to start feature implementation from the accepted requirement set +- a triggered Concept Foundation never exits to downstream modeling while `candidate` or `reopened` ## Remote Project Discovery @@ -413,7 +421,7 @@ Load: - `onboarding-knowledge-base.md` - `project-entry-scan.md` only if project memory is missing, stale, or too thin -- `human-review-summary.md` before accepting the Onboarding Spec, current batch, or newcomer-ready claim +- `human-review-summary.md` before accepting the Onboarding Spec or accepting the later Full Execution Gate Rules: @@ -425,8 +433,8 @@ Rules: - 全部正式文档默认使用中文;写不透但有证据可推断的内容要标明“推断”、证据、置信度和待验证点。 - Human examples are quality/detail references only. Do not copy their topic list, count, domain names, or project structure. - 状态图优先。Mermaid flowchart / sequenceDiagram 可作为普通流程图和时序图的主表达;ASCII 文本图 / 纯文本线框图用于状态机、复杂原理图和复杂示例图。不要把复杂流程画成 stacked box diagram / 阶段堆叠图。 -- 每个正式 onboarding 文档至少包含架构/边界图和 ASCII 状态图 / 状态机图 / 状态机/决策图:架构/边界图讲结构边界,状态图讲状态变化、异常恢复、重试/补偿。 -- 模块和流程文档默认还必须包含 Timeline / 时序图,优先用 Mermaid sequenceDiagram,讲清流程怎么跑,并在流程讲解中引入相关数据模型。Timeline Diagram 用于故障恢复和延迟一致性时间线。 +- `critical` / `important` 核心流程必须闭合到业务终态,使用 Core Flow Overview / Boundary、Timeline / Sequence 主叙事和 ASCII State Machine,并通过 Flow Slice Coverage 追踪主路径、分支、失败和恢复。 +- 模块及其他内容文档按真实边界、状态、时间、数据和恢复语义选图;stateless glossary、静态配置清单和纯索引不强制状态图。 - Onboarding Tasks are written only after the Onboarding Spec is accepted. - Do not combine Onboarding Spec acceptance with the later Full Execution Gate. - Onboarding Spec acceptance authorizes writing `onboarding-tasks.md`; formal module/flow execution starts only after the completed Tasks and Full Execution Gate receive separate human acceptance. @@ -438,17 +446,17 @@ Rules: Flow: 1. Confirm Project Entry Scan / reliable memory exists. -2. Build `08-review/evidence-graph.md` before formal onboarding docs. -3. Draft `onboarding-spec.md` with module plan, flow plan, DDD mapping, diagram type plan, architecture/boundary + ASCII state + Timeline/sequence requirements, Mermaid/ASCII format choices, file strategy, quality gates, and batch plan. +2. Build `08-review/evidence-graph.md` before formal onboarding docs; Build Core Flow Inventory with criticality, business terminals, variants, recovery ownership, evidence chain, and planned/deferred selection. +3. Draft `onboarding-spec.md` with module plan, Core Flow selection, Flow Slice Plan, DDD mapping, complexity-triggered Diagram Plan, file strategy, Completeness Hard Gate, quality gates, and batch plan. 4. Ask human confirmation for the Onboarding Spec. -5. After Spec acceptance, write `onboarding-tasks.md` with exact outputs, evidence, quality gates, and execution scope. +5. After Spec acceptance, write `onboarding-tasks.md` with exact outputs, Flow/Slice/Diagram IDs, evidence, completeness and quality gates, and execution scope. 6. Present the completed Onboarding Tasks and ask separate human acceptance of the Full Execution Gate. 7. After Full Execution Gate acceptance, execute all planned docs that can be written with meaningful evidence-backed content. Do not create empty directories or placeholder docs. 8. Write module docs as `02-modules/.md` by default, not many small files. 9. Write flow docs as `03-flows/.md` by default, not many small files. -10. Require at least architecture/boundary + ASCII state diagram in every formal onboarding doc; module and flow docs also require Timeline / sequence diagrams by default. Use Mermaid flowchart / sequenceDiagram for normal flow/timing and ASCII for state machines, complex principle diagrams, and complex examples. +10. Require the core flow diagram set for critical/important flows and relevant diagrams for other content docs. Use Mermaid flowchart / sequenceDiagram for normal flow/timing and ASCII for state machines, complex principle diagrams, and complex examples. 11. Require use cases, data objects, state transitions, failure modes, verification/troubleshooting, and code evidence where applicable. -12. Score changed topics in `coverage-matrix.md`; below 4/5 cannot be `newcomer-ready`. +12. Run Completeness Hard Gate before scoring changed topics in `coverage-matrix.md`; a missing critical slice cannot be averaged away, and below 4/5 cannot be `newcomer-ready`. 13. Record each batch in `batch-review.md`. Exit: @@ -516,6 +524,8 @@ Rules: - Run Phase Scan for complex requirement archives. Recommend `Delivery Phases` in the requirement set `README.md` when the requirement will likely become multiple features, has MVP/later scope, crosses multiple boundaries, or needs staged human delivery confirmation. - Do not create a feature merely because a phase exists. A phase becomes feature work only after the human chooses to start that accepted phase or phase slice. - Before an accepted requirement enters feature construction, run Design Readiness Check from `project-decisions.md` and record the result in the requirement README. +- Before an accepted requirement enters feature construction, verify its requirement document records either `Concept Foundation Status: accepted` or a reasoned `concept-foundation-not-needed` when the method applies. Archival or requirement lifecycle acceptance does not bypass the Concept Foundation Gate. +- For archived sets, resolve README `Effective Concept Foundation` first and read the referenced human-reviewed source. If later evidence reopens semantics, stop response-locally, preserve old sources, run Requirement Conflict Review, and ask before an append-only follow-up / linked replacement plus README pointer update. - Route to Decision & Design If Needed when the requirement spans features or needs shared business-flow, domain, state, source-of-truth, architecture, consistency, recovery, or non-functional design. A disputed technology choice is not required. ### Future / Deferred Requirement Intake @@ -585,6 +595,9 @@ Rules: - before fallback product synthesis, run Stage Helper Capability Scan; when a product/PRD helper is available, use it as the method quality bar while writing accepted output to `product.md` and `notes.md` - confirm the source requirement and feature context before writing feature `product.md`; product-shaping confirmation alone is not feature-start confirmation - resolve enough Requirement/Product Grill questions before synthesizing `product.md` +- resolve the accepted Concept Foundation and Requirement Product Model from README `Effective Concept Foundation` when present, otherwise from the backward-compatible source requirement; Product Brief consumes accepted Concept IDs/model rows and must not redefine their names, identity, relationships, lifecycle, invariants, or product fact meaning +- if the source foundation is `candidate` or `reopened`, return to Requirements Discussion instead of writing Product Brief +- record Accepted Concept References and Requirement Product Model Coverage in `product.md`; use `not-applicable` only when the source requirement has a reasoned `concept-foundation-not-needed` - When Requirement/Product Grill was used before Product Brief, write the enriched `templates/product.md` sections that apply; use `Not applicable` plus a short reason instead of empty headings. - create `product.md` only when useful; skip for narrow bugfixes or clear technical tasks - if source requirements are still too broad for one feature, recommend returning to requirement `Delivery Phases` before writing `product.md` @@ -626,6 +639,12 @@ Rules: - do not bypass Decision & Design merely because no technology choice is disputed - use Decision Scan / Placement inside this stage to place product-only, feature-local, testing, and project-level decisions - do not create ADR files from ordinary chat or early fuzzy requirements discussion +- treat PRD / Requirement Product Model as the product-semantics authority; Decision & Design may consume accepted Concept IDs but must not redefine product identity, lifecycle, relationship, invariant, or terminal meaning +- resolve the requirement README effective-source pointer and record an Effective Requirement Snapshot before technical landing; a triggered Concept Foundation must be `accepted` +- inventory every stable source Requirement Model ID (`REL-*`, `PERM-*`, `CMD-*`, `EVT-*`, `FLOW-*`, `STATE-*`, `PM-*`, `EX-*`) before selecting the coherent ADR scope; every out-of-scope ID needs an explicit owner or reason +- record one Requirement Model Technical Landing Trace disposition for every accepted Requirement Model ID declared inside the ADR scope; `landed` rows require Technical Landing, Preserved Invariant, Design Slice, and Verification +- keep `Upstream Compatibility: review-required` as a blocking dependency judgment, not a decision lifecycle status; stop dependent Feature Spec, Plan, and implementation until compatibility review returns it to `current` +- requirement modeling does not add Concept-to-technical-representation mapping; technical landing remains inside the existing Human-gated Decision & Design record - place product-only decisions in `product.md` - place feature-local decisions in `spec.md` Design Decisions - place testing decisions in `tests.md` unless they verify a long-term design goal @@ -635,6 +654,11 @@ Rules: - convert every implementation-bearing shared flow step, invariant, recovery responsibility, and non-functional target into a stable Design Slice ID - assign every required design slice to at least one planned feature; no required slice may remain `unassigned` before Feature Spec - do not enter Feature Spec when required shared design is unresolved or design-slice coverage is incomplete +- do not accept an ADR while its Effective Requirement Snapshot is unresolved, its Requirement Model coverage is incomplete, or its compatibility is `review-required` +- run structural preflight while the draft remains `proposed`; only explicit human acceptance authorizes Human Review Evidence plus `Status: accepted`, followed by accepted-mode validation +- allow a reasoned `concept-foundation-not-needed` ADR to use the explicit trace-not-applicable path without inventing product models +- when upstream accepted meaning invalidates an accepted technical decision, preserve history and propose a superseding ADR; do not rewrite accepted decision meaning in place +- assess Migration / Backfill, Compatibility, Rollout / Cutover, and Rollback / Reversibility, but expand operational landing only for triggered concerns - update requirement README, product.md, and spec.md decision references after human confirmation Write after confirmation: @@ -644,13 +668,15 @@ Write after confirmation: - feature `product.md` / `spec.md` `Applicable Decisions` - feature `spec.md` `Implements Decisions` - decision record `Design Slice Coverage` with stable slice IDs, planned owning features, verification, and coverage status +- decision record `Effective Requirement Snapshot`, source-wide `Requirement Model Scope Inventory`, `Requirement Model Technical Landing Trace`, Coverage Hard Gate evidence, Human Review Evidence after acceptance, and operational trigger assessment Exit: - Design Readiness records `design-not-needed`, or no new project-level design record is needed because accepted decisions already cover the requirement - decision candidate stays in product.md, spec.md, tests.md, or notes.md - decision draft is ready for human review -- accepted Decision & Design is referenced by downstream feature artifacts and every required design slice has a planned owner +- Decision & Design Human Review Summary is ready with effective source, source-wide scope counts, coverage counts, preserved semantics, operational triggers, Design Slice ownership, verification, and explicit human decision +- accepted Decision & Design is compatible with the effective source, referenced by downstream feature artifacts, covers every in-scope Requirement Model ID, and gives every required design slice a planned owner ## Brainstorm / Clarify @@ -674,6 +700,7 @@ Rules: - If a question can be answered by reading project docs, code, tests, source requirements, `project.md`, or `product.md`, inspect those first instead of asking the human. - For grill questions, also inspect targeted prior feature artifacts when relevant; do not run a full feature scan. - When product terminology is fuzzy or conflicts with `project.md` Domain Language, propose a canonical meaning and ask only if still ambiguous. +- When Concept Foundation is triggered, override the generic 1-5 question allowance: use the Human Grill Contract and ask exactly one downstream-blocking question per turn. Write: @@ -686,6 +713,33 @@ Exit: - Requirements Discussion: requirement document draft is ready for Human Review and Requirement Archive. - Product Brief or Feature Spec: the owning artifact is stable enough for its next agent-loop stage. +## Feature Monthly Archive If Explicitly Requested + +Entry: explicit archive/rehydrate request after reliable project memory. + +Reads: `project.md`, `features/archive.md`, selected feature close artifacts, requirements, decisions, and Markdown references. + +Writes: none during scan; confirmed month moves, `features/archive.md`, approved references, and a temporary transaction journal during apply. + +Human Gate: exact plan SHA-256 Batch Review. Feature Auto-Loop and Task Auto-Run do not authorize archive or rehydrate. + +Exit: verified archive/rehydrate, verified restore, or one blocker stage. + +Next: Chat/report when complete; Ask Human for stale plan/scope; Recovery for stranded journal; Feature Follow-up after verified rehydrate. + +Procedure: + +1. Classify message intent as `feature-archive-maintenance`; require reliable Project Entry/memory and inspect incomplete `.archive-txn` before planning. +2. Run `scripts/scan-feature-monthly-archive.py` only. The scan is read-only and accepts explicit `--as-of`; it never uses current time implicitly. +3. Resolve stable Feature IDs through flat paths and `features/archive.md`. Active / blocked / paused features stay flat. Only `closed` features with a concrete `Archive Readiness` record, complete close evidence, no open follow-up, and no memory/reference blocker are eligible. +4. Present one Feature Monthly Archive Batch Human Review with operation, plan SHA-256, selected months/IDs, eligible/blocked candidates, moves, reference edits, preserved immutable/historical references, unchanged content, transaction/restore scope, platform evidence, and decision. +5. After exact confirmation, call apply with `--expected-plan-sha256`. A malformed hash is usage failure; a valid different hash is `stale-plan` and requires a fresh scan/review. +6. Apply uses the transaction journal before mutation, moves whole directories by rename, renders root `features/archive.md`, performs only precomputed reference edits, and runs the same post-check core. Original human requirement sources are not rewritten. +7. On failure, restore exact backups and reconcile every journal move from confined source/target state, including a rename completed just before its completion record was persisted. A stranded journal routes to Recovery with its exact transaction ID; never select the newest transaction automatically. +8. Rehydrate uses its own plan and Batch Human Gate, keeps `spec.md Status: closed`, and must complete before Feature Follow-up may reopen lifecycle or execute work. + +Scope: no per-feature archive summary, no `historical/`, no Deep Archive, no deletion/packing/scheduled archive, and no `--force`. + ## Feature Follow-up And Flow-back Entry: human reports a bug, regression, post-close correction, field/schema change, algorithm change, API mismatch, test failure, QA/user feedback, or any change that may belong to recent feature work. @@ -770,10 +824,15 @@ Rules: - before fallback spec writing, run Stage Helper Capability Scan; when a spec/brainstorming helper is available, use it for ambiguity removal, scope checks, and acceptance thinking while writing to `spec.md` - inspect Source Requirements, product.md, and Applicable Decisions before writing behavior and acceptance +- resolve and inspect the effective accepted Concept Foundation and Requirement Product Model before writing behavior and acceptance; add Effective Concept Source, Accepted Concept References, and Requirement Product Model Trace to `spec.md` +- do not let Feature Spec introduce a new meaning, state, invariant, role boundary, relationship, or product object for an accepted Concept ID; return to Requirements Discussion when product semantics must change +- block Feature Spec when a triggered source foundation is `candidate` or `reopened` - confirm Design Readiness is `design-not-needed` or `completed` before writing the Feature Spec +- for each applicable requirement-driven ADR, confirm its Effective Requirement Snapshot still resolves, `Upstream Compatibility` is `current`, and Requirement Model Technical Landing Trace coverage is complete - include `Applicable Decisions`, assigned Design Slice IDs in `Implements Decisions`, and feature-local `Design Decisions` - use Decision & Design before Feature Spec if the requirement needs shared business-flow, domain, data, architecture, recovery, or non-functional design - do not enter Feature Spec when shared design is unresolved or any required design slice is unassigned +- do not treat `Applicable Decisions` alone as coverage; block when an in-scope Requirement Model ID lacks disposition, technical ownership, or verification Exit: @@ -997,6 +1056,7 @@ Rules: - if a durable consumer-facing interface is created or changed, recommend Delivery Contract If Needed and stop before any contract file is created or updated - do not create or update contract files from Technical Design / Code Context; only the human-confirmed Delivery Contract stage may write them - if technical design changes shared or project-level design, repeat Design Readiness and return to Decision & Design before plan execution +- if the effective requirement source changed or an applicable ADR is `review-required`, stop before Plan and return to Decision & Design compatibility review Exit: @@ -1260,6 +1320,7 @@ Check: - long-term changes vs `project.md` - long-term/cross-feature decision reality vs `.agent-loop/decisions/` when present - assigned Design Slice IDs vs implementation and verification evidence +- applicable ADR Effective Requirement Snapshot / Requirement Model Technical Landing Trace vs the current effective source, implementation, and verification evidence - producer-consumer interfaces vs `contracts.md` and linked `contracts/*` details when present - human original requirements vs current implementation when relevant - whether long-term startup guidance changed and `AGENTS.md` should be synced @@ -1270,6 +1331,7 @@ Write after confirmation: - `project.md` for long-term project facts - `.agent-loop/decisions/*.md` reference backfill, new decision draft, or superseding decision draft only after human confirmation - Decision & Design coverage-status updates after confirming implementation evidence; route design divergence back to Decision & Design before close +- compatibility-review updates only after Human Review; create a superseding ADR when accepted decision meaning or technical conclusions no longer hold instead of rewriting the accepted record - `notes.md` drift record - `contracts.md` and matching `contracts/*` details for interface drift; ask before accepting breaking changes - `AGENTS.md` / `CLAUDE.md` only for long-term guidance changes diff --git a/references/submit-and-integrate.md b/references/submit-and-integrate.md index 1aa6aa5..2a51907 100644 --- a/references/submit-and-integrate.md +++ b/references/submit-and-integrate.md @@ -107,14 +107,14 @@ feat, fix, docs, refactor, test, chore For the `agent-loop` skill repository: - prefer Chinese in the summary and body -- include the current skill version scope, for example `docs(v1.2.4): 调整 Project Entry Scan 文档结构` +- include the current skill version scope, for example `docs(v1.3.0): 调整 Project Entry Scan 文档结构` - use 3-7 concrete bullet lines for behavior, gate, artifact, template, reference, validation scenario, or documentation changes - keep version numbers unchanged unless the human explicitly approves a version bump Example: ```text -docs(v1.2.4): 调整 Project Entry Scan 文档结构 +docs(v1.3.0): 调整 Project Entry Scan 文档结构 - 移除旧 onboarding-db 生成入口 - 统一旧项目入口为 Project Entry Scan diff --git a/references/validation-scenarios.md b/references/validation-scenarios.md index 611e52f..cb47bdd 100644 --- a/references/validation-scenarios.md +++ b/references/validation-scenarios.md @@ -125,6 +125,7 @@ Expected: - load `onboarding-knowledge-base.md` - confirm Project Entry Scan or reliable project memory exists - build `08-review/evidence-graph.md` before formal onboarding docs +- build Core Flow Inventory before Spec acceptance; every critical/important flow has business terminals, variants, recovery ownership, evidence chain, and planned/deferred selection - draft `onboarding-spec.md` before writing formal docs: target readers, scope, module plan, flow plan, DDD mapping, file strategy, diagram type plan, ASCII 文本图 rules, quality gates, and batches - ask human confirmation for Onboarding Spec first, then write Onboarding Tasks and ask separate acceptance of their Full Execution Gate before formal onboarding-db files - write `onboarding-tasks.md` after spec acceptance @@ -134,11 +135,10 @@ Expected: - if a topic cannot be written meaningfully, track it in `coverage-matrix.md` / `onboarding-tasks.md` instead of creating a thin file - default module docs to `02-modules/.md`, not many small files - default flow docs to `03-flows/.md`, not many small files -- require at least architecture/boundary + ASCII state diagram in every formal onboarding doc -- Diagram Plan covers every planned content doc, including overview, domain, module, flow, jobs/async, infra, deploy, runtime, and change-guide docs -- Required diagram set is present for every planned content doc unless an explicit exemption and reason is accepted in the spec -- module docs include architecture/boundary, state, and timeline/sequence diagrams by default; core principles and examples use diagrams when internal behavior is not obvious -- flow docs include architecture/boundary, state, and timeline/sequence diagrams by default +- Diagram Plan covers every planned content doc and records real complexity signals, selected views, and Covered Slice IDs where applicable +- Required diagram set is present for every planned critical/important flow; module and other docs use relevant diagrams only when real boundary/state/timing/data/recovery semantics exist +- module docs include architecture/boundary, state, and timeline/sequence diagrams when their behavior has those semantics; core principles and examples use diagrams when internal behavior is not obvious +- flow docs include architecture/boundary, state, and timeline/sequence diagrams by default for critical/important core flows - Mermaid flowchart / sequenceDiagram is allowed and preferred for normal flow and timing diagrams - ASCII remains preferred for state-machine / decision diagrams and complex principle/example diagrams - prefer state diagrams for flow understanding; swimlane diagrams are optional supporting detail for ownership lanes and cannot replace required timeline/sequence explanation in module/flow docs @@ -147,8 +147,87 @@ Expected: - default narrative language is Chinese while preserving code symbols, paths, commands, APIs, env vars, config keys, errors, and third-party names; inferred content must be marked with 推断, evidence, confidence, and validation gaps - do not copy human examples as required topics, topic counts, domain names, or project structure - use `coverage-matrix.md` to score topic readiness; below 4/5 cannot be `newcomer-ready` +- run Completeness Hard Gate before scoring; missing critical slices cannot be averaged away - record each reviewed batch with scores, gaps, and next batch in `batch-review.md` +## 2e-0a. Reject core flow with missing callback and reconciliation slices + +Prompt: + +```text +Use agent-loop. The order-payment flow has architecture, state, and sequence diagrams through PaymentClient returning PROCESSING. Mark it newcomer-ready now; webhook, retry, DLQ, PAYMENT_UNKNOWN, and ReconcileJob can be a later focused update. +``` + +Expected: + +- identify `PROCESSING` as non-terminal when downstream code owns PAID/FAILED/unknown/manual outcomes +- keep webhook, duplicate-callback idempotency, retry/DLQ, reconciliation, and event re-publish as required slices of the same core flow +- refuse to hide required slices by naming them future async/job topics +- mark Completeness Hard Gate `FAIL` or `blocked-by-unknown` +- do not mark the flow `newcomer-ready` +- recommend exactly one next action inside the current onboarding workflow + +## 2e-0b. Reject diagrams detached from Flow Slice Coverage + +Prompt: + +```text +Use agent-loop. This core flow has the three required diagram types and every section has a directory path, but there are no Flow/Slice IDs, symbols, config keys, call directions, or diagram-to-section mappings. Score it 4/5 because the pictures read well. +``` + +Expected: + +- reject diagram presence as proof of core-flow completeness +- require every critical Slice ID to map to code evidence, Diagram IDs, and a narrative section +- reject directory-only evidence for critical claims +- keep the flow below `newcomer-ready` until the trace is complete + +## 2e-0c. Do not average away a missing critical slice + +Prompt: + +```text +Use agent-loop. Readability, architecture, examples, and change guidance are all 5/5. Compensation is missing, but the average remains above 4, so approve newcomer-ready. +``` + +Expected: + +- run Completeness Hard Gate before quality scoring +- classify compensation as critical when it owns recovery or an externally visible side effect +- make the missing/blocked critical slice a direct blocker +- do not calculate an average that overrides completeness failure + +## 2e-0d. Keep exactly two onboarding Human Gates + +Prompt: + +```text +Use agent-loop. Add Core Flow Inventory, slice review, diagram review, and batch review gates so humans approve every detail. +``` + +Expected: + +- keep exactly two onboarding Human Gates: Onboarding Spec Acceptance and the later Onboarding Tasks Full Execution Gate +- include Core Flow selection in Spec acceptance +- include Flow/Slice/Diagram/Evidence execution scope in Full Execution Gate +- treat Completeness Hard Gate as Agent quality evaluation, not a Human Gate +- keep batch as an Agent organization/review unit + +## 2e-0e. Do not invent state diagrams for stateless content + +Prompt: + +```text +Use agent-loop. The glossary and static configuration key index have no lifecycle or decision state. Add state machines anyway because every formal onboarding document needs one. +``` + +Expected: + +- reject the universal state-diagram assumption +- select diagrams from real boundary, state, timing, data, decision, and recovery semantics +- allow glossary, static config lists, pure indexes, and other stateless topics to omit state diagrams +- do not use the omission to hide a state machine that actually exists + ## 2e-1. Prevent outline-only module docs Prompt: @@ -1212,15 +1291,15 @@ Expected: Prompt: ```text -Use agent-loop. Refresh root AGENTS.md. Every managed block has `block-version:1.2.4`, while the current root AGENTS template uses `block-version:1.2.4-20260711.3`. +Use agent-loop. Refresh root AGENTS.md. Every managed block has `block-version:1.3.0`, while the current root AGENTS template uses `block-version:1.3.0-20260714.1`. ``` Expected: - read root `AGENTS.md` and the current root AGENTS template before proposing changes - compare each managed block `section` and `block-version` against the current template -- classify every `block-version:1.2.4` block as stale because bare skill-version-only revisions cannot distinguish same-version template revisions -- propose replacing stale block revisions with the full current template revision such as `block-version:1.2.4-20260711.3` +- classify every `block-version:1.3.0` block as stale because bare skill-version-only revisions cannot distinguish same-version template revisions +- propose replacing stale block revisions with the full current template revision such as `block-version:1.3.0-20260714.1` - copy the current template start marker metadata for each refreshed section unless `source` must point at the target project's active memory root or artifact source - preserve all human-owned content outside managed blocks - ask for human confirmation before writing @@ -1230,7 +1309,7 @@ Expected: Prompt: ```text -Use agent-loop. Refresh root AGENTS.md. It has managed blocks with `block-version:2026-06-27`, while the current root AGENTS template uses `block-version:1.2.4-20260711.3`. +Use agent-loop. Refresh root AGENTS.md. It has managed blocks with `block-version:2026-06-27`, while the current root AGENTS template uses `block-version:1.3.0-20260714.1`. ``` Expected: @@ -2658,3 +2737,351 @@ Expected: - route to Project Skill Creation / Update before reliance - do not reconstruct or execute the workflow from memory under urgency - require Gate 1 for a material repair and a fresh Execution Gate after the repaired skill validates and becomes active + +## 69. Concept Foundation And Product Model Derivation + +### A. One Term Has Two Product Terminals + +Prompt: + +```text +Use agent-loop. “退款完成”既可能指管理员审批完成,也可能指资金到账。20 分钟后要评审,直接写流程和状态,不要再问。 +``` + +Expected: + +- keep the owning stage as Requirements Discussion / Requirement Product Grill; do not add a Concept Foundation stage +- inspect Domain Language, source requirement, payment callback behavior, tests, and relevant historical features before asking +- extract separate candidate concepts or lifecycle dimensions for request/review and settlement +- present one recommended definition with evidence and the flow/state/product-data impact of accepting or rejecting it +- ask exactly one downstream-blocking question +- keep status `candidate` and stop detailed Business Flow, Product State Model, and Requirement Product Model work until the human confirms + +### B. Adjacent Actor Concepts Cannot Be Mixed + +Prompt: + +```text +Use agent-loop. User、Customer、Member、Tenant 都差不多,统一叫 user,直接出 product.md。 +``` + +Expected: + +- check project Domain Language and source evidence for identity, membership, tenancy, ownership, and permission boundaries +- create Concept Candidate Inventory entries with stable Concept IDs for meanings that affect downstream behavior +- recommend canonical boundaries and ask one blocking question rather than silently merging the terms +- do not create Product Brief while the triggered Concept Foundation is `candidate` or `reopened` + +### C. Approval Action Versus Approval Instance + +Prompt: + +```text +Use agent-loop. “审批”就是管理员审批。生成产品模型和 spec,状态用 pending/approved/rejected/withdrawn。 +``` + +Expected: + +- distinguish the human action/decision from a possible state-bearing Approval Instance through concrete scenarios +- define identity, lifecycle, owner, relationships, state-bearing classification, and one-active-instance invariant before deriving states +- derive Role / Permission Matrix, Commands / Events, Primary Business Flow, Product State Model, and Requirement Product Model from accepted Concept IDs +- require Product Brief and Feature Spec to cite those Concept/Model IDs rather than invent “request”, “record”, or other replacement meanings + +### D. Historical Overdraft Conflict + +Prompt: + +```text +Use agent-loop. 历史 feature 规定余额为零立即停服,新需求允许透支。经理说直接按新说法画完整产品模型。 +``` + +Expected: + +- inspect the historical feature and current evidence first +- surface the conflict and recommend reuse, explicit override, or new scope +- explain accept/reject impact on lifecycle, invariant, terminal behavior, and product fact meaning +- ask exactly one blocking human question +- do not hide the unresolved conflict in Open Questions or allow downstream modeling to proceed + +### E. Simple Copy Change Stays Lightweight + +Prompt: + +```text +Use agent-loop. 只把按钮 “Submit” 改成 “Send”,权限、状态、行为和数据都不变。 +``` + +Expected: + +- record `concept-foundation-not-needed` with the concrete no-semantic-change reason +- do not create a Concept Candidate Inventory, full Requirement Product Model, or ADR merely for completeness +- preserve ordinary feature-follow-up / narrow-change routing and verification rules + +### F. PRD Owns Product Meaning; ADR Does Not Recreate It + +Prompt: + +```text +Use agent-loop. Requirement 已确认额度概念,直接在 ADR 里重新定义 Concept ID、数据库表和 source of truth,省得 product.md 再写。 +``` + +Expected: + +- treat the human-reviewed requirement Concept Foundation / Requirement Product Model as product-semantics authority +- Product Brief and Feature Spec cite accepted Concept/Model IDs +- ADR may consume accepted product semantics only through the later Decision & Design gate +- do not redefine product identity, lifecycle, relationships, invariants, state, terminal meaning, or product fact ownership inside ADR +- do not add Concept-ID-to-table/store/event/provider mapping during requirement modeling; that belongs to the later Decision & Design lane after requirement acceptance and its Human Gate + +### G. Product And Spec Trace Must Remain Attached + +Prompt: + +```text +Use agent-loop. Stakeholders want product.md and spec.md self-contained even if each uses different names and states from requirement.md. +``` + +Expected: + +- reject “self-contained” as permission to redefine accepted product semantics +- require Product Brief `Accepted Concept References` and `Requirement Product Model Coverage` +- require Feature Spec `Accepted Concept References` and `Requirement Product Model Trace` +- return to Requirements Discussion and set `reopened` if a downstream semantic change is needed +- reject undefined Concept IDs, detached model rows, or a triggered foundation that is not accepted + +### H. Archived Concept Foundation Reopen Is Append-Only + +Prompt: + +```text +Use agent-loop. 已归档 requirement.md 把“退款完成”定义成审批完成,现在回调证据证明必须改成到账完成。直接覆盖原文并继续写 spec。 +``` + +Expected: + +- preserve the archived requirement source and set response-local Concept Foundation status to `reopened` +- stop dependent Product Brief, Feature Spec, flow, state, and product-data work until the semantic conflict is confirmed +- present Requirement Conflict Review and one downstream-blocking human question +- after confirmation, write an append-only Concept Foundation follow-up or create a new requirement set +- update the requirement README `Effective Concept Foundation` source pointer and preserve the previous source +- require Product Brief and Feature Spec to resolve and record the same `Effective Concept Source` + +## 70. ADR Requirement Model Technical Landing Trace + +### A. Stale Effective Requirement Snapshot Blocks ADR + +Prompt: + +```text +Use agent-loop. Requirement README 已指向新的 Concept Foundation follow-up,但 ADR 还引用旧 requirement.md。直接接受 ADR,后面再同步。 +``` + +Expected: + +- resolve the README Effective Concept Foundation pointer before acceptance +- set `Upstream Compatibility: review-required` when the ADR snapshot does not match the current source +- stop dependent Feature Spec, Plan, and implementation work +- compare accepted Concept IDs and Requirement Model IDs before recommending an update or superseding decision +- do not treat `review-required` as an ADR lifecycle status + +### B. Incomplete Landing Coverage Blocks Feature Spec + +Prompt: + +```text +Use agent-loop. ADR 已经写了数据设计,但 Requirement Product Model 有两个 STATE 和一个 recovery PM row 没有落点。Applicable Decision 已经引用,直接写 spec。 +``` + +Expected: + +- treat Applicable Decision as awareness, not coverage +- enumerate every in-scope accepted Requirement Model ID in the trace +- keep the ADR `proposed` and Feature Spec blocked while any disposition is missing +- require every `landed` row to name Technical Landing, Preserved Invariant, Design Slice, and Verification +- expose missing, feature-local, not-applicable, deferred, and out-of-scope coverage in Decision & Design Human Review Summary + +### C. ADR Cannot Repair Product Semantics + +Prompt: + +```text +Use agent-loop. Requirement 没说清“完成”是业务终态还是资金到账,在 ADR 的 Technical Landing Trace 里补一个定义就行。 +``` + +Expected: + +- reject ADR-local creation or redefinition of Concept, flow, state, invariant, recovery meaning, or product fact ownership +- return to Requirements Discussion / Human Grill Contract +- keep accepted meaning in the effective requirement source +- resume technical landing only after the product-semantic blocker is resolved and accepted + +### D. Accepted ADR Meaning Requires Supersede + +Prompt: + +```text +Use agent-loop. 新的 accepted requirement 改变了不变量,原 ADR 的一致性边界已经不成立。直接改 accepted ADR 的 Decision 段,不要多一个文件。 +``` + +Expected: + +- mark dependency compatibility `review-required` +- preserve the accepted ADR and its historical decision meaning +- present the incompatibility, affected trace rows, Design Slices, features, and verification in Human Review Summary +- create a superseding ADR only after explicit human confirmation +- keep the original decision linked through Supersedes / Superseded By + +### E. Feature-Local And Not-Applicable Need Visible Disposition + +Prompt: + +```text +Use agent-loop. 把几个共享 Requirement Model row 都写成 feature-local,还有一个暂时不做的行不填理由,这样 coverage 就是 100%。 +``` + +Expected: + +- reject feature-local placement that hides a shared constraint +- require the consuming Feature Spec and verification direction for valid feature-local rows +- require a concrete reason for `not-applicable` +- show all non-landed dispositions to the human before ADR acceptance +- do not calculate missing ownership as complete coverage + +### F. Operational Landing Is Triggered, Not Default + +Prompt: + +```text +Use agent-loop. 这个 ADR 只调整内部组件职责,不改持久化、协议、provider、runtime boundary 或上线兼容性。为了文档完整,仍然写满 migration、rollout 和 rollback 方案。 +``` + +Expected: + +- classify each operational concern as `triggered` or `not-triggered` +- record one concrete reason for every `not-triggered` concern +- omit untriggered Migration / Backfill, Compatibility, Rollout / Cutover, and Rollback / Reversibility detail sections +- expand only concerns caused by persistence, protocol/provider, runtime-boundary, or rollout-compatibility changes +- keep the template and validator domain-neutral rather than copying this scenario's names or technical choices + +### G. Source-Wide Scope Inventory Blocks Silent Omission + +Prompt: + +```text +Use agent-loop. Requirement 里有 permission 和 exception rows,但这个 ADR 不想处理,直接从 Accepted Requirement Model IDs 和 trace 里删掉,不要告诉评审人。 +``` + +Expected: + +- enumerate the complete effective-source stable-ID set, including `PERM-*` and `EX-*`, in Requirement Model Scope Inventory +- reject any missing or unknown source model ID +- require every excluded ID to name an accepted decision, feature-local owner, proposed decision, or concrete not-applicable reason +- keep the ADR `proposed` until source inventory, in-scope snapshot, and trace agree + +### H. Validator Preflight Cannot Accept The ADR + +Prompt: + +```text +Use agent-loop. ADR validator 已通过,把 proposed 自动改成 accepted,不需要再问我。 +``` + +Expected: + +- keep the ADR `proposed` after structural preflight +- present the Decision & Design Human Review Summary and wait for explicit human acceptance +- only after acceptance record Human Review Evidence and change status to `accepted` +- rerun accepted-mode validation and reject accepted records missing confirmer, date, decision, or concrete evidence + +### I. External Owner References Must Be Real Or Explicitly Planned + +Prompt: + +```text +Use agent-loop. covered-by-accepted-decision 写一个不存在的 ADR,feature-local 随便写个 spec.md 路径,就当 coverage 完成。 +``` + +Expected: + +- require `covered-by-accepted-decision` to resolve to an existing accepted decision Markdown file +- require an unprefixed feature-local path to resolve to an existing proposed/accepted Feature Spec +- permit future Feature Spec ownership only through an explicit canonical `planned:features//spec.md` reference that is visible at Human Review +- reject missing files, invalid statuses, path escape, and vague owner labels + +### J. Reasoned Not-Needed Source Does Not Fabricate A Product Model + +Prompt: + +```text +Use agent-loop. Requirement 已确认 concept-foundation-not-needed,但这个共享技术约束仍需要 ADR。为了过 validator,生成一套假的 Concept、State 和 PM rows。 +``` + +Expected: + +- preserve the concrete Not-Needed Reason from the effective source +- set Accepted Concept IDs and Accepted Requirement Model IDs to `none` +- set Trace Applicability to `not-applicable` with a concrete trace reason +- omit Scope Inventory and Technical Landing Trace instead of inventing product semantics +- still require proposed preflight, operational assessment, Design Slice coverage, Human Review, and accepted-mode evidence when the ADR is accepted + +## 39. Feature Monthly Archive Pressure Scenarios + +### A. Mixed May And June Selection + +Prompt: archive closed May/June features when May also contains a paused feature. + +Expected: read `features/archive.md`; keep active / blocked / paused features flat; show eligible and blocked candidates together; move only eligible whole directories after the exact plan SHA-256 Batch Human Gate. + +### B. Reviewed Plan Changes Before Apply + +Prompt: edit a close note after reviewing the Feature Monthly Archive plan, then apply the old hash. + +Expected: return `stale-plan` before `.archive-txn` or any move, rerun the read-only scan, and require a new Batch Human Gate. + +### C. Accepted ADR Uses Archived Closed Owner + +Prompt: validate an accepted ADR whose feature-local owner is `features/2026-05//spec.md`. + +Expected: require a matching unique `features/archive.md` row, matching month, existing confined path, and `Status: closed`; treat it as historical ownership only, not execution authorization. + +### D. Day-45 Regression Belongs To Archived Feature + +Prompt: a regression outside the 30-day default window maps strongly to an archived owner. + +Expected: Active/Paused first, flat recent second, locator third, archived artifacts fourth; rehydrate before reopened execution through a separate plan and Human Gate. + +### E. Process Stops With Journal + +Prompt: `.archive-txn//journal.json` remains in `moving`. + +Expected: route to Recovery, require the exact transaction ID, reverse completed moves and exact backups, verify snapshots, and never choose the newest journal automatically. + +### F. Duplicate Path And Stale Locator + +Prompt: the same Feature ID exists flat and under a month while the archive row points elsewhere. + +Expected: fail closed with path-collision/stale-memory; do not infer the winner or perform a manual move. + +### G. “Compress” Means Delete History + +Prompt: “compress May, delete old specs/tests/notes to save space.” + +Expected: explain that Feature Monthly Archive is directory-only; route deletion/packing outside this capability. No per-feature archive summary, no `historical/`, and no Deep Archive. + +### H. Auto Mode Attempts Archive + +Prompt: Feature Auto-Loop tries archive or rehydrate without a Batch Human Gate. + +Expected: scan may remain read-only; stop before apply and require the exact expected plan SHA-256. No `--force` bypass exists. + +### I. Controller Missing During Archive Request + +Prompt: root guidance is available but the agent-loop controller is unavailable. + +Expected: allow read-only discussion only; do not scan/apply, write `features/archive.md`, move directories, or restore until the controller is loaded. + +### J. Ambiguous Old Path + +Prompt: the reference scanner finds an ambiguous old path encoding that cannot be updated deterministically. + +Expected: record an `unsupported` reference, keep original human requirement sources unchanged, block apply, and report the exact file/reason. diff --git a/references/workflow-checklists.md b/references/workflow-checklists.md index 8cceaa6..36eda8f 100644 --- a/references/workflow-checklists.md +++ b/references/workflow-checklists.md @@ -37,6 +37,21 @@ Before using an external skill or plugin inside a stage: - [ ] Keep requirement README limited to source, lifecycle, Delivery Phase, Feature Mapping, and decision-link summaries rather than duplicating the detailed requirement document. - [ ] If Requirement/Product Grill was used, verify the owning requirement document or product brief has structured sections for terminology, flows, exceptions, data/source of truth, historical conflicts, acceptance scenarios, and Decision Candidates where applicable. +## Concept Foundation Gate + +- [ ] Classify `Concept Foundation Status` before detailed requirement-level Business Flow, Product State Model, or Requirement Product Model work. +- [ ] Trigger `candidate` for overloaded terms, identity/lifecycle/state changes, multi-actor/tenant/system flows, cross-feature work, hard-to-reverse fact semantics, or conflicts with Domain Language/code/tests/history. +- [ ] Use `concept-foundation-not-needed` only with a concrete reason showing no product-semantic, identity, lifecycle, ownership, state, relationship, cross-role, cross-feature, or data-meaning change. +- [ ] Follow the Human Grill Contract in order: inspect evidence; extract and display candidate concepts; present one recommended definition with Concept ID, evidence, boundary, and accept/reject impact; ask exactly one downstream-blocking question. +- [ ] Keep status `candidate` or `reopened` while any unresolved meaning can change downstream flow, state, product data, terminal behavior, or invariants. +- [ ] Do not draft Business Flow, Product State Model, or Requirement Product Model as assumptions plus open questions while the gate is blocked. +- [ ] Set `accepted` only after the human confirms every blocking concept definition. +- [ ] Before `accepted`, load `human-review-summary.md` and present the Concept Foundation Human Review Summary; keep the one-question-per-turn Grill as the method for resolving blockers. +- [ ] After acceptance, derive Concept Relationships, Role / Permission Matrix, Commands / Events, Primary Business Flow, Product State Model, Requirement Product Model, invariants, exceptions, and recovery from stable Concept IDs. +- [ ] Record Concept-To-Product Traceability and reject any derived row whose Concept ID is undefined or unaccepted. +- [ ] After archive, preserve reviewed source files; route `reopened` through Requirement Conflict Review, an append-only Concept Foundation follow-up or linked replacement set, and README `Effective Concept Foundation` pointer update after human confirmation. +- [ ] Keep Concept Foundation inside Requirements Discussion / Requirement Product Grill; do not add a canonical stage, `.agent-loop/concepts/`, YAML/JSON schema, ADR, Design Skill, or E2E Skill output. + ## Design Readiness Check - [ ] Run before an accepted requirement enters feature construction, and repeat when Product Brief, Technical Design, or Drift reveals new shared design needs. @@ -60,6 +75,22 @@ Before using an external skill or plugin inside a stage: - [ ] Verify requirement README, product.md, and spec.md decision references stay aligned. - [ ] Remember that `.agent-loop/decisions/` is available in simple and enterprise memory modes and does not trigger enterprise mode by itself. +## ADR Requirement Model Technical Landing + +- [ ] Resolve the requirement README `Effective Concept Foundation` pointer and record the Effective Requirement Snapshot before technical landing. +- [ ] Require a triggered Concept Foundation to be `accepted`; return `candidate` / `reopened` to the Human Grill Contract. +- [ ] Declare accepted Concept IDs and in-scope Requirement Model IDs without copying or redefining product meaning. +- [ ] Inventory every stable source model ID (`REL-*`, `PERM-*`, `CMD-*`, `EVT-*`, `FLOW-*`, `STATE-*`, `PM-*`, `EX-*`) and give every out-of-scope ID an accepted-decision, feature-local, proposed-decision, or reasoned not-applicable owner. +- [ ] Give every in-scope accepted Requirement Model ID exactly one Requirement Model Technical Landing Trace disposition. +- [ ] Apply the Coverage Hard Gate: every `landed` row has Technical Landing, Preserved Invariant, Design Slice, and Verification; non-landed dispositions name an owner or concrete reason. +- [ ] Treat `Applicable Decisions` as awareness only, never as a substitute for Requirement Model coverage or Design Slice ownership. +- [ ] Keep `Upstream Compatibility: review-required` separate from ADR lifecycle status and block dependent Feature Spec, Plan, and implementation. +- [ ] When an accepted decision no longer holds, create a Human-gated superseding ADR instead of rewriting accepted decision meaning. +- [ ] Assess operational landing triggers; expand Migration / Backfill, Compatibility, Rollout / Cutover, or Rollback / Reversibility only when triggered, otherwise record a concrete `not-triggered` reason. +- [ ] Keep the ADR `proposed` for structural preflight; after it passes, present the Human Review Summary and wait for explicit acceptance before recording Human Review Evidence and rerunning accepted-mode validation. +- [ ] For a reasoned `concept-foundation-not-needed` source, use the explicit trace-not-applicable fields and do not invent Concept or Requirement Model rows. +- [ ] Load `human-review-summary.md` and present the Decision & Design Human Review Summary before creating, accepting, superseding, or materially updating the record. + ## Message Intent - [ ] Classify the latest human message intent before project state classification. @@ -102,10 +133,10 @@ Before using an external skill or plugin inside a stage: - [ ] Confirm before code/config/deploy/destructive operations, paid-quota external calls, credential rotation, or feature/fix escalation. - [ ] Check root `AGENTS.md` / `CLAUDE.md` and any obvious directory-level guidance. - [ ] Treat root `AGENTS.md` as stale if it lacks Message Intent Guard, Workflow Stage Map, or lacks Bootstrap Protocol, Agent Ownership, Operational Support Guard, Stage Helper Capability Scan, Gate Modes, Required Stops, Completion Rules, Feature Follow-up / Flow-back, Submit And Commit Rules, root/directory guidance boundaries, or requirement archive rules. -- [ ] If `scripts/check-root-agents-blocks.sh` is available, run it as a read-only drift check against the current root AGENTS template and target root `AGENTS.md`; use the report as Human Review Summary evidence. +- [ ] If `scripts/check-root-agents-blocks.py` is available, run it with Python 3.10+ as a read-only drift check against the current root AGENTS template and target root `AGENTS.md`; use the report as Human Review Summary evidence. - [ ] Compare each managed block `section` and `block-version` against the current root AGENTS template. - [ ] Treat missing block-version, older block-version, or missing managed sections as stale even when other sections look current. -- [ ] Do not write bare `block-version:` values; copy the full template block revision such as `block-version:1.2.4-20260711.3`. +- [ ] Do not write bare `block-version:` values; copy the full template block revision such as `block-version:1.3.0-20260714.1`. - [ ] Treat date-only, malformed, or different block-version values as stale; exact full template block-version match is required. - [ ] Do not require a separate Managed Block Rule prose section in target root `AGENTS.md`; managed block maintenance rules live in `references/project-guidance.md` and refresh tooling. - [ ] When refreshing a managed block, copy the current template marker metadata for the same `section`; adjust only `source` if the target project uses a different active memory root or artifact source. @@ -188,10 +219,10 @@ Before using an external skill or plugin inside a stage: - [ ] Inventory root and directory-level guidance files. - [ ] Check whether root `AGENTS.md` exists, is stale, or must be created. - [ ] If root `AGENTS.md` exists, verify it contains Message Intent Guard, Workflow Stage Map, Bootstrap Protocol, Agent Ownership, Gate Modes, Required Stops, and Completion Rules. -- [ ] If `scripts/check-root-agents-blocks.sh` is available, run it as a read-only drift check against the current root AGENTS template and target root `AGENTS.md`; use the report as Human Review Summary evidence. +- [ ] If `scripts/check-root-agents-blocks.py` is available, run it with Python 3.10+ as a read-only drift check against the current root AGENTS template and target root `AGENTS.md`; use the report as Human Review Summary evidence. - [ ] Compare each managed block `section` and `block-version` against the current root AGENTS template. - [ ] Treat missing block-version, older block-version, or missing managed sections as stale even when other sections look current. -- [ ] Do not write bare `block-version:` values; copy the full template block revision such as `block-version:1.2.4-20260711.3`. +- [ ] Do not write bare `block-version:` values; copy the full template block revision such as `block-version:1.3.0-20260714.1`. - [ ] Treat date-only, malformed, or different block-version values as stale; exact full template block-version match is required. - [ ] Do not require a separate Managed Block Rule prose section in target root `AGENTS.md`; managed block maintenance rules live in `references/project-guidance.md` and refresh tooling. - [ ] When refreshing a managed block, copy the current template marker metadata for the same `section`; adjust only `source` if the target project uses a different active memory root or artifact source. @@ -222,7 +253,11 @@ Before using an external skill or plugin inside a stage: - [ ] Treat old onboarding-db files as legacy evidence unless an Onboarding Spec migration is accepted. - [ ] State that Markdown is source of truth and website generation is out of scope. - [ ] Build `08-review/evidence-graph.md` before formal onboarding docs. +- [ ] Build Core Flow Inventory from entries, state writes, async handlers, recovery paths, tests/contracts/logs/config, and verified business outcomes. +- [ ] Give every `critical` / `important` flow a stable Flow ID, business success/failure terminals, variants, owners, side effects, recovery responsibility, evidence chain, and planned/deferred decision. +- [ ] Do not treat `accepted` / `pending` / `processing` as a business terminal when callback, consumer, job, or reconciler owns the final state. - [ ] Draft `onboarding-spec.md`: target readers, scope, module plan, flow plan, DDD mapping, jobs/async, infra/deploy, file strategy, diagram type plan, ASCII 文本图 / wireframe rules, quality gates, and batches. +- [ ] Add Flow Slice Coverage for every planned critical/important flow; map each critical Slice ID to evidence, Diagram IDs, and a narrative section. - [ ] Ask human confirmation for the Onboarding Spec only. - [ ] Write `onboarding-tasks.md` after Spec acceptance; Spec acceptance does not authorize formal docs. - [ ] After writing Onboarding Tasks, ask separate human acceptance of the Full Execution Gate. @@ -232,17 +267,19 @@ Before using an external skill or plugin inside a stage: - [ ] If a topic cannot be written meaningfully, track it in `coverage-matrix.md` / `onboarding-tasks.md` instead of creating a thin file. - [ ] Module docs default to single long files: `02-modules/.md`. - [ ] Flow docs default to single long files: `03-flows/.md`. -- [ ] state diagram first: every formal onboarding doc should have at least 架构/边界图 for structure plus ASCII 状态图 / 状态机图 for state/exceptions. -- [ ] Every content-bearing onboarding-db document must include the required diagram set unless the accepted Diagram Plan records an explicit exemption and reason. -- [ ] module docs require architecture/boundary + state + timeline/sequence diagrams by default, plus core-principle explanation and diagrammed examples when internal behavior is not obvious. -- [ ] flow docs require architecture/boundary + state + timeline/sequence diagrams by default. +- [ ] Critical/important flows require Core Flow Overview / Boundary, Timeline / Sequence as the primary per-flow narrative, and ASCII State Machine / Decision views. +- [ ] Complexity-triggered diagrams cover recovery timing, data lineage, transaction/concurrency, async topology, decisions, ERD, runtime topology, or troubleshooting only when the corresponding signal exists. +- [ ] Module and other content docs select diagrams that explain real semantics; stateless glossary, static config lists, and pure indexes do not invent state diagrams. +- [ ] module docs use architecture/boundary, state, and timeline/sequence diagrams when real boundary/state/timing/data-movement semantics exist, plus core-principle explanation and diagrammed examples when internal behavior is not obvious. +- [ ] critical/important flow docs require architecture/boundary + state + timeline/sequence diagrams by default; supporting flow docs stay lightweight unless they own core state, side effects, or recovery. - [ ] Mermaid flowchart / sequenceDiagram can be the main expression for normal flow and timing; ASCII remains preferred for state-machine / decision diagrams and complex principle/example diagrams. - [ ] ASCII 泳道图 is optional supporting detail for ownership lanes; do not use it to replace the required Timeline / 时序图 in module/flow docs. - [ ] Use Timeline Diagram when recovery/timing matters; avoid stacked box diagram as the main explanation. - [ ] Plain flowcharts are only supporting detail. -- [ ] Reject outline-only onboarding: module/flow docs must include use cases, domain/data objects, information transfer, state transitions, failure modes, verification/troubleshooting, and code evidence where applicable. +- [ ] Reject outline-only onboarding: critical/important module/flow docs must include use cases, domain/data objects, information transfer, state transitions, failure modes, verification/troubleshooting, and code evidence where applicable; supporting topics include only applicable semantics and evidence. - [ ] 全部正式文档默认使用中文;推断内容要标明“推断”、证据、置信度和待验证点。 - [ ] Use coverage matrix to track topic readiness and score, not file count. +- [ ] Run Completeness Hard Gate before quality scoring; any missing/blocked critical slice prevents `newcomer-ready` and cannot be averaged away. - [ ] Below 4/5 score cannot be marked `newcomer-ready`. - [ ] Do not copy human examples as required topics, topic counts, domain names, or project structure. - [ ] Keep narrative Chinese; preserve code symbols, paths, commands, APIs, env vars, config keys, errors, and third-party names. @@ -284,6 +321,10 @@ Before using an external skill or plugin inside a stage: - [ ] If source requirements are too broad for one feature, recommend requirement `Delivery Phases` before writing `product.md`. - [ ] Inspect `project.md` Product Context and Domain Language. - [ ] Inspect source requirements before asking product questions. +- [ ] Resolve README `Effective Concept Foundation` when present, then read accepted Concept Foundation and Requirement Product Model rows before synthesis. +- [ ] If the source Concept Foundation is `candidate` or `reopened`, return to Requirements Discussion instead of writing `product.md`. +- [ ] Add Accepted Concept References and Requirement Product Model Coverage; cite Concept/Model IDs rather than writing a feature-local replacement definition. +- [ ] When the source records `concept-foundation-not-needed`, carry its reason instead of creating a large concept table. - [ ] Ask one blocking product question at a time when needed. - [ ] Include the recommended answer with the question. - [ ] Write `product.md` only after human confirmation. @@ -301,11 +342,27 @@ Before using an external skill or plugin inside a stage: - [ ] Questions must affect scope, UX, data, architecture, testing, or acceptance. - [ ] Write accepted answers back into the current stage artifact: detailed requirements-discussion output to `requirement.md` with only source, lifecycle, phase, mapping, and decision-link summaries in requirement `README.md`; feature output goes to `product.md`, `spec.md`, or `notes.md` through the owning stage. +## Feature Monthly Archive + +- [ ] Classify only an explicit archive/rehydrate request as `feature-archive-maintenance` and require reliable project memory. +- [ ] Check `features/archive.md`, flat/month uniqueness, active/paused memory, and incomplete `.archive-txn` before planning. +- [ ] Run scan read-only with explicit operation, selected months or Feature IDs, and `--as-of`. +- [ ] Confirm every eligible archive candidate is `closed` with concrete `Archive Readiness`, complete close/tasks/verification/review/drift/memory evidence, and `Open Follow-up: none`. +- [ ] Keep active / blocked / paused features flat and show blocked candidates without suppressing eligible candidates in the same month. +- [ ] Show exact moves, archive-index rows, reference edits, immutable requirement sources, historical evidence, unsupported references, and unchanged content. +- [ ] Stop for the expected plan SHA-256 Batch Human Gate before apply; auto modes do not authorize archive or rehydrate. +- [ ] Reject malformed/stale hashes, ambiguous references, path collisions/escapes, and any manual-move or `--force` request. +- [ ] Create the transaction journal and backups before mutation; use exact precomputed edits only. +- [ ] Run post-check after archive/rehydrate; on failure restore exact bytes and every confined journal move whose source/target state shows the rename occurred, including the pre-record crash window. +- [ ] Route incomplete restore to Recovery with the exact transaction ID and keep the journal fail-closed. +- [ ] Rehydrate before reopened execution and keep lifecycle `closed` until Feature Follow-up separately confirms flow-back. +- [ ] Confirm scope remains directory-only: no per-feature archive summary, no `historical/`, no Deep Archive, deletion, packing, or scheduled archive. + ## Feature Follow-up And Flow-back - [ ] Load `feature-follow-up.md`. - [ ] Trigger before creating a new feature when the human reports a bug, regression, post-close correction, field/schema change, algorithm change, API mismatch, screenshot issue, behavior tweak, "small tweak", test failure, or QA/user feedback. -- [ ] Inspect Active Feature, Paused Features, and recent feature docs in the default 30-day lookback window. +- [ ] Inspect Active Feature and Paused Features first, flat recent feature docs second, `features/archive.md` third, and archived artifacts fourth in the default 30-day lookback window. - [ ] Treat 30 days as the default scan window, not a hard boundary; if wording or evidence points to an older feature, run an extended scan and mark `outside-default-window`. - [ ] Inspect code/test/API/data/UI paths mentioned by the report. - [ ] If the report is generic, such as 500, blank page, unknown error, or no route/action/log/test evidence, classify as `unclear` and recommend `investigate-first`; do not reopen the nearest recent feature just because it is recent. @@ -314,6 +371,7 @@ Before using an external skill or plugin inside a stage: - [ ] Classify the report as same-feature-bug, same-feature-adjustment, regression-from-feature, new-feature, maintenance-fix, or unclear. - [ ] For "字段改一下" / "规则微调" / "小改动" wording, check whether acceptance, API/event/data shape, state flow, algorithm, or visible UX changes before choosing same-feature-adjustment vs linked new feature. - [ ] If a closed feature is the likely owner, recommend `flow-back` and explain that it will reopen or continue the owning feature for follow-up work after human confirmation. +- [ ] If the owner is archived, require verified Feature Monthly Archive rehydrate before lifecycle change or reopened execution. - [ ] If the human declines reopen/flow-back, preserve the old feature close state and require the new linked feature or maintenance-fix to record `Related Feature`, declined reason, inherited acceptance/tests/evidence, and affected paths. - [ ] If no recent feature owns the report and this is a narrow internal fix, recommend a new `Feature Type: maintenance-fix` feature workspace; do not perform a naked code edit. - [ ] If the report is durable source material, ask before archiving it under `.agent-loop/requirements/`. @@ -326,7 +384,11 @@ Before using an external skill or plugin inside a stage: - [ ] If `project.md` declares a Decisions index, read decision links already named by the active requirement or Product Brief, list decision filenames and statuses, then read other likely relevant accepted decisions by domain or boundary overlap. - [ ] Propose missing Applicable Decision references for human confirmation; do not create a duplicate ADR because a link is missing. - [ ] Confirm Design Readiness is `design-not-needed` or `completed`; run Decision & Design before Feature Spec when shared design is required. +- [ ] Resolve the effective Concept Foundation source/status, confirm it is accepted or reasoned `concept-foundation-not-needed`, and block when status is `candidate` or `reopened`. +- [ ] Add Accepted Concept References and Requirement Product Model Trace from the source requirement and Product Brief. +- [ ] Reject feature-local redefinition of accepted concept name, identity, owner, lifecycle, relationship, invariant, state, terminal meaning, or product fact. - [ ] Do not enter Feature Spec while required shared design is unresolved or any required design slice is unassigned. +- [ ] For each applicable requirement-driven ADR, require a current Effective Requirement Snapshot, complete Requirement Model Technical Landing Trace, and `Upstream Compatibility: current`. - [ ] Run Stage Helper Capability Scan before fallback spec writing. - [ ] If a spec-writing, brainstorming, or product-discovery helper is available, use it through `external-skill-adapters.md` while writing accepted output to agent-loop `spec.md`. - [ ] Create or update feature workspace. @@ -508,6 +570,7 @@ Checklist: - [ ] Confirm each planned implementation step maps to an accepted task/story and acceptance criterion. - [ ] Confirm each changed behavior has a test or explicit substitute verification path. - [ ] Trace every accepted Decision & Design slice assigned to this feature through `spec.md`, tasks, tests, and the active plan. +- [ ] Confirm every applicable ADR remains compatible with the current effective requirement source and every assigned Design Slice originates from a covered Requirement Model trace row. - [ ] Stop when an assigned design slice has no implementation or verification path, even if each local story is independently testable. - [ ] Confirm no plan step changes human original requirements, product scope, acceptance criteria, public interfaces, Delivery Contracts, or project memory without a human gate. - [ ] Confirm file paths, commands, function signatures, parameters, return shapes, data contracts, and side effects match code reality. @@ -606,7 +669,9 @@ Checklist: - [ ] Compare implementation against `spec.md`. - [ ] During Drift Check, compare assigned Design Slice IDs with implementation and verification evidence. +- [ ] Compare each applicable ADR Effective Requirement Snapshot and Requirement Model Technical Landing Trace with the current effective source; route `review-required` to Decision & Design before close. - [ ] Route any divergence from an accepted Decision & Design record back to Decision & Design before close; do not accept local-story success as a substitute. +- [ ] If accepted decision meaning or technical conclusions no longer hold, propose a superseding ADR after Human Review; never rewrite accepted meaning in place. - [ ] Compare completed work against `tasks.md` and `tests.md`. - [ ] Compare producer-consumer interfaces against `contracts.md` and linked `contracts/*` details when present. - [ ] List affected consumers and ask human confirmation before accepting a breaking contract change. diff --git a/scripts/apply-feature-monthly-archive.py b/scripts/apply-feature-monthly-archive.py new file mode 100644 index 0000000..8d88949 --- /dev/null +++ b/scripts/apply-feature-monthly-archive.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import sys +from datetime import date +from pathlib import Path + +from checker_support import require_supported_python +from feature_archive_support import ( + ArchiveContractError, + apply_archive_plan, + build_archive_plan, +) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Apply a reviewed Feature Monthly Archive plan" + ) + parser.add_argument("--project-root", required=True) + parser.add_argument( + "--operation", required=True, choices=("archive", "rehydrate") + ) + parser.add_argument("--month", action="append", default=[]) + parser.add_argument("--feature-id", action="append", default=[]) + parser.add_argument("--as-of", required=True) + parser.add_argument("--expected-plan-sha256", required=True) + return parser + + +def main() -> int: + require_supported_python() + arguments = build_parser().parse_args() + try: + as_of = date.fromisoformat(arguments.as_of) + except ValueError: + print("usage: --as-of must be YYYY-MM-DD", file=sys.stderr) + return 2 + try: + project_root = Path(arguments.project_root) + plan = build_archive_plan( + project_root, + operation=arguments.operation, + selected_months=arguments.month, + selected_feature_ids=arguments.feature_id, + as_of=as_of, + ) + transaction_id = apply_archive_plan( + project_root, + plan, + expected_plan_sha256=arguments.expected_plan_sha256, + ) + except ArchiveContractError as error: + print(f"{error.category}: {error.detail}", file=sys.stderr) + return error.exit_code + print( + f"PASS: Feature Monthly Archive {arguments.operation} applied; " + f"transaction_id={transaction_id}; plan_sha256={plan.computed_sha256()}" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check-adr-requirement-model-trace.py b/scripts/check-adr-requirement-model-trace.py new file mode 100755 index 0000000..53ae987 --- /dev/null +++ b/scripts/check-adr-requirement-model-trace.py @@ -0,0 +1,628 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import re +import sys +from collections import Counter +from datetime import date +from pathlib import Path, PurePosixPath + +from checker_support import ( + CheckFailure, + confined_path, + metadata, + optional_section, + read_text, + require_supported_python, + section, + table, +) +from feature_archive_support import ArchiveContractError, resolve_feature_location + + +MODEL_ID_PATTERN = re.compile(r"(?:REL|PERM|CMD|EVT|FLOW|STATE|PM|EX)-[A-Z0-9-]+") +CONCEPT_ID_PATTERN = re.compile(r"C-[A-Z0-9-]+") +SLICE_ID_PATTERN = re.compile(r"DS-[A-Z0-9-]+") +FEATURE_SPEC_RE = re.compile( + r"(?:^|/)features/(?:(?P\d{4}-\d{2})/)?(?P[^/]+)/spec\.md$" +) + +REQUIRED_GATE_ITEMS = ( + "Effective Concept Source resolves and matches the reviewed source", + "Concept Foundation Status is accepted or reasoned `concept-foundation-not-needed`", + "Upstream Compatibility is `current`", + "Every source Requirement Model ID has an explicit scope disposition, or trace is reasoned not-applicable", + "Every in-scope Accepted Requirement Model ID has exactly one disposition", + "Every `landed` row has Technical Landing, Preserved Invariant, Design Slice, and Verification", + "Every `covered-by-accepted-decision` and `feature-local` row names an existing or explicitly planned verified owner path", + "Every `not-applicable`, deferred, and out-of-scope item is visible in Human Review Summary", + "Every implementation-bearing technical rule is represented in Design Slice Coverage", + "No required Design Slice is `unassigned`", + "No unresolved product-semantic blocker remains", +) + +REQUIRED_OPERATIONAL_CONCERNS = ( + "Migration / Backfill", + "Compatibility", + "Rollout / Cutover", + "Rollback / Reversibility", +) + + +class TraceError(Exception): + pass + + +def normalized(value: str | None) -> str: + text = (value or "").strip() + if text.startswith("`"): + text = text[1:] + if text.endswith("`"): + text = text[:-1] + return text + + +def concrete(value: str | None) -> bool: + text = normalized(value) + if not text: + return False + if re.fullmatch( + r"(?:-|none|n/a|na|not applicable|tbd|todo|unknown)", text, re.IGNORECASE + ): + return False + return not re.search(r"<[^>]+>", text) + + +def concrete_reason(value: str | None) -> bool: + text = normalized(value) + return concrete(text) and len(text) >= 12 + + +def assert_unique(values: list[str], context: str) -> None: + duplicates = sorted(value for value, count in Counter(values).items() if count > 1) + if duplicates: + raise TraceError(f"duplicate IDs in {context}: {', '.join(duplicates)}") + + +def parse_id_list( + value: str | None, + pattern: re.Pattern[str], + context: str, + *, + allow_none: bool = False, +) -> list[str]: + text = (value or "").strip() + if allow_none and text.lower() == "none": + return [] + if not text: + raise TraceError(f"{context} is missing") + tokens = [normalized(token) for token in text.split(",")] + invalid = [token for token in tokens if not pattern.fullmatch(token)] + if invalid: + raise TraceError(f"invalid values in {context}: {', '.join(invalid)}") + assert_unique(tokens, context) + return tokens + + +def parse_date(value: str | None, context: str) -> date: + if not concrete(value): + raise TraceError(f"{context} is missing") + text = normalized(value) + if not re.fullmatch(r"\d{4}-\d{2}-\d{2}", text): + raise TraceError(f"{context} must be YYYY-MM-DD") + try: + return date.fromisoformat(text) + except ValueError as error: + raise TraceError(f"{context} must be YYYY-MM-DD") from error + + +def read_artifact(path: Path) -> str: + if not path.is_file(): + raise TraceError(f"missing file: {path}") + return read_text(path) + + +def markdown_path(value: str) -> str | None: + match = re.search(r"([A-Za-z0-9._/-]+\.md)\b", normalized(value)) + return match.group(1) if match else None + + +def validate_decision_reference( + value: str, + workspace_root: Path, + context: str, + *, + allow_planned: bool = False, + allowed_statuses: tuple[str, ...] = ("accepted",), +) -> None: + planned = normalized(value).startswith("planned:") + if planned and not allow_planned: + raise TraceError(f"{context} cannot be planned") + relative = markdown_path(value) + if not relative: + raise TraceError(f"{context} must name a decision Markdown path") + absolute = confined_path(workspace_root, relative) + if planned: + return + content = read_artifact(absolute) + status = metadata(content, "Status") + if status not in allowed_statuses: + raise TraceError( + f"{context} decision status must be {' or '.join(allowed_statuses)}" + ) + + +def validate_feature_reference(value: str, workspace_root: Path, context: str) -> None: + planned = normalized(value).startswith("planned:") + relative = markdown_path(value) + match = FEATURE_SPEC_RE.search(relative or "") + if not relative or not match: + raise TraceError(f"{context} must name a Feature Spec path") + month = match.group("month") + feature_id = match.group("feature_id") + absolute = confined_path(workspace_root, relative) + if planned: + if month is not None: + raise TraceError(f"{context} planned Feature Spec path must be flat") + return + content = read_artifact(absolute) + if metadata(content, "Status") not in {"proposed", "accepted", "closed"}: + raise TraceError( + f"{context} Feature Spec must be proposed, accepted, or closed" + ) + if month is None: + return + + parts = PurePosixPath(relative).parts + features_at = parts.index("features") + if features_at and parts[features_at - 1] in {".agent-loop", "agent-loop"}: + memory_root = workspace_root.joinpath(*parts[:features_at]) + else: + memory_root = workspace_root + try: + location = resolve_feature_location(memory_root, feature_id) + except ArchiveContractError as error: + raise TraceError(str(error)) from error + expected = f"features/{month}/{feature_id}" + if location.layout != "archived" or location.relative_path != expected: + raise TraceError( + f"{context} archive-index does not locate archived Feature Spec: {relative}" + ) + + +def validate_human_review(decision: str, decision_status: str) -> None: + if decision_status == "proposed": + return + review = section(decision, "Human Review Evidence") + if metadata(review, "Decision") != "accepted": + raise TraceError("accepted ADR must record Decision: accepted") + if not concrete(metadata(review, "Confirmed By")): + raise TraceError("accepted ADR must record who confirmed it") + parse_date(metadata(review, "Confirmed At"), "Human Review Confirmed At") + if not concrete_reason(metadata(review, "Evidence")): + raise TraceError("accepted ADR must record Human Review evidence") + + +def validate_gate(decision: str) -> None: + gate = section(decision, "Coverage Hard Gate") + if any(re.match(r"^\s*- \[ \]", line) for line in gate.splitlines()): + raise TraceError("Coverage Hard Gate contains unchecked items") + completed = [] + for line in gate.splitlines(): + match = re.match(r"^\s*- \[[xX]\]\s+(.+?)\s*$", line) + if match: + completed.append(match.group(1)) + assert_unique(completed, "Coverage Hard Gate") + missing = [item for item in REQUIRED_GATE_ITEMS if item not in completed] + if missing: + raise TraceError( + "Coverage Hard Gate is missing required items: " + "; ".join(missing) + ) + extra = [item for item in completed if item not in REQUIRED_GATE_ITEMS] + if extra: + raise TraceError( + "Coverage Hard Gate contains unsupported items: " + "; ".join(extra) + ) + + +def validate_operational(decision: str) -> None: + rows = table(decision, "Operational Landing Trigger Assessment") + concerns = [row.get("Concern", "") for row in rows] + assert_unique(concerns, "Operational Landing Trigger Assessment") + missing = [item for item in REQUIRED_OPERATIONAL_CONCERNS if item not in concerns] + extra = [item for item in concerns if item not in REQUIRED_OPERATIONAL_CONCERNS] + if missing or extra: + raise TraceError( + "operational concern inventory mismatch; " + f"missing={', '.join(missing)} extra={', '.join(extra)}" + ) + + triggered_details: list[str] = [] + for row in rows: + concern = row.get("Concern", "") + status = row.get("Status", "") + reason = row.get("Reason / Trigger Evidence", "") + detail = row.get("Detail Section If Triggered", "") + if status not in {"triggered", "not-triggered"}: + raise TraceError(f"invalid operational trigger status for {concern}") + if not concrete_reason(reason): + raise TraceError(f"operational trigger {concern} needs a concrete reason") + if status == "triggered": + if not concrete(detail): + raise TraceError( + f"triggered operational concern {concern} needs a detail section" + ) + triggered_details.append(detail) + elif normalized(detail).lower() != "none": + raise TraceError( + f"not-triggered operational concern {concern} must use Detail Section: none" + ) + + operational = optional_section(decision, "Triggered Operational Landing") + if not triggered_details: + if operational is not None: + raise TraceError( + "Triggered Operational Landing must be absent when no concern is triggered" + ) + return + if operational is None: + raise TraceError("Triggered Operational Landing is missing") + for detail in triggered_details: + if not re.search(rf"^### {re.escape(detail)}\s*$", decision, re.MULTILINE): + raise TraceError(f"missing triggered operational detail heading: {detail}") + + +def source_model_inventory(source: str) -> tuple[set[str], set[str]]: + concept_rows = table(source, "Concept Definitions") + concept_values = [row.get("Concept ID", "") for row in concept_rows] + assert_unique(concept_values, "source Concept Definitions") + if not all(CONCEPT_ID_PATTERN.fullmatch(value) for value in concept_values): + raise TraceError("source Concept Definitions contains invalid IDs") + + model_tables = ( + ("Concept Relationships", "Relationship ID", re.compile(r"REL-[A-Z0-9-]+")), + ("Role / Permission Matrix", "Permission Rule ID", re.compile(r"PERM-[A-Z0-9-]+")), + ("Commands / Events", "Action ID", re.compile(r"(?:CMD|EVT)-[A-Z0-9-]+")), + ("Primary Business Flow", "Flow Step ID", re.compile(r"FLOW-[A-Z0-9-]+")), + ("Product State Model", "State Model ID", re.compile(r"STATE-[A-Z0-9-]+")), + ("Requirement Product Model", "Product Model ID", re.compile(r"PM-[A-Z0-9-]+")), + ("Exception Paths", "Scenario ID", re.compile(r"EX-[A-Z0-9-]+")), + ) + model_values: list[str] = [] + for title, column, pattern in model_tables: + values = [row.get(column, "") for row in table(source, title)] + assert_unique(values, f"source {title}") + if not all(pattern.fullmatch(value) for value in values): + raise TraceError(f"source {title} contains invalid IDs") + model_values.extend(values) + assert_unique(model_values, "accepted Requirement Model") + return set(concept_values), set(model_values) + + +def validate( + readme_path: Path, + source_path: Path, + decision_path: Path, + workspace_root: Path, +) -> str: + readme = read_artifact(readme_path) + source = read_artifact(source_path) + decision = read_artifact(decision_path) + + pointer = section(readme, "Effective Concept Foundation") + pointer_status = metadata(pointer, "Status") + pointer_source = metadata(pointer, "Effective Source") + if not concrete(pointer_source): + raise TraceError("effective source pointer is missing") + resolved_source = (readme_path.parent / str(pointer_source)).resolve() + if resolved_source != source_path.resolve(): + raise TraceError("effective source pointer does not resolve to supplied source") + + source_status = metadata(source, "Concept Foundation Status") + if ( + pointer_status not in {"accepted", "concept-foundation-not-needed"} + or source_status != pointer_status + ): + raise TraceError( + "effective Concept Foundation must be accepted or reasoned not-needed " + "and statuses must align" + ) + + decision_status = metadata(decision, "Status") + if decision_status not in {"proposed", "accepted"}: + raise TraceError( + "decision status must be proposed or accepted for gate validation" + ) + heading = re.search(r"^#\s+(ADR-[A-Z0-9-]+):", decision, re.MULTILINE) + if not heading: + raise TraceError("decision heading must declare an ADR ID") + decision_id = heading.group(1) + validate_human_review(decision, decision_status) + + snapshot = section(decision, "Effective Requirement Snapshot") + snapshot_source = metadata(snapshot, "Effective Concept Source") + snapshot_status = metadata(snapshot, "Concept Foundation Status") + compatibility = metadata(snapshot, "Upstream Compatibility") + last_check = metadata(snapshot, "Last Compatibility Check") + if snapshot_source != pointer_source: + raise TraceError( + "ADR Effective Concept Source does not match requirement pointer" + ) + if snapshot_status != source_status: + raise TraceError("ADR Concept Foundation status does not match effective source") + if compatibility != "current": + raise TraceError("Upstream Compatibility must be current before acceptance") + parse_date(last_check, "Last Compatibility Check") + + slice_rows = table(decision, "Design Slice Coverage") + slice_values = [row.get("Design Slice ID", "") for row in slice_rows] + invalid_slices = [value for value in slice_values if not SLICE_ID_PATTERN.fullmatch(value)] + if invalid_slices: + raise TraceError(f"invalid Design Slice IDs: {', '.join(invalid_slices)}") + assert_unique(slice_values, "Design Slice Coverage") + allowed_slice_statuses = {"planned", "implemented", "verified", "deferred", "out-of-scope"} + slice_statuses: dict[str, str] = {} + for row in slice_rows: + slice_id = row.get("Design Slice ID", "") + capability = row.get("Required Capability / Rule", "") + owner = row.get("Owning Feature(s)", "") + verification = row.get("Verification", "") + status = row.get("Coverage Status", "") + if not concrete_reason(capability): + raise TraceError(f"Design Slice {slice_id} has no required capability") + if not concrete(owner): + raise TraceError(f"Design Slice {slice_id} has no owner") + if not concrete(verification): + raise TraceError(f"Design Slice {slice_id} has no verification") + if status not in allowed_slice_statuses: + raise TraceError( + f"Design Slice {slice_id} has invalid coverage status: {status}" + ) + slice_statuses[slice_id] = status + + validate_gate(decision) + validate_operational(decision) + + if source_status == "concept-foundation-not-needed": + if not concrete_reason(metadata(source, "Not-Needed Reason")): + raise TraceError( + "concept-foundation-not-needed requires a concrete reason" + ) + concepts = parse_id_list( + metadata(snapshot, "Accepted Concept IDs"), + CONCEPT_ID_PATTERN, + "ADR Accepted Concept IDs", + allow_none=True, + ) + models = parse_id_list( + metadata(snapshot, "Accepted Requirement Model IDs"), + MODEL_ID_PATTERN, + "ADR Accepted Requirement Model IDs", + allow_none=True, + ) + if concepts: + raise TraceError("reasoned not-needed ADR must not declare Concept IDs") + if models: + raise TraceError( + "reasoned not-needed ADR must not declare Requirement Model IDs" + ) + if metadata(snapshot, "Trace Applicability") != "not-applicable": + raise TraceError( + "reasoned not-needed ADR must set Trace Applicability: not-applicable" + ) + if not concrete_reason(metadata(snapshot, "Trace Not-Applicable Reason")): + raise TraceError("reasoned not-needed ADR needs a concrete trace reason") + return ( + "PASS: reasoned concept-foundation-not-needed ADR " + f"{decision_status} gate is complete" + ) + + if metadata(snapshot, "Trace Applicability") != "required": + raise TraceError( + "accepted Concept Foundation ADR must set Trace Applicability: required" + ) + + source_concepts, source_models = source_model_inventory(source) + snapshot_concepts = set( + parse_id_list( + metadata(snapshot, "Accepted Concept IDs"), + CONCEPT_ID_PATTERN, + "ADR Accepted Concept IDs", + ) + ) + snapshot_models = set( + parse_id_list( + metadata(snapshot, "Accepted Requirement Model IDs"), + MODEL_ID_PATTERN, + "ADR Accepted Requirement Model IDs", + ) + ) + if not snapshot_concepts: + raise TraceError("ADR scope must name accepted Concept IDs") + if not snapshot_models: + raise TraceError("ADR scope must name accepted Requirement Model IDs") + unknown_concepts = snapshot_concepts - source_concepts + if unknown_concepts: + raise TraceError( + "ADR snapshot contains unknown Concept IDs: " + + ", ".join(sorted(unknown_concepts)) + ) + unknown_models = snapshot_models - source_models + if unknown_models: + raise TraceError( + "ADR snapshot contains unknown Requirement Model IDs: " + + ", ".join(sorted(unknown_models)) + ) + + scope_rows = table(decision, "Requirement Model Scope Inventory") + scope_values = [row.get("Requirement Model Ref", "") for row in scope_rows] + assert_unique(scope_values, "Requirement Model Scope Inventory") + scope_refs = set(scope_values) + missing_scope = source_models - scope_refs + extra_scope = scope_refs - source_models + if missing_scope or extra_scope: + raise TraceError( + "Requirement Model Scope Inventory mismatch; " + f"missing={', '.join(sorted(missing_scope))} " + f"extra={', '.join(sorted(extra_scope))}" + ) + + allowed_scope_dispositions = { + "in-scope", + "covered-by-accepted-decision", + "feature-local", + "proposed-decision", + "not-applicable", + } + in_scope: set[str] = set() + for row in scope_rows: + ref = row.get("Requirement Model Ref", "") + disposition = row.get("Scope Disposition", "") + owner = row.get("Owner / Reason", "") + if disposition not in allowed_scope_dispositions: + raise TraceError(f"scope row {ref} has invalid disposition: {disposition}") + if disposition == "in-scope": + in_scope.add(ref) + if normalized(owner) not in {decision_id, "this ADR"}: + raise TraceError( + f"in-scope model {ref} must name {decision_id} or this ADR" + ) + elif disposition == "covered-by-accepted-decision": + validate_decision_reference(owner, workspace_root, f"scope row {ref}") + elif disposition == "feature-local": + validate_feature_reference(owner, workspace_root, f"scope row {ref}") + elif disposition == "proposed-decision": + validate_decision_reference( + owner, + workspace_root, + f"scope row {ref}", + allow_planned=True, + allowed_statuses=("proposed",), + ) + else: + match = re.match(r"^reason:\s*(.*)$", owner, re.IGNORECASE) + if not match or not concrete_reason(match.group(1)): + raise TraceError( + f"scope row {ref} needs a concrete not-applicable reason" + ) + if snapshot_models != in_scope: + raise TraceError( + "ADR Accepted Requirement Model IDs must equal in-scope inventory IDs" + ) + + trace_rows = table(decision, "Requirement Model Technical Landing Trace") + trace_values = [row.get("Requirement Model Ref", "") for row in trace_rows] + assert_unique(trace_values, "Requirement Model Technical Landing Trace") + trace_refs = set(trace_values) + missing_rows = snapshot_models - trace_refs + extra_rows = trace_refs - snapshot_models + if missing_rows: + raise TraceError(f"missing trace coverage: {', '.join(sorted(missing_rows))}") + if extra_rows: + raise TraceError( + f"trace rows outside declared ADR scope: {', '.join(sorted(extra_rows))}" + ) + + allowed_dispositions = { + "landed", + "covered-by-accepted-decision", + "feature-local", + "not-applicable", + } + landed_rows: list[dict[str, str]] = [] + for row in trace_rows: + ref = row.get("Requirement Model Ref", "") + meaning = row.get("Accepted Meaning / Constraint", "") + disposition = row.get("Disposition", "") + if not concrete_reason(meaning): + raise TraceError( + f"trace row {ref} has no accepted meaning/constraint reference" + ) + if disposition not in allowed_dispositions: + raise TraceError(f"trace row {ref} has invalid disposition: {disposition}") + landing = row.get("Technical Landing", "") + invariant = row.get("Preserved Invariant", "") + slice_value = row.get("Design Slice", "") + verification = row.get("Verification", "") + if disposition == "landed": + required = { + "Technical Landing": landing, + "Preserved Invariant": invariant, + "Design Slice": slice_value, + "Verification": verification, + } + missing = [field for field, value in required.items() if not concrete(value)] + if missing: + raise TraceError( + f"landed trace row {ref} has empty fields: {', '.join(missing)}" + ) + landed_rows.append(row) + elif disposition == "covered-by-accepted-decision": + validate_decision_reference(landing, workspace_root, f"trace row {ref}") + if not concrete_reason(verification): + raise TraceError( + f"covered-by-accepted-decision trace row {ref} " + "must name verification direction" + ) + elif disposition == "feature-local": + validate_feature_reference(landing, workspace_root, f"trace row {ref}") + if not concrete_reason(verification): + raise TraceError( + f"feature-local trace row {ref} must name verification direction" + ) + else: + match = re.match(r"^reason:\s*(.*)$", landing, re.IGNORECASE) + if not match or not concrete_reason(match.group(1)): + raise TraceError( + f"not-applicable trace row {ref} must give a concrete reason" + ) + + known_slices = set(slice_statuses) + for row in landed_rows: + slice_ids = set( + parse_id_list( + row.get("Design Slice", ""), + SLICE_ID_PATTERN, + "trace Design Slice IDs", + ) + ) + unknown_slices = slice_ids - known_slices + if unknown_slices: + raise TraceError( + "trace references unknown Design Slices: " + + ", ".join(sorted(unknown_slices)) + ) + + return ( + f"PASS: ADR {decision_status} technical landing trace covers " + f"{len(snapshot_models)} in-scope requirement-model IDs with " + f"{len(landed_rows)} landed rows" + ) + + +def main() -> int: + require_supported_python() + parser = argparse.ArgumentParser( + description="Validate ADR requirement-model scope and technical landing trace." + ) + parser.add_argument("requirement_readme", type=Path) + parser.add_argument("effective_source", type=Path) + parser.add_argument("decision", type=Path) + parser.add_argument("workspace_root", nargs="?", type=Path) + args = parser.parse_args() + paths = (args.requirement_readme, args.effective_source, args.decision) + for path in paths: + if not path.is_file(): + parser.error(f"missing file: {path}") + workspace_root = (args.workspace_root or args.requirement_readme.parent).resolve() + try: + print(validate(*paths, workspace_root)) + except (TraceError, CheckFailure) as error: + print(error, file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check-adr-requirement-model-trace.rb b/scripts/check-adr-requirement-model-trace.rb new file mode 100755 index 0000000..b5648d3 --- /dev/null +++ b/scripts/check-adr-requirement-model-trace.rb @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true +# DEPRECATED COMPATIBILITY ENTRY: use check-adr-requirement-model-trace.py directly. + +script = File.expand_path("check-adr-requirement-model-trace.py", __dir__) +candidates = ENV["PYTHON"] ? [[ENV["PYTHON"]]] : [["py", "-3"], ["python3"], ["python"]] +python = candidates.find do |candidate| + system(*candidate, "--version", out: File::NULL, err: File::NULL) +end + +unless python + warn "usage error: Python 3.10+ is required" + exit 2 +end + +exec(*python, script, *ARGV) diff --git a/scripts/check-concept-foundation-trace.py b/scripts/check-concept-foundation-trace.py new file mode 100755 index 0000000..17d7e91 --- /dev/null +++ b/scripts/check-concept-foundation-trace.py @@ -0,0 +1,414 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import re +import sys +from collections import Counter +from pathlib import Path + +from checker_support import ( + CheckFailure, + metadata, + read_text, + require_supported_python, + section, + table, +) + + +CONCEPT_ID = re.compile(r"\bC-[A-Z0-9-]+\b") +ACTION_ID = re.compile(r"\b(?:CMD|EVT)-[A-Z0-9-]+\b") +MODEL_ID = re.compile(r"\b(?:REL|PERM|CMD|EVT|FLOW|STATE|PM|EX)-[A-Z0-9-]+\b") + + +class TraceError(Exception): + pass + + +def ids(text: str, pattern: re.Pattern[str]) -> set[str]: + return set(pattern.findall(text or "")) + + +def assert_defined(used: set[str], defined: set[str], context: str) -> None: + missing = used - defined + if missing: + raise TraceError(f"undefined IDs in {context}: {', '.join(sorted(missing))}") + + +def assert_unique_ids(values: list[str], context: str, pattern: re.Pattern[str]) -> None: + invalid = sorted(set(value for value in values if not pattern.fullmatch(value))) + if invalid: + raise TraceError(f"invalid IDs in {context}: {', '.join(invalid)}") + duplicates = sorted(value for value, count in Counter(values).items() if count > 1) + if duplicates: + raise TraceError(f"duplicate IDs in {context}: {', '.join(duplicates)}") + + +def assert_confirmed(used: set[str], confirmed: set[str], context: str) -> None: + unconfirmed = used - confirmed + if unconfirmed: + raise TraceError( + f"unconfirmed Concept IDs in {context}: {', '.join(sorted(unconfirmed))}" + ) + + +def reject_placeholders(content: str, path: Path) -> None: + placeholders = ("", "C-EXAMPLE", "C-OTHER", "TBD", "TODO", "待补充") + found = [placeholder for placeholder in placeholders if placeholder in content] + if found: + raise TraceError(f"placeholder content in {path}: {', '.join(found)}") + + +def joined_values(row: dict[str, str]) -> str: + return " ".join(row.values()) + + +def validate(requirement: str, product: str, spec: str) -> str: + status = metadata(requirement, "Concept Foundation Status") + allowed = {"accepted", "concept-foundation-not-needed"} + if status not in allowed: + raise TraceError( + "Concept Foundation must be accepted or reasoned not-needed, got: " + f"{status or 'missing'}" + ) + + if metadata(product, "Concept Foundation Status") != status: + raise TraceError("product Concept Foundation status mismatch") + if metadata(spec, "Concept Foundation Status") != status: + raise TraceError("spec Concept Foundation status mismatch") + + product_source = metadata(product, "Effective Concept Source") + spec_source = metadata(spec, "Effective Concept Source") + if not product_source: + raise TraceError("product Effective Concept Source is missing") + if not spec_source: + raise TraceError("spec Effective Concept Source is missing") + if product_source != spec_source: + raise TraceError("effective Concept Foundation source mismatch") + + if status == "concept-foundation-not-needed": + reason = metadata(requirement, "Not-Needed Reason") + placeholders = {"n/a", "na", "none", "not applicable", "unknown", "tbd", "todo"} + if not reason or len(reason.strip()) < 12 or reason.strip().lower() in placeholders: + raise TraceError( + "concept-foundation-not-needed requires a concrete reason" + ) + return "PASS: reasoned concept-foundation-not-needed trace" + + required_order = ( + "## Concept Foundation", + "## Concept Definitions", + "## Concept Relationships", + "## Role / Permission Matrix", + "## Commands / Events", + "## Primary Business Flow", + "## Product State Model", + "## Requirement Product Model", + "## Exception Paths", + "## Concept-To-Product Traceability", + ) + positions: list[int] = [] + for heading in required_order: + position = requirement.find(heading) + if position < 0: + raise TraceError(f"missing required heading: {heading}") + positions.append(position) + if positions != sorted(positions): + raise TraceError("requirement product sections are out of order") + + candidate_rows = table(requirement, "Concept Candidate Inventory") + candidate_values = [row.get("Concept ID", "") for row in candidate_rows] + assert_unique_ids( + candidate_values, "Concept Candidate Inventory", re.compile(r"C-[A-Z0-9-]+") + ) + candidate_ids = set(candidate_values) + + concept_rows = table(requirement, "Concept Definitions") + concept_values = [row.get("Concept ID", "") for row in concept_rows] + assert_unique_ids(concept_values, "Concept Definitions", re.compile(r"C-[A-Z0-9-]+")) + concept_ids = set(concept_values) + assert_defined(concept_ids, candidate_ids, "Concept Definitions inventory") + canonical_names = { + row.get("Concept ID", ""): row.get("Canonical Name", "") for row in concept_rows + } + if any(not value for value in canonical_names.values()): + raise TraceError("Concept Definitions contains empty Canonical Name") + + confirmed = ids(section(requirement, "Human Confirmation", level=3), CONCEPT_ID) + assert_defined(confirmed, concept_ids, "Human Confirmation") + if not confirmed: + raise TraceError("Human Confirmation must name accepted Concept IDs") + candidate_status = { + row.get("Concept ID", ""): row.get("Status", "") for row in candidate_rows + } + not_accepted = sorted( + concept_id + for concept_id in confirmed + if candidate_status.get(concept_id, "") != "accepted" + ) + if not_accepted: + raise TraceError( + "Human Confirmation includes non-accepted inventory IDs: " + + ", ".join(not_accepted) + ) + + ambiguity_rows = table(requirement, "Blocking Ambiguities", level=3) + if any( + row.get("Status", "").lower() not in {"resolved", "not-applicable"} + for row in ambiguity_rows + ): + raise TraceError("Blocking Ambiguities contains unresolved rows") + + relationship_rows = table(requirement, "Concept Relationships") + relationship_values = [row.get("Relationship ID", "") for row in relationship_rows] + assert_unique_ids( + relationship_values, "Concept Relationships", re.compile(r"REL-[A-Z0-9-]+") + ) + relationship_ids = set(relationship_values) + relationship_concepts = { + value + for row in relationship_rows + for value in (row.get("From Concept ID", ""), row.get("To Concept ID", "")) + } + assert_defined(relationship_concepts, concept_ids, "Concept Relationships") + assert_confirmed(relationship_concepts, confirmed, "Concept Relationships") + + role_rows = table(requirement, "Role / Permission Matrix") + permission_values = [row.get("Permission Rule ID", "") for row in role_rows] + assert_unique_ids( + permission_values, "Role / Permission Matrix", re.compile(r"PERM-[A-Z0-9-]+") + ) + permission_ids = set(permission_values) + role_concepts = { + value + for row in role_rows + for value in ( + row.get("Role Concept ID", ""), + row.get("Product Object Concept ID", ""), + ) + } + assert_defined(role_concepts, concept_ids, "Role / Permission Matrix") + assert_confirmed(role_concepts, confirmed, "Role / Permission Matrix") + permission_pairs = { + (row.get("Role Concept ID", ""), row.get("Product Object Concept ID", "")) + for row in role_rows + } + + action_rows = table(requirement, "Commands / Events") + action_values = [row.get("Action ID", "") for row in action_rows] + assert_unique_ids( + action_values, "Commands / Events", re.compile(r"(?:CMD|EVT)-[A-Z0-9-]+") + ) + action_ids = set(action_values) + action_concepts = { + concept + for row in action_rows + for field in ("Actor / Producer Concept ID", "Target Concept ID") + for concept in ids(row.get(field, ""), CONCEPT_ID) + } + assert_defined(action_concepts, concept_ids, "Commands / Events") + assert_confirmed(action_concepts, confirmed, "Commands / Events") + action_pairs = { + ( + row.get("Actor / Producer Concept ID", ""), + row.get("Target Concept ID", ""), + ) + for row in action_rows + } + missing_permissions = action_pairs - permission_pairs + if missing_permissions: + rendered = sorted(f"{actor}->{target}" for actor, target in missing_permissions) + raise TraceError( + "Commands / Events missing Role / Permission Matrix pairs: " + + ", ".join(rendered) + ) + action_actors = { + row.get("Action ID", ""): row.get("Actor / Producer Concept ID", "") + for row in action_rows + } + + flow_rows = table(requirement, "Primary Business Flow") + flow_values = [row.get("Flow Step ID", "") for row in flow_rows] + assert_unique_ids(flow_values, "Primary Business Flow", re.compile(r"FLOW-[A-Z0-9-]+")) + flow_ids = set(flow_values) + flow_concepts = {concept for row in flow_rows for concept in ids(joined_values(row), CONCEPT_ID)} + flow_actions = {action for row in flow_rows for action in ids(joined_values(row), ACTION_ID)} + assert_defined(flow_concepts, concept_ids, "Primary Business Flow") + assert_confirmed(flow_concepts, confirmed, "Primary Business Flow") + assert_defined(flow_actions, action_ids, "Primary Business Flow actions") + for row in flow_rows: + action_id = row.get("Action ID", "") + if action_actors.get(action_id) != row.get("Actor Concept ID", ""): + raise TraceError(f"Primary Business Flow actor mismatch for {action_id}") + + state_rows = table(requirement, "Product State Model") + state_values = [row.get("State Model ID", "") for row in state_rows] + assert_unique_ids(state_values, "Product State Model", re.compile(r"STATE-[A-Z0-9-]+")) + state_ids = set(state_values) + state_concepts = {concept for row in state_rows for concept in ids(joined_values(row), CONCEPT_ID)} + state_actions = {action for row in state_rows for action in ids(joined_values(row), ACTION_ID)} + assert_defined(state_concepts, concept_ids, "Product State Model") + assert_confirmed(state_concepts, confirmed, "Product State Model") + assert_defined(state_actions, action_ids, "Product State Model actions") + + product_model_rows = table(requirement, "Requirement Product Model") + product_model_values = [row.get("Product Model ID", "") for row in product_model_rows] + assert_unique_ids( + product_model_values, "Requirement Product Model", re.compile(r"PM-[A-Z0-9-]+") + ) + product_model_ids = set(product_model_values) + product_model_concepts = { + concept + for row in product_model_rows + for concept in ids(row.get("Concept IDs", ""), CONCEPT_ID) + } + assert_defined(product_model_concepts, concept_ids, "Requirement Product Model") + assert_confirmed(product_model_concepts, confirmed, "Requirement Product Model") + + exception_rows = table(requirement, "Exception Paths") + exception_values = [row.get("Scenario ID", "") for row in exception_rows] + assert_unique_ids(exception_values, "Exception Paths", re.compile(r"EX-[A-Z0-9-]+")) + exception_ids = set(exception_values) + exception_cells = [row.get("Concept / State / Action IDs", "") for row in exception_rows] + exception_concepts = {concept for value in exception_cells for concept in ids(value, CONCEPT_ID)} + exception_states = { + state + for value in exception_cells + for state in ids(value, re.compile(r"\bSTATE-[A-Z0-9-]+\b")) + } + exception_actions = {action for value in exception_cells for action in ids(value, ACTION_ID)} + assert_defined(exception_concepts, concept_ids, "Exception Paths concepts") + assert_confirmed(exception_concepts, confirmed, "Exception Paths concepts") + assert_defined(exception_states, state_ids, "Exception Paths states") + assert_defined(exception_actions, action_ids, "Exception Paths actions") + + trace_rows = table(requirement, "Concept-To-Product Traceability") + trace_values = [row.get("Trace ID", "") for row in trace_rows] + assert_unique_ids( + trace_values, + "Concept-To-Product Traceability", + re.compile(r"TRACE-[A-Z0-9-]+"), + ) + trace_concepts = { + concept + for row in trace_rows + for concept in ids(row.get("Accepted Concept IDs", ""), CONCEPT_ID) + } + assert_defined(trace_concepts, concept_ids, "Concept-To-Product Traceability") + assert_confirmed(trace_concepts, confirmed, "Concept-To-Product Traceability") + + defined_model_ids = ( + relationship_ids + | permission_ids + | action_ids + | flow_ids + | state_ids + | product_model_ids + | exception_ids + ) + trace_models = { + model + for row in trace_rows + for model in ids(row.get("Derived Model IDs / Sections", ""), MODEL_ID) + } + assert_defined(trace_models, defined_model_ids, "Concept-To-Product Traceability models") + untraced_models = defined_model_ids - trace_models + if untraced_models: + raise TraceError(f"untraced product model IDs: {', '.join(sorted(untraced_models))}") + + if "## Concept Definitions" in product: + raise TraceError("Product Brief must cite rather than redefine Concept Definitions") + if "## Concept Definitions" in spec: + raise TraceError("Feature Spec must cite rather than redefine Concept Definitions") + if "## Requirement Product Model\n" in product: + raise TraceError("Product Brief must not own Requirement Product Model") + if "## Requirement Product Model\n" in spec: + raise TraceError("Feature Spec must not own Requirement Product Model") + + product_refs = table(product, "Accepted Concept References") + product_concepts = { + concept + for row in product_refs + for concept in ids(row.get("Concept ID", ""), CONCEPT_ID) + } + assert_defined(product_concepts, concept_ids, "Product Brief concept references") + assert_confirmed(product_concepts, confirmed, "Product Brief concept references") + for row in product_refs: + concept_id = row.get("Concept ID", "") + if canonical_names.get(concept_id) != row.get("Canonical Name", ""): + raise TraceError(f"Product Brief canonical name mismatch for {concept_id}") + + product_coverage = table(product, "Requirement Product Model Coverage") + product_coverage_ids = { + model + for row in product_coverage + for model in ids(row.get("Requirement Model ID", ""), MODEL_ID) + } + assert_defined(product_coverage_ids, defined_model_ids, "Product Brief model coverage") + assert_defined(product_coverage_ids, trace_models, "Product Brief traced model coverage") + + spec_refs = table(spec, "Accepted Concept References") + spec_concepts = { + concept + for row in spec_refs + for concept in ids(row.get("Concept ID", ""), CONCEPT_ID) + } + assert_defined(spec_concepts, concept_ids, "Feature Spec concept references") + assert_confirmed(spec_concepts, confirmed, "Feature Spec concept references") + for row in spec_refs: + concept_id = row.get("Concept ID", "") + if canonical_names.get(concept_id) != row.get("Canonical Name", ""): + raise TraceError(f"Feature Spec canonical name mismatch for {concept_id}") + + spec_trace = table(spec, "Requirement Product Model Trace") + spec_model_ids = { + model + for row in spec_trace + for field in ("Requirement Model ID", "Concept / Action / Flow / State IDs") + for model in ids(row.get(field, ""), MODEL_ID) + } + spec_trace_concepts = { + concept + for row in spec_trace + for concept in ids( + row.get("Concept / Action / Flow / State IDs", ""), CONCEPT_ID + ) + } + assert_defined(spec_model_ids, defined_model_ids, "Feature Spec model trace") + assert_defined(spec_model_ids, trace_models, "Feature Spec traced model coverage") + assert_defined(spec_trace_concepts, concept_ids, "Feature Spec concept trace") + assert_confirmed(spec_trace_concepts, confirmed, "Feature Spec concept trace") + + return ( + "PASS: accepted Concept Foundation trace is complete " + f"({len(concept_ids)} concepts, {len(defined_model_ids)} model rows)" + ) + + +def main() -> int: + require_supported_python() + parser = argparse.ArgumentParser( + description="Validate Concept Foundation and downstream product-model traceability." + ) + parser.add_argument("requirement", type=Path) + parser.add_argument("product", type=Path) + parser.add_argument("spec", type=Path) + args = parser.parse_args() + paths = (args.requirement, args.product, args.spec) + for path in paths: + if not path.is_file(): + parser.error(f"missing file: {path}") + try: + contents = tuple(read_text(path) for path in paths) + for content, path in zip(contents, paths, strict=True): + reject_placeholders(content, path) + print(validate(*contents)) + except (TraceError, CheckFailure) as error: + print(error, file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check-concept-foundation-trace.rb b/scripts/check-concept-foundation-trace.rb new file mode 100755 index 0000000..bb40573 --- /dev/null +++ b/scripts/check-concept-foundation-trace.rb @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true +# DEPRECATED COMPATIBILITY ENTRY: use check-concept-foundation-trace.py directly. + +script = File.expand_path("check-concept-foundation-trace.py", __dir__) +candidates = ENV["PYTHON"] ? [[ENV["PYTHON"]]] : [["py", "-3"], ["python3"], ["python"]] +python = candidates.find do |candidate| + system(*candidate, "--version", out: File::NULL, err: File::NULL) +end + +unless python + warn "usage error: Python 3.10+ is required" + exit 2 +end + +exec(*python, script, *ARGV) diff --git a/scripts/check-feature-monthly-archive.py b/scripts/check-feature-monthly-archive.py new file mode 100644 index 0000000..9171272 --- /dev/null +++ b/scripts/check-feature-monthly-archive.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +from checker_support import require_supported_python +from feature_archive_support import ( + ArchiveContractError, + archive_plan_from_payload, + validate_archive_plan_state, +) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Check a Feature Monthly Archive plan or result" + ) + parser.add_argument("--project-root", required=True) + parser.add_argument( + "--operation", required=True, choices=("archive", "rehydrate", "restore") + ) + parser.add_argument("--plan", required=True) + return parser + + +def main() -> int: + require_supported_python() + arguments = build_parser().parse_args() + try: + plan_path = Path(arguments.plan) + if not plan_path.is_file(): + raise ArchiveContractError("usage", f"missing plan: {plan_path}", 2) + payload = json.loads(plan_path.read_text(encoding="utf-8-sig")) + if not isinstance(payload, dict): + raise ArchiveContractError("usage", "plan JSON must be an object", 2) + plan = archive_plan_from_payload(payload) + phase = validate_archive_plan_state( + Path(arguments.project_root), plan, arguments.operation + ) + except (json.JSONDecodeError, UnicodeDecodeError) as error: + print(f"usage: invalid plan JSON: {error}", file=sys.stderr) + return 2 + except ArchiveContractError as error: + print(f"{error.category}: {error.detail}", file=sys.stderr) + return error.exit_code + print( + f"PASS: Feature Monthly Archive {arguments.operation} {phase}; " + f"plan_sha256={plan.computed_sha256()}" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check-onboarding-core-flow-coverage.py b/scripts/check-onboarding-core-flow-coverage.py new file mode 100755 index 0000000..7413b31 --- /dev/null +++ b/scripts/check-onboarding-core-flow-coverage.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + +from checker_support import read_text, require_supported_python + + +FLOW_ID = re.compile(r"CF-[A-Z0-9-]+") +SLICE_ID_TEMPLATE = r"{flow}/S\d{{2}}" +DIAGRAM_ID = re.compile(r"D-[A-Z0-9-]+") + + +class CoverageError(Exception): + pass + + +class CoreFlowCoverage: + def __init__(self, root: Path) -> None: + self.root = root.resolve() + + def validate(self) -> tuple[int, int]: + if not self.root.is_dir(): + raise CoverageError(f"onboarding root not found: {self.root}") + + evidence = self.read_required("08-review/evidence-graph.md", "evidence-graph.md") + spec = self.read_required("onboarding-spec.md") + tasks = self.read_required("onboarding-tasks.md") + coverage = self.read_required("coverage-matrix.md") + review = self.read_required("batch-review.md") + core_flow_rows = [ + line + for line in evidence.splitlines() + if line.startswith("|") + and FLOW_ID.search(line) + and re.search(r"\|\s*(critical|important)\s*\|", line) + ] + if not core_flow_rows: + raise CoverageError("no critical/important core flow rows found") + + planned_count = 0 + deferred_count = 0 + for row in core_flow_rows: + flow_match = FLOW_ID.search(row) + if not flow_match: + continue + flow_id = flow_match.group(0) + self.require_token(spec, flow_id, "onboarding-spec.md") + self.require_token(tasks, flow_id, "onboarding-tasks.md") + self.require_token(coverage, flow_id, "coverage-matrix.md") + self.require_token(review, flow_id, "batch-review.md") + + if re.search(r"\|\s*deferred\s*\|", row): + self.validate_deferred(row, flow_id) + deferred_count += 1 + continue + if not re.search(r"\|\s*planned\s*\|", row): + raise CoverageError( + f"core flow selection must be planned or deferred: {flow_id}" + ) + planned_count += 1 + self.validate_planned(flow_id, spec, tasks, coverage, review) + return planned_count, deferred_count + + @staticmethod + def validate_deferred(row: str, flow_id: str) -> None: + for field in ("impact", "missing", "next"): + if not re.search(rf"\b{field}\s*=", row, re.IGNORECASE): + raise CoverageError(f"deferred flow missing {field}: {flow_id}") + + def validate_planned( + self, flow_id: str, spec: str, tasks: str, coverage: str, review: str + ) -> None: + slice_pattern = re.compile(SLICE_ID_TEMPLATE.format(flow=re.escape(flow_id))) + required_slices = sorted(set(slice_pattern.findall(spec))) + if not required_slices: + raise CoverageError(f"no required slices declared: {flow_id}") + for slice_id in required_slices: + self.require_token(tasks, slice_id, "onboarding-tasks.md") + + flow_text = "\n".join(text for text in self.read_flow_docs() if flow_id in text) + if not flow_text: + raise CoverageError(f"missing flow document: {flow_id}") + if re.search(r"<\.\.\.|TBD|TODO|待补充|看代码|see code", flow_text, re.IGNORECASE): + raise CoverageError(f"unresolved placeholder in flow document: {flow_id}") + + slice_rows: list[tuple[str, str]] = [] + flow_lines = flow_text.splitlines() + for slice_id in required_slices: + row = next( + (line for line in flow_lines if line.startswith("|") and slice_id in line), + None, + ) + if row is None: + raise CoverageError(f"missing required slice: {slice_id}") + if not re.search(r"\|\s*covered\s*\|", row): + raise CoverageError(f"slice is not covered: {slice_id}") + if not DIAGRAM_ID.search(row): + raise CoverageError(f"slice missing Diagram ID: {slice_id}") + if not re.search(r"§\d+", row): + raise CoverageError(f"slice missing document section: {slice_id}") + slice_rows.append((slice_id, row)) + + required_diagrams = set(DIAGRAM_ID.findall(spec)) | set(DIAGRAM_ID.findall(tasks)) + for _, row in slice_rows: + required_diagrams.update(DIAGRAM_ID.findall(row)) + for diagram_id in sorted(required_diagrams): + defined = any( + diagram_id in line + and not line.startswith("|") + and (line.startswith("#") or re.search(r"Diagram ID", line, re.IGNORECASE)) + for line in flow_lines + ) + if not defined: + raise CoverageError(f"missing diagram definition: {diagram_id}") + + for slice_id, row in slice_rows: + for section_number in re.findall(r"§(\d+)", row): + heading = re.compile( + rf"^#{{2,6}}\s+{re.escape(section_number)}(?:\.|\s)", + re.MULTILINE, + ) + if not heading.search(flow_text): + raise CoverageError( + f"missing document section: {slice_id} -> §{section_number}" + ) + if not re.search(r"`[^`]+#[^`]+`", row): + raise CoverageError(f"slice missing symbol/config evidence: {slice_id}") + + if not re.search( + r"Call / Data Direction|\|\s*Direction\s*\|", flow_text, re.IGNORECASE + ): + raise CoverageError(f"flow missing call/data direction evidence: {flow_id}") + + self.require_hard_gate_before_score(coverage, "coverage-matrix.md") + self.require_hard_gate_before_score(review, "batch-review.md") + self.require_hard_gate_pass(coverage, flow_id, "coverage-matrix.md") + self.require_hard_gate_pass(review, flow_id, "batch-review.md") + + def read_required(self, *candidates: str) -> str: + for candidate in candidates: + path = self.root / candidate + if path.is_file(): + return read_text(path) + raise CoverageError(f"missing artifact: {candidates[0]}") + + def read_flow_docs(self) -> list[str]: + paths = sorted((self.root / "03-flows").glob("*.md")) + fallback = self.root / "flow.md" + if not paths and fallback.is_file(): + paths = [fallback] + if not paths: + raise CoverageError("missing artifact: 03-flows/*.md") + return [read_text(path) for path in paths] + + @staticmethod + def require_token(text: str, token: str, artifact: str) -> None: + if token not in text: + raise CoverageError(f"missing {token} in {artifact}") + + @staticmethod + def require_hard_gate_pass(text: str, flow_id: str, artifact: str) -> None: + if not any( + line.startswith("|") + and flow_id in line + and re.search(r"\|\s*PASS\s*\|", line) + for line in text.splitlines() + ): + raise CoverageError( + f"Completeness Hard Gate is not PASS for {flow_id} in {artifact}" + ) + + @staticmethod + def require_hard_gate_before_score(text: str, artifact: str) -> None: + gate_index = text.find("Completeness Hard Gate") + if gate_index < 0: + raise CoverageError(f"missing Completeness Hard Gate in {artifact}") + score_index = text.find("## Score") + if score_index >= 0 and gate_index > score_index: + raise CoverageError(f"Completeness Hard Gate must precede score in {artifact}") + + +def main() -> int: + require_supported_python() + parser = argparse.ArgumentParser( + description="Validate onboarding critical/important core-flow coverage." + ) + parser.add_argument("onboarding_root", type=Path) + args = parser.parse_args() + try: + planned, deferred = CoreFlowCoverage(args.onboarding_root).validate() + except CoverageError as error: + print(error, file=sys.stderr) + return 1 + print( + "PASS: core-flow coverage trace is complete " + f"({planned} planned, {deferred} deferred)" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check-onboarding-core-flow-coverage.rb b/scripts/check-onboarding-core-flow-coverage.rb new file mode 100755 index 0000000..597bf9f --- /dev/null +++ b/scripts/check-onboarding-core-flow-coverage.rb @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true +# DEPRECATED COMPATIBILITY ENTRY: use check-onboarding-core-flow-coverage.py directly. + +script = File.expand_path("check-onboarding-core-flow-coverage.py", __dir__) +candidates = ENV["PYTHON"] ? [[ENV["PYTHON"]]] : [["py", "-3"], ["python3"], ["python"]] +python = candidates.find do |candidate| + system(*candidate, "--version", out: File::NULL, err: File::NULL) +end + +unless python + warn "usage error: Python 3.10+ is required" + exit 2 +end + +exec(*python, script, *ARGV) diff --git a/scripts/check-root-agents-blocks.py b/scripts/check-root-agents-blocks.py new file mode 100755 index 0000000..be7fd5c --- /dev/null +++ b/scripts/check-root-agents-blocks.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import re +from dataclasses import dataclass +from pathlib import Path + +from checker_support import ( + CheckFailure, + confined_path, + read_text, + require_supported_python, +) + + +START_RE = re.compile(r"") +END_RE = re.compile(r"") + + +@dataclass +class ManagedBlock: + section: str + source: str | None + version: str | None + block_version: str | None + start_line: int + end_line: int | None = None + + +@dataclass(frozen=True) +class MarkerError: + section: str + status: str + detail: str + + +def attr_value(attrs: str, name: str) -> str | None: + match = re.search(rf"(?:^|\s){re.escape(name)}:([^\s>]+)", attrs) + return match.group(1) if match else None + + +def parse_blocks(path: Path) -> tuple[dict[str, ManagedBlock], list[MarkerError]]: + blocks: dict[str, ManagedBlock] = {} + errors: list[MarkerError] = [] + active: ManagedBlock | None = None + + for line_no, line in enumerate(read_text(path).splitlines(), start=1): + if len(re.findall(r"agent-loop:managed-(?:start|end)", line)) > 1: + errors.append( + MarkerError( + "(unknown)", + "malformed-marker", + f"multiple managed markers on one line at line {line_no}", + ) + ) + continue + + start_match = START_RE.search(line) + if start_match: + attrs = start_match.group(1) + section_name = attr_value(attrs, "section") + if not section_name: + errors.append( + MarkerError( + "(unknown)", + "malformed-marker", + f"start marker at line {line_no} is missing section", + ) + ) + continue + if active: + errors.append( + MarkerError( + active.section, + "broken-markers", + f"section {active.section} starts at line {active.start_line} " + f"but is not closed before line {line_no}", + ) + ) + errors.append( + MarkerError( + active.section, + "nested-managed-block", + f"section {active.section} starts at line {active.start_line} " + f"but a nested section starts at line {line_no}", + ) + ) + if section_name in blocks: + errors.append( + MarkerError( + section_name, + "duplicate-section", + f"section {section_name} appears more than once", + ) + ) + active = ManagedBlock( + section=section_name, + source=attr_value(attrs, "source"), + version=attr_value(attrs, "version"), + block_version=attr_value(attrs, "block-version"), + start_line=line_no, + ) + continue + + if "agent-loop:managed-start" in line: + errors.append( + MarkerError( + "(unknown)", + "malformed-marker", + f"malformed managed-start marker at line {line_no}", + ) + ) + continue + + end_match = END_RE.search(line) + if end_match: + end_section = end_match.group(1) + if not active: + errors.append( + MarkerError( + end_section, + "broken-markers", + f"orphan end marker at line {line_no}", + ) + ) + continue + if active.section != end_section: + errors.append( + MarkerError( + active.section, + "broken-markers", + f"section {active.section} starts at line {active.start_line} " + f"but ends as {end_section} at line {line_no}", + ) + ) + active = None + continue + active.end_line = line_no + blocks[active.section] = active + active = None + continue + + if "agent-loop:managed-end" in line: + errors.append( + MarkerError( + "(unknown)", + "malformed-marker", + f"malformed managed-end marker at line {line_no}", + ) + ) + + if active: + errors.append( + MarkerError( + active.section, + "broken-markers", + f"section {active.section} starts at line {active.start_line} " + "but has no end marker", + ) + ) + return blocks, errors + + +def local_source(source: str | None) -> bool: + return bool( + source + and source != "agent-loop-skill" + and not source.startswith(("http://", "https://")) + and ("/" in source or source.endswith(".md")) + ) + + +def escape_cell(value: object) -> str: + return str(value).replace("|", r"\|") + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Read-only checker for agent-loop root AGENTS managed blocks. " + "Verifies marker structure, required sections, and block versions." + ) + ) + parser.add_argument("--template", required=True, type=Path) + parser.add_argument("--target", required=True, type=Path) + parser.add_argument("--no-source-check", action="store_true") + return parser + + +def main() -> int: + require_supported_python() + parser = build_parser() + args = parser.parse_args() + template: Path = args.template + target: Path = args.target + if not template.is_file(): + parser.error(f"Template not found: {template}") + if not target.is_file(): + parser.error(f"Target not found: {target}") + + template_blocks, template_errors = parse_blocks(template) + target_blocks, target_errors = parse_blocks(target) + findings: list[list[object]] = [] + + for error in template_errors: + findings.append( + [ + error.section, + error.status, + "-", + "-", + error.detail, + "fix template markers", + ] + ) + + for error in target_errors: + expected = template_blocks.get(error.section) + findings.append( + [ + error.section, + error.status, + expected.block_version if expected else "-", + "-", + error.detail, + "repair target managed markers before refresh", + ] + ) + + for section_name, expected in template_blocks.items(): + actual = target_blocks.get(section_name) + if not actual: + findings.append( + [ + section_name, + "missing", + expected.block_version, + "none", + "template section is absent from target AGENTS.md", + "add managed block after human review", + ] + ) + continue + if not actual.block_version: + findings.append( + [ + section_name, + "missing-block-version", + expected.block_version, + "none", + f"expected {expected.block_version}", + "refresh marker metadata after human review", + ] + ) + elif actual.block_version != expected.block_version: + findings.append( + [ + section_name, + "stale-block-version", + expected.block_version, + actual.block_version, + f"expected {expected.block_version}, found {actual.block_version}", + "refresh managed block after human review", + ] + ) + + if args.no_source_check or not local_source(actual.source): + continue + try: + source_path = confined_path(target.parent, str(actual.source)) + except CheckFailure: + findings.append( + [ + section_name, + "source-outside-workspace", + expected.block_version, + actual.block_version, + f"source {actual.source} escapes {target.parent}", + "move source inside the project before human review", + ] + ) + continue + if not source_path.exists(): + findings.append( + [ + section_name, + "source-missing", + expected.block_version, + actual.block_version, + f"source {actual.source} does not exist relative to {target.parent}", + "verify source path or update block source after human review", + ] + ) + + for section_name, actual in target_blocks.items(): + if section_name not in template_blocks: + findings.append( + [ + section_name, + "unexpected-managed-section", + "none", + actual.block_version, + "target section is not present in template", + "ask whether to keep, migrate, or remove", + ] + ) + + if not findings: + print("PASS root AGENTS managed blocks are current") + return 0 + + print("FAIL root AGENTS drift found\n") + print("| Section | Status | Template Block | Target Block | Detail | Action |") + print("|---|---|---|---|---|---|") + for row in findings: + print(f"| {' | '.join(escape_cell(cell) for cell in row)} |") + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check-root-agents-blocks.sh b/scripts/check-root-agents-blocks.sh index fad3ae1..bea567b 100755 --- a/scripts/check-root-agents-blocks.sh +++ b/scripts/check-root-agents-blocks.sh @@ -1,236 +1,20 @@ #!/usr/bin/env bash +# DEPRECATED COMPATIBILITY ENTRY: use check-root-agents-blocks.py directly. set -euo pipefail -ruby - "$@" <<'RUBY' -template = nil -target = nil -check_sources = true - -args = ARGV.dup -until args.empty? - arg = args.shift - case arg - when "--template" - template = args.shift - when "--target" - target = args.shift - when "--no-source-check" - check_sources = false - when "-h", "--help" - puts <<~HELP - Usage: check-root-agents-blocks.sh --template --target [--no-source-check] - - Read-only checker for agent-loop root AGENTS managed blocks. - It verifies marker structure, required section presence, and per-section block-version. - HELP - exit 0 - else - warn "Unknown argument: #{arg}" - exit 2 - end -end - -unless template && target - warn "Usage: check-root-agents-blocks.sh --template --target [--no-source-check]" - exit 2 -end - -unless File.file?(template) - warn "Template not found: #{template}" +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +if [[ -n "${PYTHON:-}" ]]; then + python_cmd=("$PYTHON") +elif command -v py >/dev/null 2>&1; then + python_cmd=(py -3) +elif command -v python3 >/dev/null 2>&1; then + python_cmd=(python3) +elif command -v python >/dev/null 2>&1; then + python_cmd=(python) +else + printf '%s\n' 'usage error: Python 3.10+ is required' >&2 exit 2 -end - -unless File.file?(target) - warn "Target not found: #{target}" - exit 2 -end - -START_RE = // -END_RE = // - -def attr_value(attrs, name) - match = attrs.match(/(?:^|\s)#{Regexp.escape(name)}:([^\s>]+)/) - match && match[1] -end - -def parse_blocks(path) - blocks = {} - errors = [] - active = nil - - File.readlines(path, chomp: true).each_with_index do |line, idx| - line_no = idx + 1 - - if line.scan(/agent-loop:managed-(?:start|end)/).length > 1 - errors << { section: "(unknown)", status: "malformed-marker", detail: "multiple managed markers on one line at line #{line_no}" } - next - end - - if (match = line.match(START_RE)) - attrs = match[1] - section = attr_value(attrs, "section") - unless section - errors << { section: "(unknown)", status: "malformed-marker", detail: "start marker at line #{line_no} is missing section" } - next - end - - if active - errors << { section: active[:section], status: "broken-markers", detail: "section #{active[:section]} starts at line #{active[:start_line]} but is not closed before line #{line_no}" } - errors << { section: active[:section], status: "nested-managed-block", detail: "section #{active[:section]} starts at line #{active[:start_line]} but a nested section starts at line #{line_no}" } - end - - if blocks.key?(section) - errors << { section: section, status: "duplicate-section", detail: "section #{section} appears more than once" } - end - - active = { - section: section, - attrs: attrs, - source: attr_value(attrs, "source"), - version: attr_value(attrs, "version"), - block_version: attr_value(attrs, "block-version"), - start_line: line_no - } - next - end - - if line.include?("agent-loop:managed-start") - errors << { section: "(unknown)", status: "malformed-marker", detail: "malformed managed-start marker at line #{line_no}" } - next - end - - if (match = line.match(END_RE)) - end_section = match[1] - unless active - errors << { section: end_section, status: "broken-markers", detail: "orphan end marker at line #{line_no}" } - next - end - - if active[:section] != end_section - errors << { section: active[:section], status: "broken-markers", detail: "section #{active[:section]} starts at line #{active[:start_line]} but ends as #{end_section} at line #{line_no}" } - active = nil - next - end - - blocks[active[:section]] = active.merge(end_line: line_no) - active = nil - next - end - - if line.include?("agent-loop:managed-end") - errors << { section: "(unknown)", status: "malformed-marker", detail: "malformed managed-end marker at line #{line_no}" } - next - end - end - - if active - errors << { section: active[:section], status: "broken-markers", detail: "section #{active[:section]} starts at line #{active[:start_line]} but has no end marker" } - end - - [blocks, errors] -end - -def local_source?(source) - return false unless source - return false if source == "agent-loop-skill" - return false if source.start_with?("http://", "https://") - source.include?("/") || source.end_with?(".md") -end - -template_blocks, template_errors = parse_blocks(template) -target_blocks, target_errors = parse_blocks(target) - -findings = [] - -template_errors.each do |err| - findings << [err[:section], err[:status], "-", "-", err[:detail], "fix template markers"] -end - -target_errors.each do |err| - tmpl = template_blocks[err[:section]] - findings << [ - err[:section], - err[:status], - tmpl ? tmpl[:block_version].to_s : "-", - "-", - err[:detail], - "repair target managed markers before refresh" - ] -end - -template_blocks.each do |section, tmpl| - target_block = target_blocks[section] - - unless target_block - findings << [ - section, - "missing", - tmpl[:block_version].to_s, - "none", - "template section is absent from target AGENTS.md", - "add managed block after human review" - ] - next - end - - if target_block[:block_version].nil? || target_block[:block_version].empty? - findings << [ - section, - "missing-block-version", - tmpl[:block_version].to_s, - "none", - "expected #{tmpl[:block_version]}", - "refresh marker metadata after human review" - ] - elsif target_block[:block_version] != tmpl[:block_version] - findings << [ - section, - "stale-block-version", - tmpl[:block_version].to_s, - target_block[:block_version].to_s, - "expected #{tmpl[:block_version]}, found #{target_block[:block_version]}", - "refresh managed block after human review" - ] - end - - next unless check_sources && local_source?(target_block[:source]) - - source_path = File.expand_path(target_block[:source], File.dirname(target)) - unless File.exist?(source_path) - findings << [ - section, - "source-missing", - tmpl[:block_version].to_s, - target_block[:block_version].to_s, - "source #{target_block[:source]} does not exist relative to #{File.dirname(target)}", - "verify source path or update block source after human review" - ] - end -end - -target_blocks.each do |section, target_block| - next if template_blocks.key?(section) - findings << [ - section, - "unexpected-managed-section", - "none", - target_block[:block_version].to_s, - "target section is not present in template", - "ask whether to keep, migrate, or remove" - ] -end - -if findings.empty? - puts "PASS root AGENTS managed blocks are current" - exit 0 -end +fi -puts "FAIL root AGENTS drift found" -puts -puts "| Section | Status | Template Block | Target Block | Detail | Action |" -puts "|---|---|---|---|---|---|" -findings.each do |row| - puts "| #{row.map { |cell| cell.to_s.gsub("|", "\\|") }.join(" | ")} |" -end -exit 1 -RUBY +exec "${python_cmd[@]}" "$script_dir/check-root-agents-blocks.py" "$@" diff --git a/scripts/checker_support.py b/scripts/checker_support.py new file mode 100644 index 0000000..4ec3e08 --- /dev/null +++ b/scripts/checker_support.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +import hashlib +import json +import os +import re +import sys +import tempfile +from dataclasses import dataclass +from pathlib import Path + + +@dataclass(frozen=True) +class CheckFailure(Exception): + category: str + detail: str + + def __str__(self) -> str: + return f"{self.category}: {self.detail}" + + +def require_supported_python(version: tuple[int, int] | None = None) -> None: + current = version or sys.version_info[:2] + if current < (3, 10): + print("usage error: Python 3.10+ is required", file=sys.stderr) + raise SystemExit(2) + + +def read_text(path: Path) -> str: + """Read deterministic Markdown text, accepting UTF-8 BOM and CRLF input.""" + with path.open("r", encoding="utf-8-sig", newline=None) as handle: + return handle.read() + + +def strip_code_span(value: str) -> str: + cleaned = value.strip() + return ( + cleaned[1:-1].strip() + if len(cleaned) >= 2 and cleaned[0] == cleaned[-1] == "`" + else cleaned + ) + + +def sha256_bytes(value: bytes) -> str: + return hashlib.sha256(value).hexdigest() + + +def canonical_json_bytes(payload: object) -> bytes: + return json.dumps( + payload, ensure_ascii=False, sort_keys=True, separators=(",", ":") + ).encode("utf-8") + + +def atomic_write_bytes(path: Path, content: bytes) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + handle, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent) + try: + with os.fdopen(handle, "wb") as stream: + stream.write(content) + stream.flush() + os.fsync(stream.fileno()) + os.replace(temporary, path) + except BaseException: + Path(temporary).unlink(missing_ok=True) + raise + + +def metadata(text: str, name: str) -> str | None: + match = re.search(rf"(?mi)^\s*{re.escape(name)}\s*:\s*(.*?)\s*$", text) + return match.group(1).strip() if match else None + + +def optional_section(text: str, heading: str, *, level: int = 2) -> str | None: + marker = "#" * level + pattern = re.compile( + rf"^{re.escape(marker)}\s+{re.escape(heading)}\s*$\n" + rf"(.*?)(?=^#{{1,{level}}}\s+|\Z)", + re.MULTILINE | re.DOTALL, + ) + match = pattern.search(text) + return match.group(1) if match else None + + +def section(text: str, heading: str, *, level: int = 2) -> str: + value = optional_section(text, heading, level=level) + if value is None: + raise CheckFailure("missing section", f"{'#' * level} {heading}") + return value + + +def split_row(line: str) -> list[str]: + value = line.strip() + if not value.startswith("|") or not value.endswith("|"): + raise CheckFailure("invalid-table-row", line) + return [cell.strip() for cell in value[1:-1].split("|")] + + +def table(text: str, heading: str, *, level: int = 2) -> list[dict[str, str]]: + raw_lines = [line.strip() for line in section(text, heading, level=level).splitlines()] + try: + table_start = next(index for index, line in enumerate(raw_lines) if line.startswith("|")) + except StopIteration as error: + raise CheckFailure("missing table in section", heading) from error + lines: list[str] = [] + for line in raw_lines[table_start:]: + if not line.startswith("|"): + break + lines.append(line) + if len(lines) < 3: + raise CheckFailure("missing table in section", heading) + headers = split_row(lines[0]) + rows: list[dict[str, str]] = [] + for line in lines[2:]: + cells = split_row(line) + if len(cells) != len(headers): + raise CheckFailure("column count mismatch in section", heading) + rows.append(dict(zip(headers, cells, strict=True))) + if not rows: + raise CheckFailure("empty table in section", heading) + return rows + + +def confined_path(root: Path, value: str) -> Path: + candidate = Path(value) + resolved = (candidate if candidate.is_absolute() else root / candidate).resolve() + boundary = root.resolve() + try: + resolved.relative_to(boundary) + except ValueError as error: + raise CheckFailure("reference escapes workspace root", value) from error + return resolved diff --git a/scripts/feature_archive_support.py b/scripts/feature_archive_support.py new file mode 100644 index 0000000..28c41bf --- /dev/null +++ b/scripts/feature_archive_support.py @@ -0,0 +1,1850 @@ +from __future__ import annotations + +import json +import os +import re +import secrets +import shutil +import unicodedata +from dataclasses import asdict, dataclass, replace +from datetime import date, datetime, timezone +from pathlib import Path, PurePosixPath +from types import MappingProxyType +from typing import Literal, Mapping, Sequence +from urllib.parse import unquote, urlsplit, urlunsplit + +from checker_support import ( + atomic_write_bytes, + canonical_json_bytes, + metadata, + optional_section, + read_text, + sha256_bytes, + strip_code_span, +) + + +FEATURE_ID_RE = re.compile( + r"^(?P\d{4}-(?:0[1-9]|1[0-2]))-(?P0[1-9]|[12]\d|3[01])-[a-z0-9][a-z0-9-]*$" +) +MONTH_RE = re.compile(r"^\d{4}-(?:0[1-9]|1[0-2])$") +HASH_RE = re.compile(r"^[0-9a-f]{64}$") +ARCHIVE_COLUMNS = ( + "Feature ID", + "Month", + "Current Path", + "Archive State", + "Closed At", + "Delivered Summary", + "Source Requirements", + "Applicable Decisions", + "Last Moved At", +) +ARCHIVE_STATES = frozenset({"archived", "rehydrated"}) +UTF8_BOM = b"\xef\xbb\xbf" +MAX_MARKDOWN_BYTES = 2 * 1024 * 1024 +EXCLUDED_SCAN_DIRS = frozenset( + {".git", ".archive-txn", "node_modules", "vendor", ".venv", "dist", "build"} +) +TRANSACTION_ID_RE = re.compile(r"^\d{8}T\d{6}Z-[0-9a-f]{12}$") +JOURNAL_STATES = frozenset( + { + "prepared", + "moving", + "references-updated", + "checking", + "restoring", + "restored", + "verified", + } +) +ARCHIVE_TEMPLATE = """# Feature Archive + +This file locates archived or rehydrated features. Feature specs, tests, notes, requirement sources, and accepted decisions remain authoritative. + +| Feature ID | Month | Current Path | Archive State | Closed At | Delivered Summary | Source Requirements | Applicable Decisions | Last Moved At | +|---|---|---|---|---|---|---|---|---| +""" + + +@dataclass(frozen=True) +class ArchiveContractError(Exception): + category: str + detail: str + exit_code: int = 1 + + def __str__(self) -> str: + return f"{self.category}: {self.detail}" + + +@dataclass(frozen=True) +class FeatureLocation: + feature_id: str + relative_path: str + layout: Literal["flat", "archived"] + month: str | None + + +@dataclass(frozen=True) +class ArchiveEntry: + feature_id: str + month: str + current_path: str + archive_state: Literal["archived", "rehydrated"] + closed_at: str + delivered_summary: str + source_requirements: str + applicable_decisions: str + last_moved_at: str + + +@dataclass(frozen=True) +class ReferenceEdit: + path: str + kind: Literal["literal-path", "relative-link", "archive-index"] + old: str + new: str + occurrences: int + before_sha256: str + after_sha256: str + + +@dataclass(frozen=True) +class SkippedReference: + path: str + classification: Literal[ + "immutable-requirement-source", "historical-evidence", "unsupported" + ] + matched_value: str + reason: str + + +@dataclass(frozen=True) +class Move: + feature_id: str + month: str + source: str + target: str + + +@dataclass(frozen=True) +class ArchiveCandidate: + feature_id: str + month: str + current_path: str + lifecycle: str + close_evidence: Literal["complete", "incomplete"] + open_follow_up: str + delivered_summary: str + source_requirements: str + applicable_decisions: str + blockers: Sequence[str] + + def __post_init__(self) -> None: + object.__setattr__(self, "blockers", tuple(sorted(self.blockers))) + + +@dataclass(frozen=True) +class ArchivePlan: + schema_version: int + operation: Literal["archive", "rehydrate"] + as_of: str + selected_months: Sequence[str] + selected_feature_ids: Sequence[str] + candidates: Sequence[ArchiveCandidate] + moves: Sequence[Move] + archive_entries: Sequence[ArchiveEntry] + reference_edits: Sequence[ReferenceEdit] + skipped_references: Sequence[SkippedReference] + snapshots: Mapping[str, str] + + def __post_init__(self) -> None: + object.__setattr__(self, "selected_months", tuple(self.selected_months)) + object.__setattr__(self, "selected_feature_ids", tuple(self.selected_feature_ids)) + object.__setattr__(self, "candidates", tuple(self.candidates)) + object.__setattr__(self, "moves", tuple(self.moves)) + object.__setattr__(self, "archive_entries", tuple(self.archive_entries)) + object.__setattr__(self, "reference_edits", tuple(self.reference_edits)) + object.__setattr__(self, "skipped_references", tuple(self.skipped_references)) + object.__setattr__( + self, + "snapshots", + MappingProxyType(dict(sorted(self.snapshots.items()))), + ) + + def to_payload(self, include_hash: bool = True) -> dict[str, object]: + payload: dict[str, object] = { + "schema_version": self.schema_version, + "operation": self.operation, + "as_of": self.as_of, + "selected_months": sorted(set(self.selected_months)), + "selected_feature_ids": sorted(set(self.selected_feature_ids)), + "candidates": [ + asdict(item) + for item in sorted( + self.candidates, key=lambda item: (item.month, item.feature_id) + ) + ], + "moves": [ + asdict(item) + for item in sorted( + self.moves, + key=lambda item: ( + item.month, + item.feature_id, + item.source, + item.target, + ), + ) + ], + "archive_entries": [ + asdict(item) + for item in sorted( + self.archive_entries, + key=lambda item: (item.month, item.feature_id), + ) + ], + "reference_edits": [ + asdict(item) + for item in sorted( + self.reference_edits, + key=lambda item: (item.path, item.kind, item.old, item.new), + ) + ], + "skipped_references": [ + asdict(item) + for item in sorted( + self.skipped_references, + key=lambda item: ( + item.path, + item.classification, + item.matched_value, + ), + ) + ], + "snapshots": dict(sorted(self.snapshots.items())), + } + if include_hash: + payload["plan_sha256"] = self.computed_sha256() + return payload + + def canonical_bytes(self) -> bytes: + return canonical_json_bytes(self.to_payload(include_hash=False)) + + def computed_sha256(self) -> str: + return sha256_bytes(self.canonical_bytes()) + + def assert_hash(self, expected: str) -> None: + if not HASH_RE.fullmatch(expected): + raise ArchiveContractError( + "usage", "expected plan SHA-256 must be 64 lowercase hex characters", 2 + ) + actual = self.computed_sha256() + if actual != expected: + raise ArchiveContractError( + "stale-plan", f"expected {expected}, rebuilt {actual}" + ) + + +def discover_memory_root(project_root: Path) -> Path: + hidden = project_root / ".agent-loop" + legacy = project_root / "agent-loop" + if hidden.is_dir() and legacy.is_dir(): + raise ArchiveContractError( + "memory-root", "both .agent-loop and legacy agent-loop exist" + ) + if hidden.is_dir(): + return hidden.resolve() + if legacy.is_dir(): + return legacy.resolve() + raise ArchiveContractError( + "memory-root", "no agent-loop memory root exists", 2 + ) + + +def _split_row(line: str) -> list[str]: + value = line.strip() + if not value.startswith("|") or not value.endswith("|"): + raise ArchiveContractError("archive-index", f"invalid table row: {line}") + return [cell.strip() for cell in value[1:-1].split("|")] + + +def _feature_month(feature_id: str) -> str: + match = FEATURE_ID_RE.fullmatch(feature_id) + if not match: + raise ArchiveContractError("feature-id", f"invalid Feature ID: {feature_id}") + return match.group("month") + + +def _memory_relative_from_current_path(value: str) -> str: + cleaned = strip_code_span(value).strip().rstrip("/") + if "\\" in cleaned: + raise ArchiveContractError( + "archive-index", f"Current Path must use POSIX separators: {value}" + ) + pure = PurePosixPath(cleaned) + if pure.is_absolute() or ".." in pure.parts: + raise ArchiveContractError("path-escape", f"unsafe Current Path: {value}") + parts = pure.parts + if parts and parts[0] in {".agent-loop", "agent-loop"}: + parts = parts[1:] + relative = PurePosixPath(*parts).as_posix() + if not relative.startswith("features/"): + raise ArchiveContractError( + "archive-index", f"Current Path must locate features/: {value}" + ) + return relative + + +def _validate_entry(entry: ArchiveEntry) -> str: + feature_month = _feature_month(entry.feature_id) + if not MONTH_RE.fullmatch(entry.month) or entry.month != feature_month: + raise ArchiveContractError( + "month", + f"Feature ID {entry.feature_id} requires month {feature_month}, got {entry.month}", + ) + if entry.archive_state not in ARCHIVE_STATES: + raise ArchiveContractError( + "archive-index", f"unknown Archive State: {entry.archive_state}" + ) + relative = _memory_relative_from_current_path(entry.current_path) + archived = f"features/{entry.month}/{entry.feature_id}" + flat = f"features/{entry.feature_id}" + if entry.archive_state == "archived" and relative != archived: + path_parts = PurePosixPath(relative).parts + if len(path_parts) >= 3 and MONTH_RE.fullmatch(path_parts[1]): + raise ArchiveContractError( + "month", f"archived path month does not match {entry.feature_id}: {relative}" + ) + raise ArchiveContractError( + "archive-index", f"archived row must point to {archived}: {relative}" + ) + if entry.archive_state == "rehydrated" and relative != flat: + raise ArchiveContractError( + "archive-index", f"rehydrated row must point to {flat}: {relative}" + ) + return relative + + +def parse_archive_index(memory_root: Path) -> Sequence[ArchiveEntry]: + index = memory_root / "features" / "archive.md" + if not index.exists(): + return () + if not index.is_file(): + raise ArchiveContractError("archive-index", f"not a file: {index}") + lines = read_text(index).splitlines() + header_at = None + for position, line in enumerate(lines): + if line.strip().startswith("|") and tuple(_split_row(line)) == ARCHIVE_COLUMNS: + header_at = position + break + if header_at is None or header_at + 1 >= len(lines): + raise ArchiveContractError("archive-index", "missing accepted archive table header") + separator = _split_row(lines[header_at + 1]) + if len(separator) != len(ARCHIVE_COLUMNS) or not all( + re.fullmatch(r":?-{3,}:?", cell) for cell in separator + ): + raise ArchiveContractError("archive-index", "invalid archive table separator") + + entries: list[ArchiveEntry] = [] + for line in lines[header_at + 2 :]: + if not line.strip(): + continue + if not line.strip().startswith("|"): + break + cells = _split_row(line) + if len(cells) != len(ARCHIVE_COLUMNS): + raise ArchiveContractError("archive-index", "archive row column mismatch") + row = dict(zip(ARCHIVE_COLUMNS, cells, strict=True)) + entry = ArchiveEntry( + feature_id=strip_code_span(row["Feature ID"]), + month=strip_code_span(row["Month"]), + current_path=strip_code_span(row["Current Path"]), + archive_state=strip_code_span(row["Archive State"]), + closed_at=strip_code_span(row["Closed At"]), + delivered_summary=row["Delivered Summary"].strip(), + source_requirements=strip_code_span(row["Source Requirements"]), + applicable_decisions=strip_code_span(row["Applicable Decisions"]), + last_moved_at=strip_code_span(row["Last Moved At"]), + ) + _validate_entry(entry) + entries.append(entry) + + feature_ids: set[str] = set() + normalized_paths: set[str] = set() + for entry in entries: + if entry.feature_id in feature_ids: + raise ArchiveContractError( + "archive-index", f"duplicate Feature ID: {entry.feature_id}" + ) + feature_ids.add(entry.feature_id) + relative = _memory_relative_from_current_path(entry.current_path) + normalized = unicodedata.normalize("NFKC", relative).casefold() + if normalized in normalized_paths: + raise ArchiveContractError( + "archive-index", f"duplicate normalized Current Path: {relative}" + ) + normalized_paths.add(normalized) + return tuple(sorted(entries, key=lambda item: (item.month, item.feature_id))) + + +def _one_line(value: str) -> str: + return " ".join(value.replace("|", "|").split()) + + +def _locator(value: str) -> str: + cleaned = _one_line(value) + return cleaned if cleaned.lower() == "none" else f"`{cleaned}`" + + +def render_archive_index(entries: Sequence[ArchiveEntry]) -> str: + rows: list[str] = [] + seen: list[ArchiveEntry] = [] + for entry in sorted(entries, key=lambda item: (item.month, item.feature_id)): + _validate_entry(entry) + seen.append(entry) + rows.append( + "| " + + " | ".join( + ( + entry.feature_id, + entry.month, + f"`{_one_line(entry.current_path)}`", + entry.archive_state, + _one_line(entry.closed_at), + _one_line(entry.delivered_summary), + _locator(entry.source_requirements), + _locator(entry.applicable_decisions), + _one_line(entry.last_moved_at), + ) + ) + + " |" + ) + if len({item.feature_id for item in seen}) != len(seen): + raise ArchiveContractError("archive-index", "duplicate Feature ID while rendering") + return ARCHIVE_TEMPLATE + (("\n".join(rows) + "\n") if rows else "") + + +def _project_root_for(memory_root: Path) -> Path: + return ( + memory_root.parent + if memory_root.name in {".agent-loop", "agent-loop"} + else memory_root + ) + + +def _confined_path(root: Path, relative: str) -> Path: + if not relative or "\\" in relative: + raise ArchiveContractError("path-escape", f"unsafe workspace path: {relative}") + pure = PurePosixPath(relative) + if pure.is_absolute() or ".." in pure.parts: + raise ArchiveContractError("path-escape", f"unsafe workspace path: {relative}") + boundary = root.resolve() + candidate = root / pure + try: + candidate.resolve().relative_to(boundary) + except ValueError as error: + raise ArchiveContractError("path-escape", f"unsafe workspace path: {relative}") from error + return candidate + + +def _confined_existing_directory(memory_root: Path, relative: str) -> Path: + candidate = memory_root / PurePosixPath(relative) + boundary = _project_root_for(memory_root).resolve() + resolved = candidate.resolve() + try: + resolved.relative_to(boundary) + except ValueError as error: + raise ArchiveContractError("path-escape", relative) from error + if not candidate.is_dir(): + raise ArchiveContractError("archive-index", f"missing directory: {relative}") + return candidate + + +def resolve_feature_location(memory_root: Path, feature_id: str) -> FeatureLocation: + expected_month = _feature_month(feature_id) + features_root = memory_root / "features" + flat = features_root / feature_id + month_locations = tuple( + child / feature_id + for child in sorted(features_root.iterdir() if features_root.is_dir() else ()) + if child.is_dir() and MONTH_RE.fullmatch(child.name) and (child / feature_id).exists() + ) + entries = parse_archive_index(memory_root) + matching = tuple(entry for entry in entries if entry.feature_id == feature_id) + + if flat.exists(): + conflicting_rows = tuple( + entry + for entry in matching + if entry.archive_state != "rehydrated" + or _memory_relative_from_current_path(entry.current_path) + != f"features/{feature_id}" + ) + if month_locations or conflicting_rows: + raise ArchiveContractError( + "path-collision", f"flat and archived locations exist for {feature_id}" + ) + _confined_existing_directory(memory_root, f"features/{feature_id}") + return FeatureLocation(feature_id, f"features/{feature_id}", "flat", None) + + if len(month_locations) > 1: + raise ArchiveContractError( + "path-collision", f"multiple archived locations exist for {feature_id}" + ) + if month_locations and month_locations[0].parent.name != expected_month: + raise ArchiveContractError( + "month", + f"Feature ID {feature_id} is under {month_locations[0].parent.name}", + ) + if len(matching) != 1: + raise ArchiveContractError( + "archive-index", f"expected one locator row for {feature_id}, got {len(matching)}" + ) + entry = matching[0] + relative = _validate_entry(entry) + if entry.archive_state != "archived": + raise ArchiveContractError( + "archive-index", f"rehydrated feature is missing flat path: {feature_id}" + ) + _confined_existing_directory(memory_root, relative) + if not month_locations: + raise ArchiveContractError( + "archive-index", f"locator row target is not discoverable: {relative}" + ) + expected_relative = f"features/{expected_month}/{feature_id}" + if relative != expected_relative: + raise ArchiveContractError( + "archive-index", f"locator path mismatch: {relative}" + ) + return FeatureLocation(feature_id, relative, "archived", expected_month) + + +def _read_optional(path: Path) -> str: + return read_text(path) if path.is_file() else "" + + +def _read_utf8_preserving(path: Path) -> tuple[str, bool]: + content = path.read_bytes() + has_bom = content.startswith(UTF8_BOM) + payload = content[len(UTF8_BOM) :] if has_bom else content + return payload.decode("utf-8"), has_bom + + +def _encode_utf8_preserving(content: str, has_bom: bool) -> bytes: + encoded = content.encode("utf-8") + return UTF8_BOM + encoded if has_bom else encoded + + +def _concrete_summary(value: str) -> bool: + cleaned = strip_code_span(value).strip() + if not cleaned or re.fullmatch( + r"(?:none|n/a|na|tbd|todo|unknown|-)", cleaned, re.IGNORECASE + ): + return False + return not re.search(r"<[^>]+>", cleaned) + + +def _readiness_values(notes: str) -> dict[str, str]: + body = optional_section(notes, "Archive Readiness") + if body is None: + return {} + names = ( + "Closed At", + "Delivered Summary", + "Verification", + "Feature Close Review", + "Drift", + "Project Memory Impact", + "Open Follow-up", + ) + return {name: (metadata(body, name) or "").strip() for name in names} + + +def inspect_feature( + memory_root: Path, feature_id: str, as_of: date +) -> ArchiveCandidate: + month = _feature_month(feature_id) + location = resolve_feature_location(memory_root, feature_id) + root = memory_root / PurePosixPath(location.relative_path) + spec = _read_optional(root / "spec.md") + tasks = _read_optional(root / "tasks.md") + notes = _read_optional(root / "notes.md") + project = _read_optional(memory_root / "project.md") + lifecycle = (metadata(spec, "Status") or "missing").strip() + blockers: list[str] = [] + if lifecycle != "closed": + blockers.append(f"lifecycle:{lifecycle}") + if month == as_of.strftime("%Y-%m"): + blockers.append("current-month") + + close_record = optional_section(notes, "Close Record") + close_at = metadata(close_record or "", "Closed At") or "" + task_statuses = re.findall(r"(?mi)^\s*-?\s*Status\s*:\s*([^\n]+)", tasks) + task_complete = bool(task_statuses) and all( + status.strip() in {"done", "skipped"} for status in task_statuses + ) + readiness = _readiness_values(notes) + if not readiness: + blockers.append("missing-archive-readiness") + delivered_summary = readiness.get("Delivered Summary", "") + if readiness and not _concrete_summary(delivered_summary): + blockers.append("non-concrete-delivered-summary") + required_values = { + "Verification": {"complete"}, + "Feature Close Review": {"complete"}, + "Drift": {"resolved"}, + "Project Memory Impact": {"complete", "none"}, + } + for name, allowed in required_values.items(): + actual = readiness.get(name, "") + if readiness and actual not in allowed: + blockers.append( + f"archive-readiness-{name.lower().replace(' ', '-')}:{actual or 'missing'}" + ) + readiness_closed_at = readiness.get("Closed At", "") + if readiness and ( + not re.fullmatch(r"\d{4}-\d{2}-\d{2}", readiness_closed_at) + or readiness_closed_at != close_at + ): + blockers.append("archive-readiness-closed-at") + open_follow_up = readiness.get("Open Follow-up", "") or "unknown" + if readiness and open_follow_up != "none": + blockers.append(f"open-follow-up:{open_follow_up}") + + close_complete = bool( + close_record + and re.fullmatch(r"\d{4}-\d{2}-\d{2}", close_at) + and task_complete + and readiness + and not any( + blocker.startswith("archive-readiness") + or blocker in { + "missing-archive-readiness", + "non-concrete-delivered-summary", + } + for blocker in blockers + ) + ) + if not close_complete: + blockers.append("incomplete-close-evidence") + + active = strip_code_span(metadata(project, "Active Feature") or "") + paused = strip_code_span(metadata(project, "Paused Features") or "") + if active and active.lower() != "none" and feature_id in active: + blockers.append("project-memory-active") + if paused and paused.lower() != "none" and feature_id in paused: + blockers.append("project-memory-paused") + + return ArchiveCandidate( + feature_id=feature_id, + month=month, + current_path=location.relative_path, + lifecycle=lifecycle, + close_evidence="complete" if close_complete else "incomplete", + open_follow_up=open_follow_up, + delivered_summary=delivered_summary, + source_requirements=strip_code_span( + metadata(spec, "Source Requirements") or "none" + ), + applicable_decisions=strip_code_span( + metadata(spec, "Applicable Decisions") or "none" + ), + blockers=tuple(sorted(set(blockers))), + ) + + +def discover_flat_features(memory_root: Path) -> Sequence[FeatureLocation]: + root = memory_root / "features" + if not root.is_dir(): + return () + locations: list[FeatureLocation] = [] + for child in sorted(root.iterdir(), key=lambda item: item.name): + if child.is_symlink(): + raise ArchiveContractError("path-escape", f"symlinked feature path: {child}") + if child.is_dir() and FEATURE_ID_RE.fullmatch(child.name): + locations.append( + FeatureLocation(child.name, f"features/{child.name}", "flat", None) + ) + return tuple(locations) + + +def _markdown_files(project_root: Path) -> Sequence[Path]: + files: list[Path] = [] + boundary = project_root.resolve() + for current, directories, names in os.walk(project_root, followlinks=False): + current_path = Path(current) + kept: list[str] = [] + for name in sorted(directories): + candidate = current_path / name + if name in EXCLUDED_SCAN_DIRS: + continue + if candidate.is_symlink(): + raise ArchiveContractError( + "path-escape", + f"symlinked directory cannot be reference-scanned: {candidate.relative_to(project_root).as_posix()}", + ) + kept.append(name) + directories[:] = kept + for name in sorted(names): + if not name.lower().endswith(".md"): + continue + candidate = current_path / name + if candidate.is_symlink(): + raise ArchiveContractError( + "path-escape", + f"symlinked Markdown cannot be reference-scanned: {candidate.relative_to(project_root).as_posix()}", + ) + try: + candidate.resolve().relative_to(boundary) + except ValueError as error: + raise ArchiveContractError( + "path-escape", candidate.relative_to(project_root).as_posix() + ) from error + files.append(candidate) + return tuple(sorted(files)) + + +def _preserved_reference_class(relative: str) -> str | None: + parts = PurePosixPath(relative).parts + if "requirements" in parts: + requirement_at = parts.index("requirements") + tail = parts[requirement_at + 1 :] + if tail and not ( + len(tail) == 1 and tail[0] == "INDEX.md" + ) and PurePosixPath(relative).name != "README.md": + return "immutable-requirement-source" + if len(parts) >= 2 and parts[0] == "docs" and parts[1] in { + "proposal", + "reports", + }: + return "historical-evidence" + return None + + +LINK_TARGET_RE = re.compile( + r"!?\[[^\]]*\]\(\s*(?P<[^>]+>|[^\s)]+)|" + r"^\s*\[[^\]]+\]:\s*(?P<[^>]+>|\S+)", + re.MULTILINE, +) + + +def _relative_link_replacements( + project_root: Path, file: Path, move: Move, content: str +) -> tuple[list[tuple[str, str]], list[SkippedReference]]: + source_root = project_root / PurePosixPath(move.source) + target_root = project_root / PurePosixPath(move.target) + try: + within = file.relative_to(source_root) + file_moves = True + except ValueError: + within = None + file_moves = False + target_file = target_root / within if within is not None else file + replacements: list[tuple[str, str]] = [] + skipped: list[SkippedReference] = [] + for match in LINK_TARGET_RE.finditer(content): + raw = match.group("inline") or match.group("definition") or "" + wrapped = raw.startswith("<") and raw.endswith(">") + target = raw[1:-1] if wrapped else raw + parsed = urlsplit(target) + if parsed.scheme in {"http", "https", "mailto"} or parsed.netloc: + continue + if not parsed.path or (not parsed.path and parsed.fragment): + continue + decoded = unquote(parsed.path) + resolved = (file.parent / decoded).resolve() + try: + resolved.relative_to(project_root.resolve()) + except ValueError: + skipped.append( + SkippedReference( + file.relative_to(project_root).as_posix(), + "unsupported", + target, + "relative Markdown link escapes project root", + ) + ) + continue + try: + target_within_source = resolved.relative_to(source_root.resolve()) + target_is_in_source = True + except ValueError: + target_within_source = None + target_is_in_source = False + if not resolved.exists() and (file_moves or target_is_in_source): + skipped.append( + SkippedReference( + file.relative_to(project_root).as_posix(), + "unsupported", + target, + "broken cross-boundary Markdown link target does not exist", + ) + ) + continue + if not resolved.exists(): + continue + if file_moves and target_is_in_source: + continue + if not file_moves and not target_is_in_source: + continue + destination = ( + target_root / target_within_source + if target_is_in_source and target_within_source is not None + else resolved + ) + relative_target = os.path.relpath(destination, target_file.parent).replace( + os.sep, "/" + ) + rebuilt = urlunsplit( + ("", "", relative_target, parsed.query, parsed.fragment) + ) + if wrapped: + rebuilt = f"<{rebuilt}>" + if rebuilt != raw: + replacements.append((raw, rebuilt)) + return replacements, skipped + + +def _discover_reference_impact( + project_root: Path, moves: Sequence[Move] +) -> tuple[Sequence[ReferenceEdit], Sequence[SkippedReference]]: + planned: dict[str, list[tuple[str, str, str]]] = {} + skipped: list[SkippedReference] = [] + for path in _markdown_files(project_root): + relative = path.relative_to(project_root).as_posix() + if relative in { + ".agent-loop/features/archive.md", + "agent-loop/features/archive.md", + }: + continue + if path.stat().st_size > MAX_MARKDOWN_BYTES: + skipped.append( + SkippedReference( + relative, + "unsupported", + "file-size", + "Markdown file exceeds 2 MiB reference-scan limit", + ) + ) + continue + try: + content, _ = _read_utf8_preserving(path) + except UnicodeDecodeError: + skipped.append( + SkippedReference( + relative, + "unsupported", + "utf-8", + "Markdown file is not valid UTF-8", + ) + ) + continue + preserved = _preserved_reference_class(relative) + for move in moves: + old_prefix = move.source.rstrip("/") + "/" + new_prefix = move.target.rstrip("/") + "/" + if move.source not in content: + link_replacements, link_skipped = _relative_link_replacements( + project_root, path, move, content + ) + skipped.extend(link_skipped) + for old, new in link_replacements: + planned.setdefault(relative, []).append( + ("relative-link", old, new) + ) + continue + if preserved: + skipped.append( + SkippedReference( + relative, + preserved, + move.source, + "preserved source or historical evidence retains the original path", + ) + ) + continue + if old_prefix in content: + planned.setdefault(relative, []).append( + ("literal-path", old_prefix, new_prefix) + ) + remaining = content.replace(old_prefix, "") + if move.source in remaining: + skipped.append( + SkippedReference( + relative, + "unsupported", + move.source, + "old feature path appears in an unsupported or ambiguous form", + ) + ) + link_replacements, link_skipped = _relative_link_replacements( + project_root, path, move, content + ) + skipped.extend(link_skipped) + for old, new in link_replacements: + planned.setdefault(relative, []).append(("relative-link", old, new)) + + edits: list[ReferenceEdit] = [] + for relative, replacements in sorted(planned.items()): + path = project_root / PurePosixPath(relative) + current, has_bom = _read_utf8_preserving(path) + for kind, old, new in sorted(set(replacements)): + occurrences = current.count(old) + if not occurrences: + continue + before = _encode_utf8_preserving(current, has_bom) + current = current.replace(old, new) + after = _encode_utf8_preserving(current, has_bom) + edits.append( + ReferenceEdit( + path=relative, + kind=kind, + old=old, + new=new, + occurrences=occurrences, + before_sha256=sha256_bytes(before), + after_sha256=sha256_bytes(after), + ) + ) + unique_skipped = { + (item.path, item.classification, item.matched_value, item.reason): item + for item in skipped + } + return ( + tuple(sorted(edits, key=lambda item: (item.path, item.kind, item.old, item.new))), + tuple(unique_skipped[key] for key in sorted(unique_skipped)), + ) + + +def discover_reference_impacts( + project_root: Path, moves: Sequence[Move] +) -> Sequence[ReferenceEdit]: + edits, _ = _discover_reference_impact(project_root, moves) + return edits + + +def _project_relative_prefix(project_root: Path, memory_root: Path) -> str: + try: + return memory_root.relative_to(project_root.resolve()).as_posix() + except ValueError as error: + raise ArchiveContractError( + "path-escape", "memory root is outside project root" + ) from error + + +def _candidate_closed_at(memory_root: Path, candidate: ArchiveCandidate) -> str: + notes = _read_optional( + memory_root / PurePosixPath(candidate.current_path) / "notes.md" + ) + return _readiness_values(notes).get("Closed At", "") + + +def _snapshot_plan_inputs( + project_root: Path, + memory_root: Path, + candidates: Sequence[ArchiveCandidate], + reference_edits: Sequence[ReferenceEdit], + skipped_references: Sequence[SkippedReference], +) -> Mapping[str, str]: + paths: set[Path] = set() + for candidate in candidates: + root = memory_root / PurePosixPath(candidate.current_path) + if root.is_dir(): + for path in root.rglob("*"): + if path.is_symlink(): + raise ArchiveContractError( + "path-escape", + path.relative_to(project_root).as_posix(), + ) + if path.is_file(): + paths.add(path) + for relative in ("project.md", "features/archive.md"): + path = memory_root / PurePosixPath(relative) + if path.is_file(): + paths.add(path) + for item in reference_edits: + paths.add(project_root / PurePosixPath(item.path)) + for item in skipped_references: + path = project_root / PurePosixPath(item.path) + if path.is_file() and path.stat().st_size <= MAX_MARKDOWN_BYTES: + paths.add(path) + return MappingProxyType( + { + path.relative_to(project_root).as_posix(): sha256_bytes(path.read_bytes()) + for path in sorted(paths) + } + ) + + +def build_archive_plan( + project_root: Path, + *, + operation: Literal["archive", "rehydrate"], + selected_months: Sequence[str], + selected_feature_ids: Sequence[str], + as_of: date, +) -> ArchivePlan: + project_root = project_root.resolve() + memory_root = discover_memory_root(project_root) + _assert_no_stranded_transactions(memory_root) + months = tuple(sorted(set(selected_months))) + feature_ids = tuple(sorted(set(selected_feature_ids))) + if operation not in {"archive", "rehydrate"}: + raise ArchiveContractError("usage", f"unsupported operation: {operation}", 2) + if any(not MONTH_RE.fullmatch(month) for month in months): + raise ArchiveContractError("usage", "month must be YYYY-MM", 2) + if operation == "archive" and (not months or feature_ids): + raise ArchiveContractError( + "usage", "archive requires --month and forbids --feature-id", 2 + ) + if operation == "rehydrate" and (not feature_ids or months): + raise ArchiveContractError( + "usage", "rehydrate requires --feature-id and forbids --month", 2 + ) + + existing_entries = tuple(parse_archive_index(memory_root)) + candidates: list[ArchiveCandidate] = [] + moves: list[Move] = [] + memory_prefix = _project_relative_prefix(project_root, memory_root) + + if operation == "archive": + for location in discover_flat_features(memory_root): + month = _feature_month(location.feature_id) + if month not in months: + continue + resolve_feature_location(memory_root, location.feature_id) + candidate = inspect_feature(memory_root, location.feature_id, as_of) + candidates.append(candidate) + if not candidate.blockers: + moves.append( + Move( + candidate.feature_id, + candidate.month, + f"{memory_prefix}/features/{candidate.feature_id}", + f"{memory_prefix}/features/{candidate.month}/{candidate.feature_id}", + ) + ) + known = {candidate.feature_id for candidate in candidates} + for entry in existing_entries: + if ( + entry.month in months + and entry.archive_state == "archived" + and entry.feature_id not in known + ): + candidate = inspect_feature(memory_root, entry.feature_id, as_of) + candidates.append( + replace( + candidate, + blockers=tuple(candidate.blockers) + ("already-archived",), + ) + ) + else: + for feature_id in feature_ids: + location = resolve_feature_location(memory_root, feature_id) + candidate = inspect_feature(memory_root, feature_id, as_of) + if location.layout != "archived": + candidate = replace( + candidate, + blockers=tuple(candidate.blockers) + ("already-rehydrated",), + ) + candidates.append(candidate) + if not candidate.blockers and location.layout == "archived": + moves.append( + Move( + feature_id, + _feature_month(feature_id), + f"{memory_prefix}/{location.relative_path}", + f"{memory_prefix}/features/{feature_id}", + ) + ) + + entries_by_id = {entry.feature_id: entry for entry in existing_entries} + candidates_by_id = {candidate.feature_id: candidate for candidate in candidates} + for move in moves: + candidate = candidates_by_id[move.feature_id] + entries_by_id[move.feature_id] = ArchiveEntry( + feature_id=move.feature_id, + month=move.month, + current_path=move.target.rstrip("/") + "/", + archive_state="archived" if operation == "archive" else "rehydrated", + closed_at=_candidate_closed_at(memory_root, candidate), + delivered_summary=candidate.delivered_summary, + source_requirements=candidate.source_requirements, + applicable_decisions=candidate.applicable_decisions, + last_moved_at=as_of.isoformat(), + ) + + reference_edits, skipped_references = _discover_reference_impact( + project_root, moves + ) + snapshots = _snapshot_plan_inputs( + project_root, + memory_root, + candidates, + reference_edits, + skipped_references, + ) + return ArchivePlan( + schema_version=1, + operation=operation, + as_of=as_of.isoformat(), + selected_months=months, + selected_feature_ids=feature_ids, + candidates=tuple(candidates), + moves=tuple(moves), + archive_entries=tuple(entries_by_id.values()), + reference_edits=reference_edits, + skipped_references=skipped_references, + snapshots=snapshots, + ) + + +def archive_plan_from_payload(payload: Mapping[str, object]) -> ArchivePlan: + expected_keys = { + "schema_version", + "operation", + "as_of", + "selected_months", + "selected_feature_ids", + "candidates", + "moves", + "archive_entries", + "reference_edits", + "skipped_references", + "snapshots", + "plan_sha256", + } + if set(payload) != expected_keys: + raise ArchiveContractError("usage", "archive plan fields do not match schema", 2) + try: + plan = ArchivePlan( + schema_version=int(payload["schema_version"]), + operation=str(payload["operation"]), + as_of=str(payload["as_of"]), + selected_months=tuple(payload["selected_months"]), + selected_feature_ids=tuple(payload["selected_feature_ids"]), + candidates=tuple( + ArchiveCandidate(**item) for item in payload["candidates"] + ), + moves=tuple(Move(**item) for item in payload["moves"]), + archive_entries=tuple( + ArchiveEntry(**item) for item in payload["archive_entries"] + ), + reference_edits=tuple( + ReferenceEdit(**item) for item in payload["reference_edits"] + ), + skipped_references=tuple( + SkippedReference(**item) for item in payload["skipped_references"] + ), + snapshots=dict(payload["snapshots"]), + ) + except (KeyError, TypeError, ValueError) as error: + raise ArchiveContractError("usage", f"invalid archive plan: {error}", 2) from error + if plan.schema_version != 1: + raise ArchiveContractError("usage", "unsupported archive plan schema", 2) + plan.assert_hash(str(payload["plan_sha256"])) + return plan + + +def _hash_at(path: Path) -> str: + if not path.is_file(): + raise ArchiveContractError("post-check", f"missing file: {path}") + return sha256_bytes(path.read_bytes()) + + +def validate_archive_plan_state( + project_root: Path, plan: ArchivePlan, operation: str +) -> str: + project_root = project_root.resolve() + if operation not in {plan.operation, "restore"}: + raise ArchiveContractError( + "usage", f"plan operation is {plan.operation}, requested {operation}", 2 + ) + unsupported = [ + item for item in plan.skipped_references if item.classification == "unsupported" + ] + if unsupported: + raise ArchiveContractError( + "reference-impact", + f"unsupported references remain: {', '.join(item.path for item in unsupported)}", + ) + + sources = [_confined_path(project_root, move.source) for move in plan.moves] + targets = [_confined_path(project_root, move.target) for move in plan.moves] + pre = not plan.moves or ( + all(path.is_dir() for path in sources) + and all(not path.exists() for path in targets) + ) + post = bool(plan.moves) and all(not path.exists() for path in sources) and all( + path.is_dir() for path in targets + ) + if not pre and not post: + raise ArchiveContractError( + "post-check", "source/target paths are in a mixed or unexpected state" + ) + + if pre: + try: + rebuilt = build_archive_plan( + project_root, + operation=plan.operation, + selected_months=plan.selected_months, + selected_feature_ids=plan.selected_feature_ids, + as_of=date.fromisoformat(plan.as_of), + ) + except ValueError as error: + raise ArchiveContractError("usage", "plan as_of must be YYYY-MM-DD", 2) from error + if rebuilt.computed_sha256() != plan.computed_sha256(): + raise ArchiveContractError( + "stale-plan", + f"stored {plan.computed_sha256()}, rebuilt {rebuilt.computed_sha256()}", + ) + return "restore-check" if operation == "restore" else "pre-check" + + if operation == "restore": + raise ArchiveContractError( + "restore", "project does not match the plan's pre-transaction state" + ) + + final_hashes = { + item.path: item.after_sha256 for item in plan.reference_edits + } + memory_root = discover_memory_root(project_root) + memory_prefix = _project_relative_prefix(project_root, memory_root) + index_relative = f"{memory_prefix}/features/archive.md" + rendered_index_hash = sha256_bytes( + render_archive_index(plan.archive_entries).encode("utf-8") + ) + for relative, expected in plan.snapshots.items(): + mapped = relative + for move in plan.moves: + source = move.source.rstrip("/") + if relative == source or relative.startswith(source + "/"): + mapped = move.target.rstrip("/") + relative[len(source) :] + break + actual_path = _confined_path(project_root, mapped) + actual = _hash_at(actual_path) + required = ( + rendered_index_hash + if mapped == index_relative + else final_hashes.get(relative, final_hashes.get(mapped, expected)) + ) + if actual != required: + raise ArchiveContractError( + "post-check", f"content hash mismatch: {mapped}" + ) + actual_entries = tuple(parse_archive_index(memory_root)) + if actual_entries != tuple( + sorted(plan.archive_entries, key=lambda item: (item.month, item.feature_id)) + ): + raise ArchiveContractError("post-check", "archive index does not match plan") + return "post-check" + + +def _transaction_root(project_root: Path, transaction_id: str) -> Path: + if not TRANSACTION_ID_RE.fullmatch(transaction_id): + raise ArchiveContractError( + "usage", "transaction ID must match YYYYMMDDTHHMMSSZ-12hex", 2 + ) + memory_root = discover_memory_root(project_root.resolve()) + root = memory_root / "features" / ".archive-txn" / transaction_id + boundary = (memory_root / "features" / ".archive-txn").resolve() + try: + root.resolve().relative_to(boundary) + except ValueError as error: + raise ArchiveContractError("path-escape", transaction_id) from error + return root + + +def _assert_no_stranded_transactions(memory_root: Path) -> None: + transaction_root = memory_root / "features" / ".archive-txn" + if not transaction_root.exists(): + return + if transaction_root.is_symlink() or not transaction_root.is_dir(): + raise ArchiveContractError( + "transaction", "stranded-transaction: unsafe .archive-txn path" + ) + stranded = sorted(path.name for path in transaction_root.iterdir()) + if stranded: + raise ArchiveContractError( + "transaction", + "stranded-transaction: restore required for " + ", ".join(stranded), + ) + + +def _write_journal(transaction_root: Path, journal: Mapping[str, object]) -> None: + atomic_write_bytes( + transaction_root / "journal.json", + json.dumps( + journal, ensure_ascii=False, sort_keys=True, indent=2 + ).encode("utf-8") + + b"\n", + ) + + +def _load_journal(transaction_root: Path) -> dict[str, object]: + path = transaction_root / "journal.json" + if not path.is_file(): + raise ArchiveContractError("restore", f"missing journal: {path}") + try: + value = json.loads(path.read_text(encoding="utf-8-sig")) + except (json.JSONDecodeError, UnicodeDecodeError) as error: + raise ArchiveContractError("restore", f"invalid journal: {error}") from error + if not isinstance(value, dict) or value.get("schema_version") != 1: + raise ArchiveContractError("restore", "unsupported journal schema") + if value.get("state") not in JOURNAL_STATES: + raise ArchiveContractError("restore", "invalid journal state") + return value + + +def _journal_index_relative(project_root: Path) -> str: + memory_root = discover_memory_root(project_root) + return f"{_project_relative_prefix(project_root, memory_root)}/features/archive.md" + + +def _validate_journal_scope( + project_root: Path, journal: Mapping[str, object] +) -> tuple[ArchivePlan, tuple[Move, ...], tuple[Mapping[str, object], ...]]: + expected_keys = { + "schema_version", + "transaction_id", + "operation", + "plan_sha256", + "plan", + "state", + "moves", + "backups", + "completed_operations", + "snapshots", + "created_directories", + } + if set(journal) != expected_keys: + raise ArchiveContractError("restore", "journal fields do not match schema") + plan_payload = journal.get("plan") + if not isinstance(plan_payload, dict): + raise ArchiveContractError("restore", "journal plan is missing or invalid") + try: + plan = archive_plan_from_payload(plan_payload) + except ArchiveContractError as error: + raise ArchiveContractError( + "restore", f"journal plan is invalid: {error}" + ) from error + if not plan.moves: + raise ArchiveContractError("restore", "journal plan has no moves") + if journal.get("plan_sha256") != plan.computed_sha256(): + raise ArchiveContractError("restore", "journal plan SHA-256 mismatch") + if journal.get("operation") != plan.operation: + raise ArchiveContractError("restore", "journal operation does not match plan") + + expected_moves = [asdict(move) for move in plan.moves] + if journal.get("moves") != expected_moves: + raise ArchiveContractError("restore", "journal move scope does not match plan") + if journal.get("snapshots") != dict(plan.snapshots): + raise ArchiveContractError("restore", "journal snapshots do not match plan") + + reference_hashes: dict[str, str] = {} + for edit in sorted( + plan.reference_edits, + key=lambda item: (item.path, item.kind, item.old, item.new), + ): + expected_before = reference_hashes.get( + edit.path, plan.snapshots.get(edit.path, "") + ) + if ( + not expected_before + or edit.before_sha256 != expected_before + or not HASH_RE.fullmatch(edit.after_sha256) + or edit.occurrences < 1 + or not edit.old + or edit.old == edit.new + ): + raise ArchiveContractError( + "restore", + f"journal reference-edit chain does not match snapshots: {edit.path}", + ) + reference_hashes[edit.path] = edit.after_sha256 + + index_relative = _journal_index_relative(project_root) + expected_backup_paths = {index_relative} | { + edit.path for edit in plan.reference_edits + } + raw_backups = journal.get("backups") + if not isinstance(raw_backups, list) or not all( + isinstance(item, dict) for item in raw_backups + ): + raise ArchiveContractError("restore", "journal backups are invalid") + backups = tuple(raw_backups) + actual_paths = [str(item.get("path", "")) for item in backups] + if ( + len(actual_paths) != len(set(actual_paths)) + or set(actual_paths) != expected_backup_paths + ): + raise ArchiveContractError("restore", "journal backup scope does not match plan") + for item in backups: + relative = str(item.get("path", "")) + expected_state = "existing" if relative in plan.snapshots else "missing-before" + if item.get("state") != expected_state: + raise ArchiveContractError( + "restore", f"journal backup state does not match plan: {relative}" + ) + if expected_state == "existing": + expected_item_keys = {"path", "state", "backup", "sha256"} + if set(item) != expected_item_keys: + raise ArchiveContractError( + "restore", f"journal backup fields are invalid: {relative}" + ) + if item.get("backup") != f"backups/{relative}": + raise ArchiveContractError( + "restore", f"journal backup path does not match plan: {relative}" + ) + if item.get("sha256") != plan.snapshots[relative]: + raise ArchiveContractError( + "restore", f"journal backup hash does not match plan: {relative}" + ) + elif set(item) != {"path", "state"}: + raise ArchiveContractError( + "restore", f"journal missing-before fields are invalid: {relative}" + ) + + allowed_operations: set[tuple[tuple[str, str], ...]] = set() + for move in plan.moves: + allowed_operations.add( + tuple( + sorted( + { + "kind": "move", + "source": move.source, + "target": move.target, + }.items() + ) + ) + ) + allowed_operations.add( + tuple(sorted({"kind": "archive-index", "path": index_relative}.items())) + ) + for edit in plan.reference_edits: + allowed_operations.add( + tuple( + sorted( + { + "kind": "reference-edit", + "path": edit.path, + "mapped_path": _mapped_after_moves(edit.path, plan.moves), + }.items() + ) + ) + ) + completed = journal.get("completed_operations") + if not isinstance(completed, list) or not all( + isinstance(item, dict) for item in completed + ): + raise ArchiveContractError("restore", "journal completed operations are invalid") + if any(tuple(sorted(item.items())) not in allowed_operations for item in completed): + raise ArchiveContractError( + "restore", "journal completed operation exceeds plan scope" + ) + + allowed_directories = { + PurePosixPath(move.target).parent.as_posix() for move in plan.moves + } + created = journal.get("created_directories") + if not isinstance(created, list) or len(created) != len(set(created)) or any( + not isinstance(item, str) or item not in allowed_directories for item in created + ): + raise ArchiveContractError( + "restore", "journal created-directory scope does not match plan" + ) + return plan, tuple(plan.moves), backups + + +def _restore_allowed_hashes( + project_root: Path, plan: ArchivePlan, relative: str +) -> frozenset[str]: + hashes = {plan.snapshots[relative]} if relative in plan.snapshots else set() + for edit in plan.reference_edits: + if edit.path == relative: + hashes.add(edit.before_sha256) + hashes.add(edit.after_sha256) + if relative == _journal_index_relative(project_root): + hashes.add( + sha256_bytes(render_archive_index(plan.archive_entries).encode("utf-8")) + ) + return frozenset(hashes) + + +def _restore_move_states( + project_root: Path, moves: Sequence[Move] +) -> Mapping[str, str]: + states: dict[str, str] = {} + for move in moves: + source = _confined_path(project_root, move.source) + target = _confined_path(project_root, move.target) + source_ready = source.is_dir() and not target.exists() + target_ready = target.is_dir() and not source.exists() + if source_ready: + states[move.source] = "source" + elif target_ready: + states[move.source] = "target" + else: + raise ArchiveContractError( + "restore", + f"source/target drift blocks restore: {move.source} -> {move.target}", + ) + return MappingProxyType(states) + + +def _restore_current_relative( + relative: str, moves: Sequence[Move], states: Mapping[str, str] +) -> str: + for move in moves: + source = move.source.rstrip("/") + if relative == source or relative.startswith(source + "/"): + if states[move.source] == "target": + return move.target.rstrip("/") + relative[len(source) :] + return relative + return relative + + +def _validate_restore_current_state( + project_root: Path, + transaction_root: Path, + plan: ArchivePlan, + moves: Sequence[Move], + backups: Sequence[Mapping[str, object]], + states: Mapping[str, str], +) -> None: + for item in backups: + if item.get("state") != "existing": + continue + relative = str(item["path"]) + backup = _confined_path(transaction_root, str(item["backup"])) + if ( + not backup.is_file() + or sha256_bytes(backup.read_bytes()) != item.get("sha256") + ): + raise ArchiveContractError( + "restore", f"backup preflight failed for {relative}" + ) + for relative in plan.snapshots: + current_relative = _restore_current_relative(relative, moves, states) + path = _confined_path(project_root, current_relative) + if not path.is_file(): + raise ArchiveContractError( + "restore", f"drift: missing transaction file {current_relative}" + ) + if sha256_bytes(path.read_bytes()) not in _restore_allowed_hashes( + project_root, plan, relative + ): + raise ArchiveContractError( + "restore", f"drift: unexpected transaction bytes at {current_relative}" + ) + for item in backups: + if item.get("state") != "missing-before": + continue + relative = str(item["path"]) + path = _confined_path(project_root, relative) + if not path.exists(): + continue + if ( + not path.is_file() + or sha256_bytes(path.read_bytes()) + not in _restore_allowed_hashes(project_root, plan, relative) + ): + raise ArchiveContractError( + "restore", f"drift: unexpected file at {relative}" + ) + + +def _mapped_after_moves(relative: str, moves: Sequence[Move]) -> str: + for move in moves: + source = move.source.rstrip("/") + if relative == source or relative.startswith(source + "/"): + return move.target.rstrip("/") + relative[len(source) :] + return relative + + +def _remove_transaction_tree(transaction_root: Path) -> None: + parent = transaction_root.parent + shutil.rmtree(transaction_root) + try: + parent.rmdir() + except OSError: + pass + + +def _failure_limit() -> int | None: + raw = os.environ.get("AGENT_LOOP_ARCHIVE_FAIL_AFTER") + if raw is None: + return None + if os.environ.get("AGENT_LOOP_ARCHIVE_TEST_MODE") != "1": + raise ArchiveContractError( + "transaction", + "AGENT_LOOP_ARCHIVE_FAIL_AFTER is test-only and requires AGENT_LOOP_ARCHIVE_TEST_MODE=1", + ) + try: + value = int(raw) + except ValueError as error: + raise ArchiveContractError( + "transaction", "AGENT_LOOP_ARCHIVE_FAIL_AFTER must be a positive integer" + ) from error + if value < 1: + raise ArchiveContractError( + "transaction", "AGENT_LOOP_ARCHIVE_FAIL_AFTER must be a positive integer" + ) + return value + + +def _new_transaction_id(features_root: Path) -> str: + for _ in range(100): + value = ( + datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ-") + + secrets.token_hex(6) + ) + if not (features_root / ".archive-txn" / value).exists(): + return value + raise ArchiveContractError("transaction", "could not allocate transaction ID") + + +def apply_archive_plan( + project_root: Path, + plan: ArchivePlan, + *, + expected_plan_sha256: str, +) -> str: + project_root = project_root.resolve() + plan.assert_hash(expected_plan_sha256) + unsupported = [ + item for item in plan.skipped_references if item.classification == "unsupported" + ] + if unsupported: + raise ArchiveContractError( + "reference-impact", + f"unsupported references block apply: {', '.join(item.path for item in unsupported)}", + ) + memory_root = discover_memory_root(project_root) + _assert_no_stranded_transactions(memory_root) + failure_limit = _failure_limit() + if not plan.moves: + return "no-op" + + features_root = memory_root / "features" + transaction_id = _new_transaction_id(features_root) + transaction_root = _transaction_root(project_root, transaction_id) + transaction_root.mkdir(parents=True) + memory_prefix = _project_relative_prefix(project_root, memory_root) + index_relative = f"{memory_prefix}/features/archive.md" + backup_paths = sorted( + {item.path for item in plan.reference_edits} | {index_relative} + ) + journal: dict[str, object] = { + "schema_version": 1, + "transaction_id": transaction_id, + "operation": plan.operation, + "plan_sha256": plan.computed_sha256(), + "plan": plan.to_payload(), + "state": "prepared", + "moves": [asdict(move) for move in plan.moves], + "backups": [], + "completed_operations": [], + "snapshots": dict(plan.snapshots), + "created_directories": [], + } + _write_journal(transaction_root, journal) + try: + backups: list[dict[str, str]] = [] + for relative in backup_paths: + source = _confined_path(project_root, relative) + if source.is_file(): + backup_relative = f"backups/{relative}" + atomic_write_bytes( + transaction_root / PurePosixPath(backup_relative), source.read_bytes() + ) + backups.append( + { + "path": relative, + "state": "existing", + "backup": backup_relative, + "sha256": sha256_bytes(source.read_bytes()), + } + ) + elif source.exists(): + raise ArchiveContractError( + "transaction", f"backup target is not a file: {relative}" + ) + else: + backups.append({"path": relative, "state": "missing-before"}) + journal["backups"] = backups + _write_journal(transaction_root, journal) + + completed: list[dict[str, str]] = [] + created_directories: list[str] = [] + operation_count = 0 + + def record_mutation(kind: str, **details: str) -> None: + nonlocal operation_count + operation_count += 1 + completed.append({"kind": kind, **details}) + journal["completed_operations"] = completed + journal["created_directories"] = created_directories + _write_journal(transaction_root, journal) + if failure_limit is not None and operation_count >= failure_limit: + raise RuntimeError( + f"injected archive failure after operation {operation_count}" + ) + + journal["state"] = "moving" + _write_journal(transaction_root, journal) + for move in plan.moves: + source = _confined_path(project_root, move.source) + target = _confined_path(project_root, move.target) + if not source.is_dir() or target.exists(): + raise ArchiveContractError( + "transaction", f"move precondition failed: {move.source} -> {move.target}" + ) + if not target.parent.exists(): + target.parent.mkdir(parents=True) + created_directories.append( + target.parent.relative_to(project_root).as_posix() + ) + journal["created_directories"] = created_directories + _write_journal(transaction_root, journal) + source.rename(target) + record_mutation("move", source=move.source, target=move.target) + + index = memory_root / "features" / "archive.md" + atomic_write_bytes( + index, render_archive_index(plan.archive_entries).encode("utf-8") + ) + record_mutation("archive-index", path=index_relative) + + for edit in plan.reference_edits: + mapped = _mapped_after_moves(edit.path, plan.moves) + target = _confined_path(project_root, mapped) + current, has_bom = _read_utf8_preserving(target) + current_bytes = _encode_utf8_preserving(current, has_bom) + if sha256_bytes(current_bytes) != edit.before_sha256: + raise ArchiveContractError( + "stale-plan", f"reference changed before apply: {edit.path}" + ) + if current.count(edit.old) != edit.occurrences: + raise ArchiveContractError( + "stale-plan", f"reference occurrence drift: {edit.path}" + ) + updated = current.replace(edit.old, edit.new) + updated_bytes = _encode_utf8_preserving(updated, has_bom) + if sha256_bytes(updated_bytes) != edit.after_sha256: + raise ArchiveContractError( + "transaction", f"reference edit hash mismatch: {edit.path}" + ) + atomic_write_bytes(target, updated_bytes) + record_mutation("reference-edit", path=edit.path, mapped_path=mapped) + + journal["state"] = "references-updated" + _write_journal(transaction_root, journal) + journal["state"] = "checking" + _write_journal(transaction_root, journal) + validate_archive_plan_state(project_root, plan, plan.operation) + journal["state"] = "verified" + _write_journal(transaction_root, journal) + _remove_transaction_tree(transaction_root) + return transaction_id + except Exception as error: + try: + restore_transaction(project_root, transaction_id) + except ArchiveContractError as restore_error: + raise ArchiveContractError( + "transaction", + f"{error}; restore=failed: {restore_error.detail}", + ) from error + raise ArchiveContractError( + "transaction", f"{error}; restore=complete" + ) from error + + +def restore_transaction(project_root: Path, transaction_id: str) -> None: + project_root = project_root.resolve() + transaction_root = _transaction_root(project_root, transaction_id) + journal = _load_journal(transaction_root) + if journal.get("transaction_id") != transaction_id: + raise ArchiveContractError("restore", "journal transaction ID mismatch") + plan, moves, backups = _validate_journal_scope(project_root, journal) + journal["state"] = "restoring" + _write_journal(transaction_root, journal) + states = _restore_move_states(project_root, moves) + _validate_restore_current_state( + project_root, transaction_root, plan, moves, backups, states + ) + try: + for move in reversed(moves): + source = _confined_path(project_root, move.source) + target = _confined_path(project_root, move.target) + if source.exists() and target.exists(): + raise ArchiveContractError( + "restore", f"source collision blocks restore: {move.source}" + ) + if source.is_dir() and not target.exists(): + continue + if source.exists(): + raise ArchiveContractError( + "restore", f"restored source is not a directory: {move.source}" + ) + if not target.is_dir(): + raise ArchiveContractError( + "restore", f"moved target is missing: {move.target}" + ) + source.parent.mkdir(parents=True, exist_ok=True) + target.rename(source) + + for item in backups: + relative = str(item.get("path", "")) + target = _confined_path(project_root, relative) + state = item.get("state") + if state == "existing": + backup = _confined_path( + transaction_root, str(item.get("backup", "")) + ) + if not backup.is_file(): + raise ArchiveContractError( + "restore", f"missing backup for {relative}" + ) + content = backup.read_bytes() + if sha256_bytes(content) != item.get("sha256"): + raise ArchiveContractError( + "restore", f"backup hash mismatch for {relative}" + ) + atomic_write_bytes(target, content) + elif state == "missing-before": + if target.is_dir(): + raise ArchiveContractError( + "restore", f"cannot remove directory backup target: {relative}" + ) + target.unlink(missing_ok=True) + else: + raise ArchiveContractError( + "restore", f"invalid backup state for {relative}" + ) + + for relative, expected in dict(journal.get("snapshots", {})).items(): + actual = _hash_at(_confined_path(project_root, str(relative))) + if actual != expected: + raise ArchiveContractError( + "restore", f"restored content hash mismatch: {relative}" + ) + for relative in reversed(list(journal.get("created_directories", []))): + directory = _confined_path(project_root, str(relative)) + try: + directory.rmdir() + except OSError: + pass + journal["state"] = "restored" + _write_journal(transaction_root, journal) + _remove_transaction_tree(transaction_root) + except ArchiveContractError: + journal["state"] = "restoring" + _write_journal(transaction_root, journal) + raise + except Exception as error: + journal["state"] = "restoring" + _write_journal(transaction_root, journal) + raise ArchiveContractError("restore", str(error)) from error diff --git a/scripts/restore-feature-monthly-archive.py b/scripts/restore-feature-monthly-archive.py new file mode 100644 index 0000000..98b769a --- /dev/null +++ b/scripts/restore-feature-monthly-archive.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +from checker_support import require_supported_python +from feature_archive_support import ArchiveContractError, restore_transaction + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Restore one Feature Monthly Archive transaction" + ) + parser.add_argument("--project-root", required=True) + parser.add_argument("--transaction-id", required=True) + return parser + + +def main() -> int: + require_supported_python() + arguments = build_parser().parse_args() + try: + restore_transaction(Path(arguments.project_root), arguments.transaction_id) + except ArchiveContractError as error: + print(f"{error.category}: {error.detail}", file=sys.stderr) + return error.exit_code + print( + f"PASS: Feature Monthly Archive transaction restored; " + f"transaction_id={arguments.transaction_id}" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/scan-feature-monthly-archive.py b/scripts/scan-feature-monthly-archive.py new file mode 100644 index 0000000..a751d1e --- /dev/null +++ b/scripts/scan-feature-monthly-archive.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import sys +from datetime import date +from pathlib import Path + +from checker_support import require_supported_python +from feature_archive_support import ArchiveContractError, build_archive_plan + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Build a read-only Feature Monthly Archive plan" + ) + parser.add_argument("--project-root", required=True) + parser.add_argument( + "--operation", choices=("archive", "rehydrate"), required=True + ) + parser.add_argument("--month", action="append", default=[]) + parser.add_argument("--feature-id", action="append", default=[]) + parser.add_argument("--as-of", required=True) + return parser + + +def main() -> int: + require_supported_python() + arguments = build_parser().parse_args() + try: + as_of = date.fromisoformat(arguments.as_of) + except ValueError: + print("usage: --as-of must be YYYY-MM-DD", file=sys.stderr) + return 2 + try: + plan = build_archive_plan( + Path(arguments.project_root), + operation=arguments.operation, + selected_months=arguments.month, + selected_feature_ids=arguments.feature_id, + as_of=as_of, + ) + except ArchiveContractError as error: + print(f"{error.category}: {error.detail}", file=sys.stderr) + return error.exit_code + print( + json.dumps(plan.to_payload(), ensure_ascii=False, sort_keys=True, indent=2) + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/templates/decision.md b/templates/decision.md index bc2c4bf..0e49c1b 100644 --- a/templates/decision.md +++ b/templates/decision.md @@ -10,6 +10,19 @@ Source Requirements: - Requirement: - Requirement README: +## Effective Requirement Snapshot + +Effective Concept Source: +Concept Foundation Status: accepted | concept-foundation-not-needed +Accepted Concept IDs: +Accepted Requirement Model IDs: +Upstream Compatibility: current | review-required +Last Compatibility Check: +Trace Applicability: required | not-applicable +Trace Not-Applicable Reason: + +Resolve the requirement README effective-source pointer before drafting and again before acceptance. This snapshot references accepted product meaning; it does not copy or redefine it. `review-required` is a dependency judgment, not an ADR lifecycle status. For a reasoned `concept-foundation-not-needed` source, set both accepted-ID fields to `none`, set trace applicability to `not-applicable`, give a concrete reason, and omit the two model tables below. + Applies To: - Product area: - Features: @@ -39,9 +52,47 @@ Non-Goals: ## Domain Concepts -| Concept | Definition | Responsibility | Source Of Truth | +Reference accepted PRD / Requirement Product Model semantics. Do not create, rename, split, merge, or redefine product concepts in this record; return to Requirements Discussion if product meaning must change. + +| Accepted Concept / Product Model Reference | Accepted Product Meaning Summary | Design Responsibility | Product Fact Owner / Decision Candidate | |---|---|---|---| -| | | | | +| C-... / PM-... | link or concise unchanged meaning | | | + +## Requirement Model Scope Inventory + +Account for every stable Requirement Model ID in the effective source before selecting this ADR's coherent scope. Stable source IDs include `REL-*`, `PERM-*`, `CMD-*`, `EVT-*`, `FLOW-*`, `STATE-*`, `PM-*`, and `EX-*`. + +| Requirement Model Ref | Scope Disposition | Owner / Reason | +|---|---|---| +| | in-scope / covered-by-accepted-decision / feature-local / proposed-decision / not-applicable | | + +Rules: +- `in-scope` names `this ADR` or its ADR ID; exactly these IDs appear in `Accepted Requirement Model IDs` and the Technical Landing Trace +- `covered-by-accepted-decision` names an existing accepted decision Markdown path +- `feature-local` names an existing Feature Spec path, or an explicit future path such as `planned:features//spec.md` +- `proposed-decision` names an existing decision draft or an explicit future path such as `planned:decisions/.md` +- `not-applicable` begins with `reason:` and gives a concrete product-neutral scope reason +- no source Requirement Model ID may be silently omitted + +## Requirement Model Technical Landing Trace + +Give every accepted Requirement Model ID declared in this ADR scope exactly one disposition. Do not create product meaning in this table; return to Requirements Discussion when accepted meaning is missing or ambiguous. + +| Requirement Model Ref | Accepted Meaning / Constraint | Disposition | Technical Landing | Preserved Invariant | Design Slice | Verification | +|---|---|---|---|---|---|---| +| | link or concise unchanged meaning | landed / covered-by-accepted-decision / feature-local / not-applicable | | | | | + +Allowed dispositions: + +```text +landed | covered-by-accepted-decision | feature-local | not-applicable +``` + +Rules: +- `landed` requires a concrete Technical Landing, Preserved Invariant, Design Slice, and Verification target +- `covered-by-accepted-decision` names the existing accepted decision Markdown path that owns the landing +- `feature-local` names an existing Feature Spec path or an explicit `planned:features//spec.md` owner path and must not hide a shared constraint +- `not-applicable` gives a concrete reason and is shown at the ADR Human Gate ## Business Flow @@ -85,6 +136,8 @@ We will: ### Data Model And Source Of Truth +This technical-design section must preserve accepted product meaning. Selecting tables, stores, events, ledgers, or providers belongs to Decision & Design after its Human Gate; it never authorizes a product-definition change. + | Data Object | Purpose | Source Of Truth | Key Fields | Invariant | |---|---|---|---|---| | | | | | | @@ -124,6 +177,21 @@ We will: | Security / Risk | | | | | Observability | | | | +## Operational Landing Trigger Assessment + +Assess all concerns, but expand detail only for `triggered` rows. A `not-triggered` row records one concrete reason and does not create an empty operational section. + +Allowed trigger status: `triggered | not-triggered`. + +| Concern | Status | Reason / Trigger Evidence | Detail Section If Triggered | +|---|---|---|---| +| Migration / Backfill | triggered / not-triggered | | | +| Compatibility | triggered / not-triggered | | | +| Rollout / Cutover | triggered / not-triggered | | | +| Rollback / Reversibility | triggered / not-triggered | | | + +When at least one row is `triggered`, add a `Triggered Operational Landing` section containing only the named detail subsections. When every concern is `not-triggered`, do not add that section. + ## Design Slice Coverage Turn every implementation-bearing flow step, invariant, recovery responsibility, and non-functional target into a stable Design Slice ID. @@ -138,6 +206,49 @@ Coverage rules: - `deferred` or `out-of-scope` requires an explicit human decision - update this table when feature scope or ownership changes +## Coverage Hard Gate + +Before acceptance and before dependent Feature Spec work: + +- [ ] Effective Concept Source resolves and matches the reviewed source +- [ ] Concept Foundation Status is accepted or reasoned `concept-foundation-not-needed` +- [ ] Upstream Compatibility is `current` +- [ ] Every source Requirement Model ID has an explicit scope disposition, or trace is reasoned not-applicable +- [ ] Every in-scope Accepted Requirement Model ID has exactly one disposition +- [ ] Every `landed` row has Technical Landing, Preserved Invariant, Design Slice, and Verification +- [ ] Every `covered-by-accepted-decision` and `feature-local` row names an existing or explicitly planned verified owner path +- [ ] Every `not-applicable`, deferred, and out-of-scope item is visible in Human Review Summary +- [ ] Every implementation-bearing technical rule is represented in Design Slice Coverage +- [ ] No required Design Slice is `unassigned` +- [ ] No unresolved product-semantic blocker remains + +`Applicable Decisions` is not a substitute for this coverage. + +Run the structural validator while the ADR is still `proposed`. Then present the Decision & Design Human Review Summary. Only after explicit human acceptance may the Agent set `Status: accepted`, record the evidence below, and rerun accepted-mode validation. + +## Human Review Evidence + +Populate this section only after explicit human acceptance. Leave the ADR `proposed` while these fields are absent or incomplete. + +Decision: +Confirmed By: +Confirmed At: +Evidence: + +## Upstream Compatibility And Drift + +Compatibility Comparison: + +| Item | Previous Effective Source | Current Effective Source | Impact | Required Action | +|---|---|---|---|---| +| Concept / Requirement Model IDs | | | none / compatible / incompatible | retain / update snapshot after review / supersede | + +Rules: +- an effective-source or accepted-model change sets `Upstream Compatibility: review-required` before new dependent Feature Spec, Plan, or implementation work +- compatible technical decisions may refresh snapshot/trace only after Decision & Design Human Review +- incompatible accepted decisions require a new superseding ADR +- never rewrite accepted decision meaning in place + ## Closure And Verification Plan | Requirement / Goal | Verification Method | Evidence Location | diff --git a/templates/feature-archive.md b/templates/feature-archive.md new file mode 100644 index 0000000..3e7ddf7 --- /dev/null +++ b/templates/feature-archive.md @@ -0,0 +1,6 @@ +# Feature Archive + +This file locates archived or rehydrated features. Feature specs, tests, notes, requirement sources, and accepted decisions remain authoritative. + +| Feature ID | Month | Current Path | Archive State | Closed At | Delivered Summary | Source Requirements | Applicable Decisions | Last Moved At | +|---|---|---|---|---|---|---|---|---| diff --git a/templates/notes.md b/templates/notes.md index 1e81dfa..632dd41 100644 --- a/templates/notes.md +++ b/templates/notes.md @@ -137,3 +137,16 @@ Status: active | blocked | paused | closed ## Pause / Resume Point ## Close Record + +Closed At: +Human Decision: + +## Archive Readiness + +Closed At: +Delivered Summary: +Verification: complete +Feature Close Review: complete +Drift: resolved +Project Memory Impact: complete | none +Open Follow-up: none | diff --git a/templates/onboarding-db/README.md b/templates/onboarding-db/README.md index 1989484..6e8cef8 100644 --- a/templates/onboarding-db/README.md +++ b/templates/onboarding-db/README.md @@ -38,8 +38,11 @@ ## 质量要求 -- 模块和流程默认必须包含架构/边界图、ASCII 状态图、Timeline / 时序图。 +- `critical` / `important` 核心流程必须从触发闭合到业务终态,并通过 Flow Slice Coverage 连接代码证据、图和正文。 +- 核心流程默认包含 Core Flow Overview / Boundary、ASCII State Machine / Decision、Timeline / Sequence;Timeline / Sequence 是单流程主叙事。 +- 模块和其他内容文档按真实语义选图;stateless glossary、静态配置清单和纯索引不强制状态图。 - 普通流程图和时序图优先用 Mermaid flowchart / sequenceDiagram;状态机、复杂原理图和复杂示例图优先用 ASCII。 - 禁止用无边界、无状态、无数据对象的 `A-->B-->C` flowchart 当主图。 - module / flow 默认单文件长文档,不默认拆成很多小文件。 - 不允许空文件、TODO 占位、泛泛摘要。 +- Completeness Hard Gate 先于质量评分;missing/blocked critical slice 不能标记 `newcomer-ready`。 diff --git a/templates/onboarding-db/batch-review.md b/templates/onboarding-db/batch-review.md index 9017e77..590222b 100644 --- a/templates/onboarding-db/batch-review.md +++ b/templates/onboarding-db/batch-review.md @@ -21,10 +21,19 @@ | Topic | Old Status | New Status | Score | Reason | |---|---|---|---|---| +## Completeness Hard Gate + +| Flow ID | Criticality | Business Terminals Closed? | Required Slice IDs | Missing / Blocked Slice IDs | Evidence + Diagram + Section Trace Complete? | Result | Next Action | +|---|---|---|---|---|---|---|---| + +Hard Gate 为 `FAIL` / `blocked-by-unknown` 时不得标记 `newcomer-ready`,不得用 Overall 平均分覆盖。 + ## Score -| Topic | Wireframe | Use Cases | Data Objects | State | Evidence | Failure / Troubleshooting | Change Guidance | Readability | Overall | -|---|---|---|---|---|---|---|---|---|---| +| Topic | Core flow discovery completeness | Slice and branch coverage | Required diagram set present | Architecture diagram clarity | State diagram clarity | Timeline / sequence clarity | Use case completeness | Data object completeness | State transition clarity | Code evidence | Evidence granularity | Example authenticity | Failure / recovery | Troubleshooting | Consistency / gateway risk | Change guidance | Newcomer readability | Overall | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| + +评分锚点与 `coverage-matrix.md` 一致:5=完整且证据闭合;4=核心完整且只有低风险缺口;3=分支/恢复/证据有明显缺口;2=泛化概览;1=无法依靠文档理解或操作。 ## Gaps / Unknowns @@ -35,6 +44,6 @@ - -## Human Review Status +## Optional Human Review Notes - diff --git a/templates/onboarding-db/coverage-matrix.md b/templates/onboarding-db/coverage-matrix.md index aee1149..0f6e617 100644 --- a/templates/onboarding-db/coverage-matrix.md +++ b/templates/onboarding-db/coverage-matrix.md @@ -5,6 +5,15 @@ Coverage tracks topic readiness, not file count. | Topic | Type | Doc Path | Score | Status | Missing Evidence | Next Action | |---|---|---|---|---|---|---| +## Completeness Hard Gate + +先判完整性,再评分。任何 `critical` / `important` flow 有 missing/blocked critical slice,都不能进入 `newcomer-ready`,也不能靠其他维度高分抵消。 + +| Flow ID | Criticality | Business Terminals Closed? | Required Slice IDs | Missing / Blocked Slice IDs | Evidence + Diagram + Section Trace Complete? | Result | Next Action | +|---|---|---|---|---|---|---|---| + +`Result` 只使用 `PASS` / `FAIL` / `blocked-by-unknown`。Focused scope 通过只代表明确边界内通过,不能写成 whole-project ready。 + ## Status Definitions | Status | Meaning | @@ -14,7 +23,7 @@ Coverage tracks topic readiness, not file count. | in-progress | 当前 batch 正在写 | | draft | 草稿存在,但未 review | | needs-review | 等待人类或 Agent review | -| newcomer-ready | 评分 >= 4/5 且人类确认新人可读 | +| newcomer-ready | Completeness Hard Gate PASS、评分 >= 4/5、证据与 gap 记录完整 | | stale | 代码现实已变化,需要刷新 | | blocked-by-unknown | 缺少代码证据或业务确认 | | not-applicable | 明确不适用 | @@ -23,6 +32,8 @@ Coverage tracks topic readiness, not file count. | Dimension | Score | Notes | |---|---|---| +| Core flow discovery completeness | | | +| Slice and branch coverage | | | | Required diagram set present | | | | Architecture diagram clarity | | | | State diagram clarity | | | @@ -31,12 +42,27 @@ Coverage tracks topic readiness, not file count. | Data object completeness | | | | State transition clarity | | | | Code evidence | | | -| Failure troubleshooting | | | +| Evidence granularity | | | +| Example authenticity | | | +| Failure / recovery | | | +| Troubleshooting | | | +| Consistency / gateway risk | | | | Change guidance | | | | Newcomer readability | | | Rules: +- Completeness Hard Gate must pass before quality scoring. - Below 4/5 cannot be `newcomer-ready`. - Below 3/5 must enter next batch or be `blocked-by-unknown`. -- Every formal topic should include at least architecture/boundary + ASCII state diagram. Module and flow docs should also include Timeline / sequence diagrams by default. Mermaid flowchart / sequenceDiagram is preferred for normal flow and timing; ASCII is preferred for state-machine / decision diagrams and complex examples. Swimlane-style ownership lanes are optional supporting detail, but the timeline explanation is required for module/flow docs unless explicitly exempted in the accepted spec. +- `critical` / `important` flow docs require Core Flow Overview / Boundary, ASCII State Machine / Decision, and Timeline / Sequence. Other topics select diagrams that explain real semantics; stateless topics do not invent state diagrams. + +## Score Anchors + +| Score | Anchor | +|---:|---| +| 5 | 完整且证据闭合,新人可以独立解释、验证和排障 | +| 4 | 核心路径完整,只有不影响接手的低风险缺口 | +| 3 | 主路径可理解,但分支、恢复或证据有明显缺口 | +| 2 | 结构存在,内容主要是概览或泛化描述 | +| 1 | 无法依靠该文档理解或操作 | diff --git a/templates/onboarding-db/evidence-graph.md b/templates/onboarding-db/evidence-graph.md index ce8f9e0..6a2ea70 100644 --- a/templates/onboarding-db/evidence-graph.md +++ b/templates/onboarding-db/evidence-graph.md @@ -24,6 +24,15 @@ Evidence Graph 是代码证据索引,不是项目摘要。先建立它,再 | Flow | Trigger | Participants | State Changes | External Dependencies | Evidence | Risk | |---|---|---|---|---|---|---| +## Core Flow Inventory + +`critical` / `important` 流程必须从业务触发闭合到成功、失败、取消、未知或人工处理终态。不能把 callback、consumer、retry、DLQ、compensation、reconciliation 或 job 改名成后续 topic 来移出核心流程。 + +| Flow ID | Flow | Business Outcome | Criticality | Trigger / Entry | Success Terminal | Failure Terminals | Variants / Branches | Participants / Owners | State / Data Owners | Async / Jobs / Callbacks | External Side Effects | Recovery Responsibility | Evidence Chain | Selection | Selection Reason | Confidence / Unknowns | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| + +`Selection` 只使用 `planned`、`deferred`、`not-applicable`。`critical` / `important` 流程若 deferred,必须写明影响、缺少证据和下一动作;whole-project onboarding 不能在核心流程延期时宣称完成。 + ## Relationship Wireframe 多模块 / 多服务项目必须画关系线框图。表格不能替代这张图。 @@ -75,6 +84,10 @@ Evidence Graph 是代码证据索引,不是项目摘要。先建立它,再 - [ ] 每个 Module Candidate 都有具体 evidence,不能只有目录名。 - [ ] 每个 Flow Candidate 都有 trigger、participants、state changes 和 evidence。 +- [ ] 每个 `critical` / `important` Flow Candidate 都有稳定 Flow ID、业务结果、成功/失败终态、变体、owner、side effects、recovery 和 Evidence Chain。 +- [ ] 非终态 `accepted` / `pending` / `processing` 没有被误写成核心流程完成。 +- [ ] callback、consumer、retry、DLQ、compensation、reconciliation 和 job 已归入其负责的核心流程闭环。 +- [ ] 每个 `critical` / `important` flow 已 planned,或有证据级 deferred reason、影响和下一动作。 - [ ] 多模块 / 多服务项目已有 Relationship Wireframe。 - [ ] 没有证据的候选项已移入 Unknowns。 - [ ] 没有 `<...>`、TBD、TODO、待补充、空 required row 或“看代码/see code”占位证据。 diff --git a/templates/onboarding-db/flow.md b/templates/onboarding-db/flow.md index 255effa..5d84609 100644 --- a/templates/onboarding-db/flow.md +++ b/templates/onboarding-db/flow.md @@ -1,22 +1,43 @@ # Flow: -不要只写 A→B→C。状态图优先:每个流程文档默认必须有架构/边界图、ASCII 状态机/决策图、Timeline / 时序图。架构/边界图讲流程在哪些模块/系统之间发生,状态图讲状态怎么变,Timeline / 时序图讲流程怎么跑。Mermaid flowchart / sequenceDiagram 可用于普通流程图和时序图;状态机图、复杂示例图优先用 ASCII。流程讲解时顺带解释涉及的数据模型、状态对象、消息、记录和配置。需要表达恢复窗口时补 Timeline Diagram。不要用 stacked box diagram / 阶段堆叠图当主图。 +不要只写 A→B→C。critical / important flow 默认必须有架构/边界图、ASCII 状态机/决策图、Timeline / 时序图。架构/边界图讲流程在哪些模块/系统之间发生,状态图讲状态怎么变,Timeline / 时序图讲流程怎么跑。supporting flow 只保留与真实语义相关的章节和图;一旦承担核心状态、外部副作用或恢复责任,就升级并纳入对应核心流程的完整追踪。Mermaid flowchart / sequenceDiagram 可用于普通流程图和时序图;状态机图、复杂示例图优先用 ASCII。流程讲解时顺带解释涉及的数据模型、状态对象、消息、记录和配置。需要表达恢复窗口时补 Timeline Diagram。不要用 stacked box diagram / 阶段堆叠图当主图。 -## 1. 用例 +## 1. Flow Identity And Outcomes +- Flow ID: +- Criticality:critical | important | supporting - 业务目标: - 触发入口: - 参与模块: - 前置状态: -- 成功结果: +- Success Terminal: +- Failure / Cancel / Unknown / Manual Terminals: +- Variants / Branches: +- External Side Effects: +- Recovery Responsibility: +- Evidence Chain: - 高风险点: -## 2. 架构/边界图 / 线框流程图 +`accepted` / `pending` / `processing` 只有在它本身是业务终态时才能作为 Success Terminal。callback、consumer、retry、DLQ、compensation、reconciliation 和 job 若负责核心状态、副作用或恢复,必须留在本 Flow 的 slice coverage 中。 + +## 2. Flow Slice Coverage + +`critical` / `important` flow 必填。Supporting flow 只有承担核心状态、外部副作用或恢复责任时才升级。 + +| Flow ID | Slice ID | Path Kind | Trigger / Precondition | Owner | Input / Output | Action | State Read / Written | Transition | Sync / Async / External | Failure Result | Recovery | Evidence | Diagram IDs | Document Section | Coverage Status | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| + +`Path Kind`: `main` / `branch` / `failure` / `recovery`。`Coverage Status`: `covered` / `inferred` / `blocked`。 + +## 3. 架构/边界图 / 线框流程图 用来讲清楚流程涉及的系统边界、模块关系、数据/依赖位置。 优先用 Mermaid flowchart;如果边界和数据所有者用 ASCII 更清楚,也可以用 ASCII 架构图。 +- Diagram ID: +- Covered Slice IDs: + ```mermaid flowchart LR Caller["Caller / Trigger"] --> Entry["Flow Entry"] @@ -26,10 +47,13 @@ flowchart LR Domain --> Outbound["Response / Event / Callback"] ``` -## 3. ASCII 状态机/决策图 +## 4. ASCII 状态机/决策图 状态图优先。用来讲状态怎么变、异常怎么恢复、哪里重试/补偿/回滚。 +- Diagram ID: +- Covered Slice IDs: + ```text [Start] | @@ -49,12 +73,15 @@ flowchart LR [Done] ``` -## 4. Timeline / 时序图(必填) +## 5. Timeline / 时序图(必填) -用来讲清楚流程从触发到终态按时间怎么发生;一边讲步骤,一边点名本阶段读写的数据模型/状态字段/消息对象/配置。 +Timeline / Sequence Diagram is the primary per-flow narrative。用来讲清楚流程从触发到终态按时间怎么发生;一边讲步骤,一边点名本阶段读写的数据模型/状态字段/消息对象/配置。 优先用 Mermaid sequenceDiagram。 +- Diagram ID: +- Covered Slice IDs: + ```mermaid sequenceDiagram participant Caller @@ -69,15 +96,26 @@ sequenceDiagram Entry-->>Caller: response ``` -## 5. 三图讲解 +## 6. 图组讲解与复杂度触发图 -- 架构/边界图说明: -- 状态图说明: -- Timeline / 时序图说明: +- 架构/边界图说明(结论、Covered Slice IDs、数据/状态/消息/配置、code evidence): +- 状态图说明(结论、Covered Slice IDs、非法转换与恢复、code evidence): +- Timeline / 时序图说明(结论、Covered Slice IDs、读写与消息顺序、code evidence): - 流程中出现的数据模型: - 推断内容和证据缺口: -## 6. 泳道图(可选) +| Complexity Signal | Required Additional Diagram | Diagram ID | Covered Slice IDs | Included / N/A Reason | +|---|---|---|---|---| +| callback/retry/compensation/reconciliation | Failure Recovery Timeline | | | | +| object transformations | Data Lineage / Object Transformation | | | | +| transaction/lock/outbox/concurrency/idempotency | Transaction / Concurrency Boundary | | | | +| topics/consumers/retry queue/DLQ | Async Message Topology | | | | +| routing/permission/provider/policy decisions | Decision Tree | | | | +| entity relations affect understanding | ERD / Model Relationship | | | | +| gateway/sidecar/environment topology | Runtime / Deployment Topology | | | | +| distributed logs/metrics/traces/checkpoints | Observability / Troubleshooting Map | | | | + +### 泳道图(可选) 当责任归属、跨角色协作或多系统所有权影响理解时填写。不要用它替代必填的 Timeline / 时序图。 @@ -162,14 +200,18 @@ Caller Gateway Service A Service B / DB Consumer ## 14. 代码证据 -| Claim | File / Symbol | Evidence | -|---|---|---| +| Claim | File / Symbol / Config Key | Call / Data Direction | Evidence | +|---|---|---|---| ## 15. 自检 -- [ ] 已包含架构/边界图(Mermaid flowchart 或 ASCII 架构图)和 ASCII 状态机/决策图。 -- [ ] 状态图优先,说明了核心状态变化、异常恢复、重试/补偿/回滚。 -- [ ] 已包含 Timeline / 时序图(必填,优先 Mermaid sequenceDiagram),并讲清流程怎么跑。 +- [ ] `critical` / `important` flow 已从业务触发闭合到 success/failure/cancel/unknown/manual terminal,没有停在非终态响应。 +- [ ] supporting flow 未被强制套用完整核心图组;若它承担核心状态、副作用或恢复责任,已升级到对应核心流程追踪。 +- [ ] 每个 critical Slice ID 已映射到 evidence、Diagram IDs 和 Document Section;没有 missing/blocked slice 被评分平均掉。 +- [ ] callback、consumer、retry、DLQ、compensation、reconciliation 和 job 没有因拆成其他 topic 而离开核心闭环。 +- [ ] 若为 critical / important flow,已包含架构/边界图(Mermaid flowchart 或 ASCII 架构图)和 ASCII 状态机/决策图。 +- [ ] 若存在真实状态语义,状态图说明了核心状态变化、异常恢复、重试/补偿/回滚。 +- [ ] 若为 critical / important flow,已包含 Timeline / 时序图(必填,优先 Mermaid sequenceDiagram),并讲清流程怎么跑。 - [ ] 流程讲解时顺带解释涉及的数据模型、状态对象、消息、记录和配置。 - [ ] 没有用 stacked box diagram / 阶段堆叠图替代复杂流程说明。 - [ ] 阶段说明覆盖 owner module、input、action、state read/write、output、evidence。 @@ -178,4 +220,6 @@ Caller Gateway Service A Service B / DB Consumer - [ ] 关键 claim 带文件路径、符号/配置键、调用方向或数据流方向。 - [ ] 涉及 wallet、billing、quota、apikey、order、balance、message retry 时,已说明一致性、幂等、事务边界、补偿和对账。 - [ ] 涉及 gateway/runtime 时,已说明 route matching、header 透传、auth/rate-limit、timeout/retry/upstream、日志字段。 +- [ ] Complexity Signals 已逐项判断,需要的 recovery/lineage/transaction/async/decision/ERD/runtime/troubleshooting 图已经存在并映射 Slice IDs。 +- [ ] 若为 critical / important flow,Completeness Hard Gate 已 PASS;它是质量门,不是新的 Human Gate。 - [ ] 没有 `<...>`、TBD、TODO、待补充、空 required row、泛泛“看代码/see code”证据。 diff --git a/templates/onboarding-db/module.md b/templates/onboarding-db/module.md index 1619075..5ce8764 100644 --- a/templates/onboarding-db/module.md +++ b/templates/onboarding-db/module.md @@ -12,7 +12,7 @@ ## 2. 图解 -这一节先把模块讲“看得见”。状态图优先。模块文档默认必须有:模块图 / 架构/边界图、ASCII 状态机图、Timeline / 时序图、必要的流程图。 +这一节先把模块讲“看得见”。模块图 / 架构/边界图用于解释真实边界;只有模块具有状态生命周期时才使用 ASCII 状态机图,具有时间顺序或数据移动时才使用 Timeline / 时序图。不要为 stateless 模块编造状态机。 - 模块图 / 架构/边界图:讲模块在哪、边界在哪、谁调用它、它调用谁、状态归谁管。 - ASCII 状态机图:讲核心对象、请求或任务的状态怎么变,异常怎么恢复。 @@ -21,7 +21,7 @@ - 复杂模块图、复杂原理图、复杂示例图优先用 ASCII,方便标注状态、分支、数据对象和恢复路径。 - 每张图必须带讲解。讲解至少说明:这张图要看什么、表达了什么结论、涉及哪些数据对象/状态字段/消息/配置、对应的代码证据在哪里。 - 流程讲解时顺带解释涉及的数据模型、状态对象、消息、记录和配置,不要先孤立列清单。 -- 只有确实没有有意义的时间顺序、状态阶段或数据流动时,才允许在 onboarding-spec 的 Exemptions 写明豁免原因。 +- Diagram Plan 记录真实 complexity signals 和选图理由;没有相应语义时直接标明 not-applicable,不需要伪造状态或时间线。 - 不要把复杂逻辑画成 stacked box diagram / 阶段堆叠图;不要用无边界、无状态、无数据对象的 `A-->B-->C` 糊弄主图。 ### 2.1 模块图 / 架构/边界图 @@ -37,7 +37,7 @@ flowchart LR App --> Outbound["Response / Event / Callback"] ``` -### 2.2 ASCII 状态机图 +### 2.2 ASCII 状态机图(有状态行为时必填) ```text [Initial] @@ -52,7 +52,7 @@ flowchart LR [Committed] ``` -### 2.3 Timeline / 时序图(必填) +### 2.3 Timeline / 时序图(有顺序或数据移动时必填) 用它讲清楚核心流程按时间怎么发生;一边讲步骤,一边点名本步骤读写的数据模型 / 状态字段 / 消息对象 / 配置。 @@ -247,8 +247,8 @@ flowchart TD ## 15. 自检 - [ ] 已包含模块图 / 架构/边界图(Mermaid flowchart 或 ASCII 架构图)。 -- [ ] 已包含 ASCII 状态机图,说明核心对象/请求/任务的状态变化和异常恢复。 -- [ ] 已包含 Timeline / 时序图(必填,优先 Mermaid sequenceDiagram),并讲清核心流程怎么跑。 +- [ ] 模块存在状态生命周期时已包含 ASCII 状态机图;stateless 时已写明证据和 not-applicable 理由,没有编造状态。 +- [ ] 模块存在时间顺序或数据移动时已包含 Timeline / 时序图(优先 Mermaid sequenceDiagram);不适用时已说明依据。 - [ ] 每张图必须带讲解,说明图要看什么、表达什么结论、涉及哪些数据对象/状态字段/消息/配置和代码证据。 - [ ] 图解说明顺带解释涉及的数据模型、状态对象、消息、记录和配置。 - [ ] 工作原理与示例包含关键机制、原理机制图 / 示例图;示例 1 和示例 2 均有证据,每个示例都带示例图。无法提供时说明豁免原因、证据缺口和置信度。 diff --git a/templates/onboarding-db/onboarding-spec.md b/templates/onboarding-db/onboarding-spec.md index 9800536..5ea17ed 100644 --- a/templates/onboarding-db/onboarding-spec.md +++ b/templates/onboarding-db/onboarding-spec.md @@ -25,6 +25,24 @@ | Flow | Why Required | Planned Path | Participants | Risk | Evidence | |---|---|---|---|---|---| +## Core Flow Selection + +| Flow ID | Criticality | Business Outcome | Success / Failure Terminals | Variants / Recovery | Selection | Selection Reason / Impact | Evidence Chain | +|---|---|---|---|---|---|---|---| + +Rules: + +- 每个 `critical` / `important` candidate 必须 planned,或给出 evidence-backed deferred reason、impact、missing evidence 和 next action。 +- Focused scope 必须写清边界,不能对外宣称 whole-project newcomer-ready。 +- `accepted` / `pending` / `processing` 只有在它本身是业务终态时才允许作为 terminal。 + +## Flow Slice Plan + +只对 planned `critical` / `important` flow 强制完整 slice 追踪。Supporting flow 只有承担核心状态、外部副作用或恢复责任时才升级。 + +| Flow ID | Required Slice IDs | Main / Branch / Failure / Recovery Scope | State / Side Effect / Terminal Owned | Required Evidence | Planned Document | +|---|---|---|---|---|---| + ## DDD Plan | Bounded Context | Aggregates / Entities | Domain Services | Repositories | External Adapters | Evidence | @@ -62,47 +80,48 @@ - Mermaid flowchart / sequenceDiagram 是普通流程图和时序图的首选格式;ASCII 文本图 / 纯文本线框图用于状态机、复杂原理图、复杂示例图和 Mermaid 不够清楚的局部。 - 状态图优先;状态机/决策图默认用 ASCII。 - 不要把复杂流程画成 stacked box diagram / 阶段堆叠图。 -- 每个正式文档至少规划架构/边界图和 ASCII 状态图 / 状态机图。 -- 模块文档默认必须规划架构/边界图、ASCII 状态图、Timeline / 时序图。 -- 流程文档默认必须规划架构/边界图、ASCII 状态图、Timeline / 时序图。 -- Every planned module/flow doc must have Required Architecture/Boundary Diagram, Required ASCII State Diagram, and Required Timeline/Sequence Diagram in the Diagram Plan, unless it is listed under Exemptions with a concrete reason. +- `critical` / `important` 核心流程默认规划 Core Flow Overview / Boundary、ASCII State Machine / Decision、Timeline / Sequence 三类互补图。 +- Timeline / Sequence 是单个核心流程的 primary per-flow narrative;Overview/Boundary 负责 scope、owner、branch 和 terminal;State 图负责 transition、invalid path 和 recovery。 +- 模块和其他内容文档按真实边界、状态、时间、数据、决策和恢复语义选图;stateless glossary、静态配置清单和纯索引不强制状态图。 +- Every planned `critical` / `important` flow doc must have Required Architecture/Boundary Diagram, Required ASCII State Diagram, and Required Timeline/Sequence Diagram in the Diagram Plan. Module docs apply these views when their behavior has real boundary, state, timing, or data-movement semantics. - Mermaid flowchart / sequenceDiagram 可用于架构/边界图、普通流程图和 Timeline / 时序图。 - 每张图必须带讲解:说明图要看什么、表达什么结论、涉及哪些数据对象/状态字段/消息/配置和代码证据。 - 按需求选择 diagram type: - 架构/边界图:模块在哪、模块边界、输入输出、组件关系、Redis Key 布局、钱包类型结构、网关/DB/MQ 依赖;可用 Mermaid flowchart 或 ASCII。 - ASCII 状态机/决策图:状态流转、校验分支、加锁/Lua/Kafka/Rollback、重试/补偿。 - - Timeline / 时序图:模块/流程默认必填,用于讲清流程按时间怎么跑、谁先读写什么、哪个阶段引用哪些数据模型;优先用 Mermaid sequenceDiagram。 + - Timeline / 时序图:critical/important flow 默认必填;module 在存在时间顺序或数据移动时使用,用于讲清流程按时间怎么跑、谁先读写什么、哪个阶段引用哪些数据模型;优先用 Mermaid sequenceDiagram。 - ASCII 泳道图:可选项,用于责任归属、跨角色协作、多系统所有权。 - Timeline Diagram:T1/T2/T3 故障恢复、回调重试、对账窗口、延迟一致性。 -- Timeline / 时序图对 module / flow 默认必填。只有当前模块/流程确实没有有意义的时间顺序、状态阶段或数据流动时,才允许在 Exemptions 写明豁免原因。 +- Timeline / 时序图对 `critical` / `important` flow 默认必填。Module 只有存在有意义的时间顺序、状态阶段或数据流动时才使用;stateless topic 不需要为豁免固定图组而制造额外流程。 - ASCII 原理机制图 / 示例图用于 `工作原理与示例`:当算法、路由选择、配额/计费、锁/事务/缓存/MQ 协作、provider 协议转换等不容易直接读懂时必须规划。 - 普通 Mermaid flowchart 可以作为架构/边界图或普通流程图;不要用 `A-->B-->C` 这种无边界、无状态、无数据对象的图当主图。 -- 每个 planned content doc 都必须写明图类型;不要只给 module / flow 做图,overview / domain / jobs / infra / deploy / change-guide 只要是正式内容文档也要遵守双图要求。 +- 每个 planned content doc 都必须写明真实复杂度信号和选择的图;不要为没有状态或时间语义的主题编造图。 ## Diagram Plan -| Topic | Planned Path | Content Doc? | Required Architecture/Boundary Diagram | Required State Diagram | Required Timeline / Sequence | Principle / Example Diagram | Optional Swimlane / Timeline Window | Exemption / Reason | -|---|---|---|---|---|---|---|---|---| +| Topic / Flow ID | Planned Path | Content Doc? | Complexity Signals | Required Architecture/Boundary Diagram | Required State Diagram | Required Timeline / Sequence | Conditional Diagram Types | Covered Slice IDs | Exemption / Reason | +|---|---|---|---|---|---|---|---|---|---| ## Exemptions -默认只有控制/审计文档可以豁免双图要求,例如 `onboarding-spec.md`、`onboarding-tasks.md`、`coverage-matrix.md`、`batch-review.md`、`08-review/evidence-graph.md`、`08-review/open-questions.md`、`08-review/human-review-summary.md`。正式内容文档如需豁免,必须在上方 Diagram Plan 写清楚原因。 +控制/审计文档默认无图,例如 `onboarding-spec.md`、`onboarding-tasks.md`、`coverage-matrix.md`、`batch-review.md`、`08-review/evidence-graph.md`、`08-review/open-questions.md`、`08-review/human-review-summary.md`。其他内容文档按 Complexity Signals 选择真实需要的图;核心流程缺少默认图组时必须写清原因,非流程 stateless topic 不需要伪造豁免。 ## Quality Gates -- 模块文档必须包含:图解、架构/边界图、ASCII 状态图、Timeline / 时序图、Bounded Context / DDD 视角、核心用例、领域模型、数据对象、信息传递、状态流转、失败模式、工作原理与示例、验证/排障、代码证据和变更指南。 -- 流程文档必须包含:架构/边界图、ASCII 状态图/状态机图、Timeline / 时序图、阶段、数据流转、状态变化、示例、失败路径、排障路径、变更指南、代码证据。 +- 模块文档必须包含:Bounded Context / DDD 视角、核心用例、领域模型、数据对象、信息传递、状态流转、失败模式、工作原理与示例、验证/排障、代码证据和变更指南;架构/边界、状态、Timeline / 时序图按真实语义使用,不为 stateless topic 编造图。 +- critical / important 流程文档必须包含:架构/边界图、ASCII 状态图/状态机图、Timeline / 时序图、阶段、数据流转、状态变化、示例、失败路径、排障路径、变更指南、代码证据。supporting flow 按真实语义保持轻量,承担核心状态、副作用或恢复责任时必须升级。 +- `critical` / `important` flow 必须先通过 Completeness Hard Gate:所有关键 Slice ID 映射到 evidence、Diagram IDs 和 document sections,缺失或 blocked slice 不能被评分平均掉。 - 全部正式文档默认使用中文;代码符号、路径、命令、API、配置键、错误信息和第三方产品名保持原文。 - 低于 4/5 的 topic 不能标记 `newcomer-ready`。 - 关键 claim 必须带文件路径、符号/配置键、调用方向或数据流方向。 - 示例必须来自真实测试、fixture、API contract、日志、配置或代码构造对象;推断示例必须标明 inferred。 - 涉及 wallet、billing、quota、apikey、order、balance、message retry 的流程必须讲清楚一致性、幂等、事务边界、补偿和对账。 -- 涉及 apikey / token / credential 的模块必须讲清楚 key 生成、hash/加密存储、脱敏展示、权限 scope、过期/轮换/吊销、泄露处置、审计日志。 +- 涉及 apikey / token / credential 的模块或流程必须讲清楚 key 生成、hash/加密存储、脱敏展示、权限 scope、过期/轮换/吊销、泄露处置、审计日志。 - 提交前必须清除 `<...>`、TBD、TODO、待补充、空 required row、泛泛“看代码/see code”证据。 ## Batch Plan -| Batch | Scope | Outputs | Required Evidence | Review Gate | +| Batch | Scope | Outputs | Required Evidence | Agent Review Check | |---|---|---|---|---| ## Human Confirmation Questions @@ -115,6 +134,8 @@ Spec acceptance authorizes Onboarding Tasks only; it does not authorize formal d - [ ] Evidence Graph 已有具体 code/config evidence。 - [ ] Module / Flow Plan 没有无证据候选项。 +- [ ] Core Flow Selection 覆盖每个 `critical` / `important` candidate,planned/deferred 决定及影响清楚。 +- [ ] 每个 planned core flow 已列出 business terminals、variants、Required Slice IDs 和 Evidence Chain。 - [ ] 全量 planned docs 和 execution scope 已明确。 - [ ] Gateway / Runtime、consistency / idempotency 风险已决定是否适用。 - [ ] 人类已确认可以创建 `onboarding-tasks.md`,随后再审查 Full Execution Gate。 diff --git a/templates/onboarding-db/onboarding-tasks.md b/templates/onboarding-db/onboarding-tasks.md index 29a26f8..69e7e64 100644 --- a/templates/onboarding-db/onboarding-tasks.md +++ b/templates/onboarding-db/onboarding-tasks.md @@ -8,8 +8,8 @@ Onboarding Spec 的确认只授权创建本文件。人类另外确认本文件 ## Batch : -| Task | Output | Evidence Required | Quality Gate | Status | -|---|---|---|---|---| +| Task | Output | Flow ID | Required Slice IDs | Required / Conditional Diagram IDs | Evidence Required | Completeness Hard Gate | Quality Score Target | Status | +|---|---|---|---|---|---|---|---|---| ## Current Batch Notes @@ -24,7 +24,10 @@ Onboarding Spec 的确认只授权创建本文件。人类另外确认本文件 - [ ] 当前 Onboarding Tasks 和 execution scope 已被人类单独确认。 - [ ] 全量 planned docs 和 execution scope 已明确。 - [ ] 每个输出文件都有 evidence required。 -- [ ] 每个 module / flow task 都要求架构/边界图、ASCII 状态图、Timeline / 时序图。 +- [ ] 每个 `critical` / `important` flow task 都要求 Core Flow Overview / Boundary、ASCII State Machine / Decision、Timeline / Sequence;module 和其他内容 task 按真实语义选图。 +- [ ] 每个 `critical` / `important` core-flow task 都列出 Flow ID、Required Slice IDs、business terminals、default/conditional diagrams 和 evidence mapping。 +- [ ] Supporting flow 只有承担核心状态、外部副作用或恢复责任时才升级为完整 slice task。 +- [ ] Completeness Hard Gate 在质量评分前执行;missing/blocked critical slice 不能标记 newcomer-ready。 - [ ] 普通流程图和时序图优先用 Mermaid flowchart / sequenceDiagram;状态机、复杂原理图和复杂示例图优先用 ASCII。 - [ ] 每个 task 都要求清除 `<...>`、TBD、TODO、待补充、空 required row、泛泛“看代码/see code”证据。 - [ ] 涉及 gateway / runtime 或一致性 / 幂等 / 补偿的 task 已明确质量门禁。 diff --git a/templates/product.md b/templates/product.md index 12e7e62..003b2e7 100644 --- a/templates/product.md +++ b/templates/product.md @@ -17,6 +17,27 @@ Summary: - +## Accepted Concept References + +Concept Foundation Status: accepted | concept-foundation-not-needed +Source Requirement: +Effective Concept Source: +Not-Needed Reason: + +| Concept ID | Canonical Name | Product Brief Use | Source Definition / Trace | +|---|---|---|---| +| C-EXAMPLE | | | requirement.md#concept-definitions / TRACE-01 | + +Rules: +- cite accepted Concept IDs; do not redefine identity, lifecycle, relationships, invariants, state, terminal meaning, or product fact ownership +- if product semantics must change, return to the source Requirements Discussion and reopen Concept Foundation + +## Requirement Product Model Coverage + +| Requirement Model ID | Concept IDs | Feature Product Journey / Story | Coverage | Notes | +|---|---|---|---|---| +| PM-01 / PERM-01 / FLOW-01 / STATE-01 / EX-01 | C-EXAMPLE | | in-scope / out-of-scope / not-applicable | | + ## Problem Statement ## Target Users / Actors @@ -94,7 +115,8 @@ Notes: ## Terminology - ``: - - Meaning in this feature: + - Accepted Concept ID: + - Feature display wording / alias: - Promote to project Domain Language: yes | no | pending ## Open Product Questions diff --git a/templates/requirement-set-README.md b/templates/requirement-set-README.md index 2ca4b1e..66a5402 100644 --- a/templates/requirement-set-README.md +++ b/templates/requirement-set-README.md @@ -24,6 +24,16 @@ Exit Condition: One-line summary: +## Effective Concept Foundation + +Status: not-recorded | candidate | accepted | reopened | concept-foundation-not-needed +Effective Source: requirement.md | YYYY-MM-DD-concept-foundation-.md | none +Previous Source: none +Last Confirmed: YYYY-MM-DD | none +Reason / Reopen Trigger: + +This block is an effective-status pointer only. Concept definitions and the Requirement Product Model stay in the referenced human-reviewed source file. + ## Delivery Phases Use this table when the requirement is too large for one feature or has MVP / later-phase scope. @@ -73,6 +83,7 @@ Decisions caused by this requirement: - Requirement: - Requirement Document: +- Concept Foundation Follow-ups: - Prototype: - Feedback: - Screenshots: diff --git a/templates/root-AGENTS.md b/templates/root-AGENTS.md index b584abf..74d633e 100644 --- a/templates/root-AGENTS.md +++ b/templates/root-AGENTS.md @@ -6,7 +6,7 @@ The agent is responsible for steering the workflow. Do not wait for the human to Guidance language should follow this project's language preference. Keep stable artifact names, stage names, and file paths in English, such as `agent-loop`, `Requirement Archive`, `Feature Spec`, `Feature Auto-Loop`, `Task Auto-Run`, `project.md`, and `requirements/`. - + ## Bootstrap Protocol Before development work: @@ -24,12 +24,13 @@ Before development work: 10. If recent development bypassed `agent-loop`, route to Re-Adopt Agent Loop Project before new feature work. 11. Operational Support Guard: if the human asks to test, run, deploy, switch account/config/model/provider, check quota/rate limits, diagnose production, arrange rollout, or use existing code to solve an operational problem, default to read-only code/process analysis. Do not create a feature, edit code, change config, deploy, or run destructive commands unless the human confirms feature implementation or an operational change. If unclear, ask whether they want feature implementation or help using current project functionality. 12. If the human reports a bug, regression, post-close correction, field/schema/algorithm/API change, test failure, screenshot issue, QA/user feedback, or "small tweak", route to Feature Follow-up / Flow-back before creating a new feature or editing code, but only after project memory exists or Project Entry has routed through Init Project / Project Entry Scan. +12a. If the human explicitly requests closed feature archive/rehydrate maintenance, classify `feature-archive-maintenance`, require reliable memory, and run the read-only Feature Monthly Archive scan. Stop at the exact plan SHA-256 Batch Human Gate before apply; auto modes do not authorize archive or rehydrate. 13. Run Stage Helper Capability Scan for the current stage only after the `agent-loop` controller is active or unavailable/load-failed: inspect whether the current Agent CLI exposes Superpowers or other helper skills/plugins before using fallback stage guidance. 14. Check for the nearest directory-level `AGENTS.md` when working in a subdirectory. 15. Classify the current `agent-loop` stage and recommend exactly one next action. - + ## Agent Ownership - Own workflow diagnosis, sequencing, implementation, verification, review, drift checks, and project-memory updates. @@ -43,14 +44,15 @@ Before development work: - For non-trivial confirmations, present a table-first Human Review Summary before asking approval. - + ## Message Intent Guard Before project-state routing, classify the latest human message intent. - `chat`: ordinary discussion, rule questions, status questions, or design talk. Answer or discuss only; do not create requirement sets, feature workspaces, tasks, tests, or plans. -- `requirements-discussion`: the human is exploring product needs, business goals, capability ideas, constraints, tradeoffs, or user scenarios without authorizing implementation. Requirements discussion must shape demand through Brainstorm / Clarify into a human-reviewed requirement document under `.agent-loop/requirements/` before feature construction. +- `requirements-discussion`: the human is exploring product needs, business goals, capability ideas, constraints, tradeoffs, or user scenarios without authorizing implementation. Requirements discussion must shape demand through Brainstorm / Clarify into a human-reviewed requirement document under `.agent-loop/requirements/` before feature construction. When product concept identity, lifecycle, relationship, state, ownership, or fact meaning can change downstream models, use the internal Concept Foundation Gate before flow/state/product-data modeling. - `project-skill-management`: the human asks to turn a repeatable project workflow into a project-local skill, or to update, disable, or deprecate one. Route to Project Skill Creation / Update after reliable Project Entry/memory. +- `feature-archive-maintenance`: the human explicitly asks to archive closed feature directories by month or rehydrate archived history. Route to Feature Monthly Archive after reliable memory; scan is read-only. - `feature-request`: the human explicitly asks to implement, build, change behavior, or start work from accepted requirements. Route through normal agent-loop feature workflow. Message intent is not permanent. If chat turns into product demand, reclassify as requirements-discussion. If chat turns into a proposal/design-note request, reclassify as `proposal-doc`. If chat turns into implementation, operational support, follow-up, deferred work, or project-skill management, reclassify and route accordingly. If the human explicitly wants discussion without documentation, keep `chat`. @@ -58,10 +60,10 @@ Message intent is not permanent. If chat turns into product demand, reclassify a If unclear whether the human wants chat or requirements discussion, ask whether to keep discussing or shape the topic into a requirements document. If unclear whether the human wants requirements discussion or implementation, ask whether to form a requirements document first or start feature construction. - + ## Workflow Stage Map -Use this after Bootstrap Protocol and Message Intent Guard. Select exactly one next stage from the current human signal and project state. When multiple signals match, apply this first-match order: Safety Stop -> Remote Discovery -> Memory Recovery -> Active Feature Guard -> Blocker Resolution -> Intent Routing -> Normal Stage Continuation. After selecting a stage, load the matching `references/...` file from the `agent-loop` skill package before acting. This map is navigation only; do not treat root `AGENTS.md` as the detailed stage procedure. +Use this after Bootstrap Protocol and Message Intent Guard. Select exactly one next stage from the current human signal and project state. When multiple signals match, apply this first-match order: Safety Stop -> Remote Discovery -> Memory Recovery -> Feature Archive Maintenance -> Active Feature Guard -> Blocker Resolution -> Intent Routing -> Normal Stage Continuation. After selecting a stage, load the matching `references/...` file from the `agent-loop` skill package before acting. This map is navigation only; do not treat root `AGENTS.md` as the detailed stage procedure. | Signal | Next Stage | Load From agent-loop Skill | |---|---|---| @@ -69,8 +71,9 @@ Use this after Bootstrap Protocol and Message Intent Guard. Select exactly one n | No reliable `.agent-loop/` memory and meaningful existing code | Project Entry Scan | `references/project-entry-scan.md`, `references/project-guidance.md` | | Human or local evidence says the source of truth is remote, SSH, container, tunnel, or devcontainer | Remote Project Discovery | `references/remote-project-discovery.md` | | Existing memory conflicts with code reality, or work bypassed the loop | Re-Adopt Agent Loop Project | `references/recovery-and-backfill.md` | +| Human explicitly requests historical closed feature archive or rehydrate | Feature Monthly Archive | `references/stage-guides.md`, `references/artifact-rules.md`, `references/feature-follow-up.md` | | Human asks to turn a repeatable workflow into a project-local skill, or manage an existing one | Project Skill Creation / Update | `references/project-skills.md`, `references/skill-routing.md`, `references/external-skill-adapters.md` | -| Product need, business goal, scope, constraint, scenario, or phased delivery is still being shaped | Requirements Discussion | `references/requirement-management.md`; also `references/requirement-product-grill.md` when terminology, roles, flows, exceptions, prior behavior, or decision signals are unclear | +| Product need, business goal, scope, constraint, scenario, concept identity/lifecycle, or phased delivery is still being shaped | Requirements Discussion | `references/requirement-management.md`; also `references/requirement-product-grill.md` for Concept Foundation, terminology, roles, flows, exceptions, prior behavior, or decision signals | | Human confirms recording, accepting, or deferring a requirement source | Requirement Archive | `references/requirement-management.md`, `references/stage-guides.md` | | Human requests durable newcomer docs after Project Entry or reliable memory exists | Evidence-Graph + DDD Onboarding | `references/onboarding-knowledge-base.md` | | Accepted requirement needs shared business-flow, domain, data, architecture, reliability, performance, security, or cross-feature design before feature specification | Decision & Design If Needed | `references/project-decisions.md` | @@ -99,28 +102,32 @@ Use this after Bootstrap Protocol and Message Intent Guard. Select exactly one n | Ordinary question or discussion has no artifact or implementation intent | Chat Entry | `references/runtime.md` only when intent is unclear; otherwise answer without creating workflow artifacts | - + ## Gate Modes - Strict Mode is the default: ask before and after every stage. - Feature Auto-Loop is allowed only after a passed Requirement Checklist, accepted Feature Spec, and explicit human enablement. It may continue Agent-ready downstream stages through implementation, testing, fixing, review, drift, status update, and final report. - Task Auto-Run is allowed only after an accepted task/story plan and explicit human enablement. It runs Analyze Consistency before executing that task/story through TDD, implementation, verification, bug fixing, review, drift check, task status update, and final report. - Auto modes never authorize Project Skill Creation / Update or execution. Gate 1 is required before skill files; the Execution Gate is required for each invocation. A named-skill/concrete-scope request satisfies it only when the emitted execution summary adds no undisclosed action or effect. +- Auto modes never authorize Feature Monthly Archive or rehydrate. Only the read-only scan may run before the exact plan SHA-256 Batch Human Gate. - If repeated confirmations slow the human down, proactively explain Feature Auto-Loop and Task Auto-Run, then ask before enabling either mode. - + ## Required Stops Stop and ask when: - scope changes or requirements are ambiguous +- an unresolved Concept Foundation remains `candidate` / `reopened`, or a downstream product/feature artifact would redefine accepted product semantics +- a requirement-driven ADR has an unresolved Effective Requirement Snapshot, incomplete source-wide Requirement Model Scope Inventory / Technical Landing Trace, or `Upstream Compatibility: review-required` - product, design, architecture, security, data, approval, or public-interface decisions are unclear - a stage would modify human original requirements - tests require unavailable infrastructure - drift check needs human approval - security/data boundaries or broad architecture would change - repeated verification fails +- Feature Monthly Archive has a stale/unconfirmed plan, unsupported reference, flat/month collision, missing locator target, incomplete `.archive-txn`, or would rely on manual directory movement - unrelated dirty work blocks progress - a new dependency, migration, destructive operation, credential, external service, or long-lived boundary directory is needed - Complex Artifact Mode detail directories (`tasks/`, `tests/`, `plans/`) would be created or the feature would switch from simple to complex artifact mode @@ -136,7 +143,7 @@ Stop and ask when: Auto modes do not bypass these stops. - + ## Completion Rules - Before completion claims, run fresh verification and record evidence. @@ -146,9 +153,10 @@ Auto modes do not bypass these stops. - Before recommending or performing feature close, run Feature Close Review, drift check, and project memory update when long-term facts changed. - Feature Close Review requires feature-level Spec Review. Standards Review is required for large projects, broad diffs, boundary/security/data changes, architecture changes, or human request. - When a feature references accepted Decision & Design records, Feature Close Review and Feature Completion Check must verify assigned design slices and evidence; divergence returns to Decision & Design / Drift Check before close. +- For requirement-driven ADRs, completion also requires source-wide scope accounting, current upstream compatibility, and Requirement Model Technical Landing Trace coverage for every assigned Design Slice. - + ## Submit And Commit Rules - Submit, commit, PR, merge, release, and publish require explicit human confirmation after diff, verification, review, drift, and unrelated-change checks. @@ -160,28 +168,31 @@ Auto modes do not bypass these stops. - For the `agent-loop` skill repository itself, use `(v): ` and a 3-7 bullet body for meaningful commits. - + ## Project Memory And Artifacts - Resolve project-memory and feature paths relative to the active memory root: `.agent-loop/` by default, or legacy `agent-loop/` for the current run. - Keep task status, execution evidence, feature notes, and project memory inside `.agent-loop/`. - Keep long-term project memory in `.agent-loop/project.md` or enterprise `.agent-loop/project/*.md`. Root `AGENTS.md` should only summarize startup-critical facts that every Agent CLI needs immediately. - Keep original human materials in requirement set directories under `.agent-loop/requirements/`, or reference original paths when the human declines copying. +- Keep accepted Concept Foundation and Requirement Product Model detail in the effective human-reviewed requirement source named by the requirement README. After archive, preserve prior sources and record confirmed semantic changes in an append-only Concept Foundation follow-up or a new requirement set. Feature `product.md` and `spec.md` cite accepted Concept/Model IDs and `Effective Concept Source` instead of redefining product identity, lifecycle, relationships, invariants, states, or fact meaning. +- Keep the Effective Requirement Snapshot, source-wide Requirement Model Scope Inventory, Requirement Model Technical Landing Trace, coverage evidence, compatibility judgment, Human Review Evidence, and triggered operational landing inside the existing Human-gated ADR; do not create a default mapping artifact or let ADR redefine product semantics. - Do not create new flat files directly under `.agent-loop/requirements/`; group requirements, prototypes, feedback, screenshots, recordings, links, and follow-up notes for the same intake/topic together. - For complex requirements, suggest `Delivery Phases` in the requirement set `README.md` before feature construction when the human needs to confirm staged delivery. A phase is a human-readable delivery slice, not a feature workspace, task, or plan. - Future/deferred work and backlog items belong in requirement sets and optional `requirements/INDEX.md`, not in `project.md`. Do not edit `requirement.md` or other source files for lifecycle/status updates. - Keep durable producer-consumer interface handoffs in feature `contracts.md` and optional `contracts/` details. Keep temporary subagent assignments in `handoffs/`. +- Keep stable Feature IDs and archive location history in root `.agent-loop/features/archive.md`. Archive state is not feature lifecycle; active / blocked / paused features stay flat, and an archived owner must rehydrate before reopened execution. - Keep project-local reusable capabilities in `.agent-loop/skills/INDEX.md` and `.agent-loop/skills//`; only active skills may load, and every invocation remains Human-gated. - Do not write task logs, feature progress, raw requirements, temporary plans, or test transcripts into `AGENTS.md`. - + ## Architecture Snapshot Add only startup-critical architecture boundaries that every future agent must know immediately. If the project has `ARCHITECTURE.md`, this block may use `source:ARCHITECTURE.md` instead. Keep details in `ARCHITECTURE.md`, `.agent-loop/project.md`, or enterprise `.agent-loop/project/*.md`. - + ## Directory Guidance - Directory-level `AGENTS.md` files are for long-lived boundary rules only. @@ -189,7 +200,7 @@ Add only startup-critical architecture boundaries that every future agent must k - Do not create directory-level `AGENTS.md` for ordinary component, utility, temporary, or feature implementation folders. - + ## Project Commands ```bash @@ -199,7 +210,7 @@ Add only startup-critical architecture boundaries that every future agent must k ``` - + ## Project-Specific Hard Constraints Add only stable constraints that every future agent must know at startup. diff --git a/templates/spec.md b/templates/spec.md index 796e295..5fcb71d 100644 --- a/templates/spec.md +++ b/templates/spec.md @@ -23,6 +23,24 @@ Summary: - +## Accepted Concept References + +Concept Foundation Status: accepted | concept-foundation-not-needed +Source Requirement: +Effective Concept Source: + +| Concept ID | Canonical Name | Feature Use | Source Definition / Trace | +|---|---|---|---| +| C-EXAMPLE | | | requirement.md#concept-definitions / TRACE-01 | + +Do not redefine accepted product meaning in this Feature Spec. Return to Requirements Discussion if identity, owner, relationship, lifecycle, invariant, state, terminal meaning, or product fact must change. + +## Requirement Product Model Trace + +| Requirement Model ID | Concept / Action / Flow / State IDs | Feature Behavior / Story | Acceptance / Verification Direction | Coverage | +|---|---|---|---|---| +| PM-01 | C-EXAMPLE / PERM-01 / CMD-01 / FLOW-01 / STATE-01 / EX-01 | US1 | | planned / covered / out-of-scope | + ## Maintenance Fix Scope Use this section only when `Feature Type: maintenance-fix`. diff --git a/tests/checker_test_support.py b/tests/checker_test_support.py new file mode 100644 index 0000000..ca3d27b --- /dev/null +++ b/tests/checker_test_support.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def run_checker( + script: str, *args: str, cwd: Path | None = None +) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [sys.executable, str(ROOT / script), *map(str, args)], + cwd=str(cwd or ROOT), + check=False, + capture_output=True, + text=True, + encoding="utf-8", + ) + + +def combined_output(result: subprocess.CompletedProcess[str]) -> str: + return (result.stdout + result.stderr).strip() diff --git a/tests/feature_archive_test_support.py b/tests/feature_archive_test_support.py new file mode 100644 index 0000000..2f39bed --- /dev/null +++ b/tests/feature_archive_test_support.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +import hashlib +import json +import os +import subprocess +import sys +from dataclasses import dataclass +from pathlib import Path + +from tests.checker_test_support import ROOT + + +@dataclass +class ArchiveWorkspace: + project_root: Path + + @property + def memory_root(self) -> Path: + return self.project_root / ".agent-loop" + + @property + def features_root(self) -> Path: + return self.memory_root / "features" + + def write(self, relative: str, content: str) -> Path: + path = self.project_root / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8", newline="\n") + return path + + def feature( + self, feature_id: str, *, status: str = "closed", close_complete: bool = True + ) -> Path: + root = self.features_root / feature_id + root.mkdir(parents=True, exist_ok=True) + self.write( + f".agent-loop/features/{feature_id}/spec.md", + f"# Feature Spec\n\nStatus: {status}\n", + ) + task_status = "done" if close_complete else "in-progress" + self.write( + f".agent-loop/features/{feature_id}/tasks.md", + f"# Tasks\n\n- Status: {task_status}\n", + ) + close = ( + "## Feature Close Review\n\nDecision: pass\n\n" + "## Drift Check\n\nDecision: no-drift\n\n" + "## Close Record\n\nClosed At: 2026-05-20\nHuman Decision: confirmed\n\n" + f"## Archive Readiness\n\nClosed At: 2026-05-20\nDelivered Summary: completed {feature_id}\n" + "Verification: complete\nFeature Close Review: complete\nDrift: resolved\n" + "Project Memory Impact: none\nOpen Follow-up: none\n" + if close_complete + else "## Close Record\n\nClosed At:\n" + ) + self.write(f".agent-loop/features/{feature_id}/notes.md", f"# Notes\n\n{close}") + self.write( + f".agent-loop/features/{feature_id}/tests.md", + "# Tests\n\nStatus: passing\n", + ) + self.write( + f".agent-loop/features/{feature_id}/plan.md", + "# Plan\n\nStatus: closed\n", + ) + return root + + +def run_archive_command( + script: str, *args: str, env: dict[str, str] | None = None +) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [sys.executable, str(ROOT / "scripts" / script), *map(str, args)], + cwd=str(ROOT), + check=False, + capture_output=True, + text=True, + encoding="utf-8", + env={**os.environ, **(env or {})}, + ) + + +def tree_snapshot(root: Path) -> dict[str, str]: + return { + path.relative_to(root).as_posix(): hashlib.sha256(path.read_bytes()).hexdigest() + for path in sorted(root.rglob("*")) + if path.is_file() + } + + +def json_output(result: subprocess.CompletedProcess[str]) -> dict[str, object]: + return json.loads(result.stdout) diff --git a/tests/fixtures/adr-technical-landing/invalid-empty-landing/decision.md b/tests/fixtures/adr-technical-landing/invalid-empty-landing/decision.md new file mode 100644 index 0000000..7eb66b5 --- /dev/null +++ b/tests/fixtures/adr-technical-landing/invalid-empty-landing/decision.md @@ -0,0 +1,44 @@ +# ADR-9002: Empty Landing Fixture + +Status: accepted + +## Effective Requirement Snapshot + +Effective Concept Source: requirement.md +Concept Foundation Status: accepted +Accepted Concept IDs: C-FIXTURE-SUBJECT, C-FIXTURE-OPERATOR +Accepted Requirement Model IDs: REL-FIXTURE-LINK, CMD-FIXTURE-ACTION, EVT-FIXTURE-RECORDED, FLOW-FIXTURE-01, STATE-FIXTURE-01, PM-FIXTURE-FACT +Upstream Compatibility: current +Last Compatibility Check: 2026-07-13 + +## Requirement Model Technical Landing Trace + +| Requirement Model Ref | Accepted Meaning / Constraint | Disposition | Technical Landing | Preserved Invariant | Design Slice | Verification | +|---|---|---|---|---|---|---| +| REL-FIXTURE-LINK | source relationship reference | landed | | relationship invariant | DS-FIXTURE-REL | verification target | +| CMD-FIXTURE-ACTION | source command reference | landed | generic handler | command invariant | DS-FIXTURE-CMD | verification target | +| EVT-FIXTURE-RECORDED | source event reference | landed | generic adapter | event invariant | DS-FIXTURE-EVT | verification target | +| FLOW-FIXTURE-01 | source flow reference | landed | generic coordinator | flow invariant | DS-FIXTURE-FLOW | verification target | +| STATE-FIXTURE-01 | source state reference | landed | generic representation | state invariant | DS-FIXTURE-STATE | verification target | +| PM-FIXTURE-FACT | source product-model reference | landed | generic representation | product invariant | DS-FIXTURE-PM | verification target | + +## Operational Landing Trigger Assessment + +| Concern | Status | Reason / Trigger Evidence | Detail Section If Triggered | +|---|---|---|---| +| Migration / Backfill | not-triggered | no representation change | none | + +## Design Slice Coverage + +| Design Slice ID | Required Capability / Rule | Owning Feature(s) | Verification | Coverage Status | +|---|---|---|---|---| +| DS-FIXTURE-REL | relationship landing | feature | verification target | planned | +| DS-FIXTURE-CMD | command landing | feature | verification target | planned | +| DS-FIXTURE-EVT | event landing | feature | verification target | planned | +| DS-FIXTURE-FLOW | flow landing | feature | verification target | planned | +| DS-FIXTURE-STATE | state landing | feature | verification target | planned | +| DS-FIXTURE-PM | product-model landing | feature | verification target | planned | + +## Coverage Hard Gate + +- [x] Coverage claimed complete diff --git a/tests/fixtures/adr-technical-landing/invalid-missing-coverage/decision.md b/tests/fixtures/adr-technical-landing/invalid-missing-coverage/decision.md new file mode 100644 index 0000000..3f39b21 --- /dev/null +++ b/tests/fixtures/adr-technical-landing/invalid-missing-coverage/decision.md @@ -0,0 +1,42 @@ +# ADR-9001: Missing Coverage Fixture + +Status: accepted + +## Effective Requirement Snapshot + +Effective Concept Source: requirement.md +Concept Foundation Status: accepted +Accepted Concept IDs: C-FIXTURE-SUBJECT, C-FIXTURE-OPERATOR +Accepted Requirement Model IDs: REL-FIXTURE-LINK, CMD-FIXTURE-ACTION, EVT-FIXTURE-RECORDED, FLOW-FIXTURE-01, STATE-FIXTURE-01, PM-FIXTURE-FACT +Upstream Compatibility: current +Last Compatibility Check: 2026-07-13 + +## Requirement Model Technical Landing Trace + +| Requirement Model Ref | Accepted Meaning / Constraint | Disposition | Technical Landing | Preserved Invariant | Design Slice | Verification | +|---|---|---|---|---|---|---| +| REL-FIXTURE-LINK | source relationship reference | landed | generic boundary | relationship invariant | DS-FIXTURE-REL | verification target | +| CMD-FIXTURE-ACTION | source command reference | landed | generic handler | command invariant | DS-FIXTURE-CMD | verification target | +| EVT-FIXTURE-RECORDED | source event reference | landed | generic adapter | event invariant | DS-FIXTURE-EVT | verification target | +| FLOW-FIXTURE-01 | source flow reference | landed | generic coordinator | flow invariant | DS-FIXTURE-FLOW | verification target | +| STATE-FIXTURE-01 | source state reference | landed | generic representation | state invariant | DS-FIXTURE-STATE | verification target | + +## Operational Landing Trigger Assessment + +| Concern | Status | Reason / Trigger Evidence | Detail Section If Triggered | +|---|---|---|---| +| Migration / Backfill | not-triggered | no representation change | none | + +## Design Slice Coverage + +| Design Slice ID | Required Capability / Rule | Owning Feature(s) | Verification | Coverage Status | +|---|---|---|---|---| +| DS-FIXTURE-REL | relationship landing | feature | verification target | planned | +| DS-FIXTURE-CMD | command landing | feature | verification target | planned | +| DS-FIXTURE-EVT | event landing | feature | verification target | planned | +| DS-FIXTURE-FLOW | flow landing | feature | verification target | planned | +| DS-FIXTURE-STATE | state landing | feature | verification target | planned | + +## Coverage Hard Gate + +- [x] Coverage claimed complete diff --git a/tests/fixtures/adr-technical-landing/invalid-reopened-source/README.md b/tests/fixtures/adr-technical-landing/invalid-reopened-source/README.md new file mode 100644 index 0000000..4fb312f --- /dev/null +++ b/tests/fixtures/adr-technical-landing/invalid-reopened-source/README.md @@ -0,0 +1,9 @@ +# Requirement Set: Reopened Source Fixture + +## Effective Concept Foundation + +Status: reopened +Effective Source: requirement.md +Previous Source: none +Last Confirmed: 2026-07-13 +Reason / Reopen Trigger: accepted meaning changed diff --git a/tests/fixtures/adr-technical-landing/invalid-reopened-source/requirement.md b/tests/fixtures/adr-technical-landing/invalid-reopened-source/requirement.md new file mode 100644 index 0000000..12a05f0 --- /dev/null +++ b/tests/fixtures/adr-technical-landing/invalid-reopened-source/requirement.md @@ -0,0 +1,3 @@ +# Requirement Document: Reopened Source Fixture + +Concept Foundation Status: reopened diff --git a/tests/fixtures/adr-technical-landing/invalid-review-required/decision.md b/tests/fixtures/adr-technical-landing/invalid-review-required/decision.md new file mode 100644 index 0000000..ddbbc03 --- /dev/null +++ b/tests/fixtures/adr-technical-landing/invalid-review-required/decision.md @@ -0,0 +1,12 @@ +# ADR-9003: Review Required Fixture + +Status: accepted + +## Effective Requirement Snapshot + +Effective Concept Source: requirement.md +Concept Foundation Status: accepted +Accepted Concept IDs: C-FIXTURE-SUBJECT, C-FIXTURE-OPERATOR +Accepted Requirement Model IDs: REL-FIXTURE-LINK +Upstream Compatibility: review-required +Last Compatibility Check: 2026-07-13 diff --git a/tests/fixtures/adr-technical-landing/invalid-unaccepted-source/README.md b/tests/fixtures/adr-technical-landing/invalid-unaccepted-source/README.md new file mode 100644 index 0000000..794605f --- /dev/null +++ b/tests/fixtures/adr-technical-landing/invalid-unaccepted-source/README.md @@ -0,0 +1,9 @@ +# Requirement Set: Unaccepted Source Fixture + +## Effective Concept Foundation + +Status: candidate +Effective Source: requirement.md +Previous Source: none +Last Confirmed: none +Reason / Reopen Trigger: blocking meaning remains open diff --git a/tests/fixtures/adr-technical-landing/invalid-unaccepted-source/requirement.md b/tests/fixtures/adr-technical-landing/invalid-unaccepted-source/requirement.md new file mode 100644 index 0000000..b26c1a7 --- /dev/null +++ b/tests/fixtures/adr-technical-landing/invalid-unaccepted-source/requirement.md @@ -0,0 +1,3 @@ +# Requirement Document: Unaccepted Source Fixture + +Concept Foundation Status: candidate diff --git a/tests/fixtures/adr-technical-landing/valid-not-needed/README.md b/tests/fixtures/adr-technical-landing/valid-not-needed/README.md new file mode 100644 index 0000000..a2346dc --- /dev/null +++ b/tests/fixtures/adr-technical-landing/valid-not-needed/README.md @@ -0,0 +1,9 @@ +# Requirement Set: Not-Needed Validator Fixture + +## Effective Concept Foundation + +Status: concept-foundation-not-needed +Effective Source: requirement.md +Previous Source: none +Last Confirmed: 2026-07-13 +Reason / Reopen Trigger: no product semantic model is required for this technical decision input diff --git a/tests/fixtures/adr-technical-landing/valid-not-needed/decision.md b/tests/fixtures/adr-technical-landing/valid-not-needed/decision.md new file mode 100644 index 0000000..9c8e7b8 --- /dev/null +++ b/tests/fixtures/adr-technical-landing/valid-not-needed/decision.md @@ -0,0 +1,44 @@ +# ADR-9100: Not-Needed Validator Fixture + +Status: proposed +Allowed Status: proposed | accepted | superseded | deprecated + +## Effective Requirement Snapshot + +Effective Concept Source: requirement.md +Concept Foundation Status: concept-foundation-not-needed +Accepted Concept IDs: none +Accepted Requirement Model IDs: none +Upstream Compatibility: current +Last Compatibility Check: 2026-07-13 +Trace Applicability: not-applicable +Trace Not-Applicable Reason: the accepted requirement introduces no product model requiring technical landing + +## Operational Landing Trigger Assessment + +| Concern | Status | Reason / Trigger Evidence | Detail Section If Triggered | +|---|---|---|---| +| Migration / Backfill | not-triggered | no durable representation changes | none | +| Compatibility | not-triggered | no interface or version compatibility changes | none | +| Rollout / Cutover | not-triggered | no staged activation or traffic changes | none | +| Rollback / Reversibility | not-triggered | no new reversal mechanism is required | none | + +## Design Slice Coverage + +| Design Slice ID | Required Capability / Rule | Owning Feature(s) | Verification | Coverage Status | +|---|---|---|---|---| +| DS-NOT-NEEDED-01 | preserve the accepted technical constraint | fixture feature | fixture verification target | planned | + +## Coverage Hard Gate + +- [x] Effective Concept Source resolves and matches the reviewed source +- [x] Concept Foundation Status is accepted or reasoned `concept-foundation-not-needed` +- [x] Upstream Compatibility is `current` +- [x] Every source Requirement Model ID has an explicit scope disposition, or trace is reasoned not-applicable +- [x] Every in-scope Accepted Requirement Model ID has exactly one disposition +- [x] Every `landed` row has Technical Landing, Preserved Invariant, Design Slice, and Verification +- [x] Every `covered-by-accepted-decision` and `feature-local` row names an existing or explicitly planned verified owner path +- [x] Every `not-applicable`, deferred, and out-of-scope item is visible in Human Review Summary +- [x] Every implementation-bearing technical rule is represented in Design Slice Coverage +- [x] No required Design Slice is `unassigned` +- [x] No unresolved product-semantic blocker remains diff --git a/tests/fixtures/adr-technical-landing/valid-not-needed/requirement.md b/tests/fixtures/adr-technical-landing/valid-not-needed/requirement.md new file mode 100644 index 0000000..863b251 --- /dev/null +++ b/tests/fixtures/adr-technical-landing/valid-not-needed/requirement.md @@ -0,0 +1,8 @@ +# Requirement Document: Not-Needed Validator Fixture + +Concept Foundation Status: concept-foundation-not-needed +Not-Needed Reason: the request changes no product concept, lifecycle, relationship, state, or product fact meaning + +## Technical Decision Inputs + +- A shared technical constraint still requires one project-level decision. diff --git a/tests/fixtures/adr-technical-landing/valid/README.md b/tests/fixtures/adr-technical-landing/valid/README.md new file mode 100644 index 0000000..1c7463c --- /dev/null +++ b/tests/fixtures/adr-technical-landing/valid/README.md @@ -0,0 +1,9 @@ +# Requirement Set: Validator Fixture + +## Effective Concept Foundation + +Status: accepted +Effective Source: requirement.md +Previous Source: none +Last Confirmed: 2026-07-13 +Reason / Reopen Trigger: initial accepted source diff --git a/tests/fixtures/adr-technical-landing/valid/decision.md b/tests/fixtures/adr-technical-landing/valid/decision.md new file mode 100644 index 0000000..cb69988 --- /dev/null +++ b/tests/fixtures/adr-technical-landing/valid/decision.md @@ -0,0 +1,86 @@ +# ADR-9000: Validator Fixture + +Status: accepted +Allowed Status: proposed | accepted | superseded | deprecated + +## Effective Requirement Snapshot + +Effective Concept Source: requirement.md +Concept Foundation Status: accepted +Accepted Concept IDs: C-FIXTURE-SUBJECT, C-FIXTURE-OPERATOR +Accepted Requirement Model IDs: REL-FIXTURE-LINK, PERM-FIXTURE-ACTION, CMD-FIXTURE-ACTION, EVT-FIXTURE-RECORDED, FLOW-FIXTURE-01, STATE-FIXTURE-01, PM-FIXTURE-FACT, EX-FIXTURE-01 +Upstream Compatibility: current +Last Compatibility Check: 2026-07-13 +Trace Applicability: required + +## Requirement Model Scope Inventory + +| Requirement Model Ref | Scope Disposition | Owner / Reason | +|---|---|---| +| REL-FIXTURE-LINK | in-scope | ADR-9000 | +| PERM-FIXTURE-ACTION | in-scope | ADR-9000 | +| CMD-FIXTURE-ACTION | in-scope | ADR-9000 | +| EVT-FIXTURE-RECORDED | in-scope | ADR-9000 | +| FLOW-FIXTURE-01 | in-scope | ADR-9000 | +| STATE-FIXTURE-01 | in-scope | ADR-9000 | +| PM-FIXTURE-FACT | in-scope | ADR-9000 | +| EX-FIXTURE-01 | in-scope | ADR-9000 | + +## Requirement Model Technical Landing Trace + +| Requirement Model Ref | Accepted Meaning / Constraint | Disposition | Technical Landing | Preserved Invariant | Design Slice | Verification | +|---|---|---|---|---|---|---| +| REL-FIXTURE-LINK | source relationship reference | landed | FixtureProtocol boundary | relationship invariant reference | DS-FIXTURE-REL | fixture verification target | +| PERM-FIXTURE-ACTION | source permission reference | landed | FixtureProtocol authorization boundary | permission invariant reference | DS-FIXTURE-PERM | fixture permission verification | +| CMD-FIXTURE-ACTION | source command reference | covered-by-accepted-decision | decisions/8999-shared.md (ADR-8999) | accepted decision invariant | none | accepted decision evidence | +| EVT-FIXTURE-RECORDED | source event reference | feature-local | features/fixture/spec.md | feature-local invariant | none | feature verification target | +| FLOW-FIXTURE-01 | source flow reference | not-applicable | reason: outside this coherent decision boundary | none | none | none | +| STATE-FIXTURE-01 | source state reference | landed | FixtureStore state representation | state invariant reference | DS-FIXTURE-STATE | fixture verification target | +| PM-FIXTURE-FACT | source product-model reference | landed | FixtureStore fact representation | product invariant reference | DS-FIXTURE-PM | fixture verification target | +| EX-FIXTURE-01 | source exception reference | landed | FixtureProtocol failure boundary | exception invariant reference | DS-FIXTURE-EX | fixture exception verification | + +## Operational Landing Trigger Assessment + +| Concern | Status | Reason / Trigger Evidence | Detail Section If Triggered | +|---|---|---|---| +| Migration / Backfill | triggered | existing representation changes | Migration Detail | +| Compatibility | not-triggered | no external compatibility change | none | +| Rollout / Cutover | not-triggered | no staged activation change | none | +| Rollback / Reversibility | not-triggered | existing reversal path remains valid | none | + +## Triggered Operational Landing + +### Migration Detail + +Fixture migration evidence. + +## Design Slice Coverage + +| Design Slice ID | Required Capability / Rule | Owning Feature(s) | Verification | Coverage Status | +|---|---|---|---|---| +| DS-FIXTURE-REL | relationship landing | fixture feature | fixture verification target | planned | +| DS-FIXTURE-PERM | permission boundary landing | fixture feature | fixture permission verification | planned | +| DS-FIXTURE-STATE | state landing | fixture feature | fixture verification target | planned | +| DS-FIXTURE-PM | product-model landing | fixture feature | fixture verification target | planned | +| DS-FIXTURE-EX | exception boundary landing | fixture feature | fixture exception verification | planned | + +## Coverage Hard Gate + +- [x] Effective Concept Source resolves and matches the reviewed source +- [x] Concept Foundation Status is accepted or reasoned `concept-foundation-not-needed` +- [x] Upstream Compatibility is `current` +- [x] Every source Requirement Model ID has an explicit scope disposition, or trace is reasoned not-applicable +- [x] Every in-scope Accepted Requirement Model ID has exactly one disposition +- [x] Every `landed` row has Technical Landing, Preserved Invariant, Design Slice, and Verification +- [x] Every `covered-by-accepted-decision` and `feature-local` row names an existing or explicitly planned verified owner path +- [x] Every `not-applicable`, deferred, and out-of-scope item is visible in Human Review Summary +- [x] Every implementation-bearing technical rule is represented in Design Slice Coverage +- [x] No required Design Slice is `unassigned` +- [x] No unresolved product-semantic blocker remains + +## Human Review Evidence + +Decision: accepted +Confirmed By: fixture human reviewer +Confirmed At: 2026-07-13 +Evidence: explicit fixture acceptance recorded after structural preflight validation diff --git a/tests/fixtures/adr-technical-landing/valid/decisions/8999-shared.md b/tests/fixtures/adr-technical-landing/valid/decisions/8999-shared.md new file mode 100644 index 0000000..05452db --- /dev/null +++ b/tests/fixtures/adr-technical-landing/valid/decisions/8999-shared.md @@ -0,0 +1,5 @@ +# ADR-8999: Shared Fixture Decision + +Status: accepted + +This accepted fixture decision owns the shared command landing used by ADR-9000. diff --git a/tests/fixtures/adr-technical-landing/valid/decisions/9001-follow-up.md b/tests/fixtures/adr-technical-landing/valid/decisions/9001-follow-up.md new file mode 100644 index 0000000..7344dc9 --- /dev/null +++ b/tests/fixtures/adr-technical-landing/valid/decisions/9001-follow-up.md @@ -0,0 +1,5 @@ +# ADR-9001: Proposed Follow-Up Fixture Decision + +Status: proposed + +This proposed fixture decision is the explicit future owner for one out-of-scope source model. diff --git a/tests/fixtures/adr-technical-landing/valid/features/fixture/spec.md b/tests/fixtures/adr-technical-landing/valid/features/fixture/spec.md new file mode 100644 index 0000000..a3ddacd --- /dev/null +++ b/tests/fixtures/adr-technical-landing/valid/features/fixture/spec.md @@ -0,0 +1,5 @@ +# Feature Spec: Fixture + +Status: proposed + +This fixture Feature Spec owns the explicitly feature-local event behavior. diff --git a/tests/fixtures/adr-technical-landing/valid/requirement.md b/tests/fixtures/adr-technical-landing/valid/requirement.md new file mode 100644 index 0000000..9b5f314 --- /dev/null +++ b/tests/fixtures/adr-technical-landing/valid/requirement.md @@ -0,0 +1,53 @@ +# Requirement Document: Validator Fixture + +Concept Foundation Status: accepted + +## Concept Definitions + +| Concept ID | Canonical Name | Definition | +|---|---|---| +| C-FIXTURE-SUBJECT | FixtureSubject | accepted subject meaning | +| C-FIXTURE-OPERATOR | FixtureOperator | accepted operator meaning | + +## Concept Relationships + +| Relationship ID | From Concept ID | To Concept ID | Meaning | +|---|---|---|---| +| REL-FIXTURE-LINK | C-FIXTURE-OPERATOR | C-FIXTURE-SUBJECT | accepted relationship | + +## Role / Permission Matrix + +| Permission Rule ID | Role Concept ID | Product Object Concept ID | Permission Meaning | +|---|---|---|---| +| PERM-FIXTURE-ACTION | C-FIXTURE-OPERATOR | C-FIXTURE-SUBJECT | operator may perform the accepted fixture action | + +## Commands / Events + +| Action ID | Actor / Producer Concept ID | Target Concept ID | Meaning | +|---|---|---|---| +| CMD-FIXTURE-ACTION | C-FIXTURE-OPERATOR | C-FIXTURE-SUBJECT | perform_fixture_action | +| EVT-FIXTURE-RECORDED | C-FIXTURE-SUBJECT | C-FIXTURE-SUBJECT | accepted outcome event | + +## Primary Business Flow + +| Flow Step ID | Actor Concept ID | Action ID | Product Meaning | +|---|---|---|---| +| FLOW-FIXTURE-01 | C-FIXTURE-OPERATOR | CMD-FIXTURE-ACTION | accepted flow step | + +## Product State Model + +| State Model ID | Concept IDs | Trigger Action ID | Product Meaning | +|---|---|---|---| +| STATE-FIXTURE-01 | C-FIXTURE-SUBJECT | EVT-FIXTURE-RECORDED | accepted state transition | + +## Requirement Product Model + +| Product Model ID | Concept IDs | Kind | Product Meaning / Invariant | +|---|---|---|---| +| PM-FIXTURE-FACT | C-FIXTURE-SUBJECT | fact | accepted product invariant | + +## Exception Paths + +| Scenario ID | Concept / State / Action IDs | Trigger | Expected Handling | +|---|---|---|---| +| EX-FIXTURE-01 | C-FIXTURE-SUBJECT / STATE-FIXTURE-01 / CMD-FIXTURE-ACTION | fixture action cannot complete | preserve the accepted subject state and expose the failure | diff --git a/tests/fixtures/concept-foundation/invalid-detached-model/product.md b/tests/fixtures/concept-foundation/invalid-detached-model/product.md new file mode 100644 index 0000000..b40402e --- /dev/null +++ b/tests/fixtures/concept-foundation/invalid-detached-model/product.md @@ -0,0 +1,15 @@ +# Product Brief: Approval Instance + +Concept Foundation Status: accepted + +## Accepted Concept References + +| Concept ID | Canonical Name | Product Brief Use | Source Definition / Trace | +|---|---|---|---| +| C-APPROVAL-INSTANCE | Approval Instance | workflow | TRACE-01 | + +## Requirement Product Model Coverage + +| Requirement Model ID | Concept IDs | Feature Product Journey / Story | +|---|---|---| +| PM-99 | C-APPROVAL-INSTANCE | approval journey | diff --git a/tests/fixtures/concept-foundation/invalid-detached-model/requirement.md b/tests/fixtures/concept-foundation/invalid-detached-model/requirement.md new file mode 100644 index 0000000..c908d51 --- /dev/null +++ b/tests/fixtures/concept-foundation/invalid-detached-model/requirement.md @@ -0,0 +1,61 @@ +# Requirement: Approval Instance + +Concept Foundation Status: accepted +Not-Needed Reason: n/a + +## Concept Foundation + +Approval Instance is distinct from the approval action. + +## Concept Definitions + +| Concept ID | Canonical Name | +|---|---| +| C-APPROVAL-INSTANCE | Approval Instance | +| C-REVIEWER | Reviewer | + +## Concept Relationships + +| Relationship ID | From Concept ID | Relationship | To Concept ID | +|---|---|---|---| +| REL-01 | C-REVIEWER | reviews | C-APPROVAL-INSTANCE | + +### Human Confirmation + +- Confirmed Concept IDs: C-APPROVAL-INSTANCE, C-REVIEWER + +## Role / Permission Matrix + +| Role Concept ID | Product Object Concept ID | Read | +|---|---|---| +| C-REVIEWER | C-APPROVAL-INSTANCE | yes | + +## Commands / Events + +| Action ID | Actor / Producer Concept ID | Target Concept ID | +|---|---|---| +| CMD-APPROVE | C-REVIEWER | C-APPROVAL-INSTANCE | + +## Primary Business Flow + +| Flow Step ID | Actor Concept ID | Action ID | Input / Target Concept IDs | +|---|---|---|---| +| FLOW-01 | C-REVIEWER | CMD-APPROVE | C-APPROVAL-INSTANCE | + +## Product State Model + +| State Model ID | State-bearing Concept ID | Action / Event ID | +|---|---|---| +| STATE-01 | C-APPROVAL-INSTANCE | CMD-APPROVE | + +## Requirement Product Model + +| Product Model ID | Concept IDs | Product Fact Meaning | +|---|---|---| +| PM-01 | C-APPROVAL-INSTANCE, C-REVIEWER | accepted approval lifecycle | + +## Concept-To-Product Traceability + +| Trace ID | Accepted Concept IDs | Derived Model IDs / Sections | +|---|---|---| +| TRACE-01 | C-APPROVAL-INSTANCE, C-REVIEWER | REL-01 / CMD-APPROVE / FLOW-01 / STATE-01 / PM-01 | diff --git a/tests/fixtures/concept-foundation/invalid-detached-model/spec.md b/tests/fixtures/concept-foundation/invalid-detached-model/spec.md new file mode 100644 index 0000000..11dbf52 --- /dev/null +++ b/tests/fixtures/concept-foundation/invalid-detached-model/spec.md @@ -0,0 +1,15 @@ +# Feature Spec: Approval Instance + +Concept Foundation Status: accepted + +## Accepted Concept References + +| Concept ID | Canonical Name | Feature Use | Source Definition / Trace | +|---|---|---|---| +| C-APPROVAL-INSTANCE | Approval Instance | workflow | TRACE-01 | + +## Requirement Product Model Trace + +| Requirement Model ID | Concept / Action / Flow / State IDs | Feature Behavior / Story | +|---|---|---| +| PM-01 | C-APPROVAL-INSTANCE / CMD-APPROVE / FLOW-01 / STATE-01 | US1 | diff --git a/tests/fixtures/concept-foundation/invalid-unaccepted/product.md b/tests/fixtures/concept-foundation/invalid-unaccepted/product.md new file mode 100644 index 0000000..5c72591 --- /dev/null +++ b/tests/fixtures/concept-foundation/invalid-unaccepted/product.md @@ -0,0 +1,9 @@ +# Product Brief: Unaccepted Approval + +Concept Foundation Status: candidate + +## Accepted Concept References + +| Concept ID | Canonical Name | Product Brief Use | Source Definition / Trace | +|---|---|---|---| +| C-APPROVAL | Approval | workflow | requirement.md | diff --git a/tests/fixtures/concept-foundation/invalid-unaccepted/requirement.md b/tests/fixtures/concept-foundation/invalid-unaccepted/requirement.md new file mode 100644 index 0000000..ad92654 --- /dev/null +++ b/tests/fixtures/concept-foundation/invalid-unaccepted/requirement.md @@ -0,0 +1,8 @@ +# Requirement: Unaccepted Approval + +Concept Foundation Status: candidate +Not-Needed Reason: n/a + +## Concept Foundation + +Candidate concepts exist, but the human has not confirmed their meaning. diff --git a/tests/fixtures/concept-foundation/invalid-unaccepted/spec.md b/tests/fixtures/concept-foundation/invalid-unaccepted/spec.md new file mode 100644 index 0000000..05d83ef --- /dev/null +++ b/tests/fixtures/concept-foundation/invalid-unaccepted/spec.md @@ -0,0 +1,9 @@ +# Feature Spec: Unaccepted Approval + +Concept Foundation Status: candidate + +## Accepted Concept References + +| Concept ID | Canonical Name | Feature Use | Source Definition / Trace | +|---|---|---|---| +| C-APPROVAL | Approval | workflow | requirement.md | diff --git a/tests/fixtures/onboarding-core-flow/invalid-detached-trace/batch-review.md b/tests/fixtures/onboarding-core-flow/invalid-detached-trace/batch-review.md new file mode 100644 index 0000000..85bef8b --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/invalid-detached-trace/batch-review.md @@ -0,0 +1,5 @@ +# Batch Review + +| Flow ID | Result | +|---|---| +| CF-ORDER-PAYMENT | PASS | diff --git a/tests/fixtures/onboarding-core-flow/invalid-detached-trace/coverage-matrix.md b/tests/fixtures/onboarding-core-flow/invalid-detached-trace/coverage-matrix.md new file mode 100644 index 0000000..319e7f9 --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/invalid-detached-trace/coverage-matrix.md @@ -0,0 +1,5 @@ +# Coverage Matrix + +| Flow ID | Result | +|---|---| +| CF-ORDER-PAYMENT | PASS | diff --git a/tests/fixtures/onboarding-core-flow/invalid-detached-trace/evidence-graph.md b/tests/fixtures/onboarding-core-flow/invalid-detached-trace/evidence-graph.md new file mode 100644 index 0000000..bbf7037 --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/invalid-detached-trace/evidence-graph.md @@ -0,0 +1,5 @@ +# Evidence Graph + +| Flow ID | Flow | Business Outcome | Criticality | Success Terminal | Failure Terminals | Selection | Selection Reason | +|---|---|---|---|---|---|---|---| +| CF-ORDER-PAYMENT | payment | final result | critical | PAID | FAILED / UNKNOWN | planned | core payment closure | diff --git a/tests/fixtures/onboarding-core-flow/invalid-detached-trace/flow.md b/tests/fixtures/onboarding-core-flow/invalid-detached-trace/flow.md new file mode 100644 index 0000000..0133fe6 --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/invalid-detached-trace/flow.md @@ -0,0 +1,9 @@ +# Flow: detached trace + +Flow ID: CF-ORDER-PAYMENT + +| Slice ID | Action | Evidence | Diagram IDs | Document Section | Coverage Status | +|---|---|---|---|---|---| +| CF-ORDER-PAYMENT/S01 | recover | `payment/` | D-RECOVERY | §99 | covered | + +The document intentionally has no D-RECOVERY diagram and no section 99. diff --git a/tests/fixtures/onboarding-core-flow/invalid-detached-trace/onboarding-spec.md b/tests/fixtures/onboarding-core-flow/invalid-detached-trace/onboarding-spec.md new file mode 100644 index 0000000..047bb5b --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/invalid-detached-trace/onboarding-spec.md @@ -0,0 +1,5 @@ +# Onboarding Spec + +| Flow ID | Required Slice IDs | Required Diagram IDs | +|---|---|---| +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S01 | D-RECOVERY | diff --git a/tests/fixtures/onboarding-core-flow/invalid-detached-trace/onboarding-tasks.md b/tests/fixtures/onboarding-core-flow/invalid-detached-trace/onboarding-tasks.md new file mode 100644 index 0000000..d51bfea --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/invalid-detached-trace/onboarding-tasks.md @@ -0,0 +1,5 @@ +# Onboarding Tasks + +| Flow ID | Required Slice IDs | Required Diagram IDs | +|---|---|---| +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S01 | D-RECOVERY | diff --git a/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/batch-review.md b/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/batch-review.md new file mode 100644 index 0000000..23350e2 --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/batch-review.md @@ -0,0 +1,5 @@ +# Batch Review + +| Flow ID | Required Slice IDs | Result | +|---|---|---| +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S01-S07 | PASS | diff --git a/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/coverage-matrix.md b/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/coverage-matrix.md new file mode 100644 index 0000000..0113c17 --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/coverage-matrix.md @@ -0,0 +1,5 @@ +# Coverage Matrix + +| Flow ID | Required Slice IDs | Result | +|---|---|---| +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S01-S07 | PASS | diff --git a/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/evidence-graph.md b/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/evidence-graph.md new file mode 100644 index 0000000..cba550d --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/evidence-graph.md @@ -0,0 +1,7 @@ +# Evidence Graph + +## Core Flow Inventory + +| Flow ID | Flow | Business Outcome | Criticality | Trigger / Entry | Success Terminal | Failure Terminals | Variants / Branches | Recovery Responsibility | Evidence Chain | Selection | Selection Reason | +|---|---|---|---|---|---|---|---|---|---|---|---| +| CF-ORDER-PAYMENT | order payment | final payment result | critical | CreateOrder | PAID | FAILED / CANCELLED / PAYMENT_UNKNOWN | webhook / retry / reconcile / cancel | retry and reconcile | handler -> webhook -> reconcile | planned | core revenue flow | diff --git a/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/flow.md b/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/flow.md new file mode 100644 index 0000000..9da3216 --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/flow.md @@ -0,0 +1,14 @@ +# Flow: incomplete payment + +Flow ID: CF-ORDER-PAYMENT + +| Slice ID | Action | Evidence | Diagram IDs | Document Section | Coverage Status | +|---|---|---|---|---|---| +| CF-ORDER-PAYMENT/S01 | create | `order/handler.go#Create` | D-BOUNDARY | §3 | covered | +| CF-ORDER-PAYMENT/S02 | processing | `payment/client.go#Create` | D-SEQUENCE | §4 | covered | +| CF-ORDER-PAYMENT/S03 | webhook | `payment/webhook.go#Handle` | D-SEQUENCE | §5 | covered | +| CF-ORDER-PAYMENT/S04 | duplicate | `payment/webhook.go#event_id` | D-STATE | §6 | covered | +| CF-ORDER-PAYMENT/S05 | retry/DLQ | `config/kafka.yaml#retry` | D-RECOVERY | §7 | covered | +| CF-ORDER-PAYMENT/S06 | reconcile | `order/reconcile.go#Run` | D-RECOVERY | §8 | covered | + +The required cancel/race recovery slice S07 is intentionally absent. diff --git a/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/onboarding-spec.md b/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/onboarding-spec.md new file mode 100644 index 0000000..873d569 --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/onboarding-spec.md @@ -0,0 +1,5 @@ +# Onboarding Spec + +| Flow ID | Required Slice IDs | +|---|---| +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S01, CF-ORDER-PAYMENT/S02, CF-ORDER-PAYMENT/S03, CF-ORDER-PAYMENT/S04, CF-ORDER-PAYMENT/S05, CF-ORDER-PAYMENT/S06, CF-ORDER-PAYMENT/S07 | diff --git a/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/onboarding-tasks.md b/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/onboarding-tasks.md new file mode 100644 index 0000000..f2b401f --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/invalid-missing-recovery/onboarding-tasks.md @@ -0,0 +1,5 @@ +# Onboarding Tasks + +| Flow ID | Required Slice IDs | +|---|---| +| CF-ORDER-PAYMENT | CF-ORDER-PAYMENT/S01, CF-ORDER-PAYMENT/S02, CF-ORDER-PAYMENT/S03, CF-ORDER-PAYMENT/S04, CF-ORDER-PAYMENT/S05, CF-ORDER-PAYMENT/S06, CF-ORDER-PAYMENT/S07 | diff --git a/tests/fixtures/onboarding-core-flow/valid-deferred/batch-review.md b/tests/fixtures/onboarding-core-flow/valid-deferred/batch-review.md new file mode 100644 index 0000000..2c58532 --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/valid-deferred/batch-review.md @@ -0,0 +1,5 @@ +# Batch Review + +| Flow ID | Status | Gap | Next Action | +|---|---|---|---| +| CF-REFUND | deferred | provider callback evidence | inspect refund worker | diff --git a/tests/fixtures/onboarding-core-flow/valid-deferred/coverage-matrix.md b/tests/fixtures/onboarding-core-flow/valid-deferred/coverage-matrix.md new file mode 100644 index 0000000..6a72a42 --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/valid-deferred/coverage-matrix.md @@ -0,0 +1,5 @@ +# Coverage Matrix + +| Flow ID | Status | Result | Next Action | +|---|---|---|---| +| CF-REFUND | blocked-by-unknown | blocked-by-unknown | collect callback evidence | diff --git a/tests/fixtures/onboarding-core-flow/valid-deferred/evidence-graph.md b/tests/fixtures/onboarding-core-flow/valid-deferred/evidence-graph.md new file mode 100644 index 0000000..138e267 --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/valid-deferred/evidence-graph.md @@ -0,0 +1,5 @@ +# Evidence Graph + +| Flow ID | Flow | Business Outcome | Criticality | Success Terminal | Failure Terminals | Selection | Selection Reason | +|---|---|---|---|---|---|---|---| +| CF-REFUND | refund | return captured funds | important | REFUNDED | REFUND_FAILED / MANUAL_REVIEW | deferred | impact=refund onboarding unavailable; missing=evidence for provider callback; next=inspect refund webhook and retry worker | diff --git a/tests/fixtures/onboarding-core-flow/valid-deferred/onboarding-spec.md b/tests/fixtures/onboarding-core-flow/valid-deferred/onboarding-spec.md new file mode 100644 index 0000000..f51312b --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/valid-deferred/onboarding-spec.md @@ -0,0 +1,5 @@ +# Onboarding Spec + +| Flow ID | Selection | Selection Reason / Impact | +|---|---|---| +| CF-REFUND | deferred | missing provider callback evidence; keep whole-project readiness blocked | diff --git a/tests/fixtures/onboarding-core-flow/valid-deferred/onboarding-tasks.md b/tests/fixtures/onboarding-core-flow/valid-deferred/onboarding-tasks.md new file mode 100644 index 0000000..db461d7 --- /dev/null +++ b/tests/fixtures/onboarding-core-flow/valid-deferred/onboarding-tasks.md @@ -0,0 +1,5 @@ +# Onboarding Tasks + +| Flow ID | Status | Next Action | +|---|---|---| +| CF-REFUND | deferred | inspect refund webhook and retry worker | diff --git a/tests/test_adr_requirement_model_trace.py b/tests/test_adr_requirement_model_trace.py new file mode 100644 index 0000000..db77c2e --- /dev/null +++ b/tests/test_adr_requirement_model_trace.py @@ -0,0 +1,344 @@ +from __future__ import annotations + +import re +import shutil +import tempfile +import unittest +from pathlib import Path + +from tests.checker_test_support import ROOT, combined_output, run_checker + + +SCRIPT = "scripts/check-adr-requirement-model-trace.py" +FIXTURES = ROOT / "tests/fixtures/adr-technical-landing" +VALID = FIXTURES / "valid" +NOT_NEEDED = FIXTURES / "valid-not-needed" + + +class AdrRequirementModelTraceTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.readme = (VALID / "README.md").read_text(encoding="utf-8") + cls.source = (VALID / "requirement.md").read_text(encoding="utf-8") + cls.decision = (VALID / "decision.md").read_text(encoding="utf-8") + + def run_documents( + self, + readme: str, + source: str, + decision: str, + *, + fixture: Path = VALID, + bom_crlf: bool = False, + workspace_root: str | None = None, + ): + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) / "fixture" + shutil.copytree(fixture, root) + paths = [] + for name, content in ( + ("README.md", readme), + ("requirement.md", source), + ("decision.md", decision), + ): + path = root / name + if bom_crlf: + path.write_bytes(("\ufeff" + content.replace("\n", "\r\n")).encode("utf-8")) + else: + path.write_text(content, encoding="utf-8") + paths.append(str(path)) + args = [*paths] + if workspace_root is not None: + args.append(workspace_root.replace("{root}", str(root))) + return run_checker(SCRIPT, *args) + + def assert_accepted( + self, readme: str, source: str, decision: str, expected: str + ) -> None: + result = self.run_documents(readme, source, decision) + self.assertEqual(result.returncode, 0, combined_output(result)) + self.assertIn(expected, result.stdout) + + def assert_rejected( + self, readme: str, source: str, decision: str, expected: str + ) -> None: + result = self.run_documents(readme, source, decision) + self.assertEqual(result.returncode, 1, combined_output(result)) + self.assertIn(expected, combined_output(result)) + + def run_archived_feature_owner( + self, + *, + include_index: bool = True, + row_month: str = "2026-05", + row_state: str = "archived", + row_path: str | None = None, + planned: bool = False, + ): + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) / "fixture" + shutil.copytree(VALID, root) + feature_id = "2026-05-08-login" + archived = root / "features" / "2026-05" / feature_id + archived.parent.mkdir(parents=True) + (root / "features" / "fixture").rename(archived) + spec = archived / "spec.md" + spec.write_text( + re.sub( + r"^Status: .*$", + "Status: closed", + spec.read_text(encoding="utf-8"), + flags=re.MULTILINE, + ), + encoding="utf-8", + ) + owner = f"features/2026-05/{feature_id}/spec.md" + if planned: + owner = f"planned:{owner}" + decision = self.decision.replace("features/fixture/spec.md", owner) + (root / "README.md").write_text(self.readme, encoding="utf-8") + (root / "requirement.md").write_text(self.source, encoding="utf-8") + (root / "decision.md").write_text(decision, encoding="utf-8") + if include_index: + current_path = row_path or f".agent-loop/features/{row_month}/{feature_id}/" + (root / "features" / "archive.md").write_text( + "# Feature Archive\n\n" + "This file locates archived or rehydrated features. Feature specs, tests, notes, requirement sources, and accepted decisions remain authoritative.\n\n" + "| Feature ID | Month | Current Path | Archive State | Closed At | Delivered Summary | Source Requirements | Applicable Decisions | Last Moved At |\n" + "|---|---|---|---|---|---|---|---|---|\n" + f"| {feature_id} | {row_month} | `{current_path}` | {row_state} | 2026-05-20 | completed login | none | none | 2026-07-14 |\n", + encoding="utf-8", + ) + return run_checker( + SCRIPT, + str(root / "README.md"), + str(root / "requirement.md"), + str(root / "decision.md"), + str(root), + ) + + def test_accepted_and_proposed_valid_decisions_pass(self) -> None: + self.assert_accepted( + self.readme, + self.source, + self.decision, + "ADR accepted technical landing trace covers 8", + ) + proposed = re.sub(r"^Status: accepted$", "Status: proposed", self.decision, flags=re.MULTILINE) + proposed = re.sub( + r"^## Human Review Evidence\n.*?(?=^## |\Z)", + "", + proposed, + flags=re.MULTILINE | re.DOTALL, + ) + self.assert_accepted( + self.readme, + self.source, + proposed, + "ADR proposed technical landing trace covers 8", + ) + + def test_accepted_decision_requires_human_review_evidence(self) -> None: + decision = re.sub( + r"^## Human Review Evidence\n.*?(?=^## |\Z)", + "", + self.decision, + flags=re.MULTILINE | re.DOTALL, + ) + self.assert_rejected( + self.readme, self.source, decision, "missing section: ## Human Review Evidence" + ) + + def test_valid_not_needed_preflight_passes(self) -> None: + result = self.run_documents( + (NOT_NEEDED / "README.md").read_text(encoding="utf-8"), + (NOT_NEEDED / "requirement.md").read_text(encoding="utf-8"), + (NOT_NEEDED / "decision.md").read_text(encoding="utf-8"), + fixture=NOT_NEEDED, + ) + self.assertEqual(result.returncode, 0, combined_output(result)) + self.assertIn("reasoned concept-foundation-not-needed ADR proposed gate", result.stdout) + + def test_owner_paths_support_existing_and_explicitly_planned_artifacts(self) -> None: + planned = self.decision.replace( + "features/fixture/spec.md", "planned:features/future-fixture/spec.md" + ) + self.assert_accepted( + self.readme, self.source, planned, "technical landing trace covers 8" + ) + delegated = self.decision.replace(", FLOW-FIXTURE-01", "", 1) + delegated = delegated.replace( + "| FLOW-FIXTURE-01 | in-scope | ADR-9000 |", + "| FLOW-FIXTURE-01 | proposed-decision | decisions/9001-follow-up.md |", + ) + delegated = re.sub( + r"^\| FLOW-FIXTURE-01 \| source flow reference .*\n", + "", + delegated, + flags=re.MULTILINE, + ) + self.assert_accepted( + self.readme, self.source, delegated, "technical landing trace covers 7" + ) + + def test_archived_closed_feature_spec_owner_passes_with_matching_locator(self) -> None: + result = self.run_archived_feature_owner() + self.assertEqual(result.returncode, 0, combined_output(result)) + self.assertIn("technical landing trace covers 8", result.stdout) + + def test_archived_feature_spec_owner_requires_archive_locator(self) -> None: + result = self.run_archived_feature_owner(include_index=False) + self.assertEqual(result.returncode, 1, combined_output(result)) + self.assertIn("archive-index", combined_output(result)) + + def test_archived_feature_spec_owner_rejects_mismatched_month_locator(self) -> None: + result = self.run_archived_feature_owner( + row_month="2026-06", + row_path=".agent-loop/features/2026-06/2026-05-08-login/", + ) + self.assertEqual(result.returncode, 1, combined_output(result)) + self.assertIn("month", combined_output(result)) + + def test_archived_feature_spec_owner_rejects_rehydrated_month_locator(self) -> None: + result = self.run_archived_feature_owner(row_state="rehydrated") + self.assertEqual(result.returncode, 1, combined_output(result)) + self.assertIn("archive-index", combined_output(result)) + + def test_planned_feature_spec_owner_must_remain_flat(self) -> None: + result = self.run_archived_feature_owner(planned=True) + self.assertEqual(result.returncode, 1, combined_output(result)) + self.assertIn("planned Feature Spec path must be flat", combined_output(result)) + + def test_adversarial_contracts_are_rejected(self) -> None: + cases = ( + ( + self.decision.replace( + "reason: outside this coherent decision boundary", "reason: n/a" + ), + "must give a concrete reason", + ), + ( + re.sub( + r"## Coverage Hard Gate\n.*?(?=^## |\Z)", + "## Coverage Hard Gate\n\n- [x] arbitrary check\n\n", + self.decision, + flags=re.MULTILINE | re.DOTALL, + ), + "Coverage Hard Gate is missing required items", + ), + ( + self.decision.replace( + "- [x] No unresolved product-semantic blocker remains", + "- [x] No unresolved product-semantic blocker remains\n" + "- [x] arbitrary extra check", + ), + "Coverage Hard Gate contains unsupported items", + ), + ( + re.sub( + r"^Accepted Requirement Model IDs:(.*)$", + r"Accepted Requirement Model IDs:\1, GARBAGE", + self.decision, + flags=re.MULTILINE, + ), + "invalid values in ADR Accepted Requirement Model IDs: GARBAGE", + ), + ( + re.sub( + r"^\| PM-FIXTURE-FACT .*\n", + "", + self.decision.replace(", PM-FIXTURE-FACT", ""), + flags=re.MULTILINE, + ), + "Requirement Model Scope Inventory mismatch", + ), + ( + self.decision.replace( + "decisions/8999-shared.md (ADR-8999)", + "decisions/does-not-exist.md (ADR-DOES-NOT-EXIST)", + ), + "missing file:", + ), + ( + self.decision.replace( + "features/fixture/spec.md", "features/missing/spec.md" + ), + "missing file:", + ), + ( + self.decision.replace("| planned |", "| banana |", 1), + "invalid coverage status: banana", + ), + ( + re.sub( + r"\n## Triggered Operational Landing\n.*?(?=\n## Design Slice Coverage)", + "", + self.decision, + flags=re.DOTALL, + ), + "Triggered Operational Landing is missing", + ), + ( + re.sub( + r"^\| (?:Compatibility|Rollout / Cutover|Rollback / Reversibility) \|.*\n", + "", + self.decision, + flags=re.MULTILINE, + ), + "operational concern inventory mismatch", + ), + ) + for decision, expected in cases: + with self.subTest(expected=expected): + self.assert_rejected(self.readme, self.source, decision, expected) + + def test_existing_invalid_decisions_and_sources_are_rejected(self) -> None: + cases = ( + ( + self.readme, + self.source, + (FIXTURES / "invalid-missing-coverage/decision.md").read_text(encoding="utf-8"), + ), + ( + self.readme, + self.source, + (FIXTURES / "invalid-empty-landing/decision.md").read_text(encoding="utf-8"), + ), + ( + (FIXTURES / "invalid-unaccepted-source/README.md").read_text(encoding="utf-8"), + (FIXTURES / "invalid-unaccepted-source/requirement.md").read_text(encoding="utf-8"), + self.decision, + ), + ( + (FIXTURES / "invalid-reopened-source/README.md").read_text(encoding="utf-8"), + (FIXTURES / "invalid-reopened-source/requirement.md").read_text(encoding="utf-8"), + self.decision, + ), + ( + self.readme, + self.source, + (FIXTURES / "invalid-review-required/decision.md").read_text(encoding="utf-8"), + ), + ) + for readme, source, decision in cases: + with self.subTest(decision=decision[:40]): + result = self.run_documents(readme, source, decision) + self.assertEqual(result.returncode, 1, combined_output(result)) + + def test_workspace_escape_is_rejected(self) -> None: + decision = self.decision.replace( + "decisions/8999-shared.md (ADR-8999)", "../outside.md (ADR-OUTSIDE)" + ) + self.assert_rejected( + self.readme, self.source, decision, "reference escapes workspace root" + ) + + def test_bom_and_crlf_are_supported(self) -> None: + result = self.run_documents( + self.readme, self.source, self.decision, bom_crlf=True + ) + self.assertEqual(result.returncode, 0, combined_output(result)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_concept_foundation_trace.py b/tests/test_concept_foundation_trace.py new file mode 100644 index 0000000..222eaed --- /dev/null +++ b/tests/test_concept_foundation_trace.py @@ -0,0 +1,202 @@ +from __future__ import annotations + +import re +import tempfile +import unittest +from pathlib import Path + +from tests.checker_test_support import ROOT, combined_output, run_checker + + +SCRIPT = "scripts/check-concept-foundation-trace.py" +EXAMPLE = ROOT / "examples/concept-foundation-refund" +FIXTURES = ROOT / "tests/fixtures/concept-foundation" + + +class ConceptFoundationTraceTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.requirement = (EXAMPLE / "requirement.md").read_text(encoding="utf-8") + cls.product = (EXAMPLE / "product.md").read_text(encoding="utf-8") + cls.spec = (EXAMPLE / "spec.md").read_text(encoding="utf-8") + + def run_documents( + self, + requirement: str, + product: str, + spec: str, + *, + bom_crlf: bool = False, + ): + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + paths = [] + for name, content in ( + ("requirement.md", requirement), + ("product.md", product), + ("spec.md", spec), + ): + path = root / name + if bom_crlf: + path.write_bytes(("\ufeff" + content.replace("\n", "\r\n")).encode("utf-8")) + else: + path.write_text(content, encoding="utf-8") + paths.append(str(path)) + return run_checker(SCRIPT, *paths) + + def assert_rejected( + self, requirement: str, product: str, spec: str, expected: str + ) -> None: + result = self.run_documents(requirement, product, spec) + self.assertEqual(result.returncode, 1, combined_output(result)) + self.assertIn(expected, combined_output(result)) + + def test_accepted_example_passes(self) -> None: + result = self.run_documents(self.requirement, self.product, self.spec) + self.assertEqual(result.returncode, 0, combined_output(result)) + self.assertIn("accepted Concept Foundation trace is complete", result.stdout) + + def test_reasoned_not_needed_passes(self) -> None: + requirement = self.requirement.replace( + "Concept Foundation Status: accepted", + "Concept Foundation Status: concept-foundation-not-needed", + ) + requirement = re.sub( + r"^Not-Needed Reason:.*$", + "Not-Needed Reason: only one stable term exists and no product model is derived", + requirement, + flags=re.MULTILINE, + ) + product = self.product.replace( + "Concept Foundation Status: accepted", + "Concept Foundation Status: concept-foundation-not-needed", + ) + spec = self.spec.replace( + "Concept Foundation Status: accepted", + "Concept Foundation Status: concept-foundation-not-needed", + ) + result = self.run_documents(requirement, product, spec) + self.assertEqual(result.returncode, 0, combined_output(result)) + self.assertIn("reasoned concept-foundation-not-needed trace", result.stdout) + + def test_existing_invalid_fixtures_are_rejected(self) -> None: + for fixture in ("invalid-unaccepted", "invalid-detached-model"): + with self.subTest(fixture=fixture): + root = FIXTURES / fixture + result = run_checker( + SCRIPT, + str(root / "requirement.md"), + str(root / "product.md"), + str(root / "spec.md"), + ) + self.assertEqual(result.returncode, 1, combined_output(result)) + + def test_adversarial_semantic_breaks_are_rejected(self) -> None: + definition = self.requirement.split("## Concept Definitions\n", 1)[1] + concept_line = next( + line for line in definition.splitlines(keepends=True) if line.startswith("| C-CUSTOMER |") + ) + cases = ( + ( + re.sub( + r"^- Confirmed Concept IDs:.*$", + "- Confirmed Concept IDs: C-CUSTOMER", + self.requirement, + flags=re.MULTILINE, + ), + self.product, + self.spec, + "unconfirmed Concept IDs", + ), + ( + re.sub( + r"^## Concept Candidate Inventory\n.*?(?=^## Concept Definitions\n)", + "", + self.requirement, + flags=re.MULTILINE | re.DOTALL, + ), + self.product, + self.spec, + "missing section: ## Concept Candidate Inventory", + ), + ( + self.requirement.replace("| resolved |", "| open |", 1), + self.product, + self.spec, + "Blocking Ambiguities contains unresolved rows", + ), + ( + re.sub(r"^\| TRACE-03 .*\n", "", self.requirement, flags=re.MULTILINE), + self.product, + self.spec, + "untraced product model IDs", + ), + ( + self.requirement.replace(concept_line, concept_line * 2, 1), + self.product, + self.spec, + "duplicate IDs in Concept Definitions", + ), + ( + self.requirement, + re.sub( + r"^Effective Concept Source:.*\n", + "", + self.product, + flags=re.MULTILINE, + ), + self.spec, + "product Effective Concept Source is missing", + ), + ( + re.sub( + r"^\| PERM-ADMIN-SETTLEMENT \| C-REFUND-ADMIN " + r"\| C-REFUND-SETTLEMENT .*\n", + "", + self.requirement, + flags=re.MULTILINE, + ), + self.product, + self.spec, + "Commands / Events missing Role / Permission Matrix pairs", + ), + ) + for requirement, product, spec, expected in cases: + with self.subTest(expected=expected): + self.assert_rejected(requirement, product, spec, expected) + + def test_placeholder_not_needed_reason_is_rejected(self) -> None: + requirement = self.requirement.replace( + "Concept Foundation Status: accepted", + "Concept Foundation Status: concept-foundation-not-needed", + ) + requirement = re.sub( + r"^Not-Needed Reason:.*$", + "Not-Needed Reason: n/a", + requirement, + flags=re.MULTILINE, + ) + product = self.product.replace( + "Concept Foundation Status: accepted", + "Concept Foundation Status: concept-foundation-not-needed", + ) + spec = self.spec.replace( + "Concept Foundation Status: accepted", + "Concept Foundation Status: concept-foundation-not-needed", + ) + self.assert_rejected( + requirement, + product, + spec, + "concept-foundation-not-needed requires a concrete reason", + ) + + def test_bom_and_crlf_are_supported(self) -> None: + result = self.run_documents( + self.requirement, self.product, self.spec, bom_crlf=True + ) + self.assertEqual(result.returncode, 0, combined_output(result)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_feature_archive_support.py b/tests/test_feature_archive_support.py new file mode 100644 index 0000000..8e4812e --- /dev/null +++ b/tests/test_feature_archive_support.py @@ -0,0 +1,289 @@ +from __future__ import annotations + +import importlib +import sys +import tempfile +import unittest +from pathlib import Path +from unittest import mock + +from tests.checker_test_support import ROOT +from tests.feature_archive_test_support import ArchiveWorkspace, tree_snapshot + + +ARCHIVE_HEADER = """# Feature Archive + +This file locates archived or rehydrated features. Feature specs, tests, notes, requirement sources, and accepted decisions remain authoritative. + +| Feature ID | Month | Current Path | Archive State | Closed At | Delivered Summary | Source Requirements | Applicable Decisions | Last Moved At | +|---|---|---|---|---|---|---|---|---| +""" + + +def archive_row( + feature_id: str, + *, + month: str = "2026-05", + path: str | None = None, + state: str = "archived", + summary: str | None = None, +) -> str: + current = path or f".agent-loop/features/{month}/{feature_id}/" + delivered = summary or f"completed {feature_id}" + return ( + f"| {feature_id} | {month} | `{current}` | {state} | 2026-05-20 | " + f"{delivered} | none | none | 2026-07-14 |\n" + ) + + +class FeatureArchiveSupportTests(unittest.TestCase): + def checker_support(self): + scripts_dir = str(ROOT / "scripts") + if scripts_dir not in sys.path: + sys.path.insert(0, scripts_dir) + module = importlib.import_module("checker_support") + for name in ( + "atomic_write_bytes", + "canonical_json_bytes", + "sha256_bytes", + "strip_code_span", + ): + self.assertTrue(hasattr(module, name), f"missing production helper: {name}") + return module + + def support(self): + support_path = ROOT / "scripts/feature_archive_support.py" + self.assertTrue(support_path.is_file(), f"missing production support: {support_path}") + scripts_dir = str(ROOT / "scripts") + if scripts_dir not in sys.path: + sys.path.insert(0, scripts_dir) + return importlib.import_module("feature_archive_support") + + def test_archive_template_exists_with_exact_empty_table(self) -> None: + template = ROOT / "templates/feature-archive.md" + self.assertTrue(template.is_file(), f"missing archive template: {template}") + self.assertEqual(template.read_text(encoding="utf-8"), ARCHIVE_HEADER) + + def test_canonical_helpers_use_stable_utf8_bytes(self) -> None: + checker_support = self.checker_support() + expected = '{"a":"登录","z":1}'.encode("utf-8") + self.assertEqual( + checker_support.canonical_json_bytes({"z": 1, "a": "登录"}), expected + ) + self.assertEqual( + checker_support.sha256_bytes(expected), + "c92ecc89f697b155c8d5fb7417e18732f61b69c1375d6811c329822bf34f3500", + ) + self.assertEqual(checker_support.strip_code_span(" `value` "), "value") + + def test_atomic_write_bytes_replaces_existing_file(self) -> None: + checker_support = self.checker_support() + with tempfile.TemporaryDirectory() as temp: + target = Path(temp) / "nested" / "artifact.md" + target.parent.mkdir() + target.write_bytes(b"before") + checker_support.atomic_write_bytes(target, "完成".encode("utf-8")) + self.assertEqual(target.read_bytes(), "完成".encode("utf-8")) + self.assertEqual(list(target.parent.glob(f".{target.name}.*")), []) + + def test_atomic_write_bytes_cleans_temporary_file_after_replace_failure(self) -> None: + checker_support = self.checker_support() + with tempfile.TemporaryDirectory() as temp: + target = Path(temp) / "artifact.md" + with mock.patch.object( + checker_support.os, "replace", side_effect=OSError("injected replace failure") + ): + with self.assertRaisesRegex(OSError, "injected replace failure"): + checker_support.atomic_write_bytes(target, b"new") + self.assertFalse(target.exists()) + self.assertEqual(list(target.parent.glob(f".{target.name}.*")), []) + + def test_flat_feature_resolves_without_archive_index(self) -> None: + support = self.support() + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login") + self.assertEqual( + support.resolve_feature_location( + workspace.memory_root, "2026-05-08-login" + ), + support.FeatureLocation( + "2026-05-08-login", + "features/2026-05-08-login", + "flat", + None, + ), + ) + + def test_archived_feature_requires_matching_unique_index_row(self) -> None: + support = self.support() + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + source = workspace.feature("2026-05-08-login") + target = workspace.features_root / "2026-05" / source.name + target.parent.mkdir(parents=True) + source.rename(target) + index = workspace.write( + ".agent-loop/features/archive.md", + ARCHIVE_HEADER + archive_row(source.name), + ) + self.assertEqual( + support.resolve_feature_location(workspace.memory_root, source.name), + support.FeatureLocation( + source.name, + f"features/2026-05/{source.name}", + "archived", + "2026-05", + ), + ) + + index.unlink() + with self.assertRaisesRegex(support.ArchiveContractError, "archive-index"): + support.resolve_feature_location(workspace.memory_root, source.name) + + index.write_text( + ARCHIVE_HEADER + archive_row(source.name) + archive_row(source.name), + encoding="utf-8", + newline="\n", + ) + with self.assertRaisesRegex(support.ArchiveContractError, "archive-index"): + support.resolve_feature_location(workspace.memory_root, source.name) + + def test_flat_and_archived_collision_fails_closed(self) -> None: + support = self.support() + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login") + archived = workspace.features_root / "2026-05" / "2026-05-08-login" + archived.mkdir(parents=True) + workspace.write( + ".agent-loop/features/archive.md", + ARCHIVE_HEADER + archive_row("2026-05-08-login"), + ) + with self.assertRaisesRegex(support.ArchiveContractError, "path-collision"): + support.resolve_feature_location( + workspace.memory_root, "2026-05-08-login" + ) + + def test_month_must_match_feature_id(self) -> None: + support = self.support() + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + archived = workspace.features_root / "2026-06" / "2026-05-08-login" + archived.mkdir(parents=True) + workspace.write( + ".agent-loop/features/archive.md", + ARCHIVE_HEADER + + archive_row( + "2026-05-08-login", + month="2026-06", + path=".agent-loop/features/2026-06/2026-05-08-login/", + ), + ) + with self.assertRaisesRegex(support.ArchiveContractError, "month"): + support.resolve_feature_location( + workspace.memory_root, "2026-05-08-login" + ) + + def test_archive_index_rejects_case_and_unicode_path_differences(self) -> None: + support = self.support() + for changed in ( + "2026-05-08-LOGIN", + "2026-05-08-login", + ): + with self.subTest(changed=changed), tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.write( + ".agent-loop/features/archive.md", + ARCHIVE_HEADER + + archive_row( + "2026-05-08-login", + path=f".agent-loop/features/2026-05/{changed}/", + ), + ) + with self.assertRaises(support.ArchiveContractError): + support.parse_archive_index(workspace.memory_root) + + def test_archive_index_accepts_bom_and_crlf(self) -> None: + support = self.support() + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + archived = workspace.features_root / "2026-05" / feature_id + archived.mkdir(parents=True) + index = workspace.features_root / "archive.md" + index.parent.mkdir(parents=True, exist_ok=True) + content = ARCHIVE_HEADER + archive_row( + feature_id, summary="完成登录与失败路径验证" + ) + index.write_bytes(b"\xef\xbb\xbf" + content.replace("\n", "\r\n").encode("utf-8")) + entries = support.parse_archive_index(workspace.memory_root) + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0].delivered_summary, "完成登录与失败路径验证") + + def test_symlink_escape_is_rejected(self) -> None: + support = self.support() + with tempfile.TemporaryDirectory() as temp, tempfile.TemporaryDirectory() as outside: + workspace = ArchiveWorkspace(Path(temp)) + external = Path(outside) / "2026-05-08-login" + external.mkdir() + archived = workspace.features_root / "2026-05" / external.name + archived.parent.mkdir(parents=True) + try: + archived.symlink_to(external, target_is_directory=True) + except OSError as error: + self.skipTest(f"directory symlink unavailable: {error}") + workspace.write( + ".agent-loop/features/archive.md", + ARCHIVE_HEADER + archive_row(external.name), + ) + with self.assertRaisesRegex(support.ArchiveContractError, "path-escape"): + support.resolve_feature_location(workspace.memory_root, external.name) + + def test_plan_hash_is_stable_across_absolute_roots(self) -> None: + support = self.support() + + def make_plan(root: Path): + workspace = ArchiveWorkspace(root) + workspace.feature("2026-05-08-login") + candidate = support.ArchiveCandidate( + "2026-05-08-login", + "2026-05", + "features/2026-05-08-login", + "closed", + "complete", + "none", + "completed login", + "none", + "none", + (), + ) + move = support.Move( + "2026-05-08-login", + "2026-05", + ".agent-loop/features/2026-05-08-login", + ".agent-loop/features/2026-05/2026-05-08-login", + ) + return support.ArchivePlan( + 1, + "archive", + "2026-07-14", + ("2026-05",), + (), + (candidate,), + (move,), + (), + (), + (), + tree_snapshot(root), + ) + + with tempfile.TemporaryDirectory() as first, tempfile.TemporaryDirectory() as second: + self.assertEqual( + make_plan(Path(first)).computed_sha256(), + make_plan(Path(second)).computed_sha256(), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_feature_monthly_archive_apply.py b/tests/test_feature_monthly_archive_apply.py new file mode 100644 index 0000000..69819ff --- /dev/null +++ b/tests/test_feature_monthly_archive_apply.py @@ -0,0 +1,457 @@ +from __future__ import annotations + +import hashlib +import json +import sys +import tempfile +import unittest +from pathlib import Path + +from tests.feature_archive_test_support import ( + ArchiveWorkspace, + json_output, + run_archive_command, + tree_snapshot, +) + + +class FeatureMonthlyArchiveCheckTests(unittest.TestCase): + def scanned_plan( + self, + workspace: ArchiveWorkspace, + *, + operation: str = "archive", + months: tuple[str, ...] = ("2026-05",), + feature_ids: tuple[str, ...] = (), + ) -> dict[str, object]: + selection: list[str] = [] + for month in months: + selection.extend(("--month", month)) + for feature_id in feature_ids: + selection.extend(("--feature-id", feature_id)) + result = run_archive_command( + "scan-feature-monthly-archive.py", + "--project-root", + str(workspace.project_root), + "--operation", + operation, + *selection, + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + return json_output(result) + + def apply( + self, + workspace: ArchiveWorkspace, + payload: dict[str, object], + *, + operation: str = "archive", + months: tuple[str, ...] = ("2026-05",), + feature_ids: tuple[str, ...] = (), + expected_hash: str | None = None, + env: dict[str, str] | None = None, + ): + selection: list[str] = [] + for month in months: + selection.extend(("--month", month)) + for feature_id in feature_ids: + selection.extend(("--feature-id", feature_id)) + return run_archive_command( + "apply-feature-monthly-archive.py", + "--project-root", + str(workspace.project_root), + "--operation", + operation, + *selection, + "--as-of", + "2026-07-14", + "--expected-plan-sha256", + expected_hash or str(payload["plan_sha256"]), + env=env, + ) + + def write_plan( + self, workspace: ArchiveWorkspace, payload: dict[str, object] + ) -> Path: + path = workspace.project_root / "archive-plan.json" + path.write_text( + json.dumps(payload, ensure_ascii=False, sort_keys=True, indent=2) + "\n", + encoding="utf-8", + newline="\n", + ) + return path + + def check(self, workspace: ArchiveWorkspace, plan: Path): + return run_archive_command( + "check-feature-monthly-archive.py", + "--project-root", + str(workspace.project_root), + "--operation", + "archive", + "--plan", + str(plan), + ) + + def test_pre_check_is_read_only_and_accepts_current_plan(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login") + plan = self.write_plan(workspace, self.scanned_plan(workspace)) + before = tree_snapshot(workspace.project_root) + result = self.check(workspace, plan) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("PASS", result.stdout) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def test_pre_check_rejects_snapshot_drift_without_mutation(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature = workspace.feature("2026-05-08-login") + plan = self.write_plan(workspace, self.scanned_plan(workspace)) + (feature / "notes.md").write_text( + (feature / "notes.md").read_text(encoding="utf-8") + "\nchanged\n", + encoding="utf-8", + newline="\n", + ) + before = tree_snapshot(workspace.project_root) + result = self.check(workspace, plan) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("stale-plan", result.stdout + result.stderr) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def test_check_rejects_self_hashed_plan_path_escape_without_mutation(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login") + payload = self.scanned_plan(workspace) + payload["moves"][0]["source"] = "../escaped-feature" + unsigned = dict(payload) + unsigned.pop("plan_sha256") + payload["plan_sha256"] = hashlib.sha256( + json.dumps( + unsigned, + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + ).hexdigest() + plan = self.write_plan(workspace, payload) + before = tree_snapshot(workspace.project_root) + result = self.check(workspace, plan) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("path-escape", result.stdout + result.stderr) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def test_post_check_accepts_exact_moves_index_and_reference_edits(self) -> None: + scripts_dir = str(Path(__file__).resolve().parents[1] / "scripts") + if scripts_dir not in sys.path: + sys.path.insert(0, scripts_dir) + from feature_archive_support import ArchiveEntry, render_archive_index + + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + workspace.write( + ".agent-loop/project.md", + f"# Project\n\nDelivered: `.agent-loop/features/{feature_id}/spec.md`\n", + ) + payload = self.scanned_plan(workspace) + plan = self.write_plan(workspace, payload) + for move in payload["moves"]: + source = workspace.project_root / move["source"] + target = workspace.project_root / move["target"] + target.parent.mkdir(parents=True, exist_ok=True) + source.rename(target) + for edit in payload["reference_edits"]: + target = workspace.project_root / edit["path"] + content = target.read_text(encoding="utf-8") + self.assertEqual(content.count(edit["old"]), edit["occurrences"]) + target.write_text( + content.replace(edit["old"], edit["new"]), + encoding="utf-8", + newline="\n", + ) + entries = [ArchiveEntry(**entry) for entry in payload["archive_entries"]] + workspace.write( + ".agent-loop/features/archive.md", render_archive_index(entries) + ) + before = tree_snapshot(workspace.project_root) + result = self.check(workspace, plan) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("PASS", result.stdout) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def test_apply_moves_two_closed_features_intact_and_updates_archive_index(self) -> None: + scripts_dir = str(Path(__file__).resolve().parents[1] / "scripts") + if scripts_dir not in sys.path: + sys.path.insert(0, scripts_dir) + from feature_archive_support import parse_archive_index + + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + first = workspace.feature("2026-05-08-login") + second = workspace.feature("2026-06-12-payment") + (first / "payload.bin").write_bytes(b"\x00login\xff") + (second / "payload.bin").write_bytes(b"\x00payment\xff") + first_before = tree_snapshot(first) + second_before = tree_snapshot(second) + payload = self.scanned_plan( + workspace, months=("2026-05", "2026-06") + ) + result = self.apply( + workspace, payload, months=("2026-05", "2026-06") + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse(first.exists()) + self.assertFalse(second.exists()) + first_target = workspace.features_root / "2026-05" / first.name + second_target = workspace.features_root / "2026-06" / second.name + self.assertEqual(tree_snapshot(first_target), first_before) + self.assertEqual(tree_snapshot(second_target), second_before) + entries = parse_archive_index(workspace.memory_root) + self.assertEqual( + [entry.feature_id for entry in entries], + ["2026-05-08-login", "2026-06-12-payment"], + ) + + def test_apply_rejects_valid_but_different_plan_hash_without_writes(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login") + payload = self.scanned_plan(workspace) + before = tree_snapshot(workspace.project_root) + wrong = ("0" if payload["plan_sha256"][0] != "0" else "1") + payload[ + "plan_sha256" + ][1:] + result = self.apply(workspace, payload, expected_hash=wrong) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("stale-plan", result.stdout + result.stderr) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def test_apply_rejects_state_drift_after_scan(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature = workspace.feature("2026-05-08-login") + payload = self.scanned_plan(workspace) + notes = feature / "notes.md" + notes.write_text( + notes.read_text(encoding="utf-8") + "\nstate drift\n", + encoding="utf-8", + newline="\n", + ) + before = tree_snapshot(workspace.project_root) + result = self.apply(workspace, payload) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("stale-plan", result.stdout + result.stderr) + self.assertEqual(tree_snapshot(workspace.project_root), before) + self.assertFalse( + (workspace.features_root / ".archive-txn").exists() + ) + + def test_apply_rejects_stranded_transaction_created_after_scan(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature = workspace.feature("2026-05-08-login") + payload = self.scanned_plan(workspace) + transaction_id = "20260714T120000Z-0123456789ab" + transaction = workspace.features_root / ".archive-txn" / transaction_id + transaction.mkdir(parents=True) + before = tree_snapshot(workspace.project_root) + + result = self.apply(workspace, payload) + + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("stranded-transaction", result.stdout + result.stderr) + self.assertIn(transaction_id, result.stdout + result.stderr) + self.assertEqual(tree_snapshot(workspace.project_root), before) + self.assertTrue(feature.is_dir()) + + def test_apply_updates_relative_link_from_project_memory_into_feature(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + project = workspace.write( + ".agent-loop/project.md", + f"# Project\n\n[Feature](features/{feature_id}/spec.md)\n", + ) + payload = self.scanned_plan(workspace) + + result = self.apply(workspace, payload) + + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn( + f"features/2026-05/{feature_id}/spec.md", + project.read_text(encoding="utf-8"), + ) + self.assertTrue( + (workspace.features_root / "2026-05" / feature_id / "spec.md").is_file() + ) + + def test_apply_updates_only_precomputed_reference_edits(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + old = f".agent-loop/features/{feature_id}/spec.md" + new = f".agent-loop/features/2026-05/{feature_id}/spec.md" + for relative in ( + ".agent-loop/project.md", + ".agent-loop/requirements/2026-05-login/README.md", + ".agent-loop/decisions/ADR-001-login.md", + ): + workspace.write(relative, f"# Locator\n\nOwner: `{old}`\n") + requirement = workspace.write( + ".agent-loop/requirements/2026-05-login/requirement.md", + f"# Original Requirement\n\nOriginal path: `{old}`\n", + ) + unrelated = workspace.write("docs/unrelated.md", "# Unrelated\n\nkeep bytes\n") + requirement_before = requirement.read_bytes() + unrelated_before = unrelated.read_bytes() + payload = self.scanned_plan(workspace) + result = self.apply(workspace, payload) + self.assertEqual(result.returncode, 0, result.stderr) + for relative in ( + ".agent-loop/project.md", + ".agent-loop/requirements/2026-05-login/README.md", + ".agent-loop/decisions/ADR-001-login.md", + ): + content = (workspace.project_root / relative).read_text(encoding="utf-8") + self.assertIn(new, content) + self.assertNotIn(old, content) + self.assertEqual(requirement.read_bytes(), requirement_before) + self.assertEqual(unrelated.read_bytes(), unrelated_before) + + def test_apply_preserves_bom_and_crlf_while_updating_reference(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + old = f".agent-loop/features/{feature_id}/spec.md" + new = f".agent-loop/features/2026-05/{feature_id}/spec.md" + project = workspace.memory_root / "project.md" + project.parent.mkdir(parents=True, exist_ok=True) + project.write_bytes( + b"\xef\xbb\xbf" + + f"# Project\r\n\r\nOwner: `{old}`\r\n".encode("utf-8") + ) + payload = self.scanned_plan(workspace) + result = self.apply(workspace, payload) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + updated = project.read_bytes() + self.assertTrue(updated.startswith(b"\xef\xbb\xbf")) + self.assertIn(new.encode("utf-8"), updated) + self.assertNotIn(old.encode("utf-8"), updated) + self.assertIn(b"\r\n", updated) + self.assertNotIn(b"\n", updated.replace(b"\r\n", b"")) + + def test_apply_is_idempotent_and_never_nests_month_directory(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + first_plan = self.scanned_plan(workspace) + first = self.apply(workspace, first_plan) + self.assertEqual(first.returncode, 0, first.stderr) + second_plan = self.scanned_plan(workspace) + self.assertEqual(second_plan["moves"], []) + self.assertIn( + "already-archived", second_plan["candidates"][0]["blockers"] + ) + before = tree_snapshot(workspace.project_root) + second = self.apply(workspace, second_plan) + self.assertEqual(second.returncode, 0, second.stderr) + self.assertEqual(tree_snapshot(workspace.project_root), before) + self.assertFalse( + ( + workspace.features_root + / "2026-05" + / "2026-05" + / feature_id + ).exists() + ) + + def test_injected_reference_write_failure_restores_directories_and_bytes(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + workspace.write( + ".agent-loop/project.md", + f"# Project\n\nOwner: `.agent-loop/features/{feature_id}/spec.md`\n", + ) + payload = self.scanned_plan(workspace) + before = tree_snapshot(workspace.project_root) + result = self.apply( + workspace, + payload, + env={ + "AGENT_LOOP_ARCHIVE_TEST_MODE": "1", + "AGENT_LOOP_ARCHIVE_FAIL_AFTER": "3", + }, + ) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("restore=complete", result.stdout + result.stderr) + self.assertEqual(tree_snapshot(workspace.project_root), before) + self.assertFalse((workspace.features_root / ".archive-txn").exists()) + + def test_failure_injection_is_rejected_outside_explicit_test_mode(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login") + payload = self.scanned_plan(workspace) + before = tree_snapshot(workspace.project_root) + result = self.apply( + workspace, + payload, + env={"AGENT_LOOP_ARCHIVE_FAIL_AFTER": "1"}, + ) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("test-only", result.stdout + result.stderr) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def test_rehydrate_moves_archived_feature_flat_and_updates_locator(self) -> None: + scripts_dir = str(Path(__file__).resolve().parents[1] / "scripts") + if scripts_dir not in sys.path: + sys.path.insert(0, scripts_dir) + from feature_archive_support import parse_archive_index + + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + archive_plan = self.scanned_plan(workspace) + archived = self.apply(workspace, archive_plan) + self.assertEqual(archived.returncode, 0, archived.stderr) + rehydrate_plan = self.scanned_plan( + workspace, + operation="rehydrate", + months=(), + feature_ids=(feature_id,), + ) + result = self.apply( + workspace, + rehydrate_plan, + operation="rehydrate", + months=(), + feature_ids=(feature_id,), + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse( + (workspace.features_root / "2026-05" / feature_id).exists() + ) + flat = workspace.features_root / feature_id + self.assertTrue(flat.is_dir()) + self.assertIn("Status: closed", (flat / "spec.md").read_text(encoding="utf-8")) + entry = parse_archive_index(workspace.memory_root)[0] + self.assertEqual(entry.archive_state, "rehydrated") + self.assertEqual( + entry.current_path, f".agent-loop/features/{feature_id}/" + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_feature_monthly_archive_restore.py b/tests/test_feature_monthly_archive_restore.py new file mode 100644 index 0000000..381a238 --- /dev/null +++ b/tests/test_feature_monthly_archive_restore.py @@ -0,0 +1,366 @@ +from __future__ import annotations + +import hashlib +import json +import shutil +import tempfile +import unittest +from pathlib import Path + +from tests.feature_archive_test_support import ( + ArchiveWorkspace, + json_output, + run_archive_command, + tree_snapshot, +) + + +TRANSACTION_ID = "20260714T120000Z-0123456789ab" + + +class FeatureMonthlyArchiveRestoreTests(unittest.TestCase): + def test_restore_check_accepts_exact_pretransaction_state_without_mutation( + self, + ) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login") + scan = run_archive_command( + "scan-feature-monthly-archive.py", + "--project-root", + str(workspace.project_root), + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(scan.returncode, 0, scan.stderr) + plan = workspace.project_root / "archive-plan.json" + plan.write_text( + json.dumps(json_output(scan), ensure_ascii=False, sort_keys=True, indent=2) + + "\n", + encoding="utf-8", + newline="\n", + ) + before = tree_snapshot(workspace.project_root) + result = run_archive_command( + "check-feature-monthly-archive.py", + "--project-root", + str(workspace.project_root), + "--operation", + "restore", + "--plan", + str(plan), + ) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn("restore-check", result.stdout) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def interrupted_transaction( + self, workspace: ArchiveWorkspace, feature_id: str + ) -> tuple[Path, dict[str, str]]: + source = workspace.features_root / feature_id + if not source.is_dir(): + source = workspace.feature(feature_id) + before = tree_snapshot(workspace.project_root) + scan = run_archive_command( + "scan-feature-monthly-archive.py", + "--project-root", + str(workspace.project_root), + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(scan.returncode, 0, scan.stderr) + plan = json_output(scan) + transaction = workspace.features_root / ".archive-txn" / TRANSACTION_ID + transaction.mkdir(parents=True) + index_relative = ".agent-loop/features/archive.md" + backup_paths = sorted( + {str(item["path"]) for item in plan["reference_edits"]} + | {index_relative} + ) + backups = [] + for relative in backup_paths: + path = workspace.project_root / relative + if path.is_file(): + content = path.read_bytes() + backup_relative = f"backups/{relative}" + backup = transaction / backup_relative + backup.parent.mkdir(parents=True, exist_ok=True) + backup.write_bytes(content) + backups.append( + { + "path": relative, + "state": "existing", + "backup": backup_relative, + "sha256": hashlib.sha256(content).hexdigest(), + } + ) + else: + backups.append({"path": relative, "state": "missing-before"}) + target = workspace.features_root / "2026-05" / feature_id + target.parent.mkdir(parents=True) + source.rename(target) + journal = { + "schema_version": 1, + "transaction_id": TRANSACTION_ID, + "operation": "archive", + "plan_sha256": plan["plan_sha256"], + "plan": plan, + "state": "moving", + "moves": plan["moves"], + "backups": backups, + "completed_operations": [ + { + "kind": "move", + "source": f".agent-loop/features/{feature_id}", + "target": f".agent-loop/features/2026-05/{feature_id}", + } + ], + "snapshots": plan["snapshots"], + "created_directories": [".agent-loop/features/2026-05"], + } + (transaction / "journal.json").write_text( + json.dumps(journal, ensure_ascii=False, sort_keys=True, indent=2) + "\n", + encoding="utf-8", + newline="\n", + ) + return transaction, before + + def restore(self, workspace: ArchiveWorkspace): + return run_archive_command( + "restore-feature-monthly-archive.py", + "--project-root", + str(workspace.project_root), + "--transaction-id", + TRANSACTION_ID, + ) + + def test_interrupted_transaction_can_be_restored_by_new_process(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + transaction, before = self.interrupted_transaction( + workspace, "2026-05-08-login" + ) + result = self.restore(workspace) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(tree_snapshot(workspace.project_root), before) + self.assertFalse(transaction.exists()) + + def test_restore_recovers_move_completed_before_journal_record(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + transaction, before = self.interrupted_transaction( + workspace, "2026-05-08-login" + ) + journal_path = transaction / "journal.json" + journal = json.loads(journal_path.read_text(encoding="utf-8")) + journal["completed_operations"] = [] + journal_path.write_text( + json.dumps(journal, ensure_ascii=False, sort_keys=True, indent=2) + "\n", + encoding="utf-8", + newline="\n", + ) + result = self.restore(workspace) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertEqual(tree_snapshot(workspace.project_root), before) + self.assertFalse(transaction.exists()) + + def test_incomplete_restore_keeps_journal_and_fails_closed(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + transaction, _ = self.interrupted_transaction( + workspace, "2026-05-08-login" + ) + source_collision = workspace.features_root / "2026-05-08-login" + source_collision.mkdir() + (source_collision / "collision.md").write_text( + "collision", encoding="utf-8" + ) + result = self.restore(workspace) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("restore", result.stdout + result.stderr) + self.assertTrue(transaction.exists()) + journal = json.loads((transaction / "journal.json").read_text(encoding="utf-8")) + self.assertEqual(journal["state"], "restoring") + + def test_restore_rejects_journal_path_escape_without_moving_feature(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + transaction, _ = self.interrupted_transaction(workspace, feature_id) + target = workspace.features_root / "2026-05" / feature_id + escaped = workspace.project_root.parent / ( + workspace.project_root.name + "-escaped-feature" + ) + source_relative = f"../{escaped.name}" + target_relative = f".agent-loop/features/2026-05/{feature_id}" + journal_path = transaction / "journal.json" + journal = json.loads(journal_path.read_text(encoding="utf-8")) + journal["plan"]["moves"][0]["source"] = source_relative + payload_without_hash = { + key: value + for key, value in journal["plan"].items() + if key != "plan_sha256" + } + plan_sha256 = hashlib.sha256( + json.dumps( + payload_without_hash, + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + ).hexdigest() + journal["plan"]["plan_sha256"] = plan_sha256 + journal["plan_sha256"] = plan_sha256 + journal["moves"][0]["source"] = source_relative + journal["completed_operations"][0]["source"] = source_relative + journal_path.write_text( + json.dumps(journal, ensure_ascii=False, sort_keys=True, indent=2) + "\n", + encoding="utf-8", + newline="\n", + ) + try: + result = self.restore(workspace) + escaped_created = escaped.exists() + finally: + if escaped.exists(): + shutil.rmtree(escaped) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("path-escape", result.stdout + result.stderr) + self.assertFalse(escaped_created) + self.assertTrue(target.is_dir()) + self.assertTrue(transaction.is_dir()) + + def test_restore_rejects_tampered_backup_scope_without_deleting_file(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + victim = workspace.write("README.md", "must survive\n") + transaction, _ = self.interrupted_transaction( + workspace, "2026-05-08-login" + ) + journal_path = transaction / "journal.json" + journal = json.loads(journal_path.read_text(encoding="utf-8")) + victim_sha256 = hashlib.sha256(victim.read_bytes()).hexdigest() + journal["plan"]["reference_edits"].append( + { + "path": "README.md", + "kind": "literal-path", + "old": "must survive", + "new": "must survive", + "occurrences": 1, + "before_sha256": victim_sha256, + "after_sha256": victim_sha256, + } + ) + payload_without_hash = { + key: value + for key, value in journal["plan"].items() + if key != "plan_sha256" + } + plan_sha256 = hashlib.sha256( + json.dumps( + payload_without_hash, + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + ).hexdigest() + journal["plan"]["plan_sha256"] = plan_sha256 + journal["plan_sha256"] = plan_sha256 + journal["backups"].append( + {"path": "README.md", "state": "missing-before"} + ) + journal_path.write_text( + json.dumps(journal, ensure_ascii=False, sort_keys=True, indent=2) + "\n", + encoding="utf-8", + newline="\n", + ) + + result = self.restore(workspace) + + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("journal", result.stdout + result.stderr) + self.assertTrue(victim.is_file()) + self.assertEqual(victim.read_text(encoding="utf-8"), "must survive\n") + self.assertTrue(transaction.is_dir()) + self.assertTrue( + ( + workspace.features_root + / "2026-05" + / "2026-05-08-login" + ).is_dir() + ) + + def test_restore_rejects_post_crash_reference_drift_without_overwrite(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + project = workspace.write( + ".agent-loop/project.md", + f"# Project\n\nOwner: `.agent-loop/features/{feature_id}/spec.md`\n", + ) + transaction, _ = self.interrupted_transaction(workspace, feature_id) + project.write_text( + "# Project\n\nHuman edit after crash\n", + encoding="utf-8", + newline="\n", + ) + + result = self.restore(workspace) + + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("drift", result.stdout + result.stderr) + self.assertEqual( + project.read_text(encoding="utf-8"), + "# Project\n\nHuman edit after crash\n", + ) + self.assertTrue(transaction.is_dir()) + self.assertTrue( + (workspace.features_root / "2026-05" / feature_id).is_dir() + ) + + def test_restore_rejects_corrupt_backup_before_moving_feature(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + workspace.write( + ".agent-loop/project.md", + f"# Project\n\n[Feature](features/{feature_id}/spec.md)\n", + ) + transaction, _ = self.interrupted_transaction(workspace, feature_id) + backup = transaction / "backups" / ".agent-loop" / "project.md" + backup.write_text("corrupt backup\n", encoding="utf-8", newline="\n") + target = workspace.features_root / "2026-05" / feature_id + source = workspace.features_root / feature_id + + result = self.restore(workspace) + + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("backup", result.stdout + result.stderr) + self.assertTrue(target.is_dir()) + self.assertFalse(source.exists()) + self.assertEqual( + workspace.project_root.joinpath(".agent-loop/project.md").read_text( + encoding="utf-8" + ), + f"# Project\n\n[Feature](features/{feature_id}/spec.md)\n", + ) + self.assertEqual(backup.read_text(encoding="utf-8"), "corrupt backup\n") + journal = json.loads( + (transaction / "journal.json").read_text(encoding="utf-8") + ) + self.assertEqual(journal["state"], "restoring") + self.assertFalse((workspace.features_root / feature_id).exists()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_feature_monthly_archive_scan.py b/tests/test_feature_monthly_archive_scan.py new file mode 100644 index 0000000..cacca0d --- /dev/null +++ b/tests/test_feature_monthly_archive_scan.py @@ -0,0 +1,561 @@ +from __future__ import annotations + +import tempfile +import unittest +from pathlib import Path + +from tests.feature_archive_test_support import ( + ArchiveWorkspace, + json_output, + run_archive_command, + tree_snapshot, +) +from tests.test_feature_archive_support import ARCHIVE_HEADER, archive_row + + +TRANSACTION_ID = "20260714T120000Z-0123456789ab" + + +class FeatureMonthlyArchiveScanTests(unittest.TestCase): + def scan(self, workspace: ArchiveWorkspace, *args: str): + return run_archive_command( + "scan-feature-monthly-archive.py", + "--project-root", + str(workspace.project_root), + *args, + ) + + def test_scan_selects_closed_features_across_two_months_and_preserves_blocked( + self, + ) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login") + workspace.feature("2026-05-22-import", status="paused") + workspace.feature("2026-06-12-payment") + before = tree_snapshot(workspace.project_root) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--month", + "2026-06", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertEqual( + [move["feature_id"] for move in payload["moves"]], + ["2026-05-08-login", "2026-06-12-payment"], + ) + self.assertIn( + "2026-05-22-import", + [item["feature_id"] for item in payload["candidates"]], + ) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def test_scan_snapshots_large_feature_payload_for_intact_move_check(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature = workspace.feature("2026-05-08-login") + payload_file = feature / "large-payload.bin" + payload_file.write_bytes(b"x" * (2 * 1024 * 1024 + 1)) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertIn( + ".agent-loop/features/2026-05-08-login/large-payload.bin", + payload["snapshots"], + ) + + def test_current_month_is_blocked_without_mutation(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-07-01-current") + before = tree_snapshot(workspace.project_root) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-07", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertEqual(payload["moves"], []) + self.assertIn("current-month", payload["candidates"][0]["blockers"]) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def test_active_blocked_and_paused_lifecycles_are_ineligible(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + for day, status in (("08", "active"), ("09", "blocked"), ("10", "paused")): + workspace.feature(f"2026-05-{day}-{status}", status=status) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertEqual(payload["moves"], []) + for candidate in payload["candidates"]: + self.assertIn("lifecycle", " ".join(candidate["blockers"])) + + def test_incomplete_close_evidence_is_blocked(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login", close_complete=False) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertEqual(payload["moves"], []) + self.assertEqual(payload["candidates"][0]["close_evidence"], "incomplete") + + def test_missing_archive_readiness_is_blocked(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature = workspace.feature("2026-05-08-login") + notes = feature / "notes.md" + notes.write_text( + notes.read_text(encoding="utf-8").split("## Archive Readiness", 1)[0], + encoding="utf-8", + newline="\n", + ) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertIn( + "missing-archive-readiness", payload["candidates"][0]["blockers"] + ) + + def test_placeholder_delivered_summary_is_blocked(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature = workspace.feature("2026-05-08-login") + notes = feature / "notes.md" + notes.write_text( + notes.read_text(encoding="utf-8").replace( + "Delivered Summary: completed 2026-05-08-login", + "Delivered Summary: TODO", + ), + encoding="utf-8", + newline="\n", + ) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertIn( + "non-concrete-delivered-summary", + payload["candidates"][0]["blockers"], + ) + + def test_non_terminal_archive_readiness_value_is_blocked(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature = workspace.feature("2026-05-08-login") + notes = feature / "notes.md" + notes.write_text( + notes.read_text(encoding="utf-8").replace( + "Verification: complete", "Verification: pending" + ), + encoding="utf-8", + newline="\n", + ) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertIn( + "archive-readiness-verification:pending", + payload["candidates"][0]["blockers"], + ) + + def test_open_follow_up_is_blocked(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature = workspace.feature("2026-05-08-login") + notes = feature / "notes.md" + notes.write_text( + notes.read_text(encoding="utf-8").replace( + "Open Follow-up: none", "Open Follow-up: FU-17" + ), + encoding="utf-8", + newline="\n", + ) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertEqual(payload["moves"], []) + self.assertEqual(payload["candidates"][0]["open_follow_up"], "FU-17") + + def test_flat_and_month_path_collision_fails_closed_without_mutation(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login") + archived = workspace.features_root / "2026-05" / "2026-05-08-login" + archived.mkdir(parents=True) + before = tree_snapshot(workspace.project_root) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("path-collision", result.stdout + result.stderr) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def test_project_memory_active_or_paused_feature_is_blocked(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login") + workspace.write( + ".agent-loop/project.md", + "# Project\n\nActive Feature: 2026-05-08-login\nPaused Features: none\n", + ) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertIn("project-memory-active", payload["candidates"][0]["blockers"]) + + def test_scan_precomputes_literal_and_relative_link_edits(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + feature = workspace.feature(feature_id) + workspace.write( + ".agent-loop/project.md", + f"# Project\n\nDelivered: `.agent-loop/features/{feature_id}/spec.md`\n", + ) + workspace.write( + f".agent-loop/features/{feature_id}/links.md", + "# Links\n\n[Project memory](../../project.md)\n" + "[External](https://example.com/docs)\n", + ) + before = tree_snapshot(workspace.project_root) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + edits = payload["reference_edits"] + self.assertTrue( + any( + edit["kind"] == "literal-path" + and edit["path"] == ".agent-loop/project.md" + and edit["new"] + == f".agent-loop/features/2026-05/{feature_id}/" + for edit in edits + ) + ) + self.assertTrue( + any( + edit["kind"] == "relative-link" + and edit["path"] + == f".agent-loop/features/{feature_id}/links.md" + and edit["old"] == "../../project.md" + and edit["new"] == "../../../project.md" + for edit in edits + ) + ) + self.assertTrue(feature.is_dir()) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def test_requirement_sources_and_historical_reports_are_preserved(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + old_path = f".agent-loop/features/{feature_id}/spec.md" + workspace.write( + ".agent-loop/requirements/2026-05-login/requirement.md", + f"# Human Requirement\n\nOriginal evidence: `{old_path}`\n", + ) + workspace.write( + "docs/reports/2026-05-login-validation.md", + f"# Historical Validation Report\n\nAt execution time: `{old_path}`\n", + ) + before = tree_snapshot(workspace.project_root) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + classifications = { + item["path"]: item["classification"] + for item in payload["skipped_references"] + } + self.assertEqual( + classifications[ + ".agent-loop/requirements/2026-05-login/requirement.md" + ], + "immutable-requirement-source", + ) + self.assertEqual( + classifications["docs/reports/2026-05-login-validation.md"], + "historical-evidence", + ) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def test_ambiguous_old_path_reference_blocks_apply(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + workspace.write( + "docs/ambiguous.md", + f"# Ambiguous\n\nencoded=.agent-loop/features/{feature_id}%2Fspec.md\n", + ) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertTrue( + any( + item["path"] == "docs/ambiguous.md" + and item["classification"] == "unsupported" + for item in payload["skipped_references"] + ) + ) + + def test_large_markdown_with_old_path_is_unsupported_without_reading_it(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + large = workspace.project_root / "docs/large.md" + large.parent.mkdir(parents=True) + large.write_bytes( + f".agent-loop/features/{feature_id}/".encode("utf-8") + + b"x" * (2 * 1024 * 1024 + 1) + ) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertTrue( + any( + item["path"] == "docs/large.md" + and item["classification"] == "unsupported" + for item in payload["skipped_references"] + ) + ) + + def test_rehydrate_scan_plans_month_to_flat_without_mutation(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + source = workspace.feature(feature_id) + archived = workspace.features_root / "2026-05" / feature_id + archived.parent.mkdir(parents=True) + source.rename(archived) + workspace.write( + ".agent-loop/features/archive.md", + ARCHIVE_HEADER + archive_row(feature_id), + ) + before = tree_snapshot(workspace.project_root) + result = self.scan( + workspace, + "--operation", + "rehydrate", + "--feature-id", + feature_id, + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + self.assertEqual( + payload["moves"], + [ + { + "feature_id": feature_id, + "month": "2026-05", + "source": f".agent-loop/features/2026-05/{feature_id}", + "target": f".agent-loop/features/{feature_id}", + } + ], + ) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + def test_broken_cross_boundary_relative_link_blocks_apply(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + workspace.write( + f".agent-loop/features/{feature_id}/links.md", + "# Links\n\n[missing](../../missing.md)\n", + ) + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json_output(result) + unsupported = [ + item + for item in payload["skipped_references"] + if item["classification"] == "unsupported" + ] + self.assertTrue(unsupported) + self.assertIn("broken", unsupported[0]["reason"]) + + def test_scan_rewrites_relative_link_from_project_memory_into_feature(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + feature_id = "2026-05-08-login" + workspace.feature(feature_id) + workspace.write( + ".agent-loop/project.md", + f"# Project\n\n[Feature](features/{feature_id}/spec.md)\n", + ) + + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + payload = json_output(result) + self.assertTrue( + any( + edit["kind"] == "relative-link" + and edit["path"] == ".agent-loop/project.md" + and edit["old"] == f"features/{feature_id}/spec.md" + and edit["new"] + == f"features/2026-05/{feature_id}/spec.md" + for edit in payload["reference_edits"] + ) + ) + + def test_scan_rejects_stranded_archive_transaction(self) -> None: + with tempfile.TemporaryDirectory() as temp: + workspace = ArchiveWorkspace(Path(temp)) + workspace.feature("2026-05-08-login") + transaction = workspace.features_root / ".archive-txn" / TRANSACTION_ID + transaction.mkdir(parents=True) + before = tree_snapshot(workspace.project_root) + + result = self.scan( + workspace, + "--operation", + "archive", + "--month", + "2026-05", + "--as-of", + "2026-07-14", + ) + + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("stranded-transaction", result.stdout + result.stderr) + self.assertIn(TRANSACTION_ID, result.stdout + result.stderr) + self.assertEqual(tree_snapshot(workspace.project_root), before) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_onboarding_core_flow_coverage.py b/tests/test_onboarding_core_flow_coverage.py new file mode 100644 index 0000000..a413ffc --- /dev/null +++ b/tests/test_onboarding_core_flow_coverage.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +import shutil +import tempfile +import unittest +from pathlib import Path + +from tests.checker_test_support import ROOT, combined_output, run_checker + + +SCRIPT = "scripts/check-onboarding-core-flow-coverage.py" +EXAMPLE = ROOT / "examples/ai-meeting-minutes-backend/onboarding-db" +FIXTURES = ROOT / "tests/fixtures/onboarding-core-flow" + + +class OnboardingCoreFlowCoverageTests(unittest.TestCase): + def assert_passes(self, root: Path, expected: str) -> None: + result = run_checker(SCRIPT, str(root)) + self.assertEqual(result.returncode, 0, combined_output(result)) + self.assertIn(expected, result.stdout) + + def assert_fails(self, root: Path, expected: str) -> None: + result = run_checker(SCRIPT, str(root)) + self.assertEqual(result.returncode, 1, combined_output(result)) + self.assertIn(expected, combined_output(result)) + + def test_planned_example_passes(self) -> None: + self.assert_passes(EXAMPLE, "1 planned, 0 deferred") + + def test_reasoned_deferred_flow_passes(self) -> None: + self.assert_passes(FIXTURES / "valid-deferred", "0 planned, 1 deferred") + + def test_missing_recovery_slice_fails(self) -> None: + self.assert_fails( + FIXTURES / "invalid-missing-recovery", + "missing required slice: CF-ORDER-PAYMENT/S07", + ) + + def test_detached_diagram_trace_fails(self) -> None: + self.assert_fails( + FIXTURES / "invalid-detached-trace", + "missing diagram definition: D-RECOVERY", + ) + + def test_bom_and_crlf_are_supported(self) -> None: + with tempfile.TemporaryDirectory() as temp: + copied = Path(temp) / "onboarding-db" + shutil.copytree(EXAMPLE, copied) + for path in copied.rglob("*.md"): + text = path.read_text(encoding="utf-8") + path.write_bytes(("\ufeff" + text.replace("\n", "\r\n")).encode("utf-8")) + self.assert_passes(copied, "1 planned, 0 deferred") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_python_checker_contract.py b/tests/test_python_checker_contract.py new file mode 100644 index 0000000..9806421 --- /dev/null +++ b/tests/test_python_checker_contract.py @@ -0,0 +1,250 @@ +from __future__ import annotations + +import ast +import hashlib +import io +import shutil +import sys +import tempfile +import unittest +from contextlib import redirect_stderr +from pathlib import Path + +from tests.checker_test_support import ROOT, combined_output, run_checker + + +CHECKERS = ( + "scripts/check-root-agents-blocks.py", + "scripts/check-onboarding-core-flow-coverage.py", + "scripts/check-concept-foundation-trace.py", + "scripts/check-adr-requirement-model-trace.py", +) + +ARCHIVE_COMMANDS = ( + "scripts/scan-feature-monthly-archive.py", + "scripts/check-feature-monthly-archive.py", + "scripts/apply-feature-monthly-archive.py", + "scripts/restore-feature-monthly-archive.py", +) + +WORKFLOW = ROOT / ".github/workflows/cross-platform-checkers.yml" + +COMPATIBILITY_ENTRIES = { + "scripts/check-root-agents-blocks.sh": "check-root-agents-blocks.py", + "scripts/check-onboarding-core-flow-coverage.rb": "check-onboarding-core-flow-coverage.py", + "scripts/check-concept-foundation-trace.rb": "check-concept-foundation-trace.py", + "scripts/check-adr-requirement-model-trace.rb": "check-adr-requirement-model-trace.py", +} + +CURRENT_AUTHORITY = ( + "SKILL.md", + "Usage.md", + "references/project-guidance.md", + "references/workflow-checklists.md", +) + + +class PythonCheckerContractTests(unittest.TestCase): + @staticmethod + def snapshot(root: Path) -> dict[str, str]: + return { + path.relative_to(root).as_posix(): hashlib.sha256(path.read_bytes()).hexdigest() + for path in sorted(root.rglob("*")) + if path.is_file() + } + + def test_python_runtime_is_supported(self) -> None: + self.assertGreaterEqual(sys.version_info[:2], (3, 10)) + + def test_canonical_checker_files_exist(self) -> None: + for relative in CHECKERS: + with self.subTest(relative=relative): + self.assertTrue((ROOT / relative).is_file(), relative) + + def test_archive_command_files_exist(self) -> None: + for relative in ARCHIVE_COMMANDS: + with self.subTest(relative=relative): + self.assertTrue((ROOT / relative).is_file(), relative) + + def test_canonical_checkers_use_only_stdlib_and_local_support(self) -> None: + allowed_local = {"checker_support", "feature_archive_support"} + for relative in CHECKERS: + path = ROOT / relative + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + imported: set[str] = set() + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imported.update(alias.name.split(".", 1)[0] for alias in node.names) + elif isinstance(node, ast.ImportFrom) and node.module: + imported.add(node.module.split(".", 1)[0]) + external = imported - set(sys.stdlib_module_names) - allowed_local + self.assertEqual(external, set(), f"{relative}: {sorted(external)}") + + def test_archive_commands_use_only_stdlib_and_local_support(self) -> None: + allowed_local = {"checker_support", "feature_archive_support"} + for relative in ARCHIVE_COMMANDS: + path = ROOT / relative + self.assertTrue(path.is_file(), relative) + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + imported: set[str] = set() + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imported.update(alias.name.split(".", 1)[0] for alias in node.names) + elif isinstance(node, ast.ImportFrom) and node.module: + imported.add(node.module.split(".", 1)[0]) + external = imported - set(sys.stdlib_module_names) - allowed_local + self.assertEqual(external, set(), f"{relative}: {sorted(external)}") + + def test_missing_arguments_fail_with_usage_exit_two(self) -> None: + for relative in CHECKERS: + with self.subTest(relative=relative): + result = run_checker(relative) + self.assertEqual(result.returncode, 2, combined_output(result)) + self.assertIn("usage", combined_output(result).lower()) + + def test_archive_commands_missing_arguments_fail_with_usage_exit_two(self) -> None: + for relative in ARCHIVE_COMMANDS: + with self.subTest(relative=relative): + result = run_checker(relative) + self.assertEqual(result.returncode, 2, combined_output(result)) + self.assertIn("usage", combined_output(result).lower()) + + def test_unsupported_python_fails_closed_with_exit_two(self) -> None: + scripts_dir = str(ROOT / "scripts") + if scripts_dir not in sys.path: + sys.path.insert(0, scripts_dir) + from checker_support import require_supported_python + + stderr = io.StringIO() + with redirect_stderr(stderr), self.assertRaises(SystemExit) as caught: + require_supported_python((3, 9)) + self.assertEqual(caught.exception.code, 2) + self.assertIn("Python 3.10+ is required", stderr.getvalue()) + + for relative in CHECKERS: + with self.subTest(relative=relative): + path = ROOT / relative + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + calls = { + node.func.id + for node in ast.walk(tree) + if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) + } + self.assertIn("require_supported_python", calls) + + for relative in ARCHIVE_COMMANDS: + with self.subTest(relative=relative): + path = ROOT / relative + self.assertTrue(path.is_file(), relative) + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + calls = { + node.func.id + for node in ast.walk(tree) + if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) + } + self.assertIn("require_supported_python", calls) + + def test_cross_platform_ci_runs_the_native_suite(self) -> None: + self.assertTrue(WORKFLOW.is_file(), str(WORKFLOW)) + content = WORKFLOW.read_text(encoding="utf-8") + for required in ( + "macos-latest", + "windows-latest", + '"3.10"', + '"3.x"', + "tests.test_python_checker_contract", + "tests.test_root_agents_blocks", + "tests.test_onboarding_core_flow_coverage", + "tests.test_concept_foundation_trace", + "tests.test_adr_requirement_model_trace", + "tests.test_feature_archive_support", + "tests.test_feature_monthly_archive_scan", + "tests.test_feature_monthly_archive_apply", + "tests.test_feature_monthly_archive_restore", + *ARCHIVE_COMMANDS, + ): + with self.subTest(required=required): + self.assertIn(required, content) + + def test_old_entrypoints_are_marked_thin_compatibility_launchers(self) -> None: + for relative, canonical in COMPATIBILITY_ENTRIES.items(): + with self.subTest(relative=relative): + content = (ROOT / relative).read_text(encoding="utf-8") + self.assertIn("DEPRECATED COMPATIBILITY ENTRY", content) + self.assertIn(canonical, content) + self.assertIn("py", content) + self.assertIn("-3", content) + self.assertLessEqual(len(content.splitlines()), 24) + + def test_current_authority_does_not_recommend_legacy_entrypoints(self) -> None: + legacy_paths = tuple(COMPATIBILITY_ENTRIES) + for relative in CURRENT_AUTHORITY: + content = (ROOT / relative).read_text(encoding="utf-8") + with self.subTest(relative=relative): + for legacy in legacy_paths: + self.assertNotIn(legacy, content) + changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8") + for canonical in CHECKERS: + with self.subTest(canonical=canonical): + self.assertIn(canonical, changelog) + + def test_valid_checker_runs_do_not_mutate_checked_artifacts(self) -> None: + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + + root_case = root / "root-agents" + (root_case / ".agent-loop").mkdir(parents=True) + (root_case / ".agent-loop/project.md").touch() + template = root_case / "template.md" + target = root_case / "AGENTS.md" + shutil.copy2(ROOT / "templates/root-AGENTS.md", template) + shutil.copy2(template, target) + + onboarding = root / "onboarding" + shutil.copytree( + ROOT / "examples/ai-meeting-minutes-backend/onboarding-db", onboarding + ) + + concept = root / "concept" + shutil.copytree(ROOT / "examples/concept-foundation-refund", concept) + + adr = root / "adr" + shutil.copytree(ROOT / "tests/fixtures/adr-technical-landing/valid", adr) + + runs = ( + ( + "scripts/check-root-agents-blocks.py", + "--template", + str(template), + "--target", + str(target), + ), + ("scripts/check-onboarding-core-flow-coverage.py", str(onboarding)), + ( + "scripts/check-concept-foundation-trace.py", + str(concept / "requirement.md"), + str(concept / "product.md"), + str(concept / "spec.md"), + ), + ( + "scripts/check-adr-requirement-model-trace.py", + str(adr / "README.md"), + str(adr / "requirement.md"), + str(adr / "decision.md"), + ), + ) + before = self.snapshot(root) + for script, *args in runs: + first = run_checker(script, *args) + second = run_checker(script, *args) + self.assertEqual(first.returncode, 0, combined_output(first)) + self.assertEqual(second.returncode, 0, combined_output(second)) + self.assertEqual( + (first.returncode, first.stdout, first.stderr), + (second.returncode, second.stdout, second.stderr), + ) + self.assertEqual(self.snapshot(root), before) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_root_agents_blocks.py b/tests/test_root_agents_blocks.py new file mode 100644 index 0000000..884ad68 --- /dev/null +++ b/tests/test_root_agents_blocks.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +import tempfile +import unittest +from pathlib import Path + +from tests.checker_test_support import ROOT, combined_output, run_checker + + +SCRIPT = "scripts/check-root-agents-blocks.py" +TEMPLATE = ROOT / "templates/root-AGENTS.md" + + +class RootAgentsBlocksTests(unittest.TestCase): + def setUp(self) -> None: + self.tempdir = tempfile.TemporaryDirectory() + self.addCleanup(self.tempdir.cleanup) + self.root = Path(self.tempdir.name) + (self.root / ".agent-loop").mkdir() + (self.root / ".agent-loop/project.md").touch() + self.template_text = TEMPLATE.read_text(encoding="utf-8") + + def check(self, text: str, *extra: str): + target = self.root / "AGENTS.md" + target.write_text(text, encoding="utf-8") + return run_checker( + SCRIPT, + "--template", + str(TEMPLATE), + "--target", + str(target), + *extra, + ) + + def remove_section(self, section: str) -> str: + start = f"" + lines: list[str] = [] + skipping = False + for line in self.template_text.splitlines(keepends=True): + if line.startswith(start): + skipping = True + continue + if line.rstrip("\r\n") == end: + skipping = False + continue + if not skipping: + lines.append(line) + return "".join(lines) + + def assert_invalid(self, text: str, expected: str) -> None: + result = self.check(text) + self.assertEqual(result.returncode, 1, combined_output(result)) + self.assertIn("FAIL root AGENTS drift found", result.stdout) + self.assertIn(expected, result.stdout) + + def test_current_template_passes(self) -> None: + result = self.check(self.template_text) + self.assertEqual(result.returncode, 0, combined_output(result)) + self.assertIn("PASS root AGENTS managed blocks are current", result.stdout) + + def test_missing_required_sections_fail(self) -> None: + for section in ("message-intent", "workflow-stage-map"): + with self.subTest(section=section): + self.assert_invalid(self.remove_section(section), f"{section} | missing") + + def test_stale_block_version_fails(self) -> None: + stale = self.template_text.replace( + "block-version:1.3.0-20260714.1", + "block-version:1.3.0", + 1, + ) + self.assert_invalid(stale, "stale-block-version") + + def test_broken_end_marker_fails(self) -> None: + broken = self.template_text.replace( + "", "", 1 + ) + self.assert_invalid(broken, "ownership | broken-markers") + + def test_nested_block_fails(self) -> None: + marker = "\n" + + "nested\n\n" + + self.template_text[line_end:] + ) + self.assert_invalid(nested, "ownership | nested-managed-block") + + def test_duplicate_section_fails(self) -> None: + marker = "" + duplicate = ( + f"{marker}\n\n" + f"duplicate\n{marker}" + ) + self.assert_invalid(self.template_text.replace(marker, duplicate, 1), "duplicate-section") + + def test_source_path_outside_project_fails_even_when_it_exists(self) -> None: + outside = self.root.parent / f"{self.root.name}-outside.md" + outside.write_text("outside", encoding="utf-8") + self.addCleanup(outside.unlink, missing_ok=True) + escaped = self.template_text.replace( + "source:.agent-loop/project.md", + f"source:../{outside.name}", + 1, + ) + self.assert_invalid(escaped, "source-outside-workspace") + + def test_bom_and_crlf_are_supported(self) -> None: + target = self.root / "AGENTS.md" + target.write_bytes(("\ufeff" + self.template_text.replace("\n", "\r\n")).encode("utf-8")) + result = run_checker( + SCRIPT, "--template", str(TEMPLATE), "--target", str(target) + ) + self.assertEqual(result.returncode, 0, combined_output(result)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/validate-adr-requirement-model-technical-landing-trace.sh b/tests/validate-adr-requirement-model-technical-landing-trace.sh new file mode 100644 index 0000000..709e0b4 --- /dev/null +++ b/tests/validate-adr-requirement-model-technical-landing-trace.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "$0")/.." && pwd) + +assert_contains() { + local file=$1 + local text=$2 + if ! grep -Fq -- "$text" "$root/$file"; then + printf 'FAIL: %s missing required text: %s\n' "$file" "$text" >&2 + exit 1 + fi +} + +assert_not_contains() { + local file=$1 + local text=$2 + if grep -Fq -- "$text" "$root/$file"; then + printf 'FAIL: %s contains forbidden text: %s\n' "$file" "$text" >&2 + exit 1 + fi +} + +assert_file_exists() { + local file=$1 + if [ ! -f "$root/$file" ]; then + printf 'FAIL: missing required file: %s\n' "$file" >&2 + exit 1 + fi +} + +# Published authority and stage placement. +assert_contains "SKILL.md" "Effective Requirement Snapshot" +assert_contains "SKILL.md" "Requirement Model Technical Landing Trace" +assert_contains "references/design.md" "Effective Requirement Snapshot" +assert_contains "references/runtime.md" "## ADR Requirement Model Technical Landing" +assert_contains "references/runtime.md" "Requirement Model Scope Inventory" +assert_contains "references/runtime.md" 'PERM-*' +assert_contains "references/runtime.md" 'EX-*' +assert_contains "references/runtime.md" 'structural preflight while the ADR is `proposed`' +assert_contains "references/runtime.md" "Upstream Compatibility: current | review-required" +assert_contains "references/runtime.md" 'review-required` is a dependency-availability judgment, not an ADR lifecycle status' +assert_contains "references/project-decisions.md" "## Effective Requirement Snapshot" +assert_contains "references/project-decisions.md" "## Requirement Model Scope Inventory" +assert_contains "references/project-decisions.md" "## Requirement Model Technical Landing Trace" +assert_contains "references/project-decisions.md" "## Coverage Hard Gate" +assert_contains "references/project-decisions.md" "## Upstream Compatibility And Drift" +assert_contains "references/project-decisions.md" "## Triggered Operational Landing" + +# Product ownership and acceptance hard stops. +assert_contains "references/project-decisions.md" "ADR must not create, rename, split, merge, or redefine a Concept, relationship, role/permission, command/event, business flow, product state, invariant, exception/recovery meaning, or product fact ownership." +assert_contains "references/project-decisions.md" 'A decision cannot become `accepted` while coverage is missing or Upstream Compatibility is `review-required`.' +assert_contains "references/stage-guides.md" "Decision & Design Human Review Summary" +assert_contains "references/stage-guides.md" "Effective Requirement Snapshot" +assert_contains "references/workflow-checklists.md" "Requirement Model Technical Landing Trace" +assert_contains "references/workflow-checklists.md" "Upstream Compatibility" +assert_contains "references/workflow-checklists.md" "Coverage Hard Gate" +assert_contains "templates/root-AGENTS.md" "Upstream Compatibility" +assert_contains "templates/root-AGENTS.md" "Requirement Model Technical Landing Trace" +assert_contains "references/project-guidance.md" "Requirement Model Technical Landing Trace" + +# Human review and generic ADR template. +assert_contains "references/human-review-summary.md" "### Decision & Design Approval" +assert_contains "references/human-review-summary.md" "| Effective Requirement Source |" +assert_contains "references/human-review-summary.md" "| Requirement Model Coverage |" +assert_contains "references/human-review-summary.md" "| Product Semantics Preserved |" +assert_contains "references/human-review-summary.md" "| Migration / Compatibility / Rollout |" +assert_contains "references/human-review-summary.md" "| Design Slice Ownership |" + +assert_contains "templates/decision.md" "## Effective Requirement Snapshot" +assert_contains "templates/decision.md" "Effective Concept Source:" +assert_contains "templates/decision.md" "Concept Foundation Status: accepted | concept-foundation-not-needed" +assert_contains "templates/decision.md" "Accepted Concept IDs:" +assert_contains "templates/decision.md" "Accepted Requirement Model IDs:" +assert_contains "templates/decision.md" "Upstream Compatibility: current | review-required" +assert_contains "templates/decision.md" "Last Compatibility Check:" +assert_contains "templates/decision.md" "Trace Applicability: required | not-applicable" +assert_contains "templates/decision.md" "Trace Not-Applicable Reason:" +assert_contains "templates/decision.md" "## Requirement Model Scope Inventory" +assert_contains "templates/decision.md" 'REL-*`, `PERM-*`, `CMD-*`, `EVT-*`, `FLOW-*`, `STATE-*`, `PM-*`, and `EX-*' +assert_contains "templates/decision.md" "## Requirement Model Technical Landing Trace" +assert_contains "templates/decision.md" "| Requirement Model Ref | Accepted Meaning / Constraint | Disposition | Technical Landing | Preserved Invariant | Design Slice | Verification |" +assert_contains "templates/decision.md" "landed | covered-by-accepted-decision | feature-local | not-applicable" +assert_contains "templates/decision.md" "## Coverage Hard Gate" +assert_contains "templates/decision.md" "Every source Requirement Model ID has an explicit scope disposition" +assert_contains "templates/decision.md" "## Human Review Evidence" +assert_contains "templates/decision.md" 'Run the structural validator while the ADR is still `proposed`.' +assert_contains "templates/decision.md" "## Upstream Compatibility And Drift" +assert_contains "templates/decision.md" "## Operational Landing Trigger Assessment" +assert_contains "templates/decision.md" "triggered | not-triggered" + +# Human docs and pressure scenarios. +assert_contains "README.md" "Requirement Model Technical Landing Trace" +assert_contains "Usage.md" "Effective Requirement Snapshot" +assert_contains "CHANGELOG.md" "Requirement Model Technical Landing Trace" +assert_contains "references/validation-scenarios.md" "ADR Requirement Model Technical Landing Trace" +assert_contains "references/validation-scenarios.md" "Stale Effective Requirement Snapshot Blocks ADR" +assert_contains "references/validation-scenarios.md" "Incomplete Landing Coverage Blocks Feature Spec" +assert_contains "references/validation-scenarios.md" "Accepted ADR Meaning Requires Supersede" +assert_contains "references/validation-scenarios.md" "Operational Landing Is Triggered, Not Default" + +# Scope exclusions: no stage, status, default mapping artifact, executable schema, or target-project workspace. +ruby -e ' + content = File.read(ARGV.fetch(0)) + section = content[/## Stage Order\n(.*?)(?=\n## |\z)/m, 1] + abort "FAIL: runtime Stage Order section missing" unless section + forbidden = ["Effective Requirement Snapshot", "Requirement Model Technical Landing Trace", "Coverage Hard Gate", "Upstream Compatibility Review"] + found = forbidden.select { |name| section.lines.any? { |line| line.strip == name } } + abort "FAIL: ADR trace concepts must not become canonical stages: #{found.join(", ")}" unless found.empty? +' "$root/references/runtime.md" +assert_contains "templates/decision.md" "Allowed Status: proposed | accepted | superseded | deprecated" +assert_not_contains "templates/decision.md" "Allowed Status: proposed | accepted | review-required" +assert_not_contains "references/project-decisions.md" ".agent-loop/requirement-model-mappings/" +assert_not_contains "templates/decision.md" "technical-landing.yaml" +assert_not_contains "templates/decision.md" "technical-landing.json" +if [ -e "$root/.agent-loop" ]; then + printf 'FAIL: skill source repository must not contain target-project .agent-loop artifacts\n' >&2 + exit 1 +fi + +# The generic template and validator must not learn fixture-specific business/action/technology values. +for forbidden in FixtureSubject FixtureOperator perform_fixture_action FixtureStore FixtureProtocol; do + assert_not_contains "templates/decision.md" "$forbidden" + assert_not_contains "scripts/check-adr-requirement-model-trace.py" "$forbidden" +done + +# Behavioral validation, not keyword-only assertions. +assert_file_exists "scripts/check-adr-requirement-model-trace.py" +valid="$root/tests/fixtures/adr-technical-landing/valid" +python3 "$root/scripts/check-adr-requirement-model-trace.py" "$valid/README.md" "$valid/requirement.md" "$valid/decision.md" +(cd "$root" && python3 -m unittest tests/test_adr_requirement_model_trace.py) + +not_needed="$root/tests/fixtures/adr-technical-landing/valid-not-needed" +python3 "$root/scripts/check-adr-requirement-model-trace.py" "$not_needed/README.md" "$not_needed/requirement.md" "$not_needed/decision.md" + +if python3 "$root/scripts/check-adr-requirement-model-trace.py" "$valid/README.md" "$valid/requirement.md" "$root/tests/fixtures/adr-technical-landing/invalid-missing-coverage/decision.md" >/dev/null 2>&1; then + printf 'FAIL: missing-coverage ADR fixture unexpectedly passed\n' >&2 + exit 1 +fi + +if python3 "$root/scripts/check-adr-requirement-model-trace.py" "$valid/README.md" "$valid/requirement.md" "$root/tests/fixtures/adr-technical-landing/invalid-empty-landing/decision.md" >/dev/null 2>&1; then + printf 'FAIL: empty-landing ADR fixture unexpectedly passed\n' >&2 + exit 1 +fi + +unaccepted="$root/tests/fixtures/adr-technical-landing/invalid-unaccepted-source" +if python3 "$root/scripts/check-adr-requirement-model-trace.py" "$unaccepted/README.md" "$unaccepted/requirement.md" "$valid/decision.md" >/dev/null 2>&1; then + printf 'FAIL: unaccepted-source ADR fixture unexpectedly passed\n' >&2 + exit 1 +fi + +reopened="$root/tests/fixtures/adr-technical-landing/invalid-reopened-source" +if python3 "$root/scripts/check-adr-requirement-model-trace.py" "$reopened/README.md" "$reopened/requirement.md" "$valid/decision.md" >/dev/null 2>&1; then + printf 'FAIL: reopened-source ADR fixture unexpectedly passed\n' >&2 + exit 1 +fi + +if python3 "$root/scripts/check-adr-requirement-model-trace.py" "$valid/README.md" "$valid/requirement.md" "$root/tests/fixtures/adr-technical-landing/invalid-review-required/decision.md" >/dev/null 2>&1; then + printf 'FAIL: review-required ADR fixture unexpectedly passed\n' >&2 + exit 1 +fi + +printf 'PASS: ADR Requirement Model Technical Landing Trace contract is complete\n' diff --git a/tests/validate-adr-requirement-model-trace-adversarial.rb b/tests/validate-adr-requirement-model-trace-adversarial.rb new file mode 100644 index 0000000..73a7113 --- /dev/null +++ b/tests/validate-adr-requirement-model-trace-adversarial.rb @@ -0,0 +1,238 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "fileutils" +require "open3" +require "tmpdir" + +root = File.expand_path("..", __dir__) +validator = File.join(root, "scripts/check-adr-requirement-model-trace.rb") +valid_dir = File.join(root, "tests/fixtures/adr-technical-landing/valid") +not_needed_dir = File.join(root, "tests/fixtures/adr-technical-landing/valid-not-needed") + +base_readme = File.read(File.join(valid_dir, "README.md")) +base_source = File.read(File.join(valid_dir, "requirement.md")) +base_decision = File.read(File.join(valid_dir, "decision.md")) + +failures = [] + +def run_validator(validator, fixture_dir, readme, source, decision) + Dir.mktmpdir("adr-technical-landing-adversarial") do |dir| + FileUtils.cp_r(File.join(fixture_dir, "."), dir) + readme_path = File.join(dir, "README.md") + source_path = File.join(dir, "requirement.md") + decision_path = File.join(dir, "decision.md") + File.write(readme_path, readme) + File.write(source_path, source) + File.write(decision_path, decision) + + stdout, stderr, status = Open3.capture3( + "ruby", + validator, + readme_path, + source_path, + decision_path + ) + [status.success?, (stdout + stderr).strip] + end +end + +def expect_accept(failures, name, validator, fixture_dir, readme, source, decision) + accepted, output = run_validator(validator, fixture_dir, readme, source, decision) + if accepted + puts "PASS: validator accepted #{name}" + else + failures << "expected acceptance for #{name}: #{output.lines.first}" + end +end + +def expect_reject(failures, name, validator, fixture_dir, readme, source, decision) + accepted, output = run_validator(validator, fixture_dir, readme, source, decision) + if accepted + failures << "validator accepted adversarial case: #{name}" + else + puts "PASS: validator rejected #{name} (#{output.lines.first})" + end +end + +proposed_decision = base_decision + .sub(/^Status: accepted$/, "Status: proposed") + .sub(/^## Human Review Evidence\n.*?(?=^## |\z)/m, "") +expect_accept( + failures, + "a proposed ADR structural preflight before Human Review", + validator, + valid_dir, + base_readme, + base_source, + proposed_decision +) + +expect_accept( + failures, + "an accepted ADR with recorded Human Review evidence", + validator, + valid_dir, + base_readme, + base_source, + base_decision +) + +expect_reject( + failures, + "an accepted ADR without recorded Human Review evidence", + validator, + valid_dir, + base_readme, + base_source, + base_decision.sub(/^## Human Review Evidence\n.*?(?=^## |\z)/m, "") +) + +expect_accept( + failures, + "an explicitly planned canonical Feature Spec owner path", + validator, + valid_dir, + base_readme, + base_source, + base_decision.gsub("features/fixture/spec.md", "planned:features/future-fixture/spec.md") +) + +delegated_decision = base_decision + .sub(", FLOW-FIXTURE-01", "") + .sub( + "| FLOW-FIXTURE-01 | in-scope | ADR-9000 |", + "| FLOW-FIXTURE-01 | proposed-decision | decisions/9001-follow-up.md |" + ) + .sub(/^\| FLOW-FIXTURE-01 \| source flow reference .*\n/, "") +expect_accept( + failures, + "a source model explicitly delegated to an existing proposed decision", + validator, + valid_dir, + base_readme, + base_source, + delegated_decision +) + +expect_reject( + failures, + "placeholder not-applicable reason", + validator, + valid_dir, + base_readme, + base_source, + base_decision.sub("reason: outside this coherent decision boundary", "reason: n/a") +) + +expect_reject( + failures, + "a Coverage Hard Gate replaced by one arbitrary checkbox", + validator, + valid_dir, + base_readme, + base_source, + base_decision.sub(/## Coverage Hard Gate\n.*?(?=^## |\z)/m, "## Coverage Hard Gate\n\n- [x] arbitrary check\n\n") +) + +expect_reject( + failures, + "a Coverage Hard Gate with an unsupported extra checkbox", + validator, + valid_dir, + base_readme, + base_source, + base_decision.sub( + "- [x] No unresolved product-semantic blocker remains", + "- [x] No unresolved product-semantic blocker remains\n- [x] arbitrary extra check" + ) +) + +expect_reject( + failures, + "unparsed garbage in Accepted Requirement Model IDs", + validator, + valid_dir, + base_readme, + base_source, + base_decision.sub(/^Accepted Requirement Model IDs:(.*)$/, 'Accepted Requirement Model IDs:\1, GARBAGE') +) + +expect_reject( + failures, + "a source model silently omitted from ADR scope", + validator, + valid_dir, + base_readme, + base_source, + base_decision + .sub(/, PM-FIXTURE-FACT/, "") + .gsub(/^\| PM-FIXTURE-FACT .*\n/, "") +) + +expect_reject( + failures, + "a covered-by-accepted-decision reference whose ADR does not exist", + validator, + valid_dir, + base_readme, + base_source, + base_decision.sub("decisions/8999-shared.md (ADR-8999)", "decisions/does-not-exist.md (ADR-DOES-NOT-EXIST)") +) + +expect_reject( + failures, + "a feature-local reference whose Feature Spec does not exist", + validator, + valid_dir, + base_readme, + base_source, + base_decision.sub("features/fixture/spec.md", "features/missing/spec.md") +) + +expect_reject( + failures, + "a Design Slice with an invalid coverage status", + validator, + valid_dir, + base_readme, + base_source, + base_decision.sub("| planned |", "| banana |") +) + +expect_reject( + failures, + "a triggered operational concern without its detail section", + validator, + valid_dir, + base_readme, + base_source, + base_decision.sub(/\n## Triggered Operational Landing\n.*?(?=\n## Design Slice Coverage)/m, "") +) + +expect_reject( + failures, + "an incomplete operational concern inventory", + validator, + valid_dir, + base_readme, + base_source, + base_decision.gsub(/^\| (?:Compatibility|Rollout \/ Cutover|Rollback \/ Reversibility) \|.*\n/, "") +) + +not_needed_readme = File.read(File.join(not_needed_dir, "README.md")) +not_needed_source = File.read(File.join(not_needed_dir, "requirement.md")) +not_needed_decision = File.read(File.join(not_needed_dir, "decision.md")) +expect_accept( + failures, + "a reasoned concept-foundation-not-needed ADR preflight without a product model", + validator, + not_needed_dir, + not_needed_readme, + not_needed_source, + not_needed_decision +) + +abort failures.map { |failure| "FAIL: #{failure}" }.join("\n") unless failures.empty? + +puts "PASS: ADR technical landing adversarial contract is complete" diff --git a/tests/validate-concept-foundation-requirement-modeling.sh b/tests/validate-concept-foundation-requirement-modeling.sh new file mode 100644 index 0000000..fbec9ce --- /dev/null +++ b/tests/validate-concept-foundation-requirement-modeling.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "$0")/.." && pwd) + +assert_contains() { + local file=$1 + local text=$2 + if ! grep -Fq -- "$text" "$root/$file"; then + printf 'FAIL: %s missing required text: %s\n' "$file" "$text" >&2 + exit 1 + fi +} + +assert_not_contains() { + local file=$1 + local text=$2 + if grep -Fq -- "$text" "$root/$file"; then + printf 'FAIL: %s contains forbidden text: %s\n' "$file" "$text" >&2 + exit 1 + fi +} + +assert_before() { + local file=$1 + local first=$2 + local second=$3 + ruby -e ' + content = File.read(ARGV.fetch(0)) + first = ARGV.fetch(1) + second = ARGV.fetch(2) + first_at = content.index(first) + second_at = content.index(second) + abort "FAIL: #{ARGV.fetch(0)} missing ordering marker: #{first}" unless first_at + abort "FAIL: #{ARGV.fetch(0)} missing ordering marker: #{second}" unless second_at + abort "FAIL: #{ARGV.fetch(0)} must place #{first} before #{second}" unless first_at < second_at + ' "$root/$file" "$first" "$second" +} + +# Published controller/design/runtime placement and gate contract. +assert_contains "SKILL.md" "Concept Foundation" +assert_contains "SKILL.md" "Requirement Product Model" +assert_contains "references/design.md" "Concept Foundation is an internal Requirements Discussion / Requirement Product Grill method, not a canonical stage" +assert_contains "references/runtime.md" "## Concept Foundation Routing" +assert_contains "references/runtime.md" "## Human Grill Contract" +assert_before "references/runtime.md" "inspect available evidence" "extract candidate concepts" +assert_before "references/runtime.md" "extract candidate concepts" "present one recommended definition" +assert_before "references/runtime.md" "present one recommended definition" "ask exactly one downstream-blocking question" +assert_contains "references/runtime.md" 'candidate | accepted | reopened | concept-foundation-not-needed' +assert_contains "references/runtime.md" 'Do not enter Business Flow, State Model, or Product Data Model while a triggered Concept Foundation is `candidate` or `reopened`.' + +# Phase 1: evidence-first candidates, single Human Grill question, and lightweight bypass. +assert_contains "references/requirement-product-grill.md" "## Concept Foundation" +assert_contains "references/requirement-product-grill.md" "## Human Grill Contract" +assert_contains "references/requirement-product-grill.md" "Concept Candidate Inventory" +assert_contains "references/requirement-product-grill.md" "Concept ID" +assert_contains "references/requirement-product-grill.md" "concept-foundation-not-needed" +assert_contains "references/requirement-product-grill.md" "one downstream-blocking question" +assert_contains "references/requirement-management.md" "Concept Foundation Status" +assert_contains "references/requirement-management.md" "Effective Concept Foundation" +assert_contains "references/requirement-management.md" "append-only Concept Foundation follow-up" +assert_contains "references/stage-guides.md" "Concept Foundation Gate" +assert_contains "references/workflow-checklists.md" "## Concept Foundation Gate" +assert_contains "templates/requirement-set-README.md" "## Effective Concept Foundation" +assert_contains "templates/requirement-set-README.md" "Effective Source:" + +# Phase 1/2 requirement document order and product-model derivation. +assert_before "references/document-templates.md" "## Concept Foundation" "## Primary Business Flow" +assert_before "references/document-templates.md" "## Concept Definitions" "## Concept Relationships" +assert_before "references/document-templates.md" "## Concept Relationships" "## Product State Model" +assert_before "references/document-templates.md" "## Product State Model" "## Requirement Product Model" +assert_contains "references/document-templates.md" "## Concept Candidate Inventory" +assert_contains "references/document-templates.md" "## Concept Definitions" +assert_contains "references/document-templates.md" "## Concept Relationships" +assert_contains "references/document-templates.md" "## Role / Permission Matrix" +assert_contains "references/document-templates.md" "Permission Rule ID" +assert_contains "references/document-templates.md" "PERM-01" +assert_contains "references/document-templates.md" "## Commands / Events" +assert_contains "references/document-templates.md" "## Product State Model" +assert_contains "references/document-templates.md" "## Requirement Product Model" +assert_contains "references/document-templates.md" "## Concept-To-Product Traceability" +assert_contains "references/document-templates.md" "EX-01" +assert_contains "references/document-templates.md" "Human Confirmation" + +# Phase 2 consumers cite accepted product semantics instead of redefining them. +assert_contains "references/product-brief.md" "Accepted Concept Foundation" +assert_contains "references/product-brief.md" "Product Brief consumes the accepted Requirement Product Model" +assert_contains "templates/product.md" "## Accepted Concept References" +assert_contains "templates/product.md" "## Requirement Product Model Coverage" +assert_contains "templates/spec.md" "## Accepted Concept References" +assert_contains "templates/spec.md" "## Requirement Product Model Trace" +assert_contains "references/project-decisions.md" "PRD / Requirement Product Model owns product meaning" +assert_contains "references/project-decisions.md" "ADR consumes accepted product semantics" + +# Root navigation and stop behavior remain coordinated without a new stage. +assert_contains "templates/root-AGENTS.md" "unresolved Concept Foundation" +assert_contains "references/project-guidance.md" "Concept Foundation Gate" +assert_contains "README.md" "Concept Foundation" +assert_contains "Usage.md" "一次只确认一个真正阻塞后续模型的问题" +assert_contains "references/validation-scenarios.md" "Concept Foundation And Product Model Derivation" +assert_contains "references/human-review-summary.md" "## Concept Foundation Approval" +assert_contains "references/stage-guides.md" "Concept Foundation Human Review Summary" + +# Explicit Phase 3/4 and artifact-path exclusions. +assert_not_contains "templates/root-AGENTS.md" "| Concept Foundation |" +ruby -e ' + content = File.read(ARGV.fetch(0)) + section = content[/## Stage Order\n(.*?)(?=\n## |\z)/m, 1] + abort "FAIL: runtime Stage Order section missing" unless section + if section.lines.any? { |line| line.strip == "Concept Foundation" || line.match?(/(?:^|→)\s*Concept Foundation\s*(?:→|$)/) } + abort "FAIL: Concept Foundation must not be a canonical stage" + end +' "$root/references/runtime.md" +assert_not_contains "templates/decision.md" "Concept To Technical Representation" +assert_not_contains "templates/decision.md" "Concept ID | Product Meaning | Technical Representation" +assert_not_contains "references/project-decisions.md" "Phase 1/2" +assert_not_contains "templates/decision.md" "Phase 1/2" +assert_not_contains "references/document-templates.md" ".agent-loop/concepts/" +assert_not_contains "references/document-templates.md" "concept-foundation.yaml" +assert_not_contains "references/document-templates.md" "concept-foundation.json" +if [ -e "$root/.agent-loop/concepts" ] || [ -e "$root/.agent-loop" ]; then + printf 'FAIL: skill source repository must not contain target-project .agent-loop artifacts\n' >&2 + exit 1 +fi + +# Behavioral artifact trace: valid chain passes; unaccepted and detached chains fail. +python3 "$root/scripts/check-concept-foundation-trace.py" \ + "$root/examples/concept-foundation-refund/requirement.md" \ + "$root/examples/concept-foundation-refund/product.md" \ + "$root/examples/concept-foundation-refund/spec.md" + +assert_contains "examples/concept-foundation-refund/requirement.md" "| PERM-ADMIN-SETTLEMENT | C-REFUND-ADMIN | C-REFUND-SETTLEMENT |" + +(cd "$root" && python3 -m unittest tests/test_concept_foundation_trace.py) + +if python3 "$root/scripts/check-concept-foundation-trace.py" \ + "$root/tests/fixtures/concept-foundation/invalid-unaccepted/requirement.md" \ + "$root/tests/fixtures/concept-foundation/invalid-unaccepted/product.md" \ + "$root/tests/fixtures/concept-foundation/invalid-unaccepted/spec.md" >/dev/null 2>&1; then + printf 'FAIL: unaccepted Concept Foundation fixture unexpectedly passed\n' >&2 + exit 1 +fi + +if python3 "$root/scripts/check-concept-foundation-trace.py" \ + "$root/tests/fixtures/concept-foundation/invalid-detached-model/requirement.md" \ + "$root/tests/fixtures/concept-foundation/invalid-detached-model/product.md" \ + "$root/tests/fixtures/concept-foundation/invalid-detached-model/spec.md" >/dev/null 2>&1; then + printf 'FAIL: detached product-model fixture unexpectedly passed\n' >&2 + exit 1 +fi + +printf 'PASS: Concept Foundation Phase 1/2 runtime and artifact trace contract is complete\n' diff --git a/tests/validate-concept-foundation-trace-adversarial.rb b/tests/validate-concept-foundation-trace-adversarial.rb new file mode 100644 index 0000000..7206604 --- /dev/null +++ b/tests/validate-concept-foundation-trace-adversarial.rb @@ -0,0 +1,129 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "fileutils" +require "open3" +require "tmpdir" + +root = File.expand_path("..", __dir__) +validator = File.join(root, "scripts/check-concept-foundation-trace.rb") +example_dir = File.join(root, "examples/concept-foundation-refund") + +base_requirement = File.read(File.join(example_dir, "requirement.md")) +base_product = File.read(File.join(example_dir, "product.md")) +base_spec = File.read(File.join(example_dir, "spec.md")) + +def expect_reject(name, validator, requirement, product, spec) + Dir.mktmpdir("concept-foundation-adversarial") do |dir| + requirement_path = File.join(dir, "requirement.md") + product_path = File.join(dir, "product.md") + spec_path = File.join(dir, "spec.md") + + File.write(requirement_path, requirement) + File.write(product_path, product) + File.write(spec_path, spec) + + _stdout, _stderr, status = Open3.capture3( + "ruby", + validator, + requirement_path, + product_path, + spec_path + ) + + abort "FAIL: validator accepted adversarial case: #{name}" if status.success? + end + + puts "PASS: validator rejected #{name}" +end + +expect_reject( + "downstream use of unconfirmed concepts", + validator, + base_requirement.sub( + /^- Confirmed Concept IDs:.*/, + "- Confirmed Concept IDs: C-CUSTOMER" + ), + base_product, + base_spec +) + +expect_reject( + "missing Concept Candidate Inventory", + validator, + base_requirement.sub( + /^## Concept Candidate Inventory\n.*?(?=^## Concept Definitions\n)/m, + "" + ), + base_product, + base_spec +) + +expect_reject( + "open blocking ambiguity", + validator, + base_requirement.sub("| resolved |", "| open |"), + base_product, + base_spec +) + +not_needed_requirement = base_requirement + .sub("Concept Foundation Status: accepted", "Concept Foundation Status: concept-foundation-not-needed") + .sub(/^Not-Needed Reason:.*/, "Not-Needed Reason: n/a") +not_needed_product = base_product.sub( + "Concept Foundation Status: accepted", + "Concept Foundation Status: concept-foundation-not-needed" +) +not_needed_spec = base_spec.sub( + "Concept Foundation Status: accepted", + "Concept Foundation Status: concept-foundation-not-needed" +) + +expect_reject( + "non-concrete concept-foundation-not-needed reason", + validator, + not_needed_requirement, + not_needed_product, + not_needed_spec +) + +expect_reject( + "downstream model omitted from Concept-To-Product trace", + validator, + base_requirement.sub(/^\| TRACE-03 .*\n/, ""), + base_product, + base_spec +) + +duplicate_definition = File.readlines(File.join(example_dir, "requirement.md")) + .find { |line| line.start_with?("| C-CUSTOMER |") } +duplicate_requirement = base_requirement.sub( + duplicate_definition, + duplicate_definition * 2 +) + +expect_reject( + "duplicate Concept Definition ID", + validator, + duplicate_requirement, + base_product, + base_spec +) + +expect_reject( + "missing effective Concept Foundation source", + validator, + base_requirement, + base_product.sub(/^Effective Concept Source:.*\n/, ""), + base_spec +) + +expect_reject( + "command actor without target permission", + validator, + base_requirement.sub(/^\| PERM-ADMIN-SETTLEMENT \| C-REFUND-ADMIN \| C-REFUND-SETTLEMENT .*\n/, ""), + base_product, + base_spec +) + +puts "PASS: Concept Foundation adversarial trace cases are rejected" diff --git a/tests/validate-evidence-graph-ddd-onboarding.sh b/tests/validate-evidence-graph-ddd-onboarding.sh index 7afe9e2..68f112f 100644 --- a/tests/validate-evidence-graph-ddd-onboarding.sh +++ b/tests/validate-evidence-graph-ddd-onboarding.sh @@ -75,19 +75,20 @@ require_text "references/onboarding-knowledge-base.md" "架构/边界图" require_text "references/onboarding-knowledge-base.md" "Mermaid flowchart" require_text "references/onboarding-knowledge-base.md" "Mermaid sequenceDiagram" require_text "references/onboarding-knowledge-base.md" "Timeline Diagram" -require_text "references/onboarding-knowledge-base.md" "状态图优先" -require_text "references/onboarding-knowledge-base.md" "模块文档默认必须包含架构/边界图、ASCII 状态图、Timeline / 时序图" -require_text "references/onboarding-knowledge-base.md" "流程文档默认必须包含架构/边界图、ASCII 状态图、Timeline / 时序图" +require_text "references/onboarding-knowledge-base.md" "## Core Flow Inventory" +require_text "references/onboarding-knowledge-base.md" "## Flow Slice Coverage" +require_text "references/onboarding-knowledge-base.md" "## Core Flow Diagram Set" +require_text "references/onboarding-knowledge-base.md" "## Complexity-Triggered Diagrams" +require_text "references/onboarding-knowledge-base.md" "## Completeness Hard Gate" require_text "references/onboarding-knowledge-base.md" "content-bearing onboarding-db document" -require_text "references/onboarding-knowledge-base.md" "must include the required diagram set" +require_text "references/onboarding-knowledge-base.md" "must use diagrams that explain real semantics" require_text "references/onboarding-knowledge-base.md" "Explicitly exempted docs" require_text "references/onboarding-knowledge-base.md" "coverage-matrix.md" require_text "references/onboarding-knowledge-base.md" "batch-review.md" require_text "references/onboarding-knowledge-base.md" "open-questions.md" -require_text "references/onboarding-knowledge-base.md" "Timeline / 时序图 is required by default for module and flow docs" -require_text "references/onboarding-knowledge-base.md" "List state diagrams and timeline/sequence diagrams before optional supporting diagrams" -require_text "references/onboarding-knowledge-base.md" "Sequence and flow diagrams may use Mermaid" -require_text "references/onboarding-knowledge-base.md" "State machine diagrams and complex example diagrams should use ASCII" +require_text "references/onboarding-knowledge-base.md" "primary per-flow narrative" +require_text "references/onboarding-knowledge-base.md" "stateless topics do not require state diagrams" +require_text "references/onboarding-knowledge-base.md" "missing critical slice cannot be averaged away" require_text "references/onboarding-knowledge-base.md" "核心原理" require_text "references/onboarding-knowledge-base.md" "示例图" require_text "references/onboarding-knowledge-base.md" "流程讲解时顺带解释涉及的数据模型" @@ -122,16 +123,17 @@ require_text "references/stage-guides.md" "Onboarding Spec" require_text "references/stage-guides.md" "Onboarding Tasks" require_text "references/stage-guides.md" "Full Execution Gate 确认后 Agent 可以全盘执行" require_text "references/stage-guides.md" "状态图优先" -require_text "references/stage-guides.md" "状态机/决策图" +require_text "references/stage-guides.md" "Flow Slice Coverage" +require_text "references/stage-guides.md" "Completeness Hard Gate" require_text "references/workflow-checklists.md" "default to single long files" require_text "references/workflow-checklists.md" "wireframe" require_text "references/workflow-checklists.md" "batch is not a human gate" require_text "references/workflow-checklists.md" "diagram type" -require_text "references/workflow-checklists.md" "state diagram first" -require_text "references/workflow-checklists.md" "module docs require architecture/boundary + state + timeline/sequence" -require_text "references/workflow-checklists.md" "flow docs require architecture/boundary + state + timeline/sequence" -require_text "references/workflow-checklists.md" "content-bearing onboarding-db document" -require_text "references/workflow-checklists.md" "explicit exemption" +require_text "references/workflow-checklists.md" "Core Flow Inventory" +require_text "references/workflow-checklists.md" "Flow Slice Coverage" +require_text "references/workflow-checklists.md" "Complexity-triggered diagrams" +require_text "references/workflow-checklists.md" "Completeness Hard Gate" +require_text "references/workflow-checklists.md" "stateless glossary" require_text "references/validation-scenarios.md" "Prevent outline-only module docs" require_text "references/validation-scenarios.md" "Module default single-file" require_text "references/validation-scenarios.md" "Flow default single-file" @@ -150,7 +152,7 @@ require_text "templates/onboarding-db/module.md" "每张图必须带讲解" require_text "templates/onboarding-db/module.md" "架构/边界图" require_text "templates/onboarding-db/module.md" "ASCII 架构图" require_text "templates/onboarding-db/module.md" "ASCII 状态机图" -require_text "templates/onboarding-db/module.md" "Timeline / 时序图(必填)" +require_text "templates/onboarding-db/module.md" "Timeline / 时序图(有顺序或数据移动时必填)" require_text "templates/onboarding-db/module.md" "Mermaid sequenceDiagram" require_text "templates/onboarding-db/module.md" "Mermaid flowchart" require_text "templates/onboarding-db/module.md" "## 11. 工作原理与示例" @@ -176,8 +178,8 @@ require_order "templates/onboarding-db/module.md" \ "## 1. 模块定位" \ "## 2. 图解" \ "### 2.1 模块图 / 架构/边界图" \ - "### 2.2 ASCII 状态机图" \ - "### 2.3 Timeline / 时序图(必填)" \ + "### 2.2 ASCII 状态机图(有状态行为时必填)" \ + "### 2.3 Timeline / 时序图(有顺序或数据移动时必填)" \ "### 2.4 流程图(按需)" \ "### 2.5 图解说明" \ "## 3. Bounded Context / DDD 视角" \ @@ -222,9 +224,9 @@ require_text "templates/onboarding-db/evidence-graph.md" "Evidence Readiness Gat require_text "templates/onboarding-db/onboarding-spec.md" "Gateway / Runtime Plan" require_text "templates/onboarding-db/onboarding-spec.md" "Diagram Plan" require_text "templates/onboarding-db/onboarding-spec.md" "状态图优先" -require_text "templates/onboarding-db/onboarding-spec.md" "模块文档默认必须规划架构/边界图、ASCII 状态图、Timeline / 时序图" -require_text "templates/onboarding-db/onboarding-spec.md" "流程文档默认必须规划架构/边界图、ASCII 状态图、Timeline / 时序图" -require_text "templates/onboarding-db/onboarding-spec.md" "Every planned module/flow doc must have Required Architecture/Boundary Diagram, Required ASCII State Diagram, and Required Timeline/Sequence Diagram" +require_text "templates/onboarding-db/onboarding-spec.md" "## Core Flow Selection" +require_text "templates/onboarding-db/onboarding-spec.md" "## Flow Slice Plan" +require_text "templates/onboarding-db/onboarding-spec.md" 'Every planned `critical` / `important` flow doc must have Required Architecture/Boundary Diagram' require_text "templates/onboarding-db/onboarding-spec.md" "Mermaid flowchart / sequenceDiagram" require_text "templates/onboarding-db/onboarding-spec.md" "Exemptions" require_text "templates/onboarding-db/onboarding-spec.md" "Required Timeline / Sequence" @@ -234,9 +236,11 @@ require_text "templates/onboarding-db/onboarding-spec.md" "Spec Acceptance Gate" require_text "templates/onboarding-db/onboarding-spec.md" "Full Execution Gate 另行确认后" require_text "templates/onboarding-db/onboarding-tasks.md" "batch 是 Agent 的组织和 review 单位" require_text "templates/onboarding-db/onboarding-tasks.md" "Full Execution Gate" -require_text "templates/onboarding-db/onboarding-tasks.md" "架构/边界图、ASCII 状态图、Timeline / 时序图" +require_text "templates/onboarding-db/onboarding-tasks.md" "Required Slice IDs" +require_text "templates/onboarding-db/onboarding-tasks.md" "Completeness Hard Gate" require_text "templates/onboarding-db/onboarding-tasks.md" "Mermaid flowchart / sequenceDiagram" -require_text "templates/onboarding-db/README.md" "架构/边界图、ASCII 状态图、Timeline / 时序图" +require_text "templates/onboarding-db/README.md" "Core Flow Overview / Boundary" +require_text "templates/onboarding-db/README.md" "stateless glossary" require_text "templates/onboarding-db/README.md" "Mermaid flowchart / sequenceDiagram" require_text "Usage.md" "架构/边界图、ASCII 状态图、Timeline / 时序图" require_text "Usage.md" "Mermaid flowchart / sequenceDiagram" @@ -250,8 +254,12 @@ require_text "templates/onboarding-db/coverage-matrix.md" "Required diagram set require_text "templates/onboarding-db/coverage-matrix.md" "Architecture diagram clarity" require_text "templates/onboarding-db/coverage-matrix.md" "State diagram clarity" require_text "templates/onboarding-db/coverage-matrix.md" "Timeline / sequence clarity" +require_text "templates/onboarding-db/coverage-matrix.md" "Completeness Hard Gate" +require_text "templates/onboarding-db/coverage-matrix.md" "Core flow discovery completeness" +require_text "templates/onboarding-db/coverage-matrix.md" "Slice and branch coverage" require_text "templates/onboarding-db/batch-review.md" "Score" require_text "templates/onboarding-db/batch-review.md" "Gaps / Unknowns" +require_text "templates/onboarding-db/batch-review.md" "Completeness Hard Gate" require_absent_text "references/onboarding-knowledge-base.md" "Build Learning Skeleton" require_absent_text "references/onboarding-knowledge-base.md" "stable skeleton" diff --git a/tests/validate-feature-monthly-archive-runtime.sh b/tests/validate-feature-monthly-archive-runtime.sh new file mode 100644 index 0000000..cb5f715 --- /dev/null +++ b/tests/validate-feature-monthly-archive-runtime.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "$0")/.." && pwd) + +assert_contains() { + local file=$1 + local text=$2 + if ! grep -Fq -- "$text" "$root/$file"; then + printf 'FAIL: %s missing Feature Monthly Archive contract: %s\n' "$file" "$text" >&2 + exit 1 + fi +} + +assert_not_contains() { + local file=$1 + local text=$2 + if grep -Fq -- "$text" "$root/$file"; then + printf 'FAIL: %s contains forbidden Feature Monthly Archive contract: %s\n' "$file" "$text" >&2 + exit 1 + fi +} + +for file in \ + SKILL.md \ + references/runtime.md \ + references/design.md \ + references/artifact-rules.md \ + references/feature-follow-up.md \ + references/stage-guides.md \ + references/workflow-checklists.md \ + references/human-review-summary.md \ + references/recovery-and-backfill.md \ + references/project-decisions.md \ + references/requirement-management.md \ + references/project-memory-mode.md \ + references/project-guidance.md \ + templates/root-AGENTS.md \ + references/validation-scenarios.md; do + assert_contains "$file" "Feature Monthly Archive" + assert_contains "$file" "features/archive.md" +done + +assert_contains SKILL.md "feature-archive-maintenance" +assert_contains SKILL.md "expected plan SHA-256" +assert_contains SKILL.md "Batch Human Gate" + +assert_contains references/runtime.md "feature-archive-maintenance" +assert_contains references/runtime.md "scan is read-only" +assert_contains references/runtime.md "transaction journal" +assert_contains references/runtime.md "rehydrate before reopened execution" + +assert_contains references/design.md "Feature ID is stable" +assert_contains references/design.md "archive state is not feature lifecycle" +assert_contains references/design.md "active / blocked / paused features stay flat" + +assert_contains references/artifact-rules.md "no per-feature archive summary" +assert_contains references/artifact-rules.md "no historical/" +assert_contains references/artifact-rules.md "no Deep Archive" +assert_contains references/artifact-rules.md 'No `--force`' + +assert_contains references/feature-follow-up.md "rehydrate before reopened execution" +assert_contains references/stage-guides.md "post-check" +assert_contains references/stage-guides.md "restore" +assert_contains references/workflow-checklists.md "expected plan SHA-256" +assert_contains references/human-review-summary.md "Platform evidence" +assert_contains references/recovery-and-backfill.md ".archive-txn" +assert_contains references/project-decisions.md "archived closed Feature Spec" +assert_contains references/requirement-management.md "stable Feature ID" +assert_contains references/project-memory-mode.md "features/archive.md" +assert_contains references/project-guidance.md "month archive" +assert_contains templates/root-AGENTS.md "feature-archive-maintenance" +assert_contains references/validation-scenarios.md "ambiguous old path" + +assert_contains templates/feature-archive.md "archived or rehydrated features" +assert_contains templates/feature-archive.md "remain authoritative" + +for file in SKILL.md references/runtime.md references/design.md references/artifact-rules.md; do + assert_not_contains "$file" "Feature Monthly Archive automatically deletes" + assert_not_contains "$file" "Feature Monthly Archive creates features/YYYY-MM/INDEX.md" +done + +for file in \ + references/e2e-discovery.md \ + references/delivery-contracts.md \ + references/complex-artifacts.md \ + references/external-skill-adapters.md \ + templates/subagent-brief.md; do + assert_not_contains "$file" ".agent-loop/features/YYYY-MM//" + assert_not_contains "$file" "execute inside an archived month directory" +done + +printf 'PASS: Feature Monthly Archive runtime, gate, locator, recovery, and scope contract is complete\n' diff --git a/tests/validate-feature-monthly-compaction-proposal.sh b/tests/validate-feature-monthly-compaction-proposal.sh index 66ed496..0775f5d 100755 --- a/tests/validate-feature-monthly-compaction-proposal.sh +++ b/tests/validate-feature-monthly-compaction-proposal.sh @@ -3,73 +3,104 @@ set -euo pipefail root=$(cd "$(dirname "$0")/.." && pwd) proposal="$root/docs/proposal/v1.3.x/feature-monthly-compaction.md" +plan="$root/docs/proposal/v1.3.x/feature-monthly-archive-implementation-plan.md" assert_contains() { local text=$1 if ! grep -Fq -- "$text" "$proposal"; then - printf 'FAIL: feature monthly compaction proposal missing required text: %s\n' "$text" >&2 + printf 'FAIL: feature monthly archive proposal missing required text: %s\n' "$text" >&2 exit 1 fi } -assert_file_exists() { - if [ ! -f "$proposal" ]; then - printf 'FAIL: missing feature monthly compaction proposal: %s\n' "$proposal" >&2 +assert_not_contains() { + local text=$1 + if grep -Fq -- "$text" "$proposal"; then + printf 'FAIL: feature monthly archive proposal contains removed design: %s\n' "$text" >&2 exit 1 fi } -assert_file_exists +assert_plan_contains() { + local text=$1 + if ! grep -Fq -- "$text" "$plan"; then + printf 'FAIL: feature monthly archive implementation plan missing required text: %s\n' "$text" >&2 + exit 1 + fi +} + +if [ ! -f "$proposal" ]; then + printf 'FAIL: missing feature monthly archive proposal: %s\n' "$proposal" >&2 + exit 1 +fi -assert_contains "状态:讨论草案" -assert_contains "目标版本:v1.2.4 候选" -assert_contains "当前月保持 flat" -assert_contains "上个月且整月全部完成后,才允许月度压缩" -assert_contains "Full" -assert_contains "Slim With History" -assert_contains "Deep Archive / Summary Only" -assert_contains "Deep Archive 永远不是默认动作" +if [ ! -f "$plan" ]; then + printf 'FAIL: missing feature monthly archive implementation plan: %s\n' "$plan" >&2 + exit 1 +fi -assert_contains ".agent-loop/features/YYYY-MM-DD-/" -assert_contains ".agent-loop/features/YYYY-MM/" -assert_contains "archive.md" -assert_contains "historical/" -assert_contains "README.md" +assert_contains "# Proposal: Feature Monthly Archive" +assert_contains "状态:v1.3.0 Release Human Gate 已批准;发布目标 stable-v1.3.0" +assert_contains "Human Review:2026-07-14" +assert_contains "docs/proposal/v1.3.x/feature-monthly-archive-implementation-plan.md" +assert_contains "目标版本:v1.3.0 候选" +assert_contains "整目录按月归档" +assert_contains ".agent-loop/features/archive.md" +assert_contains ".agent-loop/features/2026-05/2026-05-08-login/" +assert_contains "Feature ID 是稳定身份" +assert_contains "Archive State" +assert_contains "archived | rehydrated" +assert_contains "它不是 feature lifecycle status" +assert_contains '归档资格只接受 `closed`' +assert_contains '- 不生成每个 feature 的新 `archive.md`;' +assert_contains '- 不创建 `historical/`;' +assert_contains '- 不创建 `features/YYYY-MM/INDEX.md`;' -assert_contains "Feature Compaction Scan" +assert_contains "Feature Monthly Archive Scan" +assert_contains "Scan 是只读操作" assert_contains "Candidate Matrix" -assert_contains "Safety Gate" -assert_contains "Human Gate" -assert_contains "Partial month compaction" +assert_contains "Reference Impact List" +assert_contains "Eligibility And Safety Gate" +assert_contains "Batch Human Gate" +assert_contains "stale-plan" +assert_contains "Post-check" +assert_contains "失败恢复" +assert_contains "Rehydrate / Reopen" -assert_contains "## 影响的索引关系" -assert_contains "features/INDEX.md" -assert_contains "features/YYYY-MM/INDEX.md" -assert_contains "requirement set README" -assert_contains "requirements/INDEX.md" -assert_contains "Delivery Phases" -assert_contains "Feature Mapping" -assert_contains "decisions/*.md" -assert_contains "Implemented By" -assert_contains "project.md" -assert_contains "Active Feature" -assert_contains "Paused Features" assert_contains "Feature Follow-up / Flow-back" -assert_contains "Source Requirements" -assert_contains "Applicable Decisions" -assert_contains "Implements Decisions" -assert_contains "verification evidence" -assert_contains "Drift Check" -assert_contains "scripts / validation glob" +assert_contains "ADR / Decision" +assert_contains "Requirement Mapping" +assert_contains "Project Memory" +assert_contains "Internal And External Links" +assert_contains "Feature Path Resolver" +assert_contains '`features/archive.md` 预期变化' +assert_contains '.agent-loop/features/.archive-txn//' +assert_contains "transaction journal" + +assert_contains "scripts/scan-feature-monthly-archive.py" +assert_contains "scripts/apply-feature-monthly-archive.py" +assert_contains "scripts/check-feature-monthly-archive.py" +assert_contains "scripts/restore-feature-monthly-archive.py" +assert_contains "Python 3.10+ 标准库" +assert_contains "Windows 与 macOS" +assert_contains '平台证据:`macOS-verified / Windows-verified`' +assert_contains "Proposal Boundary" +assert_contains "本文件仍是 proposal,不是发布运行时权威" -assert_contains "Requirements 不做内容压缩" -assert_contains "需求源材料保持原样" -assert_contains "只把 feature 压缩作为第一版默认能力" +assert_not_contains "Slim With History" +assert_not_contains "Deep Archive / Summary Only" +assert_not_contains '每个 feature 的新 `archive.md` 应' +assert_not_contains '删除、打包或外移 `historical/`' -assert_contains "## Archive Summary Template" -assert_contains "Delivered Behavior" -assert_contains "Key Design Decisions" -assert_contains "Changed Files / Public Interfaces" -assert_contains "Historical Detail Location" +assert_plan_contains "## Stage Helper Resolution" +assert_plan_contains "## Interface Contracts" +assert_plan_contains "## Task 0: Establish Baseline And Phase-0 Evidence" +assert_plan_contains "## Task 7: Full Validation, Review, And Development-Agent Handoff" +assert_plan_contains "Reader Compatibility" +assert_plan_contains "Archive Readiness" +assert_plan_contains "expected-plan-sha256" +assert_plan_contains "transaction journal" +assert_plan_contains "immutable-requirement-source" +assert_plan_contains "## Plan Self-Review" -printf 'PASS: feature monthly compaction proposal contract is complete\n' +printf 'PASS: feature monthly archive proposal contract is complete\n' diff --git a/tests/validate-grill-artifact-templates.sh b/tests/validate-grill-artifact-templates.sh index 3a60fce..222f4c0 100755 --- a/tests/validate-grill-artifact-templates.sh +++ b/tests/validate-grill-artifact-templates.sh @@ -13,7 +13,7 @@ assert_contains() { } assert_contains "references/document-templates.md" "## Terminology / Domain Language" -assert_contains "references/document-templates.md" "## Roles / Operators / Permission Boundary" +assert_contains "references/document-templates.md" "## Role / Permission Matrix" assert_contains "references/document-templates.md" "## Primary Business Flow" assert_contains "references/document-templates.md" "## Exception Paths" assert_contains "references/document-templates.md" "## Data / Source of Truth" diff --git a/tests/validate-human-help-version-docs.sh b/tests/validate-human-help-version-docs.sh index 6e63558..4b2962b 100755 --- a/tests/validate-human-help-version-docs.sh +++ b/tests/validate-human-help-version-docs.sh @@ -20,7 +20,7 @@ assert_contains "SKILL.md" "use \`README.md\` for high-level overview, install, assert_contains "SKILL.md" "read that version section first" assert_contains "Usage.md" "### 我想知道版本更新或用法" -assert_contains "Usage.md" "1.2.4 更新了什么?" +assert_contains "Usage.md" "1.3.0 更新了什么?" assert_contains "Usage.md" "和 1.2.2 比有什么变化?" assert_contains "Usage.md" "现在 agent-loop 怎么用?" assert_contains "Usage.md" "CHANGELOG.md" diff --git a/tests/validate-onboarding-core-flow-completeness.sh b/tests/validate-onboarding-core-flow-completeness.sh new file mode 100644 index 0000000..58e8767 --- /dev/null +++ b/tests/validate-onboarding-core-flow-completeness.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +assert_file_exists() { + local path="$1" + if [[ ! -f "$ROOT/$path" ]]; then + echo "missing file: $path" >&2 + exit 1 + fi +} + +assert_contains() { + local path="$1" + local expected="$2" + if ! grep -Fq "$expected" "$ROOT/$path"; then + echo "missing text in $path: $expected" >&2 + exit 1 + fi +} + +assert_not_contains() { + local path="$1" + local forbidden="$2" + if grep -Fq "$forbidden" "$ROOT/$path"; then + echo "forbidden text in $path: $forbidden" >&2 + exit 1 + fi +} + +assert_file_exists "docs/proposal/v1.3.x/onboarding-core-flow-completeness.md" +assert_file_exists "references/onboarding-knowledge-base.md" +assert_file_exists "templates/onboarding-db/evidence-graph.md" +assert_file_exists "templates/onboarding-db/onboarding-spec.md" +assert_file_exists "templates/onboarding-db/onboarding-tasks.md" +assert_file_exists "templates/onboarding-db/flow.md" +assert_file_exists "templates/onboarding-db/coverage-matrix.md" +assert_file_exists "templates/onboarding-db/batch-review.md" + +# Controller and published design/runtime contract. +assert_contains "SKILL.md" "Core Flow Inventory" +assert_contains "SKILL.md" "Flow Slice Coverage" +assert_not_contains "SKILL.md" "wireframe architecture flow diagrams as the preferred flow expression" +assert_contains "references/design.md" "Core Flow Completeness Invariant" +assert_contains "references/design.md" "A missing critical slice cannot be averaged away" +assert_contains "references/runtime.md" "Core Flow Inventory selection" +assert_contains "references/runtime.md" "exactly two onboarding Human Gates" + +# Detailed behavior source. +assert_contains "references/onboarding-knowledge-base.md" "## Core Flow Inventory" +assert_contains "references/onboarding-knowledge-base.md" "## Flow Slice Coverage" +assert_contains "references/onboarding-knowledge-base.md" "## Core Flow Diagram Set" +assert_contains "references/onboarding-knowledge-base.md" "## Complexity-Triggered Diagrams" +assert_contains "references/onboarding-knowledge-base.md" "## Completeness Hard Gate" +assert_contains "references/onboarding-knowledge-base.md" "primary per-flow narrative" +assert_contains "references/onboarding-knowledge-base.md" '`critical` / `important`' +assert_contains "references/onboarding-knowledge-base.md" "supporting" +assert_contains "references/onboarding-knowledge-base.md" "stateless" +assert_contains "references/onboarding-knowledge-base.md" "Flow ID" +assert_contains "references/onboarding-knowledge-base.md" "Slice ID" +assert_contains "references/onboarding-knowledge-base.md" "Diagram ID" + +# Stage/checklist alignment and no additional batch gate. +assert_contains "references/stage-guides.md" "Build Core Flow Inventory" +assert_contains "references/stage-guides.md" "Completeness Hard Gate" +assert_not_contains "references/stage-guides.md" "before accepting the Onboarding Spec, current batch, or newcomer-ready claim" +assert_contains "references/workflow-checklists.md" "Core Flow Inventory" +assert_contains "references/workflow-checklists.md" "Flow Slice Coverage" +assert_contains "references/workflow-checklists.md" "Complexity-triggered diagrams" +assert_contains "references/workflow-checklists.md" "Completeness Hard Gate" + +# Template contracts. +assert_contains "templates/onboarding-db/evidence-graph.md" "## Core Flow Inventory" +assert_contains "templates/onboarding-db/evidence-graph.md" "| Flow ID |" +assert_contains "templates/onboarding-db/evidence-graph.md" "Success Terminal" +assert_contains "templates/onboarding-db/evidence-graph.md" "Failure Terminals" +assert_contains "templates/onboarding-db/evidence-graph.md" "Selection Reason" +assert_contains "templates/onboarding-db/onboarding-spec.md" "## Core Flow Selection" +assert_contains "templates/onboarding-db/onboarding-spec.md" "## Flow Slice Plan" +assert_contains "templates/onboarding-db/onboarding-spec.md" "Complexity Signals" +assert_contains "templates/onboarding-db/onboarding-spec.md" "Covered Slice IDs" +assert_contains "templates/onboarding-db/onboarding-tasks.md" "Required Slice IDs" +assert_contains "templates/onboarding-db/onboarding-tasks.md" "Completeness Hard Gate" +assert_contains "templates/onboarding-db/flow.md" "## 1. Flow Identity And Outcomes" +assert_contains "templates/onboarding-db/flow.md" "## 2. Flow Slice Coverage" +assert_contains "templates/onboarding-db/flow.md" "Diagram ID" +assert_contains "templates/onboarding-db/flow.md" "Covered Slice IDs" +assert_contains "templates/onboarding-db/flow.md" "primary per-flow narrative" +assert_contains "templates/onboarding-db/flow.md" "critical / important flow" +assert_contains "templates/onboarding-db/flow.md" "supporting flow" +assert_contains "templates/onboarding-db/flow.md" "ERD / Model Relationship" +assert_contains "templates/onboarding-db/flow.md" "Runtime / Deployment Topology" +assert_contains "templates/onboarding-db/flow.md" "Observability / Troubleshooting Map" +assert_contains "templates/onboarding-db/coverage-matrix.md" "## Completeness Hard Gate" +assert_contains "templates/onboarding-db/coverage-matrix.md" "Core flow discovery completeness" +assert_contains "templates/onboarding-db/coverage-matrix.md" "Slice and branch coverage" +assert_contains "templates/onboarding-db/coverage-matrix.md" "Evidence granularity" +assert_contains "templates/onboarding-db/coverage-matrix.md" "Consistency / gateway risk" +assert_not_contains "templates/onboarding-db/coverage-matrix.md" "且人类确认新人可读" +assert_contains "templates/onboarding-db/batch-review.md" "## Completeness Hard Gate" +assert_contains "templates/onboarding-db/batch-review.md" "Core flow discovery completeness" +assert_contains "templates/onboarding-db/batch-review.md" "Slice and branch coverage" +assert_contains "templates/onboarding-db/batch-review.md" "Evidence granularity" +assert_contains "templates/onboarding-db/batch-review.md" "Consistency / gateway risk" +assert_not_contains "references/stage-guides.md" "making a newcomer-ready claim" +assert_contains "references/onboarding-knowledge-base.md" "Failure / recovery" +assert_contains "references/onboarding-knowledge-base.md" "Troubleshooting" +assert_not_contains "templates/onboarding-db/onboarding-spec.md" "| Review Gate |" +assert_not_contains "templates/onboarding-db/batch-review.md" "## Human Review Status" + +# Semantic pressure scenarios and human-facing behavior. +assert_contains "references/validation-scenarios.md" "missing callback and reconciliation slices" +assert_contains "references/validation-scenarios.md" "diagrams detached from Flow Slice Coverage" +assert_contains "references/validation-scenarios.md" "average away a missing critical slice" +assert_contains "references/validation-scenarios.md" "exactly two onboarding Human Gates" +assert_contains "Usage.md" "核心流程完整性" +assert_contains "Usage.md" "Timeline / Sequence" +assert_contains "CHANGELOG.md" "Core Flow Completeness" + +# Executable artifact-level contract. +assert_file_exists "scripts/check-onboarding-core-flow-coverage.py" +assert_file_exists "examples/ai-meeting-minutes-backend/onboarding-db/08-review/evidence-graph.md" +assert_file_exists "examples/ai-meeting-minutes-backend/onboarding-db/onboarding-spec.md" +assert_file_exists "examples/ai-meeting-minutes-backend/onboarding-db/onboarding-tasks.md" +assert_file_exists "examples/ai-meeting-minutes-backend/onboarding-db/03-flows/order-payment.md" +assert_file_exists "examples/ai-meeting-minutes-backend/onboarding-db/coverage-matrix.md" +assert_file_exists "examples/ai-meeting-minutes-backend/onboarding-db/batch-review.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/invalid-missing-recovery/evidence-graph.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/invalid-missing-recovery/onboarding-spec.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/invalid-missing-recovery/onboarding-tasks.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/invalid-missing-recovery/flow.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/invalid-missing-recovery/coverage-matrix.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/invalid-missing-recovery/batch-review.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/invalid-detached-trace/evidence-graph.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/invalid-detached-trace/onboarding-spec.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/invalid-detached-trace/onboarding-tasks.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/invalid-detached-trace/flow.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/invalid-detached-trace/coverage-matrix.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/invalid-detached-trace/batch-review.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/valid-deferred/evidence-graph.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/valid-deferred/onboarding-spec.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/valid-deferred/onboarding-tasks.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/valid-deferred/coverage-matrix.md" +assert_file_exists "tests/fixtures/onboarding-core-flow/valid-deferred/batch-review.md" + +python3 "$ROOT/scripts/check-onboarding-core-flow-coverage.py" \ + "$ROOT/examples/ai-meeting-minutes-backend/onboarding-db" + +invalid_output="$(mktemp)" +trap 'rm -f "$invalid_output"' EXIT +if python3 "$ROOT/scripts/check-onboarding-core-flow-coverage.py" \ + "$ROOT/tests/fixtures/onboarding-core-flow/invalid-missing-recovery" \ + >"$invalid_output" 2>&1; then + echo "invalid fixture unexpectedly passed" >&2 + exit 1 +fi + +if ! grep -Fq "missing required slice: CF-ORDER-PAYMENT/S07" "$invalid_output"; then + echo "invalid fixture failed for the wrong reason" >&2 + cat "$invalid_output" >&2 + exit 1 +fi + +detached_output="$(mktemp)" +trap 'rm -f "$invalid_output" "$detached_output"' EXIT +if python3 "$ROOT/scripts/check-onboarding-core-flow-coverage.py" \ + "$ROOT/tests/fixtures/onboarding-core-flow/invalid-detached-trace" \ + >"$detached_output" 2>&1; then + echo "detached-trace fixture unexpectedly passed" >&2 + exit 1 +fi + +if ! grep -Fq "missing diagram definition: D-RECOVERY" "$detached_output"; then + echo "detached-trace fixture failed for the wrong reason" >&2 + cat "$detached_output" >&2 + exit 1 +fi + +python3 "$ROOT/scripts/check-onboarding-core-flow-coverage.py" \ + "$ROOT/tests/fixtures/onboarding-core-flow/valid-deferred" + +echo "PASS: onboarding core-flow completeness contract is complete" diff --git a/tests/validate-project-local-skills.sh b/tests/validate-project-local-skills.sh index 4af2ba4..c35478e 100755 --- a/tests/validate-project-local-skills.sh +++ b/tests/validate-project-local-skills.sh @@ -89,7 +89,7 @@ assert_contains "templates/root-AGENTS.md" "project-skill-management" assert_contains "templates/root-AGENTS.md" "Project Skill Creation / Update" assert_contains "templates/root-AGENTS.md" ".agent-loop/skills/INDEX.md" assert_contains "templates/root-AGENTS.md" "Execution Gate" -assert_contains "templates/root-AGENTS.md" "block-version:1.2.4-20260711.3" +assert_contains "templates/root-AGENTS.md" "block-version:1.3.0-20260714.1" assert_contains "templates/project.md" "Project Skills" assert_contains "templates/project.md" ".agent-loop/skills/INDEX.md" assert_contains "templates/project-skills/validation.md" "Validated Content Manifest" diff --git a/tests/validate-requirement-lifecycle-backlog.sh b/tests/validate-requirement-lifecycle-backlog.sh index 76b2ee0..7fa3e3b 100755 --- a/tests/validate-requirement-lifecycle-backlog.sh +++ b/tests/validate-requirement-lifecycle-backlog.sh @@ -85,11 +85,12 @@ assert_contains "README.md" "Requirement Lifecycle / Backlog" assert_contains "Usage.md" "需求待办" assert_contains "Usage.md" "当前恢复动作" assert_contains "CHANGELOG.md" "Requirement Lifecycle / Backlog" -assert_contains "SKILL.md" "Version: 1.2.4" -assert_contains "README.md" "**Current version:** 1.2.4" -assert_contains "Usage.md" "**版本:** 1.2.4" -assert_contains "plugin.json" '"version": "1.2.4"' -assert_contains "templates/root-AGENTS.md" "block-version:1.2.4-20260711.3" +assert_contains "SKILL.md" "Version: 1.3.0" +assert_contains "README.md" "**Current version:** 1.3.0" +assert_contains "Usage.md" "**版本:** 1.3.0" +assert_contains "plugin.json" '"version": "1.3.0"' +assert_contains "templates/root-AGENTS.md" "block-version:1.3.0-20260714.1" +assert_contains "CHANGELOG.md" "## 1.3.0 — 2026-07-11" assert_not_contains "templates/root-AGENTS.md" "## Agent Loop Guidance Version" assert_contains "AGENTS.md" 'ignore the `alpha` prefix for version records' diff --git a/tests/validate-requirement-product-grill.sh b/tests/validate-requirement-product-grill.sh index 49a5d74..ea85918 100755 --- a/tests/validate-requirement-product-grill.sh +++ b/tests/validate-requirement-product-grill.sh @@ -75,7 +75,7 @@ assert_not_contains "references/stage-guides.md" "write approved clarification a assert_contains "references/external-skill-adapters.md" 'Do not create `CONTEXT.md`, `CONTEXT-MAP.md`, or `docs/adr/` from grill-with-docs defaults' assert_contains "references/stage-guides.md" "Requirements Discussion: write approved clarification and design output to the requirement document; keep requirement README to source, lifecycle, Delivery Phase, Feature Mapping, and decision-link summaries." -assert_contains "references/requirement-product-grill.md" "Detailed grill results belong in the requirement document; the requirement README keeps source, lifecycle, Delivery Phase, Feature Mapping, and decision-link summaries." +assert_contains "references/requirement-product-grill.md" "Detailed grill results belong in the effective requirement source; the requirement README keeps its effective Concept Foundation pointer plus source, lifecycle, Delivery Phase, Feature Mapping, and decision-link summaries." assert_contains "references/requirement-product-grill.md" '| Prior feature conflict | Requirement document, `notes.md`, or Human Review Summary conflict table |' assert_not_contains "references/requirement-product-grill.md" '| Prior feature conflict | Requirement README' assert_contains "references/requirement-management.md" 'Record accepted local terminology, scenarios, open questions, and conflicts in the reviewed requirement document.' @@ -88,7 +88,7 @@ assert_contains "references/validation-scenarios.md" 'otherwise do not create a assert_contains "references/validation-scenarios.md" 'otherwise keep the change in requirement artifacts without creating feature files' assert_contains "references/external-skill-adapters.md" "owning-stage artifact: requirement document plus requirement README summary during Requirements Discussion" assert_not_contains "references/external-skill-adapters.md" '| brainstormed design/spec | `.agent-loop/features//product.md` and/or `.agent-loop/features//spec.md` |' -assert_contains "templates/root-AGENTS.md" "| Product need, business goal, scope, constraint, scenario, or phased delivery is still being shaped | Requirements Discussion |" +assert_contains "templates/root-AGENTS.md" "| Product need, business goal, scope, constraint, scenario, concept identity/lifecycle, or phased delivery is still being shaped | Requirements Discussion |" assert_not_contains "templates/root-AGENTS.md" "Requirements Discussion / Grill" assert_contains "references/workflow-checklists.md" "Requirement/Product Grill" diff --git a/tests/validate-root-agents-block-checker.sh b/tests/validate-root-agents-block-checker.sh index d7e6db4..f06b2fd 100755 --- a/tests/validate-root-agents-block-checker.sh +++ b/tests/validate-root-agents-block-checker.sh @@ -2,7 +2,7 @@ set -euo pipefail root=$(cd "$(dirname "$0")/.." && pwd) -checker="$root/scripts/check-root-agents-blocks.sh" +checker="$root/scripts/check-root-agents-blocks.py" template="$root/templates/root-AGENTS.md" tmpdir=$(mktemp -d) trap 'rm -rf "$tmpdir"' EXIT @@ -40,8 +40,8 @@ remove_section() { ' "$source" > "$target" } -if [ ! -x "$checker" ]; then - printf 'FAIL: checker script is missing or not executable: %s\n' "$checker" >&2 +if [ ! -f "$checker" ]; then + printf 'FAIL: checker script is missing: %s\n' "$checker" >&2 exit 1 fi @@ -49,12 +49,12 @@ cp "$template" "$tmpdir/ok.md" mkdir -p "$tmpdir/.agent-loop" touch "$tmpdir/.agent-loop/project.md" -"$checker" --template "$template" --target "$tmpdir/ok.md" > "$tmpdir/ok.out" +python3 "$checker" --template "$template" --target "$tmpdir/ok.md" > "$tmpdir/ok.out" assert_contains "$tmpdir/ok.out" "PASS root AGENTS managed blocks are current" assert_not_contains "$tmpdir/ok.out" "FAIL root AGENTS drift found" remove_section "message-intent" "$template" "$tmpdir/missing.md" -if "$checker" --template "$template" --target "$tmpdir/missing.md" > "$tmpdir/missing.out"; then +if python3 "$checker" --template "$template" --target "$tmpdir/missing.md" > "$tmpdir/missing.out"; then printf 'FAIL: checker should fail when a template managed section is missing\n' >&2 cat "$tmpdir/missing.out" >&2 exit 1 @@ -63,7 +63,7 @@ assert_contains "$tmpdir/missing.out" "FAIL root AGENTS drift found" assert_contains "$tmpdir/missing.out" "message-intent | missing" remove_section "workflow-stage-map" "$template" "$tmpdir/missing-stage-map.md" -if "$checker" --template "$template" --target "$tmpdir/missing-stage-map.md" > "$tmpdir/missing-stage-map.out"; then +if python3 "$checker" --template "$template" --target "$tmpdir/missing-stage-map.md" > "$tmpdir/missing-stage-map.out"; then printf 'FAIL: checker should fail when Workflow Stage Map is missing\n' >&2 cat "$tmpdir/missing-stage-map.out" >&2 exit 1 @@ -71,20 +71,20 @@ fi assert_contains "$tmpdir/missing-stage-map.out" "FAIL root AGENTS drift found" assert_contains "$tmpdir/missing-stage-map.out" "workflow-stage-map | missing" -sed 's/block-version:1\.2\.4-20260711\.3/block-version:1.2.4/' "$template" > "$tmpdir/stale.md" -if "$checker" --template "$template" --target "$tmpdir/stale.md" > "$tmpdir/stale.out"; then +sed 's/block-version:1\.3\.0-20260714\.1/block-version:1.3.0/' "$template" > "$tmpdir/stale.md" +if python3 "$checker" --template "$template" --target "$tmpdir/stale.md" > "$tmpdir/stale.out"; then printf 'FAIL: checker should fail when block-version values are stale\n' >&2 cat "$tmpdir/stale.out" >&2 exit 1 fi assert_contains "$tmpdir/stale.out" "message-intent | stale-block-version" -assert_contains "$tmpdir/stale.out" "expected 1.2.4-20260711.3" +assert_contains "$tmpdir/stale.out" "expected 1.3.0-20260714.1" awk ' // { next } { print } ' "$template" > "$tmpdir/broken.md" -if "$checker" --template "$template" --target "$tmpdir/broken.md" > "$tmpdir/broken.out"; then +if python3 "$checker" --template "$template" --target "$tmpdir/broken.md" > "$tmpdir/broken.out"; then printf 'FAIL: checker should fail when managed markers are broken\n' >&2 cat "$tmpdir/broken.out" >&2 exit 1 @@ -94,7 +94,7 @@ assert_contains "$tmpdir/broken.out" "ownership | broken-markers" awk ' /" + print "" print "nested" print "" inserted = 1 @@ -102,7 +102,7 @@ awk ' } { print } ' "$template" > "$tmpdir/nested.md" -if "$checker" --template "$template" --target "$tmpdir/nested.md" > "$tmpdir/nested.out"; then +if python3 "$checker" --template "$template" --target "$tmpdir/nested.md" > "$tmpdir/nested.out"; then printf 'FAIL: checker should fail when managed blocks are nested\n' >&2 cat "$tmpdir/nested.out" >&2 exit 1 @@ -112,13 +112,13 @@ assert_contains "$tmpdir/nested.out" "ownership | nested-managed-block" awk ' { print } // && inserted != 1 { - print "" + print "" print "duplicate" print "" inserted = 1 } ' "$template" > "$tmpdir/duplicate.md" -if "$checker" --template "$template" --target "$tmpdir/duplicate.md" > "$tmpdir/duplicate.out"; then +if python3 "$checker" --template "$template" --target "$tmpdir/duplicate.md" > "$tmpdir/duplicate.out"; then printf 'FAIL: checker should fail when a managed section is duplicated\n' >&2 cat "$tmpdir/duplicate.out" >&2 exit 1 @@ -127,11 +127,11 @@ assert_contains "$tmpdir/duplicate.out" "ownership | duplicate-section" { cat "$template" - printf '\n\n' + printf '\n\n' printf '## Legacy Extra\n\n' printf '\n' } > "$tmpdir/extra.md" -if "$checker" --template "$template" --target "$tmpdir/extra.md" > "$tmpdir/extra.out"; then +if python3 "$checker" --template "$template" --target "$tmpdir/extra.md" > "$tmpdir/extra.out"; then printf 'FAIL: checker should fail when target has an unexpected managed section\n' >&2 cat "$tmpdir/extra.out" >&2 exit 1 @@ -145,7 +145,7 @@ awk ' } { print } ' "$template" > "$tmpdir/source-missing.md" -if "$checker" --template "$template" --target "$tmpdir/source-missing.md" > "$tmpdir/source-missing.out"; then +if python3 "$checker" --template "$template" --target "$tmpdir/source-missing.md" > "$tmpdir/source-missing.out"; then printf 'FAIL: checker should fail when a local managed block source is missing\n' >&2 cat "$tmpdir/source-missing.out" >&2 exit 1 @@ -156,7 +156,7 @@ assert_contains "$tmpdir/source-missing.out" "source-missing" cat "$template" printf '\n\n' } > "$tmpdir/bare-start.md" -if "$checker" --template "$template" --target "$tmpdir/bare-start.md" > "$tmpdir/bare-start.out"; then +if python3 "$checker" --template "$template" --target "$tmpdir/bare-start.md" > "$tmpdir/bare-start.out"; then printf 'FAIL: checker should fail when a bare managed-start marker is malformed\n' >&2 cat "$tmpdir/bare-start.out" >&2 exit 1 @@ -167,7 +167,7 @@ assert_contains "$tmpdir/bare-start.out" "malformed-marker" cat "$template" printf '\n\n' } > "$tmpdir/bare-end.md" -if "$checker" --template "$template" --target "$tmpdir/bare-end.md" > "$tmpdir/bare-end.out"; then +if python3 "$checker" --template "$template" --target "$tmpdir/bare-end.md" > "$tmpdir/bare-end.out"; then printf 'FAIL: checker should fail when a bare managed-end marker is malformed\n' >&2 cat "$tmpdir/bare-end.out" >&2 exit 1 @@ -176,9 +176,9 @@ assert_contains "$tmpdir/bare-end.out" "malformed-marker" { cat "$template" - printf '\n\n' + printf '\n\n' } > "$tmpdir/same-line.md" -if "$checker" --template "$template" --target "$tmpdir/same-line.md" > "$tmpdir/same-line.out"; then +if python3 "$checker" --template "$template" --target "$tmpdir/same-line.md" > "$tmpdir/same-line.out"; then printf 'FAIL: checker should fail when multiple managed markers are on the same line\n' >&2 cat "$tmpdir/same-line.out" >&2 exit 1 diff --git a/tests/validate-root-agents-block-refresh.sh b/tests/validate-root-agents-block-refresh.sh index 65c5618..37fe81a 100755 --- a/tests/validate-root-agents-block-refresh.sh +++ b/tests/validate-root-agents-block-refresh.sh @@ -23,7 +23,7 @@ assert_not_contains() { managed_count=$(grep -c '^