Skip to content

feat(github-templates): discover and parse GitHub-native issue templates (FAFF-1112) - #1120

Merged
alechill merged 5 commits into
mainfrom
faff-1112-faff-jotplot-discover-parse-github-native-issue-templates
Oct 24, 2026
Merged

alechill merged 5 commits into
mainfrom
faff-1112-faff-jotplot-discover-parse-github-native-issue-templates

Conversation

@alechill

Copy link
Copy Markdown
Contributor

Summary

Adds faff github-templates scan, a pure offline scanner for a repo's own GitHub issue templates. It reads .github/ISSUE_TEMPLATE/*.md and 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

  • 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, so --type default returns only universal candidates.
  • Unquoted labels: [bug, triage] reads back from parseYamlSubset as a string. The module normalises label shapes locally; shared-infra.js is untouched.
  • The scan never reads tracking.tracker, never touches the network or git, and treats every absence or malformation as a recorded miss at exit 0.
  • Registered as a verb: COMMANDS, REGION_MAP, REGION_SELFTEST_ARGV, DISPATCH_SURFACES, and a docs/guide/cli.md row.

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, lstat keeps 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 run discovered npx tsc --noEmit -p tsconfig.json from the repo root, where no tsconfig.json exists (CI runs it inside plugin/skills/faff). The rung fails regardless of this change. I ran tsc in the right directory and it passes.

Acceptance Criteria

  • faff-1112#done:1 A repo with .github/ISSUE_TEMPLATE/bug_report.md yields that file in the bug candidate 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 labels
  • faff-1112#done:2 The scan produces identical output for the same tree whether tracking.tracker is linear, 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.tracker
  • faff-1112#done:3 No 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 notes
    Method: test test/github-templates.test.mjs::an unreadable file is dropped, noted, and the scan continues
  • faff-1112#done:4 plugin/skills/faff/bin/lib/github-templates.js exists, carries exactly one // === region:factory — github-templates — FAFF‑1112 … === banner, and faff regions check passes.
    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 banner
    Method: command node plugin/skills/faff/bin/faff regions check
  • faff-1112#done:5 The module is plain .js with no .ts sibling, and build-manifest.json is 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:6 Its require set is exactly node:fs, node:path, ./argv, ./shared-infra, ./native-map — no child_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 banner
  • faff-1112#done:7 The six faff-type tokens appear in the module only via FAFF_TYPES imported 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 banner
  • faff-1112#done:8 Every emitted candidate satisfies universal === (type === null) and universal === true implies form === "bare".
    Verified: universal===(type===null), universal implies bare
    Method: test test/github-templates.test.mjs::ordering, no dedup, non-markdown and nested handling
  • faff-1112#done:9 faff 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 set
  • faff-1112#done:10 --type <t> returns every candidate whose type is t plus every universal candidate, 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 handling
    Method: test test/github-templates.test.mjs::holdout: a classifying bare file is a single-type candidate
  • faff-1112#done:11 --type default returns exactly the universal candidates, and the type gate never emits default for 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 empty
    Method: test test/github-templates.test.mjs::bare fence-less file is a universal candidate in all six type sets
  • faff-1112#done:12 A directory-form *.md that fails to classify appears in dropped with reason unclassified and 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 universal
  • faff-1112#done:13 A bare .github/ISSUE_TEMPLATE.md that fails to classify is a candidate for all six types with type: 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 sets
  • faff-1112#done:14 A bare .github/ISSUE_TEMPLATE.md that does classify is a single-type candidate with universal: 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 candidate
  • faff-1112#done:15 normalize_labels returns ["bug","triage"] for each of labels: [bug, triage], labels: ["bug","triage"], labels: '[bug, triage]', and the block-sequence form; ["bug"] for labels: 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 list
  • faff-1112#done:16 classify resolves labels before name before about, and a template named RFC proposal classifies spike, not feature.
    Verified: labels>name>about; RFC proposal is spike
    Method: test test/github-templates.test.mjs::signal precedence is labels, then name, then about
    Method: test test/github-templates.test.mjs::holdout: RFC proposal classifies spike, not feature
  • faff-1112#done:17 Candidate output is sorted with universal last 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 handling
  • faff-1112#done:18 config.yml and config.yaml are dropped with reason excluded-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 evaluated
  • faff-1112#done:19 A non-.md file in the directory is dropped with reason not-markdown; a nested directory is skipped with no dropped entry.
    Verified: non-.md dropped not-markdown; nested dir silent
    Method: test test/github-templates.test.mjs::ordering, no dedup, non-markdown and nested handling
  • faff-1112#done:20 An unreadable file is dropped with reason unreadable, recorded in notes, 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 continues
  • faff-1112#done:21 A *.md file 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 lines
  • faff-1112#done:22 Two 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 handling
  • faff-1112#done:23 faff github-templates scan is reachable from the faff entrypoint via a COMMANDS entry.
    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 labels
  • faff-1112#done:24 REGION_MAP maps github-templates to factory and REGION_SELFTEST_ARGV invokes its --selftest; faff regions selftest --region factory passes.
    Verified: REGION_MAP + REGION_SELFTEST_ARGV; regions selftest factory passes
    Method: command node plugin/skills/faff/bin/faff regions selftest --region factory
  • faff-1112#done:25 DISPATCH_SURFACES in cli-surface.js carries a github-templates surface declaring the scan subcommand and the --type / --json / --root / --selftest flags.
    Verified: DISPATCH_SURFACES carries github-templates surface; lint-cli-coverage/doc pass
    Method: command node plugin/skills/faff/bin/faff lint-cli-doc
  • faff-1112#done:26 docs/guide/cli.md carries a row for the verb and faff lint-cli-doc passes bidirectionally.
    Verified: docs/guide/cli.md row; lint-cli-doc passes (139 documented)
    Method: command node plugin/skills/faff/bin/faff lint-cli-doc
  • faff-1112#done:27 faff lint-cli-coverage passes, satisfied by test/github-templates.test.mjs.
    Verified: lint-cli-coverage passes (139 covered)
    Method: command node plugin/skills/faff/bin/faff lint-cli-coverage
  • faff-1112#done:28 The spec records a **Chosen:** for the shared-versus-duplicate classifier question, and the module imports FAFF_TYPES rather 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 banner
  • faff-1112#done:29 The spec records a **Chosen:** for the parseYamlSubset flow-sequence precursor, labels stays in scope, and shared-infra.js is 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.js
  • faff-1112#done:30 Two scenarios document the default-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 empty
    Method: test test/github-templates.test.mjs::bare fence-less file is a universal candidate in all six type sets

Closes FAFF-1112

…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>
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Ready to land. Run this locally in a real terminal:

faff effects declare --run-dir .faff/anchors/run-20261024-044511-beepboop-list-f66619 --issue FAFF-1112 --step merge <<'EOF'
[{"kind":"merge","target":"pr:1120","reversible":true}]
EOF
faff merge-gate --pr 1120 --issue FAFF-1112 --run-dir .faff/anchors/run-20261024-044511-beepboop-list-f66619 --level L3 \
  --execute --merge-args "--squash --delete-branch"

@alechill
alechill merged commit 8cebceb into main Oct 24, 2026
14 checks passed
@alechill
alechill deleted the faff-1112-faff-jotplot-discover-parse-github-native-issue-templates branch October 24, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant