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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/memory/pipeline/change-lifecycle.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
type: memory
description: "Change naming, folder structure, `.status.yaml` (6-stage pipeline + `plan:` block, flock-serialized writes + fsync durability + sparse-key insertion), `.fab-status.yaml` symlink (atomic temp+rename swap, dangling-target validation), `stage_hooks` pre/post commands around start/finish (documented in c5tr), git integration (bookkeeping decoupled; ship-time branch↔change hard guard since w7dp), `/fab-status` (six-glyph progress legend incl. `⏭` skipped), `/fab-switch` (six-state `{state}` qualifier), backlog scanning, optional `summary` log field (FKF C-lite source, 5943) + the generated FKF memory-artifact surface (`fab memory-index` emits per-folder `log.md` + `fkf_version`/`type: memory` frontmatter, bmzo)"
description: "Change naming, folder structure, `.status.yaml` (6-stage pipeline + `plan:` block, flock-serialized writes + fsync durability + sparse-key insertion), `.fab-status.yaml` symlink (atomic temp+rename swap, dangling-target validation), `stage_hooks` pre/post commands around start/finish (documented in c5tr), git integration (bookkeeping decoupled; ship-time branch↔change hard guard since w7dp), `/fab-status` (six-glyph progress legend incl. `⏭` skipped), `/fab-switch` (six-state `{state}` qualifier), backlog scanning, optional `summary` log field (FKF C-lite source, 5943) + the generated FKF memory-artifact surface (`fab memory-index` emits per-folder `log.md` + `fkf_version`/`type: memory` frontmatter, bmzo); adoption (`/fab-adopt`) as an alternate pipeline entry-point with the honest `apply: skipped` state (composed from existing skip/reset transitions, t54n)"
---
# Change Lifecycle

Expand Down Expand Up @@ -151,6 +151,12 @@ The stages split into four phases:
- **Hydration** (4): hydrate (hydrates into memory files)
- **Integration** (5-6): ship (PR creation via `/git-pr`), review-pr (PR review feedback via `/git-pr-review`)

### Adoption — an Alternate Entry-Point with `apply` Skipped (t54n)

The pipeline normally starts at intake. **`/fab-adopt`** (see [execution-skills.md](/pipeline/execution-skills.md) § `/fab-adopt`) is an **alternate entry-point** for a *completed* change authored **off-pipeline** — a feature branch with an OPEN (or not-yet-created) PR whose code was written without fab (scenario B; a MERGED PR is the out-of-scope scenario A and STOPs). Of the six stages, exactly one — **apply** — cannot meaningfully re-run on an adopted change (the code already exists; there is nothing to generate). Every other stage *can* run for real, just *late*: intake reconstructed from the diff, review run on the diff before merge, hydrate writing memory before merge, ship retrofitting the PR `## Meta`, review-pr resuming normally. Adoption is therefore **the real pipeline entered late, with apply skipped** — not a parallel "fake pipeline."

**`apply: skipped` honest-state pattern.** The defining stance is *honest state*: only the stage that genuinely cannot run is marked `skipped`; the rest are marked truthfully and actually execute. `/fab-adopt` reaches the end-state `apply: skipped`, `review: active`, `hydrate/ship/review-pr: pending` by composing the **existing** `skip` and `reset` transitions — `fab status skip {name} apply` (forward-cascades all downstream to `skipped`) then `fab status reset {name} review fab-adopt` (re-activates review `skipped→active` and cascades its downstream back to `pending`); no new Go transition is added (see [schemas.md](/pipeline/schemas.md) § Adoption's `apply=skipped, review=active` is a transition COMPOSITION). The off-pipeline fact is recorded once via `fab status set-summary {name} "adopted off-pipeline change; apply skipped"`. This honors Constitution II — `docs/memory/` finally reflects the off-pipeline change at hydrate (the permanent-loss recovery the bypass skipped), and the change becomes visible to the generated `log.md` history via its `summary:`. A `skipped` apply also *displays* honestly via the `⏭` glyph (`/fab-status`, `/fab-switch`, `fab change list`). Adoption adds **no new state** to the vocabulary — `skipped` already existed; it is simply the first pipeline path that marks `apply` skipped as a deliberate, supported entry rather than an edge case.

After hydrate completes, the change folder remains in `fab/changes/`. To move it to the archive, run `/fab-archive` — a standalone housekeeping command (not a pipeline stage). `/fab-archive` moves the folder to `fab/changes/archive/yyyy/mm/` (date-bucketed by the folder's `YYMMDD` prefix), updates the archive index, marks backlog items done (exact-ID check always; keyword scan with interactive confirmation), and conditionally removes `.fab-status.yaml` if the archived change was active. Whether it was active is captured by reading the symlink directly (exact folder match) **before** the folder rename (mz4q — a prerequisite of resolve's dangling-target validation, which would otherwise never see the post-rename pointer as the archived change); `pointer: cleared` is reported only when the symlink actually pointed at the archived change, `pointer: skipped` otherwise — including when no symlink exists, where the old post-rename resolution path could have single-change-guessed the answer. To restore an archived change, run `/fab-archive restore <change-name>` — this moves the folder back to `fab/changes/`, removes the index entry, and optionally activates via `--switch`. Existing flat archive entries are migrated to the date-bucketed structure via `/fab-setup migrations` (see `$(fab kit-path)/migrations/0.24.0-to-0.32.0.md`).

**Archive CLI exit semantics (k4ge)**: `fab change archive` requires exactly one argument (`cobra.ExactArgs(1)` — zero args is a non-zero usage error, no longer exit-0 help text). Re-archiving an already-archived change is an idempotent soft skip — `already archived: {change}` on stdout, exit 0 — including the genuinely-archived case where the source folder is gone from `fab/changes/` (an archive-scan fallback in `Archive()`; see [execution-skills.md](/pipeline/execution-skills.md)). An argument matching no change anywhere (active or archived) still propagates the resolution error non-zero, and ambiguous archive matches do not soft-skip. Partial failure (the move succeeded but the backlog mark failed) prints the YAML report and exits non-zero — re-running soft-skips. **`fab batch archive` confirmation/preview model (753q)**: archive is the one bulk-mutating member of the batch family whose moves are effectively irreversible within the loop, so it diverges from `batch new`/`batch switch` (which stay list-by-default behind `--all`) to a list-then-confirm model with a `--yes` escape hatch (the apt/npm/gh pattern; this replaced the 260612-ye8r explicit-`--all` model). A bare `fab batch archive` on an interactive stdin lists the archivable set then prompts `Archive these N? [y/N]` with **default No** — a bare Enter or any non-`y`/`yes` answer aborts (exit 0, nothing archived). `--yes`/`-y` archives all archivable changes with no prompt (the non-interactive escape hatch; resolved behavior of the former `--all`). `--dry-run` lists what would be archived with no prompt and no action (the former `--list`). A non-TTY stdin without `--yes` refuses rather than hangs — the handler returns a single multi-line error (it does not print its own `ERROR:` lines) so `main()`'s centralized printer emits it once as `ERROR: refusing to prompt for confirmation on a non-interactive stdin.` followed by `Re-run with --yes to archive non-interactively` on stderr, exit non-zero (the tmux/operator runtime is non-interactive, so those call sites pass `--yes`). Explicit args (`fab batch archive foo bar`) archive the named changes with no prompt and no TTY guard (naming them is the opt-in). `--dry-run --yes` is mutually exclusive → exit non-zero. The empty archivable set stays a benign no-op (`No archivable changes found.` + `Archived 0, skipped 0, failed 0.` footer, exit 0), checked **before** any prompt or non-TTY guard so finding F49 is preserved; explicitly named targets that are genuinely archived route to the loop's `already archived, skipping` path (counted skipped, exit 0); the explicit-args path where nothing resolves anywhere keeps exit 1 (`No valid changes to archive.`).
Expand Down
Loading
Loading