Skip to content

feat(tools): add gsd_requirement_list/get and gsd_decision_list/get - #1613

Open
pimmink wants to merge 1 commit into
open-gsd:mainfrom
pimmink:feat/canonical-read-tools
Open

feat(tools): add gsd_requirement_list/get and gsd_decision_list/get#1613
pimmink wants to merge 1 commit into
open-gsd:mainfrom
pimmink:feat/canonical-read-tools

Conversation

@pimmink

@pimmink pimmink commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Problem

Closes #1608

Agents cannot authoritatively verify what is in the canonical requirement/decision store without parsing Markdown projections (REQUIREMENTS.md, DECISIONS.md). These projections can be stale, missing, or inconsistent — particularly after gsd migrate or a failed projection regen. Any workflow that calls gsd_requirement_save cannot safely detect duplicates without a full projection parse.

Confirmed absent from bootstrap/db-tools.ts, workflow-tool-executors.ts, and bootstrap/memory-tools.ts in v1.12.0.


New tools (4)

gsd_requirement_list

Reads from the canonical requirements table. Returns authoritative live-DB contents, never a stale projection.

Inputs:

Field Type Description
class optional string Filter by requirement class (JS-level filter, all 13 classes supported)
status optional string Filter by status — active, validated, deferred, etc.
milestoneId optional string Filter to requirements owned by or supporting a milestone (e.g. M005)
limit optional number Max results — default 200, hard cap 500

Output: { count: number, requirements: Requirement[] }

gsd_requirement_get

Point-lookup by stable R### ID. Returns the full Requirement row or a typed error object — never throws, never fabricates.

Inputs: { id: string } — e.g. R021

Output: Requirement row, or { error: not_found | db_unavailable }

  • not_found: ID does not exist or is superseded (active-only)
  • db_unavailable: GSD database could not be opened

gsd_decision_list

Reads from the canonical memories table (ADR-013 Stage 3 — the legacy decisions table is no longer authoritative). Returns the same Decision shape as the existing prompt-inline query layer.

Inputs:

Field Type Description
scope optional string Exact-match filter on scope (e.g. architecture)
milestoneId optional string Substring filter on when_context (e.g. M005)
includeSuperseded optional boolean Include superseded decisions — default false
limit optional number Max results — default 200, hard cap 500

Active path (includeSuperseded: false): delegates to queryDecisionsFromMemories (existing function, already used by the prompt-inline path).

Full-chain path (includeSuperseded: true): delegates to getAllDecisionsFromMemories + JS-level scope/milestoneId filter.

Output: { count: number, decisions: Decision[] }

gsd_decision_get

Point-lookup by stable D### ID from the memories table. Tombstone-aware.

Inputs: { id: string, includeSuperseded?: boolean }

Output: Decision row, or { error: not_found | db_unavailable }

  • not_found: ID is absent, is a deleted: true tombstone, or is superseded (without includeSuperseded: true)
  • db_unavailable: GSD database could not be opened

Implementation details

context-store.ts additions

Two new exported helpers:

getRequirementById(id: string): Requirement | null
Direct SELECT * FROM requirements WHERE id = :id AND superseded_by IS NULL. Degrades gracefully (null) when DB unavailable. Never throws.

getDecisionById(id: string, includeSuperseded = false): Decision | null
Queries json_extract(structured_fields, '$.sourceDecisionId') = :id on the memories table, consistent with ADR-013 Stage 3. Handles tombstone (deleted: true) and superseded guard in the JS loop (same pattern as readDecisionsFromMemories).

db-tools.ts additions

Four tools follow the exact registerWorkflowTool pattern of existing tools:

  • requirementListExecute / requirementListTool / registerWorkflowTool(pi, requirementListTool)
  • requirementGetExecute / requirementGetTool / registerWorkflowTool(pi, requirementGetTool)
  • decisionListExecute / decisionListTool / registerWorkflowTool(pi, decisionListTool)
  • decisionGetExecute / decisionGetTool / registerWorkflowTool(pi, decisionGetTool)

Each tool has: name, label, description, promptSnippet, promptGuidelines, parameters (TypeBox), execute, renderCall, renderResult.

Error responses use structured details objects ({ error: 'not_found' | 'db_unavailable' }) matching the existing gsd_milestone_status pattern, so TUI renderResult can distinguish warning from error colouring.


Tests (16 passing, node:test + node:assert/strict)

▶ getRequirementById
  ✔ returns null when DB is unavailable
  ✔ requirement list filter: class filter applied in JS after DB query
  ✔ requirement list limit: hard cap at 500
  ✔ requirement list limit: caller requesting 201 is capped to 200 default behaviour
✔ getRequirementById (12ms)
▶ getDecisionById
  ✔ decision list: includeSuperseded=false excludes superseded rows (JS filter)
  ✔ decision list: scope filter applied in JS for includeSuperseded=true path
  ✔ decision list: milestoneId filter applied in JS for includeSuperseded=true path
  ✔ decision list limit: hard cap at 500 (same logic as requirements)
  ✔ getDecisionById: structured_fields parse — deleted tombstone returns null
  ✔ getDecisionById: structured_fields parse — superseded_by check
  ✔ getDecisionById: reconstructed Decision shape has all required fields
  ✔ getDecisionById: missing sourceDecisionId in structured_fields → skip row
✔ getDecisionById (23ms)
▶ tool error response contracts
  ✔ db_unavailable response has correct details shape
  ✔ not_found response distinguishes itself from db_unavailable
  ✔ requirement list success response has count and requirements array
  ✔ decision list success response has count and decisions array
✔ tool error response contracts (2ms)
ℹ tests 16 | pass 16 | fail 0

Tests cover: JS-level filter correctness, hard-cap limit logic, tombstone detection, superseded_by guard with toggle, Decision shape reconstruction, malformed row skipping, error response shape contracts.


Non-breaking

Zero deletions. No changes to existing tools, query functions, or DB schema. The four new tools are purely additive. verify:fast passes clean.


Design notes vs original issue

The original issue proposed a two-PR split (requirements first, decisions second) due to the ADR-013 Stage 3 complexity. In practice, queryDecisionsFromMemories and getAllDecisionsFromMemories already handle the memories-path entirely — no new DB query complexity was needed. A single PR is cleaner.

The gsd_decision_list / gsd_decision_get tools do NOT touch the legacy decisions table. All reads go through the memories authority path, consistent with ADR-013 Stage 3.

AI assistance used (disclosed per CONTRIBUTING.md).


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…pen-gsd#1608)

Adds four read-only canonical store tools that give agents authoritative
DB access without parsing stale markdown projections.

## New tools

### gsd_requirement_list
- Reads from the canonical `requirements` table
- Filters: class (JS-level), status (DB-level), milestoneId (DB-level)
- Limit: default 200, hard cap 500
- Returns: { count, requirements[] } — full Requirement rows

### gsd_requirement_get
- Point-lookup by stable R### ID
- Typed error responses: not_found | db_unavailable
- Returns null for superseded requirements (active-only by design)

### gsd_decision_list
- Reads from the canonical `memories` table (ADR-013 Stage 3)
- Filters: scope (exact match), milestoneId (substring on when_context)
- includeSuperseded: false (default) → queryDecisionsFromMemories
  includeSuperseded: true → getAllDecisionsFromMemories + JS filter
- Limit: default 200, hard cap 500
- Returns: { count, decisions[] } — full Decision rows

### gsd_decision_get
- Point-lookup by stable D### ID from memories table
- includeSuperseded: true to retrieve superseded decisions
- Typed error: not_found (absent/tombstoned/superseded) | db_unavailable
- Tombstone-aware: deleted: true rows return not_found

## Context-store additions

Two new exported helpers in `context-store.ts`:
- getRequirementById(id): Requirement | null
- getDecisionById(id, includeSuperseded?): Decision | null

Both degrade gracefully (return null) when DB unavailable. Never throw.
Decision lookup reads `json_extract(structured_fields, '$.sourceDecisionId')`
consistent with ADR-013 Stage 3 memory-backed authority.

## Tests (16 passing, node:test + node:assert/strict)

- JS-level filter correctness (class, scope, milestoneId, superseded)
- Hard-cap limit logic (200 default, 500 ceiling)
- Tombstone detection (deleted: true → null)
- superseded_by guard with includeSuperseded toggle
- Decision shape reconstruction from structured_fields
- Malformed row skipping (invalid JSON, missing sourceDecisionId)
- Error response shape contracts (not_found vs db_unavailable)

AI assistance used (disclosed per CONTRIBUTING.md).

Closes open-gsd#1608
@pimmink
pimmink requested a review from jeremymcs as a code owner August 6, 2026 12:55
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🟠 PR Risk Report — HIGH

Files changed 3
Systems affected 2
Overall risk 🟠 HIGH

Affected Systems

Risk System
🟠 high GSD Workflow
🟢 low Loader/Bootstrap
File Breakdown
Risk File Systems
🟠 src/resources/extensions/gsd/bootstrap/db-tools.ts GSD Workflow, Loader/Bootstrap
src/resources/extensions/gsd/context-store.ts (unclassified)
src/resources/extensions/gsd/tests/canonical-read-tools.test.ts (unclassified)

⚠️ 🟠 High risk — the following systems require verification before merge:

  • 🟠 GSD Workflow: verify GSD workflow state transitions end-to-end

⛔ This PR should not be merged without executing this follow-up prompt.

Ask your coding agent to verify before submitting:

Review this PR for risks in: GSD Workflow. Verify:

1. verify GSD workflow state transitions end-to-end

Before modifying any code, assess the scope of this fix:

- Identify the root cause, not just the reported symptom.
- Search the codebase for other call sites, similar patterns, or duplicated logic that may share the same bug.
- List affected tests, documentation, and any downstream consumers that depend on the current behavior.
- Flag any changes that extend beyond the immediate file or function.

Report findings first. Then propose a fix scoped to the actual root cause, and wait for confirmation before applying changes outside the originally reported location.

💡 Have a Codex subscription? Get an independent second opinion: codex review --adversarial

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(tools): add canonical read tools for requirements and decisions (gsd_requirement_list/get, gsd_decision_list/get)

1 participant