From 1a6ac9e82e6b050a1eb1a825cebee1b8edc616bd Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Wed, 22 Jul 2026 18:12:32 +0500 Subject: [PATCH 1/3] docs: add research discovery flow --- memory-bank/README.md | 3 + memory-bank/dna/frontmatter.md | 3 + memory-bank/dna/governance.md | 2 +- memory-bank/flows/README.md | 2 + memory-bank/flows/research.md | 149 ++++++++++++++++++ memory-bank/flows/routing.md | 27 +++- memory-bank/flows/templates/README.md | 14 ++ .../flows/templates/research/README.md | 22 +++ memory-bank/flows/templates/research/brief.md | 85 ++++++++++ .../flows/templates/research/decision.md | 70 ++++++++ .../flows/templates/research/evidence.md | 62 ++++++++ .../templates/research/package-README.md | 51 ++++++ memory-bank/flows/templates/research/plan.md | 73 +++++++++ .../flows/templates/research/synthesis.md | 58 +++++++ memory-bank/research/README.md | 33 ++++ 15 files changed, 648 insertions(+), 6 deletions(-) create mode 100644 memory-bank/flows/research.md create mode 100644 memory-bank/flows/templates/research/README.md create mode 100644 memory-bank/flows/templates/research/brief.md create mode 100644 memory-bank/flows/templates/research/decision.md create mode 100644 memory-bank/flows/templates/research/evidence.md create mode 100644 memory-bank/flows/templates/research/package-README.md create mode 100644 memory-bank/flows/templates/research/plan.md create mode 100644 memory-bank/flows/templates/research/synthesis.md create mode 100644 memory-bank/research/README.md diff --git a/memory-bank/README.md b/memory-bank/README.md index e0072d5..f9b2642 100644 --- a/memory-bank/README.md +++ b/memory-bank/README.md @@ -25,6 +25,9 @@ audience: humans_and_agents - [`prd/README.md`](prd/README.md) Читать, когда нужно: описать продуктовую инициативу между общим product context и downstream feature packages. +- [`research/README.md`](research/README.md) + Читать, когда нужно: провести evidence-backed market, product или technical research до коммита в delivery и передать вывод в подходящий canonical owner. + - [`epics/README.md`](epics/README.md) Читать, когда нужно: вести крупную инициативу через roadmap, decision log, risks и набор связанных delivery subissues. diff --git a/memory-bank/dna/frontmatter.md b/memory-bank/dna/frontmatter.md index 97d5c3a..1e69ca1 100644 --- a/memory-bank/dna/frontmatter.md +++ b/memory-bank/dna/frontmatter.md @@ -20,6 +20,7 @@ status: active |---|---|---| | `derived_from` | Есть upstream-документ | Прямые upstream-зависимости. Каждый элемент — строка (путь) или объект `{path, fit}`, где `fit` объясняет scope зависимости | | `delivery_status` | Lifecycle-owning canonical `brief.md` | `planned` / `in_progress` / `done` / `cancelled` | +| `research_status` | Lifecycle-owning canonical research `brief.md` | `intake` / `framed` / `collecting` / `synthesizing` / `decision_ready` / `validated` / `invalidated` / `inconclusive` / `parked` / `cancelled` / `rerouted` | | `decision_status` | ADR-документы | `proposed` / `accepted` / `superseded` / `rejected` | ## Дополнительные поля @@ -30,6 +31,8 @@ Governed-документы могут содержать дополнитель Для `doc_kind: feature-support` документ является reference / companion внутри feature package и не владеет `delivery_status`, canonical requirements, selected solution или execution sequencing. +Для `doc_kind: research` lifecycle owner-ом остается canonical `brief.md` research package. Его `research_status` описывает состояние исследования, а не delivery. `plan.md`, `evidence.md`, `synthesis.md` и `decision.md` являются отдельными owner-ами метода, наблюдений, выводов и disposition; ни один из них не заменяет canonical downstream PRD, epic, feature, ADR или product document после handoff. + ## Примеры ```yaml diff --git a/memory-bank/dna/governance.md b/memory-bank/dna/governance.md index be0f453..89bab2e 100644 --- a/memory-bank/dna/governance.md +++ b/memory-bank/dna/governance.md @@ -28,7 +28,7 @@ Governance-документы (DNA, flows) используют дополнит | Поле | Значения | Назначение | |-|-|-| -| `doc_kind` | `governance`, `project`, `product`, `domain`, `prd`, `use_case`, `epic`, `feature`, `feature-support`, `engineering`, `ops`, `adr`, `prompt`, `process` | Тип документа или артефакта | +| `doc_kind` | `governance`, `project`, `product`, `domain`, `prd`, `research`, `use_case`, `epic`, `feature`, `feature-support`, `engineering`, `ops`, `adr`, `prompt`, `process` | Тип документа или артефакта | | `doc_function` | `canonical`, `index`, `template`, `derived`, `reference`, `convention`, `roadmap`, `decision_log`, `subissue_registry`, `risk_register` | Роль: canonical owner факта, навигационный индекс, шаблон, downstream artifact, reference companion, convention или specialized epic owner | Эти поля обязательны для governance-документов и рекомендуются для product/domain/ops/engineering/project документов, чтобы агенты могли различать слой знания и роль файла. diff --git a/memory-bank/flows/README.md b/memory-bank/flows/README.md index 8e2e9fc..9e4127b 100644 --- a/memory-bank/flows/README.md +++ b/memory-bank/flows/README.md @@ -6,6 +6,7 @@ purpose: Навигация по task routing, lifecycle flows и governed-ша derived_from: - ../dna/governance.md - routing.md + - research.md - incident.md - bug-fix.md - small-change.md @@ -24,6 +25,7 @@ audience: humans_and_agents Каталог `memory-bank/flows/` содержит reusable process-layer для шаблона: lifecycle rules, taxonomy стабильных идентификаторов и governed templates. - [Task Routing](routing.md) — порядок выбора flow, routing predicates, повторный routing и Human Routing. +- [Research & Discovery Flow](research.md) — evidence-backed lifecycle research-задач, от question framing до decision и handoff без преждевременного delivery. - [Incident And PIR Flow](incident.md) — containment, recovery, timeline, RCA, PIR и prevention work. - [Bug Fix Flow](bug-fix.md) — reproduction, analysis, fix, regression coverage и closure. - [Small Change Flow](small-change.md) — direct delivery без feature package, design и execution plan, но с обязательным routing record. diff --git a/memory-bank/flows/research.md b/memory-bank/flows/research.md new file mode 100644 index 0000000..afa4a1f --- /dev/null +++ b/memory-bank/flows/research.md @@ -0,0 +1,149 @@ +--- +title: Research And Discovery Flow +doc_kind: governance +doc_function: canonical +purpose: "Определяет evidence-backed lifecycle research-задач: market research, product discovery и technical discovery от decision question до disposition и handoff." +derived_from: + - ../dna/governance.md + - ../dna/frontmatter.md + - routing.md +canonical_for: + - research_directory_structure + - research_lifecycle + - research_artifact_ownership + - research_evidence_provenance + - research_synthesis_and_confidence_rules + - research_disposition_and_handoff_rules +status: active +audience: humans_and_agents +--- + +# Research And Discovery Flow + +Research & Discovery Flow управляет задачей, чьим первым outcome является не delivery, а evidence-backed answer для named decision owner. **Discovery** — подходящее имя product-oriented режима этого flow, но не заменяет общий термин `research`: market research, technical spike и desk research могут не быть product discovery. + +## Package Rules + +1. Все документы одного исследования живут в `memory-bank/research/R-XXX/`. +2. `README.md` создаётся первым, владеет package index и `research_stage`. +3. `brief.md` — canonical owner decision question, mode, scope, assumptions, stopping condition и `research_status`. +4. `plan.md` — conditional owner method: sample/source strategy, collection protocol, timebox, bias/ethics/privacy controls. Не создавай его для очевидного, compact desk research, если method уже достаточно прозрачен в `brief.md`. +5. `evidence.md` — owner evidence log и provenance. Raw sources могут жить в `sources/`, но должны быть linked и иметь контекст получения. +6. `synthesis.md` — owner findings, confidence, limitations, disconfirming evidence и remaining uncertainty. +7. `decision.md` — owner recommendation, disposition и promotion map. Он не становится вторым active owner фактов, переданных downstream. +8. Используй templates из `memory-bank/flows/templates/research/`. + +## Research Modes + +| Mode | Typical question | Typical method | Usual handoff | +| --- | --- | --- | --- | +| `market` | Есть ли сегмент, спрос, positioning или конкурентный gap? | desk research, interviews, survey, analytics | product/marketing context, PRD, campaign initiative | +| `product_discovery` | Какая user problem/opportunity стоит delivery и какое направление может сработать? | interviews, journey analysis, prototype/usability test, experiment | PRD, Epic, Feature | +| `technical_discovery` | Feasible ли approach, integration или non-functional target; какой вариант предпочтителен? | code reading, spike, prototype, benchmark, vendor evaluation | ADR, Epic, Feature, Refactoring | +| `exploratory` | Что неизвестно и какое следующее решение оправдано? | bounded desk research or mixed methods | another research package, product context, no action | + +Mode выбирает method и reviewers, но не меняет ownership или gates. + +## Lifecycle + +```mermaid +flowchart LR + RT["Task Routing
Research route"] --> RI["Research Intake
brief.md: draft"] + RI --> QF["Question Framed
brief.md: active"] + QF --> PR["Plan Ready
plan.md: active when required"] + QF --> EC["Evidence Collection"] + PR --> EC + EC --> SY["Synthesis Ready
synthesis.md: active"] + SY --> DR["Decision Ready
decision.md: active"] + DR --> VA["Validated → handoff"] + DR --> IV["Invalidated / Inconclusive"] + QF --> PK["Parked / Cancelled"] + EC --> PK + SY --> PK +``` + +`research_status` belongs only to `brief.md`: `intake`, `framed`, `collecting`, `synthesizing`, `decision_ready`, `validated`, `invalidated`, `inconclusive`, `parked`, `cancelled` or `rerouted`. + +## Transition Gates + +### Bootstrap → Question Framed + +- [ ] `README.md` и `brief.md` созданы по templates. +- [ ] `brief.md` имеет `status: active` и `research_status: framed`. +- [ ] записаны source/trigger, research mode, decision question и decision owner. +- [ ] scope/non-scope, working assumptions и stopping condition explicit. +- [ ] known evidence и material unknowns разделены; hypothesis не записана как fact. +- [ ] no delivery feature, implementation plan, accepted ADR or committed roadmap created solely from this research. + +### Question Framed → Evidence Collection + +- [ ] method proportionate uncertainty and risk; `plan.md` active when its trigger applies. +- [ ] sources/sample, collection window and evidence quality criteria are explicit. +- [ ] applicable privacy, consent, legal, security and vendor-access constraints are recorded. +- [ ] bias risks and at least one possible disconfirming signal are named. +- [ ] `brief.md` → `research_status: collecting`. + +Create `plan.md` when research involves participants, a survey, prototype/experiment, benchmark, privileged/external data, non-trivial sampling, or a method choice that a reviewer could reasonably challenge. + +### Evidence Collection → Synthesis Ready + +- [ ] every material observation in `evidence.md` has source/provenance, date or freshness, collection context and quality note. +- [ ] evidence distinguishes observations, source claims and analyst interpretation. +- [ ] collection stopped by the stated condition or an explicitly recorded justified change. +- [ ] `synthesis.md` is `active`, includes findings, confidence, limitations and disconfirming/absent evidence. +- [ ] `brief.md` → `research_status: synthesizing`. + +### Synthesis Ready → Decision Ready + +- [ ] `decision.md` is `active` and names decision owner. +- [ ] recommendation answers the original decision question or explicitly says why it cannot. +- [ ] reasonable alternatives, confidence and residual uncertainty are visible. +- [ ] disposition is one of `validated`, `invalidated`, `inconclusive`, `parked`, `cancelled` or `rerouted`. +- [ ] a proposed delivery or architecture change is only a recommendation until its downstream owner is created and routed. +- [ ] `brief.md` → `research_status: decision_ready` before the owner decides, then to the matching disposition state. + +## Outcome / Exit Contract + +### Observable Outcome + +The decision owner can make the named decision with traceable evidence, stated confidence and known limitations — or can explicitly decide that evidence is insufficient. + +### Terminal Dispositions and Handoff + +| Disposition | Meaning | Required handoff | +| --- | --- | --- | +| `validated` | Evidence sufficiently supports the hypothesis/direction. | Reroute any delivery to PRD, Epic, Feature, ADR or another owner; link the target from `decision.md`. | +| `invalidated` | Evidence sufficiently contradicts the hypothesis/direction. | Record rationale; optionally update product/marketing context. No delivery is implied. | +| `inconclusive` | Evidence cannot support a reliable decision. | Name the uncertainty, owner and next research question or stopping rationale. | +| `parked` | Work is intentionally deferred. | Name owner and review trigger/date. | +| `cancelled` | Work is stopped before a decision. | Record reason, evidence retained and consequences. | +| `rerouted` | The original question belongs in another existing flow. | Link target route and archive/close this package without duplicate ownership. | + +When a durable fact is accepted, promote it before closing the research package: product/market facts go to their product owner; initiative intent to PRD or epic charter; delivery requirements to a feature brief; selected architecture to ADR or feature design. `decision.md` retains links and rationale, not a duplicate active fact. + +## Boundary Rules + +1. Research asks and answers a question; it does not silently commit implementation. +2. `brief.md` does not own findings, selected solution, delivery scope, implementation sequence or acceptance test contract. +3. `evidence.md` preserves provenance and does not turn correlation, a source claim or a participant quote into a conclusion without synthesis. +4. `synthesis.md` may state confidence and recommendation inputs, but the decision owner records final disposition in `decision.md`. +5. Research evidence is not automatically representative, causal or current. Record sampling limits, freshness and material conflicts. +6. Do not copy private participant data, credentials, customer data or restricted source content into the repository. Store a minimal reference, access boundary and derived observation instead. +7. A technical spike may contain disposable code or benchmark commands, but production implementation requires a new routed delivery flow. +8. If findings change an active canonical fact, update that owner first; research artifacts remain derived evidence. + +## Stable Identifiers + +| Prefix | Meaning | Owner | +| --- | --- | --- | +| `RQ-*` | research question or sub-question | `brief.md` | +| `HYP-*` | falsifiable working hypothesis | `brief.md` | +| `RSC-*` / `RNS-*` | research scope / non-scope | `brief.md` | +| `ASM-*` | research assumption | `brief.md` | +| `STOP-*` | stopping condition | `brief.md` or `plan.md` | +| `SRC-*` | source or evidence item | `evidence.md` | +| `OBS-*` | observation grounded in source(s) | `evidence.md` | +| `FND-*` | synthesized finding | `synthesis.md` | +| `LIM-*` | limitation, bias or confidence constraint | `synthesis.md` | +| `REC-*` | recommendation | `decision.md` | +| `HD-*` | downstream handoff / promotion | `decision.md` | diff --git a/memory-bank/flows/routing.md b/memory-bank/flows/routing.md index dd8346f..1d97d48 100644 --- a/memory-bank/flows/routing.md +++ b/memory-bank/flows/routing.md @@ -34,6 +34,9 @@ Issue / Task | +-- Bug? ----------------------------> Bug Fix Flow | + +-- Нужен evidence-backed ответ + | до коммита в delivery? ----------> Research & Discovery Flow + | +-- Issue достаточен, | design и plan не нужны? --------> Small Change Flow | @@ -58,11 +61,12 @@ Issue / Task | --- | --- | --- | | 1 | Есть активный operational impact, требуется containment или PIR? | [`Incident Flow`](incident.md) | | 2 | Наблюдаемое поведение противоречит уже ожидаемому? | [`Bug Fix Flow`](bug-fix.md) | -| 3 | Выполнены все `Small Change` predicates ниже? | [`Small Change Flow`](small-change.md) | -| 4 | Работа крупнее одной delivery-feature и требует общего roadmap, cross-feature risk register или нескольких delivery units? | [`Epic Flow`](epic.md) | -| 5 | Цель — изменить внутреннюю структуру при сохранении поведения? | [`Refactoring Flow`](refactoring.md) | -| 6 | Задача укладывается в одну delivery-unit и создаёт или materially меняет пользовательское поведение либо доставляет плановое infrastructure, engineering или operations изменение с проверяемым outcome? | [`Feature Flow`](feature.md) | -| 7 | Маршрут остаётся неоднозначным или риск не контролируется? | Human Routing | +| 3 | Главная цель — получить evidence-backed answer для decision owner, а delivery outcome, scope или выбранный подход ещё не приняты? | [`Research & Discovery Flow`](research.md) | +| 4 | Выполнены все `Small Change` predicates ниже? | [`Small Change Flow`](small-change.md) | +| 5 | Работа крупнее одной delivery-feature и требует общего roadmap, cross-feature risk register или нескольких delivery units? | [`Epic Flow`](epic.md) | +| 6 | Цель — изменить внутреннюю структуру при сохранении поведения? | [`Refactoring Flow`](refactoring.md) | +| 7 | Задача укладывается в одну delivery-unit и создаёт или materially меняет пользовательское поведение либо доставляет плановое infrastructure, engineering или operations изменение с проверяемым outcome? | [`Feature Flow`](feature.md) | +| 8 | Маршрут остаётся неоднозначным или риск не контролируется? | Human Routing | ### Small Change Gate @@ -76,6 +80,17 @@ Issue / Task Размер diff и оценка длительности сами по себе не являются routing predicates. +### Research & Discovery Gate + +Выбирай этот route, когда задача прежде всего уменьшает uncertainty для решения, а не доставляет заранее определённое изменение. Примеры: market research, product discovery, technical feasibility spike, comparative evaluation, desk research или due diligence. + +- вопрос, decision owner и expected decision могут быть зафиксированы; +- scope может быть exploratory, но должен быть timeboxed или иметь явный stopping condition; +- evidence, confidence и limitations важнее implementation plan; +- task не создаёт delivery package, ADR или committed roadmap только на основании неподтверждённой гипотезы. + +Не выбирай Research Flow, если expected behavior уже известен и нужна только реализация: route сразу в минимальный delivery flow. Incident и Bug Fix остаются выше него: containment и восстановление expected behavior не ждут исследования. + ### Epic Intake Handoff Если признаки Epic route уже подтверждены, но problem, outcome, границы или evidence ещё недостаточны для canonical `charter.md`, задача всё равно маршрутизируется в [`Epic Flow`](epic.md). В этом случае Epic Flow начинается с `Epic Intake`: создаётся proposal package с `README.md` и `brief.md`, а недостающие факты фиксируются как open questions. @@ -86,6 +101,7 @@ Issue / Task - Не начинай выбранный flow, пока не выполнены его entry gates. - Если в `Small Change` понадобились design, execution plan или новый устойчивый project fact, останови реализацию и повтори routing. +- Если Research Flow сформировал delivery proposal, architecture decision, product initiative или change request, не начинай delivery внутри research package: зафиксируй disposition и повтори routing в PRD, Epic, Feature, ADR или другой применимый owner. - Если в Feature Flow выяснилось, что работа крупнее одной delivery-feature и требует общего roadmap, cross-feature risk register или нескольких delivery units, останови feature package и повтори routing в [`Epic Flow`](epic.md). - Не создавай delivery feature packages из Epic Intake. До `Roadmap Ready` proposal может называть только candidate delivery slices; accepted subissues и `FT-*` появляются после соответствующих epic gates. - Если report оказался изменением ожидаемого поведения, а не дефектом, выйди из Bug Fix Flow и повтори routing. @@ -107,6 +123,7 @@ Issue / Task - issue/task или draft PR называет выбранный flow; для active incident достаточно alert или incident-management record, подтверждающего operational impact или необходимость containment; - запись показывает, какие entry predicates сделали route допустимым; provisional incident record может быть дополнен полным routing record после containment; - для Epic route запись дополнительно указывает `Epic Intake`, когда facts ещё недостаточны для прямого `Bootstrap Epic`; +- для Research route запись указывает decision question, decision owner и stopping condition; - для применимого delivery flow его canonical owner фиксирует отдельный validation profile decision по [`validation-profiles.md`](../engineering/validation-profiles.md); это downstream evidence выбора flow, а не дополнительный route; - для `Human Routing` зафиксированы вопрос, риск или конкурирующие routes. diff --git a/memory-bank/flows/templates/README.md b/memory-bank/flows/templates/README.md index 6523f13..45056c1 100644 --- a/memory-bank/flows/templates/README.md +++ b/memory-bank/flows/templates/README.md @@ -7,6 +7,13 @@ derived_from: - ../../dna/governance.md - prd/PRD-XXX.md - use-case/UC-XXX.md + - research/README.md + - research/package-README.md + - research/brief.md + - research/plan.md + - research/evidence.md + - research/synthesis.md + - research/decision.md - epic/README.md - epic/package-README.md - epic/brief.md @@ -40,6 +47,13 @@ audience: humans_and_agents - [PRD-XXX: Product Initiative Name](prd/PRD-XXX.md) — компактный Product Requirements Document для инициативы, которая еще не разложена на один конкретный feature slice. - [UC-XXX: Use Case Name](use-case/UC-XXX.md) — канонический use case для устойчивого пользовательского или операционного сценария; selection и lifecycle определяет [Use Case Flow](../use-case.md). +- [Research Templates](research/README.md) — индекс шаблонов `R-XXX` package для market, product и technical research. +- [R-XXX Package README Template](research/package-README.md) — routing index и lifecycle stage owner research package. +- [R-XXX: Research Brief Template](research/brief.md) — canonical decision question, hypotheses, boundaries и stopping condition. +- [R-XXX: Research Plan Template](research/plan.md) — conditional method, sampling/source strategy и collection controls. +- [R-XXX: Evidence Log Template](research/evidence.md) — provenance-preserving log источников и observations. +- [R-XXX: Research Synthesis Template](research/synthesis.md) — findings, confidence, limitations и disconfirming evidence. +- [R-XXX: Research Decision Template](research/decision.md) — disposition, recommendation и promotion/handoff map. - [Epic Templates](epic/README.md) — индекс шаблонов `EP-XXX` package. - [EP-XXX Package README Template](epic/package-README.md) — routing index и lifecycle stage owner для epic package, включая intake-only состояние. - [EP-XXX: Epic Proposal Template](epic/brief.md) — обязательный при Epic Intake brief с proposal disposition и promotion contract; при прямом Bootstrap Epic не создаётся. diff --git a/memory-bank/flows/templates/research/README.md b/memory-bank/flows/templates/research/README.md new file mode 100644 index 0000000..858fc53 --- /dev/null +++ b/memory-bank/flows/templates/research/README.md @@ -0,0 +1,22 @@ +--- +title: Research Templates Index +doc_kind: governance +doc_function: index +purpose: Wrapper-шаблоны для instantiated `memory-bank/research/R-XXX/` packages. +derived_from: + - ../../research.md + - ../../../dna/frontmatter.md +status: active +audience: humans_and_agents +--- + +# Research Templates Index + +Начни с package `README.md` и `brief.md`. Добавляй `plan.md` только по trigger из Research & Discovery Flow; `evidence.md`, `synthesis.md` и `decision.md` появляются по мере перехода lifecycle. + +- [`package-README.md`](package-README.md) — package index и `research_stage` owner. +- [`brief.md`](brief.md) — canonical decision question, scope and lifecycle owner. +- [`plan.md`](plan.md) — conditional research-method owner. +- [`evidence.md`](evidence.md) — provenance and observation log. +- [`synthesis.md`](synthesis.md) — findings, confidence and limitations. +- [`decision.md`](decision.md) — disposition and promotion map. diff --git a/memory-bank/flows/templates/research/brief.md b/memory-bank/flows/templates/research/brief.md new file mode 100644 index 0000000..9b074c3 --- /dev/null +++ b/memory-bank/flows/templates/research/brief.md @@ -0,0 +1,85 @@ +--- +title: R-XXX Research Brief Template +doc_kind: governance +doc_function: template +purpose: Wrapper-шаблон canonical research brief: decision question, hypotheses, boundaries and lifecycle state without findings or delivery design. +derived_from: + - ../../research.md + - ../../../dna/frontmatter.md +status: active +audience: humans_and_agents +template_for: research +template_target_path: ../../../research/R-XXX/brief.md +--- + +# R-XXX Research Brief Template + +## Instantiated Frontmatter + +```yaml +--- +title: "R-XXX: " +doc_kind: research +doc_function: canonical +purpose: "Canonical decision question, boundaries and lifecycle state for research R-XXX." +derived_from: + - ../../flows/research.md +status: draft +research_status: intake +audience: humans_and_agents +--- +``` + +## Instantiated Body + +```markdown +# R-XXX: + +## Intake + +| Field | Value | +| --- | --- | +| Source / trigger | `` | +| Research owner | `` | +| Decision owner | `` | +| Research mode | `market / product_discovery / technical_discovery / exploratory` | +| Decision deadline / timebox | `` | + +## Decision Question + +- `RQ-01` `` + +## Working Hypotheses + +- `HYP-01` `` + +## Scope + +- `RSC-01` `` + +## Non-Scope + +- `RNS-01` `` + +## Assumptions and Known Evidence + +| ID | Statement | Type | Source / confidence | +| --- | --- | --- | --- | +| `ASM-01` | `` | Assumption | `` | +| `` | `` | Evidence | `` | + +## Stopping Condition + +- `STOP-01` `` + +## Open Questions + +| Question | Blocks | Owner | Resolution evidence | +| --- | --- | --- | --- | + +## Boundary Check + +- [ ] This brief contains a question and hypotheses, not findings presented as facts. +- [ ] No committed delivery scope, selected solution, ADR decision or implementation sequence is defined here. +- [ ] Required privacy, consent, legal, security or access constraints are named or explicitly `none`. +``` diff --git a/memory-bank/flows/templates/research/decision.md b/memory-bank/flows/templates/research/decision.md new file mode 100644 index 0000000..065db1b --- /dev/null +++ b/memory-bank/flows/templates/research/decision.md @@ -0,0 +1,70 @@ +--- +title: R-XXX Research Decision Template +doc_kind: governance +doc_function: template +purpose: Wrapper-шаблон research disposition, recommendation and downstream promotion map. +derived_from: + - ../../research.md +status: active +audience: humans_and_agents +template_for: research +template_target_path: ../../../research/R-XXX/decision.md +--- + +# R-XXX Research Decision Template + +## Instantiated Frontmatter + +```yaml +--- +title: "R-XXX: Research Decision" +doc_kind: research +doc_function: canonical +purpose: "Decision disposition and promotion map for research R-XXX." +derived_from: + - brief.md + - synthesis.md + - ../../flows/research.md +status: draft +research_disposition: pending +audience: humans_and_agents +--- +``` + +## Instantiated Body + +```markdown +# R-XXX: Research Decision + +## Decision + +| Field | Value | +| --- | --- | +| Decision owner | `` | +| Decision date | `` | +| Disposition | `pending / validated / invalidated / inconclusive / parked / cancelled / rerouted` | +| Decision reference | `` | + +## Recommendation + +- `REC-01` `` + +## Alternatives Considered + +| Alternative | Why not selected / what would change the decision | +| --- | --- | + +## Promotion and Handoff Map + +| ID | Accepted or retained fact | Canonical downstream owner | Target route / link | +| --- | --- | --- | --- | +| `HD-01` | `` | `` | `` | + +For `validated` delivery proposals, create or link the target owner and repeat Task Routing before implementation. For `inconclusive`, `parked` or `cancelled`, name owner and review trigger/next question. Do not leave this document as a duplicate active owner after promotion. + +## Closure Check + +- [ ] Disposition answers `RQ-01` or explicitly records why it cannot. +- [ ] Recommendation is traceable to `FND-*` and `LIM-*`. +- [ ] Handoff does not create delivery scope, implementation steps or an accepted architecture decision by implication. +``` diff --git a/memory-bank/flows/templates/research/evidence.md b/memory-bank/flows/templates/research/evidence.md new file mode 100644 index 0000000..2065297 --- /dev/null +++ b/memory-bank/flows/templates/research/evidence.md @@ -0,0 +1,62 @@ +--- +title: R-XXX Research Evidence Template +doc_kind: governance +doc_function: template +purpose: Wrapper-шаблон provenance-preserving evidence and observation log for research R-XXX. +derived_from: + - ../../research.md +status: active +audience: humans_and_agents +template_for: research +template_target_path: ../../../research/R-XXX/evidence.md +--- + +# R-XXX Research Evidence Template + +## Instantiated Frontmatter + +```yaml +--- +title: "R-XXX: Evidence Log" +doc_kind: research +doc_function: canonical +purpose: "Traceable evidence and observations collected for research R-XXX." +derived_from: + - brief.md + - plan.md + - ../../flows/research.md +status: draft +audience: humans_and_agents +--- +``` + +## Instantiated Body + +```markdown +# R-XXX: Evidence Log + +Do not copy restricted source material, personal data or credentials here. Record a minimal reference, access boundary and derived observation. + +## Sources + +| ID | Source / provenance | Date / freshness | Collection context | Access / quality note | +| --- | --- | --- | --- | --- | +| `SRC-01` | `` | `` | `` | `` | + +## Observations + +| ID | Observation | Supporting `SRC-*` | Applies to | Interpretation boundary | +| --- | --- | --- | --- | --- | +| `OBS-01` | `` | `SRC-01` | `RQ-01 / HYP-01` | `` | + +## Collection Log + +| Date | Activity | Result | Deviation / reason | +| --- | --- | --- | --- | + +## Evidence Quality Check + +- [ ] Each material observation traces to one or more `SRC-*`. +- [ ] Observations are separated from source claims and analyst interpretation. +- [ ] Freshness, sample/source limitations and conflicts are recorded. +``` diff --git a/memory-bank/flows/templates/research/package-README.md b/memory-bank/flows/templates/research/package-README.md new file mode 100644 index 0000000..aba19e2 --- /dev/null +++ b/memory-bank/flows/templates/research/package-README.md @@ -0,0 +1,51 @@ +--- +title: R-XXX Research Package README Template +doc_kind: governance +doc_function: template +purpose: Wrapper-шаблон индекса и lifecycle stage для research package. +derived_from: + - ../../research.md +status: active +audience: humans_and_agents +template_for: research +template_target_path: ../../../research/R-XXX/README.md +--- + +# R-XXX Research Package README Template + +## Instantiated Frontmatter + +```yaml +--- +title: "R-XXX: " +doc_kind: research +doc_function: index +purpose: "Навигация и текущая stage evidence-backed research R-XXX." +derived_from: + - ../../flows/research.md + - brief.md +status: active +research_stage: intake +audience: humans_and_agents +--- +``` + +## Instantiated Body + +```markdown +# R-XXX: + +## Current Stage + +- Stage: `intake` +- Research owner: `` +- Decision owner: `` +- Source / trigger: `` +- Next gate: `Bootstrap → Question Framed` + +## Annotated Index + +- [Research Brief](brief.md) — canonical question, boundaries, hypotheses and stopping condition. + +Add `plan.md`, `evidence.md`, `synthesis.md` and `decision.md` only when they exist. For each, state the facts it owns; do not create placeholder links. +``` diff --git a/memory-bank/flows/templates/research/plan.md b/memory-bank/flows/templates/research/plan.md new file mode 100644 index 0000000..32eaafe --- /dev/null +++ b/memory-bank/flows/templates/research/plan.md @@ -0,0 +1,73 @@ +--- +title: R-XXX Research Plan Template +doc_kind: governance +doc_function: template +purpose: Wrapper-шаблон conditional research plan: method, collection protocol, quality controls and stopping rules. +derived_from: + - ../../research.md +status: active +audience: humans_and_agents +template_for: research +template_target_path: ../../../research/R-XXX/plan.md +--- + +# R-XXX Research Plan Template + +Создавай, когда method choice, sampling, participant contact, experiment, benchmark, privileged data или collection protocol требуют review. Для compact desk research без такого trigger достаточно method note в `brief.md`. + +## Instantiated Frontmatter + +```yaml +--- +title: "R-XXX: Research Plan" +doc_kind: research +doc_function: canonical +purpose: "Research method and collection protocol for R-XXX." +derived_from: + - brief.md + - ../../flows/research.md +status: draft +audience: humans_and_agents +--- +``` + +## Instantiated Body + +```markdown +# R-XXX: Research Plan + +## Method + +| Question / hypothesis | Method | Why this method fits | Quality threshold | +| --- | --- | --- | --- | +| `RQ-01` / `HYP-01` | `` | `` | `` | + +## Sources or Sample + +| Group / source | Inclusion and exclusion | Target / access boundary | Sampling limitation | +| --- | --- | --- | --- | + +## Collection Protocol + +Steps, instrument version, benchmark environment or query strategy sufficient for another reviewer to understand how evidence was obtained. + +## Controls + +| Risk | Control | Owner | +| --- | --- | --- | +| Bias / confounder | `` | `` | +| Consent, privacy, legal or security | `` | `` | +| Source freshness / vendor claim | `` | `` | + +## Stop Rules + +- `STOP-01` `` + +## Plan Approval + +| Field | Value | +| --- | --- | +| Status | `draft / active` | +| Reviewer / decision owner | `` | +| Approval reference | `` | +``` diff --git a/memory-bank/flows/templates/research/synthesis.md b/memory-bank/flows/templates/research/synthesis.md new file mode 100644 index 0000000..9b88584 --- /dev/null +++ b/memory-bank/flows/templates/research/synthesis.md @@ -0,0 +1,58 @@ +--- +title: R-XXX Research Synthesis Template +doc_kind: governance +doc_function: template +purpose: Wrapper-шаблон synthesis of research findings, confidence, limitations and remaining uncertainty. +derived_from: + - ../../research.md +status: active +audience: humans_and_agents +template_for: research +template_target_path: ../../../research/R-XXX/synthesis.md +--- + +# R-XXX Research Synthesis Template + +## Instantiated Frontmatter + +```yaml +--- +title: "R-XXX: Research Synthesis" +doc_kind: research +doc_function: canonical +purpose: "Findings, confidence and limitations synthesized from evidence for R-XXX." +derived_from: + - brief.md + - evidence.md +status: draft +audience: humans_and_agents +--- +``` + +## Instantiated Body + +```markdown +# R-XXX: Research Synthesis + +## Findings + +| ID | Finding | Evidence | Confidence | Implication for `RQ-*` / `HYP-*` | +| --- | --- | --- | --- | --- | +| `FND-01` | `` | `OBS-01, SRC-01` | `high / medium / low` | `` | + +## Limitations and Disconfirming Evidence + +| ID | Limitation / conflicting signal | Effect on conclusion | Mitigation or next question | +| --- | --- | --- | --- | +| `LIM-01` | `` | `` | `` | + +## Answer to Decision Question + +Answer `RQ-01` in direct language. If evidence is insufficient, say so; do not convert an uncertain inference into a fact. + +## Review Check + +- [ ] Every finding traces to evidence. +- [ ] Confidence reflects evidence quality rather than desired outcome. +- [ ] Alternative explanations and remaining uncertainty are visible. +``` diff --git a/memory-bank/research/README.md b/memory-bank/research/README.md new file mode 100644 index 0000000..c951778 --- /dev/null +++ b/memory-bank/research/README.md @@ -0,0 +1,33 @@ +--- +title: Research Packages Index +doc_kind: research +doc_function: index +purpose: Навигация по instantiated research packages. Читать, чтобы провести evidence-backed research до решения о product, marketing или technical direction. +derived_from: + - ../dna/governance.md + - ../flows/research.md +status: active +audience: humans_and_agents +--- + +# Research Packages Index + +Каталог `memory-bank/research/` хранит instantiated research packages вида `R-XXX/`. + +## Rules + +- Создавай package только когда Task Routing выбрал [Research & Discovery Flow](../flows/research.md). +- Один package отвечает на один decision question; несколько независимых questions маршрутизируй отдельно. +- Bootstrap начинается с `README.md` и canonical `brief.md`. `plan.md` создаётся, когда метод не очевиден или нужен collection/experiment; `evidence.md`, `synthesis.md` и `decision.md` появляются по lifecycle gates. +- Research не создаёт committed feature scope, implementation sequence, accepted architecture или roadmap. После disposition устойчивые факты передаются в PRD, epic, feature, ADR, product context или другой canonical owner. +- Для package используй шаблоны из [`../flows/templates/research/`](../flows/templates/research/). + +## Naming + +- Базовый формат: `R-XXX/`. +- Вместо `XXX` используй issue id, ticket id или другой стабильный ключ. +- Один package = один evidence-backed decision question, а не папка для всех заметок проекта. + +## Instantiated Research + +В шаблонном репозитории этот каталог может быть пустым. Это нормально. From 9aa223fe18f8b8b8be1d812f658770117c60ecd0 Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Thu, 23 Jul 2026 00:26:45 +0500 Subject: [PATCH 2/3] feat: govern research lifecycle --- README.md | 5 +- docs/ownership.md | 4 +- memory-bank/dna/frontmatter.md | 2 +- memory-bank/dna/lifecycle.md | 2 +- memory-bank/flows/research.md | 12 ++--- memory-bank/flows/routing.md | 2 +- memory-bank/flows/templates/README.md | 6 +-- .../flows/templates/research/README.md | 4 +- memory-bank/flows/templates/research/brief.md | 5 +- .../flows/templates/research/decision.md | 11 ++-- .../flows/templates/research/evidence.md | 8 +-- .../templates/research/package-README.md | 13 ++--- memory-bank/flows/templates/research/plan.md | 2 +- .../flows/templates/research/synthesis.md | 4 +- tools/internal/doctor/doctor_test.go | 40 ++++++++++++++ tools/internal/doctor/governance.go | 52 ++++++++++++++++++- tools/internal/ownership/classify.go | 2 +- tools/internal/ownership/classify_test.go | 4 +- 18 files changed, 135 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 37b678a..628651c 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Memory Bank — переносимый documentation-first шаблон для ## Как это работает -`dna/` задаёт governance-ядро: Single Source of Truth, зависимости между документами, lifecycle, frontmatter и правила навигации. Постоянный контекст проекта находится в `product/`, `domain/`, `engineering/` и `ops/`; инициативы, сценарии и решения — в PRD, epic, use case и ADR. +`dna/` задаёт governance-ядро: Single Source of Truth, зависимости между документами, lifecycle, frontmatter и правила навигации. Постоянный контекст проекта находится в `product/`, `domain/`, `engineering/` и `ops/`; research, инициативы, сценарии и решения — в Research, PRD, epic, use case и ADR. Для значимой delivery-фичи контекст созревает поэтапно: @@ -41,6 +41,7 @@ problem space solution space execution space | [`engineering/`](memory-bank/engineering/README.md) | Архитектура, тестирование, coding style, git workflow и границы автономии агента | | [`ops/`](memory-bank/ops/README.md) | Локальная разработка, окружения, конфигурация, релизы и runbooks | | [`prd/`](memory-bank/prd/README.md) | Продуктовые инициативы между общим product context и отдельными фичами | +| [`research/`](memory-bank/research/README.md) | Evidence-backed market, product и technical research до решения о delivery | | [`epics/`](memory-bank/epics/README.md) | Крупные инициативы с roadmap, рисками, решениями и delivery subissues | | [`use-cases/`](memory-bank/use-cases/README.md) | Канонические пользовательские и операционные сценарии | | [`features/`](memory-bank/features/README.md) | Пакеты отдельных delivery-фич | @@ -65,7 +66,7 @@ problem space solution space execution space ## Выбор рабочего процесса -Каждая задача сначала проходит [Task Routing](memory-bank/flows/routing.md). Он направляет работу в Incident, Bug Fix, Small Change, Epic, Refactoring, Feature или на ручное решение. +Каждая задача сначала проходит [Task Routing](memory-bank/flows/routing.md). Он направляет работу в Incident, Bug Fix, Research & Discovery, Small Change, Epic, Refactoring, Feature или на ручное решение. Корневой README даёт только обзор. Условия входа, lifecycle, обязательные артефакты и exit contract принадлежат каноническим документам в [`memory-bank/flows/`](memory-bank/flows/README.md) и не дублируются здесь. diff --git a/docs/ownership.md b/docs/ownership.md index eb39671..6233b51 100644 --- a/docs/ownership.md +++ b/docs/ownership.md @@ -6,9 +6,9 @@ | Класс | Текущая граница шаблона | Поведение update | | --- | --- | --- | -| `managed` | `memory-bank/dna/`, `flows/`, `prompts/`, а также top-level template-индексы `prd/README.md`, `epics/README.md`, `use-cases/README.md`, `features/README.md`, `adr/README.md` | Проверяет текущий payload по digest. Чистый файл обновляется или удаляется; локальный drift становится conflict. | +| `managed` | `memory-bank/dna/`, `flows/`, `prompts/`, а также top-level template-индексы `prd/README.md`, `research/README.md`, `epics/README.md`, `use-cases/README.md`, `features/README.md`, `adr/README.md` | Проверяет текущий payload по digest. Чистый файл обновляется или удаляется; локальный drift становится conflict. | | `adapted` | `memory-bank/README.md`, `product/`, `domain/`, `engineering/`, `ops/` | Хранит digest исходной template-base, но не требует совпадения текущего файла. Чистый файл может получить новую base; одновременные upstream и downstream изменения становятся conflict. | -| `user-owned` | Instantiated-документы в `prd/`, `epics/`, `use-cases/`, `features/`, `adr/` и неизвестные downstream paths | Никогда автоматически не перезаписывается и не удаляется. Неизвестный существующий файл получает этот класс по fail-safe правилу. | +| `user-owned` | Instantiated-документы в `prd/`, `research/`, `epics/`, `use-cases/`, `features/`, `adr/` и неизвестные downstream paths | Никогда автоматически не перезаписывается и не удаляется. Неизвестный существующий файл получает этот класс по fail-safe правилу. | | `generated` | `memory-bank/.generated/` зарезервирован для будущих детерминированных генераторов; в текущем template таких файлов нет | Может быть пересоздан или удалён только детерминированным producer. | `base_digest` и `base_mode` (`100644` или `100755`) описывают файл в зафиксированной template-base. `payload_digest` и `payload_mode` присутствуют только там, где текущий файл является проверяемым managed/generated contract. Поэтому обычная специализация adapted-документа не считается drift, а изменение executable bit managed-файла проверяется так же, как изменение его содержимого. diff --git a/memory-bank/dna/frontmatter.md b/memory-bank/dna/frontmatter.md index 1e69ca1..17db022 100644 --- a/memory-bank/dna/frontmatter.md +++ b/memory-bank/dna/frontmatter.md @@ -31,7 +31,7 @@ Governed-документы могут содержать дополнитель Для `doc_kind: feature-support` документ является reference / companion внутри feature package и не владеет `delivery_status`, canonical requirements, selected solution или execution sequencing. -Для `doc_kind: research` lifecycle owner-ом остается canonical `brief.md` research package. Его `research_status` описывает состояние исследования, а не delivery. `plan.md`, `evidence.md`, `synthesis.md` и `decision.md` являются отдельными owner-ами метода, наблюдений, выводов и disposition; ни один из них не заменяет canonical downstream PRD, epic, feature, ADR или product document после handoff. +Для `doc_kind: research` lifecycle owner-ом остается canonical `brief.md` research package. Его `research_status` описывает состояние исследования, включая terminal disposition, а не delivery. `plan.md`, `evidence.md`, `synthesis.md` и `decision.md` являются отдельными owner-ами метода, наблюдений, выводов, decision rationale и handoff; ни один из них не создаёт второй lifecycle state и не заменяет canonical downstream PRD, epic, feature, ADR или product document после handoff. ## Примеры diff --git a/memory-bank/dna/lifecycle.md b/memory-bank/dna/lifecycle.md index b353305..c90d290 100644 --- a/memory-bank/dna/lifecycle.md +++ b/memory-bank/dna/lifecycle.md @@ -23,5 +23,5 @@ status: active Перед фиксацией изменений в governed-документации: - [ ] frontmatter валиден, для `active` non-root задан `derived_from` -- [ ] для lifecycle-owning canonical `brief.md` задан `delivery_status`, для `adr` — `decision_status` +- [ ] для lifecycle-owning feature `brief.md` задан `delivery_status`, для lifecycle-owning research `brief.md` — `research_status`, для `adr` — `decision_status` - [ ] parent `README.md` обновлён при изменении состава или reading order diff --git a/memory-bank/flows/research.md b/memory-bank/flows/research.md index afa4a1f..97eccd6 100644 --- a/memory-bank/flows/research.md +++ b/memory-bank/flows/research.md @@ -25,12 +25,12 @@ Research & Discovery Flow управляет задачей, чьим первы ## Package Rules 1. Все документы одного исследования живут в `memory-bank/research/R-XXX/`. -2. `README.md` создаётся первым, владеет package index и `research_stage`. +2. `README.md` создаётся первым и владеет только package index. Текущий lifecycle state не дублируется в index: его единственный owner — `research_status` в `brief.md`. 3. `brief.md` — canonical owner decision question, mode, scope, assumptions, stopping condition и `research_status`. 4. `plan.md` — conditional owner method: sample/source strategy, collection protocol, timebox, bias/ethics/privacy controls. Не создавай его для очевидного, compact desk research, если method уже достаточно прозрачен в `brief.md`. -5. `evidence.md` — owner evidence log и provenance. Raw sources могут жить в `sources/`, но должны быть linked и иметь контекст получения. +5. `evidence.md` — owner evidence log и provenance. Каждый material fact или observation обязан сослаться через `SRC-*` на clickable original-source link или stable access-controlled source record; raw sources могут жить в `sources/`, но должны быть linked и иметь контекст получения. 6. `synthesis.md` — owner findings, confidence, limitations, disconfirming evidence и remaining uncertainty. -7. `decision.md` — owner recommendation, disposition и promotion map. Он не становится вторым active owner фактов, переданных downstream. +7. `decision.md` — owner decision rationale, recommendation и promotion map. Terminal disposition записывается только как `research_status` в `brief.md`; этот документ не создаёт второго lifecycle state или active owner фактов, переданных downstream. 8. Используй templates из `memory-bank/flows/templates/research/`. ## Research Modes @@ -87,7 +87,7 @@ Create `plan.md` when research involves participants, a survey, prototype/experi ### Evidence Collection → Synthesis Ready -- [ ] every material observation in `evidence.md` has source/provenance, date or freshness, collection context and quality note. +- [ ] every material observation and factual claim in `evidence.md` has a linked `SRC-*`, source/provenance, date or freshness, collection context and quality note. - [ ] evidence distinguishes observations, source claims and analyst interpretation. - [ ] collection stopped by the stated condition or an explicitly recorded justified change. - [ ] `synthesis.md` is `active`, includes findings, confidence, limitations and disconfirming/absent evidence. @@ -125,8 +125,8 @@ When a durable fact is accepted, promote it before closing the research package: 1. Research asks and answers a question; it does not silently commit implementation. 2. `brief.md` does not own findings, selected solution, delivery scope, implementation sequence or acceptance test contract. -3. `evidence.md` preserves provenance and does not turn correlation, a source claim or a participant quote into a conclusion without synthesis. -4. `synthesis.md` may state confidence and recommendation inputs, but the decision owner records final disposition in `decision.md`. +3. `evidence.md` preserves provenance: every material fact links to `SRC-*`, and each `SRC-*` links to its original source or stable access-controlled record. It does not turn correlation, a source claim or a participant quote into a conclusion without synthesis. +4. `synthesis.md` may state confidence and recommendation inputs, but the decision owner records final terminal disposition only as `research_status` in `brief.md`; `decision.md` records its rationale and handoff. 5. Research evidence is not automatically representative, causal or current. Record sampling limits, freshness and material conflicts. 6. Do not copy private participant data, credentials, customer data or restricted source content into the repository. Store a minimal reference, access boundary and derived observation instead. 7. A technical spike may contain disposable code or benchmark commands, but production implementation requires a new routed delivery flow. diff --git a/memory-bank/flows/routing.md b/memory-bank/flows/routing.md index 1d97d48..0137029 100644 --- a/memory-bank/flows/routing.md +++ b/memory-bank/flows/routing.md @@ -101,7 +101,7 @@ Issue / Task - Не начинай выбранный flow, пока не выполнены его entry gates. - Если в `Small Change` понадобились design, execution plan или новый устойчивый project fact, останови реализацию и повтори routing. -- Если Research Flow сформировал delivery proposal, architecture decision, product initiative или change request, не начинай delivery внутри research package: зафиксируй disposition и повтори routing в PRD, Epic, Feature, ADR или другой применимый owner. +- Если Research Flow сформировал delivery proposal, architecture decision, product initiative или change request, не начинай delivery внутри research package: зафиксируй terminal disposition в `brief.md: research_status` и повтори routing в PRD, Epic, Feature, ADR или другой применимый owner. - Если в Feature Flow выяснилось, что работа крупнее одной delivery-feature и требует общего roadmap, cross-feature risk register или нескольких delivery units, останови feature package и повтори routing в [`Epic Flow`](epic.md). - Не создавай delivery feature packages из Epic Intake. До `Roadmap Ready` proposal может называть только candidate delivery slices; accepted subissues и `FT-*` появляются после соответствующих epic gates. - Если report оказался изменением ожидаемого поведения, а не дефектом, выйди из Bug Fix Flow и повтори routing. diff --git a/memory-bank/flows/templates/README.md b/memory-bank/flows/templates/README.md index 45056c1..41fa831 100644 --- a/memory-bank/flows/templates/README.md +++ b/memory-bank/flows/templates/README.md @@ -48,12 +48,12 @@ audience: humans_and_agents - [PRD-XXX: Product Initiative Name](prd/PRD-XXX.md) — компактный Product Requirements Document для инициативы, которая еще не разложена на один конкретный feature slice. - [UC-XXX: Use Case Name](use-case/UC-XXX.md) — канонический use case для устойчивого пользовательского или операционного сценария; selection и lifecycle определяет [Use Case Flow](../use-case.md). - [Research Templates](research/README.md) — индекс шаблонов `R-XXX` package для market, product и technical research. -- [R-XXX Package README Template](research/package-README.md) — routing index и lifecycle stage owner research package. -- [R-XXX: Research Brief Template](research/brief.md) — canonical decision question, hypotheses, boundaries и stopping condition. +- [R-XXX Package README Template](research/package-README.md) — routing index research package; lifecycle state не дублируется здесь. +- [R-XXX: Research Brief Template](research/brief.md) — canonical decision question, hypotheses, boundaries, stopping condition и единственный lifecycle owner (`research_status`). - [R-XXX: Research Plan Template](research/plan.md) — conditional method, sampling/source strategy и collection controls. - [R-XXX: Evidence Log Template](research/evidence.md) — provenance-preserving log источников и observations. - [R-XXX: Research Synthesis Template](research/synthesis.md) — findings, confidence, limitations и disconfirming evidence. -- [R-XXX: Research Decision Template](research/decision.md) — disposition, recommendation и promotion/handoff map. +- [R-XXX: Research Decision Template](research/decision.md) — decision rationale, recommendation и promotion/handoff map; terminal state остаётся в `brief.md`. - [Epic Templates](epic/README.md) — индекс шаблонов `EP-XXX` package. - [EP-XXX Package README Template](epic/package-README.md) — routing index и lifecycle stage owner для epic package, включая intake-only состояние. - [EP-XXX: Epic Proposal Template](epic/brief.md) — обязательный при Epic Intake brief с proposal disposition и promotion contract; при прямом Bootstrap Epic не создаётся. diff --git a/memory-bank/flows/templates/research/README.md b/memory-bank/flows/templates/research/README.md index 858fc53..a710234 100644 --- a/memory-bank/flows/templates/research/README.md +++ b/memory-bank/flows/templates/research/README.md @@ -14,9 +14,9 @@ audience: humans_and_agents Начни с package `README.md` и `brief.md`. Добавляй `plan.md` только по trigger из Research & Discovery Flow; `evidence.md`, `synthesis.md` и `decision.md` появляются по мере перехода lifecycle. -- [`package-README.md`](package-README.md) — package index и `research_stage` owner. +- [`package-README.md`](package-README.md) — package index со ссылкой на lifecycle owner. - [`brief.md`](brief.md) — canonical decision question, scope and lifecycle owner. - [`plan.md`](plan.md) — conditional research-method owner. - [`evidence.md`](evidence.md) — provenance and observation log. - [`synthesis.md`](synthesis.md) — findings, confidence and limitations. -- [`decision.md`](decision.md) — disposition and promotion map. +- [`decision.md`](decision.md) — decision rationale and promotion map; terminal state остаётся в `brief.md`. diff --git a/memory-bank/flows/templates/research/brief.md b/memory-bank/flows/templates/research/brief.md index 9b074c3..e499372 100644 --- a/memory-bank/flows/templates/research/brief.md +++ b/memory-bank/flows/templates/research/brief.md @@ -2,7 +2,7 @@ title: R-XXX Research Brief Template doc_kind: governance doc_function: template -purpose: Wrapper-шаблон canonical research brief: decision question, hypotheses, boundaries and lifecycle state without findings or delivery design. +purpose: "Wrapper-шаблон canonical research brief: decision question, hypotheses, boundaries and lifecycle state without findings or delivery design." derived_from: - ../../research.md - ../../../dna/frontmatter.md @@ -66,7 +66,7 @@ audience: humans_and_agents | ID | Statement | Type | Source / confidence | | --- | --- | --- | --- | | `ASM-01` | `` | Assumption | `` | -| `` | `` | Evidence | `` | +| `` | `` | Evidence | `[SRC-XX]()` | ## Stopping Condition @@ -80,6 +80,7 @@ audience: humans_and_agents ## Boundary Check - [ ] This brief contains a question and hypotheses, not findings presented as facts. +- [ ] Every known fact has a clickable source link; unsupported statements remain assumptions or open questions. - [ ] No committed delivery scope, selected solution, ADR decision or implementation sequence is defined here. - [ ] Required privacy, consent, legal, security or access constraints are named or explicitly `none`. ``` diff --git a/memory-bank/flows/templates/research/decision.md b/memory-bank/flows/templates/research/decision.md index 065db1b..2909dea 100644 --- a/memory-bank/flows/templates/research/decision.md +++ b/memory-bank/flows/templates/research/decision.md @@ -2,7 +2,7 @@ title: R-XXX Research Decision Template doc_kind: governance doc_function: template -purpose: Wrapper-шаблон research disposition, recommendation and downstream promotion map. +purpose: Wrapper-шаблон research decision rationale, recommendation and downstream promotion map. derived_from: - ../../research.md status: active @@ -20,13 +20,12 @@ template_target_path: ../../../research/R-XXX/decision.md title: "R-XXX: Research Decision" doc_kind: research doc_function: canonical -purpose: "Decision disposition and promotion map for research R-XXX." +purpose: "Decision rationale and promotion map for research R-XXX." derived_from: - brief.md - synthesis.md - ../../flows/research.md status: draft -research_disposition: pending audience: humans_and_agents --- ``` @@ -42,9 +41,10 @@ audience: humans_and_agents | --- | --- | | Decision owner | `` | | Decision date | `` | -| Disposition | `pending / validated / invalidated / inconclusive / parked / cancelled / rerouted` | | Decision reference | `` | +Terminal disposition is recorded only in sibling `brief.md` as `research_status`. When finalizing this decision, set `brief.md` to the matching terminal state: `validated`, `invalidated`, `inconclusive`, `parked`, `cancelled` or `rerouted`. + ## Recommendation - `REC-01` `` @@ -64,7 +64,8 @@ For `validated` delivery proposals, create or link the target owner and repeat T ## Closure Check -- [ ] Disposition answers `RQ-01` or explicitly records why it cannot. +- [ ] Sibling `brief.md` records the matching terminal `research_status`. +- [ ] `synthesis.md` answers `RQ-01` or explicitly records why it cannot; this decision links that answer through its recommendation and rationale. - [ ] Recommendation is traceable to `FND-*` and `LIM-*`. - [ ] Handoff does not create delivery scope, implementation steps or an accepted architecture decision by implication. ``` diff --git a/memory-bank/flows/templates/research/evidence.md b/memory-bank/flows/templates/research/evidence.md index 2065297..f40118a 100644 --- a/memory-bank/flows/templates/research/evidence.md +++ b/memory-bank/flows/templates/research/evidence.md @@ -23,13 +23,14 @@ doc_function: canonical purpose: "Traceable evidence and observations collected for research R-XXX." derived_from: - brief.md - - plan.md - ../../flows/research.md status: draft audience: humans_and_agents --- ``` +Если в package существует `plan.md`, добавь его в `derived_from`. Для compact desk research без плана оставь frontmatter выше без этой зависимости. + ## Instantiated Body ```markdown @@ -41,13 +42,13 @@ Do not copy restricted source material, personal data or credentials here. Recor | ID | Source / provenance | Date / freshness | Collection context | Access / quality note | | --- | --- | --- | --- | --- | -| `SRC-01` | `` | `` | `` | `` | +| `SRC-01` | `[]()` | `` | `` | `` | ## Observations | ID | Observation | Supporting `SRC-*` | Applies to | Interpretation boundary | | --- | --- | --- | --- | --- | -| `OBS-01` | `` | `SRC-01` | `RQ-01 / HYP-01` | `` | +| `OBS-01` | `` | `[SRC-01]()` | `RQ-01 / HYP-01` | `` | ## Collection Log @@ -57,6 +58,7 @@ Do not copy restricted source material, personal data or credentials here. Recor ## Evidence Quality Check - [ ] Each material observation traces to one or more `SRC-*`. +- [ ] Every `SRC-*` contains a clickable link to its original source or a stable access-controlled source record; an interview code alone is insufficient. - [ ] Observations are separated from source claims and analyst interpretation. - [ ] Freshness, sample/source limitations and conflicts are recorded. ``` diff --git a/memory-bank/flows/templates/research/package-README.md b/memory-bank/flows/templates/research/package-README.md index aba19e2..b20874d 100644 --- a/memory-bank/flows/templates/research/package-README.md +++ b/memory-bank/flows/templates/research/package-README.md @@ -2,7 +2,7 @@ title: R-XXX Research Package README Template doc_kind: governance doc_function: template -purpose: Wrapper-шаблон индекса и lifecycle stage для research package. +purpose: Wrapper-шаблон индекса research package без дублирования lifecycle state из canonical brief. derived_from: - ../../research.md status: active @@ -20,12 +20,11 @@ template_target_path: ../../../research/R-XXX/README.md title: "R-XXX: " doc_kind: research doc_function: index -purpose: "Навигация и текущая stage evidence-backed research R-XXX." +purpose: "Навигация по evidence-backed research package R-XXX." derived_from: - ../../flows/research.md - brief.md status: active -research_stage: intake audience: humans_and_agents --- ``` @@ -35,13 +34,9 @@ audience: humans_and_agents ```markdown # R-XXX: -## Current Stage +## Lifecycle Owner -- Stage: `intake` -- Research owner: `` -- Decision owner: `` -- Source / trigger: `` -- Next gate: `Bootstrap → Question Framed` +Текущий lifecycle state хранится только в поле `research_status` документа [Research Brief](brief.md). Не копируй status или current stage в этот index. ## Annotated Index diff --git a/memory-bank/flows/templates/research/plan.md b/memory-bank/flows/templates/research/plan.md index 32eaafe..8d51f54 100644 --- a/memory-bank/flows/templates/research/plan.md +++ b/memory-bank/flows/templates/research/plan.md @@ -2,7 +2,7 @@ title: R-XXX Research Plan Template doc_kind: governance doc_function: template -purpose: Wrapper-шаблон conditional research plan: method, collection protocol, quality controls and stopping rules. +purpose: "Wrapper-шаблон conditional research plan: method, collection protocol, quality controls and stopping rules." derived_from: - ../../research.md status: active diff --git a/memory-bank/flows/templates/research/synthesis.md b/memory-bank/flows/templates/research/synthesis.md index 9b88584..88b03c8 100644 --- a/memory-bank/flows/templates/research/synthesis.md +++ b/memory-bank/flows/templates/research/synthesis.md @@ -38,7 +38,7 @@ audience: humans_and_agents | ID | Finding | Evidence | Confidence | Implication for `RQ-*` / `HYP-*` | | --- | --- | --- | --- | --- | -| `FND-01` | `` | `OBS-01, SRC-01` | `high / medium / low` | `` | +| `FND-01` | `` | `[OBS-01](evidence.md#observations), [SRC-01](evidence.md#sources)` | `high / medium / low` | `` | ## Limitations and Disconfirming Evidence @@ -52,7 +52,7 @@ Answer `RQ-01` in direct language. If evidence is insufficient, say so; do not c ## Review Check -- [ ] Every finding traces to evidence. +- [ ] Every finding and factual claim traces through linked `OBS-*` to linked `SRC-*`; do not state uncited facts as findings. - [ ] Confidence reflects evidence quality rather than desired outcome. - [ ] Alternative explanations and remaining uncertainty are visible. ``` diff --git a/tools/internal/doctor/doctor_test.go b/tools/internal/doctor/doctor_test.go index 9247967..db20992 100644 --- a/tools/internal/doctor/doctor_test.go +++ b/tools/internal/doctor/doctor_test.go @@ -302,6 +302,46 @@ func TestFeatureBriefOwnsDeliveryStatusWithoutOptionalClassification(t *testing. } } +func TestResearchLifecycleMetadataIsValidatedAndOwnedByBrief(t *testing.T) { + repo := t.TempDir() + write := func(relative, contents string) { + t.Helper() + fullPath := filepath.Join(repo, filepath.FromSlash(relative)) + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(fullPath, []byte(contents), 0o644); err != nil { + t.Fatal(err) + } + } + write("memory-bank/research/R-001/brief.md", "---\nstatus: active\nresearch_status: unknown\n---\n# Brief\n") + write("memory-bank/research/R-001/evidence.md", "---\nstatus: draft\nresearch_status: collecting\n---\n# Evidence\n") + write("memory-bank/research/R-002/evidence.md", "---\nstatus: draft\n---\n# Evidence\n") + write("memory-bank/archive/research/R-003/brief.md", "---\nstatus: active\nresearch_status: framed\n---\n# Archived brief\n") + + report, err := Run(Options{RepoRoot: repo, ScopeRoot: "memory-bank", Profile: ProfileTemplate, MaxDepth: 3}) + if err != nil { + t.Fatal(err) + } + for _, code := range []string{"governance.research_status_invalid", "lifecycle.research_status_wrong_owner", "lifecycle.research_brief_missing"} { + if !hasFinding(report, code) { + t.Fatalf("missing %s in %#v", code, report.Findings) + } + } +} + +func TestResearchBriefOwnsResearchStatusWithoutOptionalClassification(t *testing.T) { + if !isCanonicalResearchBrief(governedDocument{ + path: "memory-bank/research/R-001/brief.md", + frontmatter: map[string]any{"status": "active", "research_status": "framed"}, + }, "memory-bank") { + t.Fatal("canonical research brief path should own research_status without optional classification") + } + if isCanonicalResearchBrief(governedDocument{path: "memory-bank/archive/research/R-001/brief.md"}, "memory-bank") { + t.Fatal("archived research brief path must not own research_status") + } +} + func TestWorkflowRunsDoctorOnlyForExecutableRunCommands(t *testing.T) { for _, test := range []struct { name string diff --git a/tools/internal/doctor/governance.go b/tools/internal/doctor/governance.go index 2c1e424..42677ce 100644 --- a/tools/internal/doctor/governance.go +++ b/tools/internal/doctor/governance.go @@ -27,7 +27,7 @@ var ( designRequirementDecision = regexp.MustCompile("(?im)^\\s*(?:(?:[-+*]|\\d+[.)])\\s+)?(?:\\|\\s*)?`?design\\s+required\\s*:\\s*`?(yes|no)`?(?:\\s*`)?(?:\\s*\\|.*|\\s*[.,;:]?\\s*)$") ) -var governanceDocKinds = []string{"governance", "project", "product", "domain", "prd", "use_case", "epic", "feature", "feature-support", "engineering", "ops", "adr", "prompt", "process"} +var governanceDocKinds = []string{"governance", "project", "product", "domain", "prd", "research", "use_case", "epic", "feature", "feature-support", "engineering", "ops", "adr", "prompt", "process"} var governanceDocFunctions = []string{"canonical", "index", "template", "derived", "reference", "convention", "roadmap", "decision_log", "subissue_registry", "risk_register"} func (report *Report) checkGovernance(scopeRoot string) { @@ -73,6 +73,7 @@ func (report *Report) checkGovernance(scopeRoot string) { } report.checkDerivedFromCycles(documents) report.checkFeatureLifecycle(documents, scopeRoot) + report.checkResearchLifecycle(documents, scopeRoot) } func parseFrontmatter(data []byte) (map[string]any, bool, error) { @@ -151,6 +152,15 @@ func validateGovernedDocument(report *Report, document governedDocument, scopeRo report.add(Finding{Code: "lifecycle.decision_status_wrong_owner", Severity: Error, Group: "lifecycle_consistency", Path: document.path, Message: "decision_status is owned only by ADR documents.", Remediation: "Move decision lifecycle state to an ADR and remove the field here."}) } } + if research, exists := document.frontmatter["research_status"]; exists { + value, valid := research.(string) + if !valid || !oneOf(value, "intake", "framed", "collecting", "synthesizing", "decision_ready", "validated", "invalidated", "inconclusive", "parked", "cancelled", "rerouted") { + report.add(Finding{Code: "governance.research_status_invalid", Severity: Error, Group: "frontmatter_governance", Path: document.path, Subject: fmt.Sprint(research), Message: "research_status is outside the governed enum.", Remediation: "Use intake, framed, collecting, synthesizing, decision_ready, validated, invalidated, inconclusive, parked, cancelled, or rerouted."}) + } + if !isCanonicalResearchBrief(document, scopeRoot) { + report.add(Finding{Code: "lifecycle.research_status_wrong_owner", Severity: Error, Group: "lifecycle_consistency", Path: document.path, Message: "research_status is owned only by a canonical research brief.md.", Remediation: "Move lifecycle state to research/R-XXX/brief.md and remove the duplicate field."}) + } + } if status == "active" && !isGovernanceRoot(document.path, scopeRoot, dnaRootExists) { if _, exists := document.frontmatter["derived_from"]; !exists { report.add(Finding{Code: "governance.derived_from_missing", Severity: Error, Group: "frontmatter_governance", Path: document.path, Message: "Active non-root document must declare derived_from.", Remediation: "Add at least one upstream path in derived_from, or archive the document if it is no longer governed."}) @@ -196,6 +206,16 @@ func isCanonicalFeatureBrief(document governedDocument) bool { return parts[featureIndex] == "features" && strings.HasPrefix(parts[featureIndex+1], "FT-") } +func isCanonicalResearchBrief(document governedDocument, scopeRoot string) bool { + prefix := path.Join(path.Clean(scopeRoot), "research") + "/" + relative, found := strings.CutPrefix(path.Clean(document.path), prefix) + if !found { + return false + } + parts := strings.Split(relative, "/") + return len(parts) == 2 && strings.HasPrefix(parts[0], "R-") && parts[1] == "brief.md" +} + func isADR(documentPath string) bool { base := path.Base(documentPath) return strings.Contains(documentPath, "/adr/") && strings.HasPrefix(base, "ADR-") && base != "ADR-XXX.md" @@ -346,6 +366,36 @@ func (report *Report) checkFeatureLifecycle(documents map[string]governedDocumen } } +func (report *Report) checkResearchLifecycle(documents map[string]governedDocument, scopeRoot string) { + prefix := strings.TrimSuffix(scopeRoot, "/") + "/research/" + packages := map[string]map[string]governedDocument{} + for documentPath, document := range documents { + if !strings.HasPrefix(documentPath, prefix) { + continue + } + relative := strings.TrimPrefix(documentPath, prefix) + parts := strings.Split(relative, "/") + if len(parts) < 2 || !strings.HasPrefix(parts[0], "R-") { + continue + } + if packages[parts[0]] == nil { + packages[parts[0]] = map[string]governedDocument{} + } + packages[parts[0]][strings.Join(parts[1:], "/")] = document + } + for packageName, files := range packages { + brief, hasBrief := files["brief.md"] + packagePath := path.Join(prefix, packageName) + if !hasBrief { + report.add(Finding{Code: "lifecycle.research_brief_missing", Severity: Error, Group: "lifecycle_consistency", Path: packagePath, Message: "Research package contains artifacts without canonical brief.md.", Remediation: "Create brief.md from the governed research template before adding research artifacts."}) + continue + } + if _, exists := brief.frontmatter["research_status"]; !exists { + report.add(Finding{Code: "lifecycle.research_status_missing", Severity: Error, Group: "lifecycle_consistency", Path: brief.path, Message: "Canonical research brief does not own research_status.", Remediation: "Add the package research lifecycle state to brief.md."}) + } + } +} + func featureDesignDecision(content string) (string, bool) { section := designRequirementSection(content) matches := designRequirementDecision.FindAllStringSubmatch(section, -1) diff --git a/tools/internal/ownership/classify.go b/tools/internal/ownership/classify.go index 9a5bb81..4604cb9 100644 --- a/tools/internal/ownership/classify.go +++ b/tools/internal/ownership/classify.go @@ -20,7 +20,7 @@ func Classify(path string) Class { return Adapted } } - for _, prefix := range []string{"memory-bank/prd/", "memory-bank/epics/", "memory-bank/use-cases/", "memory-bank/features/", "memory-bank/adr/"} { + for _, prefix := range []string{"memory-bank/prd/", "memory-bank/research/", "memory-bank/epics/", "memory-bank/use-cases/", "memory-bank/features/", "memory-bank/adr/"} { if strings.HasPrefix(path, prefix) { if path == prefix+"README.md" { return Managed diff --git a/tools/internal/ownership/classify_test.go b/tools/internal/ownership/classify_test.go index 1871194..9baedd2 100644 --- a/tools/internal/ownership/classify_test.go +++ b/tools/internal/ownership/classify_test.go @@ -10,6 +10,8 @@ func TestCurrentTemplateBoundary(t *testing.T) { "memory-bank/domain/model.md": Adapted, "memory-bank/engineering/architecture.md": Adapted, "memory-bank/README.md": Adapted, + "memory-bank/research/README.md": Managed, + "memory-bank/research/R-001/README.md": UserOwned, "memory-bank/features/README.md": Managed, "memory-bank/features/FT-001/brief.md": UserOwned, "memory-bank/features/FT-001/README.md": UserOwned, @@ -24,7 +26,7 @@ func TestCurrentTemplateBoundary(t *testing.T) { } func TestOnlyTopLevelArtifactIndexesAreManaged(t *testing.T) { - for _, directory := range []string{"prd", "epics", "use-cases", "features", "adr"} { + for _, directory := range []string{"prd", "research", "epics", "use-cases", "features", "adr"} { t.Run(directory, func(t *testing.T) { if got := Classify("memory-bank/" + directory + "/README.md"); got != Managed { t.Fatalf("top-level index classified as %q, want %q", got, Managed) From d0195c90a3f3d9e2202979ab693aa8e644d3ee36 Mon Sep 17 00:00:00 2001 From: Danil Pismenny Date: Thu, 23 Jul 2026 11:36:00 +0500 Subject: [PATCH 3/3] fix: validate research lifecycle gates --- dependency-tree.md | 16 +++ memory-bank/flows/templates/research/plan.md | 1 - tools/internal/doctor/doctor_test.go | 125 +++++++++++++++++++ tools/internal/doctor/governance.go | 104 +++++++++++++++ 4 files changed, 245 insertions(+), 1 deletion(-) diff --git a/dependency-tree.md b/dependency-tree.md index e22db0b..75fd4a9 100644 --- a/dependency-tree.md +++ b/dependency-tree.md @@ -58,11 +58,19 @@ memory-bank/dna/principles.md ├── memory-bank/flows/feature.md ├── memory-bank/flows/incident.md ├── memory-bank/flows/refactoring.md + ├── memory-bank/flows/research.md ├── memory-bank/flows/routing.md ├── memory-bank/flows/small-change.md ├── memory-bank/flows/templates/README.md ├── memory-bank/flows/templates/adr/ADR-XXX.md ├── memory-bank/flows/templates/prd/PRD-XXX.md + ├── memory-bank/flows/templates/research/README.md + ├── memory-bank/flows/templates/research/package-README.md + ├── memory-bank/flows/templates/research/brief.md + ├── memory-bank/flows/templates/research/plan.md + ├── memory-bank/flows/templates/research/evidence.md + ├── memory-bank/flows/templates/research/synthesis.md + ├── memory-bank/flows/templates/research/decision.md ├── memory-bank/flows/templates/use-case/UC-XXX.md ├── memory-bank/ops/README.md ├── memory-bank/ops/config.md @@ -71,6 +79,7 @@ memory-bank/dna/principles.md ├── memory-bank/ops/runbooks/README.md ├── memory-bank/ops/stages.md ├── memory-bank/prd/README.md + ├── memory-bank/research/README.md ├── memory-bank/use-cases/README.md └── memory-bank/adr/README.md ``` @@ -84,6 +93,7 @@ memory-bank/dna/principles.md - Этот файл `dependency-tree.md` зависит от [`memory-bank/dna/governance.md`](memory-bank/dna/governance.md), но сознательно живет вне `memory-bank/`. - [`memory-bank/flows/routing.md`](memory-bank/flows/routing.md) зависит от governance и [`memory-bank/engineering/autonomy-boundaries.md`](memory-bank/engineering/autonomy-boundaries.md); branch flows используют его как upstream owner route selection. - [`memory-bank/flows/feature.md`](memory-bank/flows/feature.md) зависит от governance, frontmatter и [`memory-bank/flows/routing.md`](memory-bank/flows/routing.md). +- [`memory-bank/flows/research.md`](memory-bank/flows/research.md) зависит от governance, frontmatter и [`memory-bank/flows/routing.md`](memory-bank/flows/routing.md); он определяет lifecycle research packages и их artifact ownership. - [`memory-bank/flows/epic.md`](memory-bank/flows/epic.md) зависит от governance/frontmatter, [`memory-bank/flows/routing.md`](memory-bank/flows/routing.md) и [`memory-bank/flows/feature.md`](memory-bank/flows/feature.md); Epic Intake templates `package-README.md` и `brief.md` зависят от этого flow. - [`memory-bank/flows/small-change.md`](memory-bank/flows/small-change.md), [`memory-bank/flows/bug-fix.md`](memory-bank/flows/bug-fix.md) и [`memory-bank/flows/refactoring.md`](memory-bank/flows/refactoring.md) зависят от routing, governance и [`memory-bank/engineering/testing-policy.md`](memory-bank/engineering/testing-policy.md). - [`memory-bank/flows/incident.md`](memory-bank/flows/incident.md) зависит от routing, governance, testing policy и дополнительно от [`memory-bank/ops/runbooks/README.md`](memory-bank/ops/runbooks/README.md). @@ -99,6 +109,12 @@ memory-bank/dna/principles.md - [`memory-bank/flows/templates/feature/implementation-plan.md`](memory-bank/flows/templates/feature/implementation-plan.md) зависит от [`memory-bank/flows/feature.md`](memory-bank/flows/feature.md), [`memory-bank/dna/frontmatter.md`](memory-bank/dna/frontmatter.md) и [`memory-bank/engineering/testing-policy.md`](memory-bank/engineering/testing-policy.md). - Feature-support templates [`runtime-surfaces.md`](memory-bank/flows/templates/feature/support/runtime-surfaces.md), [`ui-reference.md`](memory-bank/flows/templates/feature/support/ui-reference.md) и [`use-cases.md`](memory-bank/flows/templates/feature/support/use-cases.md) зависят от [`memory-bank/flows/feature.md`](memory-bank/flows/feature.md) и [`memory-bank/dna/frontmatter.md`](memory-bank/dna/frontmatter.md). +### Research-related Docs + +- [`memory-bank/research/README.md`](memory-bank/research/README.md) зависит от [`memory-bank/dna/governance.md`](memory-bank/dna/governance.md) и [`memory-bank/flows/research.md`](memory-bank/flows/research.md). +- [`memory-bank/flows/templates/research/README.md`](memory-bank/flows/templates/research/README.md) зависит от [`memory-bank/flows/research.md`](memory-bank/flows/research.md) и [`memory-bank/dna/frontmatter.md`](memory-bank/dna/frontmatter.md). +- Research package templates [`package-README.md`](memory-bank/flows/templates/research/package-README.md), [`plan.md`](memory-bank/flows/templates/research/plan.md), [`evidence.md`](memory-bank/flows/templates/research/evidence.md), [`synthesis.md`](memory-bank/flows/templates/research/synthesis.md) и [`decision.md`](memory-bank/flows/templates/research/decision.md) зависят от [`memory-bank/flows/research.md`](memory-bank/flows/research.md); [`brief.md`](memory-bank/flows/templates/research/brief.md) дополнительно зависит от [`memory-bank/dna/frontmatter.md`](memory-bank/dna/frontmatter.md). + ### Product And Domain Docs - [`memory-bank/product/context.md`](memory-bank/product/context.md), [`memory-bank/product/vision.md`](memory-bank/product/vision.md), [`memory-bank/product/customers.md`](memory-bank/product/customers.md), [`memory-bank/product/metrics.md`](memory-bank/product/metrics.md), [`memory-bank/product/marketing.md`](memory-bank/product/marketing.md) и [`memory-bank/product/roadmap.md`](memory-bank/product/roadmap.md) зависят от [`memory-bank/dna/governance.md`](memory-bank/dna/governance.md) и product upstream-документов, указанных в их `derived_from`. diff --git a/memory-bank/flows/templates/research/plan.md b/memory-bank/flows/templates/research/plan.md index 8d51f54..5e55d74 100644 --- a/memory-bank/flows/templates/research/plan.md +++ b/memory-bank/flows/templates/research/plan.md @@ -67,7 +67,6 @@ Steps, instrument version, benchmark environment or query strategy sufficient fo | Field | Value | | --- | --- | -| Status | `draft / active` | | Reviewer / decision owner | `` | | Approval reference | `` | ``` diff --git a/tools/internal/doctor/doctor_test.go b/tools/internal/doctor/doctor_test.go index db20992..24266bd 100644 --- a/tools/internal/doctor/doctor_test.go +++ b/tools/internal/doctor/doctor_test.go @@ -330,6 +330,131 @@ func TestResearchLifecycleMetadataIsValidatedAndOwnedByBrief(t *testing.T) { } } +func TestResearchLifecycleArtifactsMatchStage(t *testing.T) { + repo := t.TempDir() + write := func(relative, contents string) { + t.Helper() + fullPath := filepath.Join(repo, filepath.FromSlash(relative)) + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(fullPath, []byte(contents), 0o644); err != nil { + t.Fatal(err) + } + } + write("memory-bank/research/R-001/brief.md", "---\nstatus: draft\nresearch_status: intake\n---\n# Brief\n") + write("memory-bank/research/R-001/synthesis.md", "---\nstatus: active\n---\n# Synthesis\n") + write("memory-bank/research/R-001/decision.md", "---\nstatus: active\n---\n# Decision\n") + write("memory-bank/research/R-002/brief.md", "---\nstatus: active\nresearch_status: collecting\n---\n# Brief\n") + write("memory-bank/research/R-002/decision.md", "---\nstatus: active\n---\n# Decision\n") + write("memory-bank/research/R-003/brief.md", "---\nstatus: active\nresearch_status: validated\n---\n# Brief\n") + write("memory-bank/research/R-003/evidence.md", "---\nstatus: draft\n---\n# Evidence\n") + write("memory-bank/research/R-003/synthesis.md", "---\nstatus: draft\n---\n# Synthesis\n") + write("memory-bank/research/R-003/decision.md", "---\nstatus: draft\n---\n# Decision\n") + write("memory-bank/research/R-004/brief.md", "---\nstatus: active\nresearch_status: collecting\n---\n# Brief\n") + write("memory-bank/research/R-004/evidence.md", "---\nstatus: archived\n---\n# Evidence\n") + write("memory-bank/research/R-005/brief.md", "---\nstatus: active\nresearch_status: cancelled\n---\n# Brief\n") + write("memory-bank/research/R-005/decision.md", "---\nstatus: draft\n---\n# Decision\n") + + report, err := Run(Options{RepoRoot: repo, ScopeRoot: "memory-bank", Profile: ProfileTemplate, MaxDepth: 3}) + if err != nil { + t.Fatal(err) + } + for _, code := range []string{"lifecycle.research_intake_later_artifact", "lifecycle.research_collecting_evidence_missing", "lifecycle.research_collecting_evidence_unusable", "lifecycle.research_collecting_later_artifact", "lifecycle.research_evidence_missing", "lifecycle.research_evidence_not_active", "lifecycle.research_synthesis_missing", "lifecycle.research_synthesis_not_active", "lifecycle.research_decision_not_active"} { + if !hasFinding(report, code) { + t.Fatalf("missing %s in %#v", code, report.Findings) + } + } +} + +func TestResearchLifecycleArtifactsAcceptValidProgression(t *testing.T) { + repo := t.TempDir() + write := func(relative, contents string) { + t.Helper() + fullPath := filepath.Join(repo, filepath.FromSlash(relative)) + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(fullPath, []byte(contents), 0o644); err != nil { + t.Fatal(err) + } + } + write("memory-bank/research/R-001/README.md", "---\nstatus: active\n---\n# Research\n") + write("memory-bank/research/R-001/brief.md", "---\nstatus: active\nresearch_status: validated\n---\n# Brief\n") + write("memory-bank/research/R-001/plan.md", "---\nstatus: active\n---\n# Plan\n") + write("memory-bank/research/R-001/evidence.md", "---\nstatus: active\n---\n# Evidence\n") + write("memory-bank/research/R-001/synthesis.md", "---\nstatus: active\n---\n# Synthesis\n") + write("memory-bank/research/R-001/decision.md", "---\nstatus: active\n---\n# Decision\n") + + report, err := Run(Options{RepoRoot: repo, ScopeRoot: "memory-bank", Profile: ProfileTemplate, MaxDepth: 3}) + if err != nil { + t.Fatal(err) + } + for _, finding := range report.Findings { + if strings.HasPrefix(finding.Code, "lifecycle.research_") { + t.Fatalf("unexpected research lifecycle finding %#v", finding) + } + } +} + +func TestResearchLifecycleAllowsDraftNextStageArtifacts(t *testing.T) { + repo := t.TempDir() + write := func(relative, contents string) { + t.Helper() + fullPath := filepath.Join(repo, filepath.FromSlash(relative)) + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(fullPath, []byte(contents), 0o644); err != nil { + t.Fatal(err) + } + } + write("memory-bank/research/R-001/README.md", "---\nstatus: active\n---\n# Research\n") + write("memory-bank/research/R-001/brief.md", "---\nstatus: active\nresearch_status: framed\n---\n# Brief\n") + write("memory-bank/research/R-001/plan.md", "---\nstatus: draft\n---\n# Plan\n") + write("memory-bank/research/R-002/README.md", "---\nstatus: active\n---\n# Research\n") + write("memory-bank/research/R-002/brief.md", "---\nstatus: active\nresearch_status: collecting\n---\n# Brief\n") + write("memory-bank/research/R-002/evidence.md", "---\nstatus: draft\n---\n# Evidence\n") + write("memory-bank/research/R-002/synthesis.md", "---\nstatus: draft\n---\n# Synthesis\n") + write("memory-bank/research/R-003/README.md", "---\nstatus: active\n---\n# Research\n") + write("memory-bank/research/R-003/brief.md", "---\nstatus: active\nresearch_status: synthesizing\n---\n# Brief\n") + write("memory-bank/research/R-003/evidence.md", "---\nstatus: active\n---\n# Evidence\n") + write("memory-bank/research/R-003/synthesis.md", "---\nstatus: active\n---\n# Synthesis\n") + write("memory-bank/research/R-003/decision.md", "---\nstatus: draft\n---\n# Decision\n") + write("memory-bank/research/R-004/README.md", "---\nstatus: active\n---\n# Research\n") + write("memory-bank/research/R-004/brief.md", "---\nstatus: active\nresearch_status: parked\n---\n# Brief\n") + write("memory-bank/research/R-004/plan.md", "---\nstatus: draft\n---\n# Plan\n") + + report, err := Run(Options{RepoRoot: repo, ScopeRoot: "memory-bank", Profile: ProfileTemplate, MaxDepth: 3}) + if err != nil { + t.Fatal(err) + } + for _, finding := range report.Findings { + if strings.HasPrefix(finding.Code, "lifecycle.research_") { + t.Fatalf("unexpected research lifecycle finding %#v", finding) + } + } +} + +func TestResearchLifecycleRequiresPackageREADME(t *testing.T) { + repo := t.TempDir() + path := filepath.Join(repo, "memory-bank", "research", "R-001", "brief.md") + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte("---\nstatus: active\nresearch_status: framed\n---\n# Brief\n"), 0o644); err != nil { + t.Fatal(err) + } + + report, err := Run(Options{RepoRoot: repo, ScopeRoot: "memory-bank", Profile: ProfileTemplate, MaxDepth: 3}) + if err != nil { + t.Fatal(err) + } + if !hasFinding(report, "lifecycle.research_package_readme_missing") { + t.Fatalf("missing lifecycle.research_package_readme_missing in %#v", report.Findings) + } +} + func TestResearchBriefOwnsResearchStatusWithoutOptionalClassification(t *testing.T) { if !isCanonicalResearchBrief(governedDocument{ path: "memory-bank/research/R-001/brief.md", diff --git a/tools/internal/doctor/governance.go b/tools/internal/doctor/governance.go index 42677ce..20e7280 100644 --- a/tools/internal/doctor/governance.go +++ b/tools/internal/doctor/governance.go @@ -384,16 +384,120 @@ func (report *Report) checkResearchLifecycle(documents map[string]governedDocume packages[parts[0]][strings.Join(parts[1:], "/")] = document } for packageName, files := range packages { + _, hasPackageREADME := files["README.md"] brief, hasBrief := files["brief.md"] packagePath := path.Join(prefix, packageName) + if !hasPackageREADME { + report.add(Finding{Code: "lifecycle.research_package_readme_missing", Severity: Error, Group: "lifecycle_consistency", Path: packagePath, Message: "Research package is missing its required README.md index.", Remediation: "Create README.md from the governed research package template before adding research artifacts."}) + } if !hasBrief { report.add(Finding{Code: "lifecycle.research_brief_missing", Severity: Error, Group: "lifecycle_consistency", Path: packagePath, Message: "Research package contains artifacts without canonical brief.md.", Remediation: "Create brief.md from the governed research template before adding research artifacts."}) continue } if _, exists := brief.frontmatter["research_status"]; !exists { report.add(Finding{Code: "lifecycle.research_status_missing", Severity: Error, Group: "lifecycle_consistency", Path: brief.path, Message: "Canonical research brief does not own research_status.", Remediation: "Add the package research lifecycle state to brief.md."}) + continue + } + researchStatus, validResearchStatus := brief.frontmatter["research_status"].(string) + if !validResearchStatus || !isResearchStatus(researchStatus) { + continue + } + report.checkResearchLifecycleStage(packagePath, brief, researchStatus, files) + } +} + +func (report *Report) checkResearchLifecycleStage(packagePath string, brief governedDocument, researchStatus string, files map[string]governedDocument) { + plan, hasPlan := files["plan.md"] + evidence, hasEvidence := files["evidence.md"] + synthesis, hasSynthesis := files["synthesis.md"] + decision, hasDecision := files["decision.md"] + briefStatus, _ := brief.frontmatter["status"].(string) + + if researchStatus == "intake" { + if briefStatus != "draft" { + report.add(Finding{Code: "lifecycle.research_intake_brief_not_draft", Severity: Error, Group: "lifecycle_consistency", Path: brief.path, Message: "An intake research package requires a draft brief.md.", Remediation: "Keep brief.md in draft until the Question Framed gate is complete, or update research_status after completing that gate."}) + } + for artifact, document := range map[string]governedDocument{"plan.md": plan, "evidence.md": evidence, "synthesis.md": synthesis, "decision.md": decision} { + if document.path != "" { + report.add(Finding{Code: "lifecycle.research_intake_later_artifact", Severity: Error, Group: "lifecycle_consistency", Path: document.path, Message: "An intake research package cannot contain " + artifact + ".", Remediation: "Remove the later-stage artifact, or complete the required gates and advance research_status."}) + } + } + return + } + + if briefStatus != "active" { + report.add(Finding{Code: "lifecycle.research_brief_not_active", Severity: Error, Group: "lifecycle_consistency", Path: brief.path, Message: "A non-intake research package requires an active brief.md.", Remediation: "Complete the Question Framed gate and set brief.md status to active."}) + } + if hasPlan && !allowsDraftResearchPlan(researchStatus) { + planStatus, _ := plan.frontmatter["status"].(string) + if planStatus != "active" { + report.add(Finding{Code: "lifecycle.research_plan_not_active", Severity: Error, Group: "lifecycle_consistency", Path: plan.path, Message: "A research plan must be active when present.", Remediation: "Activate plan.md after completing the method gate, or remove it when a plan is not required."}) + } + } + + if researchStatus == "framed" && (isActiveResearchArtifact(evidence, hasEvidence) || isActiveResearchArtifact(synthesis, hasSynthesis) || isActiveResearchArtifact(decision, hasDecision)) { + report.add(Finding{Code: "lifecycle.research_framed_later_artifact", Severity: Error, Group: "lifecycle_consistency", Path: packagePath, Message: "A framed research package cannot activate evidence, synthesis, or decision artifacts before Evidence Collection.", Remediation: "Keep later-stage artifacts in draft, or advance research_status after completing the applicable gate."}) + } + if researchStatus == "collecting" { + if !hasEvidence { + report.add(Finding{Code: "lifecycle.research_collecting_evidence_missing", Severity: Error, Group: "lifecycle_consistency", Path: packagePath, Message: "A collecting research package requires evidence.md.", Remediation: "Create evidence.md before setting research_status to collecting."}) + } else if evidenceStatus, _ := evidence.frontmatter["status"].(string); !oneOf(evidenceStatus, "draft", "active") { + report.add(Finding{Code: "lifecycle.research_collecting_evidence_unusable", Severity: Error, Group: "lifecycle_consistency", Path: evidence.path, Message: "A collecting research package requires evidence.md to be draft or active.", Remediation: "Restore evidence.md to draft or active before continuing collection."}) + } + if isActiveResearchArtifact(synthesis, hasSynthesis) || isActiveResearchArtifact(decision, hasDecision) { + report.add(Finding{Code: "lifecycle.research_collecting_later_artifact", Severity: Error, Group: "lifecycle_consistency", Path: packagePath, Message: "A collecting research package cannot activate synthesis or decision artifacts.", Remediation: "Keep later-stage artifacts in draft, or advance research_status after completing the applicable gate."}) } } + if researchStatus == "synthesizing" && isActiveResearchArtifact(decision, hasDecision) { + report.add(Finding{Code: "lifecycle.research_synthesizing_decision_present", Severity: Error, Group: "lifecycle_consistency", Path: decision.path, Message: "A synthesizing research package cannot activate decision.md before the Decision Ready gate.", Remediation: "Keep decision.md in draft, or advance research_status after completing the Decision Ready gate."}) + } + if researchStatus == "synthesizing" || requiresResearchDecisionArtifacts(researchStatus, hasDecision) { + if !hasEvidence { + report.add(Finding{Code: "lifecycle.research_evidence_missing", Severity: Error, Group: "lifecycle_consistency", Path: packagePath, Message: "A synthesizing or decided research package requires evidence.md.", Remediation: "Create evidence.md and complete evidence collection before synthesis or decision."}) + } else if evidenceStatus, _ := evidence.frontmatter["status"].(string); evidenceStatus != "active" { + report.add(Finding{Code: "lifecycle.research_evidence_not_active", Severity: Error, Group: "lifecycle_consistency", Path: evidence.path, Message: "A synthesizing or decided research package requires an active evidence.md.", Remediation: "Complete evidence collection and set evidence.md status to active before synthesis or decision."}) + } + if !hasSynthesis { + report.add(Finding{Code: "lifecycle.research_synthesis_missing", Severity: Error, Group: "lifecycle_consistency", Path: packagePath, Message: "A synthesizing or decided research package requires synthesis.md.", Remediation: "Create and activate synthesis.md before advancing beyond evidence collection."}) + } else if synthesisStatus, _ := synthesis.frontmatter["status"].(string); synthesisStatus != "active" { + report.add(Finding{Code: "lifecycle.research_synthesis_not_active", Severity: Error, Group: "lifecycle_consistency", Path: synthesis.path, Message: "A synthesizing or decided research package requires an active synthesis.md.", Remediation: "Complete the Evidence Collection gate and set synthesis.md status to active."}) + } + } + if requiresResearchDecisionArtifacts(researchStatus, hasDecision) { + if !hasDecision { + report.add(Finding{Code: "lifecycle.research_decision_missing", Severity: Error, Group: "lifecycle_consistency", Path: packagePath, Message: "A decision-ready or decided research package requires decision.md.", Remediation: "Create and activate decision.md before setting research_status to decision_ready or a terminal disposition."}) + } else if decisionStatus, _ := decision.frontmatter["status"].(string); decisionStatus != "active" { + report.add(Finding{Code: "lifecycle.research_decision_not_active", Severity: Error, Group: "lifecycle_consistency", Path: decision.path, Message: "A decision-ready or decided research package requires an active decision.md.", Remediation: "Complete the Decision Ready gate and set decision.md status to active."}) + } + } +} + +func isResearchStatus(researchStatus string) bool { + return oneOf(researchStatus, "intake", "framed", "collecting", "synthesizing", "decision_ready", "validated", "invalidated", "inconclusive", "parked", "cancelled", "rerouted") +} + +func isEvidenceBasedResearchOutcome(researchStatus string) bool { + return oneOf(researchStatus, "validated", "invalidated", "inconclusive") +} + +func requiresResearchDecisionArtifacts(researchStatus string, hasDecision bool) bool { + return researchStatus == "decision_ready" || isEvidenceBasedResearchOutcome(researchStatus) || (isEarlyTerminalResearchStatus(researchStatus) && hasDecision) +} + +func isEarlyTerminalResearchStatus(researchStatus string) bool { + return oneOf(researchStatus, "parked", "cancelled", "rerouted") +} + +func allowsDraftResearchPlan(researchStatus string) bool { + return oneOf(researchStatus, "framed", "parked", "cancelled", "rerouted") +} + +func isActiveResearchArtifact(document governedDocument, exists bool) bool { + if !exists { + return false + } + status, _ := document.frontmatter["status"].(string) + return status == "active" } func featureDesignDecision(content string) (string, bool) {