Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
id: AILOG-2026-09-17-003
title: followups declare — supported writer for follow-up work classification (#432, #431)
status: accepted
created: 2026-09-17
agent: claude-opus-5-1m
confidence: high
review_required: false
risk_level: low
eu_ai_act_risk: not_applicable
nist_genai_risks: [human_ai_config]
iso_42001_clause: []
files_modified:
- cli/src/followups.rs
- cli/src/commands/followups/declare.rs
- cli/src/commands/followups/new.rs
- cli/src/commands/followups/status.rs
- cli/src/commands/followups/mod.rs
- cli/src/main.rs
- cli/src/validation.rs
- cli/tests/followups_test.rs
- cli/tests/validate_test.rs
- dist/.claude/skills/straymark-followups/SKILL.md (+ codex/agent/qoder/qwen mirrors)
- dist/.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md (+ es, zh-CN)
- dist/.straymark/templates/follow-ups-backlog.md
- docs/adopters/CLI-REFERENCE.md (+ es, zh-CN)
- docs/adopters/BATON.md (+ es, zh-CN)
- experiment-baton/07-track-c-adopter-kit.md
- cli/Cargo.toml, Cargo.lock, dist/dist-manifest.yml, version tables and footers, CHANGELOG.md, CLAUDE.md
observability_scope: none
tags: [followups, baton, adopter-feedback, cli]
related:
- 07-ai-audit/agent-logs/AILOG-2026-07-26-001-followups-mutation-creation-verbs.md
---

# AILOG: `followups declare` (#432, #431)

## Summary

Adds the missing supported writer for a follow-up's declared work classification. `straymark
followups declare FU-NNN --work-verb <v> [--design-provenance <p>]` writes an existing entry's
declaration, and `followups new` takes the same flags. Also fixes the CLI half of #431:
`straymark validate` warned on the registry template's commented example line in every adopter.

## Context

Estoa (#432) found two rules in tension. The registry is CLI-owned ("never hand-edit an entry"),
yet Track C asks follow-ups to declare `Work verb` / `Design provenance`, and no verb wrote those
bullets. The adopter left four entries undeclared rather than hand-edit the registry or put the
declaration in `Notes`, which is the correct call and the one this change makes unnecessary.

## Actions Performed

1. `followups::Declaration`: the validated declaration, built only from the controlled vocabulary.
`design_provenance` is accepted only with `implement`, as the ratification specifies. The
writer is strict; `validate` and Baton stay lenient readers.
2. `followups::set_entry_declaration`: replaces both bullets **as a unit**. They go where the old
ones were, else after `Cost` (the template position), else after the last bullet. The trailing
newlines are preserved as in `set_entry_field`.
3. `followups declare`: new subcommand. It is a no-op when the declaration is unchanged, refuses
invalid input before writing, and writes through `write_recounted` like every other verb.
4. `followups new --work-verb … [--design-provenance …]`; `followups status FU-NNN` shows the
declaration. `Entry` parses the two fields.
5. `check_followups_work_verb` ignores HTML comments and fenced blocks (#431). Before, the stock
template produced 2 advisory warnings in every adopter's `validate`.
6. Skill (all agent mirrors, regenerated by `gen_minimal_skills` and copied for qoder/qwen),
pattern doc ×3, template note, CLI-REFERENCE ×3, BATON.md ×3 and the Track C kit document the
writer. Versions bumped to fw-4.45.0 / cli-3.49.0. The stale CLI rows in the version tables
(cli-3.45.0) are corrected on the way.

## Decisions Made

- **Name `declare`**, not a generic `set-field`. Declaration is the ratified concept (#332); a
generic field setter would reopen hand-editing by another route.
- **Unit semantics.** Omitting `--design-provenance` removes a previous one. Otherwise
re-declaring `implement/upstream` as `design` would leave a contradictory provenance behind, the
same "agreement is on the whole declaration" rule adopted for Baton's task inheritance in
PR #430. `note` / `set-status` / `new` (the AILOG in `related`) set the write-path precedent
this verb follows.
- **No `--clear`.** Undeclaring an entry has no reported need, and "undeclared" is the default
state anyway.
- Not in scope: `drift --apply` still extracts entries undeclared. Inferring a verb from the source
AILOG would be the heuristic the #332 decision removed.

## Impact

- **Functionality**: adopters can declare follow-ups without hand-editing the registry; `validate`
is quiet on the stock template.
- **Performance / Security / Privacy / Environmental**: N/A.

## Verification

- [x] `cargo test -p straymark-cli`: 778 passed. The new tests cover `new` with a declaration and
rejected declarations leaving the registry untouched; `declare` set/replace/remove, no-op and
the neighbouring entry kept intact; validate on the shipped template and on a live invalid
value; a declaration round trip; the vocabulary.
- [x] Installed CLI 3.48 emits 2 warnings on the shipped template; this branch emits 0.
- [x] `gen_minimal_skills --check` and the qoder/qwen mirror tests pass.
- [x] Clippy: no new warnings for `straymark-cli` (18/24, same as `main`).
- [x] `straymark validate`: 0 errors.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ and this project uses [independent versioning](README.md#versioning) for Framewo

---

## Framework 4.45.0 / CLI 3.49.0 — 2026-09-17

Adopter report from Estoa (#432, Track C of Baton): the follow-ups registry is CLI-owned ("never hand-edit an entry"), and Track C asks each follow-up to declare its `Work verb` / `Design provenance`, but no verb could write those fields. An adopter honouring both rules was left with undeclared entries. This release adds the missing writer.

### Added (CLI)

- **`straymark followups declare FU-NNN --work-verb <v> [--design-provenance <p>]`** (#432): writes an existing entry's declared work classification, the bullets Baton's router reads (#332).
- The vocabulary is validated: `design | implement | audit | operate`; provenance `new | upstream`, accepted only with `implement`. Invalid input is refused before anything is written.
- The declaration is written **as a unit**: both bullets are replaced together, and omitting `--design-provenance` removes a previous provenance instead of pairing a stale one with the new verb. Re-declaring the same value is a no-op.
- **`followups new --work-verb … [--design-provenance …]`**: declares an entry at creation. The bullets land after `Cost`, as in the template's entry shape.
- `followups status FU-NNN` shows the declaration.

### Fixed (CLI)

- **`straymark validate` no longer warns on the registry template's example line** (#431). The shipped `follow-ups-backlog.md` documents the entry shape inside an HTML comment whose `- **Work verb**: design | implement | audit | operate` line tripped `FOLLOWUP-WORK-VERB` / `FOLLOWUP-DESIGN-PROVENANCE` in every adopter's registry. The check now reads only live entries and skips HTML comments and fenced blocks.

### Changed (Framework)

- The `straymark-followups` skill (all agent mirrors), `FOLLOW-UPS-BACKLOG-PATTERN.md` (EN/es/zh-CN) and the registry template's "never hand-edit" note now include `declare` and the declaration flags of `new` as the supported way to declare a follow-up. The skill adds a short "declare the work classification" step that says to leave an entry undeclared rather than guess, and never to put a declaration in `Notes`.

## Framework 4.44.0 / CLI 3.48.0 — 2026-08-14

Third and closing PR of CHARTER-02 (#419): a remediation AILOG used to be able to close a Critical finding with prose alone — no mechanical check preventing recurrence, and nothing asked for one. This release makes the absence of a guard a first-class, validatable fact instead of an invisible omission.
Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@ Loom releases are GitHub-release-only (no crates.io while experimental). Tag for
| `straymark followups verify FU-NNN [--premise "..."] [--verified] [--at DATE]` | Re-verify a dated hypothesis's premise at execution: surface/record the premise, stamp `Verified-at` (AIDEC-2026-07-18-001) |
| `straymark followups note FU-NNN "<text>" [--source ID]` | Append a dated annotation to an entry's `Notes` in one validated edit (#355) |
| `straymark followups set-status FU-NNN <status>` | Change an entry's status **and** recompute the CLI-owned counters in the same step — no `recount` to forget (#355) |
| `straymark followups new --title … --origin "CHARTER-NN §Scope" [--bucket …] [--premise …]` | Create an entry declared ex-ante, at Charter-declaration time, with an atomically-assigned id (#360) |
| `straymark followups new --title … --origin "CHARTER-NN §Scope" [--bucket …] [--premise …] [--work-verb … [--design-provenance …]]` | Create an entry declared ex-ante, at Charter-declaration time, with an atomically-assigned id (#360); optionally declared (#432) |
| `straymark followups declare FU-NNN --work-verb <v> [--design-provenance <p>]` | Write an entry's declared work classification (Baton #332) as one validated unit — the supported writer for the `Work verb` / `Design provenance` bullets (#432) |
| `straymark architecture generate [path] [--force] [--out DIR]` | EXPERIMENTAL (Loom A1.2) — write a first-draft `architecture/model.yml` + `plan.drawio` by mining codebase structure (top-level source dirs → components) enriched with ADR C4 diagrams + "Affected Components" tables. `--force` overwrites; `--out` overrides the default `.straymark/architecture/` |
| `straymark architecture sync [path] [--out DIR] [--apply]` | EXPERIMENTAL (Loom A1.3) — append-only: detect new top-level source dirs / ADR components not yet covered by the model and append them to `model.yml` + `plan.drawio` (never clobbers human edits/geometry). Dry-run by default; `--apply` writes |
| `straymark architecture validate [path] [--out DIR] [--output FMT]` | EXPERIMENTAL (Loom A1.3) — report model↔plan.drawio integrity signals (`undrawn`/`unmodeled`/`empty`) via `core::architecture::validate_model`. `--output text\|json\|markdown`; exits 1 when any signal is found (CI-gateable) |
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ StrayMark uses independent version tags for each component:

| Component | Tag prefix | Example | Includes |
| --- | --- | --- | --- |
| Framework | `fw-` | `fw-4.44.0` | Templates (12 types), governance, directives, Charter template + schema |
| CLI | `cli-` | `cli-3.45.0` | The `straymark` binary |
| Framework | `fw-` | `fw-4.45.0` | Templates (12 types), governance, directives, Charter template + schema |
| CLI | `cli-` | `cli-3.49.0` | The `straymark` binary |
| Loom (EXPERIMENTAL) | `loom-` | `loom-0.4.2` | The `straymark-loom` visualization server, downloaded on demand by `straymark loom serve` |

Check installed versions with `straymark status` or `straymark about`.
Expand Down
2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "straymark-cli"
version = "3.48.0"
version = "3.49.0"
edition = "2021"
description = "CLI for StrayMark — the cognitive discipline your AI-assisted projects need"
license = "MIT"
Expand Down
68 changes: 68 additions & 0 deletions cli/src/commands/followups/declare.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//! `straymark followups declare FU-NNN --work-verb … [--design-provenance …]`
//! — write an existing entry's declared work classification (Baton #332).
//!
//! The registry is CLI-owned ("never hand-edit an entry"), and Track C asks
//! follow-ups to declare `Work verb` / `Design provenance` — yet no verb could
//! write them: `new` had no flags, `note` only appends to `Notes` (not the
//! slot Baton reads), and `drift --apply` extracts without them. An adopter
//! honouring both rules was left with undeclared entries (Estoa, #432).
//!
//! The declaration is written as a unit: both bullets are replaced together,
//! so omitting `--design-provenance` removes a previous one instead of letting
//! a stale provenance pair with the new verb. The vocabulary is validated here
//! (the strict writer); `validate` and Baton stay lenient readers.

use anyhow::{anyhow, bail, Result};
use colored::Colorize;

use crate::commands::followups::note::guard_parse_warnings;
use crate::followups::{self, Declaration};

pub fn run(path: &str, fu_id: &str, work_verb: &str, design_provenance: Option<&str>) -> Result<()> {
let declaration = Declaration::new(work_verb, design_provenance)?;

let resolved = crate::utils::resolve_project_root(path)
.ok_or_else(|| anyhow!("StrayMark not installed. Run 'straymark init' first."))?;
let registry_path = followups::registry_path(&resolved.path);
if !registry_path.exists() {
bail!(
"No follow-ups registry at {}.\n hint: see STRAYMARK.md §16 for the adoption walkthrough.",
registry_path.display()
);
}
let registry = followups::parse_registry(&registry_path)?;
guard_parse_warnings(&registry)?;

let entry = followups::find_entry_unique(&registry, fu_id)?.clone();
let describe = |verb: Option<&str>, provenance: Option<&str>| match (verb, provenance) {
(None, _) => "undeclared".to_string(),
(Some(v), None) => v.to_string(),
(Some(v), Some(p)) => format!("{v} / {p}"),
};
let previous = describe(entry.work_verb.as_deref(), entry.design_provenance.as_deref());
let next = describe(
Some(&declaration.work_verb),
declaration.design_provenance.as_deref(),
);

if declaration.matches(&entry) {
println!(
" {} {} is already declared `{}` — nothing to change.",
"OK".green().bold(),
entry.fu_id,
next
);
return Ok(());
}

let body = followups::set_entry_declaration(&registry.body, &entry, &declaration);
followups::write_recounted(&registry_path, &registry.frontmatter_raw, &body)?;

crate::utils::success(&format!(
"{}: {} → {}",
entry.fu_id,
previous.dimmed(),
next.bold()
));
Ok(())
}
3 changes: 3 additions & 0 deletions cli/src/commands/followups/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
//! annotate, re-state and create entries through the same surgical helpers the
//! rest of the namespace writes with, recomputing the CLI-owned counters in the
//! same step so the edit-then-`recount` desync window does not exist.
//! `declare` (#432) does the same for an entry's declared work classification
//! (Baton #332), the one declaration slot that still had no writer.

pub mod declare;
pub mod drift;
pub mod install_merge_driver;
pub mod list;
Expand Down
13 changes: 13 additions & 0 deletions cli/src/commands/followups/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub struct NewArgs<'a> {
pub trigger: Option<&'a str>,
pub destination: Option<&'a str>,
pub cost: Option<&'a str>,
pub work_verb: Option<&'a str>,
pub design_provenance: Option<&'a str>,
pub premise: Option<&'a str>,
}

Expand Down Expand Up @@ -66,6 +68,16 @@ pub fn run(args: NewArgs<'_>) -> Result<()> {
if status == FuStatus::Promoted {
bail!("An entry cannot be created as `promoted` — use `followups promote` on an existing entry.");
}
// Declared work classification (Baton #332, #432). Validated before any
// write: the registry only ever receives a well-formed declaration.
let declaration = match (args.work_verb, args.design_provenance) {
(Some(verb), provenance) => Some(followups::Declaration::new(verb, provenance)?),
(None, Some(_)) => bail!(
"--design-provenance needs --work-verb implement: provenance qualifies an \
implementation, it is not a declaration on its own."
),
(None, None) => None,
};

let resolved = utils::resolve_project_root(args.path)
.ok_or_else(|| anyhow!("StrayMark not installed. Run 'straymark init' first."))?;
Expand Down Expand Up @@ -95,6 +107,7 @@ pub fn run(args: NewArgs<'_>) -> Result<()> {
args.trigger,
args.destination,
args.cost,
declaration.as_ref(),
args.premise,
&notes,
);
Expand Down
2 changes: 2 additions & 0 deletions cli/src/commands/followups/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ fn print_entry_detail(registry: &Registry, id: &str) -> Result<()> {
print_field("Trigger", entry.trigger.as_deref());
print_field("Destination", entry.destination.as_deref());
print_field("Cost", entry.cost.as_deref());
print_field("Work verb", entry.work_verb.as_deref());
print_field("Design provenance", entry.design_provenance.as_deref());
if !entry.labels.is_empty() {
print_field("Labels", Some(&entry.labels.join(", ")));
}
Expand Down
Loading
Loading