spec
Skill173 starsChanged 13 days agoMITView on GitHub
Write requirement specs from feature requests
168 other projects use a version of this.
Adopted by
Most recently taken up by
- thistleknot/skillstheir own version, first seen Nov 8, 2026
- varelaia/claude-skills-publictheir own version, first seen Nov 8, 2026
- mrlemoos/galiciatheir own version, first seen Nov 8, 2026
- PeanutGT/NutsSkillstheir own version, first seen Nov 8, 2026
- standardagents/skillstheir own version, first seen Nov 8, 2026
- Surething-io/cockpittheir own version, first seen Nov 8, 2026
Used with
Projects with it also use backprop, ios-clean and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
JuliusBrusseeSkillbackprop
Bug → spec protocol. When a bug is found or a test fails, trace the cause, decide whether a new §V invariant would catch recurrence, append to §B. This is the one non-obvious thing SDD does that plan-then-execute doesn't. Triggers on test failure, bug report, post-mortem, or explicit user ask.
Also in 4 projects that use this
Saqlain-811Skillios-clean
Remove the DebugBridge SPM package and all #if DEBUG wiring from an iOS app. (gstack)
Also in 5 projects that use this
Saqlain-811Skillios-fix
Autonomous iOS bug fixer. (gstack)
Also in 5 projects that use this
bastosSkillios-design-review
Review iOS app UI/UX design quality using Apple's design principles and the Liquid Glass-era design language from WWDC25/26. Use when asked for a design review, UX critique, visual design assessment, or evaluation of an app's interface quality, design intent, alignment with the new Apple design system, or usability. Triggers: "design review", "UX review", "visual design critique", "review the UI", "does this design work", "evaluate this interface", "design feedback", "Liquid Glass review".
Also in 5 projects that use this
ShreyRaviSkillplan-tune
Self-tuning question sensitivity + developer psychographic for gstack (v1: observational). Review which AskUserQuestion prompts fire across gstack skills, set per-question preferences (never-ask / always-ask / ask-only-for-one-way), inspect the dual-track profile (what you declared vs what your behavior suggests), and enable/disable question tuning. Conversational interface — no CLI syntax required.
Also in 7 projects that use this
timurgaleevSkilldocument-generate
Generate missing docs for a feature, module or project as tutorials, how-tos, reference and explanation.
Also in 6 projects that use this
- +2 more from Saqlain-811
Other versions168 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 168
- garrytan/gstack★ 141ksame name
- getsentry/sentry-react-native★ 1.8ksame name
- JuliusBrussee/cavekit★ 1.1ksame name
- kdlbs/kandev★ 1.1ksame name
- getsentry/sentry-dart★ 878same name
- poshan0126/dotclaude★ 871same name
- codewithmukesh/dotnet-claude-kit★ 808same name
- SparkDevNetwork/Rock★ 701same name
- hyperlink-academy/leaflet★ 527same name
- strands-agents/box★ 503same name
158 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
The file
Show its rules and text
We split this skill into the rules it gives an agent
13 rules. 0 are common, 6 are distinctive, and 7 are very specific to this project.
0
Common
Most of the 145 edited versions we read keep these.
6
Distinctive
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 17
Run this block first, with your shell tool, and read its output before you continue.
- Very specific: line 43
Before proceeding, verify that .adlc/context/project-overview.md exists.
- Very specific: line 48
Read .adlc/context/project-overview.md for grounding context (skip if already in conversation)
- Very specific: line 49
Read .adlc/context/architecture.md for existing patterns (skip if already in conversation)
- Distinctive: line 167
Before retrieval fires, derive a structured query from the feature request.
- Distinctive: line 169
Read the feature request (the argument) alongside any grounding context already in conversation.
- Distinctive: line 205
Run a weighted-score retrieval over three corpora using the query from Step 1.5.
- Distinctive: line 235
Keep the block and the prose above in sync when changing either.
- Distinctive: line 244
If any directory is empty or missing, skip it and continue (cold-start path).
- Very specific: line 389
Create requirement.md using the template from .adlc/templates/requirement-template.md
- Very specific: line 411
a. Append
## Provenance: after ## Out of Scope, recording what the spec was derived from and what the source did not answer: - Very specific: line 430
Every assumed gap → a ## Assumptions entry containing the gap question verbatim:
- Very specific: line 435
Every open gap (blocking, unanswered — non-interactive mode only) → a ## Open Questions entry, also verbatim:
“Common” is measured: kept by at least 6 in 10 of the 145 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
/spec — Requirement Specification
You are writing a requirement spec following the spec-driven ADLC process.
Ethos
!test -s .adlc/ETHOS.md && cat .adlc/ETHOS.md || echo No ethos found — run /init, or /adlc:init on the plugin install, to vendor .adlc/ETHOS.md
Context
Run this block first, with your shell tool, and read its output before you continue. Each read prints a notice instead of failing when its file is absent.
# adlc-context
echo '--- ADLC context'
cat .adlc/context/project-overview.md 2>/dev/null || echo 'No project overview found'
echo '--- Requirement template'
cat .adlc/templates/requirement-template.md 2>/dev/null || echo 'No requirement template found — run /init (/adlc:init on the plugin install) to vendor .adlc/templates'
echo '--- Taxonomy'
cat .adlc/context/taxonomy.md 2>/dev/null || echo 'No taxonomy found — consider running /init (/adlc:init on the plugin install) to scaffold one'Input
The feature request is the argument: whatever follows /spec in the invocation (on Claude Code it can also arrive as an ARGUMENTS: line appended to this text). With none, ask the user what to build.
Lane flags — read and strip these before anything else (REQ-652 BR-4). The argument may carry --lite or --full, each a standalone token, to force the REQ's lane instead of letting Step 1.7 triage it. Read them from the argument above and remove every occurrence of both tokens, before the Prerequisites check and before Step 1.4's intake gate:
- Both present — stop with
/spec: --lite and --full are mutually exclusive — pass at most oneand do nothing else: no intake, no retrieval, and no Step 2. An id allocated for a request that then errors is burned, becauseadlc_alloc_idadvances a machine-global counter and pushes a remote reservation ref. - One present — remember which. Step 1.7 records it instead of triaging.
- Neither — Step 1.7 triages.
From here on, "the feature request" means the argument with those tokens removed, and every fence below that passes the argument receives the stripped text: where the argument appears inline in a fence, delete the flag tokens from it before running the fence. adlc_intake_detect reads its argument raw, so a leftover flag would count as request text, and --lite notes.txt would no longer resolve as a readable file path. --intake is not a lane flag; it stays. This is the model reading the argument, as with /sprint --workflow and /wrapup --close-out — no shell parses the lane flags, and partials/intake.sh knows nothing about them.
Prerequisites
Before proceeding, verify that .adlc/context/project-overview.md exists. If it doesn't, stop and tell the user: "The .adlc/ structure hasn't been initialized. Run /init (/adlc:init on the plugin install) first to set up the project context."
Instructions
Step 1: Understand the Request
- Read
.adlc/context/project-overview.mdfor grounding context (skip if already in conversation) - Read
.adlc/context/architecture.mdfor existing patterns (skip if already in conversation) - If the feature request is vague or ambiguous, ask clarifying questions before proceeding. Wait for answers. When Step 1.4's intake gate activates, that step supersedes this item — ambiguity is then handled by the structured gap list rather than by ad-hoc questions here. This item stays in force for the ordinary short-request path, where there is not enough source material to check against the template.
Step 1.4: Unstructured-Source Intake
Requirements do not always arrive as a coherent feature request. They arrive as meeting notes, a chat transcript, a ticket dump, a voice-note transcription, or three paragraphs of stakeholder prose. This step accepts that input and produces two things: the distilled feature request Step 1.5 will tag, and an explicit, classified gap list naming what the source does not answer.
The gap list is the point. A spec written from a transcript will always contain assumptions; the failure mode is not making them, it is making them invisibly.
This step runs before Step 1.5 because Step 1.5 derives the retrieval query from the request, and a distilled statement is better tag input than a raw transcript. It runs before Step 2 because an interactive blocking-gap halt must not burn a REQ id — adlc_alloc_id mutates a shared machine-global counter and pushes a remote reservation ref.
Activation gate (BR-1). Intake activates only when the input is unstructured. Source the partial and call the detector in the same fenced block (BR-10 — the
cross-fence-fnrule). The detector gets the feature request with--liteand--fullalready stripped (Input), here and in step 2's fence: substitute it for<request>, quotes included, as one single-quoted literal with every'in it written'\''(conventions.md "Bash in skills", "Substituting a value into a fence"). Never double-quote it: a backtick or$in the request would run.ADLC_TOOLKIT_ROOT="${CLAUDE_PLUGIN_ROOT}"; [ -n "$ADLC_TOOLKIT_ROOT" ] || [ ! -d "$HOME/.claude/skills/partials" ] || ADLC_TOOLKIT_ROOT="$HOME/.claude/skills"; [ -n "$ADLC_TOOLKIT_ROOT" ] || ADLC_TOOLKIT_ROOT=$(cat "$HOME/.claude/adlc/plugin-root" 2>/dev/null); export ADLC_TOOLKIT_ROOT="${ADLC_TOOLKIT_ROOT:-$HOME/.claude/skills}" if [ -f .adlc/partials/intake.sh ]; then . .adlc/partials/intake.sh; else . "$ADLC_TOOLKIT_ROOT/partials/intake.sh"; fi adlc_intake_detect '<request>'; gate=$? echo "intake_gate=$gate reason=${ADLC_INTAKE_REASON:-none} kind=${ADLC_INTAKE_KIND:-none} inline=${ADLC_INTAKE_INLINE:-0}" # This fence is a GATE PROBE only — step 2 re-derives everything in its own shell. # For an inline source, detect materializes a temp dir; discard this probe's copy # here or every intake run leaks one. Deletion guards live in the partial, never # at the call site. adlc_intake_cleanup "" "$ADLC_INTAKE_PATH" exit 0adlc_intake_detectreturns 1 for an ordinary feature request — none of BR-1's three triggers (an explicit--intakeflag, an argument resolving to a readable file path, or an argument exceeding 25 lines) fired. When it returns 1, skip the entire rest of Step 1.4 and go straight to Step 1.5. No intake runs, no gap list is produced, no## Provenancesection is written, and no stderr line is emitted (AC-1). This is the common path and it must stay exactly as fast and as quiet as it is today.It returns 0 when intake should run, having exported
ADLC_INTAKE_REASON(which trigger fired),ADLC_INTAKE_KIND(transcript|notes|ticket|prose),ADLC_INTAKE_PATH, andADLC_INTAKE_INLINE.Segment the source before delegating (BR-12). Segmentation is what makes a partial delegate summary detectable. Without it a truncated read yields zero gaps precisely because the unread remainder is invisible, and BR-11's benign path would certify the result as complete.
Segmentation and credential redaction happen in one fenced block, and that block re-runs the detector rather than relying on step 1's exports. This is not redundancy: each fenced block is a separate shell invocation, so nothing — not even an exported variable — survives from step 1's block. Re-deriving is the only correct option, which is why the detector is cheap and idempotent.
ADLC_TOOLKIT_ROOT="${CLAUDE_PLUGIN_ROOT}"; [ -n "$ADLC_TOOLKIT_ROOT" ] || [ ! -d "$HOME/.claude/skills/partials" ] || ADLC_TOOLKIT_ROOT="$HOME/.claude/skills"; [ -n "$ADLC_TOOLKIT_ROOT" ] || ADLC_TOOLKIT_ROOT=$(cat "$HOME/.claude/adlc/plugin-root" 2>/dev/null); export ADLC_TOOLKIT_ROOT="${ADLC_TOOLKIT_ROOT:-$HOME/.claude/skills}" if [ -f .adlc/partials/intake.sh ]; then . .adlc/partials/intake.sh; else . "$ADLC_TOOLKIT_ROOT/partials/intake.sh"; fi adlc_intake_detect '<request>' || exit 0 adlc_intake_segment "$ADLC_INTAKE_PATH" || exit $? adlc_intake_redact "$ADLC_INTAKE_CORPUS" || exit $? echo "INTAKE_CORPUS=$ADLC_INTAKE_CORPUS" echo "INTAKE_SOURCE=$ADLC_INTAKE_SOURCE" echo "INTAKE_SEGMENTS=$ADLC_INTAKE_SEGMENTS" echo "INTAKE_LINES=$ADLC_INTAKE_LINES" echo "INTAKE_INLINE=$ADLC_INTAKE_INLINE"INTAKE_INLINE=1means the source was pasted rather than a file on disk, andadlc_intake_detectmaterialized it into a private temp dir so the file-based steps below work uniformly. It matters at cleanup (step 7): a materialized temp dir is ours to delete, a user's source file never is.Return codes from
adlc_intake_segment: 0 = segmented; 2 = source unreadable; 3 = over budget. On rc=3 the partial has already printed a refusal naming the actual line count and the 8000-line / 40-segment budget. Halt. Do not write a spec (AC-10). Tell the operator to split the source and run intake on each part. Never truncate: reading the first 8000 lines silently would recreate the exact invisible-compression failure this step exists to eliminate.The corpus embeds only the source's basename (BR-7); full local paths stay on the machine. Redaction runs before anything leaves the machine, applying the same 5-pattern chain
/proceedPhase 5 uses on its verify diff.Thread the echoed values forward as literals. Later fenced blocks cannot read them as variables — substitute the actual printed values into the commands below, the same way the telemetry
flagpath is threaded through Step 1.6. Writing"$ADLC_INTAKE_CORPUS"in a later block would silently expand to an empty string.- Read the source body — gated delegation, hard fallback (BR-5). Read
spec/references/intake-source-read.mdand follow it before you continue. It holds this step in full: the telemetry flag, the delegate gate, the delegated read with its coverage reconciliation and citation checks, the fallback read, and the telemetry emit, all working from the literals step 2 echoed. Nothing in this file replaces it. Identify gaps against the template's sections. Get the checklist — derived from the requirement template, never hardcoded, so a future template section is gap-checked automatically:
ADLC_TOOLKIT_ROOT="${CLAUDE_PLUGIN_ROOT}"; [ -n "$ADLC_TOOLKIT_ROOT" ] || [ ! -d "$HOME/.claude/skills/partials" ] || ADLC_TOOLKIT_ROOT="$HOME/.claude/skills"; [ -n "$ADLC_TOOLKIT_ROOT" ] || ADLC_TOOLKIT_ROOT=$(cat "$HOME/.claude/adlc/plugin-root" 2>/dev/null); export ADLC_TOOLKIT_ROOT="${ADLC_TOOLKIT_ROOT:-$HOME/.claude/skills}" if [ -f .adlc/partials/intake.sh ]; then . .adlc/partials/intake.sh; else . "$ADLC_TOOLKIT_ROOT/partials/intake.sh"; fi adlc_intake_sections<!-- intake-gap-rules: read verbatim by space/build.mjs (REQ-670); keep the markers -->For each section returned, ask what the source material does not answer, and write the gap as a specific unanswered question, never a category label. "Who is allowed to archive a project — any member, or only the owner?" is a gap. "Permissions unclear" is not; it names a topic instead of a question, and nobody can answer it.
A section with everything it needs produces no gap. A complete, unambiguous source produces zero gaps, no halt, and no Open Questions (BR-11).
Classify every gap (BR-2). Each gap is exactly one of:
Severity Test Disposition blockingA faithful spec cannot be written without the answer — an undefined entity, a missing permission model, two source statements that contradict halt (interactive) or Open Questions (non-interactive) assumptionThe spec can proceed under a stated assumption a reviewer can later challenge Assumptions section Classification is per-gap and must be justified in one sentence. The justification is what stops
blockingfrom becoming a reflex andassumptionfrom becoming a dumping ground.<!-- /intake-gap-rules -->Disposition by mode (BR-3, BR-4). Non-interactive is detected by the same conditions Step 1.5 item 4 already lists — do not restate them here; one definition, referenced twice. Of those conditions exactly one is reachable today: dispatch into a subagent context that cannot receive further user input.
/specis human-invoked —/proceedrefuses to create a spec (proceed/SKILL.md"Arguments" and "What This Skill Does NOT Do") and/sprintrequires the spec to already exist on the integration branch — so no pipeline calls/specat present. The rule is written to the general condition so it holds unchanged if that ever becomes reachable.Interactive mode — blocking gaps halt before the spec file is written (ETHOS #1: stop and clarify rather than guess). Present them as a numbered list and wait:
Intake found <N> blocking gap(s). The spec cannot be written faithfully without these: 1. [System Model] Who is allowed to archive a project — any member, or only the owner? 2. [Business Rules] Does archiving cascade to child items, or leave them active? Answer these, or say "assume" with your intent and they will be recorded as stated assumptions.Assumption gaps never halt in either mode.
Non-interactive mode — never halt (AC-4). Write blocking gaps into
## Open Questions, and emit exactly one stderr line naming the count:/spec: intake found <N> blocking gap(s) — written to Open Questions, not answered (non-interactive mode)Carry the result into Step 3, and clean up. Retain the distilled feature request (it replaces the argument as the input Step 1.5 tags and Step 3 writes from), the classified gap list with dispositions, and the provenance triple — source basename,
kind, and intake date. Step 3 persists all three.Then remove the temp files, substituting the literals echoed in step 2. The corpus holds a redacted copy of the source and there is no
trapthat can span fenced blocks, so this is the only cleanup:ADLC_TOOLKIT_ROOT="${CLAUDE_PLUGIN_ROOT}"; [ -n "$ADLC_TOOLKIT_ROOT" ] || [ ! -d "$HOME/.claude/skills/partials" ] || ADLC_TOOLKIT_ROOT="$HOME/.claude/skills"; [ -n "$ADLC_TOOLKIT_ROOT" ] || ADLC_TOOLKIT_ROOT=$(cat "$HOME/.claude/adlc/plugin-root" 2>/dev/null); export ADLC_TOOLKIT_ROOT="${ADLC_TOOLKIT_ROOT:-$HOME/.claude/skills}" if [ -f .adlc/partials/intake.sh ]; then . .adlc/partials/intake.sh; else . "$ADLC_TOOLKIT_ROOT/partials/intake.sh"; fi # Both arguments are the LITERALS echoed in step 2. adlc_intake_cleanup removes the # corpus, and removes the source dir ONLY when the source was the inline file intake # itself materialized — a user-supplied source file is theirs and is never deleted. adlc_intake_cleanup <INTAKE_CORPUS literal> <INTAKE_SOURCE literal>
Step 1.5: Derive Query Tags for Retrieval
Before retrieval fires, derive a structured query from the feature request. This query drives both context loading (Step 1.6) and the self-tagging of the new REQ (Step 3).
- Read the feature request (the argument) alongside any grounding context already in conversation. Extract likely area signals:
- component — which narrow area this touches (e.g.,
API/auth,iOS/SwiftUI,adlc/spec) - domain — broader problem domain (e.g.,
auth,payments,ui,adlc) - stack — tech layers implicated (e.g.,
express,firestore,swiftui,markdown) - concerns — cross-cutting dimensions (e.g.,
security,perf,a11y,retrieval) - tags — free-form keywords from the feature description (e.g.,
password-reset,pagination,caching)
- component — which narrow area this touches (e.g.,
- Construct the query object:
query = { component: "<proposed>", domain: "<proposed>", stack: [<proposed>], concerns: [<proposed>], tags: [<proposed>] } - Interactive mode (manual
/specinvocation): surface the proposed query to the user and wait for confirmation or edits:Proposed retrieval query for this feature: component: <value> domain: <value> stack: [<values>] concerns: [<values>] tags: [<values>] Confirm or edit any field before retrieval fires. Non-interactive / pipeline mode — detect this when ANY of:
- The argument already contains explicit tag values (e.g., a caller passed
component: Xortags: [...]in the prompt) - The invocation prompt explicitly says "invoked from /proceed" or "pipeline mode" or supplies an inherited query object
- Running inside a subagent context that cannot receive further user input (e.g., dispatched via the Agent tool)
In any of these cases: do NOT block for confirmation. Use caller-supplied tag values verbatim; for any unspecified dimension, use the proposed value from sub-step 2. Proceed directly to Step 1.6.
- The argument already contains explicit tag values (e.g., a caller passed
- Retain the confirmed
queryobject. It is reused by Step 1.6 (retrieval) and Step 3 (self-tagging the new REQ's frontmatter).
Step 1.6: Unified Retrieval Across Corpora
Run a weighted-score retrieval over three corpora using the query from Step 1.5. This is the only retrieval behavior — the prior 3-tier lesson grep is removed.
Enumerate candidate files with three Grep passes (paths relative to project root):
.adlc/knowledge/lessons/*.md— no status filter, all lessons are candidates.adlc/specs/*/requirement.md— exclusion filter (BUG-194): admit every spec EXCEPT those whose frontmatterstatusis one ofdraft,superseded,cancelled,rejected. A spec with a missing or unparseablestatusis admitted (it is dropped later by sub-step 2 only if the whole frontmatter block is malformed)..adlc/bugs/*.md— include only where frontmatterstatusisresolvedorclosed
The spec filter is an exclusion list, not an allowlist. This direction is load-bearing, not stylistic: the terminal status every shipped REQ lands in is
complete(written by/wrapupStep 4.1 and/proceedPhase 6-8), and consumer repos additionally carry the legacy synonymsdone,completed, anddeployed. An allowlist has to enumerate all of those correctly or it silently discards the corpus — which is exactly what BUG-194 was: the originalapproved|in-progress|deployedallowlist admitted 0 of 42 toolkit specs and 11 of 543 ecosystem-wide, because two of its three values are vocabulary no toolkit skill has ever written. An exclusion list fails toward recall: an unrecognized or newly-invented status is retrieved (cheap — one extra scored candidate) rather than silently dropped (expensive — invisible corpus loss).Admitted (non-exhaustive, for orientation):
complete,deployed,done,completed,approved,in-progress,in-review. Excluded, and why:draft(not yet validated — not prior art),superseded/cancelled/rejected(withdrawn — citing them would propagate decisions that were explicitly reversed).The exclusion set below is the machine-readable source of truth for this filter.
tools/lint-skills'sretrieval-status-paritycheck reads it and fails if any status the lifecycle skills write appears in it (BUG-194's recurrence guard — LESSON-012: structural enforcement, not a prose promise). Keep the block and the prose above in sync when changing either.
<!-- retrieval-status: spec-exclude -->draft— not yet validated; not prior artsuperseded— replaced by a later REQ; citing it propagates a reversed decisioncancelled— abandoned before shippingrejected— explicitly declined
<!-- /retrieval-status -->If any directory is empty or missing, skip it and continue (cold-start path).
1a. Status-filter shrink diagnostic (BUG-194 — do not skip). After the spec
pass, compare the number of .adlc/specs/*/requirement.md files that exist on
disk against the number that survived the exclusion filter. If the directory
held one or more spec files and zero survived, this is not a cold start
— it is a filter/vocabulary mismatch. Emit to stderr:
/spec: WARNING — spec corpus has <N> requirement.md files but 0 passed the status filter (statuses seen: <comma-separated distinct values>). Retrieval is running on lessons+bugs only. This is a filter/vocabulary mismatch, not a cold start — see BUG-194.Then continue (this is a warning, never a halt). Carry the fact forward: the
## Retrieved Context section in Step 3 must say Spec corpus suppressed by
status filter — see stderr warning (BUG-194) rather than the plain
no-context line, so the degradation is visible in the artifact and not only in
the terminal. This diagnostic exists because the cold-start message in
sub-step 8 is otherwise byte-identical for "no specs exist" and "every spec
was filtered out" — the failure mode BUG-194 hid behind for four months
(LESSON-012: a silent path needs a structural signal, not a prose promise).
- Read the frontmatter of every candidate using Read with
limit: 30(enough to cover full frontmatter block including any leading HTML comments, e.g., the lesson template's naming-convention comment). Parse these fields:component,domain,stack,concerns,tags,updated,created,status,supersedes.supersedesis parsed for presentation only — sub-steps 3–6 never read it (REQ-646 BR-7).confidenceis not parsed: nothing in this step presents or scores it. If the frontmatter is malformed (missing---delimiters, unparseable YAML), skip that doc and continue — do not crash. - Compute a weighted score per candidate using the following rule:
+3ifdoc.component == query.component+2ifdoc.domain == query.domain+2 × |doc.concerns ∩ query.concerns|+1 × |doc.stack ∩ query.stack|+1 × |doc.tags ∩ query.tags|+1foundational floor only for lesson documents with none of the five tag fields populated. Specs and bugs with zero tag overlap score0.
- Filter out every doc with final score
0. - Sort using a strict lexicographic key
(score DESC, effective_date DESC, corpus_priority ASC, id ASC):effective_dateper doc is the first non-empty value in this chain:updated→created→ file mtime → epoch-minimum (if all are absent)corpus_prioritymapslesson=0,bug=1,spec=2- Interpretation: highest score first; among equal scores, newest
effective_datewins; among equal scores and equal dates, corpus prioritylesson > bug > specapplies; final tiebreak is alphabeticalid - Missing dates never cause retrieval failures — they are treated as oldest and lose date tiebreaks
- Take the top 15 globally across all corpora. There are no per-corpus quotas (no minimum-lesson floor, no maximum-bug cap). If fewer than 15 candidates survive filtering, take what is available.
- Body-read of top-15 docs — gated delegation, hard fallback. Read
spec/references/retrieval-body-read.mdand follow it before you continue. It holds this sub-step in full: the telemetry flag, the delegate gate, the delegated read with its coverage reconciliation and citation checks, the fallback read, and the telemetry emit. Nothing in this file replaces it.
7a. Superseded annotation (REQ-646 BR-7, ADR-6 — presentation only). For each
retrieved lesson in the top 15 (specs and bugs carry no supersedes; skip
this sub-step when no lesson was retrieved), ask whether another lesson names it
in supersedes. The superseding lesson is usually not in the top 15, so the
supersedes parsed in sub-step 2 cannot answer this — run one corpus-wide
reverse lookup per retrieved lesson. Substitute the lesson's id for <id> only
after it matches ^LESSON-[0-9]{3,6}$ — an id that does not match is skipped,
never substituted — and the case re-checks it before anything uses it:
ID='<id>'
case "$ID" in
LESSON-[0-9][0-9][0-9]|LESSON-[0-9][0-9][0-9][0-9]|LESSON-[0-9][0-9][0-9][0-9][0-9]|LESSON-[0-9][0-9][0-9][0-9][0-9][0-9])
LC_ALL=C find .adlc/knowledge/lessons -maxdepth 1 -type f -name 'LESSON-*.md' -exec awk -v id="$ID" 'FNR==1{s=0;skip=0;c=0;sub(/^\357\273\277/,"")} {sub(/\r$/,"")} skip{next} s==0&&/^[ \t]*$/{next} s==0&&/^<!--/{c=1} c{ if(/-->/){c=0}; next } s==0&&!/^---$/{skip=1;next} s<2&&/^---$/{s++;next} s==1&&$(0)~("^supersedes:[ \t][^#]*" id "([^0-9]|$)"){print FILENAME;skip=1}' {} + 2>/dev/null ;;
*) echo "superseded lookup: $ID is not a LESSON-nnn id — skipped" >&2 ;;
esacEach printed path is a superseding lesson; its id is the LESSON-<digits>
prefix of the basename (ignore a hit on the lesson's own file). Record
superseded by LESSON-yyy next to the retrieved lesson — every hit,
comma-separated. No output means not superseded. The id reaches awk as data
(-v), so the case guards the only place it is substituted. The program is
single-quoted and reads only the frontmatter block, found the way
adlc_lesson_check finds it: a leading BOM and each line's trailing CR are
dropped, blank lines and HTML comments are skipped, and the block opens only
when the first other line is --- — a file that opens with anything else, such
as a legacy lesson whose --- lines are horizontal rules in its body, is
skipped whole. The match needs a blank after supersedes: (the one key
spelling the check accepts), and [^#]* stops at a trailing # comment, so
neither a body line nor a comment that mentions the id is a hit. 2>/dev/null
keeps a missing lessons directory quiet — no output, so not superseded.
-exec … {} + runs nothing when no file matches, where GNU xargs would run
its command once on empty input; the quoted -name never hands the shell an
unmatched glob (zsh aborts on one — LESSON-335); and ([^0-9]|$) stops
LESSON-40 matching LESSON-404 without a word-boundary escape, which BSD
tools silently ignore (LESSON-013).
This changes nothing about retrieval. A superseded lesson keeps its score,
its rank, and its place in the top 15; sub-steps 3–6 are not re-run, and neither
supersedes nor confidence feeds the score. Weighting or filtering by either
is deferred until the corpus carries the fields (LESSON-571: a scoring change
must be designed against the writer vocabulary it reads).
- Surface the retrieval summary to the user before authoring continues. This is always shown — there is no verbose flag gate:
TheRetrieved context for this REQ: LESSON-034 (lesson, score 5, superseded by LESSON-040): Silent failure remediation BUG-012 (bug, score 5): Auth rate-limit bypass REQ-019 (spec, score 3): Prior login redesign ... (etc.), superseded by LESSON-yyysuffix appears only on a lesson sub-step 7a found superseded; every other line keeps the plain(corpus, score)form. The score shown is the one sub-steps 3–6 computed — the annotation never changes it. - Cold-start path: if every corpus is empty, or all candidates filter out to zero, skip retrieval and record this explicitly when Step 3 writes the
## Retrieved Contextsection. Proceed to authoring without retrieved bodies. A true cold start requires the corpora to be empty or every candidate to lose on score. If the spec corpus had files on disk and lost them all to the status filter, that is the BUG-194 mismatch, not a cold start — sub-step 1a's warning fires and Step 3 records the suppressed-corpus line instead of the cold-start line.
Step 1.7: Triage the Lane
Every new REQ gets exactly one lane, lite or full, which Step 3 writes to its frontmatter (REQ-652 BR-1). /proceed reads it. A lite REQ skips spec validation, architecture, task breakdown, architecture validation, the reflector and the five-agent panel; it gets one review pass instead, plus a mechanical lane check of the real diff before the PR. Never ask the operator to choose, in interactive or non-interactive mode. The lane is triage's call, or the flag's.
This step runs after Step 1.6 because the last criterion below is judged against what retrieval returned, and before Step 2 so a lane is settled before any id is allocated (REQ-652 ADR-2). Triage reads the feature request, or Step 1.4's distilled request when intake ran.
- Forced lane. If Input found
--liteor--full, skip the judgment. The lane is the flag's,lane_reasonis"forced by --lite"or"forced by --full", andlane_forcedistrue. Go to Step 2. A forced lite REQ is still subject to/proceed's lane check: a solo run halts there and asks the operator, and an unattended sprint runner escalates. Otherwise, triage against REQ-652 BR-2. The REQ goes to the full lane if triage finds evidence that any of these full-lane triggers holds:
- it changes a domain schema, an API contract, a validator or a data model
- it changes auth, security, secrets, deploy, CI or infrastructure
- it touches more than one deployable or more than one repo. A cross-repo REQ is always full
- it adds or upgrades a dependency
- its footprint is more than
max_filessource files.max_filesis 3 unless.adlc/config.ymlsetslanes.lite.max_files; read that file with the Read tool when it exists, never with a shell YAML parse. Files the lane check does not count (tests,.adlc/,CHANGELOG*, and anylanes.lite.count_excludeglob; the full list is in the toolkit'spartials/lane-check.md) do not count here either. Changed lines are not counted at all - it reverses a decision recorded in an existing REQ or lesson for the same component, judged against the context Step 1.6 retrieved
When none holds, the lane is
lite. Name the files the change will most likely touch before judging criteria 1, 2 and 5. A project'slanes.lite.blocked_pathsin.adlc/config.ymllists where its schemas, contracts and infrastructure live, so a likely file under one of those globs is evidence for criterion 1 or 2.- Uncertain means lite (REQ-652 BR-3). When you cannot tell whether a criterion holds, choose
liteand say so inlane_reason, naming the criterion you could not settle. A wrong lite start costs one escalation:/proceed's lane check reads the real diff, and a change that crossed a line escalates to the full lane with the spec and the code kept. A wrong full start costs the whole full pipeline. - Write the reason.
lane_reasonis one line. Forfull, it names the criterion that fired and the evidence, e.g."adds a field to the Page data model in packages/domain". Forlite, it says why none fired, e.g."one component, copy only: no schema, config or dependency change", and says which criterion was uncertain when one was.lane_forcedisfalse. When an escalation later shows the triage was wrong,/wrapupwrites a lesson naming this reason (REQ-652 BR-14), so make it specific enough to be tuned from.
Retain the lane, lane_reason and lane_forced for Step 3 and Step 4.
A missing lane means full (REQ-652 BR-12). Every reader treats a spec without the field as a full-lane REQ, so every spec written before this step existed keeps its behavior. Only lite and full are lane values; never write the field empty.
Step 2: Determine the Next REQ ID
- Use the global atomic counter file
${ADLC_HOME:-$HOME/.claude}/.global-next-req(shared across all repos for unique IDs) — but the counter is now a cache, not the authority: the remote is the source of truth (REQ-518). Allocation derives the remote high-water, takesmax(remote, local), allocatesmax + 1, and fast-forwards the local counter — all inside the existingmkdirlock with its symlink/TOCTOU guards intact. - Allocate via the shared
partials/id-alloc.shhelper (BR-5 — one parameterized helper replaces the three near-identical inline blocks; the lock block + its REQ-416/LESSON-014 rationale live in the partial). Source it and calladlc_alloc_idin the same fenced block (the cross-fence-fn rule — see conventions.md "Bash in skills"):ADLC_TOOLKIT_ROOT="${CLAUDE_PLUGIN_ROOT}"; [ -n "$ADLC_TOOLKIT_ROOT" ] || [ ! -d "$HOME/.claude/skills/partials" ] || ADLC_TOOLKIT_ROOT="$HOME/.claude/skills"; [ -n "$ADLC_TOOLKIT_ROOT" ] || ADLC_TOOLKIT_ROOT=$(cat "$HOME/.claude/adlc/plugin-root" 2>/dev/null); export ADLC_TOOLKIT_ROOT="${ADLC_TOOLKIT_ROOT:-$HOME/.claude/skills}" if [ -f .adlc/partials/id-alloc.sh ]; then . .adlc/partials/id-alloc.sh; else . "$ADLC_TOOLKIT_ROOT/partials/id-alloc.sh"; fi REQ_NUM=$(adlc_alloc_id req) # `exit 1` inside adlc_alloc_id's subshell terminates only the subshell — REQ_NUM # would be silently empty. Guard the parent context (REQ-416 verify D-pass). [ -n "$REQ_NUM" ] || { echo "ERROR: failed to allocate REQ number — aborting before writing malformed spec" >&2; exit 1; } # If ADLC_ALLOC_DEGRADED=1 was set (remote unreachable), the helper already warned on # stderr — record "id allocated without remote verification — verify before PR" in the # spec's Assumptions section (BR-3). Never block spec-writing on network availability.adlc_alloc_id reqhandles the absent-counter bootstrap scan internally (highestREQ-xxxunder$ADLC_REPOS_ROOT, BSD-safe), themkdirlock that serializes concurrent/sprintsessions, and the remote high-water max. Single-machine behavior is unchanged: when the remote has no higher allocation, the same id is produced as before (BR-7).
Step 3: Create the Requirement Spec
- Create directory:
.adlc/specs/REQ-xxx-feature-slug/ - Create
requirement.mdusing the template from.adlc/templates/requirement-template.md - Fill in all sections:
- Frontmatter: id, title, status (
draft),deployable(carry the template default unless the feature is explicitly non-deployable — e.g., iOS-only or docs-only), created date, updated date, AND the five query tags from Step 1.5 —component,domain,stack,concerns,tags. This self-tagging makes the new REQ retrievable for future/specinvocations (per REQ-258 BR-7). Then the three lane fields from Step 1.7, aftertags:
A forced lane readslane: "lite" lane_reason: "one component, copy only: no schema, config or dependency change" lane_forced: falselane: "full",lane_reason: "forced by --full",lane_forced: true. Quote every lane string value, even one that looks safe. An unquoted:insidelane_reasonmakes the frontmatter unparseable, and Step 1.6 skips a spec whose frontmatter does not parse, so the REQ silently drops out of retrieval.lane_forcedis a bare boolean. Do not writelane_escalated;/proceedsets it when it escalates a REQ. - Description: What the feature does and why — be specific and grounded in the project context
- System Model: Structured data model — Entities (fields, types, constraints), Events (triggers, payloads), Permissions (actions, roles). Remove sub-sections that don't apply to this feature.
- Business Rules: Explicit, testable constraints governing behavior (e.g., "Only item owner can delete"). Numbered BR-1, BR-2, etc.
- Acceptance Criteria: Concrete, testable criteria as checkboxes
- External Dependencies: Any new APIs, services, or libraries needed
- Assumptions: Things assumed to be true that could affect the design. If Step 1.4 ran, every
assumption-severity gap is written here — see sub-step 5. - Open Questions: Questions that need answers before implementation. If Step 1.4 ran in non-interactive mode, every unanswered
blockinggap is written here — see sub-step 5. - Out of Scope: Items explicitly excluded to prevent scope creep
- Retrieved Context (NEW, always present): append a
## Retrieved Contextsection at the end of the spec listing every retrieved source from the retrieval summary produced in Step 1.6 in the formID (corpus, score): title— a lesson Step 1.6 sub-step 7a found superseded carries the same parenthetical as the summary,ID (corpus, score, superseded by LESSON-yyy): title(e.g.LESSON-034 (lesson, score 5, superseded by LESSON-040): Silent failure remediation), with its score unchanged. If no context was retrieved (cold-start path — either the corpus is empty or no documents scored above zero), write exactly:No prior context retrieved — no tagged documents matched this area.If instead Step 1.6 sub-step 1a fired (spec files existed on disk but none survived the status filter), write exactly:Spec corpus suppressed by status filter — see stderr warning (BUG-194).and, when lessons or bugs still matched, list them beneath it. Never emit the cold-start line for a status-filter suppression — collapsing the two is the defect BUG-194 fixed.
- Frontmatter: id, title, status (
- Inline citations: when a retrieved doc directly informed a Business Rule, Assumption, or Acceptance Criterion, add an inline citation in the form
(informed by BUG-012)or(informed by REQ-019, LESSON-034)at the end of that line. Citations are required when the retrieved doc is load-bearing for the rule; optional when the doc was background reading only. Persist the intake result — ONLY if Step 1.4 ran (REQ-594). If intake did not activate, skip this sub-step entirely: write no
## Provenanceheading, no placeholder, and no empty section. A spec written without intake is exactly the shape it is today (BR-8, BR-11).a. Append
## Provenanceafter## Out of Scope, recording what the spec was derived from and what the source did not answer:## Provenance - Source: `standup-2026-08-27.txt` (kind: transcript) - Intake date: 2026-08-27 | Section | Severity | Gap | Disposition | |---|---|---|---| | System Model | blocking | Who is allowed to archive a project — any member, or only the owner? | answered | | Business Rules | assumption | Does archiving cascade to child items, or leave them active? | assumed | | Acceptance Criteria | blocking | What should the UI show while an archive is in flight? | open |Record the source basename only — never a full local path (BR-7). This table is the complete gap list with every entry classified and attributed to a named section, which is what makes the intake result auditable in one place.
b. Mirror gaps into the working sections. Provenance is the audit record; Assumptions and Open Questions are the surfaces the rest of the pipeline already reads. Writing gaps in both places is deliberate — do not collapse the duplication.
- Every
assumedgap → a## Assumptionsentry containing the gap question verbatim:
Verbatim matters: the gap text is what a reviewer greps for to confirm nothing was quietly reworded between the gap list and the spec. Put commentary around the question, never inside it.- Does archiving cascade to child items, or leave them active? — assumed: archiving is shallow; child items stay active. (intake gap: Business Rules) - Every
opengap (blocking, unanswered — non-interactive mode only) → a## Open Questionsentry, also verbatim:- [ ] What should the UI show while an archive is in flight? (intake gap: Acceptance Criteria — blocking, unanswered at intake) answeredgaps (blocking, resolved by the operator in interactive mode) appear in Provenance only. Their answers are already reflected in the spec body.
c. Zero gaps is the benign path (BR-11). A complete, unambiguous source adds no Assumptions entries and no Open Questions entries. The only difference from a non-intake spec is the
## Provenancesection, whose table is then empty apart from a_No gaps identified._line.- Every
Step 4: Present for Review
- Display the full requirement spec to the user
- Show the lane and its reason on one line, and whether triage or a flag chose it:
Lane: lite (triaged) — one component, copy only: no schema, config or dependency change Lane: full (forced by --full) - Highlight any assumptions or open questions that need input
- Remind the user of the next step. For a full-lane REQ, run
/validatebefore advancing to/architect. For a lite-lane REQ, run/proceed REQ-xxx, which skips validation and architecture;/proceed --full REQ-xxxescalates it by hand.
Quality Checklist
- Acceptance criteria are specific and testable (not vague)
- Description explains the "why" not just the "what"
- Assumptions are explicitly stated
- Out of scope items prevent scope creep
- No implementation details leaked into the requirement (that's for architecture phase)
- Retrieved Context section present
- Frontmatter carries a quoted
lane("lite"or"full"), a quoted one-linelane_reason, andlane_forced, chosen by Step 1.7 without asking the operator
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

