Skip to content

Commit 7b95ffc

Browse files
montfortclaude
andauthored
feat(cli): cli-3.22.0 — charter close reconciles follow-ups + offers TDE promotion (#135 Tier 3) (#236)
Closes the loop between Charter close and the follow-ups registry — the last open tier of the follow-ups automation roadmap (#135), unblocked now that drift detection is reliable (cli-3.21.0, #229/#231). After an interactive `charter close`, the command runs the default `followups drift` scan (committed git range ∪ working tree) over the just-written AILOGs, extracts any §Follow-ups / R<N> (new) content not yet in the registry into `## Bucket: ready`, then offers per-entry TDE promotion against the four AGENT-RULES.md §3 criteria. Declining leaves the follow-up extracted (captured, not promoted); accepting runs the `followups promote` flow. No-op without a registry or when nothing is unextracted; skipped on the --from-template paths. The scan reuses the stabilized default drift, not `originating_ailogs` (that field is the ex-ante seed, not the execution AILOGs where follow-ups live). Refactors `followups drift` into reusable side-effect-free cores — detect_drift_candidates() and apply_candidates() (returns the created FU ids) — shared by `followups drift` and the close integration. drift output unchanged. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 36bb3f4 commit 7b95ffc

11 files changed

Lines changed: 319 additions & 89 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ and this project uses [independent versioning](README.md#versioning) for Framewo
77

88
---
99

10+
## CLI 3.22.0 — `charter close` reconciles follow-ups and offers TDE promotion (RFC #135 Tier 3)
11+
12+
Closes the loop between Charter close and the follow-ups registry — the last open tier of the follow-ups automation roadmap ([#135](https://github.com/StrangeDaysTech/straymark/issues/135)), unblocked now that drift detection is reliable (cli-3.21.0, #229/#231).
13+
14+
### Added (CLI)
15+
16+
- **`charter close` follow-ups integration**: after an **interactive** close, the command runs the default `followups drift` scan (committed git range ∪ working tree) over the just-written AILOGs, extracts any `§Follow-ups` / `R<N> (new)` content not yet in the registry into `## Bucket: ready`, and then offers **per-entry TDE promotion** against the four AGENT-RULES.md §3 criteria. Declining a prompt leaves the follow-up extracted (captured, not promoted); accepting runs the `followups promote` flow (creates the TDE with `promoted_from_followup` traceability). No-op when there is no registry or nothing is unextracted; skipped on the `--from-template` paths (no interactive prompt context). The scan reuses the stabilized default drift (not scoped to `originating_ailogs`, which is the ex-ante seed rather than the execution AILOGs where follow-ups live).
17+
18+
### Changed (CLI)
19+
20+
- Refactored `followups drift` into reusable, side-effect-free cores — `detect_drift_candidates()` (scan + per-follow-up hash dedup) and `apply_candidates()` (write + return the created `FU-NNN` ids) — shared by `followups drift` and the new `charter close` integration. `followups drift` output is unchanged.
21+
22+
---
23+
1024
## Framework 4.25.0 / CLI 3.21.0 — follow-ups drift correctness: see the working tree + catch appended follow-ups
1125

1226
Two silent-data-loss bugs in `straymark followups drift`, both surfaced by the reference adopter (Sentinel) and both prerequisites for wiring drift into the Charter-close flow (RFC [#135](https://github.com/StrangeDaysTech/straymark/issues/135) Tier 3).

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ StrayMark uses independent version tags for each component:
278278
| Component | Tag prefix | Example | Includes |
279279
| --- | --- | --- | --- |
280280
| Framework | `fw-` | `fw-4.25.0` | Templates (12 types), governance, directives, Charter template + schema |
281-
| CLI | `cli-` | `cli-3.21.0` | The `straymark` binary |
281+
| CLI | `cli-` | `cli-3.22.0` | The `straymark` binary |
282282

283283
Check installed versions with `straymark status` or `straymark about`.
284284

cli/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "straymark-cli"
3-
version = "3.21.0"
3+
version = "3.22.0"
44
edition = "2021"
55
description = "CLI for StrayMark — the cognitive discipline your AI-assisted projects need"
66
license = "MIT"

cli/src/commands/charter/close.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use colored::Colorize;
2121
use std::path::{Path, PathBuf};
2222

2323
use crate::charter::{self, charters_dir, Charter, CharterStatus};
24+
use crate::followups;
2425
use crate::prompts;
2526
use crate::telemetry_schema::TelemetrySchema;
2627
use crate::utils;
@@ -134,9 +135,92 @@ pub fn run(
134135
);
135136
}
136137
}
138+
139+
// Tier 3 (RFC #135): in the interactive close, scan recent AILOGs for
140+
// unextracted follow-ups, extract them into the registry, and offer to
141+
// promote each to a TDE. Only the interactive flow has a confirmed TTY
142+
// (require_interactive ran above) and a finalized close; the --from-template
143+
// paths are skipped — run `straymark followups drift --apply` there.
144+
if !from_template {
145+
offer_followup_promotions(path, project_root)?;
146+
}
147+
148+
Ok(())
149+
}
150+
151+
/// Reconcile the follow-ups registry against the work just closed, then offer
152+
/// per-entry TDE promotion. No-op when the project has no registry or when the
153+
/// recent AILOGs carry no unextracted follow-up content. (Tier 3, RFC #135.)
154+
fn offer_followup_promotions(path: &str, project_root: &Path) -> Result<()> {
155+
use crate::commands::followups::{drift, promote};
156+
157+
let registry_path = followups::registry_path(project_root);
158+
if !registry_path.exists() {
159+
// Project hasn't adopted the follow-ups registry — nothing to reconcile.
160+
return Ok(());
161+
}
162+
let registry = followups::parse_registry(&registry_path)?;
163+
// Default scan (git range ∪ working tree): at close time this is exactly the
164+
// Charter's just-written AILOGs. Not scoped to `originating_ailogs` — that
165+
// field is the ex-ante seed, not the execution AILOGs where follow-ups live.
166+
let drifted = drift::detect_drift_candidates(project_root, &registry, false, None);
167+
if drifted.is_empty() {
168+
return Ok(());
169+
}
170+
171+
let today = Local::now().format("%Y-%m-%d").to_string();
172+
let report = drift::apply_candidates(&registry_path, &registry, &drifted, &today)?;
173+
174+
println!();
175+
println!("{}", "── Follow-ups ──".bold());
176+
utils::success(&format!(
177+
"Extracted {} follow-up{} from {} AILOG(s) into the registry (`## Bucket: ready`).",
178+
report.applied.len(),
179+
if report.applied.len() == 1 { "" } else { "s" },
180+
report.ailogs
181+
));
182+
if report.suspected > 0 {
183+
println!(
184+
" {} {} carried a closure marker → extracted as suspected-closed; confirm at triage.",
185+
"!".magenta().bold(),
186+
report.suspected
187+
);
188+
}
189+
println!(
190+
" Review against the TDE-promotion criteria (AGENT-RULES.md §3): prior-Charter heritage, spans multiple modules/Charters, needs a dedicated Charter, or needs human prioritization."
191+
);
192+
193+
for fu in &report.applied {
194+
// suspected-closed entries are likely already resolved in-Charter, so
195+
// they are rarely transversal debt — flag that context in the prompt.
196+
let marker = if fu.suspected_closed {
197+
" [suspected-closed]"
198+
} else {
199+
""
200+
};
201+
let label = format!(
202+
"Promote {} — {}{} to a TDE?",
203+
fu.fu_id,
204+
truncate_desc(&fu.description, 60),
205+
marker
206+
);
207+
if prompts::prompt_bool(&label, false)? {
208+
promote::run(path, &fu.fu_id, None)?;
209+
}
210+
}
137211
Ok(())
138212
}
139213

214+
/// Single-line, length-bounded version of a follow-up description for prompts.
215+
fn truncate_desc(s: &str, max: usize) -> String {
216+
if s.chars().count() <= max {
217+
s.to_string()
218+
} else {
219+
let truncated: String = s.chars().take(max).collect();
220+
format!("{}…", truncated.trim_end())
221+
}
222+
}
223+
140224
/// Build the `.straymark/charters/CHARTER-NN.telemetry.yaml` path for a Charter.
141225
fn telemetry_path_for(charters_state_dir: &Path, charter: &Charter) -> PathBuf {
142226
// Strip optional slug suffix to keep the telemetry filename stable across

0 commit comments

Comments
 (0)