Sprint/story artifact generator: allocates the next sprint number from a git-tracked registry (monotonic, no duplicates), writes docs/userstories/sprint-NNNN-<slug>.md with typed YAML front matter + named prose slots, and measures existing files for grammar/schema drift (US-3.96.7).
| Tier | tools |
| Role | unclassified (baselined) |
| Path | crates/tools/artifact-scaffold |
| Edition | 2021 |
| Targets | tools-artifact-scaffold, tools_artifact_scaffold |
| Public items | 16 across 0 modules |
| Tests | 17 |
What it is for
US-3.96.7: artifact scaffolder -- "the harness creates the object, AI fills slots." Two halves:
new_story: allocates the next sprint number from a git-tracked
registry (docs/reference/sprint-number-registry.txt, monotonic -- read the current NEXT-TO-ALLOCATE value, write back current+1), and writes docs/userstories/sprint-<N>-<slug>.md with typed YAML front matter + named prose-slot headings (empty, for an AI/operator to fill -- this crate creates structure, never content). Filename carries identity only (the sprint number + a human-readable slug); state (status, dates) lives in the front matter, never re-derived from or encoded into the filename.
check: measures EXISTINGdocs/userstories/*.mdfiles for
grammar/schema drift -- missing/malformed front matter, and a sprint number (parsed from the FILENAME, since most existing files predate front matter entirely) shared by more than one file. Existing files are counted, never renamed or rewritten.
Deliberately dependency-free path-walking: check reads docs/userstories/ directly via std::fs::read_dir rather than shelling out to git ls-files -- this crate has no existing git-tracked-walk helper of its own (that's tools-org-knowledge::corpus::walk, a different crate), and a plain directory listing is the AC's own framing ("GIVEN existing docs/userstories files").
Capabilities
crate root
US-3.96.7: artifact scaffolder -- "the harness creates the object, AI
| Item |
|---|
pub const TEMPLATE_REGISTRY_MAX_BYTES: u64 |
fn slugify(title : & str) -> String |
fn render_story_markdown(front_matter : & StoryFrontMatter, sections : & TemplateSection,) -> Result <String, ScaffoldError> |
pub const REGISTRY_PATH: & str |
fn sprint_number_from_filename(file_name : & str) -> Option <String> |
fn extract_status_line(text : & str) -> Option <& str> |
CheckReport
US-3.96.7: artifact scaffolder -- "the harness creates the object, AI
| Item |
|---|
pub struct CheckReport |
CheckReport :: fn as_metrics(& self) -> Vec <(& 'static str, usize)> |
fn check(repo_root : & Path) -> std::io::Result <CheckReport> |
ScaffoldError
US-3.96.7: artifact scaffolder -- "the harness creates the object, AI
| Item |
|---|
pub enum ScaffoldError |
fn load_template_registry(repo_root : & Path) -> Result <Vec <TemplateSection>, ScaffoldError> |
fn new_story(repo_root : & Path, title : & str, today : & str) -> Result <PathBuf, ScaffoldError> |
StatusCategory
US-3.96.7: artifact scaffolder -- "the harness creates the object, AI
| Item |
|---|
pub enum StatusCategory |
StatusCategory :: fn as_str(self) -> & 'static str |
fn classify_status(line : Option <& str>) -> StatusCategory |
StoryFrontMatter
US-3.96.7: artifact scaffolder -- "the harness creates the object, AI
| Item |
|---|
pub struct StoryFrontMatter |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
No public modules: the crate root is its whole surface.
Public surface
`crate root`
| Item | What it is |
|---|---|
pub const TEMPLATE_REGISTRY_MAX_BYTES: u64 | The template registry is a few kilobytes; anything past this is not it. |
pub struct StoryFrontMatter | Typed YAML front matter for a scaffolded story doc |
pub enum ScaffoldError | — |
fn slugify(title : & str) -> String | Lowercase, ASCII-alnum-run slug: non-alphanumeric runs become one -, leading/trailing - trimmed |
fn render_story_markdown(front_matter : & StoryFrontMatter, sections : & TemplateSection,) -> Result <String, ScaffoldError> | Render a scaffolded story doc: YAML front matter + named, empty prose slots matching Gate 1's own required structure (user story, acceptance criteria, technical plan, tasks) -- this operationalizes an existing prose convention into something check can measure |
fn load_template_registry(repo_root : & Path) -> Result <Vec <TemplateSection>, ScaffoldError> | Sprint 4.47 U21: the sections of a story document, read from the rendering of story_template_section that corpus sprint-template-export writes |
fn new_story(repo_root : & Path, title : & str, today : & str) -> Result <PathBuf, ScaffoldError> | tools-artifact-scaffold new story --title "<t>": allocate the next sprint number, render the doc, write it to <repo_root>/docs/userstories/sprint-<N>-<slug>.md, and persist the registry's advance |
pub const REGISTRY_PATH: & str | Relative to the repo root: the git-tracked NEXT-TO-ALLOCATE registry. |
fn sprint_number_from_filename(file_name : & str) -> Option <String> | sprint extracted from a sprint-<N>-<slug>-sprint-doc.md filename, if it matches that shape (N may contain ., never - -- the first - after the sprint- prefix always ends the number) |
pub struct CheckReport | What one check pass found, as named metrics ("feeds run_measurement via corpus audit" -- US-3.96.6/3.96.8's job to persist these, not this crate's). |
pub enum StatusCategory | Mechanical classification of a userstory's free-text **Status:** line |
StatusCategory :: fn as_str(self) -> & 'static str | — |
fn extract_status_line(text : & str) -> Option <& str> | Extract the first **Status:**-prefixed line's text, if present |
fn classify_status(line : Option <& str>) -> StatusCategory | Classify one **Status:** line's text by keyword match, checked in this priority order (a line naming both "done" and "planned" -- e.g |
CheckReport :: fn as_metrics(& self) -> Vec <(& 'static str, usize)> | (metric_name, value) pairs, the exact shape corpus audit forwards into run_measurement (US-3.96.8). |
fn check(repo_root : & Path) -> std::io::Result <CheckReport> | Measure every <repo_root>/docs/userstories/*.md file |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
{TemplateSection,TEMPLATE_REGISTRY_PATH} | operations_control_plane::story::{TemplateSection,TEMPLATE_REGISTRY_PATH} |
Boundary
Reaches into operations.
Shares tier tools with 84 other crates: tools-advisory-reach, tools-archive-guard, tools-ask-ai-core, tools-ask-ais, tools-ask-gemini, tools-book, tools-book-report, tools-boundary-scan, … (84 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | tools |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/tools/artifact-scaffold |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_tools["tools"] --> n_operations["operations"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `operations-control-plane` | operations | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
chrono | ^0.4 | serde | no | always |
clap | ^4 | derive | no | always |
serde | ^1 | derive, derive | no | always |
serde_json | ^1 | — | no | always |
serde_yaml | ^0.9 | — | no | always |
thiserror | ^2 | — | no | always |
Development, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `tools-cli-conformance` | tools | no | always |
Build. None.
Depended on by. 1 workspace crate.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR n_tools_corpus["tools-corpus"] -->|uses| SELF SELF["tools-artifact-scaffold"] SELF -->|development| n_tools_cli_conformance["tools-cli-conformance"] SELF -->|runtime| n_operations_control_plane["operations-control-plane"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| bin | tools-artifact-scaffold | `src/main.rs` |
| lib | tools_artifact_scaffold | `src/lib.rs` |
Error model
| Error type | Named by |
|---|---|
ScaffoldError | load_template_registry, new_story, render_story_markdown |
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | none detected |
| database access | none detected |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
1 workspace crate depends on this one: tools-corpus.
Verification
| Kind | Count |
|---|---|
| Unit tests | 17 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
crate root | 14 | 0 | 2 |
What the tests establish, by name:
a_malformed_template_registry_is_refused_by_line—src/lib.rsallocate_and_new_story_end_to_end_on_a_temp_tree—src/lib.rsas_metrics_names_are_stable—src/lib.rscheck_categorizes_status_lines_mechanically—src/lib.rscheck_counts_missing_and_malformed_front_matter_and_duplicates—src/lib.rsclassify_status_matches_keywords_in_priority_order—src/lib.rsextract_front_matter_finds_the_block—src/lib.rsextract_front_matter_none_without_leading_fence—src/lib.rsextract_status_line_finds_the_bold_prefixed_line—src/lib.rsnew_story_refuses_to_overwrite_an_existing_file—src/lib.rsnew_story_refuses_without_the_template_registry_and_names_the_export—src/lib.rsparse_registry_accepts_major_dot_minor—src/lib.rsrender_story_markdown_prints_the_registry_sections_in_order_with_their_slots—src/lib.rsslugify_empty_input_is_empty—src/lib.rsslugify_lowercases_and_collapses_punctuation—src/lib.rssprint_number_from_filename_stops_at_first_dash_after_prefix—src/lib.rscli_definition_conforms—src/main.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 14 | 16 |
Public modules with a //! block | 0 | 0 |
pie showData
title Public items with rustdoc
"Documented" : 14
"No rustdoc detected" : 2
Metrics
| Metric | Value |
|---|---|
| Rust source files | 2 |
| Source lines | 1101 |
| Code lines | 862 |
| Public API items | 16 |
| Public modules | 0 |
| Tests | 17 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 7 |
| Workspace reverse dependencies | 1 |
pie showData
title Public API by kind
"constant" : 2
"enum" : 2
"function" : 8
"method" : 2
"struct" : 2
pie showData
title Rust source composition
"Code" : 862
"Blank or comment" : 239
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.