archive
暂无简介
- License 许可
- MIT
- 所属仓库
- markosAMO/Alfred
- 最近更新
- 2026-10-13
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- mode
- auto
- name
- archive
- next
- []
- skippable
- false
03 SKILL.md 正文
archive
Close the change: fold what was learned into the places that outlive it, then commit.
This is the phase that keeps the documentation from going stale, because updating it is a step in the work rather than something to remember afterwards.
Preconditions
verify passed
review no blocking findings
Neither is inferred from the absence of an error. Both reports are read and their results checked. A change archived without them merges a delta describing behaviour nobody confirmed exists.
Final state, not the last snapshot
The reports this phase reads are snapshots. verify ran at a moment; commits may have
landed since. Archiving the snapshot as if it were current records a state that stopped
being true.
Rank the sources before writing anything:
1 the repository now: the code, the tests, what passes today
2 facts the orchestrator carries about work done after the reports
3 the verify and review artifacts
4 the original request
When a higher source says resolved and a lower one says open, report the final state and cite where it was resolved — the commit, the later run. Do not repeat the stale claim.
When two sources contradict and neither outranks the other, record the contradiction: both statements, where each came from, and when each was written. Never resolve it silently in either direction. A contradiction written down is a question someone can answer; one resolved by guessing is an error nobody can find.
Attribute what comes from a snapshot, rather than restating it as a present fact.
per the verify artifact, at verification time: 47 tests passing
Carry final numbers — test counts, warnings, uncovered lines — from the highest source that covers them. Numbers copied from the verify artifact after later work changed them are wrong in a document that outlives the change.
Distinct failures stay distinct
Never merge two defects into one causal story because they appeared together. A cause is recorded as confirmed only with evidence; otherwise the failure is recorded as undiagnosed.
An invented causal link is worse than an admitted gap: diagnose will recall it as a
precedent, and the next investigation starts from a conclusion nobody proved.
Features: merging the delta
The delta in .alfred/changes/{change}/spec.md is folded into paths.master_specs.
ADDED the requirement is appended to its specification
MODIFIED the existing requirement is replaced, and the Previously line is dropped
REMOVED the requirement is deleted
The master specification describes current behaviour, in the present tense, with no history of how it got there. Git holds the history, and a specification carrying its own changelog becomes unreadable by the fifth change.
The change directory stays, and the configuration decides what is in it.
keep every phase's artifact, plus the record
ephemeral the record and the delta spec
pointer the address file, and the record
Either way it is what a reader follows back from a master specification to understand why a
requirement says what it says. Under ephemeral that path is shorter and it is the record
that answers, which is the point of writing one.
The record
Every change writes one, in every mode. It is the document the change leaves behind, and
under artifacts.retain: final_only it is the only new one.
Written from templates/docs/record.md to the locator the orchestrator passed, which
resolves under paths.change_records.
.alfred/changes/login-google/record.md
It is written for someone arriving from a master specification months later with no other context: what the system does now, why, how it was decided, what was required, and how it ended. Not a list of phases that ran, and not a list of keys.
How it was decided is the section that pays. It carries the approach that was taken
and the alternatives that were rejected, with the reason each was rejected, from the design.
Under final_only the design document is about to be deleted, and this section is the only
part of it that survives. Without it the next change reopens a decision that was already
settled, which is the cost the whole mode exists to avoid paying.
Result reports the state at close, not the state when verify and review wrote their
reports. Work continues after a report is written — a finding gets fixed in a later commit,
a blocked task gets finished — and a record that repeats a stale report sends the next reader
to redo finished work, or to trust that something is pending when it closed. Where the two
disagree and neither can be ranked, both are recorded with their source and time, never
resolved silently.
A change closed incomplete says so here. archive may close work that did not finish when
the user asks for it explicitly; it may never describe that work as finished.
Removing the working documents
Under artifacts.retain: final_only, the working documents are removed once the record
exists.
removed every phase's artifact of the change, {phase}.md, and inputs/
kept record.md
spec.md, under artifacts.keep_delta_spec
paths.master_specs, architecture.md, code_conventions.md
They are removed from the working tree before staging, so they never enter the commit and the closing diff shows the record, the delta, the merged specifications and the code.
A document an earlier pass committed is tracked, and its removal is staged the same way the state file's is. A change that ran straight through never committed them, and they are simply deleted.
This is the part to be honest about: with a memory backend configured, the documents were
indexed as each phase completed and memory is the copy. With backend: none there is no
copy, and they are gone. That is what the mode means, and it is the right trade only because
what a later reader needs was lifted into the record first. init says so when the mode is
chosen against no backend, and alfred doctor repeats it.
Order matters and is not negotiable:
1 merge the delta into paths.master_specs
2 write the record
3 read the record back and check it is not empty and carries How it was decided
4 only then remove the working documents
Step 3 is the whole safety of the operation, and it is the same check reindex makes before
deleting under pointer. A record that was written empty, or written without the decisions,
is indistinguishable from a correct one right up to the moment the inputs are gone. A failed
read-back stops the phase, deletes nothing, and names the record.
retain: all skips this section entirely and is the behaviour of every earlier version.
Removing the state file
State exists so continue can resume. A change that closed has nothing to resume.
Under artifacts.state_on_completion: delete, this phase removes
.alfred/state/{change}.yaml.
Whether that removal is staged depends on whether the file was ever committed. A change
that ran and closed in one pass never committed its state, so the file is untracked and
deleting it is the whole operation. A change that was interrupted and resumed — the case
the file exists for — was committed by an earlier pass, and the removal has to be staged or
the file returns on the next checkout and continue finds a change that finished months
ago.
tracked git rm, staged with the closing commit
untracked delete it
The phase checks which it is rather than assuming: git rm on an untracked file fails the
commit, and a plain delete on a tracked one leaves the repository asserting the change is
still open.
It is removed only when the change actually closed:
closed complete removed
closed incomplete kept, and the record says what was left open
verify failed this phase never ran, so it is kept
review blocking same
abandoned nothing closed it, so it is kept
The second row is the one that looks inconsistent and is not. Open work is work somebody may
come back to, and a record reading "three tasks unfinished" beside no state leaves nothing
able to resume them. See skills/_shared/state-contract.md.
Closing the address file
Under pointer, every phase before this one appended a row to
.alfred/changes/{change}/README.md as it completed. This phase closes it, from
templates/docs/addresses.md: the remaining rows, and the postmortem key for a bug.
It is not the description of the change — the record is, and it sits beside this file in every mode. The address file is the index that makes the entries reachable, and it stops where the record starts. The outcome, the commit and the specifications that changed are written once, in the record, because two documents carrying the same outcome go out of step at the first correction with nothing to say which is current.
Both are staged together, for the reason the state file is: an index whose record is missing
describes documents nobody can place, and a record with no index under pointer names
nothing that can be fetched.
Bugs: the postmortem
A bug writes a postmortem to memory rather than to the specifications.
remember(
key: alfred/postmortem/{slug}
type: postmortem
title: what failed, in the terms someone would search for
)
**Symptom**: what was observed
**Root cause**: what actually caused it
**Fix**: what changed
**Why it was not caught**: the gap that let it through
Why it was not caught is the field that pays. The others explain one bug; this one names
the missing test, the unwritten scenario or the wrong assumption that will let the next one
through.
diagnose recalls these before investigating, so a class of failure is diagnosed once.
A bug whose fix changed observable behaviour also carries a delta, and that delta is merged as above.
Committing
Per git in the configuration:
stage the files reported by apply
stage the record, the delta spec and the merged master specifications
stage the removal of the state file, when the change closed complete
one commit for the whole change
conventional commit message
no AI attribution of any kind
push
One commit, still. The working documents were already removed from the working tree, before staging, so there is nothing to un-stage and no second commit to tidy up after the first.
A removal is staged explicitly. git.stage: files_changed_by_alfred names files Alfred
wrote, and a file Alfred deleted is not one of them: left unstaged, the state file is
committed as though it still existed and comes back on the next checkout.
Staging everything present would sweep in whatever the user left in progress. The file list
comes from the subagent reports, which is why apply requires it.
State the scope as what is excluded, never as what is included. "Stage the source and the tests" is read literally by something that has no way to know a locale file, a migration or a configuration key was part of the work — observed twice in one run, both times leaving the change incomplete in the commit.
everything the change needs, excluding .alfred/ and .alfred/changes/
That exclusion scopes the work apply produced, which is code. Alfred's own documents are
not swept in by it: the record, the delta spec and the merged specifications are staged by
name, on the line above, and the state file's removal with them. Naming them is what keeps
the two rules from cancelling each other — an exclusion broad enough to keep the working
documents out of the commit is broad enough to keep the record out too.
Compare before committing. What git reports as changed is compared against the union of
every files_changed from apply, and any file in one and not the other is reported:
changed and not reported apply touched a file and did not say so
reported and not changed a task reported work it did not do
Neither is fatal and neither is resolved silently. A file changed and not reported is the
collision apply is built to detect, and the same difference is what worktree.sh close
refuses on afterwards — by then the commit already exists and the fix is another commit.
Compared here, it is a question asked while the answer is still cheap.
Alfred's own documents are outside that comparison. apply never wrote them, so every one
of them is "changed and not reported" by construction, and including them would report the
record as a collision on every change.
State is staged with the change — its removal when the change closed complete, its current
contents otherwise, so a collaborator who clones mid-change can continue. Under pointer
the address file is staged with it: they answer the same question from two
sides and neither is useful alone, per skills/_shared/phase-protocol.md. The skill
registry is not: it is ignored, per skills/_shared/skill-resolver.md.
feat(auth): sign in with Google
Implements .alfred/changes/login-google/record.md.
3 requirements, 7 scenarios, 47 tests.
The record does not carry its own commit hash
It cannot. A commit's hash covers the tree it contains, so a file inside that tree can never state the hash — writing it in changes it, and amending to correct it changes it again.
Git already answers the question from both sides, and neither needs a hash in the document:
which commit closed this change git log --oneline -1 -- <the record>
which record does this commit close the commit message names it
The record's commit: field therefore carries the commit's subject line, which is decided
before the hash exists and is stable under amend. A phase that finds itself amending to
write a hash into the tree that produced it has hit the regress, not a step it missed.
The message names the record rather than the directory. Under final_only the directory
holds the record and the delta and nothing else, and a message pointing at a directory that
used to hold eight documents reads as though seven went missing.
Changes in their own worktree
When state carries a worktree, this phase runs inside it, and the commit lands on
branch.
The delta is merged inside the worktree, into the copy of paths.master_specs that is
on this branch, and it goes into this change's commit like every other file. Not into the
main checkout.
Writing to the main checkout from a worktree is wrong three times over. The merged requirement is not in the commit, so the branch describes behaviour its own specification does not mention. Two changes archiving close together write the same file with no branch between them and one silently wins. And the main checkout ends up dirty, which is the state the worktree layout exists to prevent, discovered later by whoever works there next.
Merged on the branch, two changes that touch the same requirement produce a merge conflict when the branches meet, which is a question git puts to a person rather than a loss nobody sees.
Under artifacts.committed: false there is no commit to put them in. The delta is still
merged in the worktree, and then this phase copies paths.master_specs, paths.changes
and paths.change_records back to the main checkout before any close — they are untracked,
so the worktree is the only place holding them and close discards untracked files.
worktree.sh close refuses while the main checkout is missing any of them, and names what
it found. See docs/artifacts.md.
paths.change_records is in that list even though it usually resolves inside
paths.changes, because it does not have to. Pointed somewhere else, the record is the one
document of the change and it would be the one thing close discarded.
This is also where retain: final_only and committed: false have to be done in the right
order. The working documents are removed first, in the worktree, and only what remains is
copied back. Copying first and deleting afterwards would carry eight documents into the main
checkout to delete seven of them there, and a close racing that deletion refuses on files
the main checkout has and the worktree no longer does.
After the push:
git.worktrees.pull_request: true open a pull request from branch to base, if the
tool for it is available; report and continue if not
git.worktrees.remove_on_archive run worktree.sh close --branch <branch> --cwd <main_checkout>
close refuses a worktree with uncommitted changes, which after a correct commit means a
file apply did not report. Stop and say which files, rather than forcing: a file changed
and not committed is the collision apply is designed to detect. The branch is deleted
only when already merged into base; otherwise it stays for the pull request. Run close
from main_checkout, never from inside the worktree being removed.
The pull request is off by default because opening one is a decision about the team's review flow, and because it needs a tool that is not always logged in. A failed attempt is reported in the completion line, never treated as a failed archive.
Memory conflicts
Every conflict a phase recorded during the change, per memory/CONTRACT.md, is collected
here into one list: what disagreed, which key, and which phase raised it.
Settle the ones that can be settled. Every phase is given the backend's full tool list,
so where the backend offers a judgment call this phase makes it, per
memory/adapters/engram.md. What reaches the user is what judgment could not decide.
This is the step that used to be skipped for the wrong reason. A phase without the tool reads exactly like a backend that cannot settle anything, so a run recorded six open conflicts, then twenty, and reported them as if that were the contract working. It was a tool list written before the tool existed.
What cannot be settled is reported, not guessed: what disagreed, which key, which phase raised it. The user settles those in one pass, or leaves them.
Conflicts raised between the artifacts of the same change — a spec that "contests" its own design — are a similarity threshold wanting tuning, not a disagreement. Say so once, with the count, rather than listing each.
A list of conflicts never blocks an archive. What they are about is written down twice already.
Carrying findings forward
should fix and suggestion findings from review are written to memory against the area
they belong to, not discarded.
alfred/area/{area}/review-findings
Two phases read them, and the key exists because for a long time neither did: review
recalls them so it raises a repeat as a repeat, and apply recalls them so the one phase
that can fix them is holding the file when it hears about them.
Write the area, the finding, the change it came from and the date. A finding with no area cannot be recalled by the phase that needs it, which is the same failure as not writing it.
Workspaces
At repository level this phase completes that repository's part and commits it.
The workspace change is archived only once every repository in repos has passed verify
and review. Until then the workspace change stays open and status names the repository
holding it. See skills/_shared/workspace-protocol.md.
Completion
Follow skills/_shared/phase-protocol.md: write the merged specifications, index them,
update state to completed, notify feature_completed.
archived: 3 requirements merged, committed as feat(auth): sign in with Google