From 69510c2779f912e921a435d0b49d2917fb9bf520 Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Sat, 6 Jun 2026 16:19:43 +0200 Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=94=A7=20Add=20Pharaoh=20Copilot=20ag?= =?UTF-8?q?ent=20definitions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vendor the Pharaoh plugin's GitHub Copilot agents and prompts so the @pharaoh.* agents are available in VS Code Copilot Chat. Added by pharaoh:setup: - .github/agents/pharaoh.*.agent.md (73) - .github/prompts/pharaoh.*.prompt.md (7) - .github/copilot-instructions.md --- .../pharaoh.activity-diagram-draft.agent.md | 10 ++ .../pharaoh.api-coverage-check.agent.md | 10 ++ .github/agents/pharaoh.arch-draft.agent.md | 10 ++ .github/agents/pharaoh.arch-review.agent.md | 10 ++ .github/agents/pharaoh.audit-fanout.agent.md | 10 ++ .github/agents/pharaoh.author.agent.md | 19 +++ .../pharaoh.block-diagram-draft.agent.md | 10 ++ .github/agents/pharaoh.bootstrap.agent.md | 13 ++ .github/agents/pharaoh.change.agent.md | 131 ++++++++++++++++ .../pharaoh.class-diagram-draft.agent.md | 10 ++ .../pharaoh.component-diagram-draft.agent.md | 10 ++ .../agents/pharaoh.context-gather.agent.md | 10 ++ .github/agents/pharaoh.coverage-gap.agent.md | 10 ++ .github/agents/pharaoh.decide.agent.md | 115 ++++++++++++++ .../agents/pharaoh.decision-record.agent.md | 10 ++ .../agents/pharaoh.decision-review.agent.md | 10 ++ .../pharaoh.deployment-diagram-draft.agent.md | 10 ++ .github/agents/pharaoh.diagram-lint.agent.md | 13 ++ .../agents/pharaoh.diagram-review.agent.md | 10 ++ .../pharaoh.dispatch-signal-check.agent.md | 10 ++ .github/agents/pharaoh.execute-plan.agent.md | 10 ++ .../pharaoh.fault-tree-diagram-draft.agent.md | 10 ++ .github/agents/pharaoh.feat-balance.agent.md | 10 ++ .../pharaoh.feat-component-extract.agent.md | 10 ++ .../pharaoh.feat-draft-from-docs.agent.md | 10 ++ .github/agents/pharaoh.feat-file-map.agent.md | 10 ++ .../agents/pharaoh.feat-flow-extract.agent.md | 10 ++ .github/agents/pharaoh.feat-review.agent.md | 10 ++ .../agents/pharaoh.finding-record.agent.md | 10 ++ .github/agents/pharaoh.flow.agent.md | 12 ++ .github/agents/pharaoh.fmea-review.agent.md | 10 ++ .github/agents/pharaoh.fmea.agent.md | 10 ++ .github/agents/pharaoh.gate-advisor.agent.md | 10 ++ .github/agents/pharaoh.id-allocate.agent.md | 10 ++ .../pharaoh.id-convention-check.agent.md | 10 ++ .../agents/pharaoh.lifecycle-check.agent.md | 10 ++ .../pharaoh.link-completeness-check.agent.md | 10 ++ .github/agents/pharaoh.mece.agent.md | 145 ++++++++++++++++++ .../agents/pharaoh.output-validate.agent.md | 10 ++ .../pharaoh.papyrus-non-empty-check.agent.md | 10 ++ .github/agents/pharaoh.plan.agent.md | 133 ++++++++++++++++ .github/agents/pharaoh.process-audit.agent.md | 10 ++ .github/agents/pharaoh.prose-migrate.agent.md | 10 ++ .github/agents/pharaoh.quality-gate.agent.md | 10 ++ .github/agents/pharaoh.release.agent.md | 110 +++++++++++++ .../pharaoh.reproducibility-check.agent.md | 10 ++ .../pharaoh.req-code-grounding-check.agent.md | 10 ++ .../pharaoh.req-codelink-annotate.agent.md | 10 ++ .github/agents/pharaoh.req-draft.agent.md | 10 ++ .github/agents/pharaoh.req-from-code.agent.md | 10 ++ .../agents/pharaoh.req-regenerate.agent.md | 10 ++ .github/agents/pharaoh.req-review.agent.md | 10 ++ .../pharaoh.review-completeness.agent.md | 10 ++ ...haraoh.self-review-coverage-check.agent.md | 10 ++ .../pharaoh.sequence-diagram-draft.agent.md | 10 ++ .github/agents/pharaoh.setup.agent.md | 133 ++++++++++++++++ .github/agents/pharaoh.spec.agent.md | 124 +++++++++++++++ .../pharaoh.sphinx-extension-add.agent.md | 10 ++ .../pharaoh.standard-conformance.agent.md | 10 ++ .../pharaoh.state-diagram-draft.agent.md | 10 ++ .../pharaoh.status-lifecycle-check.agent.md | 13 ++ .../agents/pharaoh.tailor-bootstrap.agent.md | 10 ++ ...aoh.tailor-code-grounding-filters.agent.md | 10 ++ .github/agents/pharaoh.tailor-detect.agent.md | 10 ++ .github/agents/pharaoh.tailor-fill.agent.md | 10 ++ .github/agents/pharaoh.tailor-review.agent.md | 10 ++ .github/agents/pharaoh.toctree-emit.agent.md | 10 ++ .github/agents/pharaoh.trace.agent.md | 107 +++++++++++++ .../pharaoh.use-case-diagram-draft.agent.md | 10 ++ .github/agents/pharaoh.verify.agent.md | 19 +++ .github/agents/pharaoh.vplan-draft.agent.md | 10 ++ .github/agents/pharaoh.vplan-review.agent.md | 10 ++ .github/agents/pharaoh.write-plan.agent.md | 10 ++ .github/copilot-instructions.md | 84 ++++++++++ .github/prompts/pharaoh.author.prompt.md | 23 +++ .github/prompts/pharaoh.change.prompt.md | 3 + .github/prompts/pharaoh.mece.prompt.md | 3 + .github/prompts/pharaoh.plan.prompt.md | 3 + .github/prompts/pharaoh.release.prompt.md | 3 + .github/prompts/pharaoh.trace.prompt.md | 3 + .github/prompts/pharaoh.verify.prompt.md | 22 +++ 81 files changed, 1821 insertions(+) create mode 100644 .github/agents/pharaoh.activity-diagram-draft.agent.md create mode 100644 .github/agents/pharaoh.api-coverage-check.agent.md create mode 100644 .github/agents/pharaoh.arch-draft.agent.md create mode 100644 .github/agents/pharaoh.arch-review.agent.md create mode 100644 .github/agents/pharaoh.audit-fanout.agent.md create mode 100644 .github/agents/pharaoh.author.agent.md create mode 100644 .github/agents/pharaoh.block-diagram-draft.agent.md create mode 100644 .github/agents/pharaoh.bootstrap.agent.md create mode 100644 .github/agents/pharaoh.change.agent.md create mode 100644 .github/agents/pharaoh.class-diagram-draft.agent.md create mode 100644 .github/agents/pharaoh.component-diagram-draft.agent.md create mode 100644 .github/agents/pharaoh.context-gather.agent.md create mode 100644 .github/agents/pharaoh.coverage-gap.agent.md create mode 100644 .github/agents/pharaoh.decide.agent.md create mode 100644 .github/agents/pharaoh.decision-record.agent.md create mode 100644 .github/agents/pharaoh.decision-review.agent.md create mode 100644 .github/agents/pharaoh.deployment-diagram-draft.agent.md create mode 100644 .github/agents/pharaoh.diagram-lint.agent.md create mode 100644 .github/agents/pharaoh.diagram-review.agent.md create mode 100644 .github/agents/pharaoh.dispatch-signal-check.agent.md create mode 100644 .github/agents/pharaoh.execute-plan.agent.md create mode 100644 .github/agents/pharaoh.fault-tree-diagram-draft.agent.md create mode 100644 .github/agents/pharaoh.feat-balance.agent.md create mode 100644 .github/agents/pharaoh.feat-component-extract.agent.md create mode 100644 .github/agents/pharaoh.feat-draft-from-docs.agent.md create mode 100644 .github/agents/pharaoh.feat-file-map.agent.md create mode 100644 .github/agents/pharaoh.feat-flow-extract.agent.md create mode 100644 .github/agents/pharaoh.feat-review.agent.md create mode 100644 .github/agents/pharaoh.finding-record.agent.md create mode 100644 .github/agents/pharaoh.flow.agent.md create mode 100644 .github/agents/pharaoh.fmea-review.agent.md create mode 100644 .github/agents/pharaoh.fmea.agent.md create mode 100644 .github/agents/pharaoh.gate-advisor.agent.md create mode 100644 .github/agents/pharaoh.id-allocate.agent.md create mode 100644 .github/agents/pharaoh.id-convention-check.agent.md create mode 100644 .github/agents/pharaoh.lifecycle-check.agent.md create mode 100644 .github/agents/pharaoh.link-completeness-check.agent.md create mode 100644 .github/agents/pharaoh.mece.agent.md create mode 100644 .github/agents/pharaoh.output-validate.agent.md create mode 100644 .github/agents/pharaoh.papyrus-non-empty-check.agent.md create mode 100644 .github/agents/pharaoh.plan.agent.md create mode 100644 .github/agents/pharaoh.process-audit.agent.md create mode 100644 .github/agents/pharaoh.prose-migrate.agent.md create mode 100644 .github/agents/pharaoh.quality-gate.agent.md create mode 100644 .github/agents/pharaoh.release.agent.md create mode 100644 .github/agents/pharaoh.reproducibility-check.agent.md create mode 100644 .github/agents/pharaoh.req-code-grounding-check.agent.md create mode 100644 .github/agents/pharaoh.req-codelink-annotate.agent.md create mode 100644 .github/agents/pharaoh.req-draft.agent.md create mode 100644 .github/agents/pharaoh.req-from-code.agent.md create mode 100644 .github/agents/pharaoh.req-regenerate.agent.md create mode 100644 .github/agents/pharaoh.req-review.agent.md create mode 100644 .github/agents/pharaoh.review-completeness.agent.md create mode 100644 .github/agents/pharaoh.self-review-coverage-check.agent.md create mode 100644 .github/agents/pharaoh.sequence-diagram-draft.agent.md create mode 100644 .github/agents/pharaoh.setup.agent.md create mode 100644 .github/agents/pharaoh.spec.agent.md create mode 100644 .github/agents/pharaoh.sphinx-extension-add.agent.md create mode 100644 .github/agents/pharaoh.standard-conformance.agent.md create mode 100644 .github/agents/pharaoh.state-diagram-draft.agent.md create mode 100644 .github/agents/pharaoh.status-lifecycle-check.agent.md create mode 100644 .github/agents/pharaoh.tailor-bootstrap.agent.md create mode 100644 .github/agents/pharaoh.tailor-code-grounding-filters.agent.md create mode 100644 .github/agents/pharaoh.tailor-detect.agent.md create mode 100644 .github/agents/pharaoh.tailor-fill.agent.md create mode 100644 .github/agents/pharaoh.tailor-review.agent.md create mode 100644 .github/agents/pharaoh.toctree-emit.agent.md create mode 100644 .github/agents/pharaoh.trace.agent.md create mode 100644 .github/agents/pharaoh.use-case-diagram-draft.agent.md create mode 100644 .github/agents/pharaoh.verify.agent.md create mode 100644 .github/agents/pharaoh.vplan-draft.agent.md create mode 100644 .github/agents/pharaoh.vplan-review.agent.md create mode 100644 .github/agents/pharaoh.write-plan.agent.md create mode 100644 .github/copilot-instructions.md create mode 100644 .github/prompts/pharaoh.author.prompt.md create mode 100644 .github/prompts/pharaoh.change.prompt.md create mode 100644 .github/prompts/pharaoh.mece.prompt.md create mode 100644 .github/prompts/pharaoh.plan.prompt.md create mode 100644 .github/prompts/pharaoh.release.prompt.md create mode 100644 .github/prompts/pharaoh.trace.prompt.md create mode 100644 .github/prompts/pharaoh.verify.prompt.md diff --git a/.github/agents/pharaoh.activity-diagram-draft.agent.md b/.github/agents/pharaoh.activity-diagram-draft.agent.md new file mode 100644 index 0000000..36bba5e --- /dev/null +++ b/.github/agents/pharaoh.activity-diagram-draft.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when drafting one activity diagram showing control flow (actions, decisions, forks/joins, swimlanes) for one procedure or algorithm. Typical ASPICE usage — SWE.3 Software Detailed Design. Renderer tailored via `pharaoh.toml`. Does NOT emit other diagram kinds. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). +handoffs: [] +--- + +# @pharaoh.activity-diagram-draft + +Use when drafting one activity diagram showing control flow (actions, decisions, forks/joins, swimlanes) for one procedure or algorithm. Typical ASPICE usage — SWE.3 Software Detailed Design. Renderer tailored via `pharaoh.toml`. Does NOT emit other diagram kinds. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). + +See [`skills/pharaoh-activity-diagram-draft/SKILL.md`](../../skills/pharaoh-activity-diagram-draft/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.api-coverage-check.agent.md b/.github/agents/pharaoh.api-coverage-check.agent.md new file mode 100644 index 0000000..0fac840 --- /dev/null +++ b/.github/agents/pharaoh.api-coverage-check.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when verifying that a source file is covered by the need catalogue on two axes — (1) at least one CREQ declares the file as its `:source_doc:`, and (2) every project-defined exception class raised in the file is named by some CREQ's title or content. Exception classes not defined in the project source tree (stdlib, third-party deps) are reported as `external` and do not fail the axis. Classifies non-behavioral files (constants, type aliases, bare re-exports) as skipped. Language-parametric via the shared regex table in `skills/shared/public-symbol-patterns.md` (python / rust / typescript / go / c / cpp / java). Single mechanical structural check. +handoffs: [] +--- + +# @pharaoh.api-coverage-check + +Use when verifying that a source file is covered by the need catalogue on two axes — (1) at least one CREQ declares the file as its `:source_doc:`, and (2) every project-defined exception class raised in the file is named by some CREQ's title or content. Exception classes not defined in the project source tree (stdlib, third-party deps) are reported as `external` and do not fail the axis. Classifies non-behavioral files (constants, type aliases, bare re-exports) as skipped. Language-parametric via the shared regex table in `skills/shared/public-symbol-patterns.md` (python / rust / typescript / go / c / cpp / java). Single mechanical structural check. + +See [`skills/pharaoh-api-coverage-check/SKILL.md`](../../skills/pharaoh-api-coverage-check/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.arch-draft.agent.md b/.github/agents/pharaoh.arch-draft.agent.md new file mode 100644 index 0000000..ccb3362 --- /dev/null +++ b/.github/agents/pharaoh.arch-draft.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when drafting a single sphinx-needs architecture element from one parent requirement. The artefact type is parameterised via `target_level` (any catalog-declared architecture type — e.g. `arch`, `swarch`, `sys-arch`, `module`, `component`, `interface`). Emits an RST directive block linking back to the parent via `:satisfies:`. +handoffs: [] +--- + +# @pharaoh.arch-draft + +Use when drafting a single sphinx-needs architecture element from one parent requirement. The artefact type is parameterised via `target_level` (any catalog-declared architecture type — e.g. `arch`, `swarch`, `sys-arch`, `module`, `component`, `interface`). Emits an RST directive block linking back to the parent via `:satisfies:`. + +See [`skills/pharaoh-arch-draft/SKILL.md`](../../skills/pharaoh-arch-draft/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.arch-review.agent.md b/.github/agents/pharaoh.arch-review.agent.md new file mode 100644 index 0000000..ac6afb0 --- /dev/null +++ b/.github/agents/pharaoh.arch-review.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when auditing a single architecture element against the 10 ISO 26262-8 §6 axes plus arch-specific axes (traceability back to requirement). Emits structured findings JSON. +handoffs: [] +--- + +# @pharaoh.arch-review + +Use when auditing a single architecture element against the 10 ISO 26262-8 §6 axes plus arch-specific axes (traceability back to requirement). Emits structured findings JSON. + +See [`skills/pharaoh-arch-review/SKILL.md`](../../skills/pharaoh-arch-review/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.audit-fanout.agent.md b/.github/agents/pharaoh.audit-fanout.agent.md new file mode 100644 index 0000000..c314f96 --- /dev/null +++ b/.github/agents/pharaoh.audit-fanout.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when running a full project audit in parallel by dispatching 5 atomic audit skills, each writing findings to a shared Papyrus workspace via pharaoh-finding-record for automatic deduplication. Emits the aggregated deduplicated findings list. +handoffs: [] +--- + +# @pharaoh.audit-fanout + +Use when running a full project audit in parallel by dispatching 5 atomic audit skills, each writing findings to a shared Papyrus workspace via pharaoh-finding-record for automatic deduplication. Emits the aggregated deduplicated findings list. + +See [`skills/pharaoh-audit-fanout/SKILL.md`](../../skills/pharaoh-audit-fanout/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.author.agent.md b/.github/agents/pharaoh.author.agent.md new file mode 100644 index 0000000..e1a8e7c --- /dev/null +++ b/.github/agents/pharaoh.author.agent.md @@ -0,0 +1,19 @@ +--- +description: Use when authoring or modifying a single sphinx-needs artefact (requirement, architecture element, test case, decision) by routing to the matching atomic drafting skill based on the project's artefact catalog. Returns the drafted RST directive with an ID, file placement suggestion, and parent link. +handoffs: + - label: Verify Authored Need + agent: pharaoh.verify + prompt: Check that the authored artefact addresses the substance of its parent + - label: Review Drafted Requirement + agent: pharaoh.req-review + prompt: Audit the drafted requirement against the ISO 26262 §6 axes + - label: Trace the Authored Need + agent: pharaoh.trace + prompt: Trace the new artefact through all link types +--- + +# @pharaoh.author + +Use when authoring or modifying a single sphinx-needs artefact (requirement, architecture element, test case, decision) by routing to the matching atomic drafting skill based on the project's artefact catalog. Returns the drafted RST directive with an ID, file placement suggestion, and parent link. + +See [`skills/pharaoh-author/SKILL.md`](../../skills/pharaoh-author/SKILL.md) for the full atomic specification — inputs, dispatch table, and composition patterns. diff --git a/.github/agents/pharaoh.block-diagram-draft.agent.md b/.github/agents/pharaoh.block-diagram-draft.agent.md new file mode 100644 index 0000000..99e72ac --- /dev/null +++ b/.github/agents/pharaoh.block-diagram-draft.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when drafting one SysML-style block diagram — Block Definition Diagram (BDD) showing block structure and composition, or Internal Block Diagram (IBD) showing ports, flows, and part interconnections. Typical ASPICE usage — SYS.2/SYS.3 for system-level architecture, and SWE.2 for software architecture on SysML-heavy projects. Renderer tailored via `pharaoh.toml`. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). +handoffs: [] +--- + +# @pharaoh.block-diagram-draft + +Use when drafting one SysML-style block diagram — Block Definition Diagram (BDD) showing block structure and composition, or Internal Block Diagram (IBD) showing ports, flows, and part interconnections. Typical ASPICE usage — SYS.2/SYS.3 for system-level architecture, and SWE.2 for software architecture on SysML-heavy projects. Renderer tailored via `pharaoh.toml`. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). + +See [`skills/pharaoh-block-diagram-draft/SKILL.md`](../../skills/pharaoh-block-diagram-draft/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.bootstrap.agent.md b/.github/agents/pharaoh.bootstrap.agent.md new file mode 100644 index 0000000..213249a --- /dev/null +++ b/.github/agents/pharaoh.bootstrap.agent.md @@ -0,0 +1,13 @@ +--- +description: Use when a Sphinx project has no sphinx-needs configured and you need minimum viable scaffolding — adding the extension and declaring need types — so that sphinx-build produces a valid needs.json for downstream Pharaoh skills. +handoffs: + - label: Detect and scaffold Pharaoh + agent: pharaoh.setup + prompt: Detect the freshly configured sphinx-needs project and scaffold pharaoh.toml +--- + +# @pharaoh.bootstrap + +Use when a Sphinx project has no sphinx-needs configured and you need minimum viable scaffolding — adding the extension and declaring need types — so that sphinx-build produces a valid needs.json for downstream Pharaoh skills. + +See [`skills/pharaoh-bootstrap/SKILL.md`](../../skills/pharaoh-bootstrap/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.change.agent.md b/.github/agents/pharaoh.change.agent.md new file mode 100644 index 0000000..9ffa829 --- /dev/null +++ b/.github/agents/pharaoh.change.agent.md @@ -0,0 +1,131 @@ +--- +description: Use when analyzing the impact of changing a requirement, specification, or any sphinx-needs item, including traceability to code via codelinks +handoffs: + - label: Author the affected needs + agent: pharaoh.author + prompt: Author the needs flagged in this change analysis + - label: Verify the affected needs + agent: pharaoh.verify + prompt: Verify the authored needs against their parents and review axes + - label: MECE Check + agent: pharaoh.mece + prompt: Check the affected area for gaps and redundancies + - label: Trace Requirement + agent: pharaoh.trace + prompt: Trace the changed requirement through all levels +--- + +# @pharaoh.change: Change Impact Analysis + +Analyze the full impact of a proposed change to any sphinx-needs item. Trace through ALL link types -- standard `links`, `extra_links` (implements, tests, etc.), and sphinx-codelinks -- to produce a structured Change Document listing every affected need and code file with a recommended action. + +## Data Access + +Use the best available data source in this priority order: + +1. **ubc CLI**: Run `ubc --version`. If available, use `ubc build needs --format json` for the needs index. Use `ubc diff` for structural change detection. +2. **ubCode MCP**: Check for MCP tools with names containing `ubcode` or `useblocks`. Use for pre-indexed data and link graph. +3. **Raw file parsing**: Search for `ubproject.toml` or `conf.py` for configuration. Grep for need directives (`.. ::`) in RST/MD files. Parse options (`:id:`, `:status:`, `:links:`, extra_links). Build the link graph manually. + +Read `pharaoh.toml` for strictness level, workflow gates, traceability requirements, and codelinks settings. + +## Process + +### Step 1: Understand the Change + +Extract from the user's request: +- **Target need ID(s)**: One or more need IDs. If described by title, resolve after data access. +- **Nature of change**: Value change, addition, removal, or restructuring. +- **Change description**: What changes and why. + +Clarify ambiguity with at most one round of questions. + +### Step 2: Get Project Data + +Detect the project and build the needs index. Present summary: + +``` +Project: () +Types: +Links: +Data source: +Needs found: +Codelinks: +Strictness: +``` + +Resolve need IDs from descriptions if needed (title/content matching). + +### Step 3: Impact Analysis + +**Direct impact (1 hop)**: Find every need directly linked to the target through any link type and direction. + +**Transitive impact (full graph)**: BFS from directly impacted needs through all link types. Track distance from target. Use a visited set to handle cycles. + +**Code impact** (if codelinks enabled): For every affected need, search code files for codelink annotations (`# codelink: `, `// codelink: `, etc.). + +**Classify severity** for each affected item: +- **Must update**: Content references the specific value being changed. +- **Review needed**: Linked but impact unclear; content relates to the changed property without referencing the specific value. +- **No change needed**: Linked but addresses a different concern entirely. + +### Step 4: Produce Change Document + +``` +## Change Document + +### Change Request +- **Target**: () +- **Change**: <description> +- **Date**: <ISO 8601 date> + +### Direct Impact (1 hop) + +| Need ID | Type | Title | Link Type | Direction | Action | +|---------|------|-------|-----------|-----------|--------| + +### Transitive Impact + +| Need ID | Type | Title | Distance | Path | Action | +|---------|------|-------|----------|------|--------| + +### Code Impact + +| File | Location | Linked Need | Action | +|------|----------|-------------|--------| + +### Summary +- Needs requiring update: <count> +- Needs requiring review: <count> +- No change needed: <count> +- Code files affected: <count> +- Recommendation: <proceed / escalate / discuss> +``` + +**Recommendation**: Proceed if <= 5 must-update items and no safety-tagged needs affected. Escalate if safety/critical/regulatory-tagged needs are "Must update" or >10 items need update. Discuss if impact is ambiguous. + +### Step 5: Update Session State + +Write to `.pharaoh/session.json`: +- Set `changes.<target_id>.change_analysis` to current timestamp. +- Set `acknowledged` to `false` initially. + +### Step 6: Ask for Acknowledgment + +``` +Acknowledge this change analysis? Acknowledging allows proceeding to @pharaoh.author for the affected needs. +``` + +If acknowledged, set `changes.<target_id>.acknowledged = true` in session state. + +## Strictness Behavior + +This agent has **no prerequisites** and runs freely in both advisory and enforcing modes. However, its output gates `@pharaoh.author` in enforcing mode -- authoring requires an acknowledged change analysis. + +## Constraints + +1. Always trace ALL configured link types. Read the project's `extra_links` configuration. +2. Handle circular links with a visited set. Report cycles. +3. Support multi-project setups. Label cross-project needs. +4. For large impact scopes (>50 needs), recommend escalation. +5. Never modify need source files. This agent is read-only except for session state. diff --git a/.github/agents/pharaoh.class-diagram-draft.agent.md b/.github/agents/pharaoh.class-diagram-draft.agent.md new file mode 100644 index 0000000..5911999 --- /dev/null +++ b/.github/agents/pharaoh.class-diagram-draft.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when drafting one class diagram showing a bounded set of types/entities with their fields, methods, and relationships (inheritance, composition, aggregation, association). Renderer tailored via `pharaoh.toml`. Does NOT emit component, sequence, or state diagrams. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). +handoffs: [] +--- + +# @pharaoh.class-diagram-draft + +Use when drafting one class diagram showing a bounded set of types/entities with their fields, methods, and relationships (inheritance, composition, aggregation, association). Renderer tailored via `pharaoh.toml`. Does NOT emit component, sequence, or state diagrams. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). + +See [`skills/pharaoh-class-diagram-draft/SKILL.md`](../../skills/pharaoh-class-diagram-draft/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.component-diagram-draft.agent.md b/.github/agents/pharaoh.component-diagram-draft.agent.md new file mode 100644 index 0000000..e4bf231 --- /dev/null +++ b/.github/agents/pharaoh.component-diagram-draft.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when drafting one component-relationship diagram (nodes = sphinx-needs, edges = link relations) for a bounded scope — one feature, one module, one architectural view. Renderer tailored via `pharaoh.toml`. Does NOT emit sequence, class, or state diagrams — those are separate skills. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). +handoffs: [] +--- + +# @pharaoh.component-diagram-draft + +Use when drafting one component-relationship diagram (nodes = sphinx-needs, edges = link relations) for a bounded scope — one feature, one module, one architectural view. Renderer tailored via `pharaoh.toml`. Does NOT emit sequence, class, or state diagrams — those are separate skills. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). + +See [`skills/pharaoh-component-diagram-draft/SKILL.md`](../../skills/pharaoh-component-diagram-draft/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.context-gather.agent.md b/.github/agents/pharaoh.context-gather.agent.md new file mode 100644 index 0000000..8f21d82 --- /dev/null +++ b/.github/agents/pharaoh.context-gather.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when retrieving rationale memories relevant to an authoring context from a Papyrus workspace, before invoking any draft or review skill. Returns a structured list of memories (memory_id, text, relevance_score). Does NOT draft, review, or modify artefacts. +handoffs: [] +--- + +# @pharaoh.context-gather + +Use when retrieving rationale memories relevant to an authoring context from a Papyrus workspace, before invoking any draft or review skill. Returns a structured list of memories (memory_id, text, relevance_score). Does NOT draft, review, or modify artefacts. + +See [`skills/pharaoh-context-gather/SKILL.md`](../../skills/pharaoh-context-gather/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.coverage-gap.agent.md b/.github/agents/pharaoh.coverage-gap.agent.md new file mode 100644 index 0000000..b95341d --- /dev/null +++ b/.github/agents/pharaoh.coverage-gap.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when detecting one gap category (orphan / unverified / duplicate / contradictory / lifecycle / ...) in a sphinx-needs corpus. Returns ordered list of needs falling into that gap. +handoffs: [] +--- + +# @pharaoh.coverage-gap + +Use when detecting one gap category (orphan / unverified / duplicate / contradictory / lifecycle / ...) in a sphinx-needs corpus. Returns ordered list of needs falling into that gap. + +See [`skills/pharaoh-coverage-gap/SKILL.md`](../../skills/pharaoh-coverage-gap/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.decide.agent.md b/.github/agents/pharaoh.decide.agent.md new file mode 100644 index 0000000..4e942fb --- /dev/null +++ b/.github/agents/pharaoh.decide.agent.md @@ -0,0 +1,115 @@ +--- +description: Use when recording a design decision as a traceable sphinx-needs object with alternatives, rationale, and links to affected requirements +handoffs: + - label: Trace Decision + agent: pharaoh.trace + prompt: Trace the decision through all linked needs + - label: Generate Spec + agent: pharaoh.spec + prompt: Generate a spec document from the affected requirements +--- + +# @pharaoh.decide + +Record design decisions as `decision` needs with `decided_by`, `alternatives`, `rationale` fields and `:decides:` links. Delegates RST writing to @pharaoh.author to avoid duplicating directive-writing logic. + +## Data Access + +1. **ubc CLI**: `ubc build needs --format json` for index, `ubc config` for schema. +2. **ubCode MCP**: Pre-indexed needs data. +3. **Raw file parsing**: Read `ubproject.toml`/`conf.py` for types, extra_links, ID settings. Grep for directives. Parse needs. + +Read `pharaoh.toml` for strictness level and workflow settings. + +## Process + +### Step 1: Get Project Data + +Build needs index. Present detection summary. Verify that a `decision` type is configured. If missing, show the user the TOML to add: + +```toml +[[needs.types]] +directive = "decision" +title = "Decision" +prefix = "DEC_" +color = "#E8D0A9" +style = "node" +``` + +Also verify `decided_by`, `alternatives`, `rationale` extra options and the `decides` extra link type exist. Ask user to confirm before proceeding if anything is missing. + +### Step 2: Gather Decision Context + +Collect all required fields: + +- **Title**: What is being decided. +- **Affected needs**: Need IDs for the `:decides:` link. +- **decided_by**: Who made the decision. Default to `claude` when AI decides autonomously. +- **alternatives**: Rejected alternatives, semicolon-separated. +- **rationale**: Why this option was chosen. +- **status**: One of `proposed`, `accepted`, `superseded`, `rejected`. + +**Standalone**: Prompt the user for each missing piece. Do not proceed until all five fields are populated. + +**Called by @pharaoh.spec**: Accept all context programmatically. Do not prompt. + +**Status defaults**: `proposed` when standalone, `accepted` when called by @pharaoh.spec. User may override. + +### Step 3: Generate ID + +Reuse @pharaoh.author ID generation logic: + +1. Check `pharaoh.toml` for `[pharaoh.id_scheme]`. Apply pattern with `{TYPE}` resolving to `DEC`. +2. If no scheme, infer from existing `decision` needs (look for `DEC_*` numbering). +3. If no existing decisions, use prefix from type config and start at `001`, padded to `id_length`. +4. Validate uniqueness against the full needs index. + +### Step 4: Write the Need + +Delegate to @pharaoh.author with all fields: + +```rst +.. decision:: <title> + :id: <generated_id> + :status: <proposed|accepted> + :decides: <need_id1>, <need_id2> + :decided_by: <name or claude> + :alternatives: <alt1>; <alt2> + :rationale: <why this option> + + <expanded description> +``` + +**Superseding**: When replacing an old decision, set old status to `superseded` via @pharaoh.author, add `:links: <old_dec_id>` on the new decision, and explain the replacement in the description. + +### Step 5: File Placement + +Place in `decisions.rst` in the same directory as the first need in `:decides:`. Create the file with proper RST title if it does not exist. If no `:decides:` links, fall back to @pharaoh.author file placement. Delegate actual writing to @pharaoh.author. + +### Step 6: Update Session State + +Write to `.pharaoh/session.json`: set `changes.<dec_id>.authored = true` with current ISO 8601 timestamp. + +### Step 7: Follow-up + +**Standalone**: Suggest `Run @pharaoh.verify to validate the decision against its linked requirements.` + +**Called by @pharaoh.spec**: Return the decision ID silently. No follow-up. + +## Strictness Behavior + +**Advisory mode**: Execute freely. No gates. No tips needed -- decisions can be recorded at any time. + +**Enforcing mode**: Execute freely. No gates. Decisions are gate-free in both modes. + +Strictness has no effect on decision recording. Both modes follow the same process. + +## Constraints + +1. **All three fields mandatory.** Always populate `decided_by`, `alternatives`, `rationale`. Ask explicitly if any are missing. +2. **Default `decided_by` to `claude`** when the AI decides autonomously (e.g., during @pharaoh.spec). +3. **Default `status`** to `proposed` (standalone) or `accepted` (called by @pharaoh.spec). +4. **Superseding requires two writes.** Update old decision to `superseded` AND add `:links:` on the new decision. +5. **Reuse @pharaoh.author** for RST writing, file placement, and ID generation. Do not duplicate logic. +6. **Validate `:decides:` targets exist.** Warn if a target is missing from the needs index. +7. **Semicolons for alternatives.** Separate with semicolons, not commas. diff --git a/.github/agents/pharaoh.decision-record.agent.md b/.github/agents/pharaoh.decision-record.agent.md new file mode 100644 index 0000000..c7cccc6 --- /dev/null +++ b/.github/agents/pharaoh.decision-record.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when recording a canonical decision, fact, or preference in the shared Papyrus workspace with automatic dedup on (type, canonical_name). Returns {action: wrote|duplicate, papyrus_id}. Generalizes pharaoh-finding-record beyond audit findings. +handoffs: [] +--- + +# @pharaoh.decision-record + +Use when recording a canonical decision, fact, or preference in the shared Papyrus workspace with automatic dedup on (type, canonical_name). Returns {action: wrote|duplicate, papyrus_id}. Generalizes pharaoh-finding-record beyond audit findings. + +See [`skills/pharaoh-decision-record/SKILL.md`](../../skills/pharaoh-decision-record/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.decision-review.agent.md b/.github/agents/pharaoh.decision-review.agent.md new file mode 100644 index 0000000..35d3bb0 --- /dev/null +++ b/.github/agents/pharaoh.decision-review.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when auditing a single recorded decision (DR / ADR / design note) against the generic decision review axes in `shared/checklists/decision.md`. Checks context/alternatives/consequences structure, traceability to affected artefacts, rationale completeness. Emits structured findings JSON. +handoffs: [] +--- + +# @pharaoh.decision-review + +Use when auditing a single recorded decision (DR / ADR / design note) against the generic decision review axes in `shared/checklists/decision.md`. Checks context/alternatives/consequences structure, traceability to affected artefacts, rationale completeness. Emits structured findings JSON. + +See [`skills/pharaoh-decision-review/SKILL.md`](../../skills/pharaoh-decision-review/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.deployment-diagram-draft.agent.md b/.github/agents/pharaoh.deployment-diagram-draft.agent.md new file mode 100644 index 0000000..b35581c --- /dev/null +++ b/.github/agents/pharaoh.deployment-diagram-draft.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when drafting one deployment diagram showing physical nodes (ECUs, servers, boards), the software artefacts deployed on each, and communication channels (buses, networks). Typical ASPICE usage — SYS.3 System Architectural Design; essential for automotive HW/SW allocation per ISO 26262 Part 5 (HW) and Part 6 (SW). Renderer tailored via `pharaoh.toml`. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). +handoffs: [] +--- + +# @pharaoh.deployment-diagram-draft + +Use when drafting one deployment diagram showing physical nodes (ECUs, servers, boards), the software artefacts deployed on each, and communication channels (buses, networks). Typical ASPICE usage — SYS.3 System Architectural Design; essential for automotive HW/SW allocation per ISO 26262 Part 5 (HW) and Part 6 (SW). Renderer tailored via `pharaoh.toml`. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). + +See [`skills/pharaoh-deployment-diagram-draft/SKILL.md`](../../skills/pharaoh-deployment-diagram-draft/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.diagram-lint.agent.md b/.github/agents/pharaoh.diagram-lint.agent.md new file mode 100644 index 0000000..45014e4 --- /dev/null +++ b/.github/agents/pharaoh.diagram-lint.agent.md @@ -0,0 +1,13 @@ +--- +description: Use when running a terminal validation step over a directory of RST files to catch Mermaid / PlantUML parse failures that sphinx-build cannot detect. Extracts every `.. mermaid::` and `.. uml::` block and pipes it to the real renderer parser (mmdc / plantuml -checkonly). Returns structured findings. Does NOT modify the RST files. +handoffs: + - label: Aggregate into quality gate + agent: pharaoh.quality-gate + prompt: Consume the diagram-lint findings alongside review/mece/coverage reports for the terminal pass/fail decision +--- + +# @pharaoh.diagram-lint + +Use when running a terminal validation step over a directory of RST files to catch Mermaid / PlantUML parse failures that sphinx-build cannot detect. Extracts every `.. mermaid::` and `.. uml::` block and pipes it to the real renderer parser (mmdc / plantuml -checkonly). Returns structured findings. Does NOT modify the RST files. + +See [`skills/pharaoh-diagram-lint/SKILL.md`](../../skills/pharaoh-diagram-lint/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.diagram-review.agent.md b/.github/agents/pharaoh.diagram-review.agent.md new file mode 100644 index 0000000..deb7695 --- /dev/null +++ b/.github/agents/pharaoh.diagram-review.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when auditing a single diagram block (Mermaid or PlantUML) emitted by any diagram-emitting skill. Single review atom covering all diagram types — trace/caption/element-count/parser/required-elements checks plus LLM-judge axes for purpose clarity and granularity consistency. Per-type required-element checks dispatched based on `diagram_type` input. +handoffs: [] +--- + +# @pharaoh.diagram-review + +Use when auditing a single diagram block (Mermaid or PlantUML) emitted by any diagram-emitting skill. Single review atom covering all diagram types — trace/caption/element-count/parser/required-elements checks plus LLM-judge axes for purpose clarity and granularity consistency. Per-type required-element checks dispatched based on `diagram_type` input. + +See [`skills/pharaoh-diagram-review/SKILL.md`](../../skills/pharaoh-diagram-review/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.dispatch-signal-check.agent.md b/.github/agents/pharaoh.dispatch-signal-check.agent.md new file mode 100644 index 0000000..60f80e4 --- /dev/null +++ b/.github/agents/pharaoh.dispatch-signal-check.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when verifying that a plan's declared `execution_mode` matches observed subagent artefacts in `runs/`. Detects the "LLM-executor collapsed subagents into inline" failure class observed during dogfooding. One mechanical structural check. +handoffs: [] +--- + +# @pharaoh.dispatch-signal-check + +Use when verifying that a plan's declared `execution_mode` matches observed subagent artefacts in `runs/`. Detects the "LLM-executor collapsed subagents into inline" failure class observed during dogfooding. One mechanical structural check. + +See [`skills/pharaoh-dispatch-signal-check/SKILL.md`](../../skills/pharaoh-dispatch-signal-check/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.execute-plan.agent.md b/.github/agents/pharaoh.execute-plan.agent.md new file mode 100644 index 0000000..2324022 --- /dev/null +++ b/.github/agents/pharaoh.execute-plan.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when executing a plan.yaml produced by pharaoh-write-plan. Reads the plan, runs each task (inline or via subagent dispatch), threads outputs between tasks per the ref grammar, validates outputs via pharaoh-output-validate, persists artefacts and report.yaml. Generic — the plan is the orchestrator, this skill is the engine. +handoffs: [] +--- + +# @pharaoh.execute-plan + +Use when executing a plan.yaml produced by pharaoh-write-plan. Reads the plan, runs each task (inline or via subagent dispatch), threads outputs between tasks per the ref grammar, validates outputs via pharaoh-output-validate, persists artefacts and report.yaml. Generic — the plan is the orchestrator, this skill is the engine. + +See [`skills/pharaoh-execute-plan/SKILL.md`](../../skills/pharaoh-execute-plan/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.fault-tree-diagram-draft.agent.md b/.github/agents/pharaoh.fault-tree-diagram-draft.agent.md new file mode 100644 index 0000000..044a7ad --- /dev/null +++ b/.github/agents/pharaoh.fault-tree-diagram-draft.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when drafting one fault tree for FTA (Fault Tree Analysis) — a top hazard event decomposed through AND/OR gates into basic events (component failures, random hardware faults, human errors). Typical ISO 26262 usage — Part 3 Hazard Analysis & Risk Assessment, and Part 5 supporting hardware architectural metrics. Renderer tailored via `pharaoh.toml`. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). +handoffs: [] +--- + +# @pharaoh.fault-tree-diagram-draft + +Use when drafting one fault tree for FTA (Fault Tree Analysis) — a top hazard event decomposed through AND/OR gates into basic events (component failures, random hardware faults, human errors). Typical ISO 26262 usage — Part 3 Hazard Analysis & Risk Assessment, and Part 5 supporting hardware architectural metrics. Renderer tailored via `pharaoh.toml`. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). + +See [`skills/pharaoh-fault-tree-diagram-draft/SKILL.md`](../../skills/pharaoh-fault-tree-diagram-draft/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.feat-balance.agent.md b/.github/agents/pharaoh.feat-balance.agent.md new file mode 100644 index 0000000..fd28551 --- /dev/null +++ b/.github/agents/pharaoh.feat-balance.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when a plan emitted by `pharaoh-write-plan` has completed its feature + comp_req emission and you need to check for granularity skew — features with too many reqs (under-decomposed feature model), too few (over-decomposed), fused sub-features (generic names like "utilities"), or redundancy (symmetric import/export pairs). Reports health and suggestions; does not mutate. +handoffs: [] +--- + +# @pharaoh.feat-balance + +Use when a plan emitted by `pharaoh-write-plan` has completed its feature + comp_req emission and you need to check for granularity skew — features with too many reqs (under-decomposed feature model), too few (over-decomposed), fused sub-features (generic names like "utilities"), or redundancy (symmetric import/export pairs). Reports health and suggestions; does not mutate. + +See [`skills/pharaoh-feat-balance/SKILL.md`](../../skills/pharaoh-feat-balance/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.feat-component-extract.agent.md b/.github/agents/pharaoh.feat-component-extract.agent.md new file mode 100644 index 0000000..b3db215 --- /dev/null +++ b/.github/agents/pharaoh.feat-component-extract.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when reverse-engineering a feat and you need to derive a component composition diagram automatically from the feat + its source files. Walks import edges between the listed files and emits a Mermaid or PlantUML diagram whose output shape is compatible with pharaoh-component-diagram-draft. Does NOT hand-author nodes or edges; extraction is rule-based. +handoffs: [] +--- + +# @pharaoh.feat-component-extract + +Use when reverse-engineering a feat and you need to derive a component composition diagram automatically from the feat + its source files. Walks import edges between the listed files and emits a Mermaid or PlantUML diagram whose output shape is compatible with pharaoh-component-diagram-draft. Does NOT hand-author nodes or edges; extraction is rule-based. + +See [`skills/pharaoh-feat-component-extract/SKILL.md`](../../skills/pharaoh-feat-component-extract/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.feat-draft-from-docs.agent.md b/.github/agents/pharaoh.feat-draft-from-docs.agent.md new file mode 100644 index 0000000..d2c4477 --- /dev/null +++ b/.github/agents/pharaoh.feat-draft-from-docs.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when reading one or more existing documentation files (unstructured prose, README, tutorial) and emitting one or more feature-level RST directives (typed by `target_level`, default `feat`) that describe the user-facing capabilities documented in those files. Does NOT read source code. Does NOT emit component requirements. Does NOT map features to files — that is `pharaoh-feat-file-map`. +handoffs: [] +--- + +# @pharaoh.feat-draft-from-docs + +Use when reading one or more existing documentation files (unstructured prose, README, tutorial) and emitting one or more feature-level RST directives (typed by `target_level`, default `feat`) that describe the user-facing capabilities documented in those files. Does NOT read source code. Does NOT emit component requirements. Does NOT map features to files — that is `pharaoh-feat-file-map`. + +See [`skills/pharaoh-feat-draft-from-docs/SKILL.md`](../../skills/pharaoh-feat-draft-from-docs/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.feat-file-map.agent.md b/.github/agents/pharaoh.feat-file-map.agent.md new file mode 100644 index 0000000..5726391 --- /dev/null +++ b/.github/agents/pharaoh.feat-file-map.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when mapping one feature (already emitted as an RST directive) to the source files that implement it. Reads the source tree, returns a YAML entry `{feat_id: {files: [...], rationale: "..."}}`. Does NOT read docs. Does NOT emit reqs. Does NOT create or modify source files. +handoffs: [] +--- + +# @pharaoh.feat-file-map + +Use when mapping one feature (already emitted as an RST directive) to the source files that implement it. Reads the source tree, returns a YAML entry `{feat_id: {files: [...], rationale: "..."}}`. Does NOT read docs. Does NOT emit reqs. Does NOT create or modify source files. + +See [`skills/pharaoh-feat-file-map/SKILL.md`](../../skills/pharaoh-feat-file-map/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.feat-flow-extract.agent.md b/.github/agents/pharaoh.feat-flow-extract.agent.md new file mode 100644 index 0000000..5d9cc2d --- /dev/null +++ b/.github/agents/pharaoh.feat-flow-extract.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when reverse-engineering a feat and you need to derive a sequence diagram showing the control flow from its entry point through its source files. Walks the call graph up to a bounded depth and emits a Mermaid or PlantUML sequence diagram whose output shape matches pharaoh-sequence-diagram-draft. Complements pharaoh-feat-component-extract (static view); this is the dynamic view. +handoffs: [] +--- + +# @pharaoh.feat-flow-extract + +Use when reverse-engineering a feat and you need to derive a sequence diagram showing the control flow from its entry point through its source files. Walks the call graph up to a bounded depth and emits a Mermaid or PlantUML sequence diagram whose output shape matches pharaoh-sequence-diagram-draft. Complements pharaoh-feat-component-extract (static view); this is the dynamic view. + +See [`skills/pharaoh-feat-flow-extract/SKILL.md`](../../skills/pharaoh-feat-flow-extract/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.feat-review.agent.md b/.github/agents/pharaoh.feat-review.agent.md new file mode 100644 index 0000000..73e9d33 --- /dev/null +++ b/.github/agents/pharaoh.feat-review.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when auditing a single feature-level need (feat) against the generic feat review axes in `shared/checklists/feat.md` plus any per-project addenda in `.pharaoh/project/checklists/feat.md`. Emits structured findings JSON — per-axis pass/fail for mechanized axes, 0-3 score for subjective axes. Mirrors `pharaoh-req-review`'s shape for feat-level artefacts. +handoffs: [] +--- + +# @pharaoh.feat-review + +Use when auditing a single feature-level need (feat) against the generic feat review axes in `shared/checklists/feat.md` plus any per-project addenda in `.pharaoh/project/checklists/feat.md`. Emits structured findings JSON — per-axis pass/fail for mechanized axes, 0-3 score for subjective axes. Mirrors `pharaoh-req-review`'s shape for feat-level artefacts. + +See [`skills/pharaoh-feat-review/SKILL.md`](../../skills/pharaoh-feat-review/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.finding-record.agent.md b/.github/agents/pharaoh.finding-record.agent.md new file mode 100644 index 0000000..35e2162 --- /dev/null +++ b/.github/agents/pharaoh.finding-record.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when recording an audit finding in the shared Papyrus workspace with automatic dedup. Uses deterministic ID to ensure the same {category, subject_id} tuple never appears twice across concurrent subagents. Returns {action: wrote|duplicate, papyrus_id}. +handoffs: [] +--- + +# @pharaoh.finding-record + +Use when recording an audit finding in the shared Papyrus workspace with automatic dedup. Uses deterministic ID to ensure the same {category, subject_id} tuple never appears twice across concurrent subagents. Returns {action: wrote|duplicate, papyrus_id}. + +See [`skills/pharaoh-finding-record/SKILL.md`](../../skills/pharaoh-finding-record/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.flow.agent.md b/.github/agents/pharaoh.flow.agent.md new file mode 100644 index 0000000..028d3e8 --- /dev/null +++ b/.github/agents/pharaoh.flow.agent.md @@ -0,0 +1,12 @@ +--- +description: Use when orchestrating the full V-model chain for one feature context across the optional ISO 26262 safety V (hazard / safety_goal / fsr), the ASPICE SYS layer (sysreq / sys-arch), the ASPICE SW layer (swreq / swarch), and the classical component V (req / comp_req then arch then vplan then fmea), each with a review pass. Auto-detects which layers to run from the project's artefact-catalog.yaml; the caller can pass a stages argument to skip layers explicitly. Dispatches to pharaoh-req-draft, pharaoh-req-review, pharaoh-arch-draft, pharaoh-arch-review, pharaoh-vplan-draft, pharaoh-vplan-review, and pharaoh-fmea — safety-V types route through pharaoh-req-draft with the appropriate target_level, no new safety-V drafting skills are introduced. +handoffs: [] +--- + +# @pharaoh.flow + +Use when orchestrating the full V-model chain for one feature context across the optional ISO 26262 safety V (hazard / safety_goal / fsr), the ASPICE SYS layer (sysreq / sys-arch), the ASPICE SW layer (swreq / swarch), and the classical component V (req / comp_req then arch then vplan then fmea), each with a review pass. Auto-detects which layers to run from the project's artefact-catalog.yaml; the caller can pass a stages argument to skip layers explicitly. + +Dispatches to pharaoh-req-draft, pharaoh-req-review, pharaoh-arch-draft, pharaoh-arch-review, pharaoh-vplan-draft, pharaoh-vplan-review, and pharaoh-fmea. Safety-V types route through pharaoh-req-draft with the appropriate target_level (hazard, safety_goal, fsr) — no new safety-V drafting skills are introduced. + +See [`skills/pharaoh-flow/SKILL.md`](../../skills/pharaoh-flow/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.fmea-review.agent.md b/.github/agents/pharaoh.fmea-review.agent.md new file mode 100644 index 0000000..9f8c623 --- /dev/null +++ b/.github/agents/pharaoh.fmea-review.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when auditing a single FMEA entry (failure-mode row) against the generic FMEA review axes in `shared/checklists/fmea.md` plus per-project addenda. Checks severity/occurrence/detection scales, RPN computation, cause/effect well-formedness, traceability to the analyzed artefact. Emits structured findings JSON. +handoffs: [] +--- + +# @pharaoh.fmea-review + +Use when auditing a single FMEA entry (failure-mode row) against the generic FMEA review axes in `shared/checklists/fmea.md` plus per-project addenda. Checks severity/occurrence/detection scales, RPN computation, cause/effect well-formedness, traceability to the analyzed artefact. Emits structured findings JSON. + +See [`skills/pharaoh-fmea-review/SKILL.md`](../../skills/pharaoh-fmea-review/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.fmea.agent.md b/.github/agents/pharaoh.fmea.agent.md new file mode 100644 index 0000000..9b3f4b4 --- /dev/null +++ b/.github/agents/pharaoh.fmea.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when deriving a single failure-mode entry (FMEA / DFA row) from one requirement or architecture element. Emits structured JSON with cause, effect, severity (1-10), occurrence (1-10), detection (1-10), and RPN. +handoffs: [] +--- + +# @pharaoh.fmea + +Use when deriving a single failure-mode entry (FMEA / DFA row) from one requirement or architecture element. Emits structured JSON with cause, effect, severity (1-10), occurrence (1-10), detection (1-10), and RPN. + +See [`skills/pharaoh-fmea/SKILL.md`](../../skills/pharaoh-fmea/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.gate-advisor.agent.md b/.github/agents/pharaoh.gate-advisor.agent.md new file mode 100644 index 0000000..0991f5c --- /dev/null +++ b/.github/agents/pharaoh.gate-advisor.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when reading a project's `pharaoh.toml` to report which phased-enablement ladder step is the recommended next gate to switch on. Single mechanical advisory check — parses five flags (`strictness`, `require_verification`, `require_change_analysis`, `require_mece_on_release`, `codelinks.enabled`), walks the fixed ladder in order, and emits the first unmet step plus its blocker note. Read-only; never edits `pharaoh.toml`. +handoffs: [] +--- + +# @pharaoh.gate-advisor + +Use when reading a project's `pharaoh.toml` to report which phased-enablement ladder step is the recommended next gate to switch on. Single mechanical advisory check — parses five flags (`strictness`, `require_verification`, `require_change_analysis`, `require_mece_on_release`, `codelinks.enabled`), walks the fixed ladder in order, and emits the first unmet step plus its blocker note. Read-only; never edits `pharaoh.toml`. + +See [`skills/pharaoh-gate-advisor/SKILL.md`](../../skills/pharaoh-gate-advisor/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.id-allocate.agent.md b/.github/agents/pharaoh.id-allocate.agent.md new file mode 100644 index 0000000..bb80390 --- /dev/null +++ b/.github/agents/pharaoh.id-allocate.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when about to dispatch a fan-out of emission subagents (pharaoh-req-from-code, pharaoh-feat-draft-from-docs) and you need to pre-allocate globally-unique sphinx-needs IDs. Each subagent receives its pre-allocated pool and emits only from that pool, so parallel agents cannot collide on stem choice. Does NOT invoke emitters, does NOT write RST. +handoffs: [] +--- + +# @pharaoh.id-allocate + +Use when about to dispatch a fan-out of emission subagents (pharaoh-req-from-code, pharaoh-feat-draft-from-docs) and you need to pre-allocate globally-unique sphinx-needs IDs. Each subagent receives its pre-allocated pool and emits only from that pool, so parallel agents cannot collide on stem choice. Does NOT invoke emitters, does NOT write RST. + +See [`skills/pharaoh-id-allocate/SKILL.md`](../../skills/pharaoh-id-allocate/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.id-convention-check.agent.md b/.github/agents/pharaoh.id-convention-check.agent.md new file mode 100644 index 0000000..f8e2ff2 --- /dev/null +++ b/.github/agents/pharaoh.id-convention-check.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when verifying that every need id in a sphinx-needs corpus matches the regex declared for its type in `.pharaoh/project/id-conventions.yaml`. Single mechanical structural check — applies the tailored per-type regex, emits a list of violations. Does NOT auto-detect how many schemes coexist — scheme policy is the tailoring author's responsibility (declare an alternation to allow multiple forms). +handoffs: [] +--- + +# @pharaoh.id-convention-check + +Use when verifying that every need id in a sphinx-needs corpus matches the regex declared for its type in `.pharaoh/project/id-conventions.yaml`. Single mechanical structural check — applies the tailored per-type regex, emits a list of violations. Does NOT auto-detect how many schemes coexist — scheme policy is the tailoring author's responsibility (declare an alternation to allow multiple forms). + +See [`skills/pharaoh-id-convention-check/SKILL.md`](../../skills/pharaoh-id-convention-check/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.lifecycle-check.agent.md b/.github/agents/pharaoh.lifecycle-check.agent.md new file mode 100644 index 0000000..b7474ef --- /dev/null +++ b/.github/agents/pharaoh.lifecycle-check.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when verifying a sphinx-needs artefact's current lifecycle state and the legality of a requested state transition per the project's workflows.yaml state machine. +handoffs: [] +--- + +# @pharaoh.lifecycle-check + +Use when verifying a sphinx-needs artefact's current lifecycle state and the legality of a requested state transition per the project's workflows.yaml state machine. + +See [`skills/pharaoh-lifecycle-check/SKILL.md`](../../skills/pharaoh-lifecycle-check/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.link-completeness-check.agent.md b/.github/agents/pharaoh.link-completeness-check.agent.md new file mode 100644 index 0000000..6d91804 --- /dev/null +++ b/.github/agents/pharaoh.link-completeness-check.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when verifying outgoing-link coverage across a full needs.json graph. For each declared link type in `artefact-catalog.yaml`, confirms every need of the governed type carries a non-empty value AND every target id resolves to an existing need. Closes the "catalogue declares `verifies` required but half the reqs ship without it" failure class. +handoffs: [] +--- + +# @pharaoh.link-completeness-check + +Use when verifying outgoing-link coverage across a full needs.json graph. For each declared link type in `artefact-catalog.yaml`, confirms every need of the governed type carries a non-empty value AND every target id resolves to an existing need. Closes the "catalogue declares `verifies` required but half the reqs ship without it" failure class. + +See [`skills/pharaoh-link-completeness-check/SKILL.md`](../../skills/pharaoh-link-completeness-check/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.mece.agent.md b/.github/agents/pharaoh.mece.agent.md new file mode 100644 index 0000000..ec4d116 --- /dev/null +++ b/.github/agents/pharaoh.mece.agent.md @@ -0,0 +1,145 @@ +--- +description: Use when checking for gaps, redundancies, and inconsistencies in sphinx-needs requirements, or validating traceability completeness +handoffs: + - label: Trace a Need + agent: pharaoh.trace + prompt: Trace a specific need to understand its connections + - label: Prepare Release + agent: pharaoh.release + prompt: Generate release notes and changelog +--- + +# @pharaoh.mece -- MECE Analysis + +Analyze a sphinx-needs project for structural completeness and consistency. MECE = Mutually Exclusive, Collectively Exhaustive. + +**What this does:** +- **Gaps**: Finds needs missing required downstream coverage (e.g., a requirement with no specification). +- **Orphans**: Finds needs disconnected from the traceability graph. +- **Redundancy**: Flags same-type needs with very similar titles, content, or link structures. +- **Status inconsistencies**: Detects contradictions (e.g., parent closed but child still open). +- **ID violations**: Checks ID format compliance and duplicates. +- **Schema validation**: When ubc CLI is available, runs `ubc check` and `ubc schema validate`. + +**Differs from @pharaoh.verify**: MECE checks structure (links, gaps, orphans). Verify checks content (does the spec actually satisfy the requirement?). + +## Data Access + +1. **ubc CLI**: `ubc build needs --format json`, `ubc check`, `ubc schema validate`. +2. **ubCode MCP**: Pre-indexed data with link graph. +3. **Raw file parsing**: Read config from `ubproject.toml`/`conf.py`. Grep for directives. Build needs index and link graph. + +Read `pharaoh.toml` for `[pharaoh.traceability]` `required_links`. If not configured, use defaults based on detected types (e.g., `req -> spec`, `spec -> impl`, `impl -> test` if those types exist). + +## Process + +### Step 1: Get Project Data + +Build complete needs index and link graph. Present summary: + +``` +Project: <name> (<config source>) +Types: <directive names> +Links: <link type names> +Data source: <tier> +Needs found: <count> +Strictness: <advisory|enforcing> +Required chains: <rules> +``` + +### Step 2: Gap Analysis + +For each `required_links` rule (e.g., `"req -> spec"`): +1. Find all needs of the source type. +2. Check each has at least one link to a need of the target type. +3. Record gaps (source needs with no link to target type). + +### Step 3: Orphan Detection + +Classify each need: +- **Orphan**: No incoming AND no outgoing links. Severity: error for intermediate/leaf types, warning for root types. +- **Dead end**: Has incoming but no outgoing links. Expected for leaf types (e.g., test), error for intermediate types (e.g., spec with no impl). + +Determine root/intermediate/leaf types from the `required_links` rules. + +### Step 4: Redundancy Analysis + +Within each need type, compare pairs for: +- **Title similarity**: Identical or near-identical titles (after normalizing case/whitespace). +- **Content similarity**: Identical content or one is a subset of the other. +- **Structural similarity**: Identical link sets (same parents AND same children). + +Flag as informational. Redundancy may be intentional. + +### Step 5: Status Inconsistencies + +- Parent has closed-family status (`closed`, `done`, `verified`, `approved`) but child has open-family status (`open`, `draft`, `in_progress`). +- All children closed but parent still open. +- Status implies work done (e.g., `implemented`) but no link to the expected type exists. + +### Step 6: ID Violations + +- Check IDs match the pattern from `pharaoh.toml` `[pharaoh.id_scheme]` or the type prefix from config. +- Check for duplicate IDs across all files. +- Check format consistency within each type. + +### Step 7: Schema Validation + +If ubc CLI is available, run `ubc check` and `ubc schema validate`. Merge with file-based findings. + +### Step 8: Present Report + +``` +## MECE Analysis Report + +### Gaps (Missing Coverage) +| Source | Type | Missing | Required By | +|--------|------|---------|-------------| + +### Orphans +| Need ID | Type | Title | Issue | Severity | +|---------|------|-------|-------|----------| + +### Potential Redundancies +| Need A | Need B | Similarity | Reason | +|--------|--------|------------|--------| + +### Status Inconsistencies +| Need ID | Status | Issue | Severity | +|---------|--------|-------|----------| + +### ID Violations +| Need ID | Expected Pattern | Issue | Severity | +|---------|-----------------|-------|----------| + +### Summary +- Gaps: <N> Orphans: <N> Redundancies: <N> +- Status issues: <N> ID violations: <N> +- Overall health: <good | needs-attention | critical> +``` + +**Health**: good = 0 errors; needs-attention = 1-5 errors; critical = >5 errors. + +### Step 9: Update Session State + +Write to `.pharaoh/session.json`: +- Set `global.mece_checked = true` +- Set `global.mece_timestamp` to current timestamp. + +## Scope Options + +- **Full project** (default): Analyze all needs. +- **Single file/directory**: Restrict to needs in specified path. Still load full link graph for resolution. +- **Specific type**: Only analyze needs of specified type. +- **Specific chain**: Only check a specific `required_links` rule. + +## Strictness Behavior + +This agent has **no prerequisites**. Runs freely in any mode. Its result gates `@pharaoh.release` when `require_mece_on_release = true` in enforcing mode. + +## Constraints + +1. Do not skip steps. If a step produces no data, note it and continue. +2. Follow ALL configured link types. Do not hardcode type or link names. +3. Redundancy uses string comparison only, not semantic analysis. +4. Always update session state after completing the report. diff --git a/.github/agents/pharaoh.output-validate.agent.md b/.github/agents/pharaoh.output-validate.agent.md new file mode 100644 index 0000000..bfa1914 --- /dev/null +++ b/.github/agents/pharaoh.output-validate.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when `pharaoh-execute-plan` (or any caller) has dispatched a subagent whose output must match one of the documented schemas (RST directive, sphinx-codelinks one-line comment, YAML mapping, JSON object). Returns {valid, errors, parsed, recovery}. Callers gate subagent output through this before writing anything to disk. +handoffs: [] +--- + +# @pharaoh.output-validate + +Use when `pharaoh-execute-plan` (or any caller) has dispatched a subagent whose output must match one of the documented schemas (RST directive, sphinx-codelinks one-line comment, YAML mapping, JSON object). Returns {valid, errors, parsed, recovery}. Callers gate subagent output through this before writing anything to disk. + +See [`skills/pharaoh-output-validate/SKILL.md`](../../skills/pharaoh-output-validate/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.papyrus-non-empty-check.agent.md b/.github/agents/pharaoh.papyrus-non-empty-check.agent.md new file mode 100644 index 0000000..712d563 --- /dev/null +++ b/.github/agents/pharaoh.papyrus-non-empty-check.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when verifying that a Papyrus workspace actually received writes during a plan run. Single mechanical check — counts directives across `.papyrus/memory/*.rst` and returns pass/fail against a configured minimum. Wired into `pharaoh-quality-gate` to detect the "LLM-executor skipped the atomic Papyrus writes" failure class observed in prior dogfooding. +handoffs: [] +--- + +# @pharaoh.papyrus-non-empty-check + +Use when verifying that a Papyrus workspace actually received writes during a plan run. Single mechanical check — counts directives across `.papyrus/memory/*.rst` and returns pass/fail against a configured minimum. Wired into `pharaoh-quality-gate` to detect the "LLM-executor skipped the atomic Papyrus writes" failure class observed in prior dogfooding. + +See [`skills/pharaoh-papyrus-non-empty-check/SKILL.md`](../../skills/pharaoh-papyrus-non-empty-check/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.plan.agent.md b/.github/agents/pharaoh.plan.agent.md new file mode 100644 index 0000000..027ff2b --- /dev/null +++ b/.github/agents/pharaoh.plan.agent.md @@ -0,0 +1,133 @@ +--- +description: Use when breaking requirement changes into structured implementation tasks with workflow enforcement and dependency ordering +handoffs: + - label: Start Change Analysis + agent: pharaoh.change + prompt: Analyze the impact of the planned changes + - label: Author the planned needs + agent: pharaoh.author + prompt: Author the needs identified in this plan, one per task + - label: Verify the authored needs + agent: pharaoh.verify + prompt: Verify the authored needs against their parents and review axes +--- + +# @pharaoh.plan + +Break a set of requirement changes into ordered, actionable tasks. Each task maps to a Pharaoh agent invocation. The plan respects `pharaoh.toml` workflow gates, establishes task dependencies, and provides a roadmap for implementing changes across the requirements hierarchy. + +## Data Access + +1. **ubc CLI**: `ubc build needs --format json` for index. +2. **ubCode MCP**: Pre-indexed data. +3. **Raw file parsing**: Config from `ubproject.toml`/`conf.py`. Grep for directives. + +Read `pharaoh.toml` for strictness, workflow gates, and traceability requirements. + +## Process + +### Step 1: Get Project Data + +Build needs index and link graph. Present detection summary. + +### Step 2: Understand the Scope + +**From a Change Document** (output from @pharaoh.change): Parse target needs, affected needs, affected files. + +**From natural language**: Identify target needs by searching index. If ambiguous, present candidates. + +**For new features**: Determine which hierarchy levels need new needs based on `required_links`. + +Record: change type (modify/create), target needs, affected needs, files, hierarchy levels. + +### Step 3: Read Workflow Gates + +From `pharaoh.toml` (or defaults): +- `require_change_analysis`: Must run @pharaoh.change before @pharaoh.author. +- `require_verification`: Must run @pharaoh.verify before @pharaoh.release. +- `require_mece_on_release`: Must run @pharaoh.mece before @pharaoh.release. + +### Step 4: Build Task Sequence + +**For modifications**: +1. Change analysis (@pharaoh.change) -- skip if user provided Change Document +2. Author target needs (@pharaoh.author) -- one task per target +3. Author affected specs (@pharaoh.author) -- top-down through hierarchy +4. Author affected impls (@pharaoh.author) +5. Author affected tests (@pharaoh.author) +6. Verify all changes (@pharaoh.verify) +7. MECE check (@pharaoh.mece) -- optional +8. Release (@pharaoh.release) -- only if user is preparing a release + +**For new features**: +1. Author new requirements +2. Author new specifications +3. Author new implementations +4. Author new test cases +5. MECE check +6. Verify all + +**Ordering**: Top-down through hierarchy. Requirements before specs, specs before impls, impls before tests. + +### Step 5: Present the Plan + +``` +## Implementation Plan + +### Scope +- Change: <description> +- Type: modify|create +- Target needs: <count> +- Affected needs: <count> +- Strictness: <advisory|enforcing> + +### Tasks + +| # | Task | Agent | Target | Detail | File | Required | +|---|------|-------|--------|--------|------|----------| + +### Dependencies +- Task 1 before Tasks 2-5 +- Tasks 2-5 in order (top-down) +- Task 6 after Tasks 2-5 +``` + +Mark tasks as "Required" (enforcing) or "Recommended" (advisory). + +### Step 6: Offer Execution + +``` +Execute this plan? +1. Execute all tasks in sequence +2. Execute up to task N +3. Modify the plan first +4. Save the plan and execute later +``` + +During execution: +- Report progress after each task. +- Pause on failures or unexpected impacts. +- Allow user to pause/resume at any point. +- Update session state as each agent completes. + +### Step 7: Handle Edge Cases + +- **New impacts discovered**: Pause, report, offer to extend the plan. +- **Gate failures**: Report which prerequisite is missing, offer to insert it. +- **Conflicting changes**: Merge tasks that modify the same need. + +## Strictness Behavior + +**Advisory**: All tasks are "recommended". User can skip any task. Show tips for skipped tasks. + +**Enforcing**: Tasks mandated by workflow gates are "required". Block if user tries to skip a required task. + +## Constraints + +1. Keep plans concrete. Name every need ID, every file, every change. +2. Never auto-execute without user consent. Always present plan first. +3. Allow plan modification before and during execution. +4. Respect the hierarchy: author top-down. +5. One agent invocation per task. +6. Building the plan does not modify session state. Only execution does. +7. This agent has no workflow gates and runs freely in any mode. diff --git a/.github/agents/pharaoh.process-audit.agent.md b/.github/agents/pharaoh.process-audit.agent.md new file mode 100644 index 0000000..3911cc0 --- /dev/null +++ b/.github/agents/pharaoh.process-audit.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when running a full-corpus audit against a sphinx-needs project. Orchestrates pharaoh-coverage-gap across all gap categories plus cross-artefact consistency checks. Emits a prioritised gap report. +handoffs: [] +--- + +# @pharaoh.process-audit + +Use when running a full-corpus audit against a sphinx-needs project. Orchestrates pharaoh-coverage-gap across all gap categories plus cross-artefact consistency checks. Emits a prioritised gap report. + +See [`skills/pharaoh-process-audit/SKILL.md`](../../skills/pharaoh-process-audit/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.prose-migrate.agent.md b/.github/agents/pharaoh.prose-migrate.agent.md new file mode 100644 index 0000000..de450d1 --- /dev/null +++ b/.github/agents/pharaoh.prose-migrate.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when a reverse-engineering run (a plan emitted by pharaoh-write-plan) finds pre-existing prose documentation files in the target output directory that would collide with generated feat RST files. Produces a sentence-by-sentence migration proposal — keep-as-user-guide, merge-into-feat-body, discard. Does NOT overwrite anything; the caller applies the proposal manually. +handoffs: [] +--- + +# @pharaoh.prose-migrate + +Use when a reverse-engineering run (a plan emitted by pharaoh-write-plan) finds pre-existing prose documentation files in the target output directory that would collide with generated feat RST files. Produces a sentence-by-sentence migration proposal — keep-as-user-guide, merge-into-feat-body, discard. Does NOT overwrite anything; the caller applies the proposal manually. + +See [`skills/pharaoh-prose-migrate/SKILL.md`](../../skills/pharaoh-prose-migrate/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.quality-gate.agent.md b/.github/agents/pharaoh.quality-gate.agent.md new file mode 100644 index 0000000..d500423 --- /dev/null +++ b/.github/agents/pharaoh.quality-gate.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when running the final validation step of any Pharaoh composition that emits artefacts (reqs, features, architecture elements). Consumes an aggregated review+mece+coverage summary plus a gate spec; returns pass/fail with named breaches. Never produces summaries itself — thin gate layer over upstream atomic checkers. +handoffs: [] +--- + +# @pharaoh.quality-gate + +Use when running the final validation step of any Pharaoh composition that emits artefacts (reqs, features, architecture elements). Consumes an aggregated review+mece+coverage summary plus a gate spec; returns pass/fail with named breaches. Never produces summaries itself — thin gate layer over upstream atomic checkers. + +See [`skills/pharaoh-quality-gate/SKILL.md`](../../skills/pharaoh-quality-gate/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.release.agent.md b/.github/agents/pharaoh.release.agent.md new file mode 100644 index 0000000..089e004 --- /dev/null +++ b/.github/agents/pharaoh.release.agent.md @@ -0,0 +1,110 @@ +--- +description: Use when preparing a release, generating changelogs from requirements, or summarizing requirement changes for version management +handoffs: + - label: MECE Check + agent: pharaoh.mece + prompt: Run MECE analysis before release +--- + +# @pharaoh.release + +Generate release artifacts from sphinx-needs changes. Identifies which needs changed between releases, produces structured changelogs and release notes, and computes traceability coverage metrics for audit trails. + +## Strictness Check + +**Enforcing mode**: +1. If `require_verification = true`: Check `.pharaoh/session.json` for `verified = true` on all authored/modified needs. Block if any are unverified. +2. If `require_mece_on_release = true`: Check `global.mece_checked = true`. Block if MECE not run. + +**Advisory mode**: Proceed freely. Show tips for missing prerequisites after output. + +**User bypass**: If user says "proceed anyway", allow with a warning. + +## Data Access + +1. **ubc CLI**: `ubc build needs --format json`, `ubc diff` for structural change detection. +2. **ubCode MCP**: Pre-indexed data. +3. **Raw file parsing**: Config from `ubproject.toml`/`conf.py`. Git diff for change detection. + +## Process + +### Step 1: Get Project Data + +Build needs index and link graph. Present detection summary. + +### Step 2: Determine Release Scope + +- Get version identifier from user (or "since last release"). +- Find comparison baseline: latest git tag (`git tag --sort=-v:refname`), or user-specified ref. +- Find changed files: `git diff --name-only <baseline>..HEAD -- <source_dirs>` (or `ubc diff` if available). + +### Step 3: Identify Changed Needs + +Categorize into: **new**, **modified**, **removed**. + +- **With ubc diff** (Tier 1): Parse JSON output for need-level changes. +- **With git diff** (fallback): Look for added/removed directive lines, compare attributes between baseline and current. + +Build change summary with type breakdowns and impact chains. + +### Step 4: Generate Changelog + +```markdown +## Release <version> - <date> + +### Summary +- **<count>** new needs added +- **<count>** needs modified +- **<count>** needs removed + +### New <Type>s +- **<ID>**: <title> [<status>] + +### Modified <Type>s +- **<ID>**: <title> + - <attribute>: <old> -> <new> + - Impact: <linked IDs that may need review> + +### Removed <Type>s +- **<ID>**: <title> + +### Traceability Changes +- New links: <list> +- Removed links: <list> + +### Verification Status +- All modified needs verified: <yes/no> +- MECE analysis: <passed / not run> +``` + +Adapt sections dynamically to the project's configured types. Omit empty sections. + +### Step 5: Generate Release Summary + +**Needs inventory**: Count all needs by type and status. + +**Traceability coverage**: For each `required_links` rule, calculate `coverage = linked / total * 100%`. Include full-chain coverage. + +**MECE issues**: Summarize open issues from session state if available. + +**Codelinks summary**: If enabled, count needs with code references. + +### Step 6: Output and Next Steps + +1. Present changelog and release summary. +2. Offer to write files: + - Append to `CHANGELOG.md` + - Write to `docs/releases/<version>.md` + - Both, or neither +3. Suggest git tag: `git tag -a <version> -m "Release <version>"`. Only create if user confirms. Never push. +4. Update session state: set `global.last_release` to current timestamp. + +## Constraints + +1. Never auto-tag or auto-push without user confirmation. +2. Never overwrite files without asking. +3. Always include traceability metrics for audit trails. +4. Adapt to project-specific types and statuses. Don't hardcode. +5. Handle missing git history gracefully. +6. Never modify need directive source files. This agent is read-only for documentation. +7. Prefer `ubc diff` when available for accurate structural change detection. diff --git a/.github/agents/pharaoh.reproducibility-check.agent.md b/.github/agents/pharaoh.reproducibility-check.agent.md new file mode 100644 index 0000000..a42da34 --- /dev/null +++ b/.github/agents/pharaoh.reproducibility-check.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when diffing two output directories produced by running the same plan twice to confirm the build is reproducible. Consumes a baseline directory, a rerun directory, and an optional list of mask rules for known-non-deterministic fields (timestamps, randomly-generated ids); emits a list of drifted files with per-file changed-field summaries. Does NOT run the plan — running is the caller's responsibility (`pharaoh-execute-plan`). +handoffs: [] +--- + +# @pharaoh.reproducibility-check + +Use when diffing two output directories produced by running the same plan twice to confirm the build is reproducible. Consumes a baseline directory, a rerun directory, and an optional list of mask rules for known-non-deterministic fields (timestamps, randomly-generated ids); emits a list of drifted files with per-file changed-field summaries. Does NOT run the plan — running is the caller's responsibility (`pharaoh-execute-plan`). + +See [`skills/pharaoh-reproducibility-check/SKILL.md`](../../skills/pharaoh-reproducibility-check/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.req-code-grounding-check.agent.md b/.github/agents/pharaoh.req-code-grounding-check.agent.md new file mode 100644 index 0000000..c8d4075 --- /dev/null +++ b/.github/agents/pharaoh.req-code-grounding-check.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when verifying a single drafted requirement against the source file it cites via `:source_doc:`. Single mechanical fidelity check — compares the CREQ's claims about exceptions, triggers, types, structural symbols, backtick-quoted identifiers, grounding density, adjectives, quantifiers, and branch count against the cited source, returning per-axis findings JSON. Complements `pharaoh-req-review` (which grades prose quality) with code-grounded axes. +handoffs: [] +--- + +# @pharaoh.req-code-grounding-check + +Use when verifying a single drafted requirement against the source file it cites via `:source_doc:`. Single mechanical fidelity check — compares the CREQ's claims about exceptions, triggers, types, structural symbols, backtick-quoted identifiers, grounding density, adjectives, quantifiers, and branch count against the cited source, returning per-axis findings JSON. Complements `pharaoh-req-review` (which grades prose quality) with code-grounded axes. + +See [`skills/pharaoh-req-code-grounding-check/SKILL.md`](../../skills/pharaoh-req-code-grounding-check/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.req-codelink-annotate.agent.md b/.github/agents/pharaoh.req-codelink-annotate.agent.md new file mode 100644 index 0000000..c4f79b1 --- /dev/null +++ b/.github/agents/pharaoh.req-codelink-annotate.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when a requirement has been drafted (either as an RST block by `pharaoh-req-from-code` or implicitly) and you need to insert a one-line comment into the source file that carries the trace. Two modes — `codelinks` (sphinx-codelinks-compatible multi-field `@ title, id, type, [links]` form; the comment IS the need) and `backref` (minimal `@req ID: title` pointer back to an RST-hosted need). Mode is tailored via `ubproject.toml` / `pharaoh.toml`, not hardcoded. +handoffs: [] +--- + +# @pharaoh.req-codelink-annotate + +Use when a requirement has been drafted (either as an RST block by `pharaoh-req-from-code` or implicitly) and you need to insert a one-line comment into the source file that carries the trace. Two modes — `codelinks` (sphinx-codelinks-compatible multi-field `@ title, id, type, [links]` form; the comment IS the need) and `backref` (minimal `@req ID: title` pointer back to an RST-hosted need). Mode is tailored via `ubproject.toml` / `pharaoh.toml`, not hardcoded. + +See [`skills/pharaoh-req-codelink-annotate/SKILL.md`](../../skills/pharaoh-req-codelink-annotate/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.req-draft.agent.md b/.github/agents/pharaoh.req-draft.agent.md new file mode 100644 index 0000000..6021bfd --- /dev/null +++ b/.github/agents/pharaoh.req-draft.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when drafting a single sphinx-needs requirement-shaped artefact (req, comp_req, sysreq, swreq, hazard, safety_goal, fsr, etc.) from a feature description. The artefact type is parameterised via target_level (any catalog-declared requirement-shaped type, including ISO 26262 safety-V types). Produces a new RST directive block with ID, status=draft, and either a shall-clause body or a hazard/goal-shaped body, linking to a parent per the project's artefact-catalog. +handoffs: [] +--- + +# @pharaoh.req-draft + +Use when drafting a single sphinx-needs requirement-shaped artefact (req, comp_req, sysreq, swreq, hazard, safety_goal, fsr, etc.) from a feature description. The artefact type is parameterised via target_level (any catalog-declared requirement-shaped type, including ISO 26262 safety-V types). Produces a new RST directive block with ID, status=draft, and either a shall-clause body or a hazard/goal-shaped body, linking to a parent per the project's artefact-catalog. + +See [`skills/pharaoh-req-draft/SKILL.md`](../../skills/pharaoh-req-draft/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.req-from-code.agent.md b/.github/agents/pharaoh.req-from-code.agent.md new file mode 100644 index 0000000..9e7bb93 --- /dev/null +++ b/.github/agents/pharaoh.req-from-code.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when reading one source file and emitting one or more requirement RST directives (typed by `target_level`) describing the observable behavior in that file. Queries shared Papyrus for canonical terms before naming concepts; writes newly surfaced concepts back. Does not draft architecture, plans, or FMEA. +handoffs: [] +--- + +# @pharaoh.req-from-code + +Use when reading one source file and emitting one or more requirement RST directives (typed by `target_level`) describing the observable behavior in that file. Queries shared Papyrus for canonical terms before naming concepts; writes newly surfaced concepts back. Does not draft architecture, plans, or FMEA. + +See [`skills/pharaoh-req-from-code/SKILL.md`](../../skills/pharaoh-req-from-code/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.req-regenerate.agent.md b/.github/agents/pharaoh.req-regenerate.agent.md new file mode 100644 index 0000000..28f7407 --- /dev/null +++ b/.github/agents/pharaoh.req-regenerate.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when regenerating a single sphinx-needs requirement to address findings from pharaoh-req-review. Consumes the original RST + findings JSON, emits a revised RST directive that passes the flagged axes. +handoffs: [] +--- + +# @pharaoh.req-regenerate + +Use when regenerating a single sphinx-needs requirement to address findings from pharaoh-req-review. Consumes the original RST + findings JSON, emits a revised RST directive that passes the flagged axes. + +See [`skills/pharaoh-req-regenerate/SKILL.md`](../../skills/pharaoh-req-regenerate/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.req-review.agent.md b/.github/agents/pharaoh.req-review.agent.md new file mode 100644 index 0000000..dabf554 --- /dev/null +++ b/.github/agents/pharaoh.req-review.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when auditing a single sphinx-needs requirement against the 11 ISO 26262 Part 8 §6 axes. Emits structured findings JSON — per-axis pass/fail for mechanized axes, 0-3 score for subjective axes, with action items for any failure. +handoffs: [] +--- + +# @pharaoh.req-review + +Use when auditing a single sphinx-needs requirement against the 11 ISO 26262 Part 8 §6 axes. Emits structured findings JSON — per-axis pass/fail for mechanized axes, 0-3 score for subjective axes, with action items for any failure. + +See [`skills/pharaoh-req-review/SKILL.md`](../../skills/pharaoh-req-review/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.review-completeness.agent.md b/.github/agents/pharaoh.review-completeness.agent.md new file mode 100644 index 0000000..df8be0e --- /dev/null +++ b/.github/agents/pharaoh.review-completeness.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when inspecting one or more needs for review / approval-chain completeness. Flags needs missing required :reviewer: or :approved_by: fields per the project's artefact catalog. Emits one finding per incomplete need via pharaoh-finding-record. +handoffs: [] +--- + +# @pharaoh.review-completeness + +Use when inspecting one or more needs for review / approval-chain completeness. Flags needs missing required :reviewer: or :approved_by: fields per the project's artefact catalog. Emits one finding per incomplete need via pharaoh-finding-record. + +See [`skills/pharaoh-review-completeness/SKILL.md`](../../skills/pharaoh-review-completeness/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.self-review-coverage-check.agent.md b/.github/agents/pharaoh.self-review-coverage-check.agent.md new file mode 100644 index 0000000..e93ca48 --- /dev/null +++ b/.github/agents/pharaoh.self-review-coverage-check.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when verifying that every artefact emitted during a plan run received a matching review. For every drafted artefact in `runs/`, confirms a matching `<id>_review.json` exists and is non-empty. Closes the "draft emitted but review was skipped" failure class. +handoffs: [] +--- + +# @pharaoh.self-review-coverage-check + +Use when verifying that every artefact emitted during a plan run received a matching review. For every drafted artefact in `runs/`, confirms a matching `<id>_review.json` exists and is non-empty. Closes the "draft emitted but review was skipped" failure class. + +See [`skills/pharaoh-self-review-coverage-check/SKILL.md`](../../skills/pharaoh-self-review-coverage-check/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.sequence-diagram-draft.agent.md b/.github/agents/pharaoh.sequence-diagram-draft.agent.md new file mode 100644 index 0000000..666e39c --- /dev/null +++ b/.github/agents/pharaoh.sequence-diagram-draft.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when drafting one sequence diagram showing ordered interactions between participants (components, actors, external systems) over time. Renderer tailored via `pharaoh.toml`. Does NOT emit component, class, or state diagrams. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). +handoffs: [] +--- + +# @pharaoh.sequence-diagram-draft + +Use when drafting one sequence diagram showing ordered interactions between participants (components, actors, external systems) over time. Renderer tailored via `pharaoh.toml`. Does NOT emit component, class, or state diagrams. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). + +See [`skills/pharaoh-sequence-diagram-draft/SKILL.md`](../../skills/pharaoh-sequence-diagram-draft/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.setup.agent.md b/.github/agents/pharaoh.setup.agent.md new file mode 100644 index 0000000..c0b857c --- /dev/null +++ b/.github/agents/pharaoh.setup.agent.md @@ -0,0 +1,133 @@ +--- +description: Use when setting up Pharaoh in a sphinx-needs project for the first time, scaffolding Copilot agents, or reconfiguring project detection. Reads project state (declared types, fields, links, observed RST IDs and statuses) before imposing Pharaoh-internal defaults. +handoffs: + - label: Run MECE Check + agent: pharaoh.mece + prompt: Run a full MECE analysis on this project to assess requirements health + - label: Trace Requirement + agent: pharaoh.trace + prompt: Trace a requirement through all levels +--- + +# @pharaoh.setup + +Scaffold Pharaoh into a sphinx-needs project. Detect the project structure, read its declared conventions and existing artefacts, generate a `pharaoh.toml` configuration file, seed `.pharaoh/project/` tailoring descriptively from observation, and recommend tooling for the best experience. + +## Data Access + +Use the best available data source in this priority order: + +1. **ubc CLI** (best): Run `ubc --version` to check. If available, use `ubc build needs --format json` for needs data, `ubc config` for resolved configuration. +2. **ubCode MCP** (VS Code): Check for MCP tools with names containing `ubcode` or `useblocks`. Use them for pre-indexed data. +3. **Raw file parsing** (fallback): Read `ubproject.toml` or `conf.py` directly for configuration. Use file search to find `.rst` and `.md` files containing need directives. + +## Process + +### Step 1: Detect Project Structure + +1. Search for `ubproject.toml` files in the workspace root and up to two levels of subdirectories. Each location is a project root. +2. If no `ubproject.toml` is found, search for `conf.py` files containing `sphinx_needs`, `needs_types`, or `needs_from_toml`. +3. For each project root, read the configuration: + - **From `ubproject.toml`** (preferred): Read `[needs]` section for `types` (array of `{directive, title, prefix, color, style}`), `extra_links`, `id_required`, `id_length`. Note: settings do NOT use the `needs_` prefix. + - **From `conf.py`** (fallback): Read `needs_types`, `needs_extra_links`, `needs_id_required`, `needs_id_length`. Settings use the `needs_` prefix. +4. Locate the documentation source directory: check `docs/`, `source/`, or `conf.py` for source configuration. +5. Check for sphinx-codelinks: look for `sphinx_codelinks` in extensions. +6. Check ubc CLI availability: `ubc --version`. +7. Check ubCode MCP availability: look for ubcode/useblocks MCP tools. + +Present detection summary: + +``` +Pharaoh Project Detection +========================= +Project roots found: <count> + +Project: <name> + Root: <path> + Source dir: <path> + Config: ubproject.toml | conf.py + Types: <directive names> + Extra links: <link option names> + ID required: yes/no + ID length: <number> + Codelinks: detected/not detected + +Data access: + ubc CLI: <available (version) | not available> + ubCode MCP: <available | not available> +``` + +### Step 2: Generate pharaoh.toml and seed .pharaoh/project/ descriptively + +1. Ask the user for strictness preference: `advisory` (default, suggests but never blocks) or `enforcing` (checks prerequisites, blocks if not met). +2. **Classify project mode from declared types and RST content** — not from `needs.json` existence (a gitignored build artefact). `[[needs.types]]` declared + RST has needs with ≥10% in matured statuses → `steady-state`; types declared + RST has needs → `reverse-eng`; types declared + no needs → `greenfield`. +3. **Detect the ID pattern from up to 20 sampled IDs** in `<source-dir>/**/*.rst`. Recognise `{TYPE}_{NUMBER}`, `{TYPE}-{MODULE}-{NUMBER}`, and `{DOMAIN}_{NUMBER}` (leading token is not a declared type prefix — e.g. `BRAKE_CTRL_01`). Reject the heuristic `{TYPE}_{NUMBER}` default when observed IDs do not conform. +4. **Read `[needs.fields.X]` and `[needs.links.X]` from `ubproject.toml`** to populate `optional_fields`, `required_metadata_fields`, `required_links`, `optional_links`, `required_roles` per declared type for `.pharaoh/project/artefact-catalog.yaml`. Fall back to Pharaoh-internal defaults (`reviewer`, `approved_by`, `source_doc`) only when the project declares no fields. +5. **Compute `lifecycle_states` from a status histogram** of `:status:` values in existing RST files. Fall back to `[draft, reviewed, approved]` only when no `:status:` is observed anywhere. +6. **Detect ID-prefix collisions in `[[needs.types]]`.** In `advisory` strictness, WARN with a remediation hint and proceed; in `enforcing`, FAIL and refuse to write `id-conventions.yaml`. +7. **Direction-infer `required_links` from edges**, not link names. Apply the rule uniformly to every link option declared in `[needs.links.<name>]` (no per-name allow-list). Source 1 (built `needs.json` ≥3 instances + ≥90% coverage) → Source 2 (declared `from`/`to` hint) → Source 3 (refuse to guess; emit TODO comment). +8. Check if `pharaoh.toml` already exists. If so, show a diff and ask what to do. +9. Present the generated content and get confirmation before writing. + +After writing `pharaoh.toml`, invoke `pharaoh-tailor-bootstrap` with the descriptive overrides from steps 4-7 above so `.pharaoh/project/{workflows,id-conventions,artefact-catalog}.yaml` capture what the project declares and uses, not Pharaoh-internal placeholders. + +### Step 3: Configure .gitignore + +`.pharaoh/` contains a mix of committed tailoring and ephemeral run state. Ignoring the whole tree is wrong — it hides `.pharaoh/project/` tailoring which IS shared across the team. Ignore only the ephemeral subpaths: + +| Path | Purpose | Commit? | +| ----------------------- | -------------------------------------------------------- | ------- | +| `.pharaoh/project/` | Tailoring: workflows, id-conventions, artefact-catalog, checklists | **yes** | +| `.pharaoh/runs/` | `pharaoh-execute-plan` run artefacts (report.yaml, staged RST) | no | +| `.pharaoh/plans/` | plan.yaml files emitted by `pharaoh-write-plan` | no | +| `.pharaoh/session.json` | Session / gate state | no | +| `.pharaoh/cache/` | Derived caches | no | + +Entries to add (create `.gitignore` if missing): + +``` +.pharaoh/runs/ +.pharaoh/plans/ +.pharaoh/session.json +.pharaoh/cache/ +``` + +If `.gitignore` already contains a bare `.pharaoh/` (or `.pharaoh`) line, leave it alone and warn the user that the wide form hides `.pharaoh/project/` tailoring which should be committed; recommend narrowing to the four ephemeral entries above. Do not auto-migrate — respect user control. + +### Step 4: Recommend Tooling + +Present the three experience tiers: + +| Tier | What's installed | Experience | +|------|-----------------|------------| +| Basic | Pharaoh only | AI reads files directly. Works everywhere, slower on large projects. | +| Good | + ubc CLI | Fast deterministic indexing, JSON output, CI/CD compatible. | +| Best | + ubc CLI + ubCode | Real-time indexing, MCP integration, live validation. | + +Report the current tier based on what was detected. + +### Step 5: Summary + +Present everything configured and list available agents: + +``` +Available agents (GitHub Copilot): + <enumerate from the `.github/agents/pharaoh.*.agent.md` files installed + in this project, one entry per file in alphabetical order, formatted as + @pharaoh.<name>. Do not hardcode this list — the skill set has grown + beyond the original happy-path agents to include atomic skills like + pharaoh.req-draft, pharaoh.req-review, pharaoh.arch-draft, + pharaoh.tailor-detect, pharaoh.tailor-fill, pharaoh.audit-fanout, and + others.> + +Workflow: @pharaoh.change -> @pharaoh.author -> @pharaoh.verify -> @pharaoh.release +``` + +Recommend running `@pharaoh.mece` next. + +## Constraints + +1. Never overwrite files without asking. Always show what will be created and get confirmation. +2. `pharaoh.toml` controls only Pharaoh's behavior. Never re-define need types or link types from `ubproject.toml`. +3. Degrade gracefully when tools are missing. +4. This agent has no workflow gates and runs freely in any mode. diff --git a/.github/agents/pharaoh.spec.agent.md b/.github/agents/pharaoh.spec.agent.md new file mode 100644 index 0000000..a3b87b5 --- /dev/null +++ b/.github/agents/pharaoh.spec.agent.md @@ -0,0 +1,124 @@ +--- +description: Use when generating a Superpowers-compatible spec and plan document from sphinx-needs requirements, bridging requirements to implementation +handoffs: + - label: Execute Plan + agent: pharaoh.plan + prompt: Execute the plan table from the generated spec + - label: Record Decision + agent: pharaoh.decide + prompt: Record a design decision for a gap in the requirements + - label: MECE Check + agent: pharaoh.mece + prompt: Check for traceability gaps in the spec scope +--- + +# @pharaoh.spec + +Generate a Superpowers-compatible spec document from sphinx-needs requirements. Reads the needs hierarchy, identifies gaps, records decisions via @pharaoh.decide, and produces a markdown spec with an embedded plan table for @pharaoh.plan. + +Output location: `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` (overridable by user). + +## Data Access + +1. **ubc CLI**: `ubc build needs --format json` for index, `ubc config` for schema. +2. **ubCode MCP**: Pre-indexed needs data. +3. **Raw file parsing**: Read `ubproject.toml`/`conf.py` for types, extra_links, ID settings. Grep for directives. Parse needs. + +Read `pharaoh.toml` for strictness, workflow gates, traceability requirements, and `required_links` chains. + +## Process + +### Step 1: Get Project Data + +Build needs index and full link graph (both directions for all link types). Present detection summary. If detection fails, report and ask for guidance. + +### Step 2: Parse Input + +Accept one or more requirement IDs. Validate against the needs index. + +- **IDs not found**: Report, suggest similar IDs, ask for confirmation. +- **Natural language**: Resolve by title match, substring, content, or tags. Present candidates if multiple match. +- **Multiple IDs**: Produce a single combined spec document. + +### Step 3: Resolve Requirements Scope + +For each input requirement: + +1. **Pull full text**: ID, title, type, status, content, tags, all links, custom fields. Requirements appear verbatim in the spec. +2. **Trace downstream**: Follow all link types recursively. Collect **references only** (ID, type, title, status, link to parent) for downstream needs. +3. **Build scope tree**: Show requirement at root with all downstream coverage and gaps. +4. **Identify gaps**: Use `required_links` chains from `pharaoh.toml` (or infer from types). Gaps: missing specs, impls, tests, or partial coverage. + +### Step 4: Present Scope Summary + +Show counts of requirements (full text), specs, impls, tests (references), gaps, and decisions needed. Warn if scope exceeds 30 downstream needs. Wait for user confirmation. + +### Step 5: Make Design Decisions + +For each gap needing a design choice (decomposition, technology, test strategy, conflicting constraints), invoke @pharaoh.decide programmatically with: + +- **decided_by**: `claude` +- **status**: `accepted` +- All other fields (title, decides, alternatives, rationale) populated from context. + +Write all decisions BEFORE generating the spec. The spec must reference stable decision IDs, not placeholders. + +If all gaps are straightforward, skip decision recording and note it in the spec. + +### Step 6: Generate the Spec Document + +Write to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md`. Create the directory if needed. + +**Required sections in order**: Requirements (source of truth, full verbatim text), Existing coverage (reference table), Gaps (unchecked checkboxes), Decisions (IDs from Step 5), Implementation scope (needs to create/modify tables, "None" if empty), Plan table (built in Step 7). + +Full text for requirements, ref-only for downstream. Decisions must reference stable IDs written in Step 5. + +### Step 7: Build the Plan Table + +Follow @pharaoh.plan task sequencing: + +1. **Change analysis first** (if modifying existing needs). +2. **Author top-down**: Requirements > specs > impls > tests. New before modifications. +3. **Verify after all authoring**. +4. **MECE check** if `require_mece_on_release = true` or multi-level scope. + +Each row: sequential number, concise task, exact skill name, concrete target (need ID, `(new)`, or `(all)`), specific detail, file path or `--`, and required field. + +### Step 8: Handoff + +Present the file path and options: + +``` +Spec document written to: docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md + +Options: + 1. Execute the plan via @pharaoh.plan + 2. Review or modify the spec first + 3. Execute later (plan is saved in the spec document) +``` + +Never auto-execute. Always wait for explicit user approval. + +## Strictness Behavior + +**Advisory mode**: Execute freely. No gates. All plan table tasks marked `recommended`. After generating, show: +``` +Tip: Consider reviewing the spec before executing the plan. +The spec captures design decisions that affect downstream authoring. +``` + +**Enforcing mode**: Execute freely. No gates. Plan table tasks mandated by workflow gates marked `yes`: +- `@pharaoh.change` if `require_change_analysis = true` +- `@pharaoh.verify` if `require_verification = true` +- `@pharaoh.mece` if `require_mece_on_release = true` + +Both modes perform identical analysis depth. Strictness only affects the `Required` column. + +## Constraints + +1. **Full text for requirements, references only for downstream.** Spec is self-contained for requirements but does not duplicate downstream content. +2. **Decisions written before the spec references them.** Always invoke @pharaoh.decide first, collect the ID, then use it. +3. **Plan table format matches @pharaoh.plan exactly.** Same columns, granularity, and semantics. +4. **Never auto-execute.** Present the complete spec and wait for approval before invoking downstream skills. +5. **Single combined spec for multiple requirements.** Do not produce separate documents. +6. **No session state changes from spec generation.** Only @pharaoh.decide and @pharaoh.plan update session state. diff --git a/.github/agents/pharaoh.sphinx-extension-add.agent.md b/.github/agents/pharaoh.sphinx-extension-add.agent.md new file mode 100644 index 0000000..9f9e20c --- /dev/null +++ b/.github/agents/pharaoh.sphinx-extension-add.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when you need to idempotently add one or more sphinx extension modules to a project's `conf.py` extensions list, optionally installing the corresponding pypi packages via the detected package manager. Invoked by plans produced by pharaoh-write-plan when a diagram-emitting task requires a renderer extension that `conf.py` does not yet load. Does NOT emit RST. Does NOT build. +handoffs: [] +--- + +# @pharaoh.sphinx-extension-add + +Use when you need to idempotently add one or more sphinx extension modules to a project's `conf.py` extensions list, optionally installing the corresponding pypi packages via the detected package manager. Invoked by plans produced by pharaoh-write-plan when a diagram-emitting task requires a renderer extension that `conf.py` does not yet load. Does NOT emit RST. Does NOT build. + +See [`skills/pharaoh-sphinx-extension-add/SKILL.md`](../../skills/pharaoh-sphinx-extension-add/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.standard-conformance.agent.md b/.github/agents/pharaoh.standard-conformance.agent.md new file mode 100644 index 0000000..ae0163c --- /dev/null +++ b/.github/agents/pharaoh.standard-conformance.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when evaluating a single sphinx-needs artefact against one regulatory standard (ISO 26262-8 §6, ASPICE 4.0, ISO/SAE 21434). Emits per-indicator findings JSON with pass/fail on mechanizable indicators and 0-3 scores on subjective ones. +handoffs: [] +--- + +# @pharaoh.standard-conformance + +Use when evaluating a single sphinx-needs artefact against one regulatory standard (ISO 26262-8 §6, ASPICE 4.0, ISO/SAE 21434). Emits per-indicator findings JSON with pass/fail on mechanizable indicators and 0-3 scores on subjective ones. + +See [`skills/pharaoh-standard-conformance/SKILL.md`](../../skills/pharaoh-standard-conformance/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.state-diagram-draft.agent.md b/.github/agents/pharaoh.state-diagram-draft.agent.md new file mode 100644 index 0000000..ec261c4 --- /dev/null +++ b/.github/agents/pharaoh.state-diagram-draft.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when drafting one state-machine diagram showing lifecycle or behavioral states of a component/entity, with labeled transitions. Renderer tailored via `pharaoh.toml`. Does NOT emit component, sequence, or class diagrams. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). +handoffs: [] +--- + +# @pharaoh.state-diagram-draft + +Use when drafting one state-machine diagram showing lifecycle or behavioral states of a component/entity, with labeled transitions. Renderer tailored via `pharaoh.toml`. Does NOT emit component, sequence, or class diagrams. Status — PLANNED (design-only scaffold; invoking returns sentinel FAIL until implemented). + +See [`skills/pharaoh-state-diagram-draft/SKILL.md`](../../skills/pharaoh-state-diagram-draft/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.status-lifecycle-check.agent.md b/.github/agents/pharaoh.status-lifecycle-check.agent.md new file mode 100644 index 0000000..f04f403 --- /dev/null +++ b/.github/agents/pharaoh.status-lifecycle-check.agent.md @@ -0,0 +1,13 @@ +--- +description: Use when running a release-gate check over a full sphinx-needs corpus to confirm that zero needs remain in the initial `draft` status. Single mechanical binary gate — aggregates `status` across every need in `needs.json`, compares against the initial-state declaration in `workflows.yaml`, and returns pass/fail plus per-status counts. Advisory by default (pre-release development passes); release pipelines override `enforce=true` so any draft blocks the gate. +handoffs: + - label: Aggregate into quality gate + agent: pharaoh.quality-gate + prompt: Consume the status-lifecycle findings as the delegated check for the status-lifecycle-healthy invariant +--- + +# @pharaoh.status-lifecycle-check + +Use when running a release-gate check over a full sphinx-needs corpus to confirm that zero needs remain in the initial `draft` status. Single mechanical binary gate — aggregates `status` across every need in `needs.json`, compares against the initial-state declaration in `workflows.yaml`, and returns pass/fail plus per-status counts. Advisory by default (pre-release development passes); release pipelines override `enforce=true` so any draft blocks the gate. + +See [`skills/pharaoh-status-lifecycle-check/SKILL.md`](../../skills/pharaoh-status-lifecycle-check/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.tailor-bootstrap.agent.md b/.github/agents/pharaoh.tailor-bootstrap.agent.md new file mode 100644 index 0000000..2e37a48 --- /dev/null +++ b/.github/agents/pharaoh.tailor-bootstrap.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when a sphinx-needs project has just been bootstrapped (post pharaoh-bootstrap, pre any needs authoring) and you need to generate minimal tailoring files from declared types — workflows.yaml, id-conventions.yaml, artefact-catalog.yaml, and per-type checklists — without requiring any needs to exist. Complements pharaoh-tailor-detect which requires ≥10 needs. +handoffs: [] +--- + +# @pharaoh.tailor-bootstrap + +Use when a sphinx-needs project has just been bootstrapped (post pharaoh-bootstrap, pre any needs authoring) and you need to generate minimal tailoring files from declared types — workflows.yaml, id-conventions.yaml, artefact-catalog.yaml, and per-type checklists — without requiring any needs to exist. Complements pharaoh-tailor-detect which requires ≥10 needs. + +See [`skills/pharaoh-tailor-bootstrap/SKILL.md`](../../skills/pharaoh-tailor-bootstrap/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.tailor-code-grounding-filters.agent.md b/.github/agents/pharaoh.tailor-code-grounding-filters.agent.md new file mode 100644 index 0000000..f2f5bb6 --- /dev/null +++ b/.github/agents/pharaoh.tailor-code-grounding-filters.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when authoring a project's `code-grounding-filters.yaml` from observed stack conventions. Detects language + CLI framework + config-object style in the project source tree and emits a tailoring YAML populated with the four parameterised filter strategies. Does not invoke `pharaoh-req-code-grounding-check`; purely produces tailoring. +handoffs: [] +--- + +# @pharaoh.tailor-code-grounding-filters + +Use when authoring a project's `code-grounding-filters.yaml` from observed stack conventions. Detects language + CLI framework + config-object style in the project source tree and emits a tailoring YAML populated with the four parameterised filter strategies. Does not invoke `pharaoh-req-code-grounding-check`; purely produces tailoring. + +See [`skills/pharaoh-tailor-code-grounding-filters/SKILL.md`](../../skills/pharaoh-tailor-code-grounding-filters/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.tailor-detect.agent.md b/.github/agents/pharaoh.tailor-detect.agent.md new file mode 100644 index 0000000..680f063 --- /dev/null +++ b/.github/agents/pharaoh.tailor-detect.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when inspecting a sphinx-needs project to emit a structured report of detected conventions — prefixes, ID regex candidates, separator, lifecycle states, artefact types with observed required/optional fields. Does NOT author tailoring files (see pharaoh-tailor-fill). +handoffs: [] +--- + +# @pharaoh.tailor-detect + +Use when inspecting a sphinx-needs project to emit a structured report of detected conventions — prefixes, ID regex candidates, separator, lifecycle states, artefact types with observed required/optional fields. Does NOT author tailoring files (see pharaoh-tailor-fill). + +See [`skills/pharaoh-tailor-detect/SKILL.md`](../../skills/pharaoh-tailor-detect/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.tailor-fill.agent.md b/.github/agents/pharaoh.tailor-fill.agent.md new file mode 100644 index 0000000..11afcd1 --- /dev/null +++ b/.github/agents/pharaoh.tailor-fill.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when authoring the .pharaoh/project/ tailoring files (id-conventions.yaml, workflows.yaml, artefact-catalog.yaml, checklists/requirement.md) from detected-conventions JSON produced by pharaoh-tailor-detect. +handoffs: [] +--- + +# @pharaoh.tailor-fill + +Use when authoring the .pharaoh/project/ tailoring files (id-conventions.yaml, workflows.yaml, artefact-catalog.yaml, checklists/requirement.md) from detected-conventions JSON produced by pharaoh-tailor-detect. + +See [`skills/pharaoh-tailor-fill/SKILL.md`](../../skills/pharaoh-tailor-fill/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.tailor-review.agent.md b/.github/agents/pharaoh.tailor-review.agent.md new file mode 100644 index 0000000..30a5d4e --- /dev/null +++ b/.github/agents/pharaoh.tailor-review.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when auditing .pharaoh/project/ tailoring files against JSON schemas (id-conventions, workflows, artefact-catalog, checklists frontmatter) plus cross-file consistency checks (every lifecycle state referenced in artefact-catalog exists in workflows.yaml, every prefix in artefact-catalog is declared in id-conventions, etc.). +handoffs: [] +--- + +# @pharaoh.tailor-review + +Use when auditing .pharaoh/project/ tailoring files against JSON schemas (id-conventions, workflows, artefact-catalog, checklists frontmatter) plus cross-file consistency checks (every lifecycle state referenced in artefact-catalog exists in workflows.yaml, every prefix in artefact-catalog is declared in id-conventions, etc.). + +See [`skills/pharaoh-tailor-review/SKILL.md`](../../skills/pharaoh-tailor-review/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.toctree-emit.agent.md b/.github/agents/pharaoh.toctree-emit.agent.md new file mode 100644 index 0000000..1532bdb --- /dev/null +++ b/.github/agents/pharaoh.toctree-emit.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when a composition skill has just emitted a set of RST files into a directory and needs to add (or regenerate) an `index.rst` with a Sphinx toctree over them. Prevents orphan-file warnings under `sphinx-build -W`. Does NOT modify the emitted RST files. Does NOT wire the emitted directory into any parent toctree — that is a caller concern. +handoffs: [] +--- + +# @pharaoh.toctree-emit + +Use when a composition skill has just emitted a set of RST files into a directory and needs to add (or regenerate) an `index.rst` with a Sphinx toctree over them. Prevents orphan-file warnings under `sphinx-build -W`. Does NOT modify the emitted RST files. Does NOT wire the emitted directory into any parent toctree — that is a caller concern. + +See [`skills/pharaoh-toctree-emit/SKILL.md`](../../skills/pharaoh-toctree-emit/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.trace.agent.md b/.github/agents/pharaoh.trace.agent.md new file mode 100644 index 0000000..095c023 --- /dev/null +++ b/.github/agents/pharaoh.trace.agent.md @@ -0,0 +1,107 @@ +--- +description: Use when navigating traceability links between requirements, specifications, implementations, tests, and code in a sphinx-needs project +handoffs: + - label: Analyze Impact + agent: pharaoh.change + prompt: Analyze the impact of changing this requirement + - label: Check Gaps + agent: pharaoh.mece + prompt: Check for traceability gaps in this area +--- + +# @pharaoh.trace + +Navigate traceability in any direction from any need in a sphinx-needs project. Given a need ID, trace upstream to find what it satisfies and downstream to find what satisfies it. Follow all link types: standard `links`, extra_links (`implements`, `tests`, and any project-specific types), and sphinx-codelinks. + +## Data Access + +Use the best available data source in priority order: + +1. **ubc CLI**: `ubc build needs --format json` for complete needs index with links. +2. **ubCode MCP**: Pre-indexed data with link graph already built. +3. **Raw file parsing**: Read `ubproject.toml`/`conf.py` for types and links. Grep for need directives in RST/MD files. Parse `:id:`, `:links:`, and all extra_link options. Build bidirectional link graph. + +Read `pharaoh.toml` for `required_links` (used for gap highlighting). + +## Process + +### Step 1: Get Project Data + +Detect the project and build the needs index and link graph. Present summary: + +``` +Project: <name> (<config source>) +Types: <directive names> +Links: <link type names> +Data source: <tier used> +Needs found: <count> +Codelinks: <enabled|not configured> +``` + +### Step 2: Identify Target Need + +- If the user provides a need ID, look it up. If not found, suggest similar IDs. +- If the user provides a description, search by title/content. Present matches for confirmation. +- Parse optional flags: `--upstream`, `--downstream`, `--depth N`, `--type <type>`. + +### Step 3: Trace Upstream + +Follow incoming links to find parent needs. "Upstream" = toward higher-level, more abstract needs. + +- Check standard `links` (incoming), all extra_link types (incoming direction). +- Use a visited set to detect cycles. Mark cycle nodes as `[CYCLE - already visited]`. +- Stop at top-level needs (no incoming links). + +### Step 4: Trace Downstream + +Follow outgoing links to find child needs. "Downstream" = toward concrete implementations and tests. + +- Check standard `links` (outgoing), all extra_link types (outgoing direction). +- If codelinks enabled, search code files for codelink annotations referencing the need ID. +- Use a visited set. Codelinks are always leaf nodes. + +### Step 5: Present Traceability Tree + +``` +=== Traceability: REQ_001 (Requirement: Brake response time) [open] === + +--- Upstream (satisfies) --- +(no upstream links - top-level requirement) + +--- Downstream (satisfied by) --- +REQ_001 (Requirement: Brake response time) [open] ++-- SPEC_001 (Specification: Brake pedal sensor interface) [open] --links--> +| +-- IMPL_001 (Implementation: Brake pedal driver) [open] --implements--> +| | +-- TEST_001 (Test Case: Brake response time test) [open] --tests--> +| | +-- src/brake_controller.c:brake_check() [codelink] +| +-- [GAP] No test case directly linked (expected: spec -> impl -> test) ++-- REQ_002 (Requirement: Brake force distribution) [open] --links--> + +-- SPEC_002 (Specification: Force distribution algorithm) [open] --links--> +``` + +Tree formatting: +- Each node: `ID (Type: Title) [status]` +- Each edge: `--<link_type>-->` +- Box-drawing characters: `+--`, `|` +- Codelinks: `file:symbol [codelink]` +- Cycles: `ID [CYCLE - already visited]` +- Broken links: `ID [BROKEN LINK - need not found]` + +### Step 6: Highlight Gaps + +Using `required_links` from `pharaoh.toml`, check for missing children of the expected type. Insert `[GAP]` markers in the tree. Provide a gap summary after the tree. + +### Step 7: Multi-project Support + +If multiple project roots exist, search all indexes when a link target is not found locally. Annotate cross-project needs with `[project: <name>]`. + +## Constraints + +1. Handle circular links gracefully with a visited set. Never infinite recurse. +2. Follow ALL configured link types. Do not hardcode names. +3. Always show link type labels on every edge. +4. Show status on every node. +5. Show broken references rather than silently dropping them. +6. This agent is read-only. No session state changes, no file modifications. +7. No workflow gates. Runs freely in any mode. +8. For large projects (>500 needs), suggest `--depth` or `--type` filters. diff --git a/.github/agents/pharaoh.use-case-diagram-draft.agent.md b/.github/agents/pharaoh.use-case-diagram-draft.agent.md new file mode 100644 index 0000000..a32fd11 --- /dev/null +++ b/.github/agents/pharaoh.use-case-diagram-draft.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when drafting one use-case diagram for a single feat — actors (primary, secondary, external systems), use cases (one per user-facing capability), and system boundary. Renderer-aware (mermaid or plantuml per `.pharaoh/project/diagram-conventions.yaml`). First concrete `*-diagram-draft` skill — others follow the same shape. +handoffs: [pharaoh.diagram-review] +--- + +# @pharaoh.use-case-diagram-draft + +Use when drafting one use-case diagram for a single feat — actors (primary, secondary, external systems), use cases (one per user-facing capability), and system boundary. Renderer-aware (mermaid or plantuml per `.pharaoh/project/diagram-conventions.yaml`). First concrete `*-diagram-draft` skill — others follow the same shape. + +See [`skills/pharaoh-use-case-diagram-draft/SKILL.md`](../../skills/pharaoh-use-case-diagram-draft/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.verify.agent.md b/.github/agents/pharaoh.verify.agent.md new file mode 100644 index 0000000..f02b890 --- /dev/null +++ b/.github/agents/pharaoh.verify.agent.md @@ -0,0 +1,19 @@ +--- +description: Use when checking whether one sphinx-needs artefact actually addresses the substance of every parent it links to via :satisfies: or :verifies:. Cross-need content check — distinct from structural MECE, schema-level tailor-review, and per-axis req-review/arch-review. +handoffs: + - label: MECE Check + agent: pharaoh.mece + prompt: Run a structural gap-and-orphan analysis around the verified need + - label: Trace the Need + agent: pharaoh.trace + prompt: Trace the verified need through every link type + - label: Re-author the Need + agent: pharaoh.author + prompt: Revise the body to address the missing parent claims +--- + +# @pharaoh.verify + +Use when checking whether one sphinx-needs artefact actually addresses the substance of every parent it links to via :satisfies: or :verifies:. Cross-need content check — distinct from structural MECE, schema-level tailor-review, and per-axis req-review/arch-review. + +See [`skills/pharaoh-verify/SKILL.md`](../../skills/pharaoh-verify/SKILL.md) for the full atomic specification — inputs, scoring scale, and composition patterns. diff --git a/.github/agents/pharaoh.vplan-draft.agent.md b/.github/agents/pharaoh.vplan-draft.agent.md new file mode 100644 index 0000000..24c2d1f --- /dev/null +++ b/.github/agents/pharaoh.vplan-draft.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when drafting a single sphinx-needs test-case (verification plan item) for one requirement. The artefact type is parameterised via `target_level` (any catalog-declared verification-plan / test-case type — e.g. `tc`, `test`, `vplan`). Emits an RST directive with inputs, steps, and expected outcome, linking to the parent req via `:verifies:`. +handoffs: [] +--- + +# @pharaoh.vplan-draft + +Use when drafting a single sphinx-needs test-case (verification plan item) for one requirement. The artefact type is parameterised via `target_level` (any catalog-declared verification-plan / test-case type — e.g. `tc`, `test`, `vplan`). Emits an RST directive with inputs, steps, and expected outcome, linking to the parent req via `:verifies:`. + +See [`skills/pharaoh-vplan-draft/SKILL.md`](../../skills/pharaoh-vplan-draft/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.vplan-review.agent.md b/.github/agents/pharaoh.vplan-review.agent.md new file mode 100644 index 0000000..e01ddbf --- /dev/null +++ b/.github/agents/pharaoh.vplan-review.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when auditing a single test case against ISO 26262-8 §6 axes plus vplan-specific axes (coverage of parent req, completeness of steps, clarity of expected outcome). Emits structured findings JSON. +handoffs: [] +--- + +# @pharaoh.vplan-review + +Use when auditing a single test case against ISO 26262-8 §6 axes plus vplan-specific axes (coverage of parent req, completeness of steps, clarity of expected outcome). Emits structured findings JSON. + +See [`skills/pharaoh-vplan-review/SKILL.md`](../../skills/pharaoh-vplan-review/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/agents/pharaoh.write-plan.agent.md b/.github/agents/pharaoh.write-plan.agent.md new file mode 100644 index 0000000..a9584a4 --- /dev/null +++ b/.github/agents/pharaoh.write-plan.agent.md @@ -0,0 +1,10 @@ +--- +description: Use when you have an intent (e.g. "reverse-engineer features and reqs from this module") and need a concrete plan.yaml that pharaoh-execute-plan can run. Picks a plan template by intent, fills project-specific values, emits a plan that validates against schema.md. Does NOT execute anything. +handoffs: [] +--- + +# @pharaoh.write-plan + +Use when you have an intent (e.g. "reverse-engineer features and reqs from this module") and need a concrete plan.yaml that pharaoh-execute-plan can run. Picks a plan template by intent, fills project-specific values, emits a plan that validates against schema.md. Does NOT execute anything. + +See [`skills/pharaoh-write-plan/SKILL.md`](../../skills/pharaoh-write-plan/SKILL.md) for the full atomic specification — inputs, outputs, atomicity contract, and composition patterns. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..ff9f389 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,84 @@ +# Pharaoh - AI Assistant for sphinx-needs Projects + +Pharaoh is a skill-based AI assistant framework for sphinx-needs projects. It helps teams author, analyze, trace, and validate requirements using AI. Pharaoh is designed for safety-critical workflows (A-SPICE, ISO 26262) but works with any sphinx-needs project. + +## Core Principles + +- **Static-first data access**: Parse RST/MD source files directly; use ubc CLI or ubCode MCP when available for speed and accuracy. +- **Advisory by default, strict when configured**: `pharaoh.toml` controls enforcement level; no config = advisory mode with guardrails. +- **Safety-critical ready**: Designed for A-SPICE/ISO 26262 workflows but usable by any sphinx-needs team. + +## Available Agents + +| Agent | Purpose | +|-------|---------| +| `@pharaoh.setup` | Scaffold Pharaoh into a project -- detect structure, generate `pharaoh.toml` | +| `@pharaoh.change` | Analyze impact of a change -- trace through needs links and codelinks, produce a Change Document | +| `@pharaoh.trace` | Navigate traceability in any direction -- show everything linked to a need across all levels | +| `@pharaoh.mece` | Gap and redundancy analysis -- find orphans, missing links, MECE violations | +| `@pharaoh.author` | AI-assisted requirement authoring -- create/modify needs with proper IDs, types, and links | +| `@pharaoh.verify` | Validate implementations against requirements -- content-level satisfaction checks | +| `@pharaoh.release` | Release management -- changelog from requirements, traceability coverage metrics | +| `@pharaoh.plan` | Structured implementation planning -- break changes into tasks with workflow enforcement | +| `@pharaoh.spec` | Generate spec from requirements -- read needs hierarchy, record decisions, produce spec with plan table | +| `@pharaoh.decide` | Record design decisions -- create `decision` needs with alternatives, rationale, and traceability links | + +## Recommended Workflow + +``` +@pharaoh.spec -> @pharaoh.decide (for gaps) + -> produces spec doc with plan table + | +@pharaoh.plan -> @pharaoh.change -> @pharaoh.author -> @pharaoh.verify -> @pharaoh.release + -> @pharaoh.mece (optional, for gap analysis) + -> @pharaoh.trace (optional, for exploration) +``` + +## Data Access Tiers + +Agents automatically use the best available data source: + +1. **ubc CLI** (best): Fast, deterministic JSON output. Install from https://ubcode.useblocks.com/ubc/installation.html +2. **ubCode MCP** (VS Code): Real-time indexed data via the ubCode extension. Automatic when the extension is running. +3. **Raw file parsing** (fallback): AI reads RST/MD files directly. Always works, slower on large projects. + +## Configuration + +### Project Configuration + +Agents read need types, link types, and ID settings from: +- `ubproject.toml` (preferred) -- the `[needs]` section +- `conf.py` (fallback) -- `needs_types`, `needs_extra_links`, etc. + +### Pharaoh Configuration (`pharaoh.toml`) + +Optional. Controls Pharaoh's workflow behavior, not sphinx-needs configuration. + +```toml +[pharaoh] +strictness = "advisory" # or "enforcing" + +[pharaoh.workflow] +require_change_analysis = true +require_verification = true +require_mece_on_release = false + +[pharaoh.traceability] +required_links = ["req -> spec", "spec -> impl", "impl -> test"] + +[pharaoh.codelinks] +enabled = true +``` + +### Advisory vs Enforcing Mode + +- **Advisory** (default): Agents suggest the recommended workflow but never block. Tips are shown for skipped steps. +- **Enforcing**: Agents check prerequisites and block if not met (e.g., `@pharaoh.author` requires `@pharaoh.change` first). + +## sphinx-codelinks Integration + +When a project uses sphinx-codelinks, Pharaoh follows codelink references in change analysis and traceability. A change to a requirement surfaces affected code files, not just other requirements. + +## Session State + +Workflow progress is tracked in `.pharaoh/session.json` (ephemeral, gitignored). This enables enforcing mode gates and tracks which needs have been analyzed, authored, and verified. diff --git a/.github/prompts/pharaoh.author.prompt.md b/.github/prompts/pharaoh.author.prompt.md new file mode 100644 index 0000000..1234b39 --- /dev/null +++ b/.github/prompts/pharaoh.author.prompt.md @@ -0,0 +1,23 @@ +--- +agent: pharaoh.author +--- + +# /pharaoh.author + +Author or modify one sphinx-needs artefact — a requirement, an architecture element, a test +case, or a decision — by routing to the right atomic drafting skill based on the project's +artefact catalog. One invocation produces one drafted RST directive with an ID, a parent link, +and a suggested file placement. + +Hand the agent: + +- the **target type** (e.g. `req`, `arch`, `tc`, `decision`), +- a short **draft seed** describing what to author, +- the **parent link** the new artefact will trace to (need-id), and +- any type-specific extras the dispatched drafter needs (e.g. `arch_type`, + `verification_level`). + +The agent picks the matching atomic drafter (`pharaoh-req-draft`, `pharaoh-arch-draft`, +`pharaoh-vplan-draft`, or `pharaoh-decide`), forwards the inputs, and returns the drafted RST +directive plus an authoring summary. Run `@pharaoh.verify` next to check the new artefact +against the substance of its parent. diff --git a/.github/prompts/pharaoh.change.prompt.md b/.github/prompts/pharaoh.change.prompt.md new file mode 100644 index 0000000..7bcd3b1 --- /dev/null +++ b/.github/prompts/pharaoh.change.prompt.md @@ -0,0 +1,3 @@ +--- +agent: pharaoh.change +--- diff --git a/.github/prompts/pharaoh.mece.prompt.md b/.github/prompts/pharaoh.mece.prompt.md new file mode 100644 index 0000000..ebb2eed --- /dev/null +++ b/.github/prompts/pharaoh.mece.prompt.md @@ -0,0 +1,3 @@ +--- +agent: pharaoh.mece +--- diff --git a/.github/prompts/pharaoh.plan.prompt.md b/.github/prompts/pharaoh.plan.prompt.md new file mode 100644 index 0000000..9babb20 --- /dev/null +++ b/.github/prompts/pharaoh.plan.prompt.md @@ -0,0 +1,3 @@ +--- +agent: pharaoh.plan +--- diff --git a/.github/prompts/pharaoh.release.prompt.md b/.github/prompts/pharaoh.release.prompt.md new file mode 100644 index 0000000..ea5d165 --- /dev/null +++ b/.github/prompts/pharaoh.release.prompt.md @@ -0,0 +1,3 @@ +--- +agent: pharaoh.release +--- diff --git a/.github/prompts/pharaoh.trace.prompt.md b/.github/prompts/pharaoh.trace.prompt.md new file mode 100644 index 0000000..250bcd8 --- /dev/null +++ b/.github/prompts/pharaoh.trace.prompt.md @@ -0,0 +1,3 @@ +--- +agent: pharaoh.trace +--- diff --git a/.github/prompts/pharaoh.verify.prompt.md b/.github/prompts/pharaoh.verify.prompt.md new file mode 100644 index 0000000..57d5ee9 --- /dev/null +++ b/.github/prompts/pharaoh.verify.prompt.md @@ -0,0 +1,22 @@ +--- +agent: pharaoh.verify +--- + +# /pharaoh.verify + +Check whether one sphinx-needs artefact actually addresses the substance of every parent it +links to via `:satisfies:` or `:verifies:`. This is a cross-need content check — distinct from +structural MECE (`@pharaoh.mece`), schema-level tailoring review (`@pharaoh.tailor-review`), +and per-axis prose review (`@pharaoh.req-review`, `@pharaoh.arch-review`, +`@pharaoh.vplan-review`). + +Hand the agent: + +- the **need-id** to verify, and +- optionally `transitive: true` to walk the full parent chain rather than just direct parents. + +The agent reads `needs.json`, walks the parent links, scores each (child, parent) pair on a +0-3 ordinal for substantive coverage, and returns a JSON document with per-pair verdicts and +concrete missing aspects. Use the result to decide whether to re-author the body via +`@pharaoh.author`, regenerate it per-axis via `@pharaoh.req-regenerate`, or move on to a +corpus-wide check with `@pharaoh.mece`. From c2a0de392798ab55120baa2f4053f2d30a5fce70 Mon Sep 17 00:00:00 2001 From: Marco Heinemann <marco.heinemann@useblocks.com> Date: Sat, 6 Jun 2026 16:20:05 +0200 Subject: [PATCH 2/9] =?UTF-8?q?=E2=9C=A8=20NEW:=20sphinx-needs=20agile=20t?= =?UTF-8?q?raceability=20via=20sphinx-codelinks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set up sphinx-needs and a lightweight, agile traceability model (aligned with useblocks/ubconnect rather than a heavyweight V-model), wired to the source and test trees with sphinx-codelinks and governed by Pharaoh. Need types (declared in docs/ubproject.toml): - feat user-observable capability (RST) - comp_req testable obligation, :satisfies: a feat (RST) - err failure condition, handled in a feat via :tested_in: / :checked_in: / :mitigated_in: (RST) - impl one-line sphinx-codelinks need in src/, :links: a feat - test one-line need in tests/, :verifies: a comp_req and/or :detects: an err (ISO 26262-8 Part 8 fault detection) Lifecycle open -> in_progress -> implemented, enforced via the status field schema enum. sphinx-codelinks is configured entirely under the [codelinks] table in ubproject.toml (so ubCode consumes the same config), with two one-line-need projects scanning src/ and tests/. Forward need->code trace via :source_doc:; reverse code->need trace via the impl/test needs. The incoming "every feat has an impl / every comp_req has a test" rule is not yet schema-validatable (sphinx-needs#1590); documented in the new docs/source/specs/ section, which also carries a Mermaid diagram of the node/link structure. Pharaoh tailoring (.pharaoh/project/) declares the artefact catalog, id scheme, lifecycle state machine, and per-type review checklists. pharaoh.toml sets advisory mode, reverse-eng gates, and required-link chains. Tooling: sphinx-needs>=8.1, sphinx-codelinks, and sphinxcontrib-mermaid added to the docs dependency group (sphinx-needs moved out of dev so the docs build is self-contained). Verified: docs build clean under `sphinx-build -nW --keep-going`; needs.json has 24 needs (4 feat / 4 comp_req / 4 err / 4 impl / 8 test) with all links resolving; test suite unchanged (106 passed); ruff and taplo clean. --- .gitignore | 7 + .pharaoh/project/artefact-catalog.yaml | 76 +++ .pharaoh/project/checklists/comp_req.md | 19 + .pharaoh/project/checklists/err.md | 26 + .pharaoh/project/checklists/feat.md | 19 + .pharaoh/project/checklists/requirement.md | 16 + .pharaoh/project/checklists/test.md | 22 + .pharaoh/project/id-conventions.yaml | 18 + .pharaoh/project/workflows.yaml | 30 ++ docs/conf.py | 18 + docs/source/index.rst | 1 + docs/source/specs/errors.rst | 67 +++ docs/source/specs/features.rst | 106 ++++ docs/source/specs/index.rst | 22 + docs/source/specs/traceability.rst | 110 ++++ docs/ubproject.toml | 232 ++++++++- pharaoh.toml | 51 ++ pyproject.toml | 9 +- src/sphinx_mounts/config.py | 1 + src/sphinx_mounts/extension.py | 1 + src/sphinx_mounts/mounter.py | 2 + tests/test_mounting.py | 8 + uv.lock | 560 ++++++++++++++++++++- 23 files changed, 1408 insertions(+), 13 deletions(-) create mode 100644 .pharaoh/project/artefact-catalog.yaml create mode 100644 .pharaoh/project/checklists/comp_req.md create mode 100644 .pharaoh/project/checklists/err.md create mode 100644 .pharaoh/project/checklists/feat.md create mode 100644 .pharaoh/project/checklists/requirement.md create mode 100644 .pharaoh/project/checklists/test.md create mode 100644 .pharaoh/project/id-conventions.yaml create mode 100644 .pharaoh/project/workflows.yaml create mode 100644 docs/source/specs/errors.rst create mode 100644 docs/source/specs/features.rst create mode 100644 docs/source/specs/index.rst create mode 100644 docs/source/specs/traceability.rst create mode 100644 pharaoh.toml diff --git a/.gitignore b/.gitignore index 694344e..06c5758 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,10 @@ bazel-* # Local-only AI agent workflow artifacts (specs, plans, etc.) — never commit. docs/superpowers/ /prompts.md + +# Pharaoh ephemeral state (do not commit). Project tailoring at +# .pharaoh/project/ IS committed and shared with the team. +.pharaoh/runs/ +.pharaoh/plans/ +.pharaoh/session.json +.pharaoh/cache/ diff --git a/.pharaoh/project/artefact-catalog.yaml b/.pharaoh/project/artefact-catalog.yaml new file mode 100644 index 0000000..7fcddbe --- /dev/null +++ b/.pharaoh/project/artefact-catalog.yaml @@ -0,0 +1,76 @@ +# artefact-catalog.yaml — per-type contract for every declared need type. +# +# Validates against schemas/artefact-catalog.schema.json. +# +# Five types: +# feat capability +# comp_req testable obligation, :satisfies: a feat +# err failure condition, handled in a feat +# impl code marker, :links: a feat +# test :verifies: a comp_req and/or :detects: an err +# impl and test are authored in code as sphinx-codelinks one-line needs (see +# the [codelinks] tables in docs/ubproject.toml), not via RST. +# +# Forward traceability is enforced via `required_metadata_fields: source_doc` +# on the RST types (need -> code). Reverse traceability (code -> feat / +# comp_req) is carried by the impl/test one-line needs' outgoing links; the +# *incoming* rule ("every feat has an impl", "every comp_req has a test") is +# NOT representable here — see https://github.com/useblocks/sphinx-needs/issues/1590. +# +# OR-rules are left to checklists, not encoded as required_links (AND semantics): +# - err: at least one of tested_in / checked_in / mitigated_in. +# - test: at least one of verifies / detects. + +feat: + required_fields: [id, status, title, source_doc] + optional_fields: [issue] + lifecycle: [open, in_progress, implemented] + required_links: [] + optional_links: [] + required_metadata_fields: [status, source_doc] + required_roles: [] + +comp_req: + required_fields: [id, status, title, satisfies, source_doc] + optional_fields: [issue] + lifecycle: [open, in_progress, implemented] + required_links: [satisfies] + optional_links: [] + required_metadata_fields: [status, source_doc] + required_roles: [] + +err: + required_fields: [id, status, title, source_doc] + optional_fields: [issue, severity] + lifecycle: [open, in_progress, implemented] + required_links: [] + optional_links: [tested_in, checked_in, mitigated_in] + required_metadata_fields: [status, source_doc, severity] + required_roles: [] + +# impl needs are authored in src/ as sphinx-codelinks one-line needs. Every +# impl carries a non-empty built-in `links` to the feat it implements — +# guaranteed by the mandatory one-line `links` field and checked by the +# "impl -> feat" chain in pharaoh.toml (kept out of required_links here, which +# lists named extra-link relations). +impl: + required_fields: [id, status, title] + optional_fields: [] + lifecycle: [open, in_progress, implemented] + required_links: [] + optional_links: [] + required_metadata_fields: [status] + required_roles: [] + +# test needs are authored in tests/ as one-line needs (second [codelinks] +# project). verifies (-> comp_req) and detects (-> err) are optional_links; the +# "at least one of verifies/detects" rule is a checklist item, not a +# required_link (which would force both). +test: + required_fields: [id, status, title] + optional_fields: [] + lifecycle: [open, in_progress, implemented] + required_links: [] + optional_links: [verifies, detects] + required_metadata_fields: [status] + required_roles: [] diff --git a/.pharaoh/project/checklists/comp_req.md b/.pharaoh/project/checklists/comp_req.md new file mode 100644 index 0000000..1291452 --- /dev/null +++ b/.pharaoh/project/checklists/comp_req.md @@ -0,0 +1,19 @@ +--- +name: Component requirement review checklist +applies_to: comp_req +axes: + - clarity + - correctness + - traceability +--- + +# Component requirement review checklist + +- [ ] ID matches `id_regex` (`^(FEAT|CREQ|ERR|IMPL|TEST)_[A-Z0-9_]*[0-9]{3}$`). +- [ ] Title states a single, testable obligation. +- [ ] Body is a "shall" clause — unambiguous, verifiable, free of "and/or" fusion. +- [ ] Status is one of `open`, `in_progress`, `implemented`. +- [ ] `:satisfies:` points at exactly the feature(s) this requirement decomposes. +- [ ] `:source_doc:` names the implementing source file(s) (forward trace). +- [ ] Verified by at least one `test` need (`test :verifies: <this id>`), + authored in the test suite as a one-line need. diff --git a/.pharaoh/project/checklists/err.md b/.pharaoh/project/checklists/err.md new file mode 100644 index 0000000..10922cd --- /dev/null +++ b/.pharaoh/project/checklists/err.md @@ -0,0 +1,26 @@ +--- +name: Error review checklist +applies_to: err +axes: + - clarity + - correctness + - traceability +--- + +# Error review checklist + +- [ ] ID matches `id_regex` (`^(FEAT|CREQ|ERR|IMPL|TEST)_[A-Z0-9_]*[0-9]{3}$`). +- [ ] Title names the observable failure condition (not its cause). +- [ ] Status is one of `open`, `in_progress`, `implemented`. +- [ ] Body explains the condition, when it arises, and the consequence if unguarded. +- [ ] **At least one** handling relation is set — `:tested_in:`, + `:checked_in:`, or `:mitigated_in:` — each pointing at the feature(s) + where the handling lives. (This OR-rule is enforced here, not via + `required_links`, which has AND semantics.) +- [ ] Relation choice matches reality: `:checked_in:` = raises/guards at runtime; + `:mitigated_in:` = graceful degradation by design; `:tested_in:` = + correctness rests on tests with no runtime guard or fallback. +- [ ] `:severity:` is set (`low` / `medium` / `high`). +- [ ] `:source_doc:` names the source file(s) where the condition is handled. +- [ ] Where a test exercises the condition, a `test` need `:detects:` this + error. diff --git a/.pharaoh/project/checklists/feat.md b/.pharaoh/project/checklists/feat.md new file mode 100644 index 0000000..b971b58 --- /dev/null +++ b/.pharaoh/project/checklists/feat.md @@ -0,0 +1,19 @@ +--- +name: Feature review checklist +applies_to: feat +axes: + - clarity + - traceability +--- + +# Feature review checklist + +- [ ] ID matches `id_regex` (`^(FEAT|CREQ|ERR|IMPL|TEST)_[A-Z0-9_]*[0-9]{3}$`). +- [ ] Title names a single user-observable capability (not an implementation detail). +- [ ] Status is one of `open`, `in_progress`, `implemented`. +- [ ] Body describes the capability in user terms (what, when, for whom). +- [ ] `:source_doc:` names the implementing source file(s) (forward trace). +- [ ] At least one code-authored `impl` need `:links:` this feature + (reverse trace — advisory; see traceability.rst and SN #1590). +- [ ] At least one `comp_req` `:satisfies:` this feature. +- [ ] No semantic overlap with another feature. diff --git a/.pharaoh/project/checklists/requirement.md b/.pharaoh/project/checklists/requirement.md new file mode 100644 index 0000000..c1c2822 --- /dev/null +++ b/.pharaoh/project/checklists/requirement.md @@ -0,0 +1,16 @@ +--- +name: Requirement review checklist (alias) +applies_to: comp_req +axes: + - clarity + - correctness + - traceability +--- + +# Requirement review checklist + +This project's requirement-shaped artefact is the **component requirement** +(`comp_req`). Pharaoh skills that reference `checklists/requirement.md` by +its well-known filename are routed here so the interop contract stays stable. + +For the actual review criteria see [comp_req.md](comp_req.md). diff --git a/.pharaoh/project/checklists/test.md b/.pharaoh/project/checklists/test.md new file mode 100644 index 0000000..8eb0e76 --- /dev/null +++ b/.pharaoh/project/checklists/test.md @@ -0,0 +1,22 @@ +--- +name: Test review checklist +applies_to: test +axes: + - clarity + - correctness + - traceability +--- + +# Test review checklist + +- [ ] ID matches `id_regex` (`^(FEAT|CREQ|ERR|IMPL|TEST)_[A-Z0-9_]*[0-9]{3}$`). +- [ ] Title states what the test checks ("Verifies …" / "Detects …"). +- [ ] Status is one of `open`, `in_progress`, `implemented`. +- [ ] **At least one** of `:verifies:` (→ comp_req) or `:detects:` (→ err) is + non-empty. (OR-rule — enforced here, not via `required_links`.) +- [ ] `:verifies:` targets are component requirements; `:detects:` targets are + errors — no cross-type mistakes. +- [ ] The one-line need sits in the test file next to the test it represents, + so its source location *is* the test (sphinx-codelinks local-url). +- [ ] An error-only test writes `[]` for the mandatory `verifies` field and the + error id for `detects`. diff --git a/.pharaoh/project/id-conventions.yaml b/.pharaoh/project/id-conventions.yaml new file mode 100644 index 0000000..46252f7 --- /dev/null +++ b/.pharaoh/project/id-conventions.yaml @@ -0,0 +1,18 @@ +# id-conventions.yaml — declared prefixes and the canonical ID regex. +# +# Validates against schemas/id-conventions.schema.json. +# +# `prefixes` mirrors [[needs.types]] in docs/ubproject.toml. `id_regex` +# matches the same shape declared there ([needs] id_regex): one of the three +# type prefixes, a SCREAMING_SNAKE domain token, and a 3-digit ordinal — +# e.g. FEAT_DIRMOUNT_001, CREQ_TOMLCONF_001, ERR_COLLISION_001. + +prefixes: + feat: FEAT_ + comp_req: CREQ_ + err: ERR_ + impl: IMPL_ + test: TEST_ + +id_regex: "^(FEAT|CREQ|ERR|IMPL|TEST)_[A-Z0-9_]*[0-9]{3}$" +separator: "_" diff --git a/.pharaoh/project/workflows.yaml b/.pharaoh/project/workflows.yaml new file mode 100644 index 0000000..093416c --- /dev/null +++ b/.pharaoh/project/workflows.yaml @@ -0,0 +1,30 @@ +# workflows.yaml — Pharaoh project-wide lifecycle state machine. +# +# Validates against the Pharaoh schemas/workflows.schema.json. +# +# Agile lifecycle: a need is identified (open), worked on (in_progress), and +# done (implemented). There is deliberately no terminal "closed"/"deprecated" +# state — an implemented need is finished, and a need that no longer applies +# is deleted from the repository rather than retired in place. The same three +# states are enforced at build time via the status field enum in +# docs/ubproject.toml ([needs.fields.status]). + +lifecycle_states: + - open + - in_progress + - implemented + +transitions: + - from: open + to: in_progress + requires: [] + - from: in_progress + to: implemented + requires: [] + # Rework paths (e.g. an implemented need reopened by a change). + - from: in_progress + to: open + requires: [] + - from: implemented + to: in_progress + requires: [] diff --git a/docs/conf.py b/docs/conf.py index 71bad71..8cdd09a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,8 +17,26 @@ "sphinx_design", "myst_parser", "sphinx.ext.intersphinx", + "sphinxcontrib.mermaid", + # sphinx_needs MUST be loaded before sphinx_codelinks: the codelinks + # setup hook fails fast if ``sphinx_needs`` is not already registered. + "sphinx_needs", + "sphinx_codelinks", ] +# Sphinx-Needs configuration is declared in ubproject.toml. The TOML form +# is consumable by ubCode and other static readers without evaluating +# this file. See https://sphinx-needs.readthedocs.io/en/latest/configuration.html#needs-from-toml +needs_from_toml = "ubproject.toml" + +# -- sphinx-codelinks: code -> need traceability ---------------------------- +# All sphinx-codelinks configuration lives under the [codelinks] table in +# ubproject.toml, so that ubCode and other static readers consume exactly the +# same config without evaluating this file. ubCode currently supports the +# one-line-need form (``@ title, id, [links]``) configured this way. The only +# thing conf.py does is point the extension at that table: +src_trace_config_from_toml = "ubproject.toml" + exclude_patterns: list[str] = [] templates_path = ["source/_static/_templates/furo"] diff --git a/docs/source/index.rst b/docs/source/index.rst index 3751505..f24e4ca 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -55,6 +55,7 @@ Use cases: usage integration bazel + specs/index changelog .. _related-projects: diff --git a/docs/source/specs/errors.rst b/docs/source/specs/errors.rst new file mode 100644 index 0000000..b4b3329 --- /dev/null +++ b/docs/source/specs/errors.rst @@ -0,0 +1,67 @@ +Errors +====== + +Each **error** (``err``) is a failure condition tied to one or more +features. The link relation records *how* the error is addressed: + +- ``:checked_in:`` — a **runtime check** in the feature detects the + condition and fails loudly (raises), so it cannot corrupt the output. +- ``:mitigated_in:`` — the feature **tolerates** the condition by design + (e.g. emits a warning and continues) instead of failing. +- ``:tested_in:`` — correctness rests on **test coverage**; there is no + dedicated runtime guard or graceful fallback for the condition. + +An error may carry more than one relation (e.g. both runtime-checked and +tested). ``:severity:`` records the impact if the condition were hit +unguarded. Where a test exercises the condition, a ``test`` need +``:detects:`` this error (see :doc:`traceability`). + +.. err:: Mounted docname collides with an existing document + :id: ERR_COLLISION_001 + :status: implemented + :checked_in: FEAT_DIRMOUNT_001, FEAT_FILEMOUNT_001 + :severity: high + :source_doc: src/sphinx_mounts/mounter.py + + Two mounts — or a mount and a host document — can produce the same + docname, which would silently shadow content. Registration detects the + collision and raises ``ValueError`` naming both contributors, so the + build fails loudly instead of dropping a document. + +.. err:: attach_to targets a non-existent host document + :id: ERR_DEADATTACH_001 + :status: implemented + :mitigated_in: FEAT_TOCWIRE_001 + :severity: low + :source_doc: src/sphinx_mounts/extension.py + + If ``attach_to`` names a docname that does not exist there is nothing to + wire. Rather than fail the build, the consistency check emits a warning + and continues — the misconfiguration is surfaced without blocking + unrelated output. This is a deliberate mitigation, not a runtime guard. + +.. err:: Incremental build serves a stale mounted document + :id: ERR_STALEMOUNT_001 + :status: implemented + :tested_in: FEAT_DIRMOUNT_001 + :severity: medium + :source_doc: src/sphinx_mounts/mounter.py + + On an incremental rebuild a changed mounted file must be re-read, and an + unchanged one must not trigger needless work. There is no runtime + assertion for this property; its correctness rests on the incremental + test cases that exercise both directions. + +.. err:: toctree_index out of range for the host document + :id: ERR_TOCIDX_001 + :status: implemented + :checked_in: FEAT_TOCWIRE_001 + :tested_in: FEAT_TOCWIRE_001 + :severity: medium + :source_doc: src/sphinx_mounts/extension.py + + A mount can request a ``toctree_index`` larger than the number of + toctrees present in the host document. Rather than silently leave the + mount unreferenced, the extension raises ``ExtensionError``. The + behaviour is both runtime-checked and covered by a test — an example of + an error carrying more than one handling relation. diff --git a/docs/source/specs/features.rst b/docs/source/specs/features.rst new file mode 100644 index 0000000..dad890e --- /dev/null +++ b/docs/source/specs/features.rst @@ -0,0 +1,106 @@ +Features & component requirements +================================= + +Each **feature** (``feat``) is a user-observable capability. Each +**component requirement** (``comp_req``) decomposes a feature into a +testable obligation and ``:satisfies:`` it. The ``:source_doc:`` field is +the forward trace to the implementing source file; each requirement is +verified by one or more ``test`` needs authored in the suite (see +:doc:`traceability`). + +Directory mounting +------------------ + +.. feat:: Directory mounting + :id: FEAT_DIRMOUNT_001 + :status: implemented + :source_doc: src/sphinx_mounts/mounter.py + + sphinx-mounts mounts an external directory into the host Sphinx project. + Every file under the directory whose extension matches the project's + configured ``source_suffix`` is registered as a document at the + ``mount_at`` prefix, with its path relative to the directory (minus the + matched suffix) as the docname tail. Sources are read in place — never + copied or symlinked. + +.. comp_req:: Suffix-matching files under a mounted directory are discovered + :id: CREQ_DIRMOUNT_001 + :status: implemented + :satisfies: FEAT_DIRMOUNT_001 + :source_doc: src/sphinx_mounts/mounter.py + + In directory mode, discovery shall register, for every file under + ``mount.dir`` whose name ends with one of ``project.source_suffix``, a + docname formed by joining ``mount_at`` with the file's path relative to + ``dir`` minus the matched suffix. Discovery order shall be deterministic + (paths sorted) regardless of filesystem walk order. + +File-list mounting +------------------ + +.. feat:: File-list mounting + :id: FEAT_FILEMOUNT_001 + :status: implemented + :source_doc: src/sphinx_mounts/mounter.py + + sphinx-mounts mounts an explicit list of individual files. Each file's + basename (minus the matched suffix) becomes a docname under ``mount_at``, + forming a flat namespace; subdirectories in the file paths are ignored. + +.. comp_req:: File-list entries become flat-namespace docnames + :id: CREQ_FILEMOUNT_001 + :status: implemented + :satisfies: FEAT_FILEMOUNT_001 + :source_doc: src/sphinx_mounts/mounter.py + + In file-list mode, the extension shall register, for each path in + ``mount.files``, a docname formed by joining ``mount_at`` with the file's + basename minus the matched ``source_suffix``. A file whose suffix is not + a registered source suffix shall be rejected with a clear error. + +Declarative TOML configuration +------------------------------ + +.. feat:: Declarative TOML configuration + :id: FEAT_TOMLCONF_001 + :status: implemented + :source_doc: src/sphinx_mounts/config.py + + Mount entries can be declared in ``ubproject.toml`` as a top-level + ``[[mounts]]`` array, so non-Python tooling (ubCode, IDEs, CI gates) can + read the configuration without evaluating ``conf.py``. The TOML list + replaces any ``mounts`` value set in ``conf.py``. + +.. comp_req:: TOML paths are anchored to the TOML file's directory + :id: CREQ_TOMLCONF_001 + :status: implemented + :satisfies: FEAT_TOMLCONF_001 + :source_doc: src/sphinx_mounts/config.py + + Relative ``dir`` and ``files`` paths declared in the TOML file shall be + anchored to the directory containing that TOML file (not to ``confdir``); + absolute paths shall be left unchanged. + +Automatic toctree wiring +------------------------ + +.. feat:: Automatic toctree wiring + :id: FEAT_TOCWIRE_001 + :status: implemented + :source_doc: src/sphinx_mounts/extension.py + + A mount can request that its entry document be wired into a host + document's toctree via ``attach_to``. sphinx-mounts appends the entry to + the selected toctree (``toctree_index``), or creates a new toctree + beneath the first section when the host document has none. + +.. comp_req:: attach_to wires the entry doc into the host toctree + :id: CREQ_TOCWIRE_001 + :status: implemented + :satisfies: FEAT_TOCWIRE_001 + :source_doc: src/sphinx_mounts/extension.py + + When a mount sets ``attach_to`` to an existing host docname, the + extension shall append ``{mount_at}/{entry_doc}`` to the toctree at + ``toctree_index`` in that document — creating a toctree if none exists — + and shall be idempotent when the entry is already referenced. diff --git a/docs/source/specs/index.rst b/docs/source/specs/index.rst new file mode 100644 index 0000000..03f36a0 --- /dev/null +++ b/docs/source/specs/index.rst @@ -0,0 +1,22 @@ +.. _specs: + +Specifications & traceability +============================= + +This section is a lightweight, agile traceability model for sphinx-mounts, +authored with `Sphinx-Needs`_. It captures the extension's user-observable +**features**, the **component requirements** that decompose them, and the +**errors** (failure conditions) each feature guards against — together with +how every error is addressed (tested, runtime-checked, or mitigated) and how +each need traces to the source code that realises it. + +The model is intentionally small. It follows the same agile shape useblocks +uses elsewhere (feature → component requirement) rather than a heavyweight +V-model, and adds an ``err`` type for failure conditions. + +.. toctree:: + :maxdepth: 2 + + features + errors + traceability diff --git a/docs/source/specs/traceability.rst b/docs/source/specs/traceability.rst new file mode 100644 index 0000000..8dfe6ef --- /dev/null +++ b/docs/source/specs/traceability.rst @@ -0,0 +1,110 @@ +Traceability +============ + +The needs in this section form a small, closed traceability loop. Every edge +is an *outgoing* link (so it is schema-validatable), and the nodes are +authored in three places: RST (``feat``, ``comp_req``, ``err``), the package +source (``impl``), and the test suite (``test``). + +Need & link structure +---------------------- + +.. mermaid:: + + flowchart LR + test["test (TEST_)"] + creq["comp_req (CREQ_)"] + feat["feat (FEAT_)"] + err["err (ERR_)"] + impl["impl (IMPL_)"] + + test -->|verifies| creq + test -.->|detects| err + creq -->|satisfies| feat + impl -->|links| feat + err -->|tested_in| feat + err -->|checked_in| feat + err -->|mitigated_in| feat + +Solid edges are always present for that need type; the dotted ``detects`` edge +is optional — only tests that exercise an error carry it. ``feat`` is the hub: +``comp_req``, ``impl``, and ``err`` all point at it, and ``test`` points at +``comp_req`` / ``err``. + +Forward trace — need → code +--------------------------- + +Every ``feat``, ``comp_req``, and ``err`` carries a ``:source_doc:`` field +naming the source file that realises it. This direction is part of each +need's own data, so it is **schema-validatable**: a project can require, via +sphinx-needs schema validation, that (say) every ``comp_req`` set a +non-empty ``source_doc``. + +.. needtable:: Features and the source that implements them + :types: feat + :columns: id, title, source_doc, status + :style: table + +.. needtable:: Errors, how they are handled, and where + :types: err + :columns: id, title, severity, tested_in, checked_in, mitigated_in + :style: table + +Reverse trace — code → needs +---------------------------- + +Both ``impl`` and ``test`` needs are authored as `sphinx-codelinks`_ +**one-line needs** in comments next to the code, and discovered at build time +(configured under the ``[codelinks]`` table in ``ubproject.toml``, which +ubCode reads as well). One ``src-trace`` directive per project renders them. + +Implementation needs (``src/``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each ``impl`` need ``:links:`` the feature it implements: + +.. src-trace:: + :project: sphinx_mounts + +- ``src/sphinx_mounts/mounter.py`` → :need:`IMPL_DIRMOUNT_001`, + :need:`IMPL_FILEMOUNT_001` +- ``src/sphinx_mounts/config.py`` → :need:`IMPL_TOMLCONF_001` +- ``src/sphinx_mounts/extension.py`` → :need:`IMPL_TOCWIRE_001` + +.. needtable:: Implementation needs and the feature each links to + :types: impl + :columns: id, title, links, status + :style: table + +Test needs (``tests/``) +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each ``test`` need ``:verifies:`` the component requirement(s) it covers and, +where it exercises a failure condition, ``:detects:`` the error (ISO 26262-8 +Part 8 fault detection): + +.. src-trace:: + :project: sphinx_mounts_tests + +.. needtable:: Test needs, the requirements they verify, and errors they detect + :types: test + :columns: id, title, verifies, detects, status + :style: table + +A successful build logs ``Oneline needs extracted: N`` (N ≥ 1) for each of the +two projects, confirming the source and test trees define needs linked into +the catalogue. + +.. note:: Backlink coverage is not yet schema-validatable (sphinx-needs #1590) + + Every edge above is an *outgoing* link (``impl → feat``, ``test → + comp_req``/``err``), which IS schema-validatable. What we cannot yet express + is the *inverse*, incoming rule — e.g. "every ``feat`` must have at least + one ``impl`` linking to it" or "every ``comp_req`` must be verified by at + least one ``test``." sphinx-needs schema validation currently constrains a + need's own fields and its **outgoing** links only; it cannot assert anything + about a need's **incoming** links / backlinks. + + Enforcing that via schema validation would be the canonical, declarative + approach — far better than an advisory check. It is tracked upstream at + `sphinx-needs#1590 <https://github.com/useblocks/sphinx-needs/issues/1590>`__. diff --git a/docs/ubproject.toml b/docs/ubproject.toml index 835107b..938e794 100644 --- a/docs/ubproject.toml +++ b/docs/ubproject.toml @@ -2,11 +2,8 @@ # importantly the ubCode language server (https://ubcode.useblocks.com/), # but also any other static reader (IDE plugins, indexers, CI gates) # that wants to understand this project's documentation setup without -# evaluating ``conf.py``. -# -# This project does *not* use sphinx-needs, but the file is still useful: -# it tells ubCode which schema to validate against and how to behave -# while editing. +# evaluating ``conf.py``. The TOML form also drives sphinx-needs itself +# via ``needs_from_toml = "ubproject.toml"`` set in ``conf.py``. "$schema" = "https://ubcode.useblocks.com/ubproject.schema.json" # Make the language server reindex the project automatically whenever a @@ -14,3 +11,228 @@ # the latest edits without having to trigger a manual rebuild. [server] index_on_save = true + +# --------------------------------------------------------------------------- +# Sphinx-Needs configuration — lightweight, agile traceability. +# +# The model deliberately mirrors the agile chain used by useblocks/ubconnect +# (feature -> component requirement) rather than a heavyweight V-model, plus +# an ``err`` type for failure conditions and an ``impl`` type that ties the +# source code to the features it implements: +# +# feat — a user-observable capability the extension provides. +# comp_req — a testable component requirement; ``:satisfies:`` a feat. +# err — an error: a failure condition tied to one or more feats. +# (The directive is ``err`` rather than ``error`` to avoid +# shadowing the built-in docutils ``.. error::`` admonition.) +# Each error declares how it is addressed via one (or more) +# of three link relations: +# :tested_in: guarded by a test in the feature's suite +# :checked_in: guarded by a runtime check in the feature +# :mitigated_in: tolerated by design (e.g. a warning + skip) +# impl — an implementation marker authored as a sphinx-codelinks +# one-line need INSIDE the source (``@ title, IMPL_id, +# [FEAT_id]``); created at build time from the [codelinks] +# config below and ``:links:`` the feature it implements. +# Not hand-written in RST. +# test — a test, authored the same way INSIDE the test suite +# (``@ title, TEST_id, [comp_req], [err]``); ``:verifies:`` the +# component requirement(s) it covers and/or ``:detects:`` the +# error(s) it checks for (ISO 26262-8 fault detection). +# +# Lifecycle is ``open -> in_progress -> implemented`` (see +# ``.pharaoh/project/workflows.yaml``). There is no terminal "closed": an +# implemented need is done, and a need that no longer applies is deleted +# from the repository rather than retired in place. +# +# Traceability is a loop of *outgoing* links (all schema-validatable): +# test -> comp_req (:verifies:) and test -> err (:detects:); comp_req -> feat +# (:satisfies:); impl -> feat (:links:); err -> feat (handling). What is NOT +# yet expressible is the *incoming* rule "every feat must have at least one +# impl linking to it" — sphinx-needs schema validation cannot constrain +# incoming links / backlinks yet +# (https://github.com/useblocks/sphinx-needs/issues/1590). The ``source_doc`` +# field additionally records need -> code paths. See +# docs/source/specs/traceability.rst. +# --------------------------------------------------------------------------- +[needs] +build_json = true + +# Force the author to set an ID by hand. A title-derived auto-ID would +# break every inbound link the moment someone reworded the title. +id_required = true + +# Allowed ID shape: one of the declared type prefixes, a SCREAMING_SNAKE +# domain token, and a 3-digit ordinal — e.g. ``FEAT_DIRMOUNT_001``, +# ``ERR_COLLISION_001``, ``TEST_TOCIDX_001``. Kept in sync with +# ``.pharaoh/project/id-conventions.yaml``. +id_regex = "(FEAT|CREQ|ERR|IMPL|TEST)_[A-Z0-9_]*[0-9]{3}" + +# Allowed lifecycle states, enforced via the built-in ``status`` field's +# schema enum. (The older ``[[needs.statuses]]`` table is deprecated in +# sphinx-needs 8.x.) A ``:status:`` outside this set becomes a +# schema-validation finding — and an error under ``sphinx-build -W``. +[needs.fields.status] +description = "Lifecycle state: open -> in_progress -> implemented." +schema = { type = "string", enum = ["open", "in_progress", "implemented"] } + +# --- Custom fields (sphinx-needs 8.x [needs.fields.<name>] shape) --------- +[needs.fields.source_doc] +description = "Path(s) under the repo root of the source file(s) that realise this need (comma-separated). Forward need -> code trace." +schema = { type = "string" } +nullable = true +default = "" + +[needs.fields.issue] +description = "GitHub issue number tracking the underlying work, if any." +schema = { type = "string" } +nullable = true +default = "" + +[needs.fields.severity] +description = "For errors: impact if the condition is hit unguarded — one of low / medium / high." +schema = { type = "string" } +nullable = true +default = "" + +# --- Link types (sphinx-needs 8.x [needs.links.<name>] shape) ------------- +[needs.links.satisfies] +description = "comp_req -> feat: the requirement satisfies (decomposes) the feature." +incoming = "is satisfied by" +outgoing = "satisfies" + +[needs.links.tested_in] +description = "error -> feat: the error is guarded by a test in the feature's test suite." +incoming = "tested errors" +outgoing = "tested in" + +[needs.links.checked_in] +description = "error -> feat: the error is guarded by a runtime check inside the feature." +incoming = "runtime-checked errors" +outgoing = "checked in" + +[needs.links.mitigated_in] +description = "error -> feat: the error is tolerated by design in the feature (graceful degradation)." +incoming = "mitigated errors" +outgoing = "mitigated in" + +[needs.links.verifies] +description = "test -> comp_req: the test verifies (exercises) the requirement." +incoming = "verified by" +outgoing = "verifies" + +[needs.links.detects] +description = "test -> err: the test checks for / detects the error condition (ISO 26262-8 fault detection)." +incoming = "detected by" +outgoing = "detects" + +# --- Need types ------------------------------------------------------------ +[[needs.types]] +directive = "feat" +title = "Feature" +prefix = "FEAT_" +color = "#BFD8D2" +style = "node" + +[[needs.types]] +directive = "comp_req" +title = "Component Requirement" +prefix = "CREQ_" +color = "#FEDCD2" +style = "node" + +[[needs.types]] +directive = "err" +title = "Error" +prefix = "ERR_" +color = "#F5B7B1" +style = "node" + +# Authored in source code as sphinx-codelinks one-line needs (see [codelinks] +# below), not via an RST directive — but still a first-class need type. +[[needs.types]] +directive = "impl" +title = "Implementation" +prefix = "IMPL_" +color = "#DF744A" +style = "node" + +# Authored in the test suite as sphinx-codelinks one-line needs (second +# [codelinks] project below), not via an RST directive. +[[needs.types]] +directive = "test" +title = "Test" +prefix = "TEST_" +color = "#DarkTurquoise" +style = "node" + +# --------------------------------------------------------------------------- +# sphinx-codelinks configuration. +# +# Read by sphinx-codelinks AND ubCode because ``conf.py`` sets +# ``src_trace_config_from_toml = "ubproject.toml"``. ubCode currently supports +# the one-line-need form configured this way. Two projects are scanned: +# +# sphinx_mounts — src/, one-line ``impl`` needs: +# # @ <title>, <IMPL_id>, [<feat-id>, ...] +# each ``:links:`` the feature(s) it implements. +# sphinx_mounts_tests — tests/, one-line ``test`` needs: +# # @ <title>, <TEST_id>, [<comp_req>], [<err>] +# each ``:verifies:`` requirement(s) and/or +# ``:detects:`` error(s). +# +# In both, ``type`` and ``status`` come from field defaults, so they are never +# written in the comment — the author writes only the title, id, and links. +# --------------------------------------------------------------------------- +[codelinks] +# Copy each marked source file into the build and cross-link the marked line +# to the need it creates (and back), so the HTML carries source <-> need both +# ways. +set_local_url = true + +[codelinks.projects.sphinx_mounts.source_discover] +src_dir = "../src/sphinx_mounts" # relative to confdir (the docs/ directory) +comment_type = "python" +gitignore = true + +[codelinks.projects.sphinx_mounts.analyse] +get_oneline_needs = true +get_need_id_refs = false + +# One-line field order. Parsing is POSITIONAL and defaults fill only TRAILING +# omitted fields, so the two defaulted fields (type, status) must come last — +# which lets authors write just ``@ title, IMPL_id, [feat-ids]``. The first +# three (title, id, links) have no default and are therefore mandatory. +[codelinks.projects.sphinx_mounts.analyse.oneline_comment_style] +needs_fields = [ + { name = "title" }, + { name = "id" }, + { name = "links", type = "list[str]" }, + { name = "type", default = "impl" }, + { name = "status", default = "implemented" }, +] + +# Second project: the test suite. Same one-line mechanism, different need type +# and link fields. +[codelinks.projects.sphinx_mounts_tests.source_discover] +src_dir = "../tests" # relative to confdir (the docs/ directory) +comment_type = "python" +gitignore = true + +[codelinks.projects.sphinx_mounts_tests.analyse] +get_oneline_needs = true +get_need_id_refs = false + +# Two list fields: ``verifies`` (-> comp_req) is mandatory — write ``[]`` when +# a test only detects an error; ``detects`` (-> err) is optional. ``type`` and +# ``status`` trail with defaults so they are never written in the comment. +[codelinks.projects.sphinx_mounts_tests.analyse.oneline_comment_style] +needs_fields = [ + { name = "title" }, + { name = "id" }, + { name = "verifies", type = "list[str]" }, + { name = "detects", type = "list[str]", default = [ + ] }, + { name = "type", default = "test" }, + { name = "status", default = "implemented" }, +] diff --git a/pharaoh.toml b/pharaoh.toml new file mode 100644 index 0000000..83ff3dc --- /dev/null +++ b/pharaoh.toml @@ -0,0 +1,51 @@ +# Pharaoh project configuration. +# +# Pharaoh wraps a sphinx-needs project with authoring, review, and audit +# skills. This file controls *Pharaoh's* behaviour only — need types, link +# options, custom fields, statuses, and id_regex live in +# ``docs/ubproject.toml`` and are not duplicated here. + +[pharaoh] +# advisory — Pharaoh suggests the recommended workflow but never blocks. +# enforcing — Pharaoh checks prerequisites before each skill and blocks if +# they are not met. +strictness = "advisory" + +[pharaoh.id_scheme] +# IDs are <TYPE-PREFIX>_<DOMAIN>_<NNN>, e.g. FEAT_DIRMOUNT_001, +# CREQ_TOMLCONF_001, ERR_COLLISION_001. The authoritative, machine-checkable +# regex lives in .pharaoh/project/id-conventions.yaml; this pattern is the +# descriptive hint used by pharaoh-id-allocate. +pattern = "{PREFIX}{DOMAIN}_{NUMBER}" +auto_increment = true + +[pharaoh.workflow] +mode = "reverse-eng" +# Reverse-engineering an existing codebase: needs are being authored from +# code that already exists. Verification matters from day one; change-analysis +# and the MECE release gate stay off until the catalogue stabilises. Tighten +# via pharaoh-gate-advisor as the catalogue grows. +require_change_analysis = false +require_verification = true +require_mece_on_release = false + +[pharaoh.traceability] +# "source-type -> target-type": every need of source-type must carry at least +# one outgoing link that reaches a target-type need. +required_links = [ + "comp_req -> feat", # every comp_req :satisfies: a feat + "err -> feat", # every err reaches a feat via one of + # :tested_in: / :checked_in: / :mitigated_in: + # (the at-least-one-handling rule) + "impl -> feat", # every code-authored impl :links: the feat it implements + # test -> comp_req / err is intentionally NOT a hard chain: a test + # :verifies: a comp_req OR :detects: an err (at-least-one), which a single + # required chain cannot express. Enforced via the test review checklist. +] + +[pharaoh.codelinks] +# sphinx-codelinks is loaded by docs/conf.py and configured under the +# [codelinks] table in docs/ubproject.toml. Two one-line-need projects: +# ``impl`` needs in src/ (:links: a feat) and ``test`` needs in tests/ +# (:verifies: a comp_req and/or :detects: an err). +enabled = true diff --git a/pyproject.toml b/pyproject.toml index 8e493a8..0023838 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,14 @@ testing = [ # project loads `myst_parser`. "myst-parser>=4.0.0", ] -docs = ["furo>=2024.5.6", "myst-parser>=4.0.0", "sphinx-design>=0.6.1"] +docs = [ + "furo>=2024.5.6", + "myst-parser>=4.0.0", + "sphinx-codelinks>=1.2.0", + "sphinx-design>=0.6.1", + "sphinx-needs>=8.1", + "sphinxcontrib-mermaid>=2.0.2", +] ty = ["ty>=0.0.37"] ruff = ["ruff>=0.8.0"] diff --git a/src/sphinx_mounts/config.py b/src/sphinx_mounts/config.py index 23f20e5..a4854bd 100644 --- a/src/sphinx_mounts/config.py +++ b/src/sphinx_mounts/config.py @@ -460,6 +460,7 @@ def load_mounts_from_toml(toml_path: Path) -> list[dict[str, Any]] | None: :raises TomlConfigError: If the file exists but is not valid TOML, or if the top-level ``mounts`` key has the wrong shape. """ + # @ TOML mount loading and path anchoring, IMPL_TOMLCONF_001, [FEAT_TOMLCONF_001] if not toml_path.is_file(): return None try: diff --git a/src/sphinx_mounts/extension.py b/src/sphinx_mounts/extension.py index 98ce8eb..112c1ae 100644 --- a/src/sphinx_mounts/extension.py +++ b/src/sphinx_mounts/extension.py @@ -105,6 +105,7 @@ def _on_doctree_read(app: Sphinx, doctree: nodes.document) -> None: :class:`ExtensionError` — silent misconfiguration would leave the mount unreferenced. """ + # @ Toctree injection on doctree-read, IMPL_TOCWIRE_001, [FEAT_TOCWIRE_001] parsed: tuple[MountConfig, ...] = getattr(app, _CACHED_KEY, ()) if not parsed: return diff --git a/src/sphinx_mounts/mounter.py b/src/sphinx_mounts/mounter.py index 8738fa5..08ebb88 100644 --- a/src/sphinx_mounts/mounter.py +++ b/src/sphinx_mounts/mounter.py @@ -144,6 +144,7 @@ def _attach_mount_dir( directory whose parent gitignores it (the canonical ``bazel-bin/...`` case) would silently produce zero files. """ + # @ Directory walk and docname registration, IMPL_DIRMOUNT_001, [FEAT_DIRMOUNT_001] added: set[str] = set() suffixes = tuple(project.source_suffix) @@ -229,6 +230,7 @@ def _build_walker( def _attach_mount_files( project: Project, mount: MountConfig, files: Iterable[Path] ) -> set[str]: + # @ File-list docname registration, IMPL_FILEMOUNT_001, [FEAT_FILEMOUNT_001] added: set[str] = set() suffixes = tuple(project.source_suffix) diff --git a/tests/test_mounting.py b/tests/test_mounting.py index 9e8b4b1..2e3e32d 100644 --- a/tests/test_mounting.py +++ b/tests/test_mounting.py @@ -47,6 +47,7 @@ def _build(make_app, host_dir: Path) -> Path: # ---------- TOML-driven tests (primary path) ---------- +# @ Verifies directory mount discovery, TEST_DIRMOUNT_001, [CREQ_DIRMOUNT_001] def test_basic_mount_makes_external_files_readable( make_app, make_host_project, bundle_simple ): @@ -649,6 +650,7 @@ def test_mount_single_file_via_files(make_app, make_host_project, bundle_simple) assert not (outdir / "_generated/api/index.html").exists() +# @ Verifies file-list mounting, TEST_FILEMOUNT_001, [CREQ_FILEMOUNT_001] def test_mount_multiple_files_via_files(make_app, make_host_project, bundle_simple): """A `files = [...]` mount with multiple entries registers all of them flat under ``mount_at`` (basename → docname tail).""" @@ -773,6 +775,7 @@ def test_exclude_filter_bundle_files(make_app, make_host_project, bundle_simple) assert not (outdir / "_generated/api-foo/details.html").exists() +# @ Detects docname collision, TEST_COLLISION_001, [], [ERR_COLLISION_001] def test_docname_conflict_raises(make_app, make_host_project, bundle_simple, tmp_path): """Two mounts producing the same docname must be rejected.""" host = make_host_project() @@ -910,6 +913,7 @@ def test_toml_overrides_conf_py_mounts( assert not (outdir / "_generated/from-py/index.html").exists() +# @ Verifies TOML path anchoring, TEST_TOMLCONF_001, [CREQ_TOMLCONF_001] def test_toml_in_subdir_anchors_paths_to_toml_directory( make_app, make_host_project, bundle_simple ): @@ -1015,6 +1019,7 @@ def test_mounts_from_toml_disabled_with_none( # ---------- attach_to: toctree integration ---------- +# @ Verifies toctree wiring, TEST_TOCWIRE_001, [CREQ_TOCWIRE_001] def test_attach_to_extends_existing_toctree(make_app, make_host_project, bundle_simple): """A mount with attach_to extends the host doc's existing toctree.""" host = make_host_project() @@ -1083,6 +1088,7 @@ def test_attach_to_targets_specific_toctree_by_index( ) +# @ Detects toctree_index out of range, TEST_TOCIDX_001, [], [ERR_TOCIDX_001] def test_attach_to_index_out_of_range_raises( make_app, make_host_project, bundle_simple ): @@ -1236,6 +1242,7 @@ def test_attach_to_idempotent_with_static_entry( assert includes.count("_generated/api-foo/index") == 1 +# @ Detects dead attach_to target, TEST_DEADATTACH_001, [], [ERR_DEADATTACH_001] def test_attach_to_warns_when_target_docname_missing( make_app, make_host_project, bundle_simple ): @@ -1316,6 +1323,7 @@ def _docs_read_in_log(log: str) -> set[str]: # ---------- incremental rebuild ---------- +# @ Detects stale incremental mount, TEST_STALEMOUNT_001, [], [ERR_STALEMOUNT_001] def test_incremental_only_reads_changed_mount_file( make_app, make_host_project, tmp_path, bundle_simple ): diff --git a/uv.lock b/uv.lock index 73c1585..a0a860f 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 requires-python = ">=3.12, <4" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version < '3.13'", +] [[package]] name = "accessible-pygments" @@ -23,6 +27,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + [[package]] name = "babel" version = "2.18.0" @@ -127,6 +149,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -136,6 +170,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "comment-parser" +version = "1.2.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.13'", +] +dependencies = [ + { name = "python-magic", version = "0.4.24", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/cd/6e4c79d4a25e3b6542d1a3728b3e2b2c4650e3f8c3cde950aaecc6f64764/comment_parser-1.2.4.tar.gz", hash = "sha256:4e03c421c07d146f5a4d248423820347f3589f60d17acea55f7fb5683faffa22", size = 8350, upload-time = "2022-01-26T04:50:40.828Z" } + +[[package]] +name = "comment-parser" +version = "1.2.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", +] +dependencies = [ + { name = "python-magic", version = "0.4.27", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/6a/354d8e640b5f90996ac07c002189a552226a0ddaad85efd14863166aaa14/comment_parser-1.2.5.tar.gz", hash = "sha256:5606b769228cafce03d538e361472896581b386f3bc44bd62f4b61ff45ff05ec", size = 8852, upload-time = "2024-12-25T05:08:14.618Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/53/12ceba92ce55851a3d34123b2774a32689f745ce4c0c9bebca1a2a07e266/comment_parser-1.2.5-py3-none-any.whl", hash = "sha256:fb3dee8f351ebd1cfc22d3f19dabf93a4f7d4c9ea13b2092242de086a4449d9e", size = 13222, upload-time = "2026-03-16T03:30:00.77Z" }, +] + [[package]] name = "coverage" version = "7.14.0" @@ -222,11 +283,11 @@ wheels = [ [[package]] name = "docutils" -version = "0.22.4" +version = "0.21.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, ] [[package]] @@ -245,6 +306,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" }, ] +[[package]] +name = "gitignore-parser" +version = "0.1.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/51/e391a1a4238f18d0abb47be479b07af265ad4519022cf51b7da47ef82487/gitignore_parser-0.1.13.tar.gz", hash = "sha256:c7e10c8190accb8ae57fb3711889e73a9c0dbc04d4222b91ace8a4bf64d2f746", size = 5603, upload-time = "2025-08-25T06:33:22.704Z" } + +[[package]] +name = "giturlparse" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/7f25a604a406be7d7d0f849bfcbc1603df084e9e58fe6170980c231138e4/giturlparse-0.14.0.tar.gz", hash = "sha256:0a13208cb3f60e067ee3d09d28e01f9c936065986004fa2d5cd6db7758e9f6e6", size = 15637, upload-time = "2025-10-22T09:21:11.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/f9/9ff5a301459f804a885f237453ba81564bc6ee54740e9f2676c2642043f6/giturlparse-0.14.0-py2.py3-none-any.whl", hash = "sha256:04fd9c262ca9a4db86043d2ef32b2b90bfcbcdefc4f6a260fd9402127880931d", size = 16299, upload-time = "2025-10-22T09:21:10.818Z" }, +] + [[package]] name = "idna" version = "3.15" @@ -351,6 +427,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-rs" +version = "0.37.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/52/06a843d1c21d11ae22fc15abc5cb7e3b83c8d5aa7e6056c009e7189f4a58/jsonschema_rs-0.37.4.tar.gz", hash = "sha256:67f36f1c445c70f9975d17a84ce37f79593f6234d7eb292830d7749e5fa58ff4", size = 1790550, upload-time = "2025-11-30T21:00:23.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/01/fc8a5338167c45cdaf8b2ebc79e1c404c881b9cb1a0f69312acc6d38ec59/jsonschema_rs-0.37.4-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5975e448092e99d6cc60793a71f0fee516dbf0fd1e6d2f6f1e4689627268f344", size = 4448311, upload-time = "2025-11-30T21:00:05.694Z" }, + { url = "https://files.pythonhosted.org/packages/48/17/073c55453ec65644957acfe9b435ab45e4a8208faba43fec7ceb0f92607c/jsonschema_rs-0.37.4-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1d3f8c8b376966c19fd4183fa979dbadc9fdd6070f2bfa4d127bdf70946963cc", size = 2277865, upload-time = "2025-11-30T21:00:07.915Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3e/584247d45ad6fe020d040f892316d7100affb51d7bb2b8b31dde9633b353/jsonschema_rs-0.37.4-cp310-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:393ece7037a0d19fd528f7a67a32749453876468871a0bd2267909a57d8d4e32", size = 2453442, upload-time = "2025-11-30T21:00:09.402Z" }, + { url = "https://files.pythonhosted.org/packages/ff/85/b3f795037abf89087f62a7e49baca82e34b4f0d576ac30a740ca61cd33ea/jsonschema_rs-0.37.4-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dedf72e5e673e3af5b9925979fd71484debada61fb7a3dfabf9bbc74b8012664", size = 2334092, upload-time = "2025-11-30T21:00:10.705Z" }, + { url = "https://files.pythonhosted.org/packages/17/e9/644c293e0169425e0a321be197266162d5df2718af18014444edfb185eb9/jsonschema_rs-0.37.4-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e159075b1846718466998d5a9294c661113b347b8b4749767680a97c8ed2bf4d", size = 2418813, upload-time = "2025-11-30T21:00:12.056Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4b/e65a64839d8c8f55352ff12ccf37035cfbc8f38a383ef57ba0621007fc1b/jsonschema_rs-0.37.4-cp310-abi3-win32.whl", hash = "sha256:0f17a61deb557faa57dffb9596e4f022873404f935114367788b1eebdec2bb00", size = 2007260, upload-time = "2025-11-30T21:00:13.919Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d8/d2f94cca643bd4bd70f43762c981e566b9afe16c367a2f05fe97d7c8200c/jsonschema_rs-0.37.4-cp310-abi3-win_amd64.whl", hash = "sha256:03b34f911e99343fc388651688683010daee538a3cf8cf86a7997bca28fdf16b", size = 2226699, upload-time = "2025-11-30T21:00:15.763Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + [[package]] name = "markdown-it-py" version = "4.2.0" @@ -447,6 +565,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "minijinja" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/43/ebdf02703cafe163402ce09f2c4fd2eaf7f6f667a0f056b9bb1ac893f7f7/minijinja-2.20.0.tar.gz", hash = "sha256:93364945c36a2b44d8b62af068ff0c11c8b3f3fa2b11599190249bd4cd2c31ad", size = 292230, upload-time = "2026-05-20T00:01:47.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/93/a2dff85e5cc4a2c62fb00c814c6f380830e733cb5b42d5cbfeeb92a3acda/minijinja-2.20.0-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9693c7bc574d881d24e3e64c63f7ba90d9e3dbc64a13cb9d730585f63b7fb058", size = 2118981, upload-time = "2026-05-20T00:01:26.185Z" }, + { url = "https://files.pythonhosted.org/packages/1d/9a/4a5f57d1fc0d2c58c2e327f2c02cdeeaa550631c901eebed3b6c477f8de2/minijinja-2.20.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d1de5065a0cd0158623a35f30d138ef9fc98156dd5f550527f06ba22f4ab70f", size = 1105692, upload-time = "2026-05-20T00:01:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e3/b7d03968426748db76823703f5460cc22a8286d58ce834dd967ae2b75cb3/minijinja-2.20.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:698bbc1ece5dad03aa5d322d7af52e54b907febe9971731b59384addf2783c8a", size = 1079016, upload-time = "2026-05-20T00:01:30.014Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b3/0b14377d8a9171f823aa51640dd23e8256641577b9e01d3a4496aecb5c2c/minijinja-2.20.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:203ab639b0f3b71d6af6910da6311348b3a4f1a84e6702ab7c1c1f392293d16c", size = 1141684, upload-time = "2026-05-20T00:01:31.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/34/4b786ec1f99ac470e53098e7888d5efe673230975e76d1e87fce9a91ff90/minijinja-2.20.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a60b4396cd68ef02a6a5470d086f0ae8ae0ecdfd16a24457897f6abd5e5cede4", size = 1268541, upload-time = "2026-05-20T00:01:33.197Z" }, + { url = "https://files.pythonhosted.org/packages/3b/69/fb95c87b560487d87ec38f871ae5b184b1a20bc11bf0b0f6054895b0b508/minijinja-2.20.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a28d60be22a2f76accf00d84274450aa1264911fa8f45c79e4120bfddf8471cc", size = 1282035, upload-time = "2026-05-20T00:01:35.002Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d4/7b934a43a144345cd898ae4c70c4cc35783ca685cc81bbf0f6f1d26b4b85/minijinja-2.20.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:cb8d3a1e30666b883eb2ba2b3619620c62504be474e85ecc490cb28334210fd6", size = 1353836, upload-time = "2026-05-20T00:01:36.964Z" }, + { url = "https://files.pythonhosted.org/packages/16/3f/498458cec8b299a11b44b7812d564ae377b8d51e3d4308925fd203c2ac92/minijinja-2.20.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:daa32983bc995b53a16616a4cb65f9d3d5e4996fe68b61cda37909161cac4674", size = 1434449, upload-time = "2026-05-20T00:01:38.86Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/339ee68f10f69be825395aeb3e543153300dc80bc3a7181b7334821ed4f1/minijinja-2.20.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:494b8ec3307ab4d69ea74fc9fed927f2bbd38c5636feab16bdb4ef2fc3891aa0", size = 1362165, upload-time = "2026-05-20T00:01:40.705Z" }, + { url = "https://files.pythonhosted.org/packages/cd/79/4e4c4559e1a77a1509fe2b03c4346b6b2571e2786fe8b566e407f6a4bf24/minijinja-2.20.0-cp38-abi3-win32.whl", hash = "sha256:1a2348e52708d58be056c7c6ddfab0bf008d80221e2264d0e1c1d007ac6903ba", size = 1034517, upload-time = "2026-05-20T00:01:42.57Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/fc1d88c3a373c9d49e31c8220bc2e8fa919405538b848bdf681f378100cd/minijinja-2.20.0-cp38-abi3-win_amd64.whl", hash = "sha256:18be801f16cfa0026cb932df32077a169e3737d5808f7d9a2ca19afcea217140", size = 1095692, upload-time = "2026-05-20T00:01:44.908Z" }, +] + [[package]] name = "myst-parser" version = "5.1.0" @@ -545,6 +682,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] +[[package]] +name = "python-magic" +version = "0.4.24" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.13'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/70/76b185393fecf78f81c12f9dc7b1df814df785f6acb545fc92b016e75a7e/python-magic-0.4.24.tar.gz", hash = "sha256:de800df9fb50f8ec5974761054a708af6e4246b03b4bdaee993f948947b0ebcf", size = 17295, upload-time = "2021-06-03T13:49:24.116Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/99/c89223c6547df268596899334ee77b3051f606077317023617b1c43162fb/python_magic-0.4.24-py2.py3-none-any.whl", hash = "sha256:4fec8ee805fea30c07afccd1592c0f17977089895bdfaae5fec870a84e997626", size = 12785, upload-time = "2021-06-03T13:49:22.828Z" }, +] + +[[package]] +name = "python-magic" +version = "0.4.27" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", +] +sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677, upload-time = "2022-06-07T20:16:59.508Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840, upload-time = "2022-06-07T20:16:57.763Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -591,6 +752,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + [[package]] name = "requests" version = "2.34.2" @@ -606,6 +781,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] +[[package]] +name = "requests-file" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/97/bf44e6c6bd8ddbb99943baf7ba8b1a8485bcd2fe0e55e5708d7fee4ff1ae/requests_file-2.1.0.tar.gz", hash = "sha256:0f549a3f3b0699415ac04d167e9cb39bccfb730cb832b4d20be3d9867356e658", size = 6891, upload-time = "2024-05-21T16:28:00.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/25/dd878a121fcfdf38f52850f11c512e13ec87c2ea72385933818e5b6c15ce/requests_file-2.1.0-py2.py3-none-any.whl", hash = "sha256:cf270de5a4c5874e84599fc5778303d496c10ae5e870bfa378818f35d21bda5c", size = 4244, upload-time = "2024-05-21T16:27:57.733Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + [[package]] name = "roman-numerals" version = "4.1.0" @@ -615,6 +815,128 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, ] +[[package]] +name = "roman-numerals-py" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "roman-numerals" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", hash = "sha256:f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9", size = 4274, upload-time = "2025-12-17T18:25:41.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/2c/daca29684cbe9fd4bc711f8246da3c10adca1ccc4d24436b17572eb2590e/roman_numerals_py-4.1.0-py3-none-any.whl", hash = "sha256:553114c1167141c1283a51743759723ecd05604a1b6b507225e91dc1a6df0780", size = 4547, upload-time = "2025-12-17T18:25:40.136Z" }, +] + +[[package]] +name = "rpds-py" +version = "2026.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, + { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, + { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, + { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, + { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, + { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, + { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, + { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, + { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, + { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, + { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, + { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, + { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, + { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, + { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, + { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, + { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, + { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, + { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, + { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, + { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, + { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, + { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, + { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, + { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, + { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, + { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, + { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, + { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, + { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, +] + [[package]] name = "ruff" version = "0.15.13" @@ -640,6 +962,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "snowballstemmer" version = "3.0.1" @@ -660,7 +991,7 @@ wheels = [ [[package]] name = "sphinx" -version = "9.1.0" +version = "8.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alabaster" }, @@ -672,7 +1003,7 @@ dependencies = [ { name = "packaging" }, { name = "pygments" }, { name = "requests" }, - { name = "roman-numerals" }, + { name = "roman-numerals-py" }, { name = "snowballstemmer" }, { name = "sphinxcontrib-applehelp" }, { name = "sphinxcontrib-devhelp" }, @@ -681,9 +1012,9 @@ dependencies = [ { name = "sphinxcontrib-qthelp" }, { name = "sphinxcontrib-serializinghtml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, ] [[package]] @@ -698,6 +1029,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496, upload-time = "2023-07-08T18:40:52.659Z" }, ] +[[package]] +name = "sphinx-codelinks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "comment-parser", version = "1.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "comment-parser", version = "1.2.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "docutils" }, + { name = "gitignore-parser" }, + { name = "giturlparse" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "pygments" }, + { name = "sphinx" }, + { name = "sphinx-needs" }, + { name = "tree-sitter" }, + { name = "tree-sitter-c-sharp" }, + { name = "tree-sitter-cpp" }, + { name = "tree-sitter-python" }, + { name = "tree-sitter-rust" }, + { name = "tree-sitter-yaml" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/ba/cd6a8ac1cb3bbb0ada060215e655e012c4e0058f5b1785179c2ac5487eaa/sphinx_codelinks-1.2.0.tar.gz", hash = "sha256:bf0f5ca6c7f6be139a73ce01d2cda091a318eea6824d25f154543d7334118436", size = 33534, upload-time = "2026-02-19T07:44:32.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/85/2765dcffe0db7e2de35cf29450fe69ddafc4d97053cf5a0ee6e7e4b9c4f4/sphinx_codelinks-1.2.0-py3-none-any.whl", hash = "sha256:046cb12f9fa3b7f36fed97522263f6a705a22c165e135395060a08830e0d42b8", size = 39681, upload-time = "2026-02-19T07:44:31.242Z" }, +] + +[[package]] +name = "sphinx-data-viewer" +version = "0.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/3a/7aeeb805327af5b0e6f072312f9bc660615045e791a5a8f7258276fdddd1/sphinx_data_viewer-0.1.5.tar.gz", hash = "sha256:a7d5e58613562bb745380bfe61ca8b69997998167fd6fa9aea55606c9a4b17e4", size = 103260, upload-time = "2024-08-28T10:34:21.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/60/5e902d53a5eb3ec7166a8eda602bd78d4b8671a73917567edc8f13a3b366/sphinx_data_viewer-0.1.5-py3-none-any.whl", hash = "sha256:b74b1d304c505c464d07c7b225ed0d84ea02dcc88bc1c49cdad7c2275fbbdad4", size = 8215, upload-time = "2024-08-28T10:34:20.136Z" }, +] + [[package]] name = "sphinx-design" version = "0.7.0" @@ -726,7 +1098,10 @@ dev = [ docs = [ { name = "furo" }, { name = "myst-parser" }, + { name = "sphinx-codelinks" }, { name = "sphinx-design" }, + { name = "sphinx-needs" }, + { name = "sphinxcontrib-mermaid" }, ] ruff = [ { name = "ruff" }, @@ -752,7 +1127,10 @@ dev = [{ name = "prek", specifier = ">=0.3.3" }] docs = [ { name = "furo", specifier = ">=2024.5.6" }, { name = "myst-parser", specifier = ">=4.0.0" }, + { name = "sphinx-codelinks", specifier = ">=1.2.0" }, { name = "sphinx-design", specifier = ">=0.6.1" }, + { name = "sphinx-needs", specifier = ">=8.1" }, + { name = "sphinxcontrib-mermaid", specifier = ">=2.0.2" }, ] ruff = [{ name = "ruff", specifier = ">=0.8.0" }] testing = [ @@ -763,6 +1141,25 @@ testing = [ ] ty = [{ name = "ty", specifier = ">=0.0.37" }] +[[package]] +name = "sphinx-needs" +version = "8.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema-rs" }, + { name = "minijinja" }, + { name = "requests" }, + { name = "requests-file" }, + { name = "sphinx" }, + { name = "sphinx-data-viewer" }, + { name = "sphinxcontrib-jquery" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/65/c7707a4bf7857ac6b8c30461dd90ae8f4986c7e5a386f1c0069249057afa/sphinx_needs-8.1.1.tar.gz", hash = "sha256:fd72d22c958ece9a90aea517fd276197d7f602fc6ecf9fd48c1840b85cb96574", size = 27649386, upload-time = "2026-05-20T15:54:48.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/e5/de7fb014e4c63e0266e61ee79b08d2b17367cec5becd171b8f17869e4953/sphinx_needs-8.1.1-py3-none-any.whl", hash = "sha256:bd5bb71aa01726e1e1b5ac4ac7ce6b1e81acb8b519b480fae2faf7553a1271c5", size = 2704205, upload-time = "2026-05-20T15:54:45.962Z" }, +] + [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" @@ -790,6 +1187,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, ] +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, +] + [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" @@ -799,6 +1208,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, ] +[[package]] +name = "sphinxcontrib-mermaid" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/75/3a1cc926da8c563c58ddc124a7b3fe5ccadcae96c96e3a6f8ac3653a210a/sphinxcontrib_mermaid-2.0.2.tar.gz", hash = "sha256:f09576c78ca93fa0e3034fd9c45aaffa7c44ab449de9c43b8b8d262afe52bc66", size = 19265, upload-time = "2026-05-05T13:59:02.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/8d/93be7e0f7fa915a576859b3bfac7a7baa3303181c44d7db7eefbd3e8a69f/sphinxcontrib_mermaid-2.0.2-py3-none-any.whl", hash = "sha256:d862e514991279fb4816302c5cfe167d2557bf3ce7125ae0cb47dac80a0f46ce", size = 14094, upload-time = "2026-05-05T13:59:01.585Z" }, +] + [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" @@ -817,6 +1240,114 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, ] +[[package]] +name = "tree-sitter" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/7c/0350cfc47faadc0d3cf7d8237a4e34032b3014ddf4a12ded9933e1648b55/tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20", size = 177961, upload-time = "2025-09-25T17:37:59.751Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/9e/20c2a00a862f1c2897a436b17edb774e831b22218083b459d0d081c9db33/tree_sitter-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ddabfff809ffc983fc9963455ba1cecc90295803e06e140a4c83e94c1fa3d960", size = 146941, upload-time = "2025-09-25T17:37:34.813Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/8512e2062e652a1016e840ce36ba1cc33258b0dcc4e500d8089b4054afec/tree_sitter-0.25.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c0c0ab5f94938a23fe81928a21cc0fac44143133ccc4eb7eeb1b92f84748331c", size = 137699, upload-time = "2025-09-25T17:37:36.349Z" }, + { url = "https://files.pythonhosted.org/packages/47/8a/d48c0414db19307b0fb3bb10d76a3a0cbe275bb293f145ee7fba2abd668e/tree_sitter-0.25.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd12d80d91d4114ca097626eb82714618dcdfacd6a5e0955216c6485c350ef99", size = 607125, upload-time = "2025-09-25T17:37:37.725Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/b95f545e9fc5001b8a78636ef942a4e4e536580caa6a99e73dd0a02e87aa/tree_sitter-0.25.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b43a9e4c89d4d0839de27cd4d6902d33396de700e9ff4c5ab7631f277a85ead9", size = 635418, upload-time = "2025-09-25T17:37:38.922Z" }, + { url = "https://files.pythonhosted.org/packages/de/4d/b734bde3fb6f3513a010fa91f1f2875442cdc0382d6a949005cd84563d8f/tree_sitter-0.25.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb1706407c0e451c4f8cc016fec27d72d4b211fdd3173320b1ada7a6c74c3ac", size = 631250, upload-time = "2025-09-25T17:37:40.039Z" }, + { url = "https://files.pythonhosted.org/packages/46/f2/5f654994f36d10c64d50a192239599fcae46677491c8dd53e7579c35a3e3/tree_sitter-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:6d0302550bbe4620a5dc7649517c4409d74ef18558276ce758419cf09e578897", size = 127156, upload-time = "2025-09-25T17:37:41.132Z" }, + { url = "https://files.pythonhosted.org/packages/67/23/148c468d410efcf0a9535272d81c258d840c27b34781d625f1f627e2e27d/tree_sitter-0.25.2-cp312-cp312-win_arm64.whl", hash = "sha256:0c8b6682cac77e37cfe5cf7ec388844957f48b7bd8d6321d0ca2d852994e10d5", size = 113984, upload-time = "2025-09-25T17:37:42.074Z" }, + { url = "https://files.pythonhosted.org/packages/8c/67/67492014ce32729b63d7ef318a19f9cfedd855d677de5773476caf771e96/tree_sitter-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0628671f0de69bb279558ef6b640bcfc97864fe0026d840f872728a86cd6b6cd", size = 146926, upload-time = "2025-09-25T17:37:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9c/a278b15e6b263e86c5e301c82a60923fa7c59d44f78d7a110a89a413e640/tree_sitter-0.25.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ddcd3e291a749b62521f71fc953f66f5fd9743973fd6dd962b092773569601", size = 137712, upload-time = "2025-09-25T17:37:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/423bba15d2bf6473ba67846ba5244b988cd97a4b1ea2b146822162256794/tree_sitter-0.25.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd88fbb0f6c3a0f28f0a68d72df88e9755cf5215bae146f5a1bdc8362b772053", size = 607873, upload-time = "2025-09-25T17:37:45.477Z" }, + { url = "https://files.pythonhosted.org/packages/ed/4c/b430d2cb43f8badfb3a3fa9d6cd7c8247698187b5674008c9d67b2a90c8e/tree_sitter-0.25.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b878e296e63661c8e124177cc3084b041ba3f5936b43076d57c487822426f614", size = 636313, upload-time = "2025-09-25T17:37:46.68Z" }, + { url = "https://files.pythonhosted.org/packages/9d/27/5f97098dbba807331d666a0997662e82d066e84b17d92efab575d283822f/tree_sitter-0.25.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d77605e0d353ba3fe5627e5490f0fbfe44141bafa4478d88ef7954a61a848dae", size = 631370, upload-time = "2025-09-25T17:37:47.993Z" }, + { url = "https://files.pythonhosted.org/packages/d4/3c/87caaed663fabc35e18dc704cd0e9800a0ee2f22bd18b9cbe7c10799895d/tree_sitter-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:463c032bd02052d934daa5f45d183e0521ceb783c2548501cf034b0beba92c9b", size = 127157, upload-time = "2025-09-25T17:37:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/d5/23/f8467b408b7988aff4ea40946a4bd1a2c1a73d17156a9d039bbaff1e2ceb/tree_sitter-0.25.2-cp313-cp313-win_arm64.whl", hash = "sha256:b3f63a1796886249bd22c559a5944d64d05d43f2be72961624278eff0dcc5cb8", size = 113975, upload-time = "2025-09-25T17:37:49.922Z" }, + { url = "https://files.pythonhosted.org/packages/07/e3/d9526ba71dfbbe4eba5e51d89432b4b333a49a1e70712aa5590cd22fc74f/tree_sitter-0.25.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65d3c931013ea798b502782acab986bbf47ba2c452610ab0776cf4a8ef150fc0", size = 146776, upload-time = "2025-09-25T17:37:50.898Z" }, + { url = "https://files.pythonhosted.org/packages/42/97/4bd4ad97f85a23011dd8a535534bb1035c4e0bac1234d58f438e15cff51f/tree_sitter-0.25.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bda059af9d621918efb813b22fb06b3fe00c3e94079c6143fcb2c565eb44cb87", size = 137732, upload-time = "2025-09-25T17:37:51.877Z" }, + { url = "https://files.pythonhosted.org/packages/b6/19/1e968aa0b1b567988ed522f836498a6a9529a74aab15f09dd9ac1e41f505/tree_sitter-0.25.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eac4e8e4c7060c75f395feec46421eb61212cb73998dbe004b7384724f3682ab", size = 609456, upload-time = "2025-09-25T17:37:52.925Z" }, + { url = "https://files.pythonhosted.org/packages/48/b6/cf08f4f20f4c9094006ef8828555484e842fc468827ad6e56011ab668dbd/tree_sitter-0.25.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:260586381b23be33b6191a07cea3d44ecbd6c01aa4c6b027a0439145fcbc3358", size = 636772, upload-time = "2025-09-25T17:37:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/57/e2/d42d55bf56360987c32bc7b16adb06744e425670b823fb8a5786a1cea991/tree_sitter-0.25.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d2ee1acbacebe50ba0f85fff1bc05e65d877958f00880f49f9b2af38dce1af0", size = 631522, upload-time = "2025-09-25T17:37:55.833Z" }, + { url = "https://files.pythonhosted.org/packages/03/87/af9604ebe275a9345d88c3ace0cf2a1341aa3f8ef49dd9fc11662132df8a/tree_sitter-0.25.2-cp314-cp314-win_amd64.whl", hash = "sha256:4973b718fcadfb04e59e746abfbb0288694159c6aeecd2add59320c03368c721", size = 130864, upload-time = "2025-09-25T17:37:57.453Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6e/e64621037357acb83d912276ffd30a859ef117f9c680f2e3cb955f47c680/tree_sitter-0.25.2-cp314-cp314-win_arm64.whl", hash = "sha256:b8d4429954a3beb3e844e2872610d2a4800ba4eb42bb1990c6a4b1949b18459f", size = 117470, upload-time = "2025-09-25T17:37:58.431Z" }, +] + +[[package]] +name = "tree-sitter-c-sharp" +version = "0.23.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/fb/7e2962bc1901daf264e7ce263b168e0139304a5f8f66c9b2baf20e550f87/tree_sitter_c_sharp-0.23.5.tar.gz", hash = "sha256:2635c7d5ec93e59f2e831b571bed99c4cc68a5d183a0994020aa769e1b990a71", size = 1147914, upload-time = "2026-04-14T16:11:22.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/c4/86d8d469400a856757a464a6ac01af97d8cdacbb595e62bdb98bf1e9db90/tree_sitter_c_sharp-0.23.5-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:61e1981cf21b09ee547b9c4c68e64fb4394325f8fc8d5f6d50d41471eba923ea", size = 333658, upload-time = "2026-04-14T16:11:11.288Z" }, + { url = "https://files.pythonhosted.org/packages/c8/13/593c8603f834eaf15082b81e079289fc9f062b4c0ab5b9489134084eec06/tree_sitter_c_sharp-0.23.5-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a75994a11f6fed3f5b8c36ad6a00e5dc43205bd912c43af3a2a54fdf649664eb", size = 376296, upload-time = "2026-04-14T16:11:12.972Z" }, + { url = "https://files.pythonhosted.org/packages/41/5a/a8855cbb5bbab28adb29c2c7f0e7be5a9f1d21450c13b3c3e613190d9b8c/tree_sitter_c_sharp-0.23.5-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aa88a780204cd153c4c1ae2d59c654cee1402212fa0d069823d6d34301587438", size = 358333, upload-time = "2026-04-14T16:11:14.214Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c8/e0f391e343f5424d0627e3b6886c77baeb1249a3f10986be00b0b64ecdab/tree_sitter_c_sharp-0.23.5-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea38fb095d85d360dc5a0bec2fa605e496228876f798c9e089d5f0e72bcef46", size = 359448, upload-time = "2026-04-14T16:11:15.419Z" }, + { url = "https://files.pythonhosted.org/packages/6f/fc/10f807ac79f928241c5e0d827fdaf91e97dfba662fc7e07d7bd664140ec1/tree_sitter_c_sharp-0.23.5-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:05a9256415e7f24d4f133133794a9c224c60d19f677a04e2f6a94c25090b6d65", size = 358144, upload-time = "2026-04-14T16:11:17.087Z" }, + { url = "https://files.pythonhosted.org/packages/de/2a/6c3e12ef0cf09138717fcc02e1de8b76a3928d1bed65c7e3c2bd3172bcef/tree_sitter_c_sharp-0.23.5-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8636dc70b5a373c35c1036ed5de98e801f2e4d105ae41e2e20b6804c36e3bf33", size = 357525, upload-time = "2026-04-14T16:11:18.214Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e0/bd287b092d611df95a9149117fd27b5947ce75527113d6898a4b4e2c8858/tree_sitter_c_sharp-0.23.5-cp310-abi3-win_amd64.whl", hash = "sha256:41a28cfa3d9ea50f5629e44550a03188c8fbd5079803dfc03554b6fd594b33fa", size = 338756, upload-time = "2026-04-14T16:11:19.661Z" }, + { url = "https://files.pythonhosted.org/packages/7f/fb/114ff43fdd256d0befed32f77c1dadee9517867181c70794571f718ed05c/tree_sitter_c_sharp-0.23.5-cp310-abi3-win_arm64.whl", hash = "sha256:2de4ebf95ddc2e92cd3105c8a8e0e7ec646bc82f52bfaf2f3acec0fa2401ec09", size = 337260, upload-time = "2026-04-14T16:11:20.849Z" }, +] + +[[package]] +name = "tree-sitter-cpp" +version = "0.23.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/2c/4dd63d705a8933543cad9b92ff31be849b164fec91a6eb63475ebc9ce668/tree_sitter_cpp-0.23.4.tar.gz", hash = "sha256:6a59c4cebb1ad1dc2e8d586cf8a72b39d21b8108b7b139d089719e81a339e41d", size = 940358, upload-time = "2024-11-11T06:59:24.934Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/ac/11d56670f7b048362db872ca866fd00ba2002a322ab179f047b7c0fb2910/tree_sitter_cpp-0.23.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aacb1759f0efd9dbc25bd8ee88184a340483018869f75412d9c3bc32c039a520", size = 287861, upload-time = "2024-11-11T06:59:15.005Z" }, + { url = "https://files.pythonhosted.org/packages/12/1c/0337c016bdc00a77a3326d12f10ee836401dd28f27db6fd5b7734bfb21ed/tree_sitter_cpp-0.23.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc3c404d9f0cbd87951213a85440afbf4c31e718f8d907fa9ee12bea4b8d276f", size = 315513, upload-time = "2024-11-11T06:59:16.679Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7b/dd38c049b10ed7fda118b903a1d28a8b55a36b98c30606ef90e8f374c6de/tree_sitter_cpp-0.23.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc43ddf1279d5d5a4ef190373f4cb16522801bec4492bcd4754edf2aeba2b7b", size = 334813, upload-time = "2024-11-11T06:59:18.253Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4d/23e390234d2acd351f5563b1079c515d7c1fe13ddb7392cee543be74dda3/tree_sitter_cpp-0.23.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:773d2cafc08bbc0f998687fa33f42f378c1a371cdb582870c4d13abb06092706", size = 316110, upload-time = "2024-11-11T06:59:19.823Z" }, + { url = "https://files.pythonhosted.org/packages/32/c7/b94a7e0e803af9d3bd4608fb4f0cfb2e9e233abaf0a38c928bfb0b1a025d/tree_sitter_cpp-0.23.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:247d127f0eb6574b0f6b30c0151e0bd0774e2e7acf9c558bdf9fbb8adc2e80c0", size = 308242, upload-time = "2024-11-11T06:59:21.466Z" }, + { url = "https://files.pythonhosted.org/packages/37/7e/909e52b3dec09c475140b0e175511e275d0d00ba2dbd7c68102d377ae0f6/tree_sitter_cpp-0.23.4-cp39-abi3-win_amd64.whl", hash = "sha256:68606a45bea92669d155399e1239f771a7767d8683cd8f8e30e7d813107030ca", size = 290997, upload-time = "2024-11-11T06:59:22.432Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6a/65435d4d1f4c735be7ffe52d7c2e7b8a7f7c2790343a2719c60c548611c8/tree_sitter_cpp-0.23.4-cp39-abi3-win_arm64.whl", hash = "sha256:712f84f18be94cbe2a148fa4fdf40fcf4a8c25a8f7670efb9f8a47ddec2fc281", size = 288203, upload-time = "2024-11-11T06:59:23.404Z" }, +] + +[[package]] +name = "tree-sitter-python" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/8b/c992ff0e768cb6768d5c96234579bf8842b3a633db641455d86dd30d5dac/tree_sitter_python-0.25.0.tar.gz", hash = "sha256:b13e090f725f5b9c86aa455a268553c65cadf325471ad5b65cd29cac8a1a68ac", size = 159845, upload-time = "2025-09-11T06:47:58.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/64/a4e503c78a4eb3ac46d8e72a29c1b1237fa85238d8e972b063e0751f5a94/tree_sitter_python-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:14a79a47ddef72f987d5a2c122d148a812169d7484ff5c75a3db9609d419f361", size = 73790, upload-time = "2025-09-11T06:47:47.652Z" }, + { url = "https://files.pythonhosted.org/packages/e6/1d/60d8c2a0cc63d6ec4ba4e99ce61b802d2e39ef9db799bdf2a8f932a6cd4b/tree_sitter_python-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:480c21dbd995b7fe44813e741d71fed10ba695e7caab627fb034e3828469d762", size = 76691, upload-time = "2025-09-11T06:47:49.038Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cb/d9b0b67d037922d60cbe0359e0c86457c2da721bc714381a63e2c8e35eba/tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86f118e5eecad616ecdb81d171a36dde9bef5a0b21ed71ea9c3e390813c3baf5", size = 108133, upload-time = "2025-09-11T06:47:50.499Z" }, + { url = "https://files.pythonhosted.org/packages/40/bd/bf4787f57e6b2860f3f1c8c62f045b39fb32d6bac4b53d7a9e66de968440/tree_sitter_python-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be71650ca2b93b6e9649e5d65c6811aad87a7614c8c1003246b303f6b150f61b", size = 110603, upload-time = "2025-09-11T06:47:51.985Z" }, + { url = "https://files.pythonhosted.org/packages/5d/25/feff09f5c2f32484fbce15db8b49455c7572346ce61a699a41972dea7318/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6d5b5799628cc0f24691ab2a172a8e676f668fe90dc60468bee14084a35c16d", size = 108998, upload-time = "2025-09-11T06:47:53.046Z" }, + { url = "https://files.pythonhosted.org/packages/75/69/4946da3d6c0df316ccb938316ce007fb565d08f89d02d854f2d308f0309f/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:71959832fc5d9642e52c11f2f7d79ae520b461e63334927e93ca46cd61cd9683", size = 107268, upload-time = "2025-09-11T06:47:54.388Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a2/996fc2dfa1076dc460d3e2f3c75974ea4b8f02f6bc925383aaae519920e8/tree_sitter_python-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:9bcde33f18792de54ee579b00e1b4fe186b7926825444766f849bf7181793a76", size = 76073, upload-time = "2025-09-11T06:47:55.773Z" }, + { url = "https://files.pythonhosted.org/packages/07/19/4b5569d9b1ebebb5907d11554a96ef3fa09364a30fcfabeff587495b512f/tree_sitter_python-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:0fbf6a3774ad7e89ee891851204c2e2c47e12b63a5edbe2e9156997731c128bb", size = 74169, upload-time = "2025-09-11T06:47:56.747Z" }, +] + +[[package]] +name = "tree-sitter-rust" +version = "0.24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/87/75cbd22b927267d310f76cca1ab3c1d9d41035dfa3eb9cc95f96ee199440/tree_sitter_rust-0.24.2.tar.gz", hash = "sha256:54fb02a5911e345308b405174465112479f56dc39e3f1e7744d7568595f00db9", size = 339341, upload-time = "2026-03-27T21:08:55.629Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/24/2b2d33af5e27c84a4fde4e8cd2594bb4ab1e1cf48756a9f40dadc84956cc/tree_sitter_rust-0.24.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3620cfd12340efa43082d45df76349ff511893a9c361da2f8d6d51e307020a59", size = 129507, upload-time = "2026-03-27T21:08:47.585Z" }, + { url = "https://files.pythonhosted.org/packages/78/2a/cf39f881a545360b5a86bb1accba1f4acc713daab01fb9edd35b6e84f473/tree_sitter_rust-0.24.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:01a46622735498493f29f3e628a90de95c96a07bfbeb88996243eb986b1cee36", size = 136812, upload-time = "2026-03-27T21:08:48.761Z" }, + { url = "https://files.pythonhosted.org/packages/ca/45/a051bbd3045a61182dde25b93ae9a33d2677c935b16952283e12eaf46051/tree_sitter_rust-0.24.2-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e033c5a93b57c88e0a835880de39fc802909ff69f57aaff6000211c196ea5190", size = 164706, upload-time = "2026-03-27T21:08:49.605Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f6/a5a146df5c0a5daea3ffcd5d7245775fe7f084357770d5a313dd6245ae78/tree_sitter_rust-0.24.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d76d1208c3638b871236090759dfc13d478921320653a6c9da5336e7c58f65a", size = 170310, upload-time = "2026-03-27T21:08:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/95/a8/f85b1ca75e01361ca5f92d226593ca4857cea49551b9f6c8fa6fc08ea917/tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87930163a462408c49ab62c667e74029bc26b4cc7123dd1bdc7352215786c64a", size = 168668, upload-time = "2026-03-27T21:08:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e1/3519f866a4679ca36acd9f5a06a779ecb8a92b18887c5546458d521df557/tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:da2b86099028fd42c6cd32878b7b16b01f8aac0f7b0e98742b7fa6bc3cf09b89", size = 162403, upload-time = "2026-03-27T21:08:52.588Z" }, + { url = "https://files.pythonhosted.org/packages/34/71/7ef609894dbfe5699eb16f7471f9b8af1d958d8ba3e29c238d7607e8cb47/tree_sitter_rust-0.24.2-cp39-abi3-win_amd64.whl", hash = "sha256:4529c125d928882ddfb879fdc6bc0704913261ecc078b6fa7902559e0daf200d", size = 129422, upload-time = "2026-03-27T21:08:54.031Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d8/050a781172745bc345f98abb7c56e72022ea0790f8e793de981c83c2ef15/tree_sitter_rust-0.24.2-cp39-abi3-win_arm64.whl", hash = "sha256:66ba90f61bd54f4c4f5d30434957daf64507c16b0313df76becb37d63f70a227", size = 128245, upload-time = "2026-03-27T21:08:54.803Z" }, +] + +[[package]] +name = "tree-sitter-yaml" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/b6/941d356ac70c90b9d2927375259e3a4204f38f7499ec6e7e8a95b9664689/tree_sitter_yaml-0.7.2.tar.gz", hash = "sha256:756db4c09c9d9e97c81699e8f941cb8ce4e51104927f6090eefe638ee567d32c", size = 84882, upload-time = "2025-10-07T14:40:36.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/29/c0b8dbff302c49ff4284666ffb6f2f21145006843bb4c3a9a85d0ec0b7ae/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7e269ddcfcab8edb14fbb1f1d34eed1e1e26888f78f94eedfe7cc98c60f8bc9f", size = 43898, upload-time = "2025-10-07T14:40:29.486Z" }, + { url = "https://files.pythonhosted.org/packages/18/0d/15a5add06b3932b5e4ce5f5e8e179197097decfe82a0ef000952c8b98216/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0807b7966e23ddf7dddc4545216e28b5a58cdadedcecca86b8d8c74271a07870", size = 44691, upload-time = "2025-10-07T14:40:30.369Z" }, + { url = "https://files.pythonhosted.org/packages/72/92/c4b896c90d08deb8308fadbad2210fdcc4c66c44ab4292eac4e80acb4b61/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1a5c60c98b6c4c037aae023569f020d0c489fad8dc26fdfd5510363c9c29a41", size = 91430, upload-time = "2025-10-07T14:40:31.16Z" }, + { url = "https://files.pythonhosted.org/packages/89/59/61f1fed31eb6d46ff080b8c0d53658cf29e10263f41ef5fe34768908037a/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88636d19d0654fd24f4f242eaaafa90f6f5ebdba8a62e4b32d251ed156c51a2a", size = 92428, upload-time = "2025-10-07T14:40:31.954Z" }, + { url = "https://files.pythonhosted.org/packages/e3/62/a33a04d19b7f9a0ded780b9c9fcc6279e37c5d00b89b00425bb807a22cc2/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1d2e8f0bb14aa4537320952d0f9607eef3021d5aada8383c34ebeece17db1e06", size = 90580, upload-time = "2025-10-07T14:40:33.037Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e7/9525defa7b30792623f56b1fba9bbba361752348875b165b8975b87398fd/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:74ca712c50fc9d7dbc68cb36b4a7811d6e67a5466b5a789f19bf8dd6084ef752", size = 90455, upload-time = "2025-10-07T14:40:33.778Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d6/8d1e1ace03db3b02e64e91daf21d1347941d1bbecc606a5473a1a605250d/tree_sitter_yaml-0.7.2-cp310-abi3-win_amd64.whl", hash = "sha256:7587b5ca00fc4f9a548eff649697a3b395370b2304b399ceefa2087d8a6c9186", size = 45514, upload-time = "2025-10-07T14:40:34.562Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c7/dcf3ea1c4f5da9b10353b9af4455d756c92d728a8f58f03c480d3ef0ead5/tree_sitter_yaml-0.7.2-cp310-abi3-win_arm64.whl", hash = "sha256:f63c227b18e7ce7587bce124578f0bbf1f890ac63d3e3cd027417574273642c4", size = 44065, upload-time = "2025-10-07T14:40:35.337Z" }, +] + [[package]] name = "ty" version = "0.0.37" @@ -842,6 +1373,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/ed/5ec4b501479bc5dad55467e2fe72e797cb9c178468c0d1a514536872ebc5/ty-0.0.37-py3-none-win_arm64.whl", hash = "sha256:6c3c2b997f68c71e14242b96d48cba3c086439556af02bb4613aa458950d5c23", size = 10958817, upload-time = "2026-05-16T05:57:08.907Z" }, ] +[[package]] +name = "typer" +version = "0.26.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/ed/ef06584ccdd5c410df0837951ecd7e15d9a6144ea1bd4c73cecab1a89891/typer-0.26.7.tar.gz", hash = "sha256:e314a34c617e419c091b2830dda3ea1f257134ff593061a8f5b9717ab8dddb3a", size = 201709, upload-time = "2026-06-03T07:18:06.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/2201973529af2c954de0bb725323c3aaed6d7f0ceee8f550dec9185df013/typer-0.26.7-py3-none-any.whl", hash = "sha256:5c87cfbc5d34491c5346ebf49c23e18d56ccb863268d3a8d592b26087c2f5e58", size = 122456, upload-time = "2026-06-03T07:18:05.732Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" From b47165bcb37e5fbbca8b30619e22014e0886bac1 Mon Sep 17 00:00:00 2001 From: Marco Heinemann <marco.heinemann@useblocks.com> Date: Sat, 6 Jun 2026 21:30:03 +0200 Subject: [PATCH 3/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Restructure=20needs:?= =?UTF-8?q?=20agile=20spine=20+=20ISO=2026262-8=20=C2=A711=20tool-error=20?= =?UTF-8?q?model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework the need/link model per review feedback. - Add `story` at the top; `feat` now :realizes: a story. - Replace the err->feat handling links (tested_in/checked_in/mitigated_in) with a single `err :affects: feat`, and move treatment to dedicated types: - `test` :prevents: an err (structural prevention; one-line in tests/) - `check` :detects: an err (runtime/CI mitigation; RST) - `restriction` :avoids: an err (usage-constraint mitigation; RST) - Drop `comp_req` (not part of the agile spine); keep `impl` (code trace). An error is treated by EITHER prevention (a test proves it cannot occur) OR mitigation for errors that can occur (a check detects it at runtime/CI, or a restriction removes its precondition — e.g. long paths -> enable long-path support or avoid Windows). Document the approach (ISO 26262-8 §11 mapping: err = tool impact; test/check/restriction = prevention/detection measures) in a new docs/source/specs/approach.rst, and update the Mermaid diagram + traceability tables. Tailoring, pharaoh.toml chains, id scheme, and per-type checklists updated to match; test one-line needs re-pointed from `verifies comp_req` to `prevents err`. Verified: sphinx-build -nW clean; needs.json has 20 needs (2 story / 4 feat / 5 err / 4 impl / 3 test / 1 check / 1 restriction), all links resolve, every error treated; 106 tests pass; ruff + taplo clean; tailoring validates against the Pharaoh schemas. --- .pharaoh/project/artefact-catalog.yaml | 85 ++++++----- .pharaoh/project/checklists/check.md | 19 +++ .pharaoh/project/checklists/comp_req.md | 19 --- .pharaoh/project/checklists/err.md | 20 ++- .pharaoh/project/checklists/feat.md | 6 +- .pharaoh/project/checklists/requirement.md | 11 +- .pharaoh/project/checklists/restriction.md | 18 +++ .pharaoh/project/checklists/story.md | 16 ++ .pharaoh/project/checklists/test.md | 17 +-- .pharaoh/project/id-conventions.yaml | 14 +- docs/source/specs/approach.rst | 71 +++++++++ docs/source/specs/errors.rst | 95 ++++++------ docs/source/specs/features.rst | 75 ++-------- docs/source/specs/index.rst | 17 +-- docs/source/specs/mitigations.rst | 40 +++++ docs/source/specs/stories.rst | 22 +++ docs/source/specs/traceability.rst | 110 ++++++-------- docs/ubproject.toml | 166 ++++++++++----------- pharaoh.toml | 35 ++--- tests/test_mounting.py | 11 +- 20 files changed, 484 insertions(+), 383 deletions(-) create mode 100644 .pharaoh/project/checklists/check.md delete mode 100644 .pharaoh/project/checklists/comp_req.md create mode 100644 .pharaoh/project/checklists/restriction.md create mode 100644 .pharaoh/project/checklists/story.md create mode 100644 docs/source/specs/approach.rst create mode 100644 docs/source/specs/mitigations.rst create mode 100644 docs/source/specs/stories.rst diff --git a/.pharaoh/project/artefact-catalog.yaml b/.pharaoh/project/artefact-catalog.yaml index 7fcddbe..9ac384d 100644 --- a/.pharaoh/project/artefact-catalog.yaml +++ b/.pharaoh/project/artefact-catalog.yaml @@ -2,39 +2,35 @@ # # Validates against schemas/artefact-catalog.schema.json. # -# Five types: -# feat capability -# comp_req testable obligation, :satisfies: a feat -# err failure condition, handled in a feat -# impl code marker, :links: a feat -# test :verifies: a comp_req and/or :detects: an err -# impl and test are authored in code as sphinx-codelinks one-line needs (see -# the [codelinks] tables in docs/ubproject.toml), not via RST. +# Agile spine + ISO 26262-8 §11 tool-error layer (see +# docs/source/specs/approach.rst): +# story agile user story +# feat feature, :realizes: a story +# err potential tool error, :affects: a feat +# test :prevents: an err (structural prevention; one-line need in tests/) +# check :detects: an err (runtime/CI mitigation; RST) +# restriction :avoids: an err (usage-constraint mitigation; RST) +# impl :links: a feat (code trace; one-line need in src/) # -# Forward traceability is enforced via `required_metadata_fields: source_doc` -# on the RST types (need -> code). Reverse traceability (code -> feat / -# comp_req) is carried by the impl/test one-line needs' outgoing links; the -# *incoming* rule ("every feat has an impl", "every comp_req has a test") is -# NOT representable here — see https://github.com/useblocks/sphinx-needs/issues/1590. -# -# OR-rules are left to checklists, not encoded as required_links (AND semantics): -# - err: at least one of tested_in / checked_in / mitigated_in. -# - test: at least one of verifies / detects. +# required_links names the outgoing relation every need of that type must +# carry. The *incoming* OR-rule "every err has at least one treatment +# (test/check/restriction)" is not representable here (AND semantics) — it is +# a checklist item and an upstream gap (sphinx-needs#1590). -feat: - required_fields: [id, status, title, source_doc] +story: + required_fields: [id, status, title] optional_fields: [issue] lifecycle: [open, in_progress, implemented] required_links: [] optional_links: [] - required_metadata_fields: [status, source_doc] + required_metadata_fields: [status] required_roles: [] -comp_req: - required_fields: [id, status, title, satisfies, source_doc] +feat: + required_fields: [id, status, title, source_doc] optional_fields: [issue] lifecycle: [open, in_progress, implemented] - required_links: [satisfies] + required_links: [realizes] optional_links: [] required_metadata_fields: [status, source_doc] required_roles: [] @@ -43,34 +39,47 @@ err: required_fields: [id, status, title, source_doc] optional_fields: [issue, severity] lifecycle: [open, in_progress, implemented] - required_links: [] - optional_links: [tested_in, checked_in, mitigated_in] + required_links: [affects] + optional_links: [] required_metadata_fields: [status, source_doc, severity] required_roles: [] -# impl needs are authored in src/ as sphinx-codelinks one-line needs. Every -# impl carries a non-empty built-in `links` to the feat it implements — -# guaranteed by the mandatory one-line `links` field and checked by the -# "impl -> feat" chain in pharaoh.toml (kept out of required_links here, which -# lists named extra-link relations). -impl: +test: required_fields: [id, status, title] optional_fields: [] lifecycle: [open, in_progress, implemented] - required_links: [] + required_links: [prevents] optional_links: [] required_metadata_fields: [status] required_roles: [] -# test needs are authored in tests/ as one-line needs (second [codelinks] -# project). verifies (-> comp_req) and detects (-> err) are optional_links; the -# "at least one of verifies/detects" rule is a checklist item, not a -# required_link (which would force both). -test: +check: + required_fields: [id, status, title] + optional_fields: [] + lifecycle: [open, in_progress, implemented] + required_links: [detects] + optional_links: [] + required_metadata_fields: [status] + required_roles: [] + +restriction: + required_fields: [id, status, title] + optional_fields: [] + lifecycle: [open, in_progress, implemented] + required_links: [avoids] + optional_links: [] + required_metadata_fields: [status] + required_roles: [] + +# impl needs are authored in src/ as one-line needs and carry a non-empty +# built-in `links` to their feat — guaranteed by the mandatory one-line field +# and checked by the "impl -> feat" chain in pharaoh.toml (kept out of +# required_links here, which lists named extra-link relations). +impl: required_fields: [id, status, title] optional_fields: [] lifecycle: [open, in_progress, implemented] required_links: [] - optional_links: [verifies, detects] + optional_links: [] required_metadata_fields: [status] required_roles: [] diff --git a/.pharaoh/project/checklists/check.md b/.pharaoh/project/checklists/check.md new file mode 100644 index 0000000..abe9a5b --- /dev/null +++ b/.pharaoh/project/checklists/check.md @@ -0,0 +1,19 @@ +--- +name: Check review checklist +applies_to: check +axes: + - clarity + - correctness + - traceability +--- + +# Check review checklist + +- [ ] ID matches `id_regex` (`^(STORY|FEAT|ERR|TEST|CHECK|REST|IMPL)_[A-Z0-9_]*[0-9]{3}$`). +- [ ] Title states what the check verifies at runtime. +- [ ] Status reflects reality (`in_progress` until the CI job is wired up). +- [ ] `:detects:` names the error(s) this check would catch. +- [ ] Body says where the check runs (CI job / tool) and what signal it + inspects (log lines, output, needs.json …). +- [ ] The check fails the build/pipeline when the error appears — not + advisory-only. diff --git a/.pharaoh/project/checklists/comp_req.md b/.pharaoh/project/checklists/comp_req.md deleted file mode 100644 index 1291452..0000000 --- a/.pharaoh/project/checklists/comp_req.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Component requirement review checklist -applies_to: comp_req -axes: - - clarity - - correctness - - traceability ---- - -# Component requirement review checklist - -- [ ] ID matches `id_regex` (`^(FEAT|CREQ|ERR|IMPL|TEST)_[A-Z0-9_]*[0-9]{3}$`). -- [ ] Title states a single, testable obligation. -- [ ] Body is a "shall" clause — unambiguous, verifiable, free of "and/or" fusion. -- [ ] Status is one of `open`, `in_progress`, `implemented`. -- [ ] `:satisfies:` points at exactly the feature(s) this requirement decomposes. -- [ ] `:source_doc:` names the implementing source file(s) (forward trace). -- [ ] Verified by at least one `test` need (`test :verifies: <this id>`), - authored in the test suite as a one-line need. diff --git a/.pharaoh/project/checklists/err.md b/.pharaoh/project/checklists/err.md index 10922cd..ffc9ef5 100644 --- a/.pharaoh/project/checklists/err.md +++ b/.pharaoh/project/checklists/err.md @@ -9,18 +9,16 @@ axes: # Error review checklist -- [ ] ID matches `id_regex` (`^(FEAT|CREQ|ERR|IMPL|TEST)_[A-Z0-9_]*[0-9]{3}$`). +- [ ] ID matches `id_regex` (`^(STORY|FEAT|ERR|TEST|CHECK|REST|IMPL)_[A-Z0-9_]*[0-9]{3}$`). - [ ] Title names the observable failure condition (not its cause). - [ ] Status is one of `open`, `in_progress`, `implemented`. - [ ] Body explains the condition, when it arises, and the consequence if unguarded. -- [ ] **At least one** handling relation is set — `:tested_in:`, - `:checked_in:`, or `:mitigated_in:` — each pointing at the feature(s) - where the handling lives. (This OR-rule is enforced here, not via - `required_links`, which has AND semantics.) -- [ ] Relation choice matches reality: `:checked_in:` = raises/guards at runtime; - `:mitigated_in:` = graceful degradation by design; `:tested_in:` = - correctness rests on tests with no runtime guard or fallback. +- [ ] `:affects:` names the feature(s) in which this error can occur. - [ ] `:severity:` is set (`low` / `medium` / `high`). -- [ ] `:source_doc:` names the source file(s) where the condition is handled. -- [ ] Where a test exercises the condition, a `test` need `:detects:` this - error. +- [ ] `:source_doc:` names the source file(s) where the condition arises. +- [ ] **Treated** by at least one of: a `test` that `:prevents:` it + (structural), a `check` that `:detects:` it (runtime/CI), or a + `restriction` that `:avoids:` it (usage constraint). Incoming rule — + reviewed here, not schema-enforced (SN #1590). +- [ ] Treatment choice matches reality: prevention only when the error is + structurally impossible once the test passes; otherwise mitigation. diff --git a/.pharaoh/project/checklists/feat.md b/.pharaoh/project/checklists/feat.md index b971b58..fc80f14 100644 --- a/.pharaoh/project/checklists/feat.md +++ b/.pharaoh/project/checklists/feat.md @@ -8,12 +8,14 @@ axes: # Feature review checklist -- [ ] ID matches `id_regex` (`^(FEAT|CREQ|ERR|IMPL|TEST)_[A-Z0-9_]*[0-9]{3}$`). +- [ ] ID matches `id_regex` (`^(STORY|FEAT|ERR|TEST|CHECK|REST|IMPL)_[A-Z0-9_]*[0-9]{3}$`). - [ ] Title names a single user-observable capability (not an implementation detail). - [ ] Status is one of `open`, `in_progress`, `implemented`. - [ ] Body describes the capability in user terms (what, when, for whom). +- [ ] `:realizes:` points at the user story this feature delivers. - [ ] `:source_doc:` names the implementing source file(s) (forward trace). - [ ] At least one code-authored `impl` need `:links:` this feature (reverse trace — advisory; see traceability.rst and SN #1590). -- [ ] At least one `comp_req` `:satisfies:` this feature. +- [ ] The errors this feature can exhibit are captured as `err` needs that + `:affects:` it, each with a treatment. - [ ] No semantic overlap with another feature. diff --git a/.pharaoh/project/checklists/requirement.md b/.pharaoh/project/checklists/requirement.md index c1c2822..bf42beb 100644 --- a/.pharaoh/project/checklists/requirement.md +++ b/.pharaoh/project/checklists/requirement.md @@ -1,16 +1,15 @@ --- name: Requirement review checklist (alias) -applies_to: comp_req +applies_to: feat axes: - clarity - - correctness - traceability --- # Requirement review checklist -This project's requirement-shaped artefact is the **component requirement** -(`comp_req`). Pharaoh skills that reference `checklists/requirement.md` by -its well-known filename are routed here so the interop contract stays stable. +This project models the requirement-shaped artefact as the **feature** +(`feat`) on the agile spine (story → feature). Pharaoh skills that reference +`checklists/requirement.md` by its well-known filename are routed here. -For the actual review criteria see [comp_req.md](comp_req.md). +For the actual review criteria see [feat.md](feat.md). diff --git a/.pharaoh/project/checklists/restriction.md b/.pharaoh/project/checklists/restriction.md new file mode 100644 index 0000000..7dafaf5 --- /dev/null +++ b/.pharaoh/project/checklists/restriction.md @@ -0,0 +1,18 @@ +--- +name: Restriction review checklist +applies_to: restriction +axes: + - clarity + - traceability +--- + +# Restriction review checklist + +- [ ] ID matches `id_regex` (`^(STORY|FEAT|ERR|TEST|CHECK|REST|IMPL)_[A-Z0-9_]*[0-9]{3}$`). +- [ ] Title states the constraint as a single rule. +- [ ] Status is one of `open`, `in_progress`, `implemented`. +- [ ] `:avoids:` names the error(s) whose precondition the restriction removes. +- [ ] Body documents the constraint and how it is communicated or enforced + (install docs, CI guard, environment requirement). +- [ ] The restriction removes the error's precondition rather than detecting + it after the fact. diff --git a/.pharaoh/project/checklists/story.md b/.pharaoh/project/checklists/story.md new file mode 100644 index 0000000..c89d03f --- /dev/null +++ b/.pharaoh/project/checklists/story.md @@ -0,0 +1,16 @@ +--- +name: Story review checklist +applies_to: story +axes: + - clarity + - traceability +--- + +# Story review checklist + +- [ ] ID matches `id_regex` (`^(STORY|FEAT|ERR|TEST|CHECK|REST|IMPL)_[A-Z0-9_]*[0-9]{3}$`). +- [ ] Title is a short, capability-level name. +- [ ] Body follows "As a <role>, I want <goal>, so that <benefit>". +- [ ] Status is one of `open`, `in_progress`, `implemented`. +- [ ] At least one `feat` `:realizes:` this story. +- [ ] The story expresses user-facing demand, not an implementation detail. diff --git a/.pharaoh/project/checklists/test.md b/.pharaoh/project/checklists/test.md index 8eb0e76..6db610d 100644 --- a/.pharaoh/project/checklists/test.md +++ b/.pharaoh/project/checklists/test.md @@ -9,14 +9,11 @@ axes: # Test review checklist -- [ ] ID matches `id_regex` (`^(FEAT|CREQ|ERR|IMPL|TEST)_[A-Z0-9_]*[0-9]{3}$`). -- [ ] Title states what the test checks ("Verifies …" / "Detects …"). +- [ ] ID matches `id_regex` (`^(STORY|FEAT|ERR|TEST|CHECK|REST|IMPL)_[A-Z0-9_]*[0-9]{3}$`). +- [ ] Title states what the test rules out ("Prevents …"). - [ ] Status is one of `open`, `in_progress`, `implemented`. -- [ ] **At least one** of `:verifies:` (→ comp_req) or `:detects:` (→ err) is - non-empty. (OR-rule — enforced here, not via `required_links`.) -- [ ] `:verifies:` targets are component requirements; `:detects:` targets are - errors — no cross-type mistakes. -- [ ] The one-line need sits in the test file next to the test it represents, - so its source location *is* the test (sphinx-codelinks local-url). -- [ ] An error-only test writes `[]` for the mandatory `verifies` field and the - error id for `detects`. +- [ ] `:prevents:` names the error(s) the test structurally rules out. +- [ ] The one-line need sits in the test file next to the test it represents + (sphinx-codelinks local-url). +- [ ] The test actually fails if the error condition is introduced — it + proves prevention, not just happy-path behaviour. diff --git a/.pharaoh/project/id-conventions.yaml b/.pharaoh/project/id-conventions.yaml index 46252f7..ee44130 100644 --- a/.pharaoh/project/id-conventions.yaml +++ b/.pharaoh/project/id-conventions.yaml @@ -3,16 +3,18 @@ # Validates against schemas/id-conventions.schema.json. # # `prefixes` mirrors [[needs.types]] in docs/ubproject.toml. `id_regex` -# matches the same shape declared there ([needs] id_regex): one of the three -# type prefixes, a SCREAMING_SNAKE domain token, and a 3-digit ordinal — -# e.g. FEAT_DIRMOUNT_001, CREQ_TOMLCONF_001, ERR_COLLISION_001. +# matches the same shape declared there: one of the type prefixes, a +# SCREAMING_SNAKE domain token, and a 3-digit ordinal — e.g. +# STORY_MOUNT_001, FEAT_DIRMOUNT_001, ERR_LONGPATH_001, REST_WINPATH_001. prefixes: + story: STORY_ feat: FEAT_ - comp_req: CREQ_ err: ERR_ - impl: IMPL_ test: TEST_ + check: CHECK_ + restriction: REST_ + impl: IMPL_ -id_regex: "^(FEAT|CREQ|ERR|IMPL|TEST)_[A-Z0-9_]*[0-9]{3}$" +id_regex: "^(STORY|FEAT|ERR|TEST|CHECK|REST|IMPL)_[A-Z0-9_]*[0-9]{3}$" separator: "_" diff --git a/docs/source/specs/approach.rst b/docs/source/specs/approach.rst new file mode 100644 index 0000000..08b6bda --- /dev/null +++ b/docs/source/specs/approach.rst @@ -0,0 +1,71 @@ +Approach +======== + +sphinx-mounts pairs **agile terminology** with the tool-error reasoning of +**ISO 26262-8 §11** ("Confidence in the use of software tools"). The agile +spine captures intent; the tool-error layer captures how the tool is kept +trustworthy. + +Agile spine +----------- + +- A **story** (``story``) is a user-facing demand — *"As a … I want … so + that …"*. +- A **feature** (``feat``) ``:realizes:`` a story: a capability the extension + provides. + +Tool-error layer +---------------- + +Every feature can fail in specific ways. Each such failure is a **potential +tool error** (``err``) that ``:affects:`` the feature. In ISO 26262-8 §11 +terms an error contributes to the tool's *impact*; the open question is +whether there is enough confidence that it will not silently corrupt the +output. + +That confidence comes from **treating** every error in one of two ways: + +**Prevention — the error cannot occur.** + A **test** (``test``) ``:prevents:`` the error: it proves, structurally, + that the condition is ruled out (e.g. a guard raises and a test asserts + it). A prevented error cannot reach the output. + +**Mitigation — the error can occur, but is contained.** + For errors that cannot be ruled out structurally, one of: + + - a **check** (``check``) ``:detects:`` the error at runtime — typically a + CI step that inspects the build log or output with another tool and + fails if the error appears; or + - a **restriction** (``restriction``) ``:avoids:`` the error by + constraining how the tool may be used — e.g. *if long mount paths are + required, enable long-path support or do not run on Windows*. + +Mapping to ISO 26262-8 §11 +-------------------------- + +.. list-table:: + :header-rows: 1 + :widths: 20 80 + + * - Concept + - ISO 26262-8 §11 role + * - ``err`` + - Potential tool error (input to Tool Impact, TI) + * - ``test`` + - Prevention measure — the error is eliminated structurally + * - ``check`` + - Tool-error **detection** measure (TD), at runtime / in CI + * - ``restriction`` + - Constraint of use that removes the error's preconditions + +A feature whose every error is prevented or mitigated carries a defensible +tool-confidence argument. :doc:`traceability` renders the full node/link +graph and the per-error treatment tables. + +Code traceability +----------------- + +Orthogonal to the error layer, an **impl** (``impl``) ``:links:`` the feature +it implements. Both ``impl`` and ``test`` are authored as one-line +`sphinx-codelinks`_ needs in the source and test trees, so each need lives +next to the code it represents. diff --git a/docs/source/specs/errors.rst b/docs/source/specs/errors.rst index b4b3329..8bd494c 100644 --- a/docs/source/specs/errors.rst +++ b/docs/source/specs/errors.rst @@ -1,67 +1,78 @@ Errors ====== -Each **error** (``err``) is a failure condition tied to one or more -features. The link relation records *how* the error is addressed: +Each **error** (``err``) is a potential tool error that ``:affects:`` one or +more features. Every error is *treated* by **prevention** (a ``test`` that +proves it cannot occur) or **mitigation** (a ``check`` that detects it, or a +``restriction`` that avoids it) — see :doc:`approach`. ``:severity:`` records +the impact if the error were hit unguarded. -- ``:checked_in:`` — a **runtime check** in the feature detects the - condition and fails loudly (raises), so it cannot corrupt the output. -- ``:mitigated_in:`` — the feature **tolerates** the condition by design - (e.g. emits a warning and continues) instead of failing. -- ``:tested_in:`` — correctness rests on **test coverage**; there is no - dedicated runtime guard or graceful fallback for the condition. - -An error may carry more than one relation (e.g. both runtime-checked and -tested). ``:severity:`` records the impact if the condition were hit -unguarded. Where a test exercises the condition, a ``test`` need -``:detects:`` this error (see :doc:`traceability`). +Prevented by a test +------------------- .. err:: Mounted docname collides with an existing document :id: ERR_COLLISION_001 :status: implemented - :checked_in: FEAT_DIRMOUNT_001, FEAT_FILEMOUNT_001 + :affects: FEAT_DIRMOUNT_001, FEAT_FILEMOUNT_001 :severity: high :source_doc: src/sphinx_mounts/mounter.py Two mounts — or a mount and a host document — can produce the same - docname, which would silently shadow content. Registration detects the - collision and raises ``ValueError`` naming both contributors, so the - build fails loudly instead of dropping a document. + docname, which would silently shadow content. Registration raises + ``ValueError`` naming both contributors. Prevented structurally by + ``TEST_COLLISION_001``. + +.. err:: File-list entry has an unregistered suffix + :id: ERR_BADSUFFIX_001 + :status: implemented + :affects: FEAT_FILEMOUNT_001 + :severity: medium + :source_doc: src/sphinx_mounts/mounter.py + + In file-list mode a file whose suffix is not one of the project's + ``source_suffix`` values cannot become a docname. The extension raises + ``ValueError`` rather than mounting it silently. Prevented structurally by + ``TEST_BADSUFFIX_001``. -.. err:: attach_to targets a non-existent host document - :id: ERR_DEADATTACH_001 +.. err:: toctree_index out of range for the host document + :id: ERR_TOCIDX_001 :status: implemented - :mitigated_in: FEAT_TOCWIRE_001 - :severity: low + :affects: FEAT_TOCWIRE_001 + :severity: medium :source_doc: src/sphinx_mounts/extension.py - If ``attach_to`` names a docname that does not exist there is nothing to - wire. Rather than fail the build, the consistency check emits a warning - and continues — the misconfiguration is surfaced without blocking - unrelated output. This is a deliberate mitigation, not a runtime guard. + A mount can request a ``toctree_index`` larger than the number of toctrees + present in the host document. The extension raises ``ExtensionError`` + rather than leaving the mount unreferenced. Prevented structurally by + ``TEST_TOCIDX_001``. + +Mitigated by a check +-------------------- -.. err:: Incremental build serves a stale mounted document - :id: ERR_STALEMOUNT_001 +.. err:: A configured mount contributes zero documents + :id: ERR_EMPTYMOUNT_001 :status: implemented - :tested_in: FEAT_DIRMOUNT_001 + :affects: FEAT_DIRMOUNT_001 :severity: medium :source_doc: src/sphinx_mounts/mounter.py - On an incremental rebuild a changed mounted file must be re-read, and an - unchanged one must not trigger needless work. There is no runtime - assertion for this property; its correctness rests on the incremental - test cases that exercise both directions. + A mount can resolve to nothing — a mistyped ``dir``, or a parent + ``.gitignore`` that hides every file — and the build still succeeds, just + without the expected pages. This cannot be ruled out structurally (an + empty directory is legal), so it is detected at runtime by + ``CHECK_MOUNTCOUNT_001``. -.. err:: toctree_index out of range for the host document - :id: ERR_TOCIDX_001 +Avoided by a restriction +------------------------ + +.. err:: Mounted path exceeds the Windows MAX_PATH limit + :id: ERR_LONGPATH_001 :status: implemented - :checked_in: FEAT_TOCWIRE_001 - :tested_in: FEAT_TOCWIRE_001 + :affects: FEAT_DIRMOUNT_001 :severity: medium - :source_doc: src/sphinx_mounts/extension.py + :source_doc: src/sphinx_mounts/mounter.py - A mount can request a ``toctree_index`` larger than the number of - toctrees present in the host document. Rather than silently leave the - mount unreferenced, the extension raises ``ExtensionError``. The - behaviour is both runtime-checked and covered by a test — an example of - an error carrying more than one handling relation. + A deep mounted tree can produce absolute paths longer than the Windows + ``MAX_PATH`` (260-character) limit, which fails the read. The condition is + environmental, not structural, so it is avoided by ``REST_WINPATH_001`` + rather than prevented in code. diff --git a/docs/source/specs/features.rst b/docs/source/specs/features.rst index dad890e..77f24df 100644 --- a/docs/source/specs/features.rst +++ b/docs/source/specs/features.rst @@ -1,19 +1,16 @@ -Features & component requirements -================================= +Features +======== -Each **feature** (``feat``) is a user-observable capability. Each -**component requirement** (``comp_req``) decomposes a feature into a -testable obligation and ``:satisfies:`` it. The ``:source_doc:`` field is -the forward trace to the implementing source file; each requirement is -verified by one or more ``test`` needs authored in the suite (see -:doc:`traceability`). - -Directory mounting ------------------- +Each **feature** (``feat``) is a user-observable capability that +``:realizes:`` a user story. ``:source_doc:`` is the forward trace to the +implementing source; an ``impl`` one-line need in that source carries the +reverse trace (see :doc:`traceability`). The errors each feature can exhibit +are in :doc:`errors`. .. feat:: Directory mounting :id: FEAT_DIRMOUNT_001 :status: implemented + :realizes: STORY_MOUNT_001 :source_doc: src/sphinx_mounts/mounter.py sphinx-mounts mounts an external directory into the host Sphinx project. @@ -23,47 +20,20 @@ Directory mounting matched suffix) as the docname tail. Sources are read in place — never copied or symlinked. -.. comp_req:: Suffix-matching files under a mounted directory are discovered - :id: CREQ_DIRMOUNT_001 - :status: implemented - :satisfies: FEAT_DIRMOUNT_001 - :source_doc: src/sphinx_mounts/mounter.py - - In directory mode, discovery shall register, for every file under - ``mount.dir`` whose name ends with one of ``project.source_suffix``, a - docname formed by joining ``mount_at`` with the file's path relative to - ``dir`` minus the matched suffix. Discovery order shall be deterministic - (paths sorted) regardless of filesystem walk order. - -File-list mounting ------------------- - .. feat:: File-list mounting :id: FEAT_FILEMOUNT_001 :status: implemented + :realizes: STORY_MOUNT_001 :source_doc: src/sphinx_mounts/mounter.py sphinx-mounts mounts an explicit list of individual files. Each file's basename (minus the matched suffix) becomes a docname under ``mount_at``, forming a flat namespace; subdirectories in the file paths are ignored. -.. comp_req:: File-list entries become flat-namespace docnames - :id: CREQ_FILEMOUNT_001 - :status: implemented - :satisfies: FEAT_FILEMOUNT_001 - :source_doc: src/sphinx_mounts/mounter.py - - In file-list mode, the extension shall register, for each path in - ``mount.files``, a docname formed by joining ``mount_at`` with the file's - basename minus the matched ``source_suffix``. A file whose suffix is not - a registered source suffix shall be rejected with a clear error. - -Declarative TOML configuration ------------------------------- - .. feat:: Declarative TOML configuration :id: FEAT_TOMLCONF_001 :status: implemented + :realizes: STORY_DECLARE_001 :source_doc: src/sphinx_mounts/config.py Mount entries can be declared in ``ubproject.toml`` as a top-level @@ -71,36 +41,13 @@ Declarative TOML configuration read the configuration without evaluating ``conf.py``. The TOML list replaces any ``mounts`` value set in ``conf.py``. -.. comp_req:: TOML paths are anchored to the TOML file's directory - :id: CREQ_TOMLCONF_001 - :status: implemented - :satisfies: FEAT_TOMLCONF_001 - :source_doc: src/sphinx_mounts/config.py - - Relative ``dir`` and ``files`` paths declared in the TOML file shall be - anchored to the directory containing that TOML file (not to ``confdir``); - absolute paths shall be left unchanged. - -Automatic toctree wiring ------------------------- - .. feat:: Automatic toctree wiring :id: FEAT_TOCWIRE_001 :status: implemented + :realizes: STORY_MOUNT_001 :source_doc: src/sphinx_mounts/extension.py A mount can request that its entry document be wired into a host document's toctree via ``attach_to``. sphinx-mounts appends the entry to the selected toctree (``toctree_index``), or creates a new toctree beneath the first section when the host document has none. - -.. comp_req:: attach_to wires the entry doc into the host toctree - :id: CREQ_TOCWIRE_001 - :status: implemented - :satisfies: FEAT_TOCWIRE_001 - :source_doc: src/sphinx_mounts/extension.py - - When a mount sets ``attach_to`` to an existing host docname, the - extension shall append ``{mount_at}/{entry_doc}`` to the toctree at - ``toctree_index`` in that document — creating a toctree if none exists — - and shall be idempotent when the entry is already referenced. diff --git a/docs/source/specs/index.rst b/docs/source/specs/index.rst index 03f36a0..e249093 100644 --- a/docs/source/specs/index.rst +++ b/docs/source/specs/index.rst @@ -3,20 +3,17 @@ Specifications & traceability ============================= -This section is a lightweight, agile traceability model for sphinx-mounts, -authored with `Sphinx-Needs`_. It captures the extension's user-observable -**features**, the **component requirements** that decompose them, and the -**errors** (failure conditions) each feature guards against — together with -how every error is addressed (tested, runtime-checked, or mitigated) and how -each need traces to the source code that realises it. - -The model is intentionally small. It follows the same agile shape useblocks -uses elsewhere (feature → component requirement) rather than a heavyweight -V-model, and adds an ``err`` type for failure conditions. +A lightweight, agile traceability model for sphinx-mounts authored with +`Sphinx-Needs`_, combined with the tool-error reasoning of ISO 26262-8 §11. +Start with :doc:`approach` for the method; :doc:`traceability` renders the +full node/link graph. .. toctree:: :maxdepth: 2 + approach + stories features errors + mitigations traceability diff --git a/docs/source/specs/mitigations.rst b/docs/source/specs/mitigations.rst new file mode 100644 index 0000000..8a68f47 --- /dev/null +++ b/docs/source/specs/mitigations.rst @@ -0,0 +1,40 @@ +Mitigations +=========== + +For errors that cannot be ruled out structurally by a test, a **check** +detects them at runtime or a **restriction** avoids them by constraining how +the tool is used. (Prevention via tests lives next to the tests — see +:doc:`traceability`.) + +Checks +------ + +A **check** (``check``) ``:detects:`` an error: an external, runtime +verification — usually a CI step — that fails if the error appears. + +.. check:: CI asserts every mount contributed documents + :id: CHECK_MOUNTCOUNT_001 + :status: in_progress + :detects: ERR_EMPTYMOUNT_001 + + A CI step parses the build's ``needs.json`` / output and fails if any + configured mount contributed zero documents, catching a mount that + silently resolved to nothing (wrong path, parent ``.gitignore``). Status + is ``in_progress`` until the CI job is wired up. + +Restrictions +------------ + +A **restriction** (``restriction``) ``:avoids:`` an error by removing its +precondition — a documented constraint on how the tool may be used. + +.. restriction:: Enable long-path support, or avoid Windows, for deep mounts + :id: REST_WINPATH_001 + :status: implemented + :avoids: ERR_LONGPATH_001 + + When mounted document paths can exceed the Windows ``MAX_PATH`` + (260-character) limit, the project must either enable long-path support + (``LongPathsEnabled``) or run the build on a platform without the limit. + This removes the precondition for the error instead of detecting it after + the fact. diff --git a/docs/source/specs/stories.rst b/docs/source/specs/stories.rst new file mode 100644 index 0000000..6fa1ff2 --- /dev/null +++ b/docs/source/specs/stories.rst @@ -0,0 +1,22 @@ +User stories +============ + +The agile demand side: what users want from sphinx-mounts. Each feature +``:realizes:`` one of these stories. + +.. story:: Mount external sources in place + :id: STORY_MOUNT_001 + :status: implemented + + As a documentation author, I want to include external source trees in my + Sphinx build without copying or symlinking them, so that generated or + sibling documentation renders in the same site and stays in sync with its + source. + +.. story:: Declare mounts once for the whole toolchain + :id: STORY_DECLARE_001 + :status: implemented + + As a user of the useblocks documentation stack, I want mounts declared in + ``ubproject.toml``, so that ubCode, IDEs, and CI read the same mount + configuration without executing ``conf.py``. diff --git a/docs/source/specs/traceability.rst b/docs/source/specs/traceability.rst index 8dfe6ef..cf27d48 100644 --- a/docs/source/specs/traceability.rst +++ b/docs/source/specs/traceability.rst @@ -1,10 +1,10 @@ Traceability ============ -The needs in this section form a small, closed traceability loop. Every edge -is an *outgoing* link (so it is schema-validatable), and the nodes are -authored in three places: RST (``feat``, ``comp_req``, ``err``), the package -source (``impl``), and the test suite (``test``). +The needs form a closed loop of *outgoing* links (each schema-validatable), +authored in three places: RST (``story``, ``feat``, ``err``, ``check``, +``restriction``), the package source (``impl``), and the test suite +(``test``). See :doc:`approach` for what each link means. Need & link structure ---------------------- @@ -13,98 +13,84 @@ Need & link structure flowchart LR test["test (TEST_)"] - creq["comp_req (CREQ_)"] - feat["feat (FEAT_)"] + check["check (CHECK_)"] + rest["restriction (REST_)"] err["err (ERR_)"] + feat["feat (FEAT_)"] + story["story (STORY_)"] impl["impl (IMPL_)"] - test -->|verifies| creq - test -.->|detects| err - creq -->|satisfies| feat + test -->|prevents| err + check -->|detects| err + rest -->|avoids| err + err -->|affects| feat + feat -->|realizes| story impl -->|links| feat - err -->|tested_in| feat - err -->|checked_in| feat - err -->|mitigated_in| feat -Solid edges are always present for that need type; the dotted ``detects`` edge -is optional — only tests that exercise an error carry it. ``feat`` is the hub: -``comp_req``, ``impl``, and ``err`` all point at it, and ``test`` points at -``comp_req`` / ``err``. +Reading right to left: a **story** is realized by **features**; each feature +is implemented by **impl** code and can exhibit **errors**; each error is +treated by a **test** (prevention), a **check** (runtime detection), or a +**restriction** (usage constraint). -Forward trace — need → code ---------------------------- +Agile spine +----------- -Every ``feat``, ``comp_req``, and ``err`` carries a ``:source_doc:`` field -naming the source file that realises it. This direction is part of each -need's own data, so it is **schema-validatable**: a project can require, via -sphinx-needs schema validation, that (say) every ``comp_req`` set a -non-empty ``source_doc``. - -.. needtable:: Features and the source that implements them +.. needtable:: Features and the story each realizes :types: feat - :columns: id, title, source_doc, status + :columns: id, title, realizes, source_doc :style: table -.. needtable:: Errors, how they are handled, and where +Errors and their treatment +-------------------------- + +.. needtable:: Errors and the feature they affect :types: err - :columns: id, title, severity, tested_in, checked_in, mitigated_in + :columns: id, title, severity, affects :style: table -Reverse trace — code → needs ----------------------------- +.. needtable:: Treatments — prevention (test) and mitigation (check / restriction) + :types: test, check, restriction + :columns: id, type, title, prevents, detects, avoids, status + :style: table -Both ``impl`` and ``test`` needs are authored as `sphinx-codelinks`_ -**one-line needs** in comments next to the code, and discovered at build time -(configured under the ``[codelinks]`` table in ``ubproject.toml``, which -ubCode reads as well). One ``src-trace`` directive per project renders them. +Code & test traceability (sphinx-codelinks) +------------------------------------------- -Implementation needs (``src/``) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``impl`` and ``test`` needs are authored as one-line `sphinx-codelinks`_ needs +in comments next to the code, discovered at build time (configured under the +``[codelinks]`` table in ``ubproject.toml``, which ubCode reads too). One +``src-trace`` directive per project renders them. -Each ``impl`` need ``:links:`` the feature it implements: +Implementation needs (``src/``) — each ``:links:`` the feature it implements: .. src-trace:: :project: sphinx_mounts -- ``src/sphinx_mounts/mounter.py`` → :need:`IMPL_DIRMOUNT_001`, - :need:`IMPL_FILEMOUNT_001` -- ``src/sphinx_mounts/config.py`` → :need:`IMPL_TOMLCONF_001` -- ``src/sphinx_mounts/extension.py`` → :need:`IMPL_TOCWIRE_001` - .. needtable:: Implementation needs and the feature each links to :types: impl :columns: id, title, links, status :style: table -Test needs (``tests/``) -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Each ``test`` need ``:verifies:`` the component requirement(s) it covers and, -where it exercises a failure condition, ``:detects:`` the error (ISO 26262-8 -Part 8 fault detection): +Test needs (``tests/``) — each ``:prevents:`` the error it structurally rules +out: .. src-trace:: :project: sphinx_mounts_tests -.. needtable:: Test needs, the requirements they verify, and errors they detect +.. needtable:: Test needs and the error each prevents :types: test - :columns: id, title, verifies, detects, status + :columns: id, title, prevents, status :style: table -A successful build logs ``Oneline needs extracted: N`` (N ≥ 1) for each of the -two projects, confirming the source and test trees define needs linked into -the catalogue. - .. note:: Backlink coverage is not yet schema-validatable (sphinx-needs #1590) - Every edge above is an *outgoing* link (``impl → feat``, ``test → - comp_req``/``err``), which IS schema-validatable. What we cannot yet express - is the *inverse*, incoming rule — e.g. "every ``feat`` must have at least - one ``impl`` linking to it" or "every ``comp_req`` must be verified by at - least one ``test``." sphinx-needs schema validation currently constrains a - need's own fields and its **outgoing** links only; it cannot assert anything - about a need's **incoming** links / backlinks. + Every edge above is an *outgoing* link, which IS schema-validatable. What + we cannot yet express is the *incoming* rule — e.g. "every ``feat`` must + have at least one ``impl`` linking to it" or "every ``err`` must have at + least one treatment (``test`` / ``check`` / ``restriction``)." sphinx-needs + schema validation currently constrains a need's own fields and its + **outgoing** links only, not its **incoming** links / backlinks. Enforcing that via schema validation would be the canonical, declarative - approach — far better than an advisory check. It is tracked upstream at + approach. It is tracked upstream at `sphinx-needs#1590 <https://github.com/useblocks/sphinx-needs/issues/1590>`__. diff --git a/docs/ubproject.toml b/docs/ubproject.toml index 938e794..c6b06eb 100644 --- a/docs/ubproject.toml +++ b/docs/ubproject.toml @@ -13,47 +13,39 @@ index_on_save = true # --------------------------------------------------------------------------- -# Sphinx-Needs configuration — lightweight, agile traceability. +# Sphinx-Needs configuration — agile terminology, ISO 26262-8 §11 reasoning. # -# The model deliberately mirrors the agile chain used by useblocks/ubconnect -# (feature -> component requirement) rather than a heavyweight V-model, plus -# an ``err`` type for failure conditions and an ``impl`` type that ties the -# source code to the features it implements: +# An agile spine (story -> feature) carries the tool's intent; an ISO 26262 +# tool-error layer hangs off each feature and records how every potential +# tool error is prevented or mitigated. See docs/source/specs/approach.rst. # -# feat — a user-observable capability the extension provides. -# comp_req — a testable component requirement; ``:satisfies:`` a feat. -# err — an error: a failure condition tied to one or more feats. -# (The directive is ``err`` rather than ``error`` to avoid -# shadowing the built-in docutils ``.. error::`` admonition.) -# Each error declares how it is addressed via one (or more) -# of three link relations: -# :tested_in: guarded by a test in the feature's suite -# :checked_in: guarded by a runtime check in the feature -# :mitigated_in: tolerated by design (e.g. a warning + skip) -# impl — an implementation marker authored as a sphinx-codelinks -# one-line need INSIDE the source (``@ title, IMPL_id, -# [FEAT_id]``); created at build time from the [codelinks] -# config below and ``:links:`` the feature it implements. -# Not hand-written in RST. -# test — a test, authored the same way INSIDE the test suite -# (``@ title, TEST_id, [comp_req], [err]``); ``:verifies:`` the -# component requirement(s) it covers and/or ``:detects:`` the -# error(s) it checks for (ISO 26262-8 fault detection). +# story — agile user story (the demand). +# feat — a feature; ``:realizes:`` a story. +# err — a potential *tool error* of a feature; ``:affects:`` a feat. +# test — a test case that ``:prevents:`` an err: it proves the error +# cannot occur structurally. Authored as a one-line +# sphinx-codelinks need in tests/. +# check — a runtime / CI check that ``:detects:`` an err (e.g. greps +# the build log via another tool to confirm it did not occur). +# restriction — a usage restriction that ``:avoids:`` an err (e.g. "enable +# long-path support, or do not run on Windows"). +# impl — implementation marker; ``:links:`` the feat it implements. +# Authored as a one-line sphinx-codelinks need in src/. +# +# An error is *treated* by EITHER a test (prevention — error cannot occur) OR +# a mitigation for errors that can occur: a check (runtime detection) or a +# restriction (usage constraint). test / check / restriction are the ISO +# 26262-8 §11 error prevention/detection measures behind the tool-confidence +# argument. # # Lifecycle is ``open -> in_progress -> implemented`` (see # ``.pharaoh/project/workflows.yaml``). There is no terminal "closed": an -# implemented need is done, and a need that no longer applies is deleted -# from the repository rather than retired in place. +# implemented need is done; a need that no longer applies is deleted. # -# Traceability is a loop of *outgoing* links (all schema-validatable): -# test -> comp_req (:verifies:) and test -> err (:detects:); comp_req -> feat -# (:satisfies:); impl -> feat (:links:); err -> feat (handling). What is NOT -# yet expressible is the *incoming* rule "every feat must have at least one -# impl linking to it" — sphinx-needs schema validation cannot constrain -# incoming links / backlinks yet -# (https://github.com/useblocks/sphinx-needs/issues/1590). The ``source_doc`` -# field additionally records need -> code paths. See -# docs/source/specs/traceability.rst. +# Every link is *outgoing* (schema-validatable). The *incoming* rules +# ("every feat has an impl", "every err has a treatment") are not yet +# expressible in sphinx-needs schema validation — +# https://github.com/useblocks/sphinx-needs/issues/1590. # --------------------------------------------------------------------------- [needs] build_json = true @@ -64,9 +56,9 @@ id_required = true # Allowed ID shape: one of the declared type prefixes, a SCREAMING_SNAKE # domain token, and a 3-digit ordinal — e.g. ``FEAT_DIRMOUNT_001``, -# ``ERR_COLLISION_001``, ``TEST_TOCIDX_001``. Kept in sync with +# ``ERR_LONGPATH_001``, ``REST_WINPATH_001``. Kept in sync with # ``.pharaoh/project/id-conventions.yaml``. -id_regex = "(FEAT|CREQ|ERR|IMPL|TEST)_[A-Z0-9_]*[0-9]{3}" +id_regex = "(STORY|FEAT|ERR|TEST|CHECK|REST|IMPL)_[A-Z0-9_]*[0-9]{3}" # Allowed lifecycle states, enforced via the built-in ``status`` field's # schema enum. (The older ``[[needs.statuses]]`` table is deprecated in @@ -78,7 +70,7 @@ schema = { type = "string", enum = ["open", "in_progress", "implemented"] } # --- Custom fields (sphinx-needs 8.x [needs.fields.<name>] shape) --------- [needs.fields.source_doc] -description = "Path(s) under the repo root of the source file(s) that realise this need (comma-separated). Forward need -> code trace." +description = "Path(s) under the repo root of the source file(s) relevant to this need (comma-separated). Forward need -> code trace." schema = { type = "string" } nullable = true default = "" @@ -96,37 +88,39 @@ nullable = true default = "" # --- Link types (sphinx-needs 8.x [needs.links.<name>] shape) ------------- -[needs.links.satisfies] -description = "comp_req -> feat: the requirement satisfies (decomposes) the feature." -incoming = "is satisfied by" -outgoing = "satisfies" - -[needs.links.tested_in] -description = "error -> feat: the error is guarded by a test in the feature's test suite." -incoming = "tested errors" -outgoing = "tested in" - -[needs.links.checked_in] -description = "error -> feat: the error is guarded by a runtime check inside the feature." -incoming = "runtime-checked errors" -outgoing = "checked in" - -[needs.links.mitigated_in] -description = "error -> feat: the error is tolerated by design in the feature (graceful degradation)." -incoming = "mitigated errors" -outgoing = "mitigated in" - -[needs.links.verifies] -description = "test -> comp_req: the test verifies (exercises) the requirement." -incoming = "verified by" -outgoing = "verifies" +[needs.links.realizes] +description = "feat -> story: the feature realizes (delivers) the user story." +incoming = "realized by" +outgoing = "realizes" + +[needs.links.affects] +description = "err -> feat: the error is a potential failure of the feature." +incoming = "can fail with" +outgoing = "affects" + +[needs.links.prevents] +description = "test -> err: the test proves the error cannot occur structurally (prevention)." +incoming = "prevented by" +outgoing = "prevents" [needs.links.detects] -description = "test -> err: the test checks for / detects the error condition (ISO 26262-8 fault detection)." +description = "check -> err: a runtime / CI check detects whether the error occurred (mitigation)." incoming = "detected by" outgoing = "detects" +[needs.links.avoids] +description = "restriction -> err: the restriction constrains usage so the error cannot arise (mitigation)." +incoming = "avoided by" +outgoing = "avoids" + # --- Need types ------------------------------------------------------------ +[[needs.types]] +directive = "story" +title = "Story" +prefix = "STORY_" +color = "#E8DAEF" +style = "node" + [[needs.types]] directive = "feat" title = "Feature" @@ -134,13 +128,6 @@ prefix = "FEAT_" color = "#BFD8D2" style = "node" -[[needs.types]] -directive = "comp_req" -title = "Component Requirement" -prefix = "CREQ_" -color = "#FEDCD2" -style = "node" - [[needs.types]] directive = "err" title = "Error" @@ -148,8 +135,22 @@ prefix = "ERR_" color = "#F5B7B1" style = "node" +[[needs.types]] +directive = "check" +title = "Check" +prefix = "CHECK_" +color = "#A9DFBF" +style = "node" + +[[needs.types]] +directive = "restriction" +title = "Restriction" +prefix = "REST_" +color = "#FAD7A0" +style = "node" + # Authored in source code as sphinx-codelinks one-line needs (see [codelinks] -# below), not via an RST directive — but still a first-class need type. +# below), not via an RST directive — but still first-class need types. [[needs.types]] directive = "impl" title = "Implementation" @@ -157,8 +158,6 @@ prefix = "IMPL_" color = "#DF744A" style = "node" -# Authored in the test suite as sphinx-codelinks one-line needs (second -# [codelinks] project below), not via an RST directive. [[needs.types]] directive = "test" title = "Test" @@ -170,16 +169,16 @@ style = "node" # sphinx-codelinks configuration. # # Read by sphinx-codelinks AND ubCode because ``conf.py`` sets -# ``src_trace_config_from_toml = "ubproject.toml"``. ubCode currently supports -# the one-line-need form configured this way. Two projects are scanned: +# ``src_trace_config_from_toml = "ubproject.toml"``. ubCode supports the +# one-line-need form configured this way. Two projects are scanned: # # sphinx_mounts — src/, one-line ``impl`` needs: # # @ <title>, <IMPL_id>, [<feat-id>, ...] # each ``:links:`` the feature(s) it implements. # sphinx_mounts_tests — tests/, one-line ``test`` needs: -# # @ <title>, <TEST_id>, [<comp_req>], [<err>] -# each ``:verifies:`` requirement(s) and/or -# ``:detects:`` error(s). +# # @ <title>, <TEST_id>, [<err-id>, ...] +# each ``:prevents:`` the error(s) it structurally +# rules out. # # In both, ``type`` and ``status`` come from field defaults, so they are never # written in the comment — the author writes only the title, id, and links. @@ -212,8 +211,8 @@ needs_fields = [ { name = "status", default = "implemented" }, ] -# Second project: the test suite. Same one-line mechanism, different need type -# and link fields. +# Second project: the test suite. Same one-line mechanism; a test ``prevents`` +# the error(s) it structurally rules out. [codelinks.projects.sphinx_mounts_tests.source_discover] src_dir = "../tests" # relative to confdir (the docs/ directory) comment_type = "python" @@ -223,16 +222,11 @@ gitignore = true get_oneline_needs = true get_need_id_refs = false -# Two list fields: ``verifies`` (-> comp_req) is mandatory — write ``[]`` when -# a test only detects an error; ``detects`` (-> err) is optional. ``type`` and -# ``status`` trail with defaults so they are never written in the comment. [codelinks.projects.sphinx_mounts_tests.analyse.oneline_comment_style] needs_fields = [ { name = "title" }, { name = "id" }, - { name = "verifies", type = "list[str]" }, - { name = "detects", type = "list[str]", default = [ - ] }, + { name = "prevents", type = "list[str]" }, { name = "type", default = "test" }, { name = "status", default = "implemented" }, ] diff --git a/pharaoh.toml b/pharaoh.toml index 83ff3dc..38e9283 100644 --- a/pharaoh.toml +++ b/pharaoh.toml @@ -6,14 +6,11 @@ # ``docs/ubproject.toml`` and are not duplicated here. [pharaoh] -# advisory — Pharaoh suggests the recommended workflow but never blocks. -# enforcing — Pharaoh checks prerequisites before each skill and blocks if -# they are not met. strictness = "advisory" [pharaoh.id_scheme] -# IDs are <TYPE-PREFIX>_<DOMAIN>_<NNN>, e.g. FEAT_DIRMOUNT_001, -# CREQ_TOMLCONF_001, ERR_COLLISION_001. The authoritative, machine-checkable +# IDs are <TYPE-PREFIX>_<DOMAIN>_<NNN>, e.g. STORY_MOUNT_001, FEAT_DIRMOUNT_001, +# ERR_LONGPATH_001, REST_WINPATH_001. The authoritative, machine-checkable # regex lives in .pharaoh/project/id-conventions.yaml; this pattern is the # descriptive hint used by pharaoh-id-allocate. pattern = "{PREFIX}{DOMAIN}_{NUMBER}" @@ -21,10 +18,9 @@ auto_increment = true [pharaoh.workflow] mode = "reverse-eng" -# Reverse-engineering an existing codebase: needs are being authored from -# code that already exists. Verification matters from day one; change-analysis -# and the MECE release gate stay off until the catalogue stabilises. Tighten -# via pharaoh-gate-advisor as the catalogue grows. +# Reverse-engineering an existing codebase: needs are authored from code that +# already exists. Verification matters from day one; change-analysis and the +# MECE release gate stay off until the catalogue stabilises. require_change_analysis = false require_verification = true require_mece_on_release = false @@ -33,19 +29,20 @@ require_mece_on_release = false # "source-type -> target-type": every need of source-type must carry at least # one outgoing link that reaches a target-type need. required_links = [ - "comp_req -> feat", # every comp_req :satisfies: a feat - "err -> feat", # every err reaches a feat via one of - # :tested_in: / :checked_in: / :mitigated_in: - # (the at-least-one-handling rule) - "impl -> feat", # every code-authored impl :links: the feat it implements - # test -> comp_req / err is intentionally NOT a hard chain: a test - # :verifies: a comp_req OR :detects: an err (at-least-one), which a single - # required chain cannot express. Enforced via the test review checklist. + "feat -> story", # every feature realizes a user story + "err -> feat", # every error affects a feature + "impl -> feat", # every impl links the feature it implements + "test -> err", # every test prevents an error + "check -> err", # every check detects an error + "restriction -> err", # every restriction avoids an error ] +# NOT a hard chain: "every err has at least one treatment (test / check / +# restriction)". That is an *incoming* OR-rule on err, which required_links +# (outgoing, AND semantics) cannot express — enforced via the err review +# checklist and tracked upstream (sphinx-needs#1590). [pharaoh.codelinks] # sphinx-codelinks is loaded by docs/conf.py and configured under the # [codelinks] table in docs/ubproject.toml. Two one-line-need projects: -# ``impl`` needs in src/ (:links: a feat) and ``test`` needs in tests/ -# (:verifies: a comp_req and/or :detects: an err). +# impl needs in src/ (:links: a feat) and test needs in tests/ (:prevents: an err). enabled = true diff --git a/tests/test_mounting.py b/tests/test_mounting.py index 2e3e32d..345943e 100644 --- a/tests/test_mounting.py +++ b/tests/test_mounting.py @@ -47,7 +47,6 @@ def _build(make_app, host_dir: Path) -> Path: # ---------- TOML-driven tests (primary path) ---------- -# @ Verifies directory mount discovery, TEST_DIRMOUNT_001, [CREQ_DIRMOUNT_001] def test_basic_mount_makes_external_files_readable( make_app, make_host_project, bundle_simple ): @@ -650,7 +649,6 @@ def test_mount_single_file_via_files(make_app, make_host_project, bundle_simple) assert not (outdir / "_generated/api/index.html").exists() -# @ Verifies file-list mounting, TEST_FILEMOUNT_001, [CREQ_FILEMOUNT_001] def test_mount_multiple_files_via_files(make_app, make_host_project, bundle_simple): """A `files = [...]` mount with multiple entries registers all of them flat under ``mount_at`` (basename → docname tail).""" @@ -708,6 +706,7 @@ def test_mount_files_with_attach_to_wires_entry_doc( assert "_generated/api/intro.html" in index_html +# @ Prevents unregistered file-list suffix, TEST_BADSUFFIX_001, [ERR_BADSUFFIX_001] def test_mount_files_unknown_suffix_raises(make_app, make_host_project, tmp_path): """A listed file whose extension is not in source_suffix is an error — the user explicitly asked for it to be mounted, so silently skipping @@ -775,7 +774,7 @@ def test_exclude_filter_bundle_files(make_app, make_host_project, bundle_simple) assert not (outdir / "_generated/api-foo/details.html").exists() -# @ Detects docname collision, TEST_COLLISION_001, [], [ERR_COLLISION_001] +# @ Prevents docname collision, TEST_COLLISION_001, [ERR_COLLISION_001] def test_docname_conflict_raises(make_app, make_host_project, bundle_simple, tmp_path): """Two mounts producing the same docname must be rejected.""" host = make_host_project() @@ -913,7 +912,6 @@ def test_toml_overrides_conf_py_mounts( assert not (outdir / "_generated/from-py/index.html").exists() -# @ Verifies TOML path anchoring, TEST_TOMLCONF_001, [CREQ_TOMLCONF_001] def test_toml_in_subdir_anchors_paths_to_toml_directory( make_app, make_host_project, bundle_simple ): @@ -1019,7 +1017,6 @@ def test_mounts_from_toml_disabled_with_none( # ---------- attach_to: toctree integration ---------- -# @ Verifies toctree wiring, TEST_TOCWIRE_001, [CREQ_TOCWIRE_001] def test_attach_to_extends_existing_toctree(make_app, make_host_project, bundle_simple): """A mount with attach_to extends the host doc's existing toctree.""" host = make_host_project() @@ -1088,7 +1085,7 @@ def test_attach_to_targets_specific_toctree_by_index( ) -# @ Detects toctree_index out of range, TEST_TOCIDX_001, [], [ERR_TOCIDX_001] +# @ Prevents toctree_index out of range, TEST_TOCIDX_001, [ERR_TOCIDX_001] def test_attach_to_index_out_of_range_raises( make_app, make_host_project, bundle_simple ): @@ -1242,7 +1239,6 @@ def test_attach_to_idempotent_with_static_entry( assert includes.count("_generated/api-foo/index") == 1 -# @ Detects dead attach_to target, TEST_DEADATTACH_001, [], [ERR_DEADATTACH_001] def test_attach_to_warns_when_target_docname_missing( make_app, make_host_project, bundle_simple ): @@ -1323,7 +1319,6 @@ def _docs_read_in_log(log: str) -> set[str]: # ---------- incremental rebuild ---------- -# @ Detects stale incremental mount, TEST_STALEMOUNT_001, [], [ERR_STALEMOUNT_001] def test_incremental_only_reads_changed_mount_file( make_app, make_host_project, tmp_path, bundle_simple ): From 6735604a2d8acb8fbc05498aa167980b3306ca53 Mon Sep 17 00:00:00 2001 From: Marco Heinemann <marco.heinemann@useblocks.com> Date: Sat, 6 Jun 2026 21:42:28 +0200 Subject: [PATCH 4/9] =?UTF-8?q?=E2=9C=A8=20Tests=20verify=20features;=20mo?= =?UTF-8?q?ve=20trace=20diagram=20to=20Approach?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: - A `test` now :verifies: the feature it exercises (mandatory) and may also :prevents: an error (optional). Re-add the four feature-verifying test needs; the three error-guarding tests now carry both links. - Move the Mermaid node/link diagram from traceability.rst into approach.rst (with the new test -> feat edge). ubproject.toml: add the `verifies` link; the tests codelinks project's one-line fields become [title, id, verifies, prevents(optional), type, status]. pharaoh.toml: chain "test -> err" -> "test -> feat". artefact-catalog: test required_links [verifies], optional_links [prevents]. Checklists updated. Verified: sphinx-build -nW clean; 24 needs (2 story / 4 feat / 5 err / 4 impl / 7 test / 1 check / 1 restriction); all links resolve; 7 tests verify a feature (3 also prevent an error); every error treated; 106 tests pass; ruff + taplo clean; tailoring schema-valid. --- .pharaoh/project/artefact-catalog.yaml | 4 +-- .pharaoh/project/checklists/feat.md | 1 + .pharaoh/project/checklists/test.md | 12 +++++--- docs/source/specs/approach.rst | 33 ++++++++++++++++++++-- docs/source/specs/traceability.rst | 38 +++++--------------------- docs/ubproject.toml | 27 ++++++++++++------ pharaoh.toml | 2 +- tests/test_mounting.py | 10 +++++-- 8 files changed, 74 insertions(+), 53 deletions(-) diff --git a/.pharaoh/project/artefact-catalog.yaml b/.pharaoh/project/artefact-catalog.yaml index 9ac384d..3071897 100644 --- a/.pharaoh/project/artefact-catalog.yaml +++ b/.pharaoh/project/artefact-catalog.yaml @@ -48,8 +48,8 @@ test: required_fields: [id, status, title] optional_fields: [] lifecycle: [open, in_progress, implemented] - required_links: [prevents] - optional_links: [] + required_links: [verifies] + optional_links: [prevents] required_metadata_fields: [status] required_roles: [] diff --git a/.pharaoh/project/checklists/feat.md b/.pharaoh/project/checklists/feat.md index fc80f14..896baab 100644 --- a/.pharaoh/project/checklists/feat.md +++ b/.pharaoh/project/checklists/feat.md @@ -16,6 +16,7 @@ axes: - [ ] `:source_doc:` names the implementing source file(s) (forward trace). - [ ] At least one code-authored `impl` need `:links:` this feature (reverse trace — advisory; see traceability.rst and SN #1590). +- [ ] At least one `test` `:verifies:` this feature. - [ ] The errors this feature can exhibit are captured as `err` needs that `:affects:` it, each with a treatment. - [ ] No semantic overlap with another feature. diff --git a/.pharaoh/project/checklists/test.md b/.pharaoh/project/checklists/test.md index 6db610d..53dd050 100644 --- a/.pharaoh/project/checklists/test.md +++ b/.pharaoh/project/checklists/test.md @@ -10,10 +10,14 @@ axes: # Test review checklist - [ ] ID matches `id_regex` (`^(STORY|FEAT|ERR|TEST|CHECK|REST|IMPL)_[A-Z0-9_]*[0-9]{3}$`). -- [ ] Title states what the test rules out ("Prevents …"). +- [ ] Title names what the test exercises. - [ ] Status is one of `open`, `in_progress`, `implemented`. -- [ ] `:prevents:` names the error(s) the test structurally rules out. +- [ ] `:verifies:` names the feature the test exercises (every test verifies a + feature). +- [ ] `:prevents:` (optional) names the error(s) the test structurally rules + out. - [ ] The one-line need sits in the test file next to the test it represents (sphinx-codelinks local-url). -- [ ] The test actually fails if the error condition is introduced — it - proves prevention, not just happy-path behaviour. +- [ ] If it claims `:prevents:`, the test actually fails when the error + condition is introduced — it proves prevention, not just happy-path + behaviour. diff --git a/docs/source/specs/approach.rst b/docs/source/specs/approach.rst index 08b6bda..d3edfaa 100644 --- a/docs/source/specs/approach.rst +++ b/docs/source/specs/approach.rst @@ -6,6 +6,32 @@ sphinx-mounts pairs **agile terminology** with the tool-error reasoning of spine captures intent; the tool-error layer captures how the tool is kept trustworthy. +Model at a glance +----------------- + +.. mermaid:: + + flowchart LR + test["test (TEST_)"] + check["check (CHECK_)"] + rest["restriction (REST_)"] + err["err (ERR_)"] + feat["feat (FEAT_)"] + story["story (STORY_)"] + impl["impl (IMPL_)"] + + test -->|verifies| feat + test -.->|prevents| err + check -->|detects| err + rest -->|avoids| err + err -->|affects| feat + feat -->|realizes| story + impl -->|links| feat + +Solid edges are mandatory for that need type; the dotted ``prevents`` edge is +optional — a test always ``:verifies:`` a feature and *may* also +``:prevents:`` an error. + Agile spine ----------- @@ -26,9 +52,10 @@ output. That confidence comes from **treating** every error in one of two ways: **Prevention — the error cannot occur.** - A **test** (``test``) ``:prevents:`` the error: it proves, structurally, - that the condition is ruled out (e.g. a guard raises and a test asserts - it). A prevented error cannot reach the output. + A **test** (``test``) — which ``:verifies:`` the feature it exercises — + also ``:prevents:`` the error: it proves, structurally, that the condition + is ruled out (e.g. a guard raises and the test asserts it). A prevented + error cannot reach the output. **Mitigation — the error can occur, but is contained.** For errors that cannot be ruled out structurally, one of: diff --git a/docs/source/specs/traceability.rst b/docs/source/specs/traceability.rst index cf27d48..23f9afa 100644 --- a/docs/source/specs/traceability.rst +++ b/docs/source/specs/traceability.rst @@ -4,33 +4,9 @@ Traceability The needs form a closed loop of *outgoing* links (each schema-validatable), authored in three places: RST (``story``, ``feat``, ``err``, ``check``, ``restriction``), the package source (``impl``), and the test suite -(``test``). See :doc:`approach` for what each link means. - -Need & link structure ----------------------- - -.. mermaid:: - - flowchart LR - test["test (TEST_)"] - check["check (CHECK_)"] - rest["restriction (REST_)"] - err["err (ERR_)"] - feat["feat (FEAT_)"] - story["story (STORY_)"] - impl["impl (IMPL_)"] - - test -->|prevents| err - check -->|detects| err - rest -->|avoids| err - err -->|affects| feat - feat -->|realizes| story - impl -->|links| feat - -Reading right to left: a **story** is realized by **features**; each feature -is implemented by **impl** code and can exhibit **errors**; each error is -treated by a **test** (prevention), a **check** (runtime detection), or a -**restriction** (usage constraint). +(``test``). The :doc:`approach` page carries the node/link diagram and +explains what each link means; this page renders the live tables and source +traces. Agile spine ----------- @@ -71,15 +47,15 @@ Implementation needs (``src/``) — each ``:links:`` the feature it implements: :columns: id, title, links, status :style: table -Test needs (``tests/``) — each ``:prevents:`` the error it structurally rules -out: +Test needs (``tests/``) — each ``:verifies:`` a feature and may also +``:prevents:`` an error: .. src-trace:: :project: sphinx_mounts_tests -.. needtable:: Test needs and the error each prevents +.. needtable:: Test needs — feature verified and any error prevented :types: test - :columns: id, title, prevents, status + :columns: id, title, verifies, prevents, status :style: table .. note:: Backlink coverage is not yet schema-validatable (sphinx-needs #1590) diff --git a/docs/ubproject.toml b/docs/ubproject.toml index c6b06eb..df47ecd 100644 --- a/docs/ubproject.toml +++ b/docs/ubproject.toml @@ -22,8 +22,9 @@ index_on_save = true # story — agile user story (the demand). # feat — a feature; ``:realizes:`` a story. # err — a potential *tool error* of a feature; ``:affects:`` a feat. -# test — a test case that ``:prevents:`` an err: it proves the error -# cannot occur structurally. Authored as a one-line +# test — a test case that ``:verifies:`` a feat (it exercises the +# feature) and may also ``:prevents:`` an err (proving the +# error cannot occur structurally). Authored as a one-line # sphinx-codelinks need in tests/. # check — a runtime / CI check that ``:detects:`` an err (e.g. greps # the build log via another tool to confirm it did not occur). @@ -98,8 +99,13 @@ description = "err -> feat: the error is a potential failure of the feature." incoming = "can fail with" outgoing = "affects" +[needs.links.verifies] +description = "test -> feat: the test exercises and verifies the feature." +incoming = "verified by" +outgoing = "verifies" + [needs.links.prevents] -description = "test -> err: the test proves the error cannot occur structurally (prevention)." +description = "test -> err: the test also proves an error cannot occur structurally (prevention)." incoming = "prevented by" outgoing = "prevents" @@ -176,9 +182,9 @@ style = "node" # # @ <title>, <IMPL_id>, [<feat-id>, ...] # each ``:links:`` the feature(s) it implements. # sphinx_mounts_tests — tests/, one-line ``test`` needs: -# # @ <title>, <TEST_id>, [<err-id>, ...] -# each ``:prevents:`` the error(s) it structurally -# rules out. +# # @ <title>, <TEST_id>, [<feat-id>], [<err-id>] +# each ``:verifies:`` the feature it exercises and +# may also ``:prevents:`` an error. # # In both, ``type`` and ``status`` come from field defaults, so they are never # written in the comment — the author writes only the title, id, and links. @@ -211,8 +217,9 @@ needs_fields = [ { name = "status", default = "implemented" }, ] -# Second project: the test suite. Same one-line mechanism; a test ``prevents`` -# the error(s) it structurally rules out. +# Second project: the test suite. A test ``verifies`` the feature it exercises +# (mandatory) and may also ``prevents`` the error(s) it structurally rules out +# (optional — write just the feature when there is no error to prevent). [codelinks.projects.sphinx_mounts_tests.source_discover] src_dir = "../tests" # relative to confdir (the docs/ directory) comment_type = "python" @@ -226,7 +233,9 @@ get_need_id_refs = false needs_fields = [ { name = "title" }, { name = "id" }, - { name = "prevents", type = "list[str]" }, + { name = "verifies", type = "list[str]" }, + { name = "prevents", type = "list[str]", default = [ + ] }, { name = "type", default = "test" }, { name = "status", default = "implemented" }, ] diff --git a/pharaoh.toml b/pharaoh.toml index 38e9283..668aaef 100644 --- a/pharaoh.toml +++ b/pharaoh.toml @@ -32,7 +32,7 @@ required_links = [ "feat -> story", # every feature realizes a user story "err -> feat", # every error affects a feature "impl -> feat", # every impl links the feature it implements - "test -> err", # every test prevents an error + "test -> feat", # every test verifies the feature it exercises "check -> err", # every check detects an error "restriction -> err", # every restriction avoids an error ] diff --git a/tests/test_mounting.py b/tests/test_mounting.py index 345943e..07b0d3c 100644 --- a/tests/test_mounting.py +++ b/tests/test_mounting.py @@ -47,6 +47,7 @@ def _build(make_app, host_dir: Path) -> Path: # ---------- TOML-driven tests (primary path) ---------- +# @ Directory mount discovery, TEST_DIRMOUNT_001, [FEAT_DIRMOUNT_001] def test_basic_mount_makes_external_files_readable( make_app, make_host_project, bundle_simple ): @@ -649,6 +650,7 @@ def test_mount_single_file_via_files(make_app, make_host_project, bundle_simple) assert not (outdir / "_generated/api/index.html").exists() +# @ File-list mounting, TEST_FILEMOUNT_001, [FEAT_FILEMOUNT_001] def test_mount_multiple_files_via_files(make_app, make_host_project, bundle_simple): """A `files = [...]` mount with multiple entries registers all of them flat under ``mount_at`` (basename → docname tail).""" @@ -706,7 +708,7 @@ def test_mount_files_with_attach_to_wires_entry_doc( assert "_generated/api/intro.html" in index_html -# @ Prevents unregistered file-list suffix, TEST_BADSUFFIX_001, [ERR_BADSUFFIX_001] +# @ Suffix guard, TEST_BADSUFFIX_001, [FEAT_FILEMOUNT_001], [ERR_BADSUFFIX_001] def test_mount_files_unknown_suffix_raises(make_app, make_host_project, tmp_path): """A listed file whose extension is not in source_suffix is an error — the user explicitly asked for it to be mounted, so silently skipping @@ -774,7 +776,7 @@ def test_exclude_filter_bundle_files(make_app, make_host_project, bundle_simple) assert not (outdir / "_generated/api-foo/details.html").exists() -# @ Prevents docname collision, TEST_COLLISION_001, [ERR_COLLISION_001] +# @ Collision guard, TEST_COLLISION_001, [FEAT_DIRMOUNT_001], [ERR_COLLISION_001] def test_docname_conflict_raises(make_app, make_host_project, bundle_simple, tmp_path): """Two mounts producing the same docname must be rejected.""" host = make_host_project() @@ -912,6 +914,7 @@ def test_toml_overrides_conf_py_mounts( assert not (outdir / "_generated/from-py/index.html").exists() +# @ TOML path anchoring, TEST_TOMLCONF_001, [FEAT_TOMLCONF_001] def test_toml_in_subdir_anchors_paths_to_toml_directory( make_app, make_host_project, bundle_simple ): @@ -1017,6 +1020,7 @@ def test_mounts_from_toml_disabled_with_none( # ---------- attach_to: toctree integration ---------- +# @ Toctree wiring, TEST_TOCWIRE_001, [FEAT_TOCWIRE_001] def test_attach_to_extends_existing_toctree(make_app, make_host_project, bundle_simple): """A mount with attach_to extends the host doc's existing toctree.""" host = make_host_project() @@ -1085,7 +1089,7 @@ def test_attach_to_targets_specific_toctree_by_index( ) -# @ Prevents toctree_index out of range, TEST_TOCIDX_001, [ERR_TOCIDX_001] +# @ Toctree index guard, TEST_TOCIDX_001, [FEAT_TOCWIRE_001], [ERR_TOCIDX_001] def test_attach_to_index_out_of_range_raises( make_app, make_host_project, bundle_simple ): From 2600938fcd9829c27646db9cc2e3ec625a48f68c Mon Sep 17 00:00:00 2001 From: Marco Heinemann <marco.heinemann@useblocks.com> Date: Sun, 7 Jun 2026 12:40:52 +0200 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=8E=A8=20Color=20the=20trace=20diagra?= =?UTF-8?q?m=20to=20match=20need-type=20colors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Style each node in the approach.rst Mermaid diagram with its need type's declared color from ubproject.toml (story/feat/err/check/restriction/impl/test), so the diagram is visually consistent with how needs render elsewhere. Also normalize the `test` type color from the named `#DarkTurquoise` to the equivalent hex `#00CED1` so the declared color and the diagram fill are identical. --- docs/source/specs/approach.rst | 9 +++++++++ docs/ubproject.toml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/source/specs/approach.rst b/docs/source/specs/approach.rst index d3edfaa..7ec7797 100644 --- a/docs/source/specs/approach.rst +++ b/docs/source/specs/approach.rst @@ -28,6 +28,15 @@ Model at a glance feat -->|realizes| story impl -->|links| feat + %% node fills match each need type's declared color in ubproject.toml + style story fill:#E8DAEF,stroke:#333,color:#000 + style feat fill:#BFD8D2,stroke:#333,color:#000 + style err fill:#F5B7B1,stroke:#333,color:#000 + style check fill:#A9DFBF,stroke:#333,color:#000 + style rest fill:#FAD7A0,stroke:#333,color:#000 + style impl fill:#DF744A,stroke:#333,color:#000 + style test fill:#00CED1,stroke:#333,color:#000 + Solid edges are mandatory for that need type; the dotted ``prevents`` edge is optional — a test always ``:verifies:`` a feature and *may* also ``:prevents:`` an error. diff --git a/docs/ubproject.toml b/docs/ubproject.toml index df47ecd..f3944a2 100644 --- a/docs/ubproject.toml +++ b/docs/ubproject.toml @@ -168,7 +168,7 @@ style = "node" directive = "test" title = "Test" prefix = "TEST_" -color = "#DarkTurquoise" +color = "#00CED1" style = "node" # --------------------------------------------------------------------------- From 7e3cdc75e615920d006959e00571f9c4a07ea446 Mon Sep 17 00:00:00 2001 From: Marco Heinemann <marco.heinemann@useblocks.com> Date: Sun, 7 Jun 2026 12:46:35 +0200 Subject: [PATCH 6/9] =?UTF-8?q?=F0=9F=8E=A8=20Frame=20check=20+=20restrict?= =?UTF-8?q?ion=20as=20a=20"Mitigation"=20group=20in=20the=20diagram?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap the check and restriction nodes in a Mermaid subgraph labelled "Mitigation" (dashed frame) in approach.rst, so the diagram visually separates the two mitigation treatments from prevention (test). Add a sentence noting the grouping. --- docs/source/specs/approach.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/source/specs/approach.rst b/docs/source/specs/approach.rst index 7ec7797..ed7d2fc 100644 --- a/docs/source/specs/approach.rst +++ b/docs/source/specs/approach.rst @@ -13,8 +13,10 @@ Model at a glance flowchart LR test["test (TEST_)"] - check["check (CHECK_)"] - rest["restriction (REST_)"] + subgraph mitigation["Mitigation"] + check["check (CHECK_)"] + rest["restriction (REST_)"] + end err["err (ERR_)"] feat["feat (FEAT_)"] story["story (STORY_)"] @@ -36,10 +38,13 @@ Model at a glance style rest fill:#FAD7A0,stroke:#333,color:#000 style impl fill:#DF744A,stroke:#333,color:#000 style test fill:#00CED1,stroke:#333,color:#000 + style mitigation fill:#FCFCFC,stroke:#888,stroke-dasharray:5 4,color:#000 Solid edges are mandatory for that need type; the dotted ``prevents`` edge is optional — a test always ``:verifies:`` a feature and *may* also -``:prevents:`` an error. +``:prevents:`` an error. The **Mitigation** frame groups the two mitigation +treatments (``check`` and ``restriction``); prevention (``test``) sits outside +it. Agile spine ----------- From 8f9af0fdf43494db0e61706df9f548ba9f42e59a Mon Sep 17 00:00:00 2001 From: Marco Heinemann <marco.heinemann@useblocks.com> Date: Sun, 7 Jun 2026 12:49:34 +0200 Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=8E=A8=20Frame=20test=20+=20impl=20as?= =?UTF-8?q?=20a=20"Codelinks"=20group=20in=20the=20diagram?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap the test and impl nodes (both authored in code as sphinx-codelinks one-line needs — test in tests/, impl in src/) in a Mermaid subgraph labelled "Codelinks" (blue dashed frame) in approach.rst, and note the grouping in the prose. --- docs/source/specs/approach.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/source/specs/approach.rst b/docs/source/specs/approach.rst index ed7d2fc..c4dbcf4 100644 --- a/docs/source/specs/approach.rst +++ b/docs/source/specs/approach.rst @@ -12,7 +12,10 @@ Model at a glance .. mermaid:: flowchart LR - test["test (TEST_)"] + subgraph codelinks["Codelinks"] + test["test (TEST_)"] + impl["impl (IMPL_)"] + end subgraph mitigation["Mitigation"] check["check (CHECK_)"] rest["restriction (REST_)"] @@ -20,7 +23,6 @@ Model at a glance err["err (ERR_)"] feat["feat (FEAT_)"] story["story (STORY_)"] - impl["impl (IMPL_)"] test -->|verifies| feat test -.->|prevents| err @@ -39,12 +41,14 @@ Model at a glance style impl fill:#DF744A,stroke:#333,color:#000 style test fill:#00CED1,stroke:#333,color:#000 style mitigation fill:#FCFCFC,stroke:#888,stroke-dasharray:5 4,color:#000 + style codelinks fill:#F4F8FB,stroke:#5B9BD5,stroke-dasharray:5 4,color:#000 Solid edges are mandatory for that need type; the dotted ``prevents`` edge is optional — a test always ``:verifies:`` a feature and *may* also ``:prevents:`` an error. The **Mitigation** frame groups the two mitigation -treatments (``check`` and ``restriction``); prevention (``test``) sits outside -it. +treatments (``check`` and ``restriction``); the **Codelinks** frame groups the +needs authored in code as sphinx-codelinks one-line comments (``test`` in +``tests/``, ``impl`` in ``src/``). Agile spine ----------- From 36b21462dcf211ba92c443d0ba3839fe86234a5b Mon Sep 17 00:00:00 2001 From: Marco Heinemann <marco.heinemann@useblocks.com> Date: Sun, 7 Jun 2026 13:08:24 +0200 Subject: [PATCH 8/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Stop=20duplicating=20t?= =?UTF-8?q?reatment=20links=20in=20err=20bodies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each err body named the need that treats it in prose (e.g. "Prevented structurally by TEST_COLLISION_001"), duplicating a link that already lives as an outgoing field on the other need: test :prevents:, check :detects:, restriction :avoids:. The model is outgoing-only, so those already surface on the err as its incoming "prevented by" / "detected by" / "avoided by" backlink — the prose reference added nothing. Drop the redundant references. For the prevention cases the trailing sentence goes entirely (the guard behaviour is already stated, and the section heading says "Prevented by a test"); for the check and restriction cases keep the rationale for why the error is mitigated rather than prevented, just without naming the treating need. --- docs/source/specs/errors.rst | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/source/specs/errors.rst b/docs/source/specs/errors.rst index 8bd494c..cb82c96 100644 --- a/docs/source/specs/errors.rst +++ b/docs/source/specs/errors.rst @@ -19,8 +19,7 @@ Prevented by a test Two mounts — or a mount and a host document — can produce the same docname, which would silently shadow content. Registration raises - ``ValueError`` naming both contributors. Prevented structurally by - ``TEST_COLLISION_001``. + ``ValueError`` naming both contributors. .. err:: File-list entry has an unregistered suffix :id: ERR_BADSUFFIX_001 @@ -31,8 +30,7 @@ Prevented by a test In file-list mode a file whose suffix is not one of the project's ``source_suffix`` values cannot become a docname. The extension raises - ``ValueError`` rather than mounting it silently. Prevented structurally by - ``TEST_BADSUFFIX_001``. + ``ValueError`` rather than mounting it silently. .. err:: toctree_index out of range for the host document :id: ERR_TOCIDX_001 @@ -43,8 +41,7 @@ Prevented by a test A mount can request a ``toctree_index`` larger than the number of toctrees present in the host document. The extension raises ``ExtensionError`` - rather than leaving the mount unreferenced. Prevented structurally by - ``TEST_TOCIDX_001``. + rather than leaving the mount unreferenced. Mitigated by a check -------------------- @@ -59,8 +56,8 @@ Mitigated by a check A mount can resolve to nothing — a mistyped ``dir``, or a parent ``.gitignore`` that hides every file — and the build still succeeds, just without the expected pages. This cannot be ruled out structurally (an - empty directory is legal), so it is detected at runtime by - ``CHECK_MOUNTCOUNT_001``. + empty directory is legal), so it is detected at runtime rather than + prevented. Avoided by a restriction ------------------------ @@ -74,5 +71,5 @@ Avoided by a restriction A deep mounted tree can produce absolute paths longer than the Windows ``MAX_PATH`` (260-character) limit, which fails the read. The condition is - environmental, not structural, so it is avoided by ``REST_WINPATH_001`` + environmental, not structural, so it is avoided by a usage restriction rather than prevented in code. From 74d10e0bd7b491d36f3d8bda4bb428038ccdfd2a Mon Sep 17 00:00:00 2001 From: Marco Heinemann <marco.heinemann@useblocks.com> Date: Sun, 7 Jun 2026 22:54:31 +0200 Subject: [PATCH 9/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Stop=20restating=20the?= =?UTF-8?q?=20status=20field=20in=20the=20check=20body?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CHECK_MOUNTCOUNT_001's body ended with "Status is in_progress until the CI job is wired up", restating the :status: in_progress field in prose. The status field and the traceability tables already carry the lifecycle, so the sentence was pure duplication. Generalises the round-before link-in-body cleanup to non-link fields: no structured field (status here, as with the link fields earlier) should be re-encoded in a need's body text. An index sweep of all 24 needs' content confirmed this was the only remaining case. --- docs/source/specs/mitigations.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/source/specs/mitigations.rst b/docs/source/specs/mitigations.rst index 8a68f47..de33928 100644 --- a/docs/source/specs/mitigations.rst +++ b/docs/source/specs/mitigations.rst @@ -19,8 +19,7 @@ verification — usually a CI step — that fails if the error appears. A CI step parses the build's ``needs.json`` / output and fails if any configured mount contributed zero documents, catching a mount that - silently resolved to nothing (wrong path, parent ``.gitignore``). Status - is ``in_progress`` until the CI job is wired up. + silently resolved to nothing (wrong path, parent ``.gitignore``). Restrictions ------------