From b695a32ee612a5b959d06e5c1d55c14dbf27ec72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Villase=C3=B1or=20Montfort?= <195970+montfort@users.noreply.github.com> Date: Sun, 26 Jul 2026 00:24:39 -0600 Subject: [PATCH] feat(followups): mutation + ex-ante creation verbs (CHARTER-01, #355 + #360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the last flows that required hand-editing the CLI-parsed registry. - `followups note FU-NNN "" [--source ID]` — dated annotation appended to Notes. The field is single-line by parser contract, so annotations compose onto the existing value; --source records what motivated it. - `followups set-status FU-NNN ` — status AND counters in one step, closing the desync window recount exists to clean up after. An out-of-vocabulary status is refused, not written: the parser is lenient, so a typo would not fail — it would silently drop the entry from every counter. `promoted` redirects to `followups promote`, which writes the TDE the status points at. - `followups new --title … --origin "CHARTER-NN §Scope"` — an entry declared ex-ante, before any AILOG exists. The id is assigned atomically and printed, so a Charter cites an entry that EXISTS: ids are minted max+1 at extraction, so the adopter's forward-referenced FU-011 would have been handed to an unrelated entry by the next `drift --apply`. All three refuse to write a registry with parse warnings — a surgical edit against a mis-read structure can corrupt neighbouring entries. One shared recompute-and-write path (`write_recounted`) backs every mutating verb, and `recount` now uses it too, so it stays the idempotent check on their arithmetic rather than a second implementation. The schema needed no change: `origin_class` already carried `ex-ante-planning` and `source_hash` is not a schema property. The vocabulary for the ex-ante case existed; only the creation path was missing. Also fixes two pre-existing shape defects in the shared write helpers, which every drift/promote/verify write had been degrading the registry with: `set_entry_field` collapsed an entry's trailing blank line (gluing it to the next `## Bucket:` heading) and `insert_into_bucket` omitted the blank line before the following section. Documented as R6 (new, not in Charter). fw-4.37.0 / cli-3.39.0. CHARTER-01 closed; AILOG-2026-07-26-001. Co-Authored-By: Claude Opus 5 (1M context) --- ...6-001-followups-mutation-creation-verbs.md | 93 ++++++ ...-registry-mutation-and-ex-ante-creation.md | 18 +- .straymark/charters/README.md | 6 +- CHANGELOG.md | 24 ++ CLAUDE.md | 3 + Cargo.lock | 2 +- README.md | 4 +- cli/Cargo.toml | 2 +- cli/src/commands/followups/mod.rs | 9 + cli/src/commands/followups/new.rs | 132 +++++++++ cli/src/commands/followups/note.rs | 79 ++++++ cli/src/commands/followups/recount.rs | 10 +- cli/src/commands/followups/set_status.rs | 100 +++++++ cli/src/commands/followups/verify.rs | 8 +- cli/src/followups.rs | 225 ++++++++++++++- cli/src/main.rs | 96 +++++++ cli/tests/followups_test.rs | 267 ++++++++++++++++++ dist/.agent/workflows/straymark-followups.md | 25 +- .../skills/straymark-followups/SKILL.md | 25 +- .../skills/straymark-followups/SKILL.md | 25 +- .../skills/straymark-followups/SKILL.md | 25 +- dist/.straymark/00-governance/AGENT-RULES.md | 2 +- .../00-governance/C4-DIAGRAM-GUIDE.md | 2 +- .../00-governance/DOCUMENTATION-POLICY.md | 2 +- .../FOLLOW-UPS-BACKLOG-PATTERN.md | 15 +- .../00-governance/QUICK-REFERENCE.md | 2 +- .../00-governance/i18n/es/AGENT-RULES.md | 2 +- .../00-governance/i18n/es/C4-DIAGRAM-GUIDE.md | 2 +- .../i18n/es/DOCUMENTATION-POLICY.md | 2 +- .../i18n/es/FOLLOW-UPS-BACKLOG-PATTERN.md | 15 +- .../00-governance/i18n/es/QUICK-REFERENCE.md | 2 +- .../00-governance/i18n/zh-CN/AGENT-RULES.md | 2 +- .../i18n/zh-CN/C4-DIAGRAM-GUIDE.md | 2 +- .../i18n/zh-CN/DOCUMENTATION-POLICY.md | 2 +- .../i18n/zh-CN/FOLLOW-UPS-BACKLOG-PATTERN.md | 15 +- .../i18n/zh-CN/QUICK-REFERENCE.md | 2 +- .../templates/follow-ups-backlog.md | 11 +- dist/dist-manifest.yml | 2 +- docs/adopters/CLI-REFERENCE.md | 62 +++- docs/i18n/es/README.md | 4 +- docs/i18n/es/adopters/CLI-REFERENCE.md | 30 +- docs/i18n/zh-CN/README.md | 4 +- docs/i18n/zh-CN/adopters/CLI-REFERENCE.md | 30 +- 43 files changed, 1320 insertions(+), 70 deletions(-) create mode 100644 .straymark/07-ai-audit/agent-logs/AILOG-2026-07-26-001-followups-mutation-creation-verbs.md create mode 100644 cli/src/commands/followups/new.rs create mode 100644 cli/src/commands/followups/note.rs create mode 100644 cli/src/commands/followups/set_status.rs diff --git a/.straymark/07-ai-audit/agent-logs/AILOG-2026-07-26-001-followups-mutation-creation-verbs.md b/.straymark/07-ai-audit/agent-logs/AILOG-2026-07-26-001-followups-mutation-creation-verbs.md new file mode 100644 index 00000000..bcad46a2 --- /dev/null +++ b/.straymark/07-ai-audit/agent-logs/AILOG-2026-07-26-001-followups-mutation-creation-verbs.md @@ -0,0 +1,93 @@ +--- +id: AILOG-2026-07-26-001 +title: Follow-ups mutation + ex-ante creation verbs (CHARTER-01, #355 + #360) +status: accepted +created: 2026-07-26 +agent: claude-opus-5-1m +confidence: high +review_required: false +risk_level: low +eu_ai_act_risk: not_applicable +nist_genai_risks: [] +iso_42001_clause: [] +lines_changed: 0 +files_modified: [] +observability_scope: none +tags: [follow-ups, cli, governance, adopter-feedback, self-adoption, charter-01] +related: [AIDEC-2026-07-18-001] +--- + +# AILOG: Follow-ups mutation + ex-ante creation verbs (CHARTER-01) + +## Summary + +Executed [CHARTER-01](../../charters/01-follow-ups-registry-mutation-and-ex-ante-creation.md): three new verbs — `followups note`, `followups set-status`, `followups new` — closing the last flows that required hand-editing the CLI-parsed registry (Weft field reports #355, #360). Ships as **fw-4.37.0 / cli-3.39.0**. First *executed* Charter of the self-adopted repo. + +## Context + +Every pre-existing `followups` verb either read the registry or wrote it as a side effect of extraction. Mutating an entry meant hand-editing markdown the CLI parses; creating one at Charter-declaration time was impossible (both population paths assume an ex-post origin). The #360 hazard was live in the adopter's tree: `CHARTER-06` cited `FU-011` three times as a forward reference with nothing reserving it, and ids are minted `max(existing) + 1` at extraction, so the next unrelated `drift --apply` would have silently repointed those citations. + +## Actions Performed + +1. **Shared write path** — `followups::write_recounted` / `recounted_frontmatter`: recompute the CLI-owned counters *from the body about to be written* and persist in one call. `recount` and `verify` were refactored onto it, so there is one implementation of the arithmetic rather than two that could disagree. +2. **`note`** — appends to `Notes` via the pure `append_note` composer: `[date]` or `[date · source]` stamp, composed onto any existing value (the field is single-line by parser contract, so annotations compose instead of stacking). +3. **`set-status`** — validates against the `FuStatus` vocabulary and writes status + counters together. Two refusals rather than silent damage: an out-of-vocabulary value (the lenient parser would degrade it to `unknown`, dropping the entry from every counter without failing) and `promoted` (redirected to `followups promote`, which writes the TDE the status points at). +4. **`new`** — mints an ex-ante entry through `render_declared_entry` (a deliberate sibling of `render_new_entry`, without its AILOG-derived `Source-hash`), inserts it into the requested bucket, recounts, and prints the assigned id. +5. **Guard for all three** — `guard_parse_warnings` aborts before any write when the registry has parse warnings. +6. **Framework** — pattern doc (EN + es + zh-CN) documents the verbs and the ex-ante origin; the registry template states the write path and that entries are never hand-edited; the `/straymark-followups` skill uses `set-status` + `note` for triage and gains a Charter-declaration step. +7. **Docs + release** — CLI-REFERENCE ×3 locales, `CLAUDE.md` command table, version tables, `CHANGELOG.md`, `dist-manifest.yml` → 4.37.0, `cli/Cargo.toml` → 3.39.0, governance footers. + +## Decisions Made + +- **The schema needed no change.** The Charter listed "confirm the ex-ante entry shape validates without `Source-hash`; adjust `$defs.entry` if not". It validates as-is: `origin` is required (so `--origin` is required, not optional), `origin_class` already carries **`ex-ante-planning`** in its enum, and `source_hash` is not a schema property at all. **The schema had already named the ex-ante case; only the creation path was missing.** That reframes #360 from "add a field" to "the vocabulary was there, the verb was not". +- **`--origin` is required** rather than defaulted. Schema v1 requires it, and an entry whose origin is guessed is the kind of half-truth the registry exists to prevent. +- **Two fixes to shared helpers, not to my new code** (see `## Risk` R6): the spacing defects belonged to `set_entry_field` / `insert_into_bucket` and were degrading the file on every `drift`/`promote`/`verify` write. Fixing them at the root rather than working around them in `note` is why the diff touches those two functions. +- **Deferred options stayed deferred.** No id-reservation primitive and no Charter-scanning in `drift`, exactly as declared. Nothing in execution argued for either. + +## Verification + +- Full workspace `cargo test`: **38 groups green**. 10 new integration tests (one per verb path: happy path, composition, malformed-registry abort with byte-identical file, unknown/`promoted` status refusal, no-op same-status, id assignment, bucket routing + `TBD` defaults, required-origin/bucket validation, and the R4 `new`-then-`drift` no-duplicate check) plus 5 new unit tests (`append_note` composition incl. whitespace-only inputs, `render_declared_entry` shape + parser round-trip, premise omission, and the two spacing regressions). +- `cargo clippy -p straymark-cli`: no warnings in `note.rs` / `set_status.rs` / `new.rs`. +- `gen_codex_skills --check`: in sync (15 skills). +- **End-to-end with the built binary** against a scratch registry, running the exact flow the Charter declared: `new` (assigned FU-003, `Origin-class: ex-ante-planning`, no hash) → `note` on an unrelated entry (status unchanged, prior note preserved) → `set-status … closed` (counters moved in the same step) → **`recount` reports "already in sync"** (the desync #355 describes cannot occur) → `list` renders all three → `drift` does not re-extract. Then the guard: appending a malformed `### FU-` heading made `note` exit 1 with nothing written. + +## Risk + +- **R1 (registry corruption) — mitigated and tested.** The parse-warning guard aborts before any write; the integration test asserts the file is byte-identical after a refused write. +- **R2 (counter desync moves into the verb) — mitigated.** One shared recompute path; the `set-status` test asserts a following `recount` finds nothing to do. +- **R3 (concurrent id minting)** — unchanged and still not claimed: atomicity is scoped to "one command, not two steps", which is the actual #360 hazard. +- **R4 (ex-ante entry confuses `drift` dedup) — verified benign.** `new`-then-`drift --scan-all` produces no duplicate; a duplicate arising from a *later* AILOG mention remains accepted behavior, resolved with `set-status … superseded`. +- **R5 (scope creep into the deferred options)** — did not occur. +- **R6 (new, not in Charter) — two pre-existing markdown-shape defects in shared write helpers.** `set_entry_field` collapsed an entry's trailing `"\n\n"` to `"\n"` (gluing the edited entry to the next `## Bucket:` heading) and `insert_into_bucket` appended entries with no blank line before the following section. Both predate this Charter and were silently degrading the registry's shape on every `drift --apply` / `promote` / `verify` write; making field writes routine via `note` is what exposed them. Fixed at the root with two regression unit tests. Severity low (cosmetic on a human-read artifact, no parse impact), but left unfixed it would have made every new `note` call visibly reshape the file. + +## Modified Files + +| File | Change | +|------|--------| +| `cli/src/followups.rs` | `write_recounted` / `recounted_frontmatter`, `append_note`, `render_declared_entry`, `CANONICAL_BUCKETS`, two shared-helper spacing fixes, 5 unit tests | +| `cli/src/commands/followups/note.rs` | New — `note` verb + the shared parse-warning guard | +| `cli/src/commands/followups/set_status.rs` | New — `set-status` verb | +| `cli/src/commands/followups/new.rs` | New — `new` verb (`NewArgs`) | +| `cli/src/commands/followups/mod.rs` | Wire the three modules | +| `cli/src/commands/followups/recount.rs` | Use the shared recompute path | +| `cli/src/commands/followups/verify.rs` | Use the shared write path (removes a duplicated recount block) | +| `cli/src/main.rs` | Three subcommands + flags + routing | +| `cli/tests/followups_test.rs` | 10 integration tests | +| `cli/Cargo.toml` | Version 3.39.0 | +| `dist/.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md` (+ i18n es, zh-CN) | Verbs + ex-ante origin section | +| `dist/.straymark/templates/follow-ups-backlog.md` | Write path + never-hand-edit note | +| `dist/.claude/skills/straymark-followups/SKILL.md` | Triage via `set-status`/`note` + Charter-declaration step | +| `dist/.codex/skills/straymark-followups/SKILL.md` | Regenerated (`gen_codex_skills`) | +| `dist/.gemini/skills/straymark-followups/SKILL.md`, `dist/.agent/workflows/straymark-followups.md` | Hand-synced (see Closing notes — the generator covers only `.codex`) | +| `dist/dist-manifest.yml` | Version 4.37.0 | +| `dist/.straymark/00-governance/{QUICK-REFERENCE,AGENT-RULES,DOCUMENTATION-POLICY,C4-DIAGRAM-GUIDE,FOLLOW-UPS-BACKLOG-PATTERN}.md` (+ i18n ×2) | fw version footers (see Closing notes) | +| `docs/adopters/CLI-REFERENCE.md` (+ i18n es, zh-CN) | Three verbs documented | +| `README.md` (+ i18n es, zh-CN) | Version tables | +| `CLAUDE.md` | Command table rows | +| `CHANGELOG.md` | fw-4.37.0 / cli-3.39.0 entry | +| `.straymark/charters/01-follow-ups-registry-mutation-and-ex-ante-creation.md` | Status → closed, closing notes | +| `.straymark/charters/README.md` | Row moved to Closed | + +## Follow-ups + +- None. The Charter's declared scope shipped in full; both deliberately-deferred options (#360 Options B and C) remain deferred with the reasoning recorded in the Charter, and nothing in execution argued for revisiting them. diff --git a/.straymark/charters/01-follow-ups-registry-mutation-and-ex-ante-creation.md b/.straymark/charters/01-follow-ups-registry-mutation-and-ex-ante-creation.md index cc47b67c..7a047c2b 100644 --- a/.straymark/charters/01-follow-ups-registry-mutation-and-ex-ante-creation.md +++ b/.straymark/charters/01-follow-ups-registry-mutation-and-ex-ante-creation.md @@ -1,7 +1,9 @@ --- charter_id: CHARTER-01-follow-ups-registry-mutation-and-ex-ante-creation -status: declared +status: closed +closed_at: 2026-07-26 effort_estimate: M +execution_ailogs: [AILOG-2026-07-26-001] trigger: "Two adopter field reports from Weft (#355, #360) filed against the same seam: every path that mutates or creates a registry entry currently goes through hand-editing a CLI-parsed file. #360 documents a live id-collision hazard in the adopter's tree — CHARTER-06 cites FU-011 three times as a forward reference with nothing reserving it." work_verb: implement design_provenance: new @@ -9,7 +11,7 @@ design_provenance: new # Charter: Follow-ups registry: mutation and ex-ante creation verbs -> **Status (mirrored from frontmatter — source of truth is above):** declared. Effort: M (1–3 days). +> **Status (mirrored from frontmatter — source of truth is above):** closed 2026-07-26. Effort: M (declared 1–3 days; executed in one session). Execution: `AILOG-2026-07-26-001`. Released as fw-4.37.0 / cli-3.39.0. > > **Origin:** adopter field reports [#355](https://github.com/StrangeDaysTech/straymark/issues/355) and [#360](https://github.com/StrangeDaysTech/straymark/issues/360), both from the Weft adopter (registered in #357). No originating AILOG — this Charter precedes execution. @@ -167,3 +169,15 @@ When closing this Charter: 4. **Status frontmatter** moves from `in-progress` to `closed` (+ `closed_at:`). 5. **Do not delete** this file. 6. Close #355 and #360 with a comment stating what shipped and what was deliberately not built (the two deferred options above), so the adopter sees the reasoning rather than silence. + +## Closing notes + +`straymark charter drift CHARTER-01` reports **1 omission and 14 scope expansions** against `## Files to modify` (29 declared, 43 modified). All four causes, remediated atomically in this same PR: + +- **`dist/.gemini/skills/…` and `dist/.agent/workflows/…` were declared as "Regenerated (same generator)" — they are not.** `gen_codex_skills` emits only the `.codex` surface; the Gemini and `.agent` mirrors have been hand-maintained since fw-4.22.0 (#221). Hand-synced with the same content edits instead. The declaration was authored from the CI check's name (`gen_codex_skills --check`) rather than from what the generator actually writes — the reconnaissance discipline (#210) was applied to the *files* but not to the *mechanism* that maintains them. +- **The governance version footers were not declared.** A framework bump requires updating the `fw-X.Y.Z` footer in `QUICK-REFERENCE`, `AGENT-RULES`, `DOCUMENTATION-POLICY`, `C4-DIAGRAM-GUIDE` and `FOLLOW-UPS-BACKLOG-PATTERN` (×3 locales each) per the release protocol in `CLAUDE.md`; `## Files to modify` listed only `dist-manifest.yml`. Applied. +- **`dist/.straymark/schemas/follow-ups-backlog.schema.v1.json` was declared and NOT modified** — correctly. The Charter declared it conditionally ("confirm the ex-ante shape validates; adjust `$defs.entry` if it does not"), and it validates unchanged: `origin_class` already contains `ex-ante-planning`, and `source_hash` is not a schema property at all. Kept in the declaration rather than deleted: the check was real work, and its outcome sharpened what #360 was — the vocabulary for the ex-ante case already existed, only the creation path was missing. + +- **`cli/src/commands/followups/verify.rs` and `Cargo.lock` were not declared.** `verify.rs` was refactored onto the shared `write_recounted` path — the same consolidation the Charter declared for `recount.rs`, but only that one file was named; the declaration under-counted its own de-duplication. `Cargo.lock` moves with any version bump and is mechanical. + +One new risk emerged, documented in the AILOG as `R6 (new, not in Charter)`: two pre-existing markdown-shape defects in the shared write helpers (`set_entry_field` collapsing an entry's trailing blank line; `insert_into_bucket` omitting the one before the next section), which every `drift`/`promote`/`verify` write had been degrading the registry with. Fixed at the root with regression tests, since `note` would otherwise have made the reshaping visible on every call. diff --git a/.straymark/charters/README.md b/.straymark/charters/README.md index 8f888cf0..449ca38c 100644 --- a/.straymark/charters/README.md +++ b/.straymark/charters/README.md @@ -10,8 +10,10 @@ Rows move from `## Active` to `## Closed` as part of the closing PR, not afterwa | NN | Charter | Status | Effort | Origin | |---|---|---|---|---| -| 01 | [Follow-ups registry: mutation and ex-ante creation verbs](01-follow-ups-registry-mutation-and-ex-ante-creation.md) | declared | M | Adopter feedback [#355](https://github.com/StrangeDaysTech/straymark/issues/355), [#360](https://github.com/StrangeDaysTech/straymark/issues/360) | +| — | _None._ | | | | ## Closed -_None yet._ +| NN | Charter | Closed | Effort | Shipped as | PR | +|---|---|---|---|---|---| +| 01 | [Follow-ups registry: mutation and ex-ante creation verbs](01-follow-ups-registry-mutation-and-ex-ante-creation.md) | 2026-07-26 | M | fw-4.37.0 / cli-3.39.0 | #376 | diff --git a/CHANGELOG.md b/CHANGELOG.md index 01087e3f..0721aa75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,30 @@ and this project uses [independent versioning](README.md#versioning) for Framewo --- +## Framework 4.37.0 / CLI 3.39.0 — 2026-07-26 + +Closes the last flows that required hand-editing the CLI-parsed follow-ups registry (CHARTER-01; Weft field reports #355 and #360). + +### Added (CLI) + +- **`straymark followups note FU-NNN "" [--source ID]`** *(new)* — append a dated annotation to an entry's `Notes` in one validated edit. `Notes` is a single-line field by parser contract, so annotations **compose** onto the existing value rather than stacking. `--source CHARTER-NN|AILOG-…` records what motivated the note, so it stays attributable instead of becoming undated prose. Covers #355's case: recording a *partial* mitigation without changing status. +- **`straymark followups set-status FU-NNN `** *(new)* — change the status **and** recompute the CLI-owned counters in the same step, closing the desync window `recount` exists to clean up after (#355): the two-step it replaces desyncs the moment you forget the second half, leaving the counters quietly lying about the backlog. A status outside the vocabulary is **refused, not written** — the parser is lenient, so a typo would not fail: it would silently drop the entry from every counter. `promoted` redirects to `followups promote`, which also writes the TDE that gives the status something to point at. +- **`straymark followups new --title … --origin …`** *(new)* — create an entry whose origin is a **Charter declaration** (ex-ante), before any AILOG exists (#360). Written with `Origin-class: ex-ante-planning` and **no `Source-hash`** (there is no AILOG to hash; inventing one would make a later `drift --apply` believe it had extracted something it never saw). The id is assigned atomically and printed, so a Charter cites an entry that **exists** instead of a reserved guess: ids are minted `max(existing) + 1` at extraction time, so the reporting adopter's forward-referenced `FU-011` would have been handed to an unrelated entry by the next `drift --apply`. +- All three **refuse to write a registry with parse warnings** — a surgical edit against a structure the parser mis-read can corrupt neighbouring entries. + +### Fixed (CLI) + +- **Field writes no longer nudge the registry's markdown shape.** `set_entry_field` collapsed an entry's trailing `"\n\n"` to `"\n"`, gluing the edited entry to the following `## Bucket:` heading; `insert_into_bucket` appended entries with no blank line before the next section. Both are shared helpers, so `drift --apply`, `promote` and `verify` were reshaping the file a little on every write. Surfaced by making field writes routine. + +### Changed (Framework) + +- [`FOLLOW-UPS-BACKLOG-PATTERN.md`](dist/.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md) (EN + es + zh-CN) documents the three verbs and the ex-ante origin (`Origin: CHARTER-NN §Scope`) as a first-class case alongside the ex-post one. The registry template documents the write path and states that entries are never hand-edited. +- The `/straymark-followups` skill (all four agent surfaces) uses `set-status` + `note` for post-Charter-close triage instead of hand-flips-then-`recount`, and gains a **Charter-declaration** step for registering an ex-ante deferral. + +### Changed (CLI) + +- **Internal:** one shared recompute-and-write path (`followups::write_recounted`) for every mutating verb, so "the entry edit and the counter update are one step" holds by construction. `recount` uses the same function, keeping it the idempotent check on the verbs' arithmetic rather than a second implementation that could disagree. + ## CLI 3.38.0 — 2026-07-25 Auditor isolation is enforced where the prompt is built, not where the report is read (#372, Sentinel field report). diff --git a/CLAUDE.md b/CLAUDE.md index 39919a58..dcac3d4b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -275,6 +275,9 @@ Loom releases are GitHub-release-only (no crates.io while experimental). Tag for | `straymark followups recount` | Recompute the CLI-owned counters after a manual-triage session (no AILOG scan, idempotent) | | `straymark followups promote FU-NNN [--premise-verified]` | Elevate an entry to a TDE document with `promoted_from_followup` traceability; surfaces the premise re-check, stamps `Verified-at` with the flag | | `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 "" [--source ID]` | Append a dated annotation to an entry's `Notes` in one validated edit (#355) | +| `straymark followups set-status FU-NNN ` | 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 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) | diff --git a/Cargo.lock b/Cargo.lock index e17bbb85..736a655d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2573,7 +2573,7 @@ dependencies = [ [[package]] name = "straymark-cli" -version = "3.38.0" +version = "3.39.0" dependencies = [ "anyhow", "arborist-metrics", diff --git a/README.md b/README.md index ad41a20f..2795ee6e 100644 --- a/README.md +++ b/README.md @@ -277,8 +277,8 @@ StrayMark uses independent version tags for each component: | Component | Tag prefix | Example | Includes | | --- | --- | --- | --- | -| Framework | `fw-` | `fw-4.36.0` | Templates (12 types), governance, directives, Charter template + schema | -| CLI | `cli-` | `cli-3.38.0` | The `straymark` binary | +| Framework | `fw-` | `fw-4.37.0` | Templates (12 types), governance, directives, Charter template + schema | +| CLI | `cli-` | `cli-3.39.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`. diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 8215c7e3..de353051 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "straymark-cli" -version = "3.38.0" +version = "3.39.0" edition = "2021" description = "CLI for StrayMark — the cognitive discipline your AI-assisted projects need" license = "MIT" diff --git a/cli/src/commands/followups/mod.rs b/cli/src/commands/followups/mod.rs index bd5cedae..65f0501f 100644 --- a/cli/src/commands/followups/mod.rs +++ b/cli/src/commands/followups/mod.rs @@ -8,10 +8,19 @@ //! `promote` automates the FU → TDE elevation, and `verify` re-checks an //! entry's premise at execution time (AIDEC-2026-07-18-001). See //! `FOLLOW-UPS-BACKLOG-PATTERN.md` and `STRAYMARK.md §16`. +//! +//! `note`, `set-status` and `new` (CHARTER-01, from Weft reports #355/#360) +//! close the last paths that required hand-editing a CLI-parsed file: they +//! 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. pub mod drift; pub mod list; +pub mod new; +pub mod note; pub mod promote; pub mod recount; +pub mod set_status; pub mod status; pub mod verify; diff --git a/cli/src/commands/followups/new.rs b/cli/src/commands/followups/new.rs new file mode 100644 index 00000000..5bf75c60 --- /dev/null +++ b/cli/src/commands/followups/new.rs @@ -0,0 +1,132 @@ +//! `straymark followups new` — mint an entry whose origin is a **Charter +//! declaration** (ex-ante), before any execution exists. +//! +//! Both pre-existing population paths assume an ex-post origin (Weft field +//! report #360): `drift --apply` extracts from AILOGs, and the hand-edit path +//! inherited that shape. But a deferral decided *at declaration time* — "the +//! Redis CI job is out of scope, register the coverage gap so it is deferred, +//! not silenced" — precedes any AILOG by design. Charters are ex-ante; AILOGs +//! are ex-post; the `declared → in-progress` window is days wide. +//! +//! The hazard this closes is not ergonomic, it is correctness. Lacking a +//! creation verb, the adopter forward-referenced `FU-011` in the Charter body +//! with nothing reserving it — and since ids are minted `max(existing) + 1` at +//! extraction time, the next unrelated `drift --apply` would hand `FU-011` to a +//! different entry, silently pointing the Charter's citations at the wrong +//! follow-up. Assigning the id here, atomically, removes the window: by the +//! time the Charter cites `FU-NNN`, the entry exists. +//! +//! Prioritization stays human, exactly as with `promote` and `verify`: this +//! verb records a decision the operator already made. + +use anyhow::{anyhow, bail, Result}; +use chrono::Local; +use colored::Colorize; + +use crate::commands::followups::note::guard_parse_warnings; +use crate::followups::{self, FuStatus}; +use crate::utils; + +pub struct NewArgs<'a> { + pub path: &'a str, + pub title: &'a str, + pub origin: &'a str, + pub bucket: &'a str, + pub status: &'a str, + pub trigger: Option<&'a str>, + pub destination: Option<&'a str>, + pub cost: Option<&'a str>, + pub premise: Option<&'a str>, +} + +pub fn run(args: NewArgs<'_>) -> Result<()> { + if args.title.trim().is_empty() { + bail!("--title is empty. An entry whose title misrepresents it starts life already skewed."); + } + if args.origin.trim().is_empty() { + bail!( + "--origin is required (schema v1 requires it): the document that decided this \ + deferral, e.g. --origin \"CHARTER-06 §Scope\"." + ); + } + if !followups::CANONICAL_BUCKETS.contains(&args.bucket) { + bail!( + "Unknown bucket '{}'. Valid: {}.", + args.bucket, + followups::CANONICAL_BUCKETS.join(" | ") + ); + } + let status = FuStatus::from_str_loose(args.status); + if status == FuStatus::Unknown { + bail!( + "Unknown status '{}'. Valid: open | in-progress | suspected-closed | closed | superseded.", + args.status + ); + } + if status == FuStatus::Promoted { + bail!("An entry cannot be created as `promoted` — use `followups promote` on an existing entry."); + } + + let resolved = utils::resolve_project_root(args.path) + .ok_or_else(|| anyhow!("StrayMark not installed. Run 'straymark init' first."))?; + let project_root = &resolved.path; + + let registry_path = followups::registry_path(project_root); + if !registry_path.exists() { + bail!( + "No follow-ups registry at {}.\n hint: run `straymark followups drift --scan-all --apply` to create it, or copy the template from `.straymark/templates/follow-ups-backlog.md` (see STRAYMARK.md §16).", + registry_path.display() + ); + } + let registry = followups::parse_registry(®istry_path)?; + guard_parse_warnings(®istry)?; + + let fu_number = followups::next_fu_number(®istry); + let today = Local::now().format("%Y-%m-%d").to_string(); + let notes = format!( + "Created by `straymark followups new` {} — declared ex-ante, before execution.", + today + ); + let block = followups::render_declared_entry( + fu_number, + args.title, + args.origin, + status.as_str(), + args.trigger, + args.destination, + args.cost, + args.premise, + ¬es, + ); + let body = followups::insert_into_bucket(®istry, args.bucket, &block); + let counters = followups::write_recounted(®istry_path, ®istry.frontmatter_raw, &body)?; + + let fu_id = format!("FU-{:03}", fu_number); + utils::success(&format!( + "{} created in bucket `{}` ({}).", + fu_id.bold(), + args.bucket, + status.as_str() + )); + println!( + " Counters: {} open / {} in-progress / {} suspected-closed (total {}).", + counters.open, counters.in_progress, counters.suspected_closed, counters.total + ); + println!(); + println!( + " {} the id is assigned and written — cite {} in the Charter body now, not a reserved guess.", + "Next:".bold(), + fu_id.cyan() + ); + if args.premise.map(str::trim).unwrap_or("").is_empty() { + println!( + " {}", + format!( + "No premise recorded. An entry is a dated hypothesis — `straymark followups verify {} --premise \"...\"` states what it rests on, so acting on it later is a seconds-long re-check.", + fu_id + ) + .dimmed() + ); + } + Ok(()) +} diff --git a/cli/src/commands/followups/note.rs b/cli/src/commands/followups/note.rs new file mode 100644 index 00000000..da0ec7b1 --- /dev/null +++ b/cli/src/commands/followups/note.rs @@ -0,0 +1,79 @@ +//! `straymark followups note FU-NNN ""` — append a dated annotation to +//! an entry's `Notes`. +//! +//! Before this verb (Weft field report #355), recording that an entry received +//! a partial mitigation — without changing its status — meant hand-editing a +//! CLI-parsed markdown file. Two things go wrong there: the edit can malform +//! the entry (wrong bullet shape, wrong field name) and break `list`/`status`/ +//! `drift`; and nothing enforces *when* the annotation was made or *what* +//! motivated it, so an entry accumulates undated prose whose provenance is +//! gone by the next triage. +//! +//! This verb writes through the same surgical field editor every other write +//! command uses, stamps the date, and records `--source` (the Charter or AILOG +//! that motivated the note) when given. + +use anyhow::{anyhow, bail, Result}; +use chrono::Local; + +use crate::followups; +use crate::utils; + +pub fn run(path: &str, fu_id: &str, text: &str, source: Option<&str>) -> Result<()> { + if text.trim().is_empty() { + bail!("Note text is empty — pass the annotation as the second argument."); + } + + let resolved = utils::resolve_project_root(path) + .ok_or_else(|| anyhow!("StrayMark not installed. Run 'straymark init' first."))?; + let project_root = &resolved.path; + + let registry_path = followups::registry_path(project_root); + 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(®istry_path)?; + + // A malformed registry aborts BEFORE any write: editing one entry inside a + // file whose structure the parser already distrusts is how a governance + // artifact gets half-corrupted. + guard_parse_warnings(®istry)?; + + let entry = followups::find_entry(®istry, fu_id) + .ok_or_else(|| { + anyhow!( + "Entry {} not found in the registry.\n hint: run `straymark followups list` to see entries.", + fu_id + ) + })? + .clone(); + + let date = Local::now().format("%Y-%m-%d").to_string(); + let notes = followups::append_note(entry.notes.as_deref(), text, &date, source); + let body = followups::set_entry_field(®istry.body, &entry, "Notes", ¬es); + followups::write_recounted(®istry_path, ®istry.frontmatter_raw, &body)?; + + utils::success(&format!("{} annotated.", entry.fu_id)); + println!(" Notes: {}", notes); + Ok(()) +} + +/// Refuse to mutate a registry the parser could not read cleanly. +pub(crate) fn guard_parse_warnings(registry: &followups::Registry) -> Result<()> { + if registry.warnings.is_empty() { + return Ok(()); + } + for w in ®istry.warnings { + utils::warn(w); + } + bail!( + "Refusing to write: the registry has {} parse warning(s) above. Fix the malformed \ + entr{} first — a surgical edit against a structure the parser mis-read can corrupt \ + neighbouring entries.", + registry.warnings.len(), + if registry.warnings.len() == 1 { "y" } else { "ies" } + ); +} diff --git a/cli/src/commands/followups/recount.rs b/cli/src/commands/followups/recount.rs index 2553b04f..9825930d 100644 --- a/cli/src/commands/followups/recount.rs +++ b/cli/src/commands/followups/recount.rs @@ -38,8 +38,14 @@ pub fn run(path: &str) -> Result<()> { utils::warn(w); } - let counters = followups::compute_counters(®istry); - let fm = followups::fm_apply_counters_and_v1(®istry.frontmatter_raw, &counters); + // Same recompute path the mutating verbs use (`note` / `set-status` / + // `new` / `verify`), so this stays the idempotent check on their arithmetic + // rather than a second implementation that could disagree with them. + let (fm, counters) = followups::recounted_frontmatter( + ®istry_path, + ®istry.frontmatter_raw, + ®istry.body, + )?; if fm == registry.frontmatter_raw { println!( diff --git a/cli/src/commands/followups/set_status.rs b/cli/src/commands/followups/set_status.rs new file mode 100644 index 00000000..0ccb1186 --- /dev/null +++ b/cli/src/commands/followups/set_status.rs @@ -0,0 +1,100 @@ +//! `straymark followups set-status FU-NNN ` — flip an entry's status +//! and recompute the CLI-owned counters in the same step. +//! +//! This closes the desync window `recount` exists to clean up after (Weft field +//! report #355). The two-step it replaces — hand-edit the `Status` bullet, then +//! remember to run `recount` — is desyncable by construction: forget the second +//! step and the frontmatter counters silently lie about the backlog, which is +//! the one thing a registry exists to be right about. +//! +//! `recount` stays as the escape hatch for a manual-triage session (several +//! statuses flipped by hand at once) and as the idempotent check that this verb +//! got the arithmetic right. + +use anyhow::{anyhow, bail, Result}; +use colored::Colorize; + +use crate::commands::followups::note::guard_parse_warnings; +use crate::followups::{self, FuStatus}; + +pub fn run(path: &str, fu_id: &str, status_input: &str) -> Result<()> { + // Validate against the canonical vocabulary rather than writing whatever + // was typed: the parser is lenient (an unrecognized status degrades to + // `Unknown` and counts toward no bucket), so a typo written here would not + // fail — it would quietly remove the entry from every counter. + let status = FuStatus::from_str_loose(status_input); + if status == FuStatus::Unknown { + bail!( + "Unknown status '{}'. Valid: open | in-progress | suspected-closed | closed | \ + superseded | promoted.\n note: an unrecognized status parses as `unknown` and \ + counts toward no bucket, so it would silently drop the entry from the counters.", + status_input + ); + } + if status == FuStatus::Promoted { + bail!( + "Use `straymark followups promote {}` to reach `promoted` — it writes the TDE \ + document and the `Promoted-to` back-pointer that make the status meaningful. \ + Setting the status alone would claim a promotion with nothing to point at.", + fu_id + ); + } + + let resolved = crate::utils::resolve_project_root(path) + .ok_or_else(|| anyhow!("StrayMark not installed. Run 'straymark init' first."))?; + let project_root = &resolved.path; + + let registry_path = followups::registry_path(project_root); + 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(®istry_path)?; + guard_parse_warnings(®istry)?; + + let entry = followups::find_entry(®istry, fu_id) + .ok_or_else(|| { + anyhow!( + "Entry {} not found in the registry.\n hint: run `straymark followups list` to see entries.", + fu_id + ) + })? + .clone(); + + let before = followups::compute_counters(®istry); + let previous = entry.status_raw.clone().unwrap_or_else(|| entry.status.as_str().to_string()); + + if entry.status == status { + println!( + " {} {} is already `{}` — nothing to change.", + "OK".green().bold(), + entry.fu_id, + status.as_str() + ); + return Ok(()); + } + + let body = followups::set_entry_field(®istry.body, &entry, "Status", status.as_str()); + let after = followups::write_recounted(®istry_path, ®istry.frontmatter_raw, &body)?; + + crate::utils::success(&format!( + "{}: {} → {}", + entry.fu_id, + previous.trim().dimmed(), + status.as_str().bold() + )); + println!( + " Counters: {} open / {} in-progress / {} suspected-closed / {} closed (was {} / {} / {} / {}).", + after.open, + after.in_progress, + after.suspected_closed, + after.closed_cumulative, + before.open, + before.in_progress, + before.suspected_closed, + before.closed_cumulative + ); + Ok(()) +} diff --git a/cli/src/commands/followups/verify.rs b/cli/src/commands/followups/verify.rs index 78bd14db..de6b5844 100644 --- a/cli/src/commands/followups/verify.rs +++ b/cli/src/commands/followups/verify.rs @@ -110,13 +110,7 @@ pub fn run( // Recompute the CLI-owned counters + v0→v1 upgrade, like every write // command — even though status is unchanged, this keeps the registry // canonical and idempotent. - let final_registry = followups::parse_registry_str( - ®istry_path, - &followups::assemble(®istry.frontmatter_raw, &body), - )?; - let counters = followups::compute_counters(&final_registry); - let fm = followups::fm_apply_counters_and_v1(®istry.frontmatter_raw, &counters); - std::fs::write(®istry_path, followups::assemble(&fm, &body))?; + followups::write_recounted(®istry_path, ®istry.frontmatter_raw, &body)?; if premise.filter(|s| !s.trim().is_empty()).is_some() { utils::success(&format!("{} premise recorded.", entry.fu_id)); diff --git a/cli/src/followups.rs b/cli/src/followups.rs index b070c7f5..7e00c6c7 100644 --- a/cli/src/followups.rs +++ b/cli/src/followups.rs @@ -1229,12 +1229,19 @@ pub fn insert_into_bucket(registry: &Registry, bucket: &str, block: &str) -> Str match target { Some(section) => { - // Insert before the next section heading, trimming trailing blank - // lines of the section so spacing stays tight. + // Insert before the next section heading, normalizing the blank + // lines on both sides: one before the new entry, and one after it + // when a following section exists — otherwise the appended entry + // butts straight against the next `## Bucket:` heading. let head = &body[..section.end]; let tail = &body[section.end..]; let head_trimmed = head.trim_end_matches('\n'); - format!("{}\n\n{}\n{}", head_trimmed, block.trim_end_matches('\n'), tail) + let block_trimmed = block.trim_end_matches('\n'); + if tail.is_empty() { + format!("{}\n\n{}\n", head_trimmed, block_trimmed) + } else { + format!("{}\n\n{}\n\n{}", head_trimmed, block_trimmed, tail) + } } None => { let trimmed = body.trim_end_matches('\n'); @@ -1272,8 +1279,12 @@ pub fn set_entry_field(body: &str, entry: &Entry, field: &str, value: &str) -> S lines.insert(insert_at, format!("- **{}**: {}", field, value)); } let mut new_block = lines.join("\n"); - // Preserve the block's trailing newline shape. - if block.ends_with('\n') && !new_block.ends_with('\n') { + // Preserve the block's trailing newline shape *exactly*. Collapsing + // "\n\n" to "\n" (the earlier behavior) ate the blank line separating the + // edited entry from the next heading, so every field write nudged the + // registry's markdown shape — visible once `note` made field writes routine. + let trailing = block.len() - block.trim_end_matches('\n').len(); + while new_block.len() - new_block.trim_end_matches('\n').len() < trailing { new_block.push('\n'); } let mut out = String::with_capacity(body.len() + 32); @@ -1283,6 +1294,112 @@ pub fn set_entry_field(body: &str, entry: &Entry, field: &str, value: &str) -> S out } +/// The five canonical buckets (schema v1 `buckets` enum). Stable at N=91 +/// entries in the reference adopter — no sixth has been needed. +pub const CANONICAL_BUCKETS: &[&str] = &[ + "ready", + "time-triggered", + "charter-triggered", + "phase-blocked", + "operational", +]; + +/// Recompute the CLI-owned counters from `body` and return the frontmatter +/// carrying them (plus the v0 → v1 upgrade every write command performs). +/// +/// Counters are derived from the entries *as they will be written*, not from +/// the registry as it was read — which is what makes "the entry edit and the +/// counter update are one step" true rather than a convention the operator has +/// to remember (#355: the hand-edit + `recount` two-step is desyncable). +pub fn recounted_frontmatter( + path: &Path, + frontmatter_raw: &str, + body: &str, +) -> Result<(String, Counters)> { + let reparsed = parse_registry_str(path, &assemble(frontmatter_raw, body))?; + let counters = compute_counters(&reparsed); + Ok((fm_apply_counters_and_v1(frontmatter_raw, &counters), counters)) +} + +/// Persist `body` to `path` with the counters recomputed from it. The single +/// write path for every mutating verb (`note`, `set-status`, `new`, `verify`). +pub fn write_recounted(path: &Path, frontmatter_raw: &str, body: &str) -> Result { + let (fm, counters) = recounted_frontmatter(path, frontmatter_raw, body)?; + std::fs::write(path, assemble(&fm, body)) + .with_context(|| format!("Failed to write registry at {}", path.display()))?; + Ok(counters) +} + +/// Compose the new value of an entry's `Notes` bullet when appending an +/// annotation. +/// +/// `Notes` is a single-line bullet (the parser reads one line per field), so +/// accumulating annotations means composing, not stacking. Each is stamped +/// `[date]` — and `[date · source]` when the caller passes what motivated it — +/// so an entry's note history stays attributable instead of becoming an +/// undated blur. Pure, so the composition is unit-testable. +pub fn append_note(existing: Option<&str>, text: &str, date: &str, source: Option<&str>) -> String { + let stamp = match source.map(str::trim).filter(|s| !s.is_empty()) { + Some(src) => format!("[{} · {}]", date, src), + None => format!("[{}]", date), + }; + let addition = format!("{} {}", stamp, text.trim()); + match existing.map(str::trim).filter(|s| !s.is_empty()) { + Some(prev) => format!("{} · {}", prev, addition), + None => addition, + } +} + +/// Render an entry declared **ex-ante** — at Charter-declaration time, before +/// any execution (#360). +/// +/// Deliberately not [`render_new_entry`]: that one serves `drift --apply` and +/// stamps a `Source-hash` derived from the originating AILOG. An ex-ante entry +/// has no AILOG to hash (the Charter precedes execution by design), and +/// inventing a hash would make a later `drift --apply` believe it had already +/// extracted something it never saw. Omitting the field is the honest encoding: +/// the dedup key is absent because the source it keys on does not exist yet. +#[allow(clippy::too_many_arguments)] +pub fn render_declared_entry( + fu_number: u32, + title: &str, + origin: &str, + status: &str, + trigger: Option<&str>, + destination: Option<&str>, + cost: Option<&str>, + premise: Option<&str>, + notes: &str, +) -> String { + let field = |v: Option<&str>| { + v.map(str::trim) + .filter(|s| !s.is_empty()) + .unwrap_or("TBD") + .to_string() + }; + let mut block = format!( + "### FU-{:03} — {}\n\ + - **Origin**: {}\n\ + - **Origin-class**: ex-ante-planning\n\ + - **Status**: {}\n\ + - **Trigger**: {}\n\ + - **Destination**: {}\n\ + - **Cost**: {}\n", + fu_number, + title.trim(), + origin.trim(), + status, + field(trigger), + field(destination), + field(cost), + ); + if let Some(p) = premise.map(str::trim).filter(|s| !s.is_empty()) { + block.push_str(&format!("- **Premise**: {}\n", p)); + } + block.push_str(&format!("- **Notes**: {}\n", notes.trim())); + block +} + #[cfg(test)] mod tests { use super::*; @@ -1888,6 +2005,104 @@ Done. ); } + #[test] + fn set_entry_field_preserves_the_blank_line_before_the_next_heading() { + // A field write used to collapse the entry's trailing "\n\n" to "\n", + // gluing the edited entry to the following heading — invisible until + // `note` made field writes routine (CHARTER-01). + let reg = parse(V0_REGISTRY); + // FU-002 is the last entry of the `ready` section, so the next line is + // a section heading — the case the collapse used to break. + let entry = find_entry(®, "FU-002").unwrap().clone(); + let new_body = set_entry_field(®.body, &entry, "Cost", "M"); + assert!( + new_body.contains("- **Cost**: M\n\n## Bucket: phase-blocked"), + "blank line before the next section must survive:\n{new_body}" + ); + } + + #[test] + fn insert_into_bucket_keeps_a_blank_line_before_the_following_section() { + let reg = parse(V0_REGISTRY); + let block = "### FU-050 — Appended\n- **Status**: open\n"; + let new_body = insert_into_bucket(®, "ready", block); + assert!( + new_body.contains("### FU-050 — Appended\n- **Status**: open\n\n## Bucket: phase-blocked"), + "appended entry must not butt against the next heading:\n{new_body}" + ); + } + + #[test] + fn append_note_stamps_and_composes() { + // No prior notes: the annotation stands alone, dated. + assert_eq!( + append_note(None, "part-a shipped", "2026-07-26", None), + "[2026-07-26] part-a shipped" + ); + // With a source: attributable to what motivated it. + assert_eq!( + append_note(None, "part-a shipped", "2026-07-26", Some("CHARTER-04")), + "[2026-07-26 · CHARTER-04] part-a shipped" + ); + // Existing notes are composed onto, never replaced — Notes is a + // single-line field, so history accumulates in one bullet. + assert_eq!( + append_note(Some("Extracted 2026-07-20."), "part-b deferred", "2026-07-26", None), + "Extracted 2026-07-20. · [2026-07-26] part-b deferred" + ); + // Whitespace-only existing notes are treated as absent. + assert_eq!( + append_note(Some(" "), "first", "2026-07-26", None), + "[2026-07-26] first" + ); + // A blank --source falls back to the bare date stamp. + assert_eq!( + append_note(None, "text", "2026-07-26", Some(" ")), + "[2026-07-26] text" + ); + } + + #[test] + fn render_declared_entry_is_ex_ante_and_hash_less() { + let block = render_declared_entry( + 12, + "Redis CI job deferred", + "CHARTER-06 §Scope", + "open", + None, + None, + Some("S"), + Some("the Redis adapter has no CI coverage today"), + "Created by `straymark followups new` 2026-07-26.", + ); + assert!(block.starts_with("### FU-012 — Redis CI job deferred\n")); + assert!(block.contains("- **Origin**: CHARTER-06 §Scope\n")); + assert!(block.contains("- **Origin-class**: ex-ante-planning\n")); + assert!(block.contains("- **Premise**: the Redis adapter has no CI coverage today\n")); + // Unset optional fields become TBD, matching the template's convention. + assert!(block.contains("- **Trigger**: TBD\n")); + assert!(block.contains("- **Destination**: TBD\n")); + assert!(block.contains("- **Cost**: S\n")); + // No AILOG to hash — see the doc comment on why absence is the honest encoding. + assert!(!block.contains("Source-hash")); + + // Round-trips through the parser as a well-formed entry. + let reg = parse(V0_REGISTRY); + let body = insert_into_bucket(®, "charter-triggered", &block); + let reparsed = parse(&assemble(®.frontmatter_raw, &body)); + let entry = find_entry(&reparsed, "FU-012").unwrap(); + assert_eq!(entry.status, FuStatus::Open); + assert_eq!(entry.origin.as_deref(), Some("CHARTER-06 §Scope")); + assert_eq!(entry.origin_class.as_deref(), Some("ex-ante-planning")); + assert!(entry.source_hash.is_none()); + } + + #[test] + fn render_declared_entry_omits_premise_when_absent() { + let block = render_declared_entry(1, "T", "CHARTER-01 §Scope", "open", None, None, None, None, "n"); + assert!(!block.contains("Premise")); + } + #[test] fn render_new_entry_marks_suspected_closed() { let block = render_new_entry( diff --git a/cli/src/main.rs b/cli/src/main.rs index 91778082..4a43f35d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -467,6 +467,70 @@ enum FollowupsCommands { #[arg(long = "path", default_value = ".")] path: String, }, + /// Append a dated annotation to an entry's Notes, in one validated edit. + /// Replaces hand-editing the CLI-parsed registry (#355). + Note { + /// Entry identifier (FU-NNN or just NNN) + fu_id: String, + /// The annotation to append. Stamped with today's date. + text: String, + /// What motivated the note (e.g. CHARTER-04, AILOG-2026-07-20-001). + /// Recorded alongside the date so the annotation stays attributable. + #[arg(long)] + source: Option, + /// Project directory (default: current directory) + #[arg(long = "path", default_value = ".")] + path: String, + }, + /// Change an entry's status AND recompute the CLI-owned counters in the + /// same step, closing the edit-then-recount desync window (#355). + SetStatus { + /// Entry identifier (FU-NNN or just NNN) + fu_id: String, + /// New status: open | in-progress | suspected-closed | closed | + /// superseded. (`promoted` is reached via `followups promote`, which + /// also writes the TDE the status points at.) + status: String, + /// Project directory (default: current directory) + #[arg(long = "path", default_value = ".")] + path: String, + }, + /// Create an entry declared ex-ante — at Charter-declaration time, before + /// any AILOG exists (#360). Assigns the id atomically and prints it, so the + /// Charter body cites an entry that already exists instead of a reserved + /// guess that the next `drift --apply` could hand to something else. + New { + /// Entry title (the first thing every later reader sees) + #[arg(long)] + title: String, + /// The document that decided the deferral, e.g. "CHARTER-06 §Scope". + /// Required — schema v1 requires an origin on every entry. + #[arg(long)] + origin: String, + /// Bucket (when it becomes actionable) + #[arg(long, default_value = "charter-triggered")] + bucket: String, + /// Initial status + #[arg(long, default_value = "open")] + status: String, + /// Observable condition that makes it actionable (default: TBD) + #[arg(long)] + trigger: Option, + /// Where the work lands: chore | mini-charter | charter-replanning | + /// operations | (default: TBD) + #[arg(long)] + destination: Option, + /// Effort estimate, free-form (default: TBD) + #[arg(long)] + cost: Option, + /// The load-bearing assumption this entry rests on — what a future + /// reader must re-check before acting (AIDEC-2026-07-18-001) + #[arg(long)] + premise: Option, + /// Project directory (default: current directory) + #[arg(long = "path", default_value = ".")] + path: String, + }, } /// Sub-analyses under `straymark analyze`. @@ -1007,6 +1071,38 @@ fn main() { verified, at.as_deref(), ), + FollowupsCommands::Note { + fu_id, + text, + source, + path, + } => commands::followups::note::run(&path, &fu_id, &text, source.as_deref()), + FollowupsCommands::SetStatus { + fu_id, + status, + path, + } => commands::followups::set_status::run(&path, &fu_id, &status), + FollowupsCommands::New { + title, + origin, + bucket, + status, + trigger, + destination, + cost, + premise, + path, + } => commands::followups::new::run(commands::followups::new::NewArgs { + path: &path, + title: &title, + origin: &origin, + bucket: &bucket, + status: &status, + trigger: trigger.as_deref(), + destination: destination.as_deref(), + cost: cost.as_deref(), + premise: premise.as_deref(), + }), }, Commands::Architecture { command } => match command { ArchitectureCommands::Generate { path, force, out } => { diff --git a/cli/tests/followups_test.rs b/cli/tests/followups_test.rs index 2532c5b5..c09a11ea 100644 --- a/cli/tests/followups_test.rs +++ b/cli/tests/followups_test.rs @@ -727,3 +727,270 @@ fn project_status_hints_when_no_registry() { .success() .stdout(predicate::str::contains("No follow-ups registry yet")); } + +// ──────────────── note / set-status / new (CHARTER-01, #355 + #360) ──────────────── + +#[test] +fn note_appends_dated_annotation_with_source_and_preserves_status() { + let tmp = TempDir::new().unwrap(); + let straymark = scaffold(tmp.path()); + write_registry(&straymark, V1_REGISTRY); + + cmd() + .args([ + "followups", "note", "FU-011", + "Part-a shipped (size cap in the codec); part-b deferred.", + "--source", "CHARTER-04", + "--path", tmp.path().to_str().unwrap(), + ]) + .assert() + .success() + .stdout(predicate::str::contains("FU-011 annotated")); + + let updated = std::fs::read_to_string(straymark.join("follow-ups-backlog.md")).unwrap(); + let block = &updated[updated.find("### FU-011").unwrap()..]; + assert!(block.contains("CHARTER-04"), "source must be recorded: {block}"); + assert!(block.contains("part-b deferred.")); + // The point of #355's case: annotate WITHOUT changing status. + assert!(block.contains("- **Status**: open")); +} + +#[test] +fn note_composes_onto_existing_notes_instead_of_replacing_them() { + let tmp = TempDir::new().unwrap(); + let straymark = scaffold(tmp.path()); + write_registry(&straymark, V1_REGISTRY); + let path = tmp.path().to_str().unwrap(); + + cmd() + .args(["followups", "note", "FU-011", "first", "--path", path]) + .assert() + .success(); + cmd() + .args(["followups", "note", "FU-011", "second", "--path", path]) + .assert() + .success(); + + let updated = std::fs::read_to_string(straymark.join("follow-ups-backlog.md")).unwrap(); + let block = &updated[updated.find("### FU-011").unwrap()..]; + assert!(block.contains("first"), "earlier note must survive: {block}"); + assert!(block.contains("second")); + // One Notes bullet, not two — the field is a single line by parser contract. + assert_eq!(block.matches("- **Notes**").count(), 1); +} + +#[test] +fn note_refuses_to_write_a_malformed_registry() { + let tmp = TempDir::new().unwrap(); + let straymark = scaffold(tmp.path()); + // A `### FU-` heading the parser cannot read: a surgical edit against a + // mis-read structure could corrupt neighbouring entries (CHARTER-01 R1). + write_registry( + &straymark, + &format!("{V1_REGISTRY}\n### FU-malformed heading with no id\n- **Status**: open\n"), + ); + let before = std::fs::read_to_string(straymark.join("follow-ups-backlog.md")).unwrap(); + + cmd() + .args([ + "followups", "note", "FU-011", "must not be written", + "--path", tmp.path().to_str().unwrap(), + ]) + .assert() + .failure() + .stderr(predicate::str::contains("Refusing to write")); + + let after = std::fs::read_to_string(straymark.join("follow-ups-backlog.md")).unwrap(); + assert_eq!(before, after, "nothing may be written when the guard fires"); +} + +#[test] +fn set_status_flips_status_and_recomputes_counters_in_one_step() { + let tmp = TempDir::new().unwrap(); + let straymark = scaffold(tmp.path()); + // v0 fixture: frontmatter claims total_open 47, reality is 2. + write_registry(&straymark, V0_REGISTRY); + let path = tmp.path().to_str().unwrap(); + + cmd() + .args(["followups", "set-status", "FU-002", "closed", "--path", path]) + .assert() + .success() + .stdout(predicate::str::contains("open")) + .stdout(predicate::str::contains("closed")); + + let updated = std::fs::read_to_string(straymark.join("follow-ups-backlog.md")).unwrap(); + assert!(updated.contains("total_open: 1"), "counters must move with the status: {updated}"); + assert!(updated.contains("schema_version: v1"), "write commands upgrade v0 → v1"); + let block = &updated[updated.find("### FU-002").unwrap()..]; + assert!(block.contains("- **Status**: closed")); + + // The desync #355 describes cannot happen: `recount` has nothing left to do. + cmd() + .args(["followups", "recount", "--path", path]) + .assert() + .success() + .stdout(predicate::str::contains("already in sync")); +} + +#[test] +fn set_status_rejects_unknown_status_and_redirects_promoted() { + let tmp = TempDir::new().unwrap(); + let straymark = scaffold(tmp.path()); + write_registry(&straymark, V1_REGISTRY); + let path = tmp.path().to_str().unwrap(); + let before = std::fs::read_to_string(straymark.join("follow-ups-backlog.md")).unwrap(); + + // A typo would parse as `unknown` and silently drop the entry from every + // counter, so it is refused rather than written. + cmd() + .args(["followups", "set-status", "FU-011", "done", "--path", path]) + .assert() + .failure() + .stderr(predicate::str::contains("Unknown status")); + + // `promoted` requires the TDE that gives the status meaning. + cmd() + .args(["followups", "set-status", "FU-011", "promoted", "--path", path]) + .assert() + .failure() + .stderr(predicate::str::contains("followups promote")); + + let after = std::fs::read_to_string(straymark.join("follow-ups-backlog.md")).unwrap(); + assert_eq!(before, after); +} + +#[test] +fn set_status_is_a_noop_when_already_in_that_status() { + let tmp = TempDir::new().unwrap(); + let straymark = scaffold(tmp.path()); + write_registry(&straymark, V1_REGISTRY); + let before = std::fs::read_to_string(straymark.join("follow-ups-backlog.md")).unwrap(); + + cmd() + .args([ + "followups", "set-status", "FU-011", "open", + "--path", tmp.path().to_str().unwrap(), + ]) + .assert() + .success() + .stdout(predicate::str::contains("already")); + + let after = std::fs::read_to_string(straymark.join("follow-ups-backlog.md")).unwrap(); + assert_eq!(before, after); +} + +#[test] +fn new_mints_ex_ante_entry_with_assigned_id_and_no_source_hash() { + let tmp = TempDir::new().unwrap(); + let straymark = scaffold(tmp.path()); + write_registry(&straymark, V1_REGISTRY); + + // V1_REGISTRY's highest entry is FU-011 → the next id is FU-012, assigned + // and written here, so the Charter can cite an entry that exists (#360). + cmd() + .args([ + "followups", "new", + "--title", "Redis CI job deferred", + "--origin", "CHARTER-06 §Scope", + "--cost", "S", + "--trigger", "when the Actions budget resets", + "--premise", "the Redis adapter has no CI coverage today", + "--path", tmp.path().to_str().unwrap(), + ]) + .assert() + .success() + .stdout(predicate::str::contains("FU-012")) + .stdout(predicate::str::contains("charter-triggered")); + + let updated = std::fs::read_to_string(straymark.join("follow-ups-backlog.md")).unwrap(); + let block = &updated[updated.find("### FU-012").unwrap()..]; + assert!(block.contains("- **Origin**: CHARTER-06 §Scope")); + // The schema already had a name for this origin; only the creation path was missing. + assert!(block.contains("- **Origin-class**: ex-ante-planning")); + assert!(block.contains("- **Status**: open")); + assert!(block.contains("- **Cost**: S")); + assert!(block.contains("- **Premise**: the Redis adapter has no CI coverage today")); + // No AILOG exists to hash: inventing a Source-hash would make a later + // `drift --apply` believe it had extracted something it never saw. + assert!(!block.contains("Source-hash"), "ex-ante entry must carry no Source-hash: {block}"); + assert!(updated.contains("total_open: 3"), "counters include the new entry: {updated}"); +} + +#[test] +fn new_lands_in_the_requested_bucket_and_defaults_unset_fields_to_tbd() { + let tmp = TempDir::new().unwrap(); + let straymark = scaffold(tmp.path()); + write_registry(&straymark, V1_REGISTRY); + + cmd() + .args([ + "followups", "new", + "--title", "Revisit the probe interval", + "--origin", "CHARTER-07 §Out of scope", + "--bucket", "ready", + "--path", tmp.path().to_str().unwrap(), + ]) + .assert() + .success(); + + let updated = std::fs::read_to_string(straymark.join("follow-ups-backlog.md")).unwrap(); + let ready = &updated[updated.find("## Bucket: ready").unwrap()..]; + assert!(ready.contains("### FU-012 — Revisit the probe interval")); + let block = &updated[updated.find("### FU-012").unwrap()..]; + assert!(block.contains("- **Trigger**: TBD")); + assert!(block.contains("- **Cost**: TBD")); +} + +#[test] +fn new_requires_origin_and_a_canonical_bucket() { + let tmp = TempDir::new().unwrap(); + let straymark = scaffold(tmp.path()); + write_registry(&straymark, V1_REGISTRY); + let path = tmp.path().to_str().unwrap(); + + cmd() + .args([ + "followups", "new", "--title", "No origin", "--origin", " ", + "--path", path, + ]) + .assert() + .failure() + .stderr(predicate::str::contains("--origin is required")); + + cmd() + .args([ + "followups", "new", "--title", "Bad bucket", + "--origin", "CHARTER-06 §Scope", "--bucket", "someday", + "--path", path, + ]) + .assert() + .failure() + .stderr(predicate::str::contains("Unknown bucket")); +} + +#[test] +fn new_then_drift_does_not_re_extract_or_duplicate(/* CHARTER-01 R4 */) { + let tmp = TempDir::new().unwrap(); + let straymark = scaffold(tmp.path()); + write_registry(&straymark, V1_REGISTRY); + let path = tmp.path().to_str().unwrap(); + + cmd() + .args([ + "followups", "new", "--title", "Redis CI job deferred", + "--origin", "CHARTER-06 §Scope", "--path", path, + ]) + .assert() + .success(); + + // A hash-less entry must not make drift misbehave (it scans AILOGs; there + // are none here, and the ex-ante entry is not derived from one). + cmd() + .args(["followups", "drift", "--scan-all", "--path", path]) + .assert() + .success(); + + let updated = std::fs::read_to_string(straymark.join("follow-ups-backlog.md")).unwrap(); + assert_eq!(updated.matches("### FU-012").count(), 1, "no duplicate: {updated}"); +} diff --git a/dist/.agent/workflows/straymark-followups.md b/dist/.agent/workflows/straymark-followups.md index d992e4a0..cc77d581 100644 --- a/dist/.agent/workflows/straymark-followups.md +++ b/dist/.agent/workflows/straymark-followups.md @@ -1,10 +1,10 @@ --- -description: Maintain the follow-ups backlog registry — the canonical answer to "what's pending?". Session-start glance, pre-commit drift --apply, post-Charter-close triage and operator-gated promote. Thin wrapper over the straymark followups CLI; never edits CLI-owned counters by hand. +description: Maintain the follow-ups backlog registry — the canonical answer to "what's pending?". Session-start glance, pre-commit drift --apply, post-Charter-close triage (note / set-status), ex-ante creation at Charter declaration (new), and operator-gated promote. Thin wrapper over the straymark followups CLI; never edits CLI-owned counters by hand. --- # StrayMark Follow-ups Registry Skill -Maintain the central follow-ups registry (`.straymark/follow-ups-backlog.md`) — the first-class artifact that aggregates `§Follow-ups` and `R (new, not in Charter)` entries across AILOGs *(first-class since fw-4.21.0 / cli-3.19.0)*. The agent is the registry's **primary maintainer**; this skill drives the three directives of `AGENT-RULES.md §13` by delegating every mutation to the CLI (`straymark followups list/status/drift/promote`). The skill contains no extraction or counting logic of its own — parsing, schema validation, counter recomputation, and FU → TDE elevation all live in the CLI. +Maintain the central follow-ups registry (`.straymark/follow-ups-backlog.md`) — the first-class artifact that aggregates `§Follow-ups` and `R (new, not in Charter)` entries across AILOGs *(first-class since fw-4.21.0 / cli-3.19.0)*. The agent is the registry's **primary maintainer**; this skill drives the three directives of `AGENT-RULES.md §13` by delegating every mutation to the CLI (`straymark followups list/status/drift/note/set-status/new/promote`). The skill contains no extraction or counting logic of its own — parsing, schema validation, counter recomputation, and FU → TDE elevation all live in the CLI. > See `.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md` and `STRAYMARK.md §16` for the pattern; `AGENT-RULES.md §13` for the shipped directives this skill wraps. @@ -53,15 +53,29 @@ Review the registry entries the just-closed Charter resolved: ```bash straymark followups list --status suspected-closed # entries awaiting confirm-or-reopen -straymark followups recount # reconcile the CLI-owned counters after manual status flips (cli-3.20.0+) +straymark followups set-status FU-NNN closed # status + counters in one step (cli-3.39.0+) +straymark followups note FU-NNN "" --source CHARTER-NN # dated annotation (cli-3.39.0+) +straymark followups recount # reconcile counters after a BULK hand-triage session (cli-3.20.0+) straymark followups promote FU-NNN # FU → TDE elevation (operator-approved) ``` -- Mark resolved entries `closed` (with the closing Charter id in `Notes`) or `superseded`. +- Mark resolved entries `closed` (or `superseded`) with **`straymark followups set-status`** — it recomputes the counters in the same step, so there is no `recount` to forget. Record *why* with `straymark followups note FU-NNN "..." --source CHARTER-NN`. - Confirm or reopen any `suspected-closed` entries that the Charter's AILOGs produced. -- After flipping statuses by hand, run `straymark followups recount` so the CLI-owned counters ride the same commit as the triage. +- **Never hand-edit an entry.** A hand-edit can malform it and break `list`/`status`/`drift`; the verbs write through the same validated helpers and refuse to touch a registry with parse warnings. `recount` remains for a bulk session where statuses were flipped by hand anyway. - For un-resolved entries that meet the TDE criteria of `AGENT-RULES.md §3` (heritage, transversal, dedicated Charter, human prioritization), **propose** promotion via `straymark followups promote FU-NNN` — promotion itself is operator-approved, per the autonomy limits of §3. +### 3b. Charter declaration — register an ex-ante deferral (cli-3.39.0+) + +Declaring a Charter and deferring something out of scope? Register it so the gap is **deferred, not silenced** — before any AILOG exists: + +```bash +straymark followups new --title "" \ + --origin "CHARTER-NN §Scope" --cost S --trigger "" \ + --premise "" +``` + +The command prints the assigned `FU-NNN`. **Cite that id in the Charter body** — never a guessed id: the entry now exists, so a later `drift --apply` cannot hand the same number to a different entry. + ### 4. Report result Surface the CLI output verbatim (counters, alerts, created TDE paths). Example after a pre-commit sync: @@ -77,6 +91,7 @@ StrayMark: registry synced — commit it together with the AILOG. ## What this skill does NOT do - **It does not edit the frontmatter counters** (`total_open`, `total_promoted`, `total_suspected_closed`, …). They are CLI-owned: `straymark followups recount` (or any write command) recomputes them. Hand-editing them is a §13 violation. +- **It does not hand-edit entries.** `note` / `set-status` / `new` (cli-3.39.0+) are the write path; hand-editing a CLI-parsed registry is what those verbs exist to replace. - **It does not promote without the operator.** `straymark followups promote` is proposed, never auto-run — prioritization and assignment stay human (`AGENT-RULES.md §3`). - **It does not delete `suspected-closed` entries.** The operator confirms (→ `closed`) or reopens them at the next triage. - **It does not re-scan AILOGs to answer "what's pending?"** when the registry exists — the registry is canonical; `drift` tells you when it is not trustworthy. diff --git a/dist/.claude/skills/straymark-followups/SKILL.md b/dist/.claude/skills/straymark-followups/SKILL.md index 0f8a74b8..3f9935f5 100644 --- a/dist/.claude/skills/straymark-followups/SKILL.md +++ b/dist/.claude/skills/straymark-followups/SKILL.md @@ -1,12 +1,12 @@ --- name: straymark-followups -description: Maintain the follow-ups backlog registry — the canonical answer to "what's pending?". Session-start glance, pre-commit drift --apply, post-Charter-close triage and operator-gated promote. Thin wrapper over the straymark followups CLI; never edits CLI-owned counters by hand. +description: Maintain the follow-ups backlog registry — the canonical answer to "what's pending?". Session-start glance, pre-commit drift --apply, post-Charter-close triage (note / set-status), ex-ante creation at Charter declaration (new), and operator-gated promote. Thin wrapper over the straymark followups CLI; never edits CLI-owned counters by hand. allowed-tools: Read, Glob, Bash(git diff *, git log *, git status *, ls *, straymark followups *) --- # StrayMark Follow-ups Registry Skill -Maintain the central follow-ups registry (`.straymark/follow-ups-backlog.md`) — the first-class artifact that aggregates `§Follow-ups` and `R (new, not in Charter)` entries across AILOGs *(first-class since fw-4.21.0 / cli-3.19.0)*. The agent is the registry's **primary maintainer**; this skill drives the three directives of `AGENT-RULES.md §13` by delegating every mutation to the CLI (`straymark followups list/status/drift/promote`). The skill contains no extraction or counting logic of its own — parsing, schema validation, counter recomputation, and FU → TDE elevation all live in the CLI. +Maintain the central follow-ups registry (`.straymark/follow-ups-backlog.md`) — the first-class artifact that aggregates `§Follow-ups` and `R (new, not in Charter)` entries across AILOGs *(first-class since fw-4.21.0 / cli-3.19.0)*. The agent is the registry's **primary maintainer**; this skill drives the three directives of `AGENT-RULES.md §13` by delegating every mutation to the CLI (`straymark followups list/status/drift/note/set-status/new/promote`). The skill contains no extraction or counting logic of its own — parsing, schema validation, counter recomputation, and FU → TDE elevation all live in the CLI. > See `.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md` and `STRAYMARK.md §16` for the pattern; `AGENT-RULES.md §13` for the shipped directives this skill wraps. @@ -55,15 +55,29 @@ Review the registry entries the just-closed Charter resolved: ```bash straymark followups list --status suspected-closed # entries awaiting confirm-or-reopen -straymark followups recount # reconcile the CLI-owned counters after manual status flips (cli-3.20.0+) +straymark followups set-status FU-NNN closed # status + counters in one step (cli-3.39.0+) +straymark followups note FU-NNN "" --source CHARTER-NN # dated annotation (cli-3.39.0+) +straymark followups recount # reconcile counters after a BULK hand-triage session (cli-3.20.0+) straymark followups promote FU-NNN # FU → TDE elevation (operator-approved) ``` -- Mark resolved entries `closed` (with the closing Charter id in `Notes`) or `superseded`. +- Mark resolved entries `closed` (or `superseded`) with **`straymark followups set-status`** — it recomputes the counters in the same step, so there is no `recount` to forget. Record *why* with `straymark followups note FU-NNN "..." --source CHARTER-NN`. - Confirm or reopen any `suspected-closed` entries that the Charter's AILOGs produced. -- After flipping statuses by hand, run `straymark followups recount` so the CLI-owned counters ride the same commit as the triage. +- **Never hand-edit an entry.** A hand-edit can malform it and break `list`/`status`/`drift`; the verbs write through the same validated helpers and refuse to touch a registry with parse warnings. `recount` remains for a bulk session where statuses were flipped by hand anyway. - For un-resolved entries that meet the TDE criteria of `AGENT-RULES.md §3` (heritage, transversal, dedicated Charter, human prioritization), **propose** promotion via `straymark followups promote FU-NNN` — promotion itself is operator-approved, per the autonomy limits of §3. +### 3b. Charter declaration — register an ex-ante deferral (cli-3.39.0+) + +Declaring a Charter and deferring something out of scope? Register it so the gap is **deferred, not silenced** — before any AILOG exists: + +```bash +straymark followups new --title "" \ + --origin "CHARTER-NN §Scope" --cost S --trigger "" \ + --premise "" +``` + +The command prints the assigned `FU-NNN`. **Cite that id in the Charter body** — never a guessed id: the entry now exists, so a later `drift --apply` cannot hand the same number to a different entry. + ### 4. Report result Surface the CLI output verbatim (counters, alerts, created TDE paths). Example after a pre-commit sync: @@ -79,6 +93,7 @@ StrayMark: registry synced — commit it together with the AILOG. ## What this skill does NOT do - **It does not edit the frontmatter counters** (`total_open`, `total_promoted`, `total_suspected_closed`, …). They are CLI-owned: `straymark followups recount` (or any write command) recomputes them. Hand-editing them is a §13 violation. +- **It does not hand-edit entries.** `note` / `set-status` / `new` (cli-3.39.0+) are the write path; hand-editing a CLI-parsed registry is what those verbs exist to replace. - **It does not promote without the operator.** `straymark followups promote` is proposed, never auto-run — prioritization and assignment stay human (`AGENT-RULES.md §3`). - **It does not delete `suspected-closed` entries.** The operator confirms (→ `closed`) or reopens them at the next triage. - **It does not re-scan AILOGs to answer "what's pending?"** when the registry exists — the registry is canonical; `drift` tells you when it is not trustworthy. diff --git a/dist/.codex/skills/straymark-followups/SKILL.md b/dist/.codex/skills/straymark-followups/SKILL.md index f8a01d85..e7650f46 100644 --- a/dist/.codex/skills/straymark-followups/SKILL.md +++ b/dist/.codex/skills/straymark-followups/SKILL.md @@ -1,11 +1,11 @@ --- name: straymark-followups -description: Maintain the follow-ups backlog registry — the canonical answer to "what's pending?". Session-start glance, pre-commit drift --apply, post-Charter-close triage and operator-gated promote. Thin wrapper over the straymark followups CLI; never edits CLI-owned counters by hand. +description: Maintain the follow-ups backlog registry — the canonical answer to "what's pending?". Session-start glance, pre-commit drift --apply, post-Charter-close triage (note / set-status), ex-ante creation at Charter declaration (new), and operator-gated promote. Thin wrapper over the straymark followups CLI; never edits CLI-owned counters by hand. --- # StrayMark Follow-ups Registry Skill -Maintain the central follow-ups registry (`.straymark/follow-ups-backlog.md`) — the first-class artifact that aggregates `§Follow-ups` and `R (new, not in Charter)` entries across AILOGs *(first-class since fw-4.21.0 / cli-3.19.0)*. The agent is the registry's **primary maintainer**; this skill drives the three directives of `AGENT-RULES.md §13` by delegating every mutation to the CLI (`straymark followups list/status/drift/promote`). The skill contains no extraction or counting logic of its own — parsing, schema validation, counter recomputation, and FU → TDE elevation all live in the CLI. +Maintain the central follow-ups registry (`.straymark/follow-ups-backlog.md`) — the first-class artifact that aggregates `§Follow-ups` and `R (new, not in Charter)` entries across AILOGs *(first-class since fw-4.21.0 / cli-3.19.0)*. The agent is the registry's **primary maintainer**; this skill drives the three directives of `AGENT-RULES.md §13` by delegating every mutation to the CLI (`straymark followups list/status/drift/note/set-status/new/promote`). The skill contains no extraction or counting logic of its own — parsing, schema validation, counter recomputation, and FU → TDE elevation all live in the CLI. > See `.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md` and `STRAYMARK.md §16` for the pattern; `AGENT-RULES.md §13` for the shipped directives this skill wraps. @@ -54,15 +54,29 @@ Review the registry entries the just-closed Charter resolved: ```bash straymark followups list --status suspected-closed # entries awaiting confirm-or-reopen -straymark followups recount # reconcile the CLI-owned counters after manual status flips (cli-3.20.0+) +straymark followups set-status FU-NNN closed # status + counters in one step (cli-3.39.0+) +straymark followups note FU-NNN "" --source CHARTER-NN # dated annotation (cli-3.39.0+) +straymark followups recount # reconcile counters after a BULK hand-triage session (cli-3.20.0+) straymark followups promote FU-NNN # FU → TDE elevation (operator-approved) ``` -- Mark resolved entries `closed` (with the closing Charter id in `Notes`) or `superseded`. +- Mark resolved entries `closed` (or `superseded`) with **`straymark followups set-status`** — it recomputes the counters in the same step, so there is no `recount` to forget. Record *why* with `straymark followups note FU-NNN "..." --source CHARTER-NN`. - Confirm or reopen any `suspected-closed` entries that the Charter's AILOGs produced. -- After flipping statuses by hand, run `straymark followups recount` so the CLI-owned counters ride the same commit as the triage. +- **Never hand-edit an entry.** A hand-edit can malform it and break `list`/`status`/`drift`; the verbs write through the same validated helpers and refuse to touch a registry with parse warnings. `recount` remains for a bulk session where statuses were flipped by hand anyway. - For un-resolved entries that meet the TDE criteria of `AGENT-RULES.md §3` (heritage, transversal, dedicated Charter, human prioritization), **propose** promotion via `straymark followups promote FU-NNN` — promotion itself is operator-approved, per the autonomy limits of §3. +### 3b. Charter declaration — register an ex-ante deferral (cli-3.39.0+) + +Declaring a Charter and deferring something out of scope? Register it so the gap is **deferred, not silenced** — before any AILOG exists: + +```bash +straymark followups new --title "" \ + --origin "CHARTER-NN §Scope" --cost S --trigger "" \ + --premise "" +``` + +The command prints the assigned `FU-NNN`. **Cite that id in the Charter body** — never a guessed id: the entry now exists, so a later `drift --apply` cannot hand the same number to a different entry. + ### 4. Report result Surface the CLI output verbatim (counters, alerts, created TDE paths). Example after a pre-commit sync: @@ -78,6 +92,7 @@ StrayMark: registry synced — commit it together with the AILOG. ## What this skill does NOT do - **It does not edit the frontmatter counters** (`total_open`, `total_promoted`, `total_suspected_closed`, …). They are CLI-owned: `straymark followups recount` (or any write command) recomputes them. Hand-editing them is a §13 violation. +- **It does not hand-edit entries.** `note` / `set-status` / `new` (cli-3.39.0+) are the write path; hand-editing a CLI-parsed registry is what those verbs exist to replace. - **It does not promote without the operator.** `straymark followups promote` is proposed, never auto-run — prioritization and assignment stay human (`AGENT-RULES.md §3`). - **It does not delete `suspected-closed` entries.** The operator confirms (→ `closed`) or reopens them at the next triage. - **It does not re-scan AILOGs to answer "what's pending?"** when the registry exists — the registry is canonical; `drift` tells you when it is not trustworthy. diff --git a/dist/.gemini/skills/straymark-followups/SKILL.md b/dist/.gemini/skills/straymark-followups/SKILL.md index f8a01d85..e7650f46 100644 --- a/dist/.gemini/skills/straymark-followups/SKILL.md +++ b/dist/.gemini/skills/straymark-followups/SKILL.md @@ -1,11 +1,11 @@ --- name: straymark-followups -description: Maintain the follow-ups backlog registry — the canonical answer to "what's pending?". Session-start glance, pre-commit drift --apply, post-Charter-close triage and operator-gated promote. Thin wrapper over the straymark followups CLI; never edits CLI-owned counters by hand. +description: Maintain the follow-ups backlog registry — the canonical answer to "what's pending?". Session-start glance, pre-commit drift --apply, post-Charter-close triage (note / set-status), ex-ante creation at Charter declaration (new), and operator-gated promote. Thin wrapper over the straymark followups CLI; never edits CLI-owned counters by hand. --- # StrayMark Follow-ups Registry Skill -Maintain the central follow-ups registry (`.straymark/follow-ups-backlog.md`) — the first-class artifact that aggregates `§Follow-ups` and `R (new, not in Charter)` entries across AILOGs *(first-class since fw-4.21.0 / cli-3.19.0)*. The agent is the registry's **primary maintainer**; this skill drives the three directives of `AGENT-RULES.md §13` by delegating every mutation to the CLI (`straymark followups list/status/drift/promote`). The skill contains no extraction or counting logic of its own — parsing, schema validation, counter recomputation, and FU → TDE elevation all live in the CLI. +Maintain the central follow-ups registry (`.straymark/follow-ups-backlog.md`) — the first-class artifact that aggregates `§Follow-ups` and `R (new, not in Charter)` entries across AILOGs *(first-class since fw-4.21.0 / cli-3.19.0)*. The agent is the registry's **primary maintainer**; this skill drives the three directives of `AGENT-RULES.md §13` by delegating every mutation to the CLI (`straymark followups list/status/drift/note/set-status/new/promote`). The skill contains no extraction or counting logic of its own — parsing, schema validation, counter recomputation, and FU → TDE elevation all live in the CLI. > See `.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md` and `STRAYMARK.md §16` for the pattern; `AGENT-RULES.md §13` for the shipped directives this skill wraps. @@ -54,15 +54,29 @@ Review the registry entries the just-closed Charter resolved: ```bash straymark followups list --status suspected-closed # entries awaiting confirm-or-reopen -straymark followups recount # reconcile the CLI-owned counters after manual status flips (cli-3.20.0+) +straymark followups set-status FU-NNN closed # status + counters in one step (cli-3.39.0+) +straymark followups note FU-NNN "" --source CHARTER-NN # dated annotation (cli-3.39.0+) +straymark followups recount # reconcile counters after a BULK hand-triage session (cli-3.20.0+) straymark followups promote FU-NNN # FU → TDE elevation (operator-approved) ``` -- Mark resolved entries `closed` (with the closing Charter id in `Notes`) or `superseded`. +- Mark resolved entries `closed` (or `superseded`) with **`straymark followups set-status`** — it recomputes the counters in the same step, so there is no `recount` to forget. Record *why* with `straymark followups note FU-NNN "..." --source CHARTER-NN`. - Confirm or reopen any `suspected-closed` entries that the Charter's AILOGs produced. -- After flipping statuses by hand, run `straymark followups recount` so the CLI-owned counters ride the same commit as the triage. +- **Never hand-edit an entry.** A hand-edit can malform it and break `list`/`status`/`drift`; the verbs write through the same validated helpers and refuse to touch a registry with parse warnings. `recount` remains for a bulk session where statuses were flipped by hand anyway. - For un-resolved entries that meet the TDE criteria of `AGENT-RULES.md §3` (heritage, transversal, dedicated Charter, human prioritization), **propose** promotion via `straymark followups promote FU-NNN` — promotion itself is operator-approved, per the autonomy limits of §3. +### 3b. Charter declaration — register an ex-ante deferral (cli-3.39.0+) + +Declaring a Charter and deferring something out of scope? Register it so the gap is **deferred, not silenced** — before any AILOG exists: + +```bash +straymark followups new --title "" \ + --origin "CHARTER-NN §Scope" --cost S --trigger "" \ + --premise "" +``` + +The command prints the assigned `FU-NNN`. **Cite that id in the Charter body** — never a guessed id: the entry now exists, so a later `drift --apply` cannot hand the same number to a different entry. + ### 4. Report result Surface the CLI output verbatim (counters, alerts, created TDE paths). Example after a pre-commit sync: @@ -78,6 +92,7 @@ StrayMark: registry synced — commit it together with the AILOG. ## What this skill does NOT do - **It does not edit the frontmatter counters** (`total_open`, `total_promoted`, `total_suspected_closed`, …). They are CLI-owned: `straymark followups recount` (or any write command) recomputes them. Hand-editing them is a §13 violation. +- **It does not hand-edit entries.** `note` / `set-status` / `new` (cli-3.39.0+) are the write path; hand-editing a CLI-parsed registry is what those verbs exist to replace. - **It does not promote without the operator.** `straymark followups promote` is proposed, never auto-run — prioritization and assignment stay human (`AGENT-RULES.md §3`). - **It does not delete `suspected-closed` entries.** The operator confirms (→ `closed`) or reopens them at the next triage. - **It does not re-scan AILOGs to answer "what's pending?"** when the registry exists — the registry is canonical; `drift` tells you when it is not trustworthy. diff --git a/dist/.straymark/00-governance/AGENT-RULES.md b/dist/.straymark/00-governance/AGENT-RULES.md index 1867fe1a..7177f953 100644 --- a/dist/.straymark/00-governance/AGENT-RULES.md +++ b/dist/.straymark/00-governance/AGENT-RULES.md @@ -418,4 +418,4 @@ When a project accumulates a high volume of AILOGs across multiple Charters and --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/C4-DIAGRAM-GUIDE.md b/dist/.straymark/00-governance/C4-DIAGRAM-GUIDE.md index eea9e916..3d2b64af 100644 --- a/dist/.straymark/00-governance/C4-DIAGRAM-GUIDE.md +++ b/dist/.straymark/00-governance/C4-DIAGRAM-GUIDE.md @@ -234,4 +234,4 @@ Use a Level 1 (Context) diagram to illustrate: --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/DOCUMENTATION-POLICY.md b/dist/.straymark/00-governance/DOCUMENTATION-POLICY.md index 3f1be82d..f2ea01c6 100644 --- a/dist/.straymark/00-governance/DOCUMENTATION-POLICY.md +++ b/dist/.straymark/00-governance/DOCUMENTATION-POLICY.md @@ -319,4 +319,4 @@ See also [ADR-2025-01-20-001] for architectural context. --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md b/dist/.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md index f798bb63..083be3ee 100644 --- a/dist/.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md +++ b/dist/.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md @@ -267,10 +267,23 @@ straymark followups drift [--apply|--scan-all] # drift detection (see above) straymark followups recount # recompute the CLI-owned counters after a manual-triage session (cli-3.20.0+) straymark followups promote FU-NNN [--premise-verified] # automate FU → TDE promotion; surfaces the premise re-check, stamps Verified-at with the flag (cli-3.37.0+) straymark followups verify FU-NNN [--premise "..."] [--verified] [--at DATE] # re-verify a dated hypothesis at execution: surface/record the premise, stamp Verified-at (cli-3.37.0+) +straymark followups note FU-NNN "" [--source CHARTER-NN|AILOG-…] # append a dated annotation to Notes (cli-3.39.0+) +straymark followups set-status FU-NNN # change status AND recompute the counters in one step (cli-3.39.0+) +straymark followups new --title "..." --origin "CHARTER-NN §Scope" [--bucket …] [--cost …] [--trigger …] [--premise …] # create an entry declared ex-ante (cli-3.39.0+) ``` `verify` and `promote --premise-verified` are the execution-time affordances of the "Epistemic status" discipline: they put the premise in front of the operator at the moment of spending and record that the re-check happened. Human judgment stays out of the CLI — it surfaces and stamps, it never decides truth. +### Mutating and creating entries (cli-3.39.0+) + +`note`, `set-status` and `new` replace the last flows that required hand-editing this CLI-parsed file. Two things went wrong there: a hand-edit can malform an entry and break `list`/`status`/`drift`, and the status-change flow was a **two-step** — edit the bullet, then remember `recount` — that desyncs the moment you forget the second half, leaving the counters quietly lying about the backlog. + +- **`note`** appends to `Notes` (a single-line field, so annotations compose rather than stack), stamped with the date and, with `--source`, with the Charter or AILOG that motivated it. Use it for the common case of recording a *partial* mitigation without changing status. +- **`set-status`** writes the status and recomputes the counters in the same step. It refuses a status outside the vocabulary — the parser is lenient, so a typo would not fail, it would silently drop the entry from every counter — and redirects `promoted` to `followups promote`, which also writes the TDE that gives the status something to point at. +- **`new`** creates an entry whose origin is a **Charter declaration** (`Origin-class: ex-ante-planning`). Both older population paths assume an ex-post origin: `drift --apply` extracts from AILOGs, and a deferral decided at declaration time precedes any AILOG by design. `new` assigns the id atomically and prints it, so the Charter body cites an entry that **exists** instead of a reserved guess that the next `drift --apply` could hand to a different entry. An ex-ante entry carries **no `Source-hash`**: there is no AILOG to hash, and inventing one would make a later `drift --apply` believe it had already extracted something it never saw. + +All three refuse to write when the registry has parse warnings: a surgical edit against a structure the parser mis-read can corrupt neighbouring entries. `recount` remains the escape hatch for a bulk manual-triage session — and the idempotent check that these verbs got the arithmetic right. + The registry also appears as a synthetic **Follow-ups** group in the `straymark explore` TUI (sub-nodes per bucket) and as a counts block in `straymark status`. --- @@ -343,4 +356,4 @@ Contributed via [issue #111](https://github.com/StrangeDaysTech/straymark/issues --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/QUICK-REFERENCE.md b/dist/.straymark/00-governance/QUICK-REFERENCE.md index d0ea6ea9..f438f76a 100644 --- a/dist/.straymark/00-governance/QUICK-REFERENCE.md +++ b/dist/.straymark/00-governance/QUICK-REFERENCE.md @@ -263,4 +263,4 @@ Mark `review_required: true` when: --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/i18n/es/AGENT-RULES.md b/dist/.straymark/00-governance/i18n/es/AGENT-RULES.md index 4d3d8ae3..c6185e50 100644 --- a/dist/.straymark/00-governance/i18n/es/AGENT-RULES.md +++ b/dist/.straymark/00-governance/i18n/es/AGENT-RULES.md @@ -418,4 +418,4 @@ Cuando un proyecto acumula un volumen alto de AILOGs a lo largo de múltiples Ch --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/i18n/es/C4-DIAGRAM-GUIDE.md b/dist/.straymark/00-governance/i18n/es/C4-DIAGRAM-GUIDE.md index 6fcbefd9..c939b585 100644 --- a/dist/.straymark/00-governance/i18n/es/C4-DIAGRAM-GUIDE.md +++ b/dist/.straymark/00-governance/i18n/es/C4-DIAGRAM-GUIDE.md @@ -234,4 +234,4 @@ Usar un diagrama de Nivel 1 (Contexto) para ilustrar: --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/i18n/es/DOCUMENTATION-POLICY.md b/dist/.straymark/00-governance/i18n/es/DOCUMENTATION-POLICY.md index 6bd6d501..c5ffef60 100644 --- a/dist/.straymark/00-governance/i18n/es/DOCUMENTATION-POLICY.md +++ b/dist/.straymark/00-governance/i18n/es/DOCUMENTATION-POLICY.md @@ -312,4 +312,4 @@ Ver también [ADR-2025-01-20-001] para contexto arquitectónico. --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/i18n/es/FOLLOW-UPS-BACKLOG-PATTERN.md b/dist/.straymark/00-governance/i18n/es/FOLLOW-UPS-BACKLOG-PATTERN.md index 7e88bcd0..c5b2ed31 100644 --- a/dist/.straymark/00-governance/i18n/es/FOLLOW-UPS-BACKLOG-PATTERN.md +++ b/dist/.straymark/00-governance/i18n/es/FOLLOW-UPS-BACKLOG-PATTERN.md @@ -267,10 +267,23 @@ straymark followups drift [--apply|--scan-all] # detección de drift (ver arri straymark followups recount # recalcula los contadores CLI-owned tras una sesión de triage manual (cli-3.20.0+) straymark followups promote FU-NNN [--premise-verified] # automatiza la promoción FU → TDE; superficie el re-chequeo de la premisa, sella Verified-at con el flag (cli-3.37.0+) straymark followups verify FU-NNN [--premise "..."] [--verified] [--at FECHA] # re-verifica una hipótesis fechada en la ejecución: superficie/registra la premisa, sella Verified-at (cli-3.37.0+) +straymark followups note FU-NNN "" [--source CHARTER-NN|AILOG-…] # anexa una anotación fechada a Notes (cli-3.39.0+) +straymark followups set-status FU-NNN # cambia el status Y recomputa los contadores en un solo paso (cli-3.39.0+) +straymark followups new --title "..." --origin "CHARTER-NN §Scope" [--bucket …] [--cost …] [--trigger …] [--premise …] # crea una entrada declarada ex-ante (cli-3.39.0+) ``` `verify` y `promote --premise-verified` son las afordancias en tiempo de ejecución de la disciplina de "Estatus epistémico": ponen la premisa frente al operador en el momento del gasto y registran que el re-chequeo ocurrió. El juicio humano queda fuera del CLI — superficie y sella, nunca decide la verdad. +### Mutar y crear entradas (cli-3.39.0+) + +`note`, `set-status` y `new` reemplazan los últimos flujos que exigían editar a mano este archivo parseado por el CLI. Ahí fallaban dos cosas: una edición a mano puede malformar una entrada y romper `list`/`status`/`drift`, y el cambio de status era un **dos-pasos** — editar el bullet y luego acordarse de `recount` — que se desincroniza en cuanto olvidas la segunda mitad, dejando a los contadores mintiendo en silencio sobre el backlog. + +- **`note`** anexa a `Notes` (campo de una sola línea, así que las anotaciones se componen en vez de apilarse), con la fecha sellada y, con `--source`, con el Charter o AILOG que la motivó. Sirve para el caso común: registrar una mitigación *parcial* sin cambiar el status. +- **`set-status`** escribe el status y recomputa los contadores en el mismo paso. Rechaza un status fuera del vocabulario — el parser es indulgente, así que un typo no fallaría: sacaría la entrada de todos los contadores en silencio — y redirige `promoted` a `followups promote`, que además escribe el TDE que le da a ese status algo a lo que apuntar. +- **`new`** crea una entrada cuyo origen es una **declaración de Charter** (`Origin-class: ex-ante-planning`). Las dos rutas anteriores asumen origen ex-post: `drift --apply` extrae de AILOGs, y un diferimiento decidido en tiempo de declaración precede a cualquier AILOG por diseño. `new` asigna el id de forma atómica y lo imprime, así que el cuerpo del Charter cita una entrada que **existe** en vez de una reserva conjeturada que el siguiente `drift --apply` podría entregarle a otra entrada. Una entrada ex-ante no lleva **`Source-hash`**: no hay AILOG que hashear, e inventar uno haría que un `drift --apply` posterior creyera haber extraído algo que nunca vio. + +Los tres se niegan a escribir si el registro tiene avisos de parseo: una edición quirúrgica contra una estructura mal leída puede corromper entradas vecinas. `recount` sigue siendo la vía de escape para una sesión de triage manual masivo — y el chequeo idempotente de que estos verbos hicieron bien la aritmética. + El registry también aparece como un grupo sintético **Follow-ups** en la TUI de `straymark explore` (sub-nodos por bucket) y como un bloque de conteos en `straymark status`. --- @@ -343,4 +356,4 @@ Contribuido vía [issue #111](https://github.com/StrangeDaysTech/straymark/issue --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/i18n/es/QUICK-REFERENCE.md b/dist/.straymark/00-governance/i18n/es/QUICK-REFERENCE.md index f1cf9644..7491c341 100644 --- a/dist/.straymark/00-governance/i18n/es/QUICK-REFERENCE.md +++ b/dist/.straymark/00-governance/i18n/es/QUICK-REFERENCE.md @@ -235,4 +235,4 @@ Marcar `review_required: true` cuando: --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/i18n/zh-CN/AGENT-RULES.md b/dist/.straymark/00-governance/i18n/zh-CN/AGENT-RULES.md index 685c25b5..e67e2b28 100644 --- a/dist/.straymark/00-governance/i18n/zh-CN/AGENT-RULES.md +++ b/dist/.straymark/00-governance/i18n/zh-CN/AGENT-RULES.md @@ -413,4 +413,4 @@ confidence: high | medium | low --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/i18n/zh-CN/C4-DIAGRAM-GUIDE.md b/dist/.straymark/00-governance/i18n/zh-CN/C4-DIAGRAM-GUIDE.md index 434f5a37..e14e1b5d 100644 --- a/dist/.straymark/00-governance/i18n/zh-CN/C4-DIAGRAM-GUIDE.md +++ b/dist/.straymark/00-governance/i18n/zh-CN/C4-DIAGRAM-GUIDE.md @@ -234,4 +234,4 @@ Rel(api, db, "Reads/Writes", "SQL") --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/i18n/zh-CN/DOCUMENTATION-POLICY.md b/dist/.straymark/00-governance/i18n/zh-CN/DOCUMENTATION-POLICY.md index f7437f3b..7950c5e9 100644 --- a/dist/.straymark/00-governance/i18n/zh-CN/DOCUMENTATION-POLICY.md +++ b/dist/.straymark/00-governance/i18n/zh-CN/DOCUMENTATION-POLICY.md @@ -311,4 +311,4 @@ review_outcome: approved # approved | revisions_requested | rejec --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/i18n/zh-CN/FOLLOW-UPS-BACKLOG-PATTERN.md b/dist/.straymark/00-governance/i18n/zh-CN/FOLLOW-UPS-BACKLOG-PATTERN.md index ad7a3227..bc8a88a9 100644 --- a/dist/.straymark/00-governance/i18n/zh-CN/FOLLOW-UPS-BACKLOG-PATTERN.md +++ b/dist/.straymark/00-governance/i18n/zh-CN/FOLLOW-UPS-BACKLOG-PATTERN.md @@ -267,10 +267,23 @@ straymark followups drift [--apply|--scan-all] # 漂移检测(见上文) straymark followups recount # 手动分诊会话后重新计算 CLI 拥有的计数器(cli-3.20.0+) straymark followups promote FU-NNN [--premise-verified] # 自动化 FU → TDE 提升;浮现前提重新核查,带该 flag 时盖上 Verified-at(cli-3.37.0+) straymark followups verify FU-NNN [--premise "..."] [--verified] [--at 日期] # 在执行时重新验证一个有日期的假设:浮现/记录前提,盖上 Verified-at(cli-3.37.0+) +straymark followups note FU-NNN "<文本>" [--source CHARTER-NN|AILOG-…] # 向 Notes 追加一条带日期的注记(cli-3.39.0+) +straymark followups set-status FU-NNN # 修改状态**并**在同一步骤重算计数器(cli-3.39.0+) +straymark followups new --title "..." --origin "CHARTER-NN §Scope" [--bucket …] [--cost …] [--trigger …] [--premise …] # 创建一条事前(ex-ante)声明的条目(cli-3.39.0+) ``` `verify` 与 `promote --premise-verified` 是"认识论地位"纪律的执行时可供性(affordance):它们在投入的那一刻把前提摆在操作员面前,并记录重新核查已经发生。人的判断留在 CLI 之外 —— 它浮现并盖印,从不裁定真伪。 +### 修改与创建条目(cli-3.39.0+) + +`note`、`set-status` 与 `new` 取代了最后那些必须手工编辑这个由 CLI 解析的文件的流程。那里有两处会出错:手工编辑可能让条目变形,进而破坏 `list`/`status`/`drift`;而修改状态是一个**两步流程** —— 先编辑 bullet,再记得运行 `recount` —— 一旦忘了后半步就会失同步,让计数器悄悄谎报 backlog。 + +- **`note`** 向 `Notes` 追加内容(该字段只有一行,所以注记是组合而非堆叠),盖上日期;带 `--source` 时还记录促成它的 Charter 或 AILOG。用于最常见的场景:记录**部分**缓解措施而不改变状态。 +- **`set-status`** 在同一步骤写入状态并重算计数器。它拒绝词表之外的状态 —— 解析器是宽容的,所以一个拼写错误不会失败,而是会悄悄把该条目从所有计数器中剔除 —— 并把 `promoted` 重定向到 `followups promote`,后者还会写出让该状态有所指的 TDE。 +- **`new`** 创建其来源为**Charter 声明**的条目(`Origin-class: ex-ante-planning`)。此前两条填充路径都假定来源是事后的:`drift --apply` 从 AILOG 中提取,而在声明时刻做出的推迟按设计先于任何 AILOG。`new` 原子地分配并打印 id,于是 Charter 正文引用的是一条**已存在**的条目,而不是下一次 `drift --apply` 可能分配给别的条目的预留猜测。事前条目**不携带 `Source-hash`**:没有 AILOG 可供哈希,而编造一个会让后续的 `drift --apply` 以为自己已经提取过它从未见过的东西。 + +三者在注册表存在解析告警时都拒绝写入:针对被解析器误读的结构做手术式编辑,可能损坏相邻条目。`recount` 仍是批量手工 triage 的逃生通道 —— 也是这些动词算术是否正确的幂等校验。 + 注册表也在 `straymark explore` TUI 中作为一个合成的 **Follow-ups** 分组出现(每个 bucket 一个子节点),并在 `straymark status` 中作为一个计数块出现。 --- @@ -343,4 +356,4 @@ straymark followups verify FU-NNN [--premise "..."] [--verified] [--at 日期] --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/00-governance/i18n/zh-CN/QUICK-REFERENCE.md b/dist/.straymark/00-governance/i18n/zh-CN/QUICK-REFERENCE.md index 812226fd..22f2fb6d 100644 --- a/dist/.straymark/00-governance/i18n/zh-CN/QUICK-REFERENCE.md +++ b/dist/.straymark/00-governance/i18n/zh-CN/QUICK-REFERENCE.md @@ -235,4 +235,4 @@ risk_level: low | medium | high | critical --- -*StrayMark fw-4.36.0 | [Strange Days Tech](https://strangedays.tech)* +*StrayMark fw-4.37.0 | [Strange Days Tech](https://strangedays.tech)* diff --git a/dist/.straymark/templates/follow-ups-backlog.md b/dist/.straymark/templates/follow-ups-backlog.md index f30da8da..c26c9116 100644 --- a/dist/.straymark/templates/follow-ups-backlog.md +++ b/dist/.straymark/templates/follow-ups-backlog.md @@ -18,7 +18,8 @@ fully_extracted_ailogs: [] # Follow-ups Backlog > Central registry of `§Follow-ups` and `R (new, not in Charter)` entries across AILOGs. -> Maintained by `straymark followups drift --apply`; counters are CLI-owned. +> Maintained by `straymark followups drift --apply` (ex-post) and `straymark followups new` +> (ex-ante, for a deferral decided at Charter-declaration time); counters are CLI-owned. > Convention: `.straymark/00-governance/FOLLOW-UPS-BACKLOG-PATTERN.md` · > Schema: `.straymark/schemas/follow-ups-backlog.schema.v1.json` @@ -26,7 +27,7 @@ fully_extracted_ailogs: [] Entry shape (v1 — optional fields marked): ### FU-NNN — -- **Origin**: AILOG-NNNN-NN-NN-NNN +- **Origin**: AILOG-NNNN-NN-NN-NNN (or CHARTER-NN §Scope for an ex-ante deferral) - **Origin-class**: ex-ante-planning | testing | telemetry | staging | real-env-bug (optional) - **Status**: open | in-progress | suspected-closed | closed | superseded | promoted - **Severity**: normal | blocking (optional) @@ -43,6 +44,12 @@ have been false at capture or gone stale since. Re-verify it at execution — th cheap moment — not at capture: `straymark followups verify FU-NNN --verified` (or `followups promote FU-NNN --premise-verified`). See the pattern doc's "Epistemic status" section. + +Never hand-edit an entry: `straymark followups note FU-NNN ""` appends a +dated annotation, `straymark followups set-status FU-NNN ` flips the +status and recomputes the counters in the same step (no `recount` to forget), +and `straymark followups new` creates one with an atomically-assigned id. A +hand-edit can malform an entry and break `list`/`status`/`drift`. --> ## Bucket: ready diff --git a/dist/dist-manifest.yml b/dist/dist-manifest.yml index d905f0db..630bced3 100644 --- a/dist/dist-manifest.yml +++ b/dist/dist-manifest.yml @@ -1,4 +1,4 @@ -version: "4.36.0" +version: "4.37.0" description: "StrayMark distribution manifest" repository: "https://github.com/StrangeDaysTech/straymark" diff --git a/docs/adopters/CLI-REFERENCE.md b/docs/adopters/CLI-REFERENCE.md index 4d846fbf..b04d71d6 100644 --- a/docs/adopters/CLI-REFERENCE.md +++ b/docs/adopters/CLI-REFERENCE.md @@ -45,8 +45,8 @@ StrayMark uses **independent version tags** for each component: | Component | Tag prefix | Example | What it includes | |-----------|-----------|---------|------------------| -| Framework | `fw-` | `fw-4.36.0` | Templates (12 types), governance docs, directives, Charter template + schema | -| CLI | `cli-` | `cli-3.38.0` | The `straymark` binary | +| Framework | `fw-` | `fw-4.37.0` | Templates (12 types), governance docs, directives, Charter template + schema | +| CLI | `cli-` | `cli-3.39.0` | The `straymark` binary | | Loom (EXPERIMENTAL) | `loom-` | `loom-0.4.2` | The `straymark-loom` visualization server, downloaded on demand by `straymark loom serve` | Framework and CLI are released independently. A framework update does not require a CLI update, and vice versa. @@ -958,6 +958,9 @@ Parsing is **lenient**: v0 registries (pre-fw-4.21.0) are read without errors; t - `straymark followups recount` — recompute the CLI-owned counters after a manual-triage session *(cli-3.20.0+)* - `straymark followups promote` — elevate an entry to a TDE document *(cli-3.19.0+)* - `straymark followups verify` — re-verify a dated hypothesis's premise at execution time *(cli-3.37.0+)* +- `straymark followups note` — append a dated annotation to an entry's `Notes` *(cli-3.39.0+)* +- `straymark followups set-status` — change an entry's status and recompute the counters in one step *(cli-3.39.0+)* +- `straymark followups new` — create an entry declared **ex-ante**, at Charter-declaration time *(cli-3.39.0+)* #### `straymark followups list [--bucket ] [--status ] [--severity ] [--label ] [path]` @@ -1040,6 +1043,61 @@ $ straymark followups verify FU-016 --premise "yrs has an independent reference ✓ FU-016 verified — Verified-at → 2026-06-04. ``` +#### `straymark followups note "" [--source ] [--path ]` *(cli-3.39.0+)* + +Append a dated annotation to an entry's `Notes` in one validated edit. Before this verb (#355), recording that an entry received a *partial* mitigation — without changing its status — meant hand-editing a CLI-parsed file: an edit that can malform the entry and break `list`/`status`/`drift`, with nothing recording when the note was made or what motivated it. + +| Flag | Default | Description | +|------|---------|-------------| +| `--source ` | — | The Charter or AILOG that motivated the note (e.g. `CHARTER-04`), recorded alongside the date so the annotation stays attributable. | + +`Notes` is a single-line field by parser contract, so annotations **compose** onto the existing value rather than stacking as new bullets. + +```bash +$ straymark followups note FU-002 "Part-a shipped (size cap in the codec); part-b deferred." --source CHARTER-04 +✓ FU-002 annotated. + Notes: Extracted 2026-07-20. · [2026-07-26 · CHARTER-04] Part-a shipped (size cap in the codec); part-b deferred. +``` + +#### `straymark followups set-status [--path ]` *(cli-3.39.0+)* + +Change an entry's status **and** recompute the CLI-owned counters in the same step. This closes the desync window `recount` exists to clean up after (#355): the two-step it replaces — hand-edit the `Status` bullet, then remember `recount` — desyncs the moment you forget the second half, leaving the counters quietly lying about the backlog. + +Valid statuses: `open` · `in-progress` · `suspected-closed` · `closed` · `superseded`. A value outside that vocabulary is **refused**, not written — the parser is lenient, so a typo would not fail: it would silently drop the entry from every counter. `promoted` redirects to `followups promote`, which also writes the TDE that gives the status something to point at. + +```bash +$ straymark followups set-status FU-002 closed +✓ FU-002: open → closed + Counters: 2 open / 0 in-progress / 0 suspected-closed / 1 closed (was 3 / 0 / 0 / 0). +``` + +#### `straymark followups new --title --origin <origin> [--bucket <name>] [--status <s>] [--trigger <t>] [--destination <d>] [--cost <c>] [--premise <p>] [--path <dir>]` *(cli-3.39.0+)* + +Create an entry whose origin is a **Charter declaration** (ex-ante), before any execution exists (#360). Both older population paths assume an ex-post origin: `drift --apply` extracts from AILOGs, and a deferral decided *at declaration time* — "the Redis CI job is out of scope; register the coverage gap so it is deferred, not silenced" — precedes any AILOG by design. + +The hazard this closes is correctness, not ergonomics. Lacking a creation verb, the reporting adopter forward-referenced `FU-011` in the Charter body with nothing reserving it; since ids are minted `max(existing) + 1` at extraction time, the next unrelated `drift --apply` would hand `FU-011` to a different entry and silently point the Charter's citations at the wrong follow-up. `new` assigns the id atomically and prints it, so by the time the Charter cites it the entry exists. + +| Flag | Default | Description | +|------|---------|-------------| +| `--title <title>` | *(required)* | Entry title — the first thing every later reader sees. | +| `--origin <origin>` | *(required)* | The document that decided the deferral, e.g. `"CHARTER-06 §Scope"`. Required because schema v1 requires an origin on every entry. | +| `--bucket <name>` | `charter-triggered` | One of the five canonical buckets. | +| `--status <s>` | `open` | Initial status. | +| `--trigger` / `--destination` / `--cost` | `TBD` | The usual entry fields; unset ones are written as `TBD`, matching the template's convention. | +| `--premise <p>` | — | The load-bearing assumption the entry rests on, so acting on it later is a seconds-long re-check. | + +The entry is written with `Origin-class: ex-ante-planning` and **no `Source-hash`**: there is no AILOG to hash, and inventing one would make a later `drift --apply` believe it had already extracted something it never saw. + +```bash +$ straymark followups new --title "Redis CI job deferred" --origin "CHARTER-06 §Scope" --cost S +✓ FU-012 created in bucket `charter-triggered` (open). + Counters: 3 open / 0 in-progress / 0 suspected-closed (total 3). + + Next: the id is assigned and written — cite FU-012 in the Charter body now, not a reserved guess. +``` + +> **All three refuse to write a registry with parse warnings.** A surgical edit against a structure the parser mis-read can corrupt neighbouring entries, so a malformed entry must be fixed first. `recount` remains the escape hatch for a bulk manual-triage session — and the idempotent check that these verbs got the arithmetic right. + --- ### `straymark compliance [path] [--standard <name>] [--region <name>] [--all] [--output <format>]` diff --git a/docs/i18n/es/README.md b/docs/i18n/es/README.md index 6300642d..1cb6c2d0 100644 --- a/docs/i18n/es/README.md +++ b/docs/i18n/es/README.md @@ -240,8 +240,8 @@ StrayMark usa tags de versión independientes para cada componente: | Componente | Prefijo de tag | Ejemplo | Incluye | |------------|---------------|---------|---------| -| Framework | `fw-` | `fw-4.36.0` | Plantillas (12 tipos), gobernanza, directivas, plantilla + schema de Charter | -| CLI | `cli-` | `cli-3.38.0` | El binario `straymark` | +| Framework | `fw-` | `fw-4.37.0` | Plantillas (12 tipos), gobernanza, directivas, plantilla + schema de Charter | +| CLI | `cli-` | `cli-3.39.0` | El binario `straymark` | | Loom (EXPERIMENTAL) | `loom-` | `loom-0.4.2` | El servidor de visualización `straymark-loom`, descargado bajo demanda por `straymark loom serve` | Verifica las versiones instaladas con `straymark status` o `straymark about`. diff --git a/docs/i18n/es/adopters/CLI-REFERENCE.md b/docs/i18n/es/adopters/CLI-REFERENCE.md index bda8375b..eb65acc3 100644 --- a/docs/i18n/es/adopters/CLI-REFERENCE.md +++ b/docs/i18n/es/adopters/CLI-REFERENCE.md @@ -45,8 +45,8 @@ StrayMark usa **tags de versión independientes** para cada componente: | Componente | Prefijo de tag | Ejemplo | Qué incluye | |------------|---------------|---------|-------------| -| Framework | `fw-` | `fw-4.36.0` | Plantillas (12 tipos), docs de gobernanza, directivas | -| CLI | `cli-` | `cli-3.38.0` | El binario `straymark` | +| Framework | `fw-` | `fw-4.37.0` | Plantillas (12 tipos), docs de gobernanza, directivas | +| CLI | `cli-` | `cli-3.39.0` | El binario `straymark` | | Loom (EXPERIMENTAL) | `loom-` | `loom-0.4.2` | El servidor de visualización `straymark-loom`, descargado bajo demanda por `straymark loom serve` | Framework y CLI se publican de forma independiente. Una actualización del framework no requiere actualización del CLI, y viceversa. @@ -844,6 +844,32 @@ $ straymark followups verify FU-016 --premise "yrs tiene una referencia independ ✓ FU-016 verified — Verified-at → 2026-06-04. ``` +#### `straymark followups note <FU-NNN> "<texto>" [--source <ID>] [--path <dir>]` *(cli-3.39.0+)* + +Anexa una anotación fechada a los `Notes` de una entrada en una sola edición validada. Antes de este verbo (#355), registrar que una entrada recibió una mitigación *parcial* — sin cambiar su status — implicaba editar a mano un archivo parseado por el CLI: una edición que puede malformar la entrada y romper `list`/`status`/`drift`, sin nada que registre cuándo se hizo la nota ni qué la motivó. + +| Flag | Default | Descripción | +|------|---------|-------------| +| `--source <ID>` | — | El Charter o AILOG que motivó la nota (ej. `CHARTER-04`), registrado junto a la fecha para que la anotación siga siendo atribuible. | + +`Notes` es un campo de una sola línea por contrato del parser, así que las anotaciones se **componen** sobre el valor existente en vez de apilarse como bullets nuevos. + +#### `straymark followups set-status <FU-NNN> <status> [--path <dir>]` *(cli-3.39.0+)* + +Cambia el status de una entrada **y** recomputa los contadores CLI-owned en el mismo paso. Cierra la ventana de desincronización que `recount` existe para limpiar (#355): el dos-pasos que reemplaza — editar el bullet `Status` y luego acordarse de `recount` — se desincroniza en cuanto olvidas la segunda mitad, dejando a los contadores mintiendo en silencio sobre el backlog. + +Statuses válidos: `open` · `in-progress` · `suspected-closed` · `closed` · `superseded`. Un valor fuera de ese vocabulario se **rechaza**, no se escribe — el parser es indulgente, así que un typo no fallaría: sacaría la entrada de todos los contadores en silencio. `promoted` redirige a `followups promote`, que además escribe el TDE que le da a ese status algo a lo que apuntar. + +#### `straymark followups new --title <título> --origin <origen> [--bucket <name>] [--status <s>] [--trigger <t>] [--destination <d>] [--cost <c>] [--premise <p>] [--path <dir>]` *(cli-3.39.0+)* + +Crea una entrada cuyo origen es una **declaración de Charter** (ex-ante), antes de que exista ejecución alguna (#360). Las dos rutas de poblado anteriores asumen origen ex-post: `drift --apply` extrae de AILOGs, y un diferimiento decidido *en tiempo de declaración* — "el job de CI de Redis queda fuera de alcance; registra el hueco de cobertura para que quede diferido, no silenciado" — precede a cualquier AILOG por diseño. + +El riesgo que cierra es de corrección, no de ergonomía. A falta de un verbo de creación, el adoptante que lo reportó forward-referenció `FU-011` en el cuerpo del Charter sin nada que lo reservara; y como los ids se acuñan `max(existente) + 1` en tiempo de extracción, el siguiente `drift --apply` no relacionado le daría `FU-011` a otra entrada, apuntando en silencio las citas del Charter al follow-up equivocado. `new` asigna el id de forma atómica y lo imprime, así que cuando el Charter lo cita la entrada ya existe. + +La entrada se escribe con `Origin-class: ex-ante-planning` y **sin `Source-hash`**: no hay AILOG que hashear, e inventar uno haría que un `drift --apply` posterior creyera haber extraído algo que nunca vio. + +> **Los tres se niegan a escribir un registro con avisos de parseo.** Una edición quirúrgica contra una estructura mal leída puede corromper entradas vecinas, así que primero hay que arreglar la entrada malformada. `recount` sigue siendo la vía de escape para una sesión de triage manual masivo — y el chequeo idempotente de que estos verbos hicieron bien la aritmética. + --- ### `straymark compliance [path] [--standard <nombre>] [--region <nombre>] [--all] [--output <formato>]` diff --git a/docs/i18n/zh-CN/README.md b/docs/i18n/zh-CN/README.md index a988ea10..dfb56d45 100644 --- a/docs/i18n/zh-CN/README.md +++ b/docs/i18n/zh-CN/README.md @@ -258,8 +258,8 @@ StrayMark 为每个组件使用独立的版本标签: | 组件 | 标签前缀 | 示例 | 包含内容 | |------|----------|------|----------| -| Framework | `fw-` | `fw-4.36.0` | 模板(12 种类型)、治理文档、指令、Charter 模板 + schema | -| CLI | `cli-` | `cli-3.38.0` | `straymark` 二进制文件 | +| Framework | `fw-` | `fw-4.37.0` | 模板(12 种类型)、治理文档、指令、Charter 模板 + schema | +| CLI | `cli-` | `cli-3.39.0` | `straymark` 二进制文件 | | Loom(实验性) | `loom-` | `loom-0.4.2` | `straymark-loom` 可视化服务器,由 `straymark loom serve` 按需下载 | 使用 `straymark status` 或 `straymark about` 查看已安装的版本。 diff --git a/docs/i18n/zh-CN/adopters/CLI-REFERENCE.md b/docs/i18n/zh-CN/adopters/CLI-REFERENCE.md index a6522857..b7b9fafe 100644 --- a/docs/i18n/zh-CN/adopters/CLI-REFERENCE.md +++ b/docs/i18n/zh-CN/adopters/CLI-REFERENCE.md @@ -45,8 +45,8 @@ StrayMark 为每个组件使用**独立的版本标签**: | 组件 | 标签前缀 | 示例 | 包含内容 | |------|----------|------|----------| -| Framework | `fw-` | `fw-4.36.0` | 模板(12 种类型)、治理文档、指令 | -| CLI | `cli-` | `cli-3.38.0` | `straymark` 二进制文件 | +| Framework | `fw-` | `fw-4.37.0` | 模板(12 种类型)、治理文档、指令 | +| CLI | `cli-` | `cli-3.39.0` | `straymark` 二进制文件 | | Loom(实验性) | `loom-` | `loom-0.4.2` | `straymark-loom` 可视化服务器,由 `straymark loom serve` 按需下载 | Framework 和 CLI 独立发布。Framework 更新不需要 CLI 更新,反之亦然。 @@ -887,6 +887,32 @@ $ straymark followups verify FU-016 --premise "yrs 有一个独立的参照物(Y ✓ FU-016 verified — Verified-at → 2026-06-04. ``` +#### `straymark followups note <FU-NNN> "<文本>" [--source <ID>] [--path <dir>]` *(cli-3.39.0+)* + +以一次经过校验的编辑,向条目的 `Notes` 追加一条带日期的注记。在此动词出现之前(#355),记录某条目收到了**部分**缓解措施 —— 而不改变其状态 —— 意味着手工编辑一个由 CLI 解析的文件:这种编辑可能让条目变形并破坏 `list`/`status`/`drift`,而且没有任何东西记录该注记是何时写的、由什么促成。 + +| Flag | 默认 | 说明 | +|------|------|------| +| `--source <ID>` | — | 促成该注记的 Charter 或 AILOG(如 `CHARTER-04`),与日期一并记录,使注记保持可归因。 | + +按解析器契约,`Notes` 是单行字段,因此注记是在既有值上**组合**,而不是堆叠为新的 bullet。 + +#### `straymark followups set-status <FU-NNN> <status> [--path <dir>]` *(cli-3.39.0+)* + +修改条目状态**并**在同一步骤重算 CLI 拥有的计数器。这关闭了 `recount` 本来用于事后清理的失同步窗口(#355):它所取代的两步流程 —— 手工编辑 `Status` bullet,再记得运行 `recount` —— 一旦忘了后半步就会失同步,让计数器悄悄谎报 backlog。 + +有效状态:`open` · `in-progress` · `suspected-closed` · `closed` · `superseded`。词表之外的取值会被**拒绝**而非写入 —— 解析器是宽容的,所以拼写错误不会失败:它会悄悄把该条目从所有计数器中剔除。`promoted` 会重定向到 `followups promote`,后者还会写出让该状态有所指的 TDE。 + +#### `straymark followups new --title <标题> --origin <来源> [--bucket <name>] [--status <s>] [--trigger <t>] [--destination <d>] [--cost <c>] [--premise <p>] [--path <dir>]` *(cli-3.39.0+)* + +创建来源为**Charter 声明**(事前)的条目,此时尚不存在任何执行(#360)。此前两条填充路径都假定来源是事后的:`drift --apply` 从 AILOG 提取,而在**声明时刻**做出的推迟 —— "Redis 的 CI job 不在范围内;登记这个覆盖缺口,使其被推迟而非被消音" —— 按设计先于任何 AILOG。 + +它关闭的是正确性风险,而非人机工程问题。由于缺少创建动词,提交该报告的采用方在 Charter 正文中前向引用了 `FU-011`,却没有任何东西为其预留;而 id 是在提取时按 `max(既有) + 1` 铸造的,于是下一次无关的 `drift --apply` 会把 `FU-011` 分配给别的条目,悄悄让 Charter 的引用指向错误的 follow-up。`new` 原子地分配并打印 id,因此当 Charter 引用它时该条目已经存在。 + +该条目写入时带 `Origin-class: ex-ante-planning` 且**不带 `Source-hash`**:没有 AILOG 可供哈希,而编造一个会让后续的 `drift --apply` 以为自己已经提取过它从未见过的东西。 + +> **三者都拒绝写入存在解析告警的注册表。** 针对被解析器误读的结构做手术式编辑可能损坏相邻条目,因此必须先修好变形的条目。`recount` 仍是批量手工 triage 的逃生通道 —— 也是这些动词算术是否正确的幂等校验。 + --- ### `straymark compliance [path] [--standard <name>] [--region <name>] [--all] [--output <format>]`