From harness-anchor
Manages a project's feature state trio (feature_list.json, progress.md, session-handoff.md) for tracking feature lifecycle from start to completion.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-anchor:feature-state-keeperThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You maintain the **machine-readable scope record** of this project. Three files form the state trio:
You maintain the machine-readable scope record of this project. Three files form the state trio:
| File | Role |
|---|---|
feature_list.json | Scope boundary + done criteria + evidence |
progress.md | Append-only session history |
session-handoff.md | Single-page "what's the state right now" |
When superpowers is also active, two record systems coexist by altitude — keep them from drifting:
| Record | Owner | Altitude |
|---|---|---|
feature_list.json | harness-anchor (this skill) | project feature ledger — one entry per feature, status + evidence; the durable source of truth for "is feature X done" |
progress.md | harness-anchor | session-level history (summaries), append-only |
docs/superpowers/plans/*.md (- [ ] steps) + TodoWrite | superpowers (writing-plans / subagent-driven-development) | execution detail for the one in-progress feature — ephemeral scaffolding for the current build |
Sync contract (prevents double-bookkeeping and drift):
feature_list.json or progress.md.status in feature_list.json with the evidence object. That single write is the durable record it's done.feature_list.json answers "what features exist / which is active / is it done"; the plan-doc + TodoWrite answer "what are the steps to build the active one." If they disagree, feature_list.json (with evidence) wins.superpowers:dispatching-parallel-agents or subagent-driven-development, the dispatched workers treat this trio — above all feature_list.json — as shared state (the dispatching skill's own rule: don't fan out over shared resources). Workers must not each write the trio; the coordinating parent reconciles once after they return — the same reflect-back-once write as item 2. Because workers are single-level, they also don't run the subagent-backed gates (/verify · /test-plan · /gc) — the parent runs those after the workers integrate, then flips the one ledger entry. Parallel work is normally within one feature (independent sub-tasks), so it is exactly the plan/Todo case above.feature_list.json firstscope-jump flow belowsession-handoff.md → what was just being done
feature_list.json → what's planned, in-progress, blocked
progress.md → recent history (only if needed for deep context)
Most sessions only need handoff + feature_list. Skip progress.md unless you genuinely need the longer view.
Cold history (archives). Once /session-end's budget step has archived, older content
lives in progress-archive.md and feature_archive.json (moved verbatim by
state-archive.mjs; hot windows: newest 20 progress sections; live features + the 10 most
recently completed pass entries). History questions → grep the archives; never load
them whole. They are read-only history: never hand-edit them, never reuse an archived id.
One retired signal to know about: the PostToolUse regression-warn matches evidence artifacts
against the hot ledger only, so an archived feature's file-specific warning retires with
it — the generic "source changed after pass" nudge continues via the retained pass entries.
Exactly one feature has status: "in-progress" at a time unless the project has an explicit multi-track plan documented in AGENTS.md.
If you discover work that doesn't match the active feature:
feature_list.json with status: "planned" and a descriptionAction-layer backstop: the post-tool-use hook warns the moment a new code module is created
while a feature is in-progress — so "record new scope as planned first" is enforced at write-time,
not only when the new work happens to be phrased in a prompt. Warn-only: it surfaces the new module
for you to either confirm in-scope or record as planned; it never blocks.
out_of_scope (optional)A feature may declare what it deliberately does NOT cover:
"out_of_scope": ["UI redesign", "performance tuning"]
Set it at feature creation when the user draws the boundary. Consumers: the PostToolUse pulse checkpoint quotes it periodically, so agent-initiated drift is checked against an explicit list instead of a guess, and scope-jump conversations can cite it. Omit the field when no exclusion is worth naming.
id is the lookup/mutation key — /verify flips the feature to pass and attaches evidence
by id, and the hooks find the active feature by it. Two features sharing an id make those updates
ambiguous (wrong entry, or both). The JSON Schema cannot catch this (draft-07 has no per-field
uniqueness), so it is on you to keep ids unique.
Mint the id with the tool BEFORE you write the new entry — don't eyeball the list (scanning a long id list for a clash is unreliable and splits your attention from composing the feature):
node ${CLAUDE_PLUGIN_ROOT}/scripts/feature-list-validate.mjs --check <candidate-id> feature_list.json
(exit 0 = free; non-zero = taken, and it prints a free suggestion). The tool treats ids in
feature_archive.json as taken too — archived ids stay reserved because history (progress
entries, evidence, commits) references them. If node is unavailable, fall back to scanning
the existing ids yourself (both files).parser-2) or a
descriptive variant (parser → parser-cli, parser-net). Never reuse a slug; never rename an
existing id to dodge the clash.Backstops if one slips through: the post-tool-use hook warns the moment a duplicate is written,
and /session-end runs feature-list-validate.mjs before committing. Both are warn-only — they
catch, they don't fix. Resolve a flagged duplicate by renaming the newer entry, as above.
Before flipping status to "pass":
| Criterion | Evidence requirement |
|---|---|
| Build passes | Build log path (e.g. .build/last-build.log) + exit code 0 |
| Type check passes | Command output captured |
| Tests pass | Test runner output showing N passed, 0 failed |
| Static analysis | Lint report file or empty-warnings confirmation |
Write evidence to feature_list.json:
{
"id": "...",
"status": "pass",
"evidence": {
"timestamp": "2026-05-28T13:00:00Z",
"commit": "abc123...",
"artifacts": [
".build/last-build.log",
"test-results/run-42.txt",
"lint-report.txt"
],
"notes": "Optional human-readable summary"
},
"completedAt": "2026-05-28T13:00:00Z"
}
If any criterion lacks evidence: do NOT mark pass. State explicitly:
"I am uncertain whether is fully done because lacks evidence. Recommend running
<command>to verify."
This is the calibrated-uncertainty pattern. Bluffing breaks user trust.
At every /session-end — and at mid-session milestones (a status flip, a verified chunk):
prepending a short entry now beats reconstructing the session at its end from a compacted
context. The template header carries HH:MM, so several entries per session are fine:
node ${CLAUDE_PLUGIN_ROOT}/scripts/progress-prepend.mjs progress.md <entry-file> — which inserts after the header without loading the whole file (cheap on a long history). Fallback: Read + prepend + Write./session-end budget step may move the
oldest sections to progress-archive.md — verbatim relocation by state-archive.mjs,
never deletion.This file is overwritten at end of session — it should reflect current state only.
Required fields:
Aim for ≤ 300 words. The next session should be able to resume from this alone.
feature_list.json validates against feature_list.schema.json (JSON Schema draft-07). When you edit feature_list.json:
project, features (array)id, name, description, status, done_criteriaid is kebab-case, unique across all features, and never renamed once set (see "Feature id uniqueness" below)status ∈ planned | in-progress | pass | blockedevidence MUST be non-null when status == "pass" (Default-FAIL invariant)If the schema file is present, an external validator (e.g. ajv-cli) can verify. The agent need not run it — write valid JSON the first time by following the shape above.
Features are kept actionable-first (in-progress → blocked → planned → pass) by
scripts/feature-list-sort.mjs, run at /session-end. Don't hand-reorder the array — let the
tool do it deterministically (it only reorders; ids, evidence, and unknown fields are preserved).
This keeps what you most need at the top, so reading the head of a long feature_list.json suffices.
For non-trivial schema constructs (allOf, oneOf, if/then, regex patterns) — invoke the docs-lookup skill. Don't guess schema syntax; feature_list.schema.json validation must stay correct, and a malformed schema silently disables enforcement.
Typical entry query: json schema <construct> (e.g. json schema if then else).
anti-hallucination-gates skill/session-end commandproject-indexing skill/status commandEach feature status flip (planned → in-progress → pass / blocked) should be its own commit:
feature_list.json to flip a feature's status, commit immediately with a message like feat(engine-init): start or feat(engine-init): mark pass.feature_list.json + progress.md (if updated). Code changes go in their own commits. (Mirrors CLAUDE.md: "never bundle unrelated layers in one commit".)requesting-code-review and finishing-a-development-branch skills for branching, PRs, and merge strategies.docs-lookup skill — don't guess flags or syntax.npx claudepluginhub redtropig/harness-anchor --plugin harness-anchorTrack feature status, decisions, risks, and changes across AI coding sessions with lightweight Markdown files. Useful for long-lived features and session handoffs.
Creates and maintains a strict JSON feature ledger where each entry tracks E2E verification status. Agents read it to pick work and flip exactly one `passes` field per session.
Orchestrates multi-session projects by implementing one feature per cycle from feature-list.json through TDD pipeline with quality gates and code review.