Contracts
activation.md
Contract: SPDD/REASONS Activation Detection
Function
def is_spdd_reasons_active(repo_root: Path) -> bool: ...
Inputs
repo_root: project root containing.kittify/charter/.
Output
Trueiff the project's active charter selection contains AT LEAST ONE of:- paradigm
structured-prompt-driven-development - tactic
reasons-canvas-fill - tactic
reasons-canvas-review - directive
DIRECTIVE_038 Falseotherwise (including when no charter exists).
Failure modes
- Missing
.kittify/charter/: returnsFalse(not an error). - Malformed governance.yaml: raises the same exception as existing charter loaders (do not swallow).
- No paradigms section in governance.yaml: returns
False(not an error).
Performance
- Reads at most two YAML files (
governance.yaml,directives.yaml). Must complete in <50ms typical.
Caching
- May be cached per-process for the lifetime of a single CLI invocation. Must NOT persist across invocations.
Tests (acceptance for WP2)
| Case | Charter selection | Expected |
|---|---|---|
| 1 | empty/no charter | False |
| 2 | only directives outside the pack | False |
| 3 | paradigm structured-prompt-driven-development selected | True |
| 4 | tactic reasons-canvas-fill selected only | True |
| 5 | tactic reasons-canvas-review selected only | True |
| 6 | directive DIRECTIVE_038 selected only | True |
| 7 | malformed governance.yaml | raises (not silently False) |
charter-context.md
Contract: Charter Context Injection
Surface
spec-kitty charter context --action <action> --json (existing CLI command). Powered by src/charter/context.py:build_charter_context().
Behavior change
- When
is_spdd_reasons_active(repo_root)isFalse: the JSONtextandcontextfields MUST equal the pre-feature output exactly (byte-or-semantic identical). This is verified by an "inactive baseline" snapshot fixture intests/charter/test_charter_context_spdd_reasons.py. - When active: the "Action Doctrine" section gains an additional subsection titled "SPDD/REASONS Guidance (action:
<action>)" appended after the existing tactic lines. Subsection content depends on action per the table inprompt-fragment.md.
Implementation seam
In src/charter/context.py, after _append_action_doctrine_lines() (line 537), add a single optional call:
if is_spdd_reasons_active(self.repo_root):
_append_spdd_reasons_guidance(lines, mission, action)
Place the helper in a new module (src/doctrine/spdd_reasons/charter_context.py) so context.py only imports a single function.
Inactive guarantee
A regression test loads a fixture project without the pack and asserts that build_charter_context() returns identical bytes to the snapshot captured before this mission was implemented.
Performance
The added work is bounded by reading and formatting at most ≤4 artifact records per call. Total budget for the SPDD branch ≤50ms additional, keeping the call within the 2s target (NFR-002).
JSON shape (unchanged)
The top-level JSON keys (result, mode, text, context, references_count, action) are unchanged. Only the inner content of text and context grows when active.
prompt-fragment.md
Contract: Conditional Prompt Fragment Rendering
Marker convention
Each command template gains AT MOST ONE conditional block per applicable action, delimited by:
<!-- spdd:reasons-block:start -->
... markdown content describing the action-scoped REASONS guidance ...
<!-- spdd:reasons-block:end -->
Renderer behavior
For each command template that contains a spdd:reasons-block:
- If
is_spdd_reasons_active(repo_root)isTrue: keep the block content but strip the surrounding<!-- spdd:reasons-block:start -->and<!-- spdd:reasons-block:end -->marker lines. Content rendered verbatim. - If
is_spdd_reasons_active(repo_root)isFalse: remove the marker lines AND every line strictly between them (including any blank line that was added solely to separate the block from surrounding text). Result must be byte-identical to the pre-feature template (verified via golden snapshot intests/prompts/test_prompt_fragment_rendering.py).
Non-goals
- No Jinja or other expression evaluation. Block contents are static markdown.
- No nested blocks. Flat blocks only.
Acceptance tests (WP4)
| Case | Project state | Expectation |
|---|---|---|
| 1 | inactive (no SPDD selection) | Five command-template outputs (specify, plan, tasks, implement, review) byte-identical to baseline snapshot. |
| 2 | active | Each output contains the corresponding REASONS guidance block, no marker lines. |
| 3 | active for specify only | Block scoped to Requirements/Entities appears in specify output; other templates may also include their action-scoped block. |
| 4 | malformed marker (missing end) | Raises a clear renderer error; does not silently truncate. |
Action-scoped block content
| Template | Block headline | Sections referenced |
|---|---|---|
| specify.md | "REASONS Guidance — Specify" | Requirements, Entities |
| plan.md | "REASONS Guidance — Plan" | Approach, Structure |
| tasks.md | "REASONS Guidance — Tasks" | Operations, WP boundaries |
| implement.md | "REASONS Guidance — Implement WP\<id\>" | Full canvas (R, E, A, S, O, N, S) |
| review.md | "REASONS Guidance — Review" | Comparison surface (R, O, N, S) + drift classification |
review-gate.md
Contract: Opt-in REASONS Review Gate
Activation
- The gate is included in the reviewer prompt only when
is_spdd_reasons_active(repo_root)isTrue. - When inactive, the reviewer prompt is byte-identical to the pre-feature output (covered by
prompt-fragment.md).
Reviewer expectations (when active)
1. Locate kitty-specs/<mission>/reasons-canvas.md. If missing, instruct the reviewer to call the spec-kitty-spdd-reasons skill to author one before completing review (do not auto-approve in absence of canvas). 2. For each Requirement and Operation in the canvas, identify supporting evidence in the diff or note its absence. 3. Detect entities, files, or surfaces touched by the diff that are absent from the canvas Structure or Approach. 4. Verify Norms and Safeguards adherence. 5. Charter directives take precedence; if a charter directive conflicts with the canvas, follow the directive and add a deviation note.
Drift classification
Reviewer outputs one of:
| Outcome | Definition | Action |
|---|---|---|
| approved | No divergence OR all divergences match Deviations entries. | APPROVE |
| approved_with_deviation | Divergence is acceptable; reviewer adds a Deviations entry. | APPROVE + canvas update |
| canvas_update_needed | Canvas was wrong; canvas should be revised before next WP. | APPROVE conditionally; open canvas update task. |
| glossary_update_needed | Term drift surfaced; glossary should be revised. | APPROVE conditionally; open glossary update task. |
| charter_follow_up | Selection should change. | APPROVE conditionally; open charter follow-up. |
| follow_up_mission | Out-of-scope work surfaced; create a separate mission. | APPROVE current scope; open follow-up. |
| scope_drift_block | Out-of-bounds undocumented work. | REJECT. |
| safeguard_violation_block | Safeguard rule violated. | REJECT. |
Tests (WP5)
| Case | Project state | Diff | Expected outcome |
|---|---|---|---|
| 1 | inactive | any | Reviewer prompt unchanged from baseline. |
| 2 | active, no canvas | any | Reviewer instructed to author canvas first. |
| 3 | active, in-bounds diff | matches canvas | approved |
| 4 | active, OOB undocumented | new file outside Structure | scope_drift_block |
| 5 | active, safeguard breach | violates a Safeguard | safeguard_violation_block |
| 6 | active, OOB but recorded in Deviations | matches Deviations entry | approved_with_deviation |