Repository navigation
feat(github-templates): discover and parse GitHub-native issue templates (FAFF-1112) - #1120
Merged
alechill merged 5 commits intoOct 24, 2026
Conversation
…mplates Signed-off-by: Alec Hill <alec@shftwst.dev>
…tes (FAFF-1112) Add `faff github-templates scan`, a pure offline scanner for a repo's own `.github/ISSUE_TEMPLATE/*.md` files and the legacy bare `.github/ISSUE_TEMPLATE.md`. It parses each file's front matter and buckets the file onto the closed faff-type taxonomy, giving create.md's tier-1 slot a GitHub-side candidate set. Scoring and selection stay with FAFF-1113. A directory-form file that does not classify is dropped. A bare file that does not classify is a universal candidate for every type. The type gate never returns `default`. `labels: [bug, triage]` reads back from parseYamlSubset as a string, so the module normalises label shapes locally and leaves shared-infra.js alone. Signed-off-by: Alec Hill <alec@shftwst.dev>
… label lists (FAFF-1112) Review follow-up. lstat keeps a symlink or FIFO out of the scan, and a label value that opens a flow sequence without closing it is a miss, not a label. Signed-off-by: Alec Hill <alec@shftwst.dev>
…1112) The usage line spelled out the six type tokens, which the module is meant to take only from FAFF_TYPES. Build it from the import, and widen the purity test so a pipe- or comma-delimited list is caught too. Signed-off-by: Alec Hill <alec@shftwst.dev>
Signed-off-by: Alec Hill <alec@shftwst.dev>
Contributor
|
Ready to land. Run this locally in a real terminal: |
alechill
deleted the
faff-1112-faff-jotplot-discover-parse-github-native-issue-templates
branch
October 24, 2026 05:02
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
faff github-templates scan, a pure offline scanner for a repo's own GitHub issue templates. It reads.github/ISSUE_TEMPLATE/*.mdand the legacy bare.github/ISSUE_TEMPLATE.md, parses each file's front matter, and buckets it onto faff's six-type taxonomy. That gives create.md's tier-1 native-template slot a GitHub-side candidate set. Scoring and selection stay with FAFF‑1113.Closes FAFF‑1112
default, so--type defaultreturns only universal candidates.labels: [bug, triage]reads back fromparseYamlSubsetas a string. The module normalises label shapes locally;shared-infra.jsis untouched.tracking.tracker, never touches the network or git, and treats every absence or malformation as a recorded miss at exit 0.COMMANDS,REGION_MAP,REGION_SELFTEST_ARGV,DISPATCH_SURFACES, and adocs/guide/cli.mdrow.Review
The adversarial pass raised one major and four minor findings. Three were fixed (usage string derived from
FAFF_TYPES, unterminated label list is a miss,lstatkeeps symlinks and FIFOs out of the scan). Two are accepted as the spec's chosen design (the fence idiom and the word-bounded classifier table).Notes for the reviewer
faff gates rundiscoverednpx tsc --noEmit -p tsconfig.jsonfrom the repo root, where notsconfig.jsonexists (CI runs it insideplugin/skills/faff). The rung fails regardless of this change. I ran tsc in the right directory and it passes.Acceptance Criteria
faff-1112#done:1A repo with.github/ISSUE_TEMPLATE/bug_report.mdyields that file in thebugcandidate set, where before the ticket it yielded nothing.Verified: bug_report.md in bug set (smoke test)
Method: test
test/github-templates.test.mjs::smoke: bug_report.md lands in the bug set with normalised flow labelsfaff-1112#done:2The scan produces identical output for the same tree whethertracking.trackerislinear,github, or unset — it never reads that key.Verified: output identical for tracker linear vs github
Method: test
test/github-templates.test.mjs::output is independent of tracking.trackerfaff-1112#done:3No input causes the scan to throw at its caller; a missing directory, an unreadable file and a malformed fence each resolve to a recorded miss at exit 0.Verified: missing dir / unreadable file / no-fence each a recorded miss at exit 0
Method: test
test/github-templates.test.mjs::no templates at all: exit 0, zero candidates, zero notesMethod: test
test/github-templates.test.mjs::an unreadable file is dropped, noted, and the scan continuesfaff-1112#done:4plugin/skills/faff/bin/lib/github-templates.jsexists, carries exactly one// === region:factory — github-templates — FAFF‑1112 … ===banner, andfaff regions checkpasses.Verified: module exists with one region banner; faff regions check passes
Method: test
test/github-templates.test.mjs::module purity: fixed require set, no literal taxonomy list, one region bannerMethod: command
node plugin/skills/faff/bin/faff regions checkfaff-1112#done:5The module is plain.jswith no.tssibling, andbuild-manifest.jsonis unchanged.Verified: plain .js, no .ts sibling, build-manifest.json untouched (git status shows neither)
Method: command
git status --short -- plugin/skills/faff/build-manifest.json 'plugin/skills/faff/bin/lib/*.ts'faff-1112#done:6Its require set is exactlynode:fs,node:path,./argv,./shared-infra,./native-map— nochild_process, no network module, no governance module.Verified: require set is exactly the five modules
Method: test
test/github-templates.test.mjs::module purity: fixed require set, no literal taxonomy list, one region bannerfaff-1112#done:7The six faff-type tokens appear in the module only viaFAFF_TYPESimported from./native-map; the module contains no literal list of the six.Verified: taxonomy via FAFF_TYPES import, no literal six-list
Method: test
test/github-templates.test.mjs::module purity: fixed require set, no literal taxonomy list, one region bannerfaff-1112#done:8Every emitted candidate satisfiesuniversal === (type === null)anduniversal === trueimpliesform === "bare".Verified: universal===(type===null), universal implies bare
Method: test
test/github-templates.test.mjs::ordering, no dedup, non-markdown and nested handlingfaff-1112#done:9faff github-templates scan --type <unknown>exits 2 as a usage error and emits no candidate set.Verified: unknown --type exits 2, no candidate set
Method: test
test/github-templates.test.mjs::unknown --type is a usage error (exit 2) and emits no candidate setfaff-1112#done:10--type <t>returns every candidate whosetypeistplus everyuniversalcandidate, and nothing else.Verified: --type t returns type t plus universal only
Method: test
test/github-templates.test.mjs::ordering, no dedup, non-markdown and nested handlingMethod: test
test/github-templates.test.mjs::holdout: a classifying bare file is a single-type candidatefaff-1112#done:11--type defaultreturns exactly the universal candidates, and the type gate never emitsdefaultfor any input.Verified: --type default returns only universal; gate never emits default
Method: test
test/github-templates.test.mjs::--type default with only typed directory templates is emptyMethod: test
test/github-templates.test.mjs::bare fence-less file is a universal candidate in all six type setsfaff-1112#done:12A directory-form*.mdthat fails to classify appears indroppedwith reasonunclassifiedand in no candidate set.Verified: directory-form unclassified dropped, in no set
Method: test
test/github-templates.test.mjs::directory-form file with no signal is dropped, never universalfaff-1112#done:13A bare.github/ISSUE_TEMPLATE.mdthat fails to classify is a candidate for all six types withtype: null, universal: true.Verified: bare unclassified is universal for all six types
Method: test
test/github-templates.test.mjs::bare fence-less file is a universal candidate in all six type setsfaff-1112#done:14A bare.github/ISSUE_TEMPLATE.mdthat does classify is a single-type candidate withuniversal: false.Verified: bare classifying is single-type, universal false
Method: test
test/github-templates.test.mjs::holdout: a classifying bare file is a single-type candidatefaff-1112#done:15normalize_labelsreturns["bug","triage"]for each oflabels: [bug, triage],labels: ["bug","triage"],labels: '[bug, triage]', and the block-sequence form;["bug"]forlabels: bug; and[]for an absent, empty, boolean or numeric value.Verified: label shapes normalise as specified
Method: test
test/github-templates.test.mjs::labels shapes all normalise to the same listfaff-1112#done:16classifyresolveslabelsbeforenamebeforeabout, and a template namedRFC proposalclassifiesspike, notfeature.Verified: labels>name>about; RFC proposal is spike
Method: test
test/github-templates.test.mjs::signal precedence is labels, then name, then aboutMethod: test
test/github-templates.test.mjs::holdout: RFC proposal classifies spike, not featurefaff-1112#done:17Candidate output is sorted withuniversallast and otherwise by repo-relative path in byte order, so two runs over the same tree produce byte-identical output.Verified: universal last, path byte order, deterministic
Method: test
test/github-templates.test.mjs::ordering, no dedup, non-markdown and nested handlingfaff-1112#done:18config.ymlandconfig.yamlare dropped with reasonexcluded-config, case-insensitively, and the bare-form arm still runs when the directory holds nothing else.Verified: config.yml/yaml dropped case-insensitively; bare arm still runs
Method: test
test/github-templates.test.mjs::config.yml only: dropped as excluded-config, bare arm still evaluatedfaff-1112#done:19A non-.mdfile in the directory is dropped with reasonnot-markdown; a nested directory is skipped with nodroppedentry.Verified: non-.md dropped not-markdown; nested dir silent
Method: test
test/github-templates.test.mjs::ordering, no dedup, non-markdown and nested handlingfaff-1112#done:20An unreadable file is dropped with reasonunreadable, recorded innotes, and the scan completes over the remaining entries.Verified: unreadable dropped, noted, scan continues
Method: test
test/github-templates.test.mjs::an unreadable file is dropped, noted, and the scan continuesfaff-1112#done:21A*.mdfile with no front-matter fence parses to all-null signals rather than reading the markdown body as key lines.Verified: no-fence file parses to null signals
Method: test
test/github-templates.test.mjs::a fence-less body is not read as key linesfaff-1112#done:22Two directory templates classifying to the same type both appear in that type's candidate set — the scan does not deduplicate.Verified: same-type directory templates both kept
Method: test
test/github-templates.test.mjs::ordering, no dedup, non-markdown and nested handlingfaff-1112#done:23faff github-templates scanis reachable from thefaffentrypoint via aCOMMANDSentry.Verified: verb reachable via COMMANDS entry
Method: test
test/github-templates.test.mjs::smoke: bug_report.md lands in the bug set with normalised flow labelsfaff-1112#done:24REGION_MAPmapsgithub-templatestofactoryandREGION_SELFTEST_ARGVinvokes its--selftest;faff regions selftest --region factorypasses.Verified: REGION_MAP + REGION_SELFTEST_ARGV; regions selftest factory passes
Method: command
node plugin/skills/faff/bin/faff regions selftest --region factoryfaff-1112#done:25DISPATCH_SURFACESincli-surface.jscarries agithub-templatessurface declaring thescansubcommand and the--type/--json/--root/--selftestflags.Verified: DISPATCH_SURFACES carries github-templates surface; lint-cli-coverage/doc pass
Method: command
node plugin/skills/faff/bin/faff lint-cli-docfaff-1112#done:26docs/guide/cli.mdcarries a row for the verb andfaff lint-cli-docpasses bidirectionally.Verified: docs/guide/cli.md row; lint-cli-doc passes (139 documented)
Method: command
node plugin/skills/faff/bin/faff lint-cli-docfaff-1112#done:27faff lint-cli-coveragepasses, satisfied bytest/github-templates.test.mjs.Verified: lint-cli-coverage passes (139 covered)
Method: command
node plugin/skills/faff/bin/faff lint-cli-coveragefaff-1112#done:28The spec records a**Chosen:**for the shared-versus-duplicate classifier question, and the module importsFAFF_TYPESrather than re-literalling the taxonomy.Verified: spec records Chosen for the classifier question; module imports FAFF_TYPES
Method: test
test/github-templates.test.mjs::module purity: fixed require set, no literal taxonomy list, one region bannerfaff-1112#done:29The spec records a**Chosen:**for theparseYamlSubsetflow-sequence precursor,labelsstays in scope, andshared-infra.jsis unchanged by this ticket's diff.Verified: spec records Chosen for the flow-sequence precursor; labels in scope; shared-infra.js absent from diff
Method: command
git diff --stat origin/main -- plugin/skills/faff/bin/lib/shared-infra.jsfaff-1112#done:30Two scenarios document thedefault-type path — the empty case and the universal-candidate case — and both are covered by tests.Verified: default-type scenarios (empty + universal) covered by tests
Method: test
test/github-templates.test.mjs::--type default with only typed directory templates is emptyMethod: test
test/github-templates.test.mjs::bare fence-less file is a universal candidate in all six type setsCloses FAFF-1112