diff --git a/.gitignore b/.gitignore index 9fd963c0..e54c0fa7 100644 --- a/.gitignore +++ b/.gitignore @@ -61,8 +61,8 @@ archive/ .sdp/checkpoints/ !.sdp/evidence/ !.sdp/checkpoints/ -.sdp/log/events.jsonl -sdp-plugin/.sdp/log/events.jsonl +.sdp/log/ +sdp-plugin/.sdp/log/ **/events.jsonl # Build output diff --git a/CHANGELOG.md b/CHANGELOG.md index 53a4bf5d..79757eca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to the Spec-Driven Protocol (SDP). +## [Unreleased] + +### Reality OSS Publication Polish + +- Published open `reality` schemas under `schema/reality/` +- Added `sdp reality validate` for emitted OSS artifact checks +- Aligned CLI/help/completions with the actual OSS `reality` baseline +- Added public `reality` spec docs and a focused OSS reference/checklist + ## [0.9.8] - 2026-02-26 ### Skills Sync, Beads Integration diff --git a/CLAUDE.md b/CLAUDE.md index 9401884c..9f26178c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ Quick reference for using SDP CLI v0.9.8 with Claude Code. ```bash @vision "AI-powered task manager" # Strategic planning -@reality --quick # Codebase analysis +@reality --quick # Local codebase baseline @feature "Add user authentication" # Plan feature @build 00-001-01 # Execute workstream @review # Quality check @@ -67,7 +67,7 @@ New project? | Level | Orchestrator | Purpose | Output | |-------|-------------|---------|--------| | **Strategic** | @vision (7 agents) | Product planning | VISION, PRD, ROADMAP | -| **Analysis** | @reality (8 agents) | Codebase analysis | Reality report | +| **Analysis** | @reality (single-repo baseline) | Local codebase analysis | Reality artifacts | | **Feature** | @feature (roadmap pre-check + @discovery + @idea + @ux + @design) | Requirements + WS | Workstreams | | **Execution** | @oneshot (@build) | Parallel execution | Implemented code | @@ -75,7 +75,7 @@ New project? **@vision** — New project, major pivot, quarterly strategic review -**@reality** — New to project, before @feature, track tech debt, quarterly review +**@reality** — New to project, before @feature, establish a local baseline, track tech debt, quarterly review **@feature** — Feature idea but no workstreams, need interactive planning (full discovery flow) @@ -96,7 +96,7 @@ New project? | Skill | Purpose | Phase | |-------|---------|-------| | `@vision` | Strategic product planning (7 expert agents) | Strategic | -| `@reality` | Codebase analysis (8 expert agents) | Analysis | +| `@reality` | Local single-repo baseline scan | Analysis | | `@feature` | Planning orchestrator (roadmap pre-check + discovery + idea + ux + design) | Planning | | `@discovery` | Product discovery gate (roadmap check, research loop) | Planning | | `@idea` | Requirements gathering (AskUserQuestion) | Planning | @@ -152,6 +152,7 @@ New project? # 2. Codebase analysis @reality --quick +sdp reality emit-oss --quick # 3. Feature planning (per feature) @feature "User can reset password via email" diff --git a/PRODUCT_VISION.md b/PRODUCT_VISION.md index 4212dda5..806ca51b 100644 --- a/PRODUCT_VISION.md +++ b/PRODUCT_VISION.md @@ -66,7 +66,7 @@ Each level is independently valuable. Upgrade path is additive. - Protocol spec with 12 skills (v0.9.8, 18 releases) - Evidence log with hash-chain provenance - CLI: `sdp doctor`, `sdp guard`, `sdp log`, `sdp status` -- Multi-agent review (6 agents), strategic planning (7 agents), codebase analysis (8 agents) +- Multi-agent review (6 agents), strategic planning (7 agents), OSS reality baseline analysis - Install script with auto-detect for Claude Code, Cursor, OpenCode - 1,004 commits, 16 stars, MIT license diff --git a/README.md b/README.md index 3f627e34..226af129 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Skills load from `sdp/.claude/skills/` (Claude) or `sdp/.cursor/skills/` (Cursor ```bash sdp init --auto +@reality --quick @feature "Your feature" @oneshot @review @@ -58,6 +59,7 @@ sdp init --auto | Skill | Purpose | |-------|---------| | `@vision` | Strategic planning | +| `@reality` | Local codebase baseline and SDP readiness scan | | `@feature` | Feature planning (→ workstreams) | | `@oneshot` | Autonomous execution | | `@build` | Single workstream (TDD) | @@ -67,7 +69,7 @@ sdp init --auto ## Optional -**CLI:** `sdp doctor`, `sdp status`, `sdp next`, `sdp guard activate`, `sdp log show`, `sdp demo` +**CLI:** `sdp doctor`, `sdp status`, `sdp next`, `sdp guard activate`, `sdp log show`, `sdp demo`, `sdp reality emit-oss`, `sdp reality validate` **Beads:** `brew tap beads-dev/tap && brew install beads` — task tracking for multi-session work. @@ -84,6 +86,7 @@ sdp init --auto | [ROADMAP.md](docs/ROADMAP.md) | Where SDP is going | | [PROTOCOL.md](docs/PROTOCOL.md) | Full specification | | [reference/](docs/reference/) | Principles, glossary, specs | +| [reference/reality-oss.md](docs/reference/reality-oss.md) | OSS reality command, outputs, checklist | ## License diff --git a/docs/CLI_REFERENCE.md b/docs/CLI_REFERENCE.md index 459976ae..0b0ec041 100644 --- a/docs/CLI_REFERENCE.md +++ b/docs/CLI_REFERENCE.md @@ -10,7 +10,7 @@ Run `sdp --help` for the full command tree and `sdp --help` for flags | Planning and execution | `sdp parse`, `sdp plan`, `sdp build`, `sdp apply`, `sdp orchestrate`, `sdp verify`, `sdp tdd`, `sdp deploy` | Parse workstreams, create plans, execute work, verify completion, and record deployment approvals | | Guard and context | `sdp guard ...`, `sdp session ...`, `sdp resolve`, `sdp git` | Enforce edit scope, validate context/branch state, resolve task identifiers, and keep session state in sync | | Evidence and audit | `sdp log ...`, `sdp decisions ...`, `sdp checkpoint ...`, `sdp coordination ...`, `sdp design record`, `sdp idea record` | Inspect evidence, trace decision history, manage checkpoints, and record design/idea evidence | -| Quality and diagnostics | `sdp quality {coverage, complexity, size, types, all}`, `sdp drift detect`, `sdp diagnose`, `sdp watch`, `sdp collision check`, `sdp contract ...`, `sdp acceptance run` | Run quality gates, detect drift, inspect failures, watch files, check collisions, validate contracts, and run smoke acceptance checks | +| Quality and diagnostics | `sdp quality {coverage, complexity, size, types, all}`, `sdp drift detect`, `sdp diagnose`, `sdp watch`, `sdp reality {emit-oss, validate}`, `sdp collision check`, `sdp contract ...`, `sdp acceptance run` | Run quality gates, detect drift, emit or validate OSS reality artifacts, inspect failures, watch files, check collisions, validate contracts, and run smoke acceptance checks | | Telemetry and metrics | `sdp telemetry {status, consent, enable, disable, analyze, export, upload}`, `sdp metrics {collect, classify, report}` | Manage local opt-in telemetry and derive benchmark/quality metrics | | Workflow support | `sdp beads ...`, `sdp task create`, `sdp memory ...`, `sdp prd ...`, `sdp prototype`, `sdp skill ...` | Integrate with Beads, manage memory/search, work with PRDs, prototype features, and inspect skills | @@ -21,6 +21,8 @@ Run `sdp --help` for the full command tree and `sdp --help` for flags | `sdp init --auto` | Initialize prompts and SDP scaffolding without prompts | | `sdp doctor` | Run health checks for hooks, config, telemetry, and repository setup | | `sdp build ` | Execute a single workstream with guard enforcement and tests | +| `sdp reality emit-oss --quick` | Emit the OSS reality baseline into `.sdp/reality/` and `docs/reality/` | +| `sdp reality validate` | Validate emitted OSS reality artifacts against the published schema contract | | `sdp verify ` | Validate workstream completion against evidence and checks | | `sdp quality all` | Run all quality gates for the current project | | `sdp telemetry status` | Show telemetry consent, event count, and storage path | @@ -28,4 +30,4 @@ Run `sdp --help` for the full command tree and `sdp --help` for flags | `sdp log show` | Show paginated evidence events with filters | | `sdp decisions log` | Record a decision in the audit trail | -See [reference/skills.md](reference/skills.md) for the skill catalog and [PROTOCOL.md](PROTOCOL.md) for the full protocol spec. +See [reference/reality-oss.md](reference/reality-oss.md) for the reality OSS contract and [reference/skills.md](reference/skills.md) for the skill catalog. diff --git a/docs/README.md b/docs/README.md index 52f9d242..bb2ab834 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,6 +11,8 @@ Protocol specification and reference for SDP (Spec-Driven Protocol). | [CLI_REFERENCE.md](CLI_REFERENCE.md) | SDP CLI commands | | [MANIFESTO.md](MANIFESTO.md) | Vision, evidence, what exists | | [ROADMAP.md](ROADMAP.md) | Where SDP is going, user-facing orientation | +| [specs/reality/OSS-SPEC.md](specs/reality/OSS-SPEC.md) | OSS reality product scope | +| [specs/reality/ARTIFACT-CONTRACT.md](specs/reality/ARTIFACT-CONTRACT.md) | Open reality artifact contract | ## Reference @@ -27,6 +29,7 @@ Protocol specification and reference for SDP (Spec-Driven Protocol). | [reference/schema-registry.md](reference/schema-registry.md) | Schema families: contracts, findings, handoffs | | [reference/integration-contracts.md](reference/integration-contracts.md) | How to apply contracts, findings, handoffs, provenance in integrations | | [reference/skills.md](reference/skills.md) | Skill catalog | +| [reference/reality-oss.md](reference/reality-oss.md) | OSS reality command, outputs, checklist | | [reference/pipeline-hooks-security.md](reference/pipeline-hooks-security.md) | Hook execution hardening | ## Schemas & Attestation diff --git a/docs/reference/README.md b/docs/reference/README.md index 99a96a1a..82890cf1 100644 --- a/docs/reference/README.md +++ b/docs/reference/README.md @@ -9,6 +9,7 @@ Quick lookup guides for SDP commands, configuration, and quality standards. ## Contents - [Commands](#commands) +- [Reality OSS](#reality-oss) - [Quality Gates](#quality-gates) - [Configuration](#configuration) - [Pipeline Hooks Security](#pipeline-hooks-security) @@ -42,6 +43,19 @@ Quick lookup guides for SDP commands, configuration, and quality standards. --- +## Reality OSS + +Local single-repo reality baseline for OSS users: + +- `sdp reality emit-oss` +- `sdp reality validate` +- `docs/specs/reality/OSS-SPEC.md` +- `docs/specs/reality/ARTIFACT-CONTRACT.md` + +**See:** [reality-oss.md](reality-oss.md) — command surface, outputs, and publish checklist + +--- + ## Quality Gates ### Mandatory Checks @@ -192,6 +206,7 @@ Structured errors with: | Need | Doc | |------|-----| | Command syntax | [../CLI_REFERENCE.md](../CLI_REFERENCE.md) | +| Reality OSS usage and checklist | [reality-oss.md](reality-oss.md) | | Quality standards | [build-spec.md](build-spec.md) | | Hook security rules | [pipeline-hooks-security.md](pipeline-hooks-security.md) | | Schema map | [schema-registry.md](schema-registry.md) | diff --git a/docs/reference/reality-oss.md b/docs/reference/reality-oss.md new file mode 100644 index 00000000..712d56ab --- /dev/null +++ b/docs/reference/reality-oss.md @@ -0,0 +1,43 @@ +# Reality OSS Reference + +Public OSS `reality` is a local, evidence-first baseline for one repository. It emits open artifacts and validates them against the published schema contract. + +## Commands + +```bash +sdp reality emit-oss [--quick|--deep|--bootstrap-sdp] [--focus=architecture|quality|testing|docs|security] +sdp reality validate +``` + +## Outputs + +Machine-readable outputs: + +- `.sdp/reality/reality-summary.json` +- `.sdp/reality/feature-inventory.json` +- `.sdp/reality/architecture-map.json` +- `.sdp/reality/integration-map.json` +- `.sdp/reality/quality-report.json` +- `.sdp/reality/drift-report.json` +- `.sdp/reality/readiness-report.json` + +Human-readable outputs: + +- `docs/reality/summary.md` +- `docs/reality/architecture.md` +- `docs/reality/quality.md` +- `docs/reality/bootstrap.md` + +## Published Contract + +- [../specs/reality/OSS-SPEC.md](../specs/reality/OSS-SPEC.md) +- [../specs/reality/ARTIFACT-CONTRACT.md](../specs/reality/ARTIFACT-CONTRACT.md) +- `schema/reality/*.schema.json` + +## Publish Checklist + +1. Run `sdp reality emit-oss` in a representative repository. +2. Run `sdp reality validate`. +3. Check that `docs/reality/` and `.sdp/reality/` changed deterministically on rerun. +4. Confirm docs and help text describe OSS `reality` as a single-repo baseline, not a consulting-grade multi-agent mesh. +5. If the public contract changes, update `schema/reality/`, `docs/specs/reality/`, and `prompts/skills/reality/SKILL.md` in the same PR. diff --git a/docs/specs/reality/ARTIFACT-CONTRACT.md b/docs/specs/reality/ARTIFACT-CONTRACT.md new file mode 100644 index 00000000..d6878b94 --- /dev/null +++ b/docs/specs/reality/ARTIFACT-CONTRACT.md @@ -0,0 +1,333 @@ +# Reality Artifact Contract + +## Status + +Draft. + +## Goal + +This document defines the open artifact contract shared by `reality` and `reality-pro`. + +The contract must remain open even if premium logic stays private. Private value should live in analysis depth and orchestration quality, not in opaque output formats. + +## Design Rules + +1. Artifacts must be machine-readable first and human-readable second. +2. Claims must distinguish observation from inference. +3. Premium outputs may extend the contract, but not break base artifacts. +4. OSS and Pro must share IDs, enums, and evidence semantics. + +## Suggested Repository Layout + +Human-readable outputs: + +- `docs/reality/summary.md` +- `docs/reality/architecture.md` +- `docs/reality/quality.md` +- `docs/reality/bootstrap.md` +- `docs/reality/c4-*.md` for Pro + +Machine-readable outputs: + +- `.sdp/reality/*.json` + +Schema definitions, once formalized: + +- `schema/reality/*.schema.json` + +Product specs: + +- `docs/specs/reality/OSS-SPEC.md` +- `docs/specs/reality/PRO-SPEC.md` +- `docs/specs/reality/ARTIFACT-CONTRACT.md` + +## Core Types + +### Claim + +Every important conclusion is a `claim`. + +Required fields: + +- `claim_id` +- `title` +- `statement` +- `status` +- `confidence` +- `source_ids` +- `review_state` + +Recommended fields: + +- `affected_repos` +- `affected_paths` +- `affected_components` +- `counter_evidence` +- `open_questions` +- `tags` + +### Claim Status Enum + +- `observed`: directly supported by code, config, test, or executable runtime evidence +- `documented`: stated in docs but not yet confirmed in implementation +- `inferred`: reasoned from indirect evidence +- `conflicted`: evidence sources disagree +- `unknown`: insufficient support + +### Review State Enum + +- `unreviewed` +- `cross_checked` +- `challenged` +- `arbitrated` + +### Source + +Every source referenced by claims must be normalized. + +Required fields: + +- `source_id` +- `kind` +- `locator` +- `revision` + +Recommended fields: + +- `repo` +- `path` +- `uri` +- `line_range` +- `excerpt_hash` +- `captured_at` + +### Source Kind Enum + +- `code` +- `test` +- `config` +- `manifest` +- `doc` +- `issue` +- `pull_request` +- `commit` +- `runtime_trace` +- `external_contract` + +## Artifact Families + +### 1. Summary Family + +Purpose: top-level run summary. + +Files: + +- `reality-summary.json` +- `summary.md` + +Must include: + +- run scope +- analyzed repos +- top findings +- readiness verdict +- artifact inventory + +### 2. Feature Inventory Family + +Purpose: reconstructed features and implementation status. + +Files: + +- `feature-inventory.json` + +Per feature entry: + +- synthetic or canonical feature id +- title +- summary +- status: `implemented|partial|candidate|dead` +- evidence claims +- confidence +- mapped components + +### 3. Architecture Family + +Purpose: reconstructed structure of the system. + +Files: + +- `architecture-map.json` +- `c4-system-context.json` (Pro) +- `c4-container.json` (Pro) +- `c4-component.json` (Pro) + +Must support: + +- nodes +- edges +- boundaries +- external integrations +- data stores +- hotspots + +Pro C4 payload expectations: + +- `c4-system-context.json`: system scope, internal/external systems, relationships +- `c4-container.json`: container inventory, responsibilities, inter-container relationships +- `c4-component.json`: component inventory, code-path mapping, relationships inside a container + +### 4. Integration Family + +Purpose: external and internal dependency mapping. + +Files: + +- `integration-map.json` + +Must include: + +- integration type +- producer and consumer +- contract type if known +- confidence +- failure and risk notes if known + +### 5. Quality Family + +Purpose: code-quality and test-quality assessment. + +Files: + +- `quality-report.json` + +Must include: + +- maintainability findings +- test posture +- hotspot ranking +- major code smells +- structural risks + +### 6. Drift and Intent Family + +Purpose: document contradictions and gaps. + +Files: + +- `drift-report.json` +- `intent-gap-report.json` (Pro) +- `conflicts-report.json` (Pro) + +Must include: + +- contradictory claims +- stale docs or misleading docs +- plan-vs-implementation mismatches +- unresolved questions + +Pro payload expectations: + +- `intent-gap-report.json`: expected state, observed state, gap type, severity, status +- `conflicts-report.json`: competing claim IDs, conflict severity, arbitration status, resolution notes if any + +### 7. Readiness Family + +Purpose: answer whether the system is safe for future agent work. + +Files: + +- `readiness-report.json` +- `agent-readiness-plan.json` (Pro) +- `bootstrap-backlog.json` (Pro) + +Required readiness enum: + +- `ready` +- `ready_with_constraints` +- `not_ready` + +Required readiness dimensions: + +- boundary clarity +- verification coverage +- hotspot concentration +- integration fragility +- documentation trust level + +Pro payload expectations: + +- `agent-readiness-plan.json`: current verdict, target verdict, phased readiness plan, allowed scope, blocked zones, exit criteria +- `bootstrap-backlog.json`: proposed workstreams, evidence-backed rationale, dependencies, exit criteria + +### 8. Persistent Memory Family + +Purpose: carry normalized memory across later reality-pro refreshes. + +Files: + +- `repo-memory.json` (Pro) + +Must include: + +- analyzed repo set +- module summaries +- feature-to-code mappings +- unresolved questions +- reusable hotspot context + +## OSS vs Pro Contract Rules + +OSS may emit: + +- summary family +- feature inventory family +- architecture family without full C4 depth +- integration family +- quality family +- drift family without premium intent recovery +- readiness family baseline + +Pro may extend with: + +- persistent memory artifacts +- full C4 artifacts +- intent-gap family +- conflict arbitration outputs +- bootstrap backlog and agent-readiness plan + +Pro must not rename or change base enums used by OSS. + +## Validation Rules + +1. Every finding of consequence must be linked to at least one source. +2. `observed` claims must reference executable evidence classes: code, config, manifest, test, or runtime trace. +3. `documented` claims may not be promoted to `observed` without stronger evidence. +4. `conflicted` claims must preserve both sides of the contradiction. +5. Final readiness verdict must cite the claims that justify it. + +Validation command for OSS artifacts: + +```bash +go run ./cmd/sdp-reality-validate . +``` + +## Schema Split + +Schema files are split by artifact family: + +- `schema/reality/claim.schema.json` +- `schema/reality/source.schema.json` +- `schema/reality/reality-summary.schema.json` +- `schema/reality/feature-inventory.schema.json` +- `schema/reality/architecture-map.schema.json` +- `schema/reality/integration-map.schema.json` +- `schema/reality/quality-report.schema.json` +- `schema/reality/drift-report.schema.json` +- `schema/reality/readiness-report.schema.json` +- `schema/reality/repo-memory.schema.json` (Pro) +- `schema/reality/conflicts-report.schema.json` (Pro) +- `schema/reality/intent-gap-report.schema.json` (Pro) +- `schema/reality/bootstrap-backlog.schema.json` (Pro) +- `schema/reality/agent-readiness-plan.schema.json` (Pro) +- `schema/reality/c4-*.schema.json` (Pro) diff --git a/docs/specs/reality/OSS-SPEC.md b/docs/specs/reality/OSS-SPEC.md new file mode 100644 index 00000000..596ca409 --- /dev/null +++ b/docs/specs/reality/OSS-SPEC.md @@ -0,0 +1,225 @@ +# Reality OSS Spec + +## Status + +Draft. + +## Purpose + +`@reality` in OSS is a local, evidence-first reverse-engineering workflow for a single repository. Its job is to recover a usable baseline of the system as it exists today and convert that baseline into SDP-ready artifacts. + +It must answer: + +- What is implemented? +- How is the code organized? +- What are the major boundaries, dependencies, and entrypoints? +- Where do code and documentation drift? +- Is this repository ready for agent-driven work under SDP? + +It must not pretend to replace a consulting-grade technical audit. + +## Goals + +1. Analyze one repository without external services. +2. Reconstruct a baseline feature inventory from code, tests, configs, and in-repo docs. +3. Recover a practical architecture map: modules, boundaries, entrypoints, integrations, data stores. +4. Produce a code-quality and test-quality baseline. +5. Detect obvious documentation drift and unsupported claims. +6. Prepare bootstrap artifacts for future SDP workstreams. + +## Non-Goals + +- Multi-repo system reconstruction. +- Native connectors to Confluence, Jira, Notion, or wiki systems. +- Persistent cross-run repository memory. +- Consulting-grade intent recovery from historical and external evidence. +- Heavy adversarial review orchestration. +- Automatic refactoring or remediation during analysis. + +## Inputs + +Required inputs: + +- repository working tree +- source code +- tests +- local configs and manifests +- local git history +- in-repo documentation + +Optional inputs: + +- manually provided local exports of external docs +- user-provided product focus or analysis scope + +## Source Priority Model + +OSS `@reality` must rank sources in this order: + +1. code and executable configuration +2. tests and fixtures +3. runtime manifests and deployment configuration +4. local repository docs +5. user notes or attached exported docs + +Every conclusion must be tagged as one of: + +- `observed` +- `documented` +- `inferred` +- `conflicted` +- `unknown` + +## Modes + +| Mode | Purpose | Expected Depth | +|---|---|---| +| `--quick` | fast baseline scan | same artifact families, reduced finding detail | +| `--deep` | full single-repo baseline | feature inventory, architecture map, quality, drift, readiness | +| `--focus=architecture|quality|testing|docs|security` | domain-specific reporting emphasis | one area, deeper local review | +| `--bootstrap-sdp` | generate agent-readiness outputs | workstream and scope bootstrap with starter recommendations | + +## Workflow + +### Phase 1: Intake + +- detect language, framework, build tool, test tool, runtime shape +- detect repository scale: file count, LOC, package/module count +- classify repo type: app, library, protocol, infra, mixed + +### Phase 2: Local Index Build + +Build a temporary local index for the current run: + +- file inventory +- import/dependency graph +- test inventory +- config inventory +- docs inventory +- entrypoint candidates + +The OSS index is run-scoped, not persistent across sessions. + +### Phase 3: Baseline Analysis + +Required analyzers: + +- structure analyzer +- architecture analyzer +- integration analyzer +- quality analyzer +- testing analyzer +- documentation drift analyzer + +### Phase 4: Limited Cross-Check Review + +OSS must still avoid single-agent overconfidence. + +Minimum review structure: + +1. primary source-first pass generates findings from code and executable config +2. secondary heuristic review checks findings against tests, manifests, and docs +3. synthesis keeps supported findings and downgrades weak claims + +If the review pass cannot confirm a claim, the claim must be downgraded to `inferred`, `conflicted`, or `unknown`. + +Spawning multiple LLM agents is optional and not required for OSS. + +### Phase 5: Synthesis + +Produce: + +- feature baseline +- architecture baseline +- risk baseline +- readiness baseline +- suggested first SDP workstreams + +## Required Outputs + +Human-readable outputs: + +- `docs/reality/summary.md` +- `docs/reality/architecture.md` +- `docs/reality/quality.md` +- `docs/reality/bootstrap.md` + +Machine-readable outputs: + +- `.sdp/reality/reality-summary.json` +- `.sdp/reality/feature-inventory.json` +- `.sdp/reality/architecture-map.json` +- `.sdp/reality/integration-map.json` +- `.sdp/reality/quality-report.json` +- `.sdp/reality/drift-report.json` +- `.sdp/reality/readiness-report.json` + +## Minimum Artifact Semantics + +### Feature Inventory + +Each feature candidate should include: + +- feature id or synthetic id +- title +- description +- evidence paths +- status: implemented, partial, candidate, dead +- confidence + +### Architecture Map + +Must include at least: + +- top-level modules +- entrypoints +- internal boundaries +- external integrations +- data stores +- high-coupling zones + +### Readiness Report + +Must answer: + +- can safe scopes be defined? +- are there tests that protect future changes? +- what hotspots block autonomous work? +- what should be isolated first? + +## Done Criteria + +OSS `@reality` is done when it can, for one repository: + +1. identify top-level code areas and entrypoints +2. produce a baseline feature inventory with evidence and confidence +3. produce an architecture map with integrations and boundaries +4. report major code-quality and documentation-drift findings +5. produce a repository readiness verdict: + - `ready` + - `ready_with_constraints` + - `not_ready` +6. emit bootstrap recommendations for SDP workstreams + +## Agent Readiness Rules + +OSS readiness focuses on future SDP execution. It must evaluate: + +- safe scope extraction +- module size and coupling +- existence of verification surfaces +- hidden dependencies +- large files and low-test zones +- unstable boundaries + +## Open-Core Boundary + +OSS owns: + +- single-repo baseline analysis +- open artifact formats +- local analyzers +- limited cross-check review inside one run +- SDP bootstrap starter outputs + +Anything requiring persistent memory, native external connectors, multi-repo orchestration, or consulting-grade adversarial synthesis belongs in `reality-pro`. diff --git a/docs/specs/reality/PRO-SPEC.md b/docs/specs/reality/PRO-SPEC.md new file mode 100644 index 00000000..e94923e8 --- /dev/null +++ b/docs/specs/reality/PRO-SPEC.md @@ -0,0 +1,333 @@ +# Reality Pro Spec + +## Status + +Draft. + +## Purpose + +`@reality-pro` is a consulting-grade, multi-source, multi-repo analysis workflow built on top of the open `reality` artifact contract. Its goal is to reconstruct how a system actually works, recover likely design intent, expose contradictions between plan and implementation, and prepare a system for high-trust agent execution. + +It should behave less like a repo scanner and more like a repeatable technical consulting pipeline. + +## Product Boundary + +`reality-pro` is private logic over open contracts. + +Open: + +- artifact formats +- claim model +- readiness statuses +- base schema families + +Private: + +- orchestration logic +- scoring heuristics +- memory implementation +- connector implementations +- domain-specific analyzers +- arbitration and synthesis logic + +## Goals + +1. Analyze one repository or a coordinated set of repositories. +2. Ingest code plus optional external knowledge sources. +3. Auto-select the right specialist agents for the detected stack and domain. +4. Require cross-review of findings and review of synthesis. +5. Reconstruct features, intent, architecture, integrations, and risks. +6. Build C4 views and a code map for the analyzed system. +7. Produce agent-readiness and bootstrap plans for future SDP work. + +## Non-Goals + +- automatic remediation by default +- replacing subject-matter experts when evidence is missing +- asserting business intent without traceable evidence +- treating documentation as truth when code contradicts it + +## Inputs + +Required: + +- one repository or explicit reposet +- git history +- build and test metadata +- local documentation + +Optional premium inputs: + +- Confluence/wiki/Notion pages +- Jira or other issue trackers +- PR history and review comments +- ADR collections +- runbooks and incident records +- schema registries and external contracts +- ownership and team metadata + +## Operating Principles + +### Multi-Source Truth + +No source is privileged by default. `reality-pro` compares sources and explicitly records disagreement. + +### Claim Typing + +Every claim must be typed: + +- `observed` +- `documented` +- `inferred` +- `conflicted` +- `unknown` + +### Confidence Discipline + +Every significant finding must include: + +- confidence score +- source set +- opposing evidence, if any +- validation status + +### Adversarial Review + +No important conclusion should survive on a single-agent pass. + +## Modes + +| Mode | Purpose | +|---|---| +| `--repo ` | deep analysis of one repository | +| `--reposet ` | coordinated multi-repo analysis | +| `--with-docs` | include external documentation sources | +| `--reconstruct-intent` | emphasize intent recovery and plan-vs-implementation gaps | +| `--bootstrap-sdp` | generate bootstrap backlog and readiness plan | +| `--domain ` | bias agent selection for domain-heavy systems | + +Current private lab baseline: + +- `go run ./cmd/sdp-reality-pro-ingest --repo .` +- `go run ./cmd/sdp-reality-pro-ingest --reposet .,/abs/path/to/other/repo` +- `go run ./cmd/sdp-reality-pro-review --project-root .` +- `go run ./cmd/sdp-reality-pro-report --project-root .` + +## Phase Model + +### Phase 1: Ingestion + +Ingest: + +- repositories +- local docs +- optional external docs +- optional issue/PR/ADR history + +Normalize all evidence into a common source model. + +### Phase 2: Persistent Index and Memory Build + +Build and maintain persistent repo memory: + +- module summaries +- glossary and domain vocabulary +- feature-to-code mappings +- integration endpoints +- previous validated claims +- unresolved questions +- hotspots and risk zones + +This memory must support incremental refresh after later repository changes. + +Current private baseline writes: + +- `.sdp/reality/repo-memory.json` +- `docs/reality/multi-repo-map.md` + +### Phase 3: Specialist Selection + +Auto-select specialists from stack and domain signals. + +Selection dimensions: + +- language and framework +- storage and database types +- platform and infrastructure stack +- security surface +- data flow and integration style +- product/domain signals + +Example specialist pool: + +- architecture analyst +- runtime analyst +- database analyst +- API analyst +- frontend analyst +- documentation analyst +- test and quality analyst +- security analyst +- domain analyst +- intent analyst + +### Phase 4: Parallel Specialist Passes + +Run specialists in parallel against the same normalized source graph. + +Each specialist must produce: + +- scoped findings +- evidence references +- confidence +- open questions +- contradictions with other evidence + +### Phase 5: Cross-Review and Dissent + +For important findings, require: + +1. primary finding +2. opposing review or skeptic pass +3. arbitration decision + +Unresolved disagreement becomes a first-class output, not hidden synthesis noise. + +Current private lab baseline emits: + +- `.sdp/reality/conflicts-report.json` +- `.sdp/reality/intent-gap-report.json` + +The current implementation uses deterministic specialist heuristics over `repo-memory.json`. It is not yet the full consulting-grade agent mesh. + +### Phase 6: System Reconstruction + +Recover: + +- feature inventory +- stated vs implemented intent +- system context +- containers +- components +- code-area map +- integration and data-flow map +- constraints and hotspots + +Current private lab baseline emits: + +- `.sdp/reality/c4-system-context.json` +- `.sdp/reality/c4-container.json` +- `.sdp/reality/c4-component.json` +- `docs/reality/c4-system-context.md` +- `docs/reality/c4-containers.md` +- `docs/reality/c4-components.md` + +### Phase 7: Readiness and Bootstrap Synthesis + +Produce: + +- readiness verdict +- first safe workstreams +- high-risk zones to avoid or isolate +- test-first recommendations +- documentation priorities +- bootstrap backlog for SDP delivery + +Current private lab baseline emits: + +- `.sdp/reality/bootstrap-backlog.json` +- `.sdp/reality/agent-readiness-plan.json` +- `docs/reality/intent-gap.md` +- `docs/reality/multi-repo-map.md` + +These outputs are deterministic syntheses over repo memory plus reviewed findings. They are executable now, but they are not yet the full multi-source consulting pipeline described in this spec. + +### Phase 8: Synthesis Review + +The final synthesis itself must be reviewed. + +The synthesis reviewer checks: + +- unsupported claims +- hidden contradictions +- overconfident language +- missing risk qualifiers +- missing next steps + +## Required Outputs + +All OSS outputs, plus: + +- `docs/reality/c4-system-context.md` +- `docs/reality/c4-containers.md` +- `docs/reality/c4-components.md` +- `docs/reality/intent-gap.md` +- `docs/reality/multi-repo-map.md` +- `.sdp/reality/repo-memory.json` +- `.sdp/reality/conflicts-report.json` +- `.sdp/reality/c4-system-context.json` +- `.sdp/reality/c4-container.json` +- `.sdp/reality/c4-component.json` +- `.sdp/reality/intent-gap-report.json` +- `.sdp/reality/bootstrap-backlog.json` +- `.sdp/reality/agent-readiness-plan.json` + +## C4 Scope + +`reality-pro` should support these layers: + +- system context +- container +- component +- code area map + +For multi-repo runs, it must also emit a repo landscape view: + +- repo roles +- ownership zones +- dependency direction +- contract and schema sharing +- version skew risks + +## Readiness Model + +`reality-pro` should judge not only code quality, but readiness for autonomous development. + +Dimensions: + +- boundary clarity +- scope extraction quality +- verification surfaces +- operational safety +- integration stability +- documentation trustworthiness +- memory completeness +- unresolved architectural ambiguity + +Verdicts: + +- `ready` +- `ready_with_constraints` +- `not_ready` + +## Consulting-Grade Expectations + +To justify premium value, `reality-pro` must provide: + +- evidence-backed claims +- explicit unknowns +- prioritized risks +- preservation advice for fragile areas +- safe-first modernization guidance +- recommended first workstreams + +## Exit Criteria + +A `reality-pro` run is complete when it has: + +1. reconstructed the analyzed system at feature and architecture level +2. produced C4 views and repo landscape views +3. identified the top contradictions between intent, docs, and implementation +4. mapped main integrations and data boundaries +5. produced a reviewed synthesis with confidence and dissent tracking +6. emitted a bootstrap-ready SDP backlog and agent-readiness plan diff --git a/prompts/commands/reality.md b/prompts/commands/reality.md index 15ac34df..89e52213 100644 --- a/prompts/commands/reality.md +++ b/prompts/commands/reality.md @@ -1,32 +1,30 @@ --- -description: Codebase analysis and architecture validation - what's actually there vs documented +description: Reconstruct a single-repo reality baseline from local evidence and emit open reality artifacts agent: builder --- -# /reality — Reality +# /reality - Reality ## Overview -This command implements the reality skill from the SDP workflow. +This command runs the OSS `@reality` baseline. It is local, evidence-first, and single-repo scoped. -See `/prompts/skills/reality/SKILL.md` for complete documentation. - -## Usage +## Runtime ```bash -/reality [arguments] +sdp reality emit-oss [--quick|--deep|--bootstrap-sdp] [--focus=architecture|quality|testing|docs|security] +sdp reality validate ``` -## Implementation - -The command delegates to the `reality` skill, which provides: +## Behavior -- Systematic workflow -- Quality gates -- Proper error handling -- Documentation +- Scans code, tests, configs, manifests, and in-repo docs. +- Emits `.sdp/reality/*.json` and `docs/reality/*.md`. +- Runs a heuristic cross-check pass inside the same repository. +- Does not spawn subagents or claim `reality-pro` behavior. ## Related -- Skills: `prompts/skills/reality/SKILL.md` -- Agents: `prompts/agents/builder.md` +- Skill: `prompts/skills/reality/SKILL.md` +- Spec: `docs/specs/reality/OSS-SPEC.md` +- Reference: `docs/reference/reality-oss.md` diff --git a/prompts/skills/reality/SKILL.md b/prompts/skills/reality/SKILL.md index 1840b5df..760bd253 100644 --- a/prompts/skills/reality/SKILL.md +++ b/prompts/skills/reality/SKILL.md @@ -1,159 +1,107 @@ --- name: reality -description: Codebase analysis and architecture validation - what's actually there vs documented -version: 2.0.0 +description: Single-repo reality scan that reconstructs code, docs, drift, integrations, and SDP readiness from local evidence +version: 2.1.0 changes: - - Converted to LLM-agnostic format - - Removed tool-specific API references - - Focus on WHAT, not HOW to invoke + - Aligned public contract with OSS runtime + - Added explicit mode surface for quick, deep, focus, and bootstrap-sdp + - Removed fake multi-agent Task fan-out claims from OSS --- -# @reality - Codebase Analysis & Architecture Validation +# @reality - Single-Repo Reality Baseline -**Analyze what's actually in your codebase (vs. what's documented).** +Use `@reality` to recover what is actually present in one repository and emit the open reality artifact set. ---- - -## Workflow - -When user invokes `@reality`: - -1. Auto-detect project type -2. Run scan based on mode (--quick, --deep, --focus) -3. Spawn expert agents in parallel using Task tool -4. Synthesize report with health score - ---- +OSS `@reality` is evidence-first and local-only. It does not pretend to be `reality-pro`. -## Step 0: Auto-Detect Project Type +## What OSS Does -```bash -# Detect language/framework -if [ -f "go.mod" ]; then PROJECT_TYPE="go" -elif [ -f "pyproject.toml" ] || [ -f "requirements.txt" ]; then PROJECT_TYPE="python" -elif [ -f "pom.xml" ] || [ -f "build.gradle" ]; then PROJECT_TYPE="java" -elif [ -f "package.json" ]; then PROJECT_TYPE="nodejs" -else PROJECT_TYPE="unknown" -fi -``` +1. Scans one repository working tree. +2. Builds a baseline from code, tests, configs, manifests, and in-repo docs. +3. Emits machine-readable artifacts in `.sdp/reality/`. +4. Emits human-readable summaries in `docs/reality/`. +5. Runs a heuristic cross-check pass across code, tests, configs, manifests, and docs inside the same run. -## Step 1: Quick Scan (--quick mode) +## What OSS Does Not Do -**Analysis:** -1. Project size (lines of code, file count) -2. Architecture (layer violations, circular dependencies) -3. Test coverage (if tests exist, estimate %) -4. Documentation (doc coverage, drift detection) -5. Quick smell check (TODO/FIXME/HACK comments, long files) +- No Task-tool fan-out. +- No "8 expert agents" promise. +- No multi-repo orchestration. +- No persistent repository memory. +- No consulting-grade adversarial synthesis. -**Output:** Health Score X/100 + Top 5 Issues +Those belong in `reality-pro`, not here. -## Step 2: Deep Analysis (--deep mode) +## Runtime Surface -Spawn 8 parallel expert analyses using Task tool with subagent_type: +Primary command: +```bash +sdp reality emit-oss [--quick|--deep|--bootstrap-sdp] [--focus=architecture|quality|testing|docs|security] +sdp reality validate ``` -Task(subagent_type="general-purpose", prompt="Analyze ARCHITECTURE...") -Task(subagent_type="general-purpose", prompt="Analyze CODE QUALITY...") -Task(subagent_type="general-purpose", prompt="Analyze TESTING...") -Task(subagent_type="general-purpose", prompt="Analyze SECURITY...") -Task(subagent_type="general-purpose", prompt="Analyze PERFORMANCE...") -Task(subagent_type="general-purpose", prompt="Analyze DOCUMENTATION...") -Task(subagent_type="general-purpose", prompt="Analyze TECHNICAL DEBT...") -Task(subagent_type="general-purpose", prompt="Analyze STANDARDS...") -``` - -Expert agents: -1. ARCHITECTURE expert - Layer mapping, dependencies, violations -2. CODE QUALITY expert - File size, complexity, duplication -3. TESTING expert - Coverage, test quality, frameworks -4. SECURITY expert - Secrets, OWASP, dependencies -5. PERFORMANCE expert - Bottlenecks, caching, scalability -6. DOCUMENTATION expert - Coverage, drift, quality -7. TECHNICAL DEBT expert - TODO/FIXME, code smells -8. STANDARDS expert - Conventions, error handling, types - -## Step 3: Synthesize Report - -Create comprehensive report with: -- Executive Summary with Health Score -- Critical Issues (Fix Now) -- Quick Wins (Fix Today) -- Detailed Analysis from each expert -- Action Items (This Week / This Month / This Quarter) - ---- - -## When to Use - -- **New to project** - "What's actually here?" -- **Before @feature** - "What can we build on?" -- **After @vision** - "How do docs match code?" -- **Quarterly review** - Track tech debt and quality trends -- **Debugging mysteries** - "Why doesn't this work?" ---- - -## Modes +Mode rules: -| Mode | Duration | Purpose | -|------|----------|---------| -| `@reality --quick` | 5-10 min | Health check + top issues | -| `@reality --deep` | 30-60 min | Comprehensive with 8 experts | -| `@reality --focus=security` | Varies | Security expert deep dive | -| `@reality --focus=architecture` | Varies | Architecture expert deep dive | -| `@reality --focus=testing` | Varies | Testing expert deep dive | -| `@reality --focus=performance` | Varies | Performance expert deep dive | +- `--deep` is the default OSS baseline. +- `--quick` emits the same artifact families with reduced evidence detail. +- `--bootstrap-sdp` keeps the same baseline scan but emphasizes first-workstream recommendations and agent-readiness notes. +- `--focus=...` adds reporting emphasis for one domain without changing the open artifact contract. ---- +## Output Contract -## Output +Human-readable outputs: -``` -## Reality Check: {project_name} +- `docs/reality/summary.md` +- `docs/reality/architecture.md` +- `docs/reality/quality.md` +- `docs/reality/bootstrap.md` -### Quick Stats -- Language: {detected} -- Size: {LOC} lines, {N} files -- Architecture: {layers detected} -- Tests: {coverage if available} +Machine-readable outputs: -### Top 5 Issues -1. {issue} - {severity} - - Location: {file:line} - - Impact: {why it matters} - - Fix: {recommendation} +- `.sdp/reality/reality-summary.json` +- `.sdp/reality/feature-inventory.json` +- `.sdp/reality/architecture-map.json` +- `.sdp/reality/integration-map.json` +- `.sdp/reality/quality-report.json` +- `.sdp/reality/drift-report.json` +- `.sdp/reality/readiness-report.json` -### Health Score: {X}/100 -``` +## OSS Review Semantics ---- +OSS still avoids single-pass overconfidence, but it does so with a local cross-check strategy: -## Vision Integration +1. Primary source-first pass over code and executable config. +2. Secondary heuristic review against tests, manifests, and docs. +3. Synthesis that downgrades weak documentation claims into drift or unresolved questions. -If PRODUCT_VISION.md exists, compare reality to vision with Vision vs Reality Gap analysis: +This is limited review, not multi-agent arbitration. -| Feature | PRD Status | Reality Status | Gap | -|---------|------------|----------------|-----| -| Feature 1 | P0 | Implemented | None | -| Feature 2 | P1 | Partial | Missing X | -| Feature 3 | P0 | Not found | Not started | +## Mode Guidance ---- +| Mode | Use When | Result | +|---|---|---| +| `--quick` | Need a fast baseline before planning | Same artifact set, less detail in findings | +| `--deep` | Need the normal OSS baseline | Full local single-repo scan | +| `--focus=architecture` | Need boundaries and entrypoints | Architecture report gets extra emphasis | +| `--focus=quality` | Need hotspots and maintainability | Quality findings get extra emphasis | +| `--focus=testing` | Need verification posture | Readiness emphasizes missing tests | +| `--focus=docs` | Need doc drift review | Drift and trust signals get extra emphasis | +| `--focus=security` | Need boundary review | Integration surfaces and failure notes are highlighted | +| `--bootstrap-sdp` | Need first SDP-safe slices | Bootstrap report emits starter workstreams | ## Examples ```bash -@reality --quick # Quick health check -@reality --deep # Deep analysis -@reality --focus=security # Security only -@reality --deep --output=docs/reality/check.md # Save report +sdp reality emit-oss +sdp reality emit-oss --quick +sdp reality emit-oss --focus=docs +sdp reality emit-oss --bootstrap-sdp --focus=architecture ``` ---- - -## See Also +## Related -- `@vision` - Strategic planning -- `@feature` - Feature planning -- `@idea` - Requirements gathering +- `docs/specs/reality/OSS-SPEC.md` +- `docs/specs/reality/ARTIFACT-CONTRACT.md` +- `docs/reference/reality-oss.md` +- `prompts/commands/reality.md` diff --git a/schema/index.json b/schema/index.json index 06670746..b4273dbf 100644 --- a/schema/index.json +++ b/schema/index.json @@ -11,6 +11,23 @@ { "id": "review-verdict", "path": "review-verdict.schema.json", "title": "SDP Review Verdict" }, { "id": "ws-verdict", "path": "ws-verdict.schema.json", "title": "SDP Workstream Verdict" }, { "id": "next-action", "path": "next-action.schema.json", "title": "SDP Next Action" }, + { "id": "reality-claim", "path": "reality/claim.schema.json", "title": "RealityClaim" }, + { "id": "reality-source", "path": "reality/source.schema.json", "title": "RealitySource" }, + { "id": "reality-summary", "path": "reality/reality-summary.schema.json", "title": "RealitySummary" }, + { "id": "reality-feature-inventory", "path": "reality/feature-inventory.schema.json", "title": "FeatureInventory" }, + { "id": "reality-architecture-map", "path": "reality/architecture-map.schema.json", "title": "ArchitectureMap" }, + { "id": "reality-integration-map", "path": "reality/integration-map.schema.json", "title": "IntegrationMap" }, + { "id": "reality-quality-report", "path": "reality/quality-report.schema.json", "title": "QualityReport" }, + { "id": "reality-drift-report", "path": "reality/drift-report.schema.json", "title": "DriftReport" }, + { "id": "reality-readiness-report", "path": "reality/readiness-report.schema.json", "title": "ReadinessReport" }, + { "id": "reality-repo-memory", "path": "reality/repo-memory.schema.json", "title": "RealityRepoMemory" }, + { "id": "reality-conflicts-report", "path": "reality/conflicts-report.schema.json", "title": "RealityConflictsReport" }, + { "id": "reality-intent-gap-report", "path": "reality/intent-gap-report.schema.json", "title": "RealityIntentGapReport" }, + { "id": "reality-bootstrap-backlog", "path": "reality/bootstrap-backlog.schema.json", "title": "RealityBootstrapBacklog" }, + { "id": "reality-agent-readiness-plan", "path": "reality/agent-readiness-plan.schema.json", "title": "RealityAgentReadinessPlan" }, + { "id": "reality-c4-system-context", "path": "reality/c4-system-context.schema.json", "title": "RealityC4SystemContext" }, + { "id": "reality-c4-container", "path": "reality/c4-container.schema.json", "title": "RealityC4Container" }, + { "id": "reality-c4-component", "path": "reality/c4-component.schema.json", "title": "RealityC4Component" }, { "id": "instructions", "path": "contracts/instructions.schema.json", "title": "Instruction Payload Contract" }, { "id": "status-view", "path": "contracts/status-view.schema.json", "title": "Status View Contract" }, { "id": "orchestration-event", "path": "contracts/orchestration-event.schema.json", "title": "Orchestration Event Contract" }, diff --git a/schema/reality/agent-readiness-plan.schema.json b/schema/reality/agent-readiness-plan.schema.json new file mode 100644 index 00000000..9bef7f80 --- /dev/null +++ b/schema/reality/agent-readiness-plan.schema.json @@ -0,0 +1,136 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/agent-readiness-plan/v1", + "title": "RealityAgentReadinessPlan", + "description": "Phased plan for taking a system from current to target agent readiness.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "current_verdict", + "target_verdict", + "phases" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "current_verdict": { + "type": "string", + "enum": [ + "ready", + "ready_with_constraints", + "not_ready" + ] + }, + "target_verdict": { + "type": "string", + "enum": [ + "ready", + "ready_with_constraints", + "not_ready" + ] + }, + "phases": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "phase_id", + "title", + "objective" + ], + "properties": { + "phase_id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "objective": { + "type": "string", + "minLength": 1 + }, + "allowed_scope": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "blocked_zones": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "required_evidence": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "verification_requirements": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "exit_criteria": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "justification_claim_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "key_risks": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "sequencing_notes": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/schema/reality/architecture-map.schema.json b/schema/reality/architecture-map.schema.json new file mode 100644 index 00000000..bb3d53e7 --- /dev/null +++ b/schema/reality/architecture-map.schema.json @@ -0,0 +1,118 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/architecture-map/v1", + "title": "ArchitectureMap", + "description": "System architecture graph reconstructed by reality.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "nodes", + "edges" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "nodes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "node_id", + "name", + "kind" + ], + "properties": { + "node_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "boundary": { + "type": "string" + }, + "repo": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "edges": { + "type": "array", + "items": { + "type": "object", + "required": [ + "from", + "to", + "relation" + ], + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "relation": { + "type": "string" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "additionalProperties": false + } + }, + "hotspots": { + "type": "array", + "items": { + "type": "object", + "required": [ + "node_id", + "reason" + ], + "properties": { + "node_id": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "severity": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/schema/reality/bootstrap-backlog.schema.json b/schema/reality/bootstrap-backlog.schema.json new file mode 100644 index 00000000..4dc6f10b --- /dev/null +++ b/schema/reality/bootstrap-backlog.schema.json @@ -0,0 +1,130 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/bootstrap-backlog/v1", + "title": "RealityBootstrapBacklog", + "description": "Bootstrap workstream backlog synthesized by reality-pro.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "workstreams" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "workstreams": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "backlog_id", + "title", + "goal", + "priority", + "status" + ], + "properties": { + "backlog_id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "goal": { + "type": "string", + "minLength": 1 + }, + "priority": { + "type": "string", + "enum": [ + "P0", + "P1", + "P2", + "P3" + ] + }, + "status": { + "type": "string", + "enum": [ + "proposed", + "sequenced", + "blocked" + ] + }, + "scope": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "repositories": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "evidence_claim_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "dependencies": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "recommended_agent": { + "type": "string" + }, + "rationale": { + "type": "string" + }, + "risk_level": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ] + }, + "exit_criteria": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/schema/reality/c4-component.schema.json b/schema/reality/c4-component.schema.json new file mode 100644 index 00000000..909c6a40 --- /dev/null +++ b/schema/reality/c4-component.schema.json @@ -0,0 +1,140 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/c4-component/v1", + "title": "RealityC4Component", + "description": "Component view emitted by reality-pro.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "container_id", + "components", + "relationships" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "container_id": { + "type": "string", + "minLength": 1 + }, + "components": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/component" + } + }, + "relationships": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/relationship" + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "$defs": { + "component": { + "type": "object", + "required": [ + "component_id", + "name", + "paths" + ], + "properties": { + "component_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string" + }, + "technology": { + "type": "string" + }, + "paths": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "interfaces": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "responsibilities": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + }, + "relationship": { + "type": "object", + "required": [ + "relationship_id", + "from", + "to", + "description" + ], + "properties": { + "relationship_id": { + "type": "string", + "minLength": 1 + }, + "from": { + "type": "string", + "minLength": 1 + }, + "to": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 1 + }, + "technology": { + "type": "string" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/schema/reality/c4-container.schema.json b/schema/reality/c4-container.schema.json new file mode 100644 index 00000000..603b095f --- /dev/null +++ b/schema/reality/c4-container.schema.json @@ -0,0 +1,136 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/c4-container/v1", + "title": "RealityC4Container", + "description": "Container view emitted by reality-pro.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "system_name", + "containers", + "relationships" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "system_name": { + "type": "string", + "minLength": 1 + }, + "containers": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/container" + } + }, + "relationships": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/relationship" + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "$defs": { + "container": { + "type": "object", + "required": [ + "container_id", + "name", + "technology" + ], + "properties": { + "container_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string" + }, + "technology": { + "type": "string", + "minLength": 1 + }, + "boundary": { + "type": "string", + "enum": [ + "internal", + "external" + ] + }, + "repo_id": { + "type": "string" + }, + "responsibilities": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + }, + "relationship": { + "type": "object", + "required": [ + "relationship_id", + "from", + "to", + "description" + ], + "properties": { + "relationship_id": { + "type": "string", + "minLength": 1 + }, + "from": { + "type": "string", + "minLength": 1 + }, + "to": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 1 + }, + "technology": { + "type": "string" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/schema/reality/c4-system-context.schema.json b/schema/reality/c4-system-context.schema.json new file mode 100644 index 00000000..4bf26c5e --- /dev/null +++ b/schema/reality/c4-system-context.schema.json @@ -0,0 +1,181 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/c4-system-context/v1", + "title": "RealityC4SystemContext", + "description": "System context view emitted by reality-pro.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "scope", + "systems", + "relationships" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "scope": { + "type": "object", + "required": [ + "system_name", + "repos" + ], + "properties": { + "system_name": { + "type": "string", + "minLength": 1 + }, + "repos": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + }, + "people": { + "type": "array", + "items": { + "$ref": "#/$defs/person" + } + }, + "systems": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/system" + } + }, + "relationships": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/relationship" + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "$defs": { + "person": { + "type": "object", + "required": [ + "person_id", + "name" + ], + "properties": { + "person_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false + }, + "system": { + "type": "object", + "required": [ + "system_id", + "name", + "boundary" + ], + "properties": { + "system_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "boundary": { + "type": "string", + "enum": [ + "internal", + "external" + ] + }, + "description": { + "type": "string" + }, + "repo_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + }, + "relationship": { + "type": "object", + "required": [ + "relationship_id", + "from", + "to", + "description" + ], + "properties": { + "relationship_id": { + "type": "string", + "minLength": 1 + }, + "from": { + "type": "string", + "minLength": 1 + }, + "to": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 1 + }, + "technology": { + "type": "string" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/schema/reality/claim.schema.json b/schema/reality/claim.schema.json new file mode 100644 index 00000000..e17c5737 --- /dev/null +++ b/schema/reality/claim.schema.json @@ -0,0 +1,102 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/claim/v1", + "title": "RealityClaim", + "description": "Normalized claim used across reality and reality-pro artifacts.", + "type": "object", + "required": [ + "claim_id", + "title", + "statement", + "status", + "confidence", + "source_ids", + "review_state" + ], + "properties": { + "claim_id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "statement": { + "type": "string", + "minLength": 1 + }, + "status": { + "type": "string", + "enum": [ + "observed", + "documented", + "inferred", + "conflicted", + "unknown" + ] + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "source_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "review_state": { + "type": "string", + "enum": [ + "unreviewed", + "cross_checked", + "challenged", + "arbitrated" + ] + }, + "affected_repos": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "affected_paths": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "affected_components": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "counter_evidence": { + "type": "array", + "items": { + "type": "string" + } + }, + "open_questions": { + "type": "array", + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false +} diff --git a/schema/reality/conflicts-report.schema.json b/schema/reality/conflicts-report.schema.json new file mode 100644 index 00000000..c387ebd7 --- /dev/null +++ b/schema/reality/conflicts-report.schema.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/conflicts-report/v1", + "title": "RealityConflictsReport", + "description": "Arbitrated disagreement report for reality-pro findings.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "conflicts" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "conflicts": { + "type": "array", + "items": { + "type": "object", + "required": [ + "conflict_id", + "summary", + "competing_claim_ids", + "severity", + "status" + ], + "properties": { + "conflict_id": { + "type": "string", + "minLength": 1 + }, + "summary": { + "type": "string", + "minLength": 1 + }, + "competing_claim_ids": { + "type": "array", + "minItems": 2, + "items": { + "type": "string", + "minLength": 1 + } + }, + "severity": { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "critical" + ] + }, + "status": { + "type": "string", + "enum": [ + "open", + "triaged", + "arbitrated" + ] + }, + "arbitrated_claim_id": { + "type": "string" + }, + "resolution_notes": { + "type": "string" + }, + "source_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/schema/reality/drift-report.schema.json b/schema/reality/drift-report.schema.json new file mode 100644 index 00000000..0bf20d00 --- /dev/null +++ b/schema/reality/drift-report.schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/drift-report/v1", + "title": "DriftReport", + "description": "Documentation and intent drift report from a reality run.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "contradictions" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "contradictions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "contradiction_id", + "summary", + "left_claim_id", + "right_claim_id" + ], + "properties": { + "contradiction_id": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "left_claim_id": { + "type": "string" + }, + "right_claim_id": { + "type": "string" + }, + "resolution_state": { + "type": "string", + "enum": [ + "open", + "triaged", + "resolved" + ] + } + }, + "additionalProperties": false + } + }, + "unresolved_questions": { + "type": "array", + "items": { + "type": "string" + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/schema/reality/feature-inventory.schema.json b/schema/reality/feature-inventory.schema.json new file mode 100644 index 00000000..46a05a62 --- /dev/null +++ b/schema/reality/feature-inventory.schema.json @@ -0,0 +1,90 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/feature-inventory/v1", + "title": "FeatureInventory", + "description": "Reconstructed feature inventory from a reality run.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "features" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "features": { + "type": "array", + "items": { + "type": "object", + "required": [ + "feature_id", + "title", + "summary", + "status", + "evidence_claim_ids", + "confidence", + "mapped_components" + ], + "properties": { + "feature_id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "summary": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "implemented", + "partial", + "candidate", + "dead" + ] + }, + "evidence_claim_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "mapped_components": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/schema/reality/integration-map.schema.json b/schema/reality/integration-map.schema.json new file mode 100644 index 00000000..0d5a495a --- /dev/null +++ b/schema/reality/integration-map.schema.json @@ -0,0 +1,74 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/integration-map/v1", + "title": "IntegrationMap", + "description": "Integration inventory reconstructed by reality.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "integrations" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "integrations": { + "type": "array", + "items": { + "type": "object", + "required": [ + "integration_id", + "integration_type", + "producer", + "consumer", + "confidence" + ], + "properties": { + "integration_id": { + "type": "string" + }, + "integration_type": { + "type": "string" + }, + "producer": { + "type": "string" + }, + "consumer": { + "type": "string" + }, + "contract_type": { + "type": "string" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "risk_notes": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/schema/reality/intent-gap-report.schema.json b/schema/reality/intent-gap-report.schema.json new file mode 100644 index 00000000..97350ade --- /dev/null +++ b/schema/reality/intent-gap-report.schema.json @@ -0,0 +1,117 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/intent-gap-report/v1", + "title": "RealityIntentGapReport", + "description": "Plan-vs-implementation gap report for reality-pro.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "gaps" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "gaps": { + "type": "array", + "items": { + "type": "object", + "required": [ + "gap_id", + "title", + "expected_state", + "observed_state", + "gap_type", + "severity", + "status" + ], + "properties": { + "gap_id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "expected_state": { + "type": "string", + "minLength": 1 + }, + "observed_state": { + "type": "string", + "minLength": 1 + }, + "gap_type": { + "type": "string", + "enum": [ + "missing", + "partial", + "contradicted", + "ambiguous" + ] + }, + "severity": { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "critical" + ] + }, + "status": { + "type": "string", + "enum": [ + "open", + "triaged", + "accepted", + "resolved" + ] + }, + "supporting_claim_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "affected_repos": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "recommended_actions": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/schema/reality/quality-report.schema.json b/schema/reality/quality-report.schema.json new file mode 100644 index 00000000..0a570581 --- /dev/null +++ b/schema/reality/quality-report.schema.json @@ -0,0 +1,104 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/quality-report/v1", + "title": "QualityReport", + "description": "Quality and test posture baseline from a reality run.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "findings" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "analysis_mode": { + "type": "string" + }, + "analysis_focus": { + "type": "string" + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "required": [ + "finding_id", + "title", + "severity", + "claim_ids" + ], + "properties": { + "finding_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "severity": { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "critical" + ] + }, + "description": { + "type": "string" + }, + "claim_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "hotspot_ranking": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "score" + ], + "properties": { + "path": { + "type": "string" + }, + "score": { + "type": "number", + "minimum": 0 + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/schema/reality/readiness-report.schema.json b/schema/reality/readiness-report.schema.json new file mode 100644 index 00000000..9ba6d829 --- /dev/null +++ b/schema/reality/readiness-report.schema.json @@ -0,0 +1,111 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/readiness-report/v1", + "title": "ReadinessReport", + "description": "Agent-readiness baseline from a reality run.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "verdict", + "dimensions", + "justification_claim_ids" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "verdict": { + "type": "string", + "enum": [ + "ready", + "ready_with_constraints", + "not_ready" + ] + }, + "dimensions": { + "type": "object", + "required": [ + "boundary_clarity", + "verification_coverage", + "hotspot_concentration", + "integration_fragility", + "documentation_trust_level" + ], + "properties": { + "boundary_clarity": { + "$ref": "#/$defs/dimensionScore" + }, + "verification_coverage": { + "$ref": "#/$defs/dimensionScore" + }, + "hotspot_concentration": { + "$ref": "#/$defs/dimensionScore" + }, + "integration_fragility": { + "$ref": "#/$defs/dimensionScore" + }, + "documentation_trust_level": { + "$ref": "#/$defs/dimensionScore" + } + }, + "additionalProperties": false + }, + "justification_claim_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "constraints": { + "type": "array", + "items": { + "type": "string" + } + }, + "suggested_workstreams": { + "type": "array", + "items": { + "type": "string" + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "$defs": { + "dimensionScore": { + "type": "object", + "required": [ + "score" + ], + "properties": { + "score": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "note": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/schema/reality/reality-summary.schema.json b/schema/reality/reality-summary.schema.json new file mode 100644 index 00000000..6997c253 --- /dev/null +++ b/schema/reality/reality-summary.schema.json @@ -0,0 +1,107 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/reality-summary/v1", + "title": "RealitySummary", + "description": "Top-level summary artifact for a reality run.", + "type": "object", + "required": [ + "spec_version", + "run_id", + "generated_at", + "scope", + "readiness_verdict", + "top_finding_claim_ids", + "artifacts" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "run_id": { + "type": "string", + "minLength": 1 + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "scope": { + "type": "object", + "required": [ + "repos" + ], + "properties": { + "repos": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "mode": { + "type": "string" + }, + "focus": { + "type": "string" + } + }, + "additionalProperties": false + }, + "readiness_verdict": { + "type": "string", + "enum": [ + "ready", + "ready_with_constraints", + "not_ready" + ] + }, + "top_finding_claim_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "review_strategy": { + "type": "object", + "required": [ + "primary", + "secondary", + "scope" + ], + "properties": { + "primary": { + "type": "string" + }, + "secondary": { + "type": "string" + }, + "scope": { + "type": "string" + } + }, + "additionalProperties": false + }, + "artifacts": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/schema/reality/repo-memory.schema.json b/schema/reality/repo-memory.schema.json new file mode 100644 index 00000000..fd86df7c --- /dev/null +++ b/schema/reality/repo-memory.schema.json @@ -0,0 +1,245 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/repo-memory/v1", + "title": "RealityRepoMemory", + "description": "Persistent normalized repository memory for reality-pro.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "repos", + "module_summaries", + "unresolved_questions" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "repos": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "repo_id", + "name", + "root_path" + ], + "properties": { + "repo_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "root_path": { + "type": "string", + "minLength": 1 + }, + "role": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "last_indexed_at": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + } + }, + "glossary_terms": { + "type": "array", + "items": { + "type": "object", + "required": [ + "term", + "definition" + ], + "properties": { + "term": { + "type": "string", + "minLength": 1 + }, + "definition": { + "type": "string", + "minLength": 1 + }, + "source_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "module_summaries": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "module_id", + "repo_id", + "summary" + ], + "properties": { + "module_id": { + "type": "string", + "minLength": 1 + }, + "repo_id": { + "type": "string", + "minLength": 1 + }, + "summary": { + "type": "string", + "minLength": 1 + }, + "paths": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "interfaces": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "risk_level": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ] + } + }, + "additionalProperties": false + } + }, + "feature_mappings": { + "type": "array", + "items": { + "type": "object", + "required": [ + "feature_id", + "title", + "confidence" + ], + "properties": { + "feature_id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "repo_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "component_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "additionalProperties": false + } + }, + "previous_validated_claim_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "unresolved_questions": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "hotspots": { + "type": "array", + "items": { + "type": "object", + "required": [ + "hotspot_id", + "repo_id", + "path", + "reason" + ], + "properties": { + "hotspot_id": { + "type": "string", + "minLength": 1 + }, + "repo_id": { + "type": "string", + "minLength": 1 + }, + "path": { + "type": "string", + "minLength": 1 + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "severity": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ] + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/schema/reality/source.schema.json b/schema/reality/source.schema.json new file mode 100644 index 00000000..6e05e883 --- /dev/null +++ b/schema/reality/source.schema.json @@ -0,0 +1,79 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/source/v1", + "title": "RealitySource", + "description": "Normalized source reference used across reality and reality-pro artifacts.", + "type": "object", + "required": [ + "source_id", + "kind", + "locator", + "revision" + ], + "properties": { + "source_id": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "enum": [ + "code", + "test", + "config", + "manifest", + "doc", + "issue", + "pull_request", + "commit", + "runtime_trace", + "external_contract" + ] + }, + "locator": { + "type": "string", + "minLength": 1 + }, + "revision": { + "type": "string", + "minLength": 1 + }, + "repo": { + "type": "string" + }, + "path": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "uri" + }, + "line_range": { + "type": "object", + "required": [ + "start", + "end" + ], + "properties": { + "start": { + "type": "integer", + "minimum": 1 + }, + "end": { + "type": "integer", + "minimum": 1 + } + }, + "additionalProperties": false + }, + "excerpt_hash": { + "type": "string", + "minLength": 1 + }, + "captured_at": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/cmd/sdp/help_integration_test.go b/sdp-plugin/cmd/sdp/help_integration_test.go index 4f817335..0ad62da1 100644 --- a/sdp-plugin/cmd/sdp/help_integration_test.go +++ b/sdp-plugin/cmd/sdp/help_integration_test.go @@ -116,6 +116,7 @@ func TestCommandsCoverage(t *testing.T) { "parse", "prd", "quality", + "reality", "skill", "status", "tdd", diff --git a/sdp-plugin/cmd/sdp/main.go b/sdp-plugin/cmd/sdp/main.go index 201320fa..110c0799 100644 --- a/sdp-plugin/cmd/sdp/main.go +++ b/sdp-plugin/cmd/sdp/main.go @@ -27,6 +27,7 @@ func main() { plan Decompose a feature into workstreams apply Execute ready workstreams from the terminal status Show current project state + reality Emit and validate OSS reality baseline artifacts next Recommend the next action to take log Inspect the evidence log demo Run a guided first-success walkthrough @@ -135,6 +136,7 @@ is provided by the Claude Plugin prompts in .claude/.`, rootCmd.AddCommand(tddCmd()) rootCmd.AddCommand(driftCmd()) rootCmd.AddCommand(qualityCmd()) + rootCmd.AddCommand(realityCmd()) rootCmd.AddCommand(watchCmd()) rootCmd.AddCommand(telemetryCmd) rootCmd.AddCommand(checkpointCmd) diff --git a/sdp-plugin/cmd/sdp/reality.go b/sdp-plugin/cmd/sdp/reality.go new file mode 100644 index 00000000..1abf3b24 --- /dev/null +++ b/sdp-plugin/cmd/sdp/reality.go @@ -0,0 +1,133 @@ +package main + +import ( + "fmt" + "path/filepath" + + "github.com/fall-out-bug/sdp/internal/config" + realityemitter "github.com/fall-out-bug/sdp/internal/reality" + "github.com/fall-out-bug/sdp/internal/ui" + "github.com/spf13/cobra" +) + +func realityCmd() *cobra.Command { + var root string + var quick bool + var deep bool + var bootstrapSDP bool + var focus string + + cmd := &cobra.Command{ + Use: "reality", + Short: "Emit and validate OSS reality baseline artifacts", + Long: `Emit and validate OSS reality baseline artifacts. + +OSS command set: + emit-oss Emit the required OSS artifact set into docs/reality and .sdp/reality + validate Validate emitted OSS artifacts against the published reality schema contract`, + } + + emitOSSCmd := &cobra.Command{ + Use: "emit-oss", + Short: "Emit the OSS reality artifact set", + RunE: func(cmd *cobra.Command, args []string) error { + modeCount := 0 + if quick { + modeCount++ + } + if deep { + modeCount++ + } + if bootstrapSDP { + modeCount++ + } + if modeCount > 1 { + return fmt.Errorf("choose only one of --quick, --deep, or --bootstrap-sdp") + } + + projectRoot := root + if projectRoot == "" { + detectedRoot, err := config.FindProjectRoot() + if err != nil { + return fmt.Errorf("find project root: %w", err) + } + projectRoot = detectedRoot + } + + absRoot, err := filepath.Abs(projectRoot) + if err != nil { + return fmt.Errorf("resolve project root: %w", err) + } + + opts := realityemitter.Options{ + Mode: realityemitter.ModeDeep, + Focus: focus, + } + switch { + case quick: + opts.Mode = realityemitter.ModeQuick + case bootstrapSDP: + opts.Mode = realityemitter.ModeBootstrapSDP + case deep: + opts.Mode = realityemitter.ModeDeep + } + + paths, err := realityemitter.EmitOSSWithOptions(absRoot, opts) + if err != nil { + return fmt.Errorf("emit reality OSS artifacts: %w", err) + } + + ui.SuccessLine("Generated %d reality OSS artifact(s) in %s [%s]", len(paths), absRoot, opts.Mode) + for _, path := range paths { + fmt.Printf(" - %s\n", path) + } + + return nil + }, + } + + validateCmd := &cobra.Command{ + Use: "validate", + Short: "Validate emitted OSS reality artifacts", + RunE: func(cmd *cobra.Command, args []string) error { + projectRoot := root + if projectRoot == "" { + detectedRoot, err := config.FindProjectRoot() + if err != nil { + return fmt.Errorf("find project root: %w", err) + } + projectRoot = detectedRoot + } + + absRoot, err := filepath.Abs(projectRoot) + if err != nil { + return fmt.Errorf("resolve project root: %w", err) + } + + validated, err := realityemitter.ValidateOSS(absRoot) + if err != nil { + for _, issue := range validated { + fmt.Fprintf(cmd.ErrOrStderr(), " - %s\n", issue) + } + return fmt.Errorf("validate reality OSS artifacts: %w", err) + } + + ui.SuccessLine("Validated %d reality OSS artifact(s) in %s", len(validated), absRoot) + for _, path := range validated { + fmt.Printf(" - %s\n", path) + } + return nil + }, + } + + emitOSSCmd.Flags().StringVar(&root, "root", "", "Project root path (default: auto-detect from current directory)") + emitOSSCmd.Flags().BoolVar(&quick, "quick", false, "Run the fast baseline mode with reduced detail") + emitOSSCmd.Flags().BoolVar(&deep, "deep", false, "Run the full single-repo baseline mode (default)") + emitOSSCmd.Flags().BoolVar(&bootstrapSDP, "bootstrap-sdp", false, "Prioritize SDP bootstrap recommendations in the emitted artifacts") + emitOSSCmd.Flags().StringVar(&focus, "focus", "", "Optional analysis focus: architecture, quality, testing, docs, security") + validateCmd.Flags().StringVar(&root, "root", "", "Project root path (default: auto-detect from current directory)") + cmd.AddCommand(emitOSSCmd) + cmd.AddCommand(validateCmd) + + return cmd +} diff --git a/sdp-plugin/cmd/sdp/reality_test.go b/sdp-plugin/cmd/sdp/reality_test.go new file mode 100644 index 00000000..ef19b310 --- /dev/null +++ b/sdp-plugin/cmd/sdp/reality_test.go @@ -0,0 +1,168 @@ +package main + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" +) + +func TestRealityCmd(t *testing.T) { + cmd := realityCmd() + + if cmd.Use != "reality" { + t.Fatalf("realityCmd() has wrong use: %s", cmd.Use) + } + + foundEmitOSS := false + foundValidate := false + for _, sub := range cmd.Commands() { + if sub.Name() == "emit-oss" { + foundEmitOSS = true + } + if sub.Name() == "validate" { + foundValidate = true + } + } + if !foundEmitOSS { + t.Fatal("realityCmd() missing emit-oss subcommand") + } + if !foundValidate { + t.Fatal("realityCmd() missing validate subcommand") + } +} + +func TestRealityEmitOSSCmd_WithRoot(t *testing.T) { + tmpDir := t.TempDir() + seedRealityProject(t, tmpDir) + + cmd := realityCmd() + cmd.SetArgs([]string{"emit-oss", "--root", tmpDir}) + + if err := cmd.Execute(); err != nil { + t.Fatalf("reality emit-oss failed: %v", err) + } + + expected := []string{ + ".sdp/reality/reality-summary.json", + ".sdp/reality/feature-inventory.json", + ".sdp/reality/architecture-map.json", + ".sdp/reality/integration-map.json", + ".sdp/reality/quality-report.json", + ".sdp/reality/drift-report.json", + ".sdp/reality/readiness-report.json", + "docs/reality/summary.md", + "docs/reality/architecture.md", + "docs/reality/quality.md", + "docs/reality/bootstrap.md", + } + for _, rel := range expected { + abs := filepath.Join(tmpDir, rel) + if _, err := os.Stat(abs); err != nil { + t.Fatalf("expected artifact missing: %s (%v)", rel, err) + } + } +} + +func TestRealityEmitOSSCmd_WithQuickFocus(t *testing.T) { + tmpDir := t.TempDir() + seedRealityProject(t, tmpDir) + writeRealityFile(t, filepath.Join(tmpDir, "configs", "app.yaml"), "database: postgres\ncache: redis\n") + + cmd := realityCmd() + cmd.SetArgs([]string{"emit-oss", "--root", tmpDir, "--quick", "--focus", "docs"}) + + if err := cmd.Execute(); err != nil { + t.Fatalf("reality emit-oss quick/docs failed: %v", err) + } + + summaryPath := filepath.Join(tmpDir, ".sdp", "reality", "reality-summary.json") + data, err := os.ReadFile(summaryPath) + if err != nil { + t.Fatalf("read summary: %v", err) + } + + var summary map[string]any + if err := json.Unmarshal(data, &summary); err != nil { + t.Fatalf("parse summary: %v", err) + } + + scope, ok := summary["scope"].(map[string]any) + if !ok { + t.Fatalf("summary scope missing or invalid: %#v", summary["scope"]) + } + if scope["mode"] != "quick" { + t.Fatalf("unexpected mode: %v", scope["mode"]) + } + if scope["focus"] != "docs" { + t.Fatalf("unexpected focus: %v", scope["focus"]) + } +} + +func TestRealityEmitOSSCmd_RejectsConflictingModes(t *testing.T) { + tmpDir := t.TempDir() + seedRealityProject(t, tmpDir) + + cmd := realityCmd() + cmd.SetArgs([]string{"emit-oss", "--root", tmpDir, "--quick", "--deep"}) + + if err := cmd.Execute(); err == nil { + t.Fatal("expected conflicting modes to fail") + } +} + +func TestRealityValidateCmd_WithRoot(t *testing.T) { + tmpDir := t.TempDir() + seedRealityProject(t, tmpDir) + + emit := realityCmd() + emit.SetArgs([]string{"emit-oss", "--root", tmpDir}) + if err := emit.Execute(); err != nil { + t.Fatalf("reality emit-oss failed: %v", err) + } + + validate := realityCmd() + validate.SetArgs([]string{"validate", "--root", tmpDir}) + if err := validate.Execute(); err != nil { + t.Fatalf("reality validate failed: %v", err) + } +} + +func TestRealityValidateCmd_FailsOnCorruptArtifact(t *testing.T) { + tmpDir := t.TempDir() + seedRealityProject(t, tmpDir) + + emit := realityCmd() + emit.SetArgs([]string{"emit-oss", "--root", tmpDir}) + if err := emit.Execute(); err != nil { + t.Fatalf("reality emit-oss failed: %v", err) + } + if err := os.WriteFile(filepath.Join(tmpDir, ".sdp", "reality", "quality-report.json"), []byte("{bad json"), 0o644); err != nil { + t.Fatalf("corrupt quality report: %v", err) + } + + validate := realityCmd() + validate.SetArgs([]string{"validate", "--root", tmpDir}) + if err := validate.Execute(); err == nil { + t.Fatal("expected reality validate to fail on corrupt artifact") + } +} + +func seedRealityProject(t *testing.T, root string) { + t.Helper() + + writeRealityFile(t, filepath.Join(root, "cmd", "demo", "main.go"), "package main\n\nfunc main() {}\n") + writeRealityFile(t, filepath.Join(root, "internal", "module", "service.go"), "package module\n\nfunc Enabled() bool { return true }\n") + writeRealityFile(t, filepath.Join(root, "internal", "module", "service_test.go"), "package module\n\nimport \"testing\"\n\nfunc TestEnabled(t *testing.T) {\n\tif !Enabled() {\n\t\tt.Fatal(\"expected true\")\n\t}\n}\n") + writeRealityFile(t, filepath.Join(root, "docs", "specs", "reality", "ARTIFACT-CONTRACT.md"), "# contract\nSee `internal/module/service.go`.\n") +} + +func writeRealityFile(t *testing.T, path, body string) { + t.Helper() + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatalf("mkdir %s: %v", filepath.Dir(path), err) + } + if err := os.WriteFile(path, []byte(body), 0o644); err != nil { + t.Fatalf("write %s: %v", path, err) + } +} diff --git a/sdp-plugin/internal/beads/client.go b/sdp-plugin/internal/beads/client.go index f895e992..c96ccd74 100644 --- a/sdp-plugin/internal/beads/client.go +++ b/sdp-plugin/internal/beads/client.go @@ -3,6 +3,7 @@ package beads import ( "bufio" "encoding/json" + "errors" "fmt" "os" "strings" @@ -51,6 +52,9 @@ func (c *Client) Ready() ([]Task, error) { output, err := c.runBeadsCommand("ready") if err != nil { + if errors.Is(err, ErrNoBeadsDatabase) { + return []Task{}, nil + } return []Task{}, fmt.Errorf("bd ready failed: %w", err) } diff --git a/sdp-plugin/internal/beads/client_with_beads_test.go b/sdp-plugin/internal/beads/client_with_beads_test.go index 3cd34ea1..cd9e9a8d 100644 --- a/sdp-plugin/internal/beads/client_with_beads_test.go +++ b/sdp-plugin/internal/beads/client_with_beads_test.go @@ -321,6 +321,39 @@ fi } } +func TestReadyWithoutDatabaseReturnsEmptySlice(t *testing.T) { + tmpDir := t.TempDir() + + bdScript := `#!/bin/bash +if [ "$1" = "ready" ]; then + echo "Error: no beads database found" >&2 + echo "Hint: run 'bd init' to create a database in the current directory" >&2 + exit 1 +fi +` + bdPath := filepath.Join(tmpDir, "bd") + if err := os.WriteFile(bdPath, []byte(bdScript), 0755); err != nil { + t.Fatalf("Failed to create fake bd: %v", err) + } + + oldPath := os.Getenv("PATH") + t.Cleanup(func() { os.Setenv("PATH", oldPath) }) + os.Setenv("PATH", tmpDir+string(os.PathListSeparator)+oldPath) + + client, err := NewClient() + if err != nil { + t.Fatalf("NewClient() failed: %v", err) + } + + tasks, err := client.Ready() + if err != nil { + t.Fatalf("Ready() should treat missing database as empty, got: %v", err) + } + if len(tasks) != 0 { + t.Fatalf("Expected 0 tasks, got %d", len(tasks)) + } +} + // TestNewClientWithFakeBeads tests NewClient detects fake beads func TestNewClientWithFakeBeads(t *testing.T) { // Create a temporary directory with a fake "bd" binary diff --git a/sdp-plugin/internal/beads/utils.go b/sdp-plugin/internal/beads/utils.go index b62f22d2..5c7e443f 100644 --- a/sdp-plugin/internal/beads/utils.go +++ b/sdp-plugin/internal/beads/utils.go @@ -1,16 +1,27 @@ package beads import ( + "errors" "fmt" "os" "os/exec" + "strings" ) +var ErrNoBeadsDatabase = errors.New("no beads database found") + // runBeadsCommand executes a Beads CLI command func (c *Client) runBeadsCommand(args ...string) (string, error) { cmd := exec.Command("bd", args...) - output, err := cmd.Output() + output, err := cmd.CombinedOutput() if err != nil { + trimmed := strings.TrimSpace(string(output)) + if strings.Contains(trimmed, "no beads database found") { + return "", fmt.Errorf("%w: %s", ErrNoBeadsDatabase, trimmed) + } + if trimmed != "" { + return "", fmt.Errorf("command failed: %s: %w", trimmed, err) + } return "", fmt.Errorf("command failed: %w", err) } diff --git a/sdp-plugin/internal/reality/emitter.go b/sdp-plugin/internal/reality/emitter.go new file mode 100644 index 00000000..438278f2 --- /dev/null +++ b/sdp-plugin/internal/reality/emitter.go @@ -0,0 +1,1387 @@ +package reality + +import ( + "bufio" + "encoding/json" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "regexp" + "sort" + "strings" +) + +const ( + specVersion = "v1.0" + fallbackGeneratedAtUTC = "1970-01-01T00:00:00Z" + hotspotLineThreshold = 800 +) + +type Mode string + +const ( + ModeQuick Mode = "quick" + ModeDeep Mode = "deep" + ModeBootstrapSDP Mode = "bootstrap_sdp" +) + +var validFocuses = map[string]bool{ + "": true, + "architecture": true, + "quality": true, + "testing": true, + "docs": true, + "security": true, +} + +type Options struct { + Mode Mode + Focus string +} + +type claim struct { + ClaimID string `json:"claim_id"` + Title string `json:"title"` + Statement string `json:"statement"` + Status string `json:"status"` + Confidence float64 `json:"confidence"` + SourceIDs []string `json:"source_ids"` + ReviewState string `json:"review_state"` + AffectedPaths []string `json:"affected_paths,omitempty"` + OpenQuestions []string `json:"open_questions,omitempty"` + Tags []string `json:"tags,omitempty"` + AffectedRepos []string `json:"affected_repos,omitempty"` + CounterEvidence []string `json:"counter_evidence,omitempty"` + AffectedElements []string `json:"affected_components,omitempty"` +} + +type source struct { + SourceID string `json:"source_id"` + Kind string `json:"kind"` + Locator string `json:"locator"` + Revision string `json:"revision"` + Repo string `json:"repo,omitempty"` + Path string `json:"path,omitempty"` +} + +type scanResult struct { + Root string + RepoName string + GeneratedAt string + RepoType string + TotalFiles int + SourceFiles int + TestsFiles int + DocFiles int + ConfigFiles int + ManifestFiles int + Modules []string + Entrypoints []string + HotspotFiles []hotspot + Integrations []integrationObservation + DocReferenceDrifts []docReferenceDrift +} + +type hotspot struct { + Path string + Lines int +} + +type integrationObservation struct { + Name string + Kind string + ContractType string + EvidencePaths []string + Confidence float64 +} + +type docReferenceDrift struct { + DocPath string + ReferencedPath string + ObservationNote string +} + +type scanAccumulator struct { + skipDirs map[string]bool + sourceExt map[string]bool + modules map[string]bool + entrypoints map[string]bool + hotspots []hotspot + integrations map[string]integrationObservation + drifts []docReferenceDrift +} + +// EmitOSS writes the OSS reality artifact set to docs/reality and .sdp/reality. +func EmitOSS(projectRoot string) ([]string, error) { + return EmitOSSWithOptions(projectRoot, Options{}) +} + +// EmitOSSWithOptions writes the OSS reality artifact set with an explicit analysis mode. +func EmitOSSWithOptions(projectRoot string, opts Options) ([]string, error) { + opts = normalizeOptions(opts) + if err := validateOptions(opts); err != nil { + return nil, err + } + + scan, err := scanProject(projectRoot) + if err != nil { + return nil, err + } + + verdict := readinessVerdict(scan) + dim := readinessDimensions(scan, opts) + constraints := readinessConstraints(scan) + claims := buildClaims(scan, verdict, opts) + sources := buildSources(scan) + claimIDs := claimIDList(claims) + topFindingIDs := topFindingClaimIDs(scan, claims) + integrations := integrationEntries(scan) + driftFindings := driftFindings(scan) + bootstrapRecommendations := bootstrapRecommendations(scan, verdict, opts) + + artifactPaths := []string{ + ".sdp/reality/reality-summary.json", + ".sdp/reality/feature-inventory.json", + ".sdp/reality/architecture-map.json", + ".sdp/reality/integration-map.json", + ".sdp/reality/quality-report.json", + ".sdp/reality/drift-report.json", + ".sdp/reality/readiness-report.json", + "docs/reality/summary.md", + "docs/reality/architecture.md", + "docs/reality/quality.md", + "docs/reality/bootstrap.md", + } + + realitySummary := map[string]any{ + "spec_version": specVersion, + "run_id": runID(scan.RepoName), + "generated_at": scan.GeneratedAt, + "scope": summaryScope(scan, opts), + "readiness_verdict": verdict, + "top_finding_claim_ids": topFindingIDs, + "review_strategy": map[string]any{ + "primary": "local source-first pass", + "secondary": "heuristic cross-check across tests, configs, manifests, and docs", + "scope": "single_repository", + }, + "artifacts": artifactPaths, + "claims": claims, + "sources": sources, + } + + featureInventory := map[string]any{ + "spec_version": specVersion, + "generated_at": scan.GeneratedAt, + "features": []map[string]any{ + { + "feature_id": "feature:repository-baseline", + "title": fmt.Sprintf("%s baseline", scan.RepoName), + "summary": featureSummary(scan, opts), + "status": featureStatus(scan), + "evidence_claim_ids": topFindingIDs, + "confidence": confidenceFromScan(scan), + "mapped_components": stringSliceOrEmpty(scan.Modules), + }, + }, + "claims": claims, + "sources": sources, + } + + archNodes := make([]map[string]any, 0, len(scan.Modules)) + for _, module := range scan.Modules { + archNodes = append(archNodes, map[string]any{ + "node_id": "module:" + module, + "name": module, + "kind": "module", + "boundary": "repository", + "repo": scan.RepoName, + "path": modulePath(module), + }) + } + for _, integration := range scan.Integrations { + nodeKind := "integration" + if integration.Kind == "data_store" { + nodeKind = "data_store" + } + archNodes = append(archNodes, map[string]any{ + "node_id": "integration:" + integration.Name, + "name": integration.Name, + "kind": nodeKind, + "boundary": "external", + "repo": scan.RepoName, + "path": strings.Join(integration.EvidencePaths, ", "), + }) + } + archEdges := inferredEdges(scan.Modules) + archEdges = append(archEdges, inferredIntegrationEdges(scan)...) + hotspots := make([]map[string]any, 0, len(scan.HotspotFiles)) + for _, h := range scan.HotspotFiles { + hotspots = append(hotspots, map[string]any{ + "node_id": "file:" + h.Path, + "reason": fmt.Sprintf("%d lines", h.Lines), + "severity": "medium", + }) + } + architectureMap := map[string]any{ + "spec_version": specVersion, + "generated_at": scan.GeneratedAt, + "nodes": archNodes, + "edges": archEdges, + "hotspots": hotspots, + "claims": claims, + "sources": sources, + } + + integrationMap := map[string]any{ + "spec_version": specVersion, + "generated_at": scan.GeneratedAt, + "integrations": integrations, + "claims": claims, + "sources": sources, + } + + qualityFindings := qualityFindings(scan, claimIDs, opts) + qualityReport := map[string]any{ + "spec_version": specVersion, + "generated_at": scan.GeneratedAt, + "analysis_mode": string(opts.Mode), + "analysis_focus": opts.Focus, + "findings": qualityFindings, + "hotspot_ranking": hotspotRanking(scan), + "claims": claims, + "sources": sources, + } + + driftReport := map[string]any{ + "spec_version": specVersion, + "generated_at": scan.GeneratedAt, + "contradictions": driftFindings, + "unresolved_questions": unresolvedQuestions(scan, opts), + "claims": claims, + "sources": sources, + } + + readinessReport := map[string]any{ + "spec_version": specVersion, + "generated_at": scan.GeneratedAt, + "verdict": verdict, + "dimensions": dim, + "justification_claim_ids": topFindingIDs, + "constraints": constraints, + "suggested_workstreams": bootstrapRecommendations, + "claims": claims, + "sources": sources, + } + + jsonOutputs := map[string]any{ + ".sdp/reality/reality-summary.json": realitySummary, + ".sdp/reality/feature-inventory.json": featureInventory, + ".sdp/reality/architecture-map.json": architectureMap, + ".sdp/reality/integration-map.json": integrationMap, + ".sdp/reality/quality-report.json": qualityReport, + ".sdp/reality/drift-report.json": driftReport, + ".sdp/reality/readiness-report.json": readinessReport, + } + + for rel, payload := range jsonOutputs { + if err := writeJSON(filepath.Join(projectRoot, rel), payload); err != nil { + return nil, err + } + } + + mdOutputs := map[string]string{ + "docs/reality/summary.md": renderSummaryMD(scan, verdict, topFindingIDs, opts), + "docs/reality/architecture.md": renderArchitectureMD(scan, opts), + "docs/reality/quality.md": renderQualityMD(scan, opts), + "docs/reality/bootstrap.md": renderBootstrapMD(scan, verdict, constraints, bootstrapRecommendations, opts), + } + for rel, body := range mdOutputs { + if err := writeText(filepath.Join(projectRoot, rel), body); err != nil { + return nil, err + } + } + + return artifactPaths, nil +} + +func scanProject(root string) (scanResult, error) { + scan := scanResult{ + Root: root, + RepoName: filepath.Base(root), + } + scan.GeneratedAt = deterministicGeneratedAt(root) + acc := newScanAccumulator() + + err := filepath.WalkDir(root, func(path string, d fs.DirEntry, walkErr error) error { + return walkProject(&scan, acc, root, path, d, walkErr) + }) + if err != nil { + return scan, err + } + + finalizeScan(&scan, acc) + scan.RepoType = classifyRepoType(scan) + + return scan, nil +} + +func newScanAccumulator() *scanAccumulator { + return &scanAccumulator{ + skipDirs: map[string]bool{ + ".git": true, + ".sdp": true, + ".beads": true, + "node_modules": true, + "vendor": true, + }, + sourceExt: map[string]bool{ + ".go": true, + ".py": true, + ".js": true, + ".ts": true, + ".java": true, + ".rs": true, + ".sh": true, + }, + modules: map[string]bool{}, + entrypoints: map[string]bool{}, + hotspots: make([]hotspot, 0), + integrations: map[string]integrationObservation{}, + drifts: make([]docReferenceDrift, 0), + } +} + +func walkProject(scan *scanResult, acc *scanAccumulator, root, path string, d fs.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + if path == root { + return nil + } + if d.IsDir() { + if acc.skipDirs[d.Name()] { + return filepath.SkipDir + } + return nil + } + info, err := d.Info() + if err != nil || !info.Mode().IsRegular() { + return nil + } + + rel, err := relativeScanPath(root, path) + if err != nil || shouldIgnoreScanPath(rel) { + return nil + } + scan.TotalFiles++ + + ext := strings.ToLower(filepath.Ext(rel)) + collectDocDrift(scan, acc, root, rel, path, ext) + collectConfigAndManifest(scan, rel) + collectSourceSignals(scan, acc, rel, path, ext) + collectIntegrations(acc, rel, path, ext) + return nil +} + +func relativeScanPath(root, path string) (string, error) { + rel, err := filepath.Rel(root, path) + if err != nil { + return "", err + } + return filepath.ToSlash(rel), nil +} + +func shouldIgnoreScanPath(rel string) bool { + return strings.HasPrefix(rel, "docs/reality/") +} + +func collectDocDrift(scan *scanResult, acc *scanAccumulator, root, rel, path, ext string) { + if !strings.HasPrefix(rel, "docs/") || ext != ".md" { + return + } + scan.DocFiles++ + acc.drifts = append(acc.drifts, scanDocReferenceDrift(root, rel, path)...) +} + +func collectConfigAndManifest(scan *scanResult, rel string) { + kind, ok := classifyConfigOrManifest(rel) + if !ok { + return + } + if kind == "config" { + scan.ConfigFiles++ + return + } + scan.ManifestFiles++ +} + +func collectSourceSignals(scan *scanResult, acc *scanAccumulator, rel, path, ext string) { + if !acc.sourceExt[ext] { + return + } + scan.SourceFiles++ + module := topModule(rel) + acc.modules[module] = true + if strings.HasSuffix(rel, "/main.go") || rel == "main.go" { + acc.entrypoints[rel] = true + } + if isTestFile(rel) { + scan.TestsFiles++ + } + lines := countLines(path) + if lines >= hotspotLineThreshold { + acc.hotspots = append(acc.hotspots, hotspot{Path: rel, Lines: lines}) + } +} + +func collectIntegrations(acc *scanAccumulator, rel, path, ext string) { + if !shouldScanForIntegrations(rel, ext) { + return + } + content := readFileForScan(path) + for _, detected := range detectIntegrations(rel, content) { + mergeIntegration(acc.integrations, detected) + } +} + +func finalizeScan(scan *scanResult, acc *scanAccumulator) { + for module := range acc.modules { + scan.Modules = append(scan.Modules, module) + } + sort.Strings(scan.Modules) + for entrypoint := range acc.entrypoints { + scan.Entrypoints = append(scan.Entrypoints, entrypoint) + } + sort.Strings(scan.Entrypoints) + sort.Slice(acc.hotspots, func(i, j int) bool { + if acc.hotspots[i].Lines == acc.hotspots[j].Lines { + return acc.hotspots[i].Path < acc.hotspots[j].Path + } + return acc.hotspots[i].Lines > acc.hotspots[j].Lines + }) + scan.HotspotFiles = acc.hotspots + scan.Integrations = flattenIntegrations(acc.integrations) + scan.DocReferenceDrifts = acc.drifts +} + +func deterministicGeneratedAt(root string) string { + cmd := exec.Command("git", "-C", root, "log", "-1", "--format=%cI") + out, err := cmd.Output() + if err != nil { + return fallbackGeneratedAtUTC + } + value := strings.TrimSpace(string(out)) + if value == "" { + return fallbackGeneratedAtUTC + } + return value +} + +func readinessVerdict(scan scanResult) string { + if scan.SourceFiles == 0 { + return "not_ready" + } + if scan.TestsFiles == 0 { + return "ready_with_constraints" + } + if len(scan.DocReferenceDrifts) > 0 { + return "ready_with_constraints" + } + return "ready" +} + +func readinessConstraints(scan scanResult) []string { + constraints := make([]string, 0) + if scan.SourceFiles == 0 { + constraints = append(constraints, "No source files detected for analysis.") + } + if scan.TestsFiles == 0 { + constraints = append(constraints, "No test files detected; verification surface is weak.") + } + if len(scan.DocReferenceDrifts) > 0 { + constraints = append(constraints, fmt.Sprintf("%d documented path references do not resolve in the repository.", len(scan.DocReferenceDrifts))) + } + if len(scan.Integrations) > 0 { + constraints = append(constraints, fmt.Sprintf("%d external integration or data-store surfaces need explicit boundary review.", len(scan.Integrations))) + } + if len(scan.HotspotFiles) > 0 { + constraints = append(constraints, "Large hotspot files detected; prefer narrow scoped changes.") + } + return constraints +} + +func readinessDimensions(scan scanResult, opts Options) map[string]any { + moduleCount := float64(len(scan.Modules)) + sourceCount := float64(max(scan.SourceFiles, 1)) + hotspotCount := float64(len(scan.HotspotFiles)) + integrationCount := float64(len(scan.Integrations)) + driftCount := float64(len(scan.DocReferenceDrifts)) + verificationScore := clamp(float64(scan.TestsFiles) / sourceCount) + docTrustScore := clamp(1 - (driftCount / float64(max(scan.DocFiles+1, 1)))) + integrationScore := clamp(1 - (integrationCount / float64(max(len(scan.Modules)+1, 1)))) + if opts.Focus == "testing" && scan.TestsFiles > 0 { + verificationScore = clamp(verificationScore + 0.1) + } + if opts.Focus == "docs" && len(scan.DocReferenceDrifts) == 0 && scan.DocFiles > 0 { + docTrustScore = clamp(docTrustScore + 0.1) + } + + return map[string]any{ + "boundary_clarity": map[string]any{ + "score": clamp(moduleCount / 8), + "note": fmt.Sprintf("%d top-level modules", len(scan.Modules)), + }, + "verification_coverage": map[string]any{ + "score": verificationScore, + "note": fmt.Sprintf("%d test files / %d source files", scan.TestsFiles, scan.SourceFiles), + }, + "hotspot_concentration": map[string]any{ + "score": clamp(1 - (hotspotCount / sourceCount)), + "note": fmt.Sprintf("%d hotspot files (>= %d lines)", len(scan.HotspotFiles), hotspotLineThreshold), + }, + "integration_fragility": map[string]any{ + "score": integrationScore, + "note": fmt.Sprintf("%d detected integration or data-store surfaces", len(scan.Integrations)), + }, + "documentation_trust_level": map[string]any{ + "score": docTrustScore, + "note": fmt.Sprintf("%d markdown docs under docs/, %d unresolved path references", scan.DocFiles, len(scan.DocReferenceDrifts)), + }, + } +} + +func buildClaims(scan scanResult, verdict string, opts Options) []claim { + claims := []claim{ + { + ClaimID: "claim:source-footprint", + Title: "Source footprint detected", + Statement: fmt.Sprintf("Detected %d source files across %d modules.", scan.SourceFiles, len(scan.Modules)), + Status: "observed", + Confidence: 0.95, + SourceIDs: []string{"source:local-tree"}, + ReviewState: "cross_checked", + Tags: []string{"baseline", "structure"}, + }, + { + ClaimID: "claim:test-posture", + Title: "Test posture baseline", + Statement: fmt.Sprintf("Detected %d test files.", scan.TestsFiles), + Status: "observed", + Confidence: 0.9, + SourceIDs: []string{"source:local-tree"}, + ReviewState: "cross_checked", + Tags: []string{"testing"}, + }, + { + ClaimID: "claim:readiness-verdict", + Title: "Readiness verdict", + Statement: fmt.Sprintf("Emitter baseline classifies repository as %s.", verdict), + Status: "inferred", + Confidence: 0.75, + SourceIDs: []string{"source:local-tree", "source:reality-contract"}, + ReviewState: "cross_checked", + Tags: []string{"readiness"}, + }, + } + if len(scan.Integrations) > 0 { + claims = append(claims, claim{ + ClaimID: "claim:integration-surface", + Title: "Integration surface recovered", + Statement: fmt.Sprintf("Detected %d integration or data-store surfaces from local code, config, manifest, and docs evidence.", len(scan.Integrations)), + Status: "observed", + Confidence: 0.8, + SourceIDs: []string{"source:local-tree", "source:config-inventory"}, + ReviewState: "cross_checked", + Tags: []string{"integration", "architecture"}, + }) + } + if len(scan.DocReferenceDrifts) > 0 { + claims = append(claims, claim{ + ClaimID: "claim:documentation-drift", + Title: "Documentation drift detected", + Statement: fmt.Sprintf("Detected %d unresolved documentation path references during local cross-check.", len(scan.DocReferenceDrifts)), + Status: "conflicted", + Confidence: 0.78, + SourceIDs: []string{"source:docs-inventory", "source:local-tree"}, + ReviewState: "challenged", + Tags: []string{"docs", "drift"}, + }) + } + if opts.Focus != "" { + claims = append(claims, claim{ + ClaimID: "claim:analysis-focus", + Title: "Focused analysis requested", + Statement: fmt.Sprintf("OSS reality emitter applied additional reporting emphasis for %s.", opts.Focus), + Status: "observed", + Confidence: 0.9, + SourceIDs: []string{"source:local-tree"}, + ReviewState: "cross_checked", + Tags: []string{"focus", opts.Focus}, + }) + } + return claims +} + +func buildSources(scan scanResult) []source { + sources := []source{ + { + SourceID: "source:local-tree", + Kind: "code", + Locator: scan.Root, + Revision: scan.GeneratedAt, + Repo: scan.RepoName, + }, + { + SourceID: "source:reality-contract", + Kind: "doc", + Locator: "docs/specs/reality/ARTIFACT-CONTRACT.md", + Revision: scan.GeneratedAt, + Repo: scan.RepoName, + Path: "docs/specs/reality/ARTIFACT-CONTRACT.md", + }, + } + if scan.DocFiles > 0 { + sources = append(sources, source{ + SourceID: "source:docs-inventory", + Kind: "doc", + Locator: "docs/", + Revision: scan.GeneratedAt, + Repo: scan.RepoName, + Path: "docs/", + }) + } + if scan.ConfigFiles > 0 || scan.ManifestFiles > 0 { + sources = append(sources, source{ + SourceID: "source:config-inventory", + Kind: "config", + Locator: "config+manifest inventory", + Revision: scan.GeneratedAt, + Repo: scan.RepoName, + }) + } + return sources +} + +func topFindingClaimIDs(scan scanResult, claims []claim) []string { + if scan.TestsFiles == 0 { + return []string{"claim:test-posture", "claim:readiness-verdict"} + } + if len(scan.DocReferenceDrifts) > 0 { + return []string{"claim:documentation-drift", "claim:readiness-verdict"} + } + if len(claims) == 0 { + return []string{} + } + return []string{claims[0].ClaimID, "claim:readiness-verdict"} +} + +func claimIDList(claims []claim) []string { + ids := make([]string, 0, len(claims)) + for _, c := range claims { + ids = append(ids, c.ClaimID) + } + return ids +} + +func qualityFindings(scan scanResult, claimIDs []string, opts Options) []map[string]any { + findings := make([]map[string]any, 0) + if scan.TestsFiles == 0 { + findings = append(findings, map[string]any{ + "finding_id": "finding:testing:missing", + "title": "Verification surface is thin", + "severity": "high", + "description": "No test files were detected in the repository baseline.", + "claim_ids": []string{"claim:test-posture", "claim:readiness-verdict"}, + }) + } + if len(scan.DocReferenceDrifts) > 0 { + finding := map[string]any{ + "finding_id": "finding:docs:drift", + "title": "Documented paths drift from repository reality", + "severity": "medium", + "description": fmt.Sprintf("%d documented path references do not resolve locally.", len(scan.DocReferenceDrifts)), + "claim_ids": []string{"claim:documentation-drift"}, + } + findings = append(findings, finding) + } + if len(scan.Integrations) > 0 { + findings = append(findings, map[string]any{ + "finding_id": "finding:integration:review", + "title": "External boundaries need explicit review", + "severity": "medium", + "description": fmt.Sprintf("%d integration or data-store surfaces were inferred from local evidence.", len(scan.Integrations)), + "claim_ids": []string{"claim:integration-surface", "claim:readiness-verdict"}, + }) + } + if len(scan.HotspotFiles) == 0 { + findings = append(findings, map[string]any{ + "finding_id": "finding:hotspot:none", + "title": "No large source hotspots detected", + "severity": "low", + "description": "No source files exceeded hotspot threshold.", + "claim_ids": []string{"claim:source-footprint"}, + }) + } else { + limit := len(scan.HotspotFiles) + if opts.Mode == ModeQuick && limit > 3 { + limit = 3 + } + for i, h := range scan.HotspotFiles[:limit] { + findings = append(findings, map[string]any{ + "finding_id": fmt.Sprintf("finding:hotspot:%d", i+1), + "title": fmt.Sprintf("Large file hotspot: %s", h.Path), + "severity": "medium", + "description": fmt.Sprintf("%s has %d lines.", h.Path, h.Lines), + "claim_ids": claimIDs, + }) + } + } + if opts.Focus == "architecture" && len(scan.Modules) > 0 { + findings = append(findings, map[string]any{ + "finding_id": "finding:architecture:modules", + "title": "Architecture focus enabled", + "severity": "low", + "description": fmt.Sprintf("Analysis emphasized %d top-level modules and %d entrypoints.", len(scan.Modules), len(scan.Entrypoints)), + "claim_ids": []string{"claim:source-footprint"}, + }) + } + if opts.Focus == "docs" && len(scan.DocReferenceDrifts) == 0 { + findings = append(findings, map[string]any{ + "finding_id": "finding:docs:stable", + "title": "No obvious documentation path drift detected", + "severity": "low", + "description": "Local docs path references resolved during the OSS cross-check pass.", + "claim_ids": []string{"claim:source-footprint"}, + }) + } + return findings +} + +func hotspotRanking(scan scanResult) []map[string]any { + result := make([]map[string]any, 0, len(scan.HotspotFiles)) + for _, h := range scan.HotspotFiles { + result = append(result, map[string]any{ + "path": h.Path, + "score": float64(h.Lines), + "reason": fmt.Sprintf("line_count=%d", h.Lines), + }) + } + return result +} + +func unresolvedQuestions(scan scanResult, opts Options) []string { + questions := []string{ + "Which integrations need contract-level documentation or dedicated boundary tests next?", + } + if scan.TestsFiles == 0 { + questions = append(questions, "Where should baseline verification tests be added first?") + } + if len(scan.DocReferenceDrifts) > 0 { + questions = append(questions, "Which documentation references should be corrected or deleted to remove stale paths?") + } + if opts.Mode == ModeBootstrapSDP { + questions = append(questions, "Which narrow first workstream creates the safest agent-executable scope?") + } + return questions +} + +func renderSummaryMD(scan scanResult, verdict string, topFindingIDs []string, opts Options) string { + var b strings.Builder + b.WriteString("# Reality Summary\n\n") + b.WriteString(fmt.Sprintf("- Repository: `%s`\n", scan.RepoName)) + b.WriteString(fmt.Sprintf("- Repo Type: `%s`\n", scan.RepoType)) + b.WriteString(fmt.Sprintf("- Generated At: `%s`\n", scan.GeneratedAt)) + b.WriteString(fmt.Sprintf("- Analysis Mode: `%s`\n", opts.Mode)) + if opts.Focus != "" { + b.WriteString(fmt.Sprintf("- Analysis Focus: `%s`\n", opts.Focus)) + } + b.WriteString(fmt.Sprintf("- Readiness Verdict: `%s`\n", verdict)) + b.WriteString(fmt.Sprintf("- Source Files: `%d`\n", scan.SourceFiles)) + b.WriteString(fmt.Sprintf("- Test Files: `%d`\n", scan.TestsFiles)) + b.WriteString(fmt.Sprintf("- Config + Manifest Files: `%d`\n", scan.ConfigFiles+scan.ManifestFiles)) + b.WriteString(fmt.Sprintf("- Modules: `%d`\n", len(scan.Modules))) + b.WriteString(fmt.Sprintf("- Integrations: `%d`\n", len(scan.Integrations))) + b.WriteString(fmt.Sprintf("- Doc Drift References: `%d`\n", len(scan.DocReferenceDrifts))) + b.WriteString("\n## Top Finding Claims\n\n") + for _, id := range topFindingIDs { + b.WriteString(fmt.Sprintf("- `%s`\n", id)) + } + return b.String() +} + +func renderArchitectureMD(scan scanResult, opts Options) string { + var b strings.Builder + b.WriteString("# Reality Architecture\n\n") + b.WriteString(fmt.Sprintf("- Analysis Mode: `%s`\n", opts.Mode)) + if opts.Focus != "" { + b.WriteString(fmt.Sprintf("- Analysis Focus: `%s`\n", opts.Focus)) + } + b.WriteString("## Modules\n\n") + if len(scan.Modules) == 0 { + b.WriteString("- none\n") + } else { + for _, m := range scan.Modules { + b.WriteString(fmt.Sprintf("- `%s`\n", m)) + } + } + b.WriteString("\n## Entrypoints\n\n") + if len(scan.Entrypoints) == 0 { + b.WriteString("- none detected\n") + } else { + for _, ep := range scan.Entrypoints { + b.WriteString(fmt.Sprintf("- `%s`\n", ep)) + } + } + b.WriteString("\n## Integrations\n\n") + if len(scan.Integrations) == 0 { + b.WriteString("- none detected\n") + } else { + for _, integration := range scan.Integrations { + b.WriteString(fmt.Sprintf("- `%s` (%s via %s)\n", integration.Name, integration.Kind, strings.Join(integration.EvidencePaths, ", "))) + } + } + return b.String() +} + +func renderQualityMD(scan scanResult, opts Options) string { + var b strings.Builder + b.WriteString("# Reality Quality\n\n") + b.WriteString(fmt.Sprintf("- Analysis Mode: `%s`\n", opts.Mode)) + if opts.Focus != "" { + b.WriteString(fmt.Sprintf("- Analysis Focus: `%s`\n", opts.Focus)) + } + b.WriteString(fmt.Sprintf("- Source Files: `%d`\n", scan.SourceFiles)) + b.WriteString(fmt.Sprintf("- Test Files: `%d`\n", scan.TestsFiles)) + b.WriteString(fmt.Sprintf("- Docs Files: `%d`\n", scan.DocFiles)) + b.WriteString(fmt.Sprintf("- Config Files: `%d`\n", scan.ConfigFiles)) + b.WriteString(fmt.Sprintf("- Manifest Files: `%d`\n", scan.ManifestFiles)) + b.WriteString("\n## Hotspots\n\n") + if len(scan.HotspotFiles) == 0 { + b.WriteString("- none\n") + } else { + for _, h := range scan.HotspotFiles { + b.WriteString(fmt.Sprintf("- `%s` (%d lines)\n", h.Path, h.Lines)) + } + } + b.WriteString("\n## Documentation Drift\n\n") + if len(scan.DocReferenceDrifts) == 0 { + b.WriteString("- none detected\n") + } else { + for _, drift := range scan.DocReferenceDrifts { + b.WriteString(fmt.Sprintf("- `%s` references missing `%s`\n", drift.DocPath, drift.ReferencedPath)) + } + } + return b.String() +} + +func renderBootstrapMD(scan scanResult, verdict string, constraints []string, recommendations []string, opts Options) string { + var b strings.Builder + b.WriteString("# Reality Bootstrap\n\n") + b.WriteString(fmt.Sprintf("- Current Verdict: `%s`\n", verdict)) + b.WriteString(fmt.Sprintf("- Analysis Mode: `%s`\n", opts.Mode)) + if opts.Focus != "" { + b.WriteString(fmt.Sprintf("- Analysis Focus: `%s`\n", opts.Focus)) + } + b.WriteString("\n## Constraints\n\n") + if len(constraints) == 0 { + b.WriteString("- none\n") + } else { + for _, c := range constraints { + b.WriteString(fmt.Sprintf("- %s\n", c)) + } + } + b.WriteString("\n## Suggested First Workstreams\n\n") + if len(recommendations) == 0 { + b.WriteString("- No immediate bootstrap workstreams inferred.\n") + } else { + for _, recommendation := range recommendations { + b.WriteString(fmt.Sprintf("- %s\n", recommendation)) + } + } + if opts.Mode == ModeBootstrapSDP { + b.WriteString("\n## Agent Readiness Notes\n\n") + b.WriteString(fmt.Sprintf("- Safe modules for first slices: `%s`\n", strings.Join(preferredBootstrapModules(scan), "`, `"))) + } + return b.String() +} + +func writeJSON(path string, payload any) error { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return err + } + data, err := json.MarshalIndent(payload, "", " ") + if err != nil { + return err + } + data = append(data, '\n') + return os.WriteFile(path, data, 0o644) +} + +func writeText(path, body string) error { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return err + } + body = strings.TrimRight(body, "\n") + "\n" + return os.WriteFile(path, []byte(body), 0o644) +} + +func featureStatus(scan scanResult) string { + if scan.SourceFiles == 0 { + return "candidate" + } + if scan.TestsFiles == 0 || len(scan.DocReferenceDrifts) > 0 { + return "partial" + } + return "implemented" +} + +func confidenceFromScan(scan scanResult) float64 { + if scan.SourceFiles == 0 { + return 0.4 + } + if scan.TestsFiles == 0 || len(scan.DocReferenceDrifts) > 0 { + return 0.7 + } + return 0.9 +} + +func inferredEdges(modules []string) []map[string]any { + moduleSet := map[string]bool{} + for _, m := range modules { + moduleSet[m] = true + } + edges := make([]map[string]any, 0) + if moduleSet["cmd"] && moduleSet["internal"] { + edges = append(edges, map[string]any{ + "from": "module:cmd", + "to": "module:internal", + "relation": "depends_on", + "confidence": 0.6, + }) + } + return edges +} + +func inferredIntegrationEdges(scan scanResult) []map[string]any { + edges := make([]map[string]any, 0, len(scan.Integrations)) + for _, integration := range scan.Integrations { + module := "root" + if len(integration.EvidencePaths) > 0 { + module = topModule(integration.EvidencePaths[0]) + } + edges = append(edges, map[string]any{ + "from": "module:" + module, + "to": "integration:" + integration.Name, + "relation": "depends_on", + "confidence": integration.Confidence, + }) + } + return edges +} + +func runID(repoName string) string { + return fmt.Sprintf("reality-oss-%s", repoName) +} + +func modulePath(module string) string { + if module == "root" { + return "." + } + return module +} + +func stringSliceOrEmpty(values []string) []string { + if values == nil { + return []string{} + } + return values +} + +func topModule(rel string) string { + parts := strings.Split(rel, "/") + if len(parts) <= 1 { + return "root" + } + return parts[0] +} + +func isTestFile(rel string) bool { + return strings.HasSuffix(rel, "_test.go") || + strings.HasSuffix(rel, ".test.js") || + strings.HasSuffix(rel, ".test.ts") || + strings.HasSuffix(rel, "_spec.py") +} + +func countLines(path string) int { + f, err := os.Open(path) + if err != nil { + return 0 + } + defer f.Close() + scanner := bufio.NewScanner(f) + lines := 0 + for scanner.Scan() { + lines++ + } + return lines +} + +func clamp(v float64) float64 { + if v < 0 { + return 0 + } + if v > 1 { + return 1 + } + return v +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func normalizeOptions(opts Options) Options { + if opts.Mode == "" { + opts.Mode = ModeDeep + } + opts.Focus = strings.TrimSpace(strings.ToLower(opts.Focus)) + return opts +} + +func validateOptions(opts Options) error { + switch opts.Mode { + case ModeQuick, ModeDeep, ModeBootstrapSDP: + default: + return fmt.Errorf("unsupported reality mode %q", opts.Mode) + } + if !validFocuses[opts.Focus] { + return fmt.Errorf("unsupported focus %q", opts.Focus) + } + return nil +} + +func summaryScope(scan scanResult, opts Options) map[string]any { + scope := map[string]any{ + "repos": []string{scan.RepoName}, + "mode": string(opts.Mode), + } + if opts.Focus != "" { + scope["focus"] = opts.Focus + } + return scope +} + +func featureSummary(scan scanResult, opts Options) string { + summary := "Repository baseline reconstructed from local source tree, tests, configs, manifests, and docs." + if opts.Mode == ModeQuick { + return summary + " Quick mode keeps the same artifact families with shallower evidence detail." + } + if opts.Mode == ModeBootstrapSDP { + return summary + " Bootstrap mode prioritizes first SDP-safe workstream recommendations." + } + return summary +} + +func classifyConfigOrManifest(rel string) (string, bool) { + lower := strings.ToLower(rel) + base := strings.ToLower(filepath.Base(rel)) + ext := strings.ToLower(filepath.Ext(rel)) + if base == "dockerfile" || strings.Contains(lower, "docker-compose") || strings.Contains(lower, "compose.yaml") { + return "manifest", true + } + if strings.Contains(lower, "deploy/") || strings.Contains(lower, "k8s/") || strings.Contains(lower, "helm/") { + return "manifest", true + } + switch ext { + case ".yaml", ".yml": + if strings.Contains(lower, "deploy") || strings.Contains(lower, "k8s") || strings.Contains(lower, "helm") { + return "manifest", true + } + return "config", true + case ".json", ".toml", ".ini", ".env": + return "config", true + } + if strings.HasSuffix(base, ".conf") { + return "config", true + } + return "", false +} + +func shouldScanForIntegrations(rel, ext string) bool { + if strings.HasPrefix(rel, "docs/reality/") { + return false + } + if _, ok := classifyConfigOrManifest(rel); ok { + return true + } + if ext == ".md" && strings.HasPrefix(rel, "docs/") { + return true + } + switch ext { + case ".go", ".py", ".js", ".ts", ".java", ".rs", ".sh": + return true + default: + return false + } +} + +func readFileForScan(path string) string { + info, err := os.Stat(path) + if err != nil || info.Size() > 1<<20 { + return "" + } + data, err := os.ReadFile(path) + if err != nil { + return "" + } + return strings.ToLower(string(data)) +} + +func detectIntegrations(rel, content string) []integrationObservation { + type pattern struct { + Name string + Kind string + ContractType string + Keywords []string + } + + patterns := []pattern{ + {Name: "postgres", Kind: "data_store", ContractType: "sql", Keywords: []string{"postgres", "postgresql", "pgx"}}, + {Name: "mysql", Kind: "data_store", ContractType: "sql", Keywords: []string{"mysql"}}, + {Name: "sqlite", Kind: "data_store", ContractType: "sql", Keywords: []string{"sqlite"}}, + {Name: "redis", Kind: "data_store", ContractType: "cache", Keywords: []string{"redis"}}, + {Name: "kafka", Kind: "integration", ContractType: "event_stream", Keywords: []string{"kafka"}}, + {Name: "nats", Kind: "integration", ContractType: "event_stream", Keywords: []string{"nats"}}, + {Name: "rabbitmq", Kind: "integration", ContractType: "message_queue", Keywords: []string{"rabbitmq"}}, + {Name: "grpc", Kind: "integration", ContractType: "rpc", Keywords: []string{"grpc"}}, + {Name: "http", Kind: "integration", ContractType: "http", Keywords: []string{"http://", "https://", "httpclient", "resty"}}, + {Name: "s3", Kind: "integration", ContractType: "object_store", Keywords: []string{"s3", "minio"}}, + {Name: "github", Kind: "integration", ContractType: "api", Keywords: []string{"github", "gh "}}, + {Name: "slack", Kind: "integration", ContractType: "api", Keywords: []string{"slack"}}, + {Name: "stripe", Kind: "integration", ContractType: "api", Keywords: []string{"stripe"}}, + } + + haystack := strings.ToLower(rel) + "\n" + content + detected := make([]integrationObservation, 0) + for _, pattern := range patterns { + for _, keyword := range pattern.Keywords { + if strings.Contains(haystack, keyword) { + confidence := 0.65 + if kind, ok := classifyConfigOrManifest(rel); ok { + if kind == "manifest" { + confidence = 0.85 + } else { + confidence = 0.8 + } + } + if strings.HasSuffix(rel, ".md") { + confidence = 0.55 + } + detected = append(detected, integrationObservation{ + Name: pattern.Name, + Kind: pattern.Kind, + ContractType: pattern.ContractType, + EvidencePaths: []string{rel}, + Confidence: confidence, + }) + break + } + } + } + return detected +} + +func mergeIntegration(target map[string]integrationObservation, detected integrationObservation) { + current, ok := target[detected.Name] + if !ok { + target[detected.Name] = detected + return + } + current.EvidencePaths = append(current.EvidencePaths, detected.EvidencePaths...) + current.EvidencePaths = dedupeStrings(current.EvidencePaths) + if detected.Confidence > current.Confidence { + current.Confidence = detected.Confidence + current.Kind = detected.Kind + current.ContractType = detected.ContractType + } + target[detected.Name] = current +} + +func flattenIntegrations(m map[string]integrationObservation) []integrationObservation { + items := make([]integrationObservation, 0, len(m)) + for _, item := range m { + sort.Strings(item.EvidencePaths) + items = append(items, item) + } + sort.Slice(items, func(i, j int) bool { + return items[i].Name < items[j].Name + }) + return items +} + +func integrationEntries(scan scanResult) []map[string]any { + entries := make([]map[string]any, 0, len(scan.Integrations)) + for _, integration := range scan.Integrations { + entries = append(entries, map[string]any{ + "integration_id": fmt.Sprintf("integration:%s", integration.Name), + "name": integration.Name, + "integration_type": integration.Kind, + "producer": "repo:" + scan.RepoName, + "consumer": "external:" + integration.Name, + "contract_type": integration.ContractType, + "confidence": integration.Confidence, + "evidence_paths": integration.EvidencePaths, + "risk_notes": []string{ + "Confirm runtime ownership and failure behavior for this boundary.", + }, + }) + } + return entries +} + +func scanDocReferenceDrift(root, rel, absPath string) []docReferenceDrift { + content := readFileForScan(absPath) + if content == "" { + return nil + } + pathPattern := regexp.MustCompile("`([A-Za-z0-9_./-]+)`") + matches := pathPattern.FindAllStringSubmatch(content, -1) + drifts := make([]docReferenceDrift, 0) + for _, match := range matches { + referenced := normalizeDocPathReference(match[1]) + if !looksLikeTrackedPath(referenced) { + continue + } + if _, err := os.Stat(filepath.Join(root, referenced)); err == nil { + continue + } + drifts = append(drifts, docReferenceDrift{ + DocPath: rel, + ReferencedPath: referenced, + ObservationNote: "Documented path does not resolve in the current repository tree.", + }) + } + return drifts +} + +func normalizeDocPathReference(ref string) string { + ref = strings.TrimSpace(ref) + ref = strings.TrimPrefix(ref, "./") + ref = strings.TrimSuffix(ref, "/") + return filepath.ToSlash(ref) +} + +func looksLikeTrackedPath(ref string) bool { + if ref == "" { + return false + } + prefixes := []string{"cmd/", "internal/", "pkg/", "api/", "deploy/", "configs/", "schema/", "scripts/"} + for _, prefix := range prefixes { + if strings.HasPrefix(ref, prefix) { + return true + } + } + suffixes := []string{".go", ".py", ".js", ".ts", ".java", ".rs", ".sh", ".md", ".yaml", ".yml", ".json", ".toml"} + for _, suffix := range suffixes { + if strings.HasSuffix(ref, suffix) { + return true + } + } + return false +} + +func driftFindings(scan scanResult) []map[string]any { + findings := make([]map[string]any, 0, len(scan.DocReferenceDrifts)) + for i, drift := range scan.DocReferenceDrifts { + findings = append(findings, map[string]any{ + "contradiction_id": fmt.Sprintf("drift:%d", i+1), + "title": fmt.Sprintf("Missing documented path: %s", drift.ReferencedPath), + "severity": "medium", + "doc_path": drift.DocPath, + "referenced_path": drift.ReferencedPath, + "note": drift.ObservationNote, + }) + } + return findings +} + +func bootstrapRecommendations(scan scanResult, verdict string, opts Options) []string { + recommendations := make([]string, 0) + if scan.TestsFiles == 0 { + recommendations = append(recommendations, "Add a first verification slice around a narrow entrypoint or core module.") + } + if len(scan.DocReferenceDrifts) > 0 { + recommendations = append(recommendations, "Reconcile stale documentation paths before delegating agent work from docs.") + } + if len(scan.Integrations) > 0 { + recommendations = append(recommendations, "Fence external integration boundaries with explicit contracts and failure notes.") + } + if len(scan.HotspotFiles) > 0 { + recommendations = append(recommendations, "Split hotspot files into smaller scopes before broad autonomous changes.") + } + if verdict == "ready" { + recommendations = append(recommendations, "Start with a small SDP workstream in the safest tested module.") + } + if opts.Mode == ModeBootstrapSDP && len(recommendations) == 0 { + recommendations = append(recommendations, "Seed the first SDP workstream from the lowest-coupling module and keep scope single-boundary.") + } + return dedupeStrings(recommendations) +} + +func preferredBootstrapModules(scan scanResult) []string { + if len(scan.Modules) == 0 { + return []string{"root"} + } + limit := len(scan.Modules) + if limit > 3 { + limit = 3 + } + return scan.Modules[:limit] +} + +func classifyRepoType(scan scanResult) string { + hasCmd := false + hasSchema := false + hasDeploy := false + for _, module := range scan.Modules { + switch module { + case "cmd": + hasCmd = true + case "schema": + hasSchema = true + case "deploy": + hasDeploy = true + } + } + switch { + case hasCmd && hasDeploy: + return "app" + case hasSchema && !hasCmd: + return "protocol" + case hasDeploy && !hasCmd: + return "infra" + case hasCmd: + return "service" + default: + return "mixed" + } +} + +func dedupeStrings(values []string) []string { + seen := map[string]bool{} + result := make([]string, 0, len(values)) + for _, value := range values { + if value == "" || seen[value] { + continue + } + seen[value] = true + result = append(result, value) + } + return result +} diff --git a/sdp-plugin/internal/reality/emitter_test.go b/sdp-plugin/internal/reality/emitter_test.go new file mode 100644 index 00000000..5229fa71 --- /dev/null +++ b/sdp-plugin/internal/reality/emitter_test.go @@ -0,0 +1,177 @@ +package reality + +import ( + "encoding/json" + "os" + "path/filepath" + "slices" + "testing" +) + +var requiredOSSArtifacts = []string{ + ".sdp/reality/reality-summary.json", + ".sdp/reality/feature-inventory.json", + ".sdp/reality/architecture-map.json", + ".sdp/reality/integration-map.json", + ".sdp/reality/quality-report.json", + ".sdp/reality/drift-report.json", + ".sdp/reality/readiness-report.json", + "docs/reality/summary.md", + "docs/reality/architecture.md", + "docs/reality/quality.md", + "docs/reality/bootstrap.md", +} + +func TestEmitOSS_WritesRequiredArtifactsAndIsDeterministic(t *testing.T) { + projectRoot := t.TempDir() + seedProject(t, projectRoot) + + paths, err := EmitOSS(projectRoot) + if err != nil { + t.Fatalf("EmitOSS first run failed: %v", err) + } + if !slices.Equal(paths, requiredOSSArtifacts) { + t.Fatalf("EmitOSS returned unexpected artifact list: %v", paths) + } + + firstRun := make(map[string][]byte, len(requiredOSSArtifacts)) + for _, rel := range requiredOSSArtifacts { + abs := filepath.Join(projectRoot, rel) + data, err := os.ReadFile(abs) + if err != nil { + t.Fatalf("expected artifact missing: %s (%v)", rel, err) + } + if len(data) == 0 { + t.Fatalf("artifact is empty: %s", rel) + } + firstRun[rel] = data + } + + readinessPath := filepath.Join(projectRoot, ".sdp/reality/readiness-report.json") + readinessData, err := os.ReadFile(readinessPath) + if err != nil { + t.Fatalf("read readiness report: %v", err) + } + var readiness map[string]any + if err := json.Unmarshal(readinessData, &readiness); err != nil { + t.Fatalf("parse readiness report: %v", err) + } + if readiness["verdict"] != "ready" { + t.Fatalf("unexpected readiness verdict: %v", readiness["verdict"]) + } + + if _, err := EmitOSS(projectRoot); err != nil { + t.Fatalf("EmitOSS second run failed: %v", err) + } + for _, rel := range requiredOSSArtifacts { + abs := filepath.Join(projectRoot, rel) + data, err := os.ReadFile(abs) + if err != nil { + t.Fatalf("read artifact after second run (%s): %v", rel, err) + } + if string(firstRun[rel]) != string(data) { + t.Fatalf("artifact changed between deterministic runs: %s", rel) + } + } +} + +func TestEmitOSSWithOptions_AnnotatesModeFocusAndIntegrations(t *testing.T) { + projectRoot := t.TempDir() + seedProject(t, projectRoot) + writeFile(t, filepath.Join(projectRoot, "configs", "app.yaml"), "database: postgres\ncache: redis\n") + + if _, err := EmitOSSWithOptions(projectRoot, Options{Mode: ModeQuick, Focus: "docs"}); err != nil { + t.Fatalf("EmitOSSWithOptions failed: %v", err) + } + + summaryData, err := os.ReadFile(filepath.Join(projectRoot, ".sdp", "reality", "reality-summary.json")) + if err != nil { + t.Fatalf("read summary: %v", err) + } + var summary map[string]any + if err := json.Unmarshal(summaryData, &summary); err != nil { + t.Fatalf("parse summary: %v", err) + } + scope, ok := summary["scope"].(map[string]any) + if !ok { + t.Fatalf("scope missing or invalid: %#v", summary["scope"]) + } + if scope["mode"] != "quick" { + t.Fatalf("unexpected mode: %v", scope["mode"]) + } + if scope["focus"] != "docs" { + t.Fatalf("unexpected focus: %v", scope["focus"]) + } + + integrationData, err := os.ReadFile(filepath.Join(projectRoot, ".sdp", "reality", "integration-map.json")) + if err != nil { + t.Fatalf("read integration map: %v", err) + } + var integrationMap map[string]any + if err := json.Unmarshal(integrationData, &integrationMap); err != nil { + t.Fatalf("parse integration map: %v", err) + } + integrations, ok := integrationMap["integrations"].([]any) + if !ok || len(integrations) == 0 { + t.Fatalf("expected integrations to be detected: %#v", integrationMap["integrations"]) + } +} + +func TestEmitOSSWithOptions_DetectsDocumentationDriftAndBootstrapRecommendations(t *testing.T) { + projectRoot := t.TempDir() + seedProject(t, projectRoot) + writeFile(t, filepath.Join(projectRoot, "docs", "architecture.md"), "# Architecture\nUses `internal/missing/service.go`.\n") + + if _, err := EmitOSSWithOptions(projectRoot, Options{Mode: ModeBootstrapSDP, Focus: "architecture"}); err != nil { + t.Fatalf("EmitOSSWithOptions bootstrap failed: %v", err) + } + + readinessData, err := os.ReadFile(filepath.Join(projectRoot, ".sdp", "reality", "readiness-report.json")) + if err != nil { + t.Fatalf("read readiness report: %v", err) + } + var readiness map[string]any + if err := json.Unmarshal(readinessData, &readiness); err != nil { + t.Fatalf("parse readiness report: %v", err) + } + if readiness["verdict"] != "ready_with_constraints" { + t.Fatalf("unexpected readiness verdict: %v", readiness["verdict"]) + } + recommendations, ok := readiness["suggested_workstreams"].([]any) + if !ok || len(recommendations) == 0 { + t.Fatalf("expected bootstrap recommendations: %#v", readiness["suggested_workstreams"]) + } + + driftData, err := os.ReadFile(filepath.Join(projectRoot, ".sdp", "reality", "drift-report.json")) + if err != nil { + t.Fatalf("read drift report: %v", err) + } + var drift map[string]any + if err := json.Unmarshal(driftData, &drift); err != nil { + t.Fatalf("parse drift report: %v", err) + } + contradictions, ok := drift["contradictions"].([]any) + if !ok || len(contradictions) == 0 { + t.Fatalf("expected documentation drift contradictions: %#v", drift["contradictions"]) + } +} + +func seedProject(t *testing.T, root string) { + t.Helper() + + writeFile(t, filepath.Join(root, "cmd", "app", "main.go"), "package main\n\nfunc main() {}\n") + writeFile(t, filepath.Join(root, "internal", "pkg", "logic.go"), "package pkg\n\nfunc Sum(a, b int) int { return a + b }\n") + writeFile(t, filepath.Join(root, "internal", "pkg", "logic_test.go"), "package pkg\n\nimport \"testing\"\n\nfunc TestSum(t *testing.T) {\n\tif Sum(2, 2) != 4 {\n\t\tt.Fatal(\"unexpected\")\n\t}\n}\n") + writeFile(t, filepath.Join(root, "docs", "overview.md"), "# Overview\n") + writeFile(t, filepath.Join(root, "docs", "specs", "reality", "ARTIFACT-CONTRACT.md"), "# contract\nSee `internal/pkg/logic.go`.\n") +} + +func writeFile(t *testing.T, path, body string) { + t.Helper() + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatalf("mkdir %s: %v", filepath.Dir(path), err) + } + if err := os.WriteFile(path, []byte(body), 0o644); err != nil { + t.Fatalf("write %s: %v", path, err) + } +} diff --git a/sdp-plugin/internal/reality/schemas/agent-readiness-plan.schema.json b/sdp-plugin/internal/reality/schemas/agent-readiness-plan.schema.json new file mode 100644 index 00000000..9bef7f80 --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/agent-readiness-plan.schema.json @@ -0,0 +1,136 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/agent-readiness-plan/v1", + "title": "RealityAgentReadinessPlan", + "description": "Phased plan for taking a system from current to target agent readiness.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "current_verdict", + "target_verdict", + "phases" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "current_verdict": { + "type": "string", + "enum": [ + "ready", + "ready_with_constraints", + "not_ready" + ] + }, + "target_verdict": { + "type": "string", + "enum": [ + "ready", + "ready_with_constraints", + "not_ready" + ] + }, + "phases": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "phase_id", + "title", + "objective" + ], + "properties": { + "phase_id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "objective": { + "type": "string", + "minLength": 1 + }, + "allowed_scope": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "blocked_zones": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "required_evidence": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "verification_requirements": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "exit_criteria": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "justification_claim_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "key_risks": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "sequencing_notes": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/architecture-map.schema.json b/sdp-plugin/internal/reality/schemas/architecture-map.schema.json new file mode 100644 index 00000000..bb3d53e7 --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/architecture-map.schema.json @@ -0,0 +1,118 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/architecture-map/v1", + "title": "ArchitectureMap", + "description": "System architecture graph reconstructed by reality.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "nodes", + "edges" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "nodes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "node_id", + "name", + "kind" + ], + "properties": { + "node_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "boundary": { + "type": "string" + }, + "repo": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "edges": { + "type": "array", + "items": { + "type": "object", + "required": [ + "from", + "to", + "relation" + ], + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "relation": { + "type": "string" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "additionalProperties": false + } + }, + "hotspots": { + "type": "array", + "items": { + "type": "object", + "required": [ + "node_id", + "reason" + ], + "properties": { + "node_id": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "severity": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/bootstrap-backlog.schema.json b/sdp-plugin/internal/reality/schemas/bootstrap-backlog.schema.json new file mode 100644 index 00000000..4dc6f10b --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/bootstrap-backlog.schema.json @@ -0,0 +1,130 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/bootstrap-backlog/v1", + "title": "RealityBootstrapBacklog", + "description": "Bootstrap workstream backlog synthesized by reality-pro.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "workstreams" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "workstreams": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "backlog_id", + "title", + "goal", + "priority", + "status" + ], + "properties": { + "backlog_id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "goal": { + "type": "string", + "minLength": 1 + }, + "priority": { + "type": "string", + "enum": [ + "P0", + "P1", + "P2", + "P3" + ] + }, + "status": { + "type": "string", + "enum": [ + "proposed", + "sequenced", + "blocked" + ] + }, + "scope": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "repositories": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "evidence_claim_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "dependencies": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "recommended_agent": { + "type": "string" + }, + "rationale": { + "type": "string" + }, + "risk_level": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ] + }, + "exit_criteria": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/c4-component.schema.json b/sdp-plugin/internal/reality/schemas/c4-component.schema.json new file mode 100644 index 00000000..909c6a40 --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/c4-component.schema.json @@ -0,0 +1,140 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/c4-component/v1", + "title": "RealityC4Component", + "description": "Component view emitted by reality-pro.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "container_id", + "components", + "relationships" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "container_id": { + "type": "string", + "minLength": 1 + }, + "components": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/component" + } + }, + "relationships": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/relationship" + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "$defs": { + "component": { + "type": "object", + "required": [ + "component_id", + "name", + "paths" + ], + "properties": { + "component_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string" + }, + "technology": { + "type": "string" + }, + "paths": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "interfaces": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "responsibilities": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + }, + "relationship": { + "type": "object", + "required": [ + "relationship_id", + "from", + "to", + "description" + ], + "properties": { + "relationship_id": { + "type": "string", + "minLength": 1 + }, + "from": { + "type": "string", + "minLength": 1 + }, + "to": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 1 + }, + "technology": { + "type": "string" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/c4-container.schema.json b/sdp-plugin/internal/reality/schemas/c4-container.schema.json new file mode 100644 index 00000000..603b095f --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/c4-container.schema.json @@ -0,0 +1,136 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/c4-container/v1", + "title": "RealityC4Container", + "description": "Container view emitted by reality-pro.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "system_name", + "containers", + "relationships" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "system_name": { + "type": "string", + "minLength": 1 + }, + "containers": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/container" + } + }, + "relationships": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/relationship" + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "$defs": { + "container": { + "type": "object", + "required": [ + "container_id", + "name", + "technology" + ], + "properties": { + "container_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string" + }, + "technology": { + "type": "string", + "minLength": 1 + }, + "boundary": { + "type": "string", + "enum": [ + "internal", + "external" + ] + }, + "repo_id": { + "type": "string" + }, + "responsibilities": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + }, + "relationship": { + "type": "object", + "required": [ + "relationship_id", + "from", + "to", + "description" + ], + "properties": { + "relationship_id": { + "type": "string", + "minLength": 1 + }, + "from": { + "type": "string", + "minLength": 1 + }, + "to": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 1 + }, + "technology": { + "type": "string" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/c4-system-context.schema.json b/sdp-plugin/internal/reality/schemas/c4-system-context.schema.json new file mode 100644 index 00000000..4bf26c5e --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/c4-system-context.schema.json @@ -0,0 +1,181 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/c4-system-context/v1", + "title": "RealityC4SystemContext", + "description": "System context view emitted by reality-pro.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "scope", + "systems", + "relationships" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "scope": { + "type": "object", + "required": [ + "system_name", + "repos" + ], + "properties": { + "system_name": { + "type": "string", + "minLength": 1 + }, + "repos": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + }, + "people": { + "type": "array", + "items": { + "$ref": "#/$defs/person" + } + }, + "systems": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/system" + } + }, + "relationships": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/relationship" + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "$defs": { + "person": { + "type": "object", + "required": [ + "person_id", + "name" + ], + "properties": { + "person_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false + }, + "system": { + "type": "object", + "required": [ + "system_id", + "name", + "boundary" + ], + "properties": { + "system_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "boundary": { + "type": "string", + "enum": [ + "internal", + "external" + ] + }, + "description": { + "type": "string" + }, + "repo_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + }, + "relationship": { + "type": "object", + "required": [ + "relationship_id", + "from", + "to", + "description" + ], + "properties": { + "relationship_id": { + "type": "string", + "minLength": 1 + }, + "from": { + "type": "string", + "minLength": 1 + }, + "to": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 1 + }, + "technology": { + "type": "string" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/claim.schema.json b/sdp-plugin/internal/reality/schemas/claim.schema.json new file mode 100644 index 00000000..e17c5737 --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/claim.schema.json @@ -0,0 +1,102 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/claim/v1", + "title": "RealityClaim", + "description": "Normalized claim used across reality and reality-pro artifacts.", + "type": "object", + "required": [ + "claim_id", + "title", + "statement", + "status", + "confidence", + "source_ids", + "review_state" + ], + "properties": { + "claim_id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "statement": { + "type": "string", + "minLength": 1 + }, + "status": { + "type": "string", + "enum": [ + "observed", + "documented", + "inferred", + "conflicted", + "unknown" + ] + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "source_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "review_state": { + "type": "string", + "enum": [ + "unreviewed", + "cross_checked", + "challenged", + "arbitrated" + ] + }, + "affected_repos": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "affected_paths": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "affected_components": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "counter_evidence": { + "type": "array", + "items": { + "type": "string" + } + }, + "open_questions": { + "type": "array", + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/conflicts-report.schema.json b/sdp-plugin/internal/reality/schemas/conflicts-report.schema.json new file mode 100644 index 00000000..c387ebd7 --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/conflicts-report.schema.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/conflicts-report/v1", + "title": "RealityConflictsReport", + "description": "Arbitrated disagreement report for reality-pro findings.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "conflicts" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "conflicts": { + "type": "array", + "items": { + "type": "object", + "required": [ + "conflict_id", + "summary", + "competing_claim_ids", + "severity", + "status" + ], + "properties": { + "conflict_id": { + "type": "string", + "minLength": 1 + }, + "summary": { + "type": "string", + "minLength": 1 + }, + "competing_claim_ids": { + "type": "array", + "minItems": 2, + "items": { + "type": "string", + "minLength": 1 + } + }, + "severity": { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "critical" + ] + }, + "status": { + "type": "string", + "enum": [ + "open", + "triaged", + "arbitrated" + ] + }, + "arbitrated_claim_id": { + "type": "string" + }, + "resolution_notes": { + "type": "string" + }, + "source_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/drift-report.schema.json b/sdp-plugin/internal/reality/schemas/drift-report.schema.json new file mode 100644 index 00000000..0bf20d00 --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/drift-report.schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/drift-report/v1", + "title": "DriftReport", + "description": "Documentation and intent drift report from a reality run.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "contradictions" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "contradictions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "contradiction_id", + "summary", + "left_claim_id", + "right_claim_id" + ], + "properties": { + "contradiction_id": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "left_claim_id": { + "type": "string" + }, + "right_claim_id": { + "type": "string" + }, + "resolution_state": { + "type": "string", + "enum": [ + "open", + "triaged", + "resolved" + ] + } + }, + "additionalProperties": false + } + }, + "unresolved_questions": { + "type": "array", + "items": { + "type": "string" + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/feature-inventory.schema.json b/sdp-plugin/internal/reality/schemas/feature-inventory.schema.json new file mode 100644 index 00000000..46a05a62 --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/feature-inventory.schema.json @@ -0,0 +1,90 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/feature-inventory/v1", + "title": "FeatureInventory", + "description": "Reconstructed feature inventory from a reality run.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "features" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "features": { + "type": "array", + "items": { + "type": "object", + "required": [ + "feature_id", + "title", + "summary", + "status", + "evidence_claim_ids", + "confidence", + "mapped_components" + ], + "properties": { + "feature_id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "summary": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "implemented", + "partial", + "candidate", + "dead" + ] + }, + "evidence_claim_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "mapped_components": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/integration-map.schema.json b/sdp-plugin/internal/reality/schemas/integration-map.schema.json new file mode 100644 index 00000000..0d5a495a --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/integration-map.schema.json @@ -0,0 +1,74 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/integration-map/v1", + "title": "IntegrationMap", + "description": "Integration inventory reconstructed by reality.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "integrations" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "integrations": { + "type": "array", + "items": { + "type": "object", + "required": [ + "integration_id", + "integration_type", + "producer", + "consumer", + "confidence" + ], + "properties": { + "integration_id": { + "type": "string" + }, + "integration_type": { + "type": "string" + }, + "producer": { + "type": "string" + }, + "consumer": { + "type": "string" + }, + "contract_type": { + "type": "string" + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "risk_notes": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/intent-gap-report.schema.json b/sdp-plugin/internal/reality/schemas/intent-gap-report.schema.json new file mode 100644 index 00000000..97350ade --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/intent-gap-report.schema.json @@ -0,0 +1,117 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/intent-gap-report/v1", + "title": "RealityIntentGapReport", + "description": "Plan-vs-implementation gap report for reality-pro.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "gaps" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "gaps": { + "type": "array", + "items": { + "type": "object", + "required": [ + "gap_id", + "title", + "expected_state", + "observed_state", + "gap_type", + "severity", + "status" + ], + "properties": { + "gap_id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "expected_state": { + "type": "string", + "minLength": 1 + }, + "observed_state": { + "type": "string", + "minLength": 1 + }, + "gap_type": { + "type": "string", + "enum": [ + "missing", + "partial", + "contradicted", + "ambiguous" + ] + }, + "severity": { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "critical" + ] + }, + "status": { + "type": "string", + "enum": [ + "open", + "triaged", + "accepted", + "resolved" + ] + }, + "supporting_claim_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "affected_repos": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "recommended_actions": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/quality-report.schema.json b/sdp-plugin/internal/reality/schemas/quality-report.schema.json new file mode 100644 index 00000000..0a570581 --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/quality-report.schema.json @@ -0,0 +1,104 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/quality-report/v1", + "title": "QualityReport", + "description": "Quality and test posture baseline from a reality run.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "findings" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "analysis_mode": { + "type": "string" + }, + "analysis_focus": { + "type": "string" + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "required": [ + "finding_id", + "title", + "severity", + "claim_ids" + ], + "properties": { + "finding_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "severity": { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "critical" + ] + }, + "description": { + "type": "string" + }, + "claim_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "hotspot_ranking": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "score" + ], + "properties": { + "path": { + "type": "string" + }, + "score": { + "type": "number", + "minimum": 0 + }, + "reason": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/readiness-report.schema.json b/sdp-plugin/internal/reality/schemas/readiness-report.schema.json new file mode 100644 index 00000000..9ba6d829 --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/readiness-report.schema.json @@ -0,0 +1,111 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/readiness-report/v1", + "title": "ReadinessReport", + "description": "Agent-readiness baseline from a reality run.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "verdict", + "dimensions", + "justification_claim_ids" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "verdict": { + "type": "string", + "enum": [ + "ready", + "ready_with_constraints", + "not_ready" + ] + }, + "dimensions": { + "type": "object", + "required": [ + "boundary_clarity", + "verification_coverage", + "hotspot_concentration", + "integration_fragility", + "documentation_trust_level" + ], + "properties": { + "boundary_clarity": { + "$ref": "#/$defs/dimensionScore" + }, + "verification_coverage": { + "$ref": "#/$defs/dimensionScore" + }, + "hotspot_concentration": { + "$ref": "#/$defs/dimensionScore" + }, + "integration_fragility": { + "$ref": "#/$defs/dimensionScore" + }, + "documentation_trust_level": { + "$ref": "#/$defs/dimensionScore" + } + }, + "additionalProperties": false + }, + "justification_claim_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "constraints": { + "type": "array", + "items": { + "type": "string" + } + }, + "suggested_workstreams": { + "type": "array", + "items": { + "type": "string" + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "$defs": { + "dimensionScore": { + "type": "object", + "required": [ + "score" + ], + "properties": { + "score": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "note": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/reality-summary.schema.json b/sdp-plugin/internal/reality/schemas/reality-summary.schema.json new file mode 100644 index 00000000..6997c253 --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/reality-summary.schema.json @@ -0,0 +1,107 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/reality-summary/v1", + "title": "RealitySummary", + "description": "Top-level summary artifact for a reality run.", + "type": "object", + "required": [ + "spec_version", + "run_id", + "generated_at", + "scope", + "readiness_verdict", + "top_finding_claim_ids", + "artifacts" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "run_id": { + "type": "string", + "minLength": 1 + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "scope": { + "type": "object", + "required": [ + "repos" + ], + "properties": { + "repos": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "mode": { + "type": "string" + }, + "focus": { + "type": "string" + } + }, + "additionalProperties": false + }, + "readiness_verdict": { + "type": "string", + "enum": [ + "ready", + "ready_with_constraints", + "not_ready" + ] + }, + "top_finding_claim_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "review_strategy": { + "type": "object", + "required": [ + "primary", + "secondary", + "scope" + ], + "properties": { + "primary": { + "type": "string" + }, + "secondary": { + "type": "string" + }, + "scope": { + "type": "string" + } + }, + "additionalProperties": false + }, + "artifacts": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/repo-memory.schema.json b/sdp-plugin/internal/reality/schemas/repo-memory.schema.json new file mode 100644 index 00000000..fd86df7c --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/repo-memory.schema.json @@ -0,0 +1,245 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/repo-memory/v1", + "title": "RealityRepoMemory", + "description": "Persistent normalized repository memory for reality-pro.", + "type": "object", + "required": [ + "spec_version", + "generated_at", + "repos", + "module_summaries", + "unresolved_questions" + ], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^v\\d+\\.\\d+$" + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "repos": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "repo_id", + "name", + "root_path" + ], + "properties": { + "repo_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "root_path": { + "type": "string", + "minLength": 1 + }, + "role": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "last_indexed_at": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + } + }, + "glossary_terms": { + "type": "array", + "items": { + "type": "object", + "required": [ + "term", + "definition" + ], + "properties": { + "term": { + "type": "string", + "minLength": 1 + }, + "definition": { + "type": "string", + "minLength": 1 + }, + "source_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + } + }, + "module_summaries": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "module_id", + "repo_id", + "summary" + ], + "properties": { + "module_id": { + "type": "string", + "minLength": 1 + }, + "repo_id": { + "type": "string", + "minLength": 1 + }, + "summary": { + "type": "string", + "minLength": 1 + }, + "paths": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "interfaces": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "risk_level": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ] + } + }, + "additionalProperties": false + } + }, + "feature_mappings": { + "type": "array", + "items": { + "type": "object", + "required": [ + "feature_id", + "title", + "confidence" + ], + "properties": { + "feature_id": { + "type": "string", + "minLength": 1 + }, + "title": { + "type": "string", + "minLength": 1 + }, + "repo_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "component_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + }, + "additionalProperties": false + } + }, + "previous_validated_claim_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "unresolved_questions": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "hotspots": { + "type": "array", + "items": { + "type": "object", + "required": [ + "hotspot_id", + "repo_id", + "path", + "reason" + ], + "properties": { + "hotspot_id": { + "type": "string", + "minLength": 1 + }, + "repo_id": { + "type": "string", + "minLength": 1 + }, + "path": { + "type": "string", + "minLength": 1 + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "severity": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ] + } + }, + "additionalProperties": false + } + }, + "claims": { + "type": "array", + "items": { + "$ref": "claim.schema.json" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "source.schema.json" + } + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/schemas/source.schema.json b/sdp-plugin/internal/reality/schemas/source.schema.json new file mode 100644 index 00000000..6e05e883 --- /dev/null +++ b/sdp-plugin/internal/reality/schemas/source.schema.json @@ -0,0 +1,79 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sdp.dev/reality/source/v1", + "title": "RealitySource", + "description": "Normalized source reference used across reality and reality-pro artifacts.", + "type": "object", + "required": [ + "source_id", + "kind", + "locator", + "revision" + ], + "properties": { + "source_id": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "enum": [ + "code", + "test", + "config", + "manifest", + "doc", + "issue", + "pull_request", + "commit", + "runtime_trace", + "external_contract" + ] + }, + "locator": { + "type": "string", + "minLength": 1 + }, + "revision": { + "type": "string", + "minLength": 1 + }, + "repo": { + "type": "string" + }, + "path": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "uri" + }, + "line_range": { + "type": "object", + "required": [ + "start", + "end" + ], + "properties": { + "start": { + "type": "integer", + "minimum": 1 + }, + "end": { + "type": "integer", + "minimum": 1 + } + }, + "additionalProperties": false + }, + "excerpt_hash": { + "type": "string", + "minLength": 1 + }, + "captured_at": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false +} diff --git a/sdp-plugin/internal/reality/validator.go b/sdp-plugin/internal/reality/validator.go new file mode 100644 index 00000000..e91f6fc8 --- /dev/null +++ b/sdp-plugin/internal/reality/validator.go @@ -0,0 +1,113 @@ +package reality + +import ( + "bytes" + "embed" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/santhosh-tekuri/jsonschema/v5" +) + +type artifactSchema struct { + ArtifactRel string + SchemaName string +} + +var requiredOSSValidationArtifacts = []artifactSchema{ + {ArtifactRel: ".sdp/reality/reality-summary.json", SchemaName: "reality-summary.schema.json"}, + {ArtifactRel: ".sdp/reality/feature-inventory.json", SchemaName: "feature-inventory.schema.json"}, + {ArtifactRel: ".sdp/reality/architecture-map.json", SchemaName: "architecture-map.schema.json"}, + {ArtifactRel: ".sdp/reality/integration-map.json", SchemaName: "integration-map.schema.json"}, + {ArtifactRel: ".sdp/reality/quality-report.json", SchemaName: "quality-report.schema.json"}, + {ArtifactRel: ".sdp/reality/drift-report.json", SchemaName: "drift-report.schema.json"}, + {ArtifactRel: ".sdp/reality/readiness-report.json", SchemaName: "readiness-report.schema.json"}, +} + +//go:embed schemas/*.json +var schemaFS embed.FS + +// ValidateOSS validates the emitted OSS reality artifacts against the published schema contract. +func ValidateOSS(projectRoot string) ([]string, error) { + issues := make([]string, 0) + validated := make([]string, 0, len(requiredOSSValidationArtifacts)) + + for _, item := range requiredOSSValidationArtifacts { + artifactPath := filepath.Join(projectRoot, item.ArtifactRel) + + artifactData, err := os.ReadFile(artifactPath) + if err != nil { + issues = append(issues, fmt.Sprintf("%s: read artifact: %v", item.ArtifactRel, err)) + continue + } + + var payload any + if err := json.Unmarshal(artifactData, &payload); err != nil { + issues = append(issues, fmt.Sprintf("%s: invalid JSON: %v", item.ArtifactRel, err)) + continue + } + + schema, err := compileEmbeddedSchema(item.SchemaName) + if err != nil { + issues = append(issues, fmt.Sprintf("%s: compile schema %s: %v", item.ArtifactRel, item.SchemaName, err)) + continue + } + if err := schema.Validate(payload); err != nil { + issues = append(issues, fmt.Sprintf("%s: schema validation failed: %v", item.ArtifactRel, err)) + continue + } + + validated = append(validated, item.ArtifactRel) + } + + if len(issues) > 0 { + return issues, fmt.Errorf("%d artifact(s) failed validation", len(issues)) + } + return validated, nil +} + +func compileEmbeddedSchema(schemaName string) (*jsonschema.Schema, error) { + compiler := jsonschema.NewCompiler() + + claimData, err := schemaFS.ReadFile("schemas/claim.schema.json") + if err != nil { + return nil, err + } + sourceData, err := schemaFS.ReadFile("schemas/source.schema.json") + if err != nil { + return nil, err + } + schemaData, err := schemaFS.ReadFile("schemas/" + schemaName) + if err != nil { + return nil, err + } + + baseName := strings.TrimSuffix(schemaName, ".schema.json") + claimAliases := []string{ + "claim.schema.json", + fmt.Sprintf("https://sdp.dev/reality/%s/claim.schema.json", baseName), + } + sourceAliases := []string{ + "source.schema.json", + fmt.Sprintf("https://sdp.dev/reality/%s/source.schema.json", baseName), + } + + for _, alias := range claimAliases { + if err := compiler.AddResource(alias, bytes.NewReader(claimData)); err != nil { + return nil, err + } + } + for _, alias := range sourceAliases { + if err := compiler.AddResource(alias, bytes.NewReader(sourceData)); err != nil { + return nil, err + } + } + if err := compiler.AddResource(schemaName, bytes.NewReader(schemaData)); err != nil { + return nil, err + } + + return compiler.Compile(schemaName) +} diff --git a/sdp-plugin/internal/reality/validator_test.go b/sdp-plugin/internal/reality/validator_test.go new file mode 100644 index 00000000..d2f16136 --- /dev/null +++ b/sdp-plugin/internal/reality/validator_test.go @@ -0,0 +1,60 @@ +package reality + +import ( + "os" + "path/filepath" + "testing" +) + +func TestValidateOSS_SucceedsOnEmittedArtifacts(t *testing.T) { + projectRoot := t.TempDir() + seedProject(t, projectRoot) + + if _, err := EmitOSSWithOptions(projectRoot, Options{Mode: ModeDeep}); err != nil { + t.Fatalf("EmitOSSWithOptions failed: %v", err) + } + + validated, err := ValidateOSS(projectRoot) + if err != nil { + t.Fatalf("ValidateOSS failed: %v", err) + } + if len(validated) != len(requiredOSSValidationArtifacts) { + t.Fatalf("expected %d validated artifacts, got %d", len(requiredOSSValidationArtifacts), len(validated)) + } +} + +func TestValidateOSS_SucceedsOnEmptyRepository(t *testing.T) { + projectRoot := t.TempDir() + + if _, err := EmitOSSWithOptions(projectRoot, Options{Mode: ModeDeep}); err != nil { + t.Fatalf("EmitOSSWithOptions failed: %v", err) + } + + validated, err := ValidateOSS(projectRoot) + if err != nil { + t.Fatalf("ValidateOSS failed for empty repository: %v", err) + } + if len(validated) != len(requiredOSSValidationArtifacts) { + t.Fatalf("expected %d validated artifacts, got %d", len(requiredOSSValidationArtifacts), len(validated)) + } +} + +func TestValidateOSS_FailsOnInvalidArtifact(t *testing.T) { + projectRoot := t.TempDir() + seedProject(t, projectRoot) + + if _, err := EmitOSS(projectRoot); err != nil { + t.Fatalf("EmitOSS failed: %v", err) + } + if err := os.WriteFile(filepath.Join(projectRoot, ".sdp", "reality", "quality-report.json"), []byte("{bad json"), 0o644); err != nil { + t.Fatalf("corrupt quality report: %v", err) + } + + issues, err := ValidateOSS(projectRoot) + if err == nil { + t.Fatal("expected ValidateOSS to fail") + } + if len(issues) == 0 { + t.Fatal("expected validation issues") + } +} diff --git a/sdp-plugin/internal/ui/completion_bash.go b/sdp-plugin/internal/ui/completion_bash.go index 598a795f..28aff980 100644 --- a/sdp-plugin/internal/ui/completion_bash.go +++ b/sdp-plugin/internal/ui/completion_bash.go @@ -10,7 +10,7 @@ _sdp_completion() { local cur prev words cword _init_completion || return - local commands="init doctor status next demo hooks plan apply build verify log guard parse beads tdd drift quality watch telemetry checkpoint orchestrate contract health diagnose memory decisions" + local commands="init doctor status next demo hooks plan apply build verify log guard parse beads tdd drift quality reality watch telemetry checkpoint orchestrate contract health diagnose memory decisions" local checkpoint_commands="create resume list clean" local orchestrate_commands="start status stop" local log_commands="show export stats trace" @@ -46,6 +46,10 @@ _sdp_completion() { COMPREPLY=($(compgen -W "coverage complexity size types all" -- "${cur}")) return ;; + reality) + COMPREPLY=($(compgen -W "emit-oss validate" -- "${cur}")) + return + ;; checkpoint) case ${words[2]} in create|resume) diff --git a/sdp-plugin/internal/ui/completion_fish.go b/sdp-plugin/internal/ui/completion_fish.go index 40668191..7a07cef4 100644 --- a/sdp-plugin/internal/ui/completion_fish.go +++ b/sdp-plugin/internal/ui/completion_fish.go @@ -39,6 +39,11 @@ function __sdp_quality_commands echo -e "report\tGenerate quality report" end +function __sdp_reality_commands + echo -e "emit-oss\tEmit the OSS reality artifact set" + echo -e "validate\tValidate emitted OSS reality artifacts" +end + complete -c sdp -f complete -c sdp -n "__fish_use_subcommand" -a init -d "Initialize project with SDP prompts" @@ -52,6 +57,7 @@ complete -c sdp -n "__fish_use_subcommand" -a beads -d "Interact with Beads task complete -c sdp -n "__fish_use_subcommand" -a tdd -d "Run TDD cycle" complete -c sdp -n "__fish_use_subcommand" -a drift -d "Detect code drift" complete -c sdp -n "__fish_use_subcommand" -a quality -d "Check code quality gates" +complete -c sdp -n "__fish_use_subcommand" -a reality -d "Emit and validate OSS reality baseline artifacts" complete -c sdp -n "__fish_use_subcommand" -a watch -d "Watch files for quality violations" complete -c sdp -n "__fish_use_subcommand" -a telemetry -d "Manage telemetry data" complete -c sdp -n "__fish_use_subcommand" -a checkpoint -d "Manage checkpoints" @@ -68,6 +74,9 @@ complete -c sdp -n "__fish_seen_subcommand_from beads" -a "(__sdp_beads_commands # Quality subcommands complete -c sdp -n "__fish_seen_subcommand_from quality" -a "(__sdp_quality_commands)" + +# Reality subcommands +complete -c sdp -n "__fish_seen_subcommand_from reality" -a "(__sdp_reality_commands)" `, nil } diff --git a/sdp-plugin/internal/ui/completion_zsh.go b/sdp-plugin/internal/ui/completion_zsh.go index 4768152b..f43a9a09 100644 --- a/sdp-plugin/internal/ui/completion_zsh.go +++ b/sdp-plugin/internal/ui/completion_zsh.go @@ -14,6 +14,7 @@ _sdp() { local -a orchestrate_commands local -a beads_commands local -a quality_commands + local -a reality_commands commands=( 'init:Initialize project with SDP prompts' @@ -27,6 +28,7 @@ _sdp() { 'tdd:Run TDD cycle (Red-Green-Refactor)' 'drift:Detect code drift from specification' 'quality:Check code quality gates' + 'reality:Emit and validate OSS reality baseline artifacts' 'watch:Watch files for quality violations' 'telemetry:Manage telemetry data' 'checkpoint:Manage checkpoints for long-running features' @@ -60,6 +62,11 @@ _sdp() { 'report:Generate quality report' ) + reality_commands=( + 'emit-oss:Emit the OSS reality artifact set' + 'validate:Validate emitted OSS reality artifacts' + ) + case $state in command) _describe 'command' commands @@ -76,6 +83,9 @@ _sdp() { quality) _describe 'quality command' quality_commands ;; + reality) + _describe 'reality command' reality_commands + ;; esac } @@ -96,6 +106,9 @@ case $line[1] in quality) _sdp quality "$words[2,-1]" ;; + reality) + _sdp reality "$words[2,-1]" + ;; *) _sdp ;;