create-afe-project 0.0.0-stage → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +458 -0
  2. package/LICENSE +21 -0
  3. package/README.md +77 -2
  4. package/bin/create-afe-project.js +123 -0
  5. package/package.json +43 -4
  6. package/src/answers.js +147 -0
  7. package/src/config.js +151 -0
  8. package/src/project.js +217 -0
  9. package/src/render.js +51 -0
  10. package/template/.claude/harness.conf +50 -0
  11. package/template/.claude/hooks/post-edit-check.sh +41 -0
  12. package/template/.claude/hooks/protect-docs.sh +315 -0
  13. package/template/.claude/hooks/session-start.sh +81 -0
  14. package/template/.claude/hooks/stop-check-decisions.sh +25 -0
  15. package/template/.claude/hooks/stop-check-docs.sh +29 -0
  16. package/template/.claude/hooks/stop-check-fast.sh +33 -0
  17. package/template/.claude/hooks/stop-check-template.sh +23 -0
  18. package/template/.claude/hooks/stop-run-project-hooks.sh +27 -0
  19. package/template/.claude/path-classes +102 -0
  20. package/template/.claude/settings.project.json +5 -0
  21. package/template/.claude/settings.template.json +92 -0
  22. package/template/.claude/skills/afe-init/SKILL.md +104 -0
  23. package/template/.claude/skills/create-pr/SKILL.md.jinja +110 -0
  24. package/template/.claude/skills/update-template/SKILL.md +40 -0
  25. package/template/.claude/skills/{% if code_review %}review-code{% endif %}/SKILL.md.jinja +50 -0
  26. package/template/.claude/skills/{% if docs_review %}docs-review{% endif %}/SKILL.md +60 -0
  27. package/template/.claude/skills/{% if dual_review %}dual-review{% endif %}/SKILL.md +58 -0
  28. package/template/.github/workflows/decisions.yml.jinja +57 -0
  29. package/template/.github/workflows/docs.yml.jinja +40 -0
  30. package/template/.github/workflows/{% if code_review %}code-review.yml{% endif %}.jinja +54 -0
  31. package/template/.github/workflows/{% if docs_review %}docs-review.yml{% endif %}.jinja +52 -0
  32. package/template/.github/workflows/{% if fail_without_change %}fail-without-change.yml{% endif %}.jinja +42 -0
  33. package/template/.github/workflows/{% if secret_scan %}secret-scan.yml{% endif %}.jinja +47 -0
  34. package/template/.markdownlint-cli2.jsonc +27 -0
  35. package/template/.template/owned.txt.jinja +65 -0
  36. package/template/AGENTS.md.jinja +91 -0
  37. package/template/CLAUDE.md +3 -0
  38. package/template/README.md.jinja +53 -0
  39. package/template/_gitignore +14 -0
  40. package/template/docs/README.md.jinja +44 -0
  41. package/template/docs/contributing.md.jinja +206 -0
  42. package/template/docs/decisions/adr-template.md +38 -0
  43. package/template/docs/documentation-rules.md.jinja +200 -0
  44. package/template/docs/testing-rules.md +80 -0
  45. package/template/scripts/check-decisions.sh +382 -0
  46. package/template/scripts/check-docs.sh +20 -0
  47. package/template/scripts/check-links.mjs +142 -0
  48. package/template/scripts/check-project.sh +27 -0
  49. package/template/scripts/check-template.sh +88 -0
  50. package/template/scripts/check.sh +21 -0
  51. package/template/scripts/env.sh +29 -0
  52. package/template/scripts/gen-decisions-index.sh +57 -0
  53. package/template/scripts/gen-settings.sh +14 -0
  54. package/template/scripts/lib/classes.sh +89 -0
  55. package/template/scripts/lib/git-base.sh +25 -0
  56. package/template/scripts/lib/harness-conf.sh +48 -0
  57. package/template/scripts/lib/json.mjs +160 -0
  58. package/template/scripts/pr-metrics.sh +80 -0
  59. package/template/scripts/protect-branch.sh +101 -0
  60. package/template/scripts/{% if acceptance_check %}check-acceptance.sh{% endif %} +47 -0
  61. package/template/scripts/{% if fail_without_change %}fail-without-change.sh{% endif %} +88 -0
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bash
2
+ # Acceptance check, the `acceptance_check` module (docs/contributing.md, section 3): every
3
+ # identifier that the section "Acceptance and test plan" of a pull request description names, by
4
+ # ID_PATTERN of .claude/harness.conf, appears as a whole word in at least one test file of
5
+ # TEST_GLOBS. It does not judge whether that test proves the identifier; the review does.
6
+ # scripts/check-decisions.sh --ready runs it. It skips when TEST_GLOBS is empty or the section
7
+ # names no identifier. A project may replace it with a stricter check in scripts/checks.d/.
8
+ #
9
+ # Usage: scripts/check-acceptance.sh --pr-body <file>
10
+ set -uo pipefail
11
+ cd "$(dirname "$0")/.."
12
+ # shellcheck source=scripts/lib/harness-conf.sh
13
+ . scripts/lib/harness-conf.sh
14
+
15
+ body=
16
+ while [ $# -gt 0 ]; do
17
+ case "$1" in
18
+ --pr-body) body="${2:?--pr-body needs a file}"; shift 2 ;;
19
+ *) echo "unknown argument: $1" >&2; exit 2 ;;
20
+ esac
21
+ done
22
+ [ -f "$body" ] || { echo "check-acceptance: --pr-body <file> is required" >&2; exit 2; }
23
+
24
+ echo "== acceptance: every identifier of the description is carried by a test file"
25
+ pattern=$(conf_get ID_PATTERN '(REQ|AC|INV)-[0-9]+')
26
+ globs=$(conf_get TEST_GLOBS)
27
+ [ -n "$globs" ] || { echo "SKIP: TEST_GLOBS is empty in .claude/harness.conf"; exit 0; }
28
+ ids=$(tr -d '\r' < "$body" |
29
+ awk '/^## / {l = $0; sub(/[[:space:]]+$/, "", l); s = (l == "## Acceptance and test plan"); next} s' |
30
+ grep -o -E -- "$pattern" | sort -u)
31
+ [ -n "$ids" ] || { echo "SKIP: the Acceptance section names no identifier"; exit 0; }
32
+ files=$(matching_files "$globs")
33
+
34
+ failures=0
35
+ while IFS= read -r id; do
36
+ hit=$(printf '%s\n' "$files" | while IFS= read -r f; do
37
+ [ -f "$f" ] && grep -q -F -w -- "$id" "$f" 2>/dev/null && { printf '%s\n' "$f"; break; }
38
+ done)
39
+ if [ -n "$hit" ]; then
40
+ echo "ok: $id in $hit"
41
+ else
42
+ echo "FAIL: $id: no test file of TEST_GLOBS carries it (docs/testing-rules.md, section 1)"
43
+ failures=$((failures + 1))
44
+ fi
45
+ done <<< "$ids"
46
+ [ "$failures" -eq 0 ] || { echo "== acceptance check failed: $failures"; exit 1; }
47
+ echo "== acceptance check passed"
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env bash
2
+ # Fail-without-change driver, the `fail_without_change` module (docs/testing-rules.md, section
3
+ # 3): each test file that the change adds, modifies, renames, or copies, by TEST_GLOBS of
4
+ # .claude/harness.conf, must pass with the change and fail on the base: a failure on both sides,
5
+ # such as a missing toolchain, is no evidence. It builds a temporary git worktree at the merge
6
+ # base, copies the change's version of the test files and of FWC_TOOLCHAIN_GLOBS into it, runs
7
+ # FWC_INSTALL there (SETUP when FWC_INSTALL is empty), then FWC_TEST with one test file as its
8
+ # last argument, and reads pass or fail from the exit code, per file: a file that passes on the
9
+ # base fails the check, unless --allow-pass-on-base is given (the pull request label
10
+ # `tests-pass-on-base`). When FWC_CLASSIFIER is set, it receives the log and the test file and
11
+ # exits 0 when the base failed for the expected reason. It skips when TEST_GLOBS or FWC_TEST is
12
+ # empty, or when no test file changed. It runs the project's own commands, locally before the
13
+ # pull request and in the module's workflow (.github/workflows/fail-without-change.yml).
14
+ #
15
+ # Usage: scripts/fail-without-change.sh [--base <ref>] [--allow-pass-on-base]
16
+ set -uo pipefail
17
+ cd "$(dirname "$0")/.."
18
+ # shellcheck source=scripts/lib/git-base.sh
19
+ . scripts/lib/git-base.sh
20
+ # shellcheck source=scripts/lib/harness-conf.sh
21
+ . scripts/lib/harness-conf.sh
22
+
23
+ base= allow=0
24
+ while [ $# -gt 0 ]; do
25
+ case "$1" in
26
+ --base) base="${2:?--base needs a ref}"; shift 2 ;;
27
+ --allow-pass-on-base) allow=1; shift ;;
28
+ *) echo "unknown argument: $1" >&2; exit 2 ;;
29
+ esac
30
+ done
31
+
32
+ echo "== fail-without-change: new and changed tests fail on the base"
33
+ globs=$(conf_get TEST_GLOBS)
34
+ test_cmd=$(conf_get FWC_TEST)
35
+ [ -n "$globs" ] && [ -n "$test_cmd" ] || { echo "SKIP: TEST_GLOBS or FWC_TEST is empty in .claude/harness.conf"; exit 0; }
36
+ mb=$(resolve_base "$base") || exit 2
37
+
38
+ # The test files the change adds or modifies, and the toolchain files to copy with them.
39
+ changed=$( { git diff --name-only --diff-filter=ACMR "$mb"; git ls-files --others --exclude-standard; } | sort -u)
40
+ tests=$(matching_files "$globs" | grep -x -F -f <(printf '%s\n' "$changed"))
41
+ [ -n "$tests" ] || { echo "SKIP: the change adds or modifies no test file"; exit 0; }
42
+ toolchain=
43
+ tc_globs=$(conf_get FWC_TOOLCHAIN_GLOBS)
44
+ [ -n "$tc_globs" ] && toolchain=$(matching_files "$tc_globs")
45
+
46
+ work=$(mktemp -d)
47
+ wt="$work/base"
48
+ git worktree add -q --detach "$wt" "$mb" || { echo "FAIL: cannot create a worktree at the base"; exit 2; }
49
+ trap 'git worktree remove --force "$wt" >/dev/null 2>&1; rm -rf "$work"' EXIT
50
+ printf '%s\n' "$tests" "$toolchain" | sed '/^$/d' | sort -u | while IFS= read -r f; do
51
+ mkdir -p "$wt/$(dirname "$f")" && cp -p "$f" "$wt/$f"
52
+ done
53
+
54
+ install=$(conf_get FWC_INSTALL "$(conf_get SETUP)")
55
+ if [ -n "$install" ] && ! (cd "$wt" && bash -c "$install") < /dev/null > "$work/install.log" 2>&1; then
56
+ echo "FAIL: FWC_INSTALL failed on the base: $install"
57
+ tail -n 20 "$work/install.log"
58
+ exit 1
59
+ fi
60
+
61
+ failures=0
62
+ classifier=$(conf_get FWC_CLASSIFIER)
63
+ while IFS= read -r t; do
64
+ # shellcheck disable=SC2016
65
+ if ! bash -c "$test_cmd \"\$1\"" fwc "$t" < /dev/null > "$work/now.log" 2>&1; then
66
+ echo "FAIL: $t does not pass with the change, so its failure on the base proves nothing:"
67
+ tail -n 20 "$work/now.log"
68
+ failures=$((failures + 1))
69
+ continue
70
+ fi
71
+ # shellcheck disable=SC2016
72
+ if (cd "$wt" && bash -c "$test_cmd \"\$1\"" fwc "$t") < /dev/null > "$work/test.log" 2>&1; then
73
+ if [ "$allow" = 1 ]; then
74
+ echo "PASS ON BASE, allowed by --allow-pass-on-base: $t"
75
+ else
76
+ echo "FAIL: $t passes on the base; a new or changed test must fail without the change"
77
+ failures=$((failures + 1))
78
+ fi
79
+ elif [ -n "$classifier" ] && ! bash -c "$classifier \"\$1\" \"\$2\"" fwc "$work/test.log" "$t" < /dev/null > "$work/class.log" 2>&1; then
80
+ echo "FAIL: $t fails on the base for another reason than the change (FWC_CLASSIFIER):"
81
+ tail -n 20 "$work/class.log"
82
+ failures=$((failures + 1))
83
+ else
84
+ echo "ok: $t passes with the change and fails on the base"
85
+ fi
86
+ done <<< "$tests"
87
+ [ "$failures" -eq 0 ] || { echo "== fail-without-change failed: $failures"; exit 1; }
88
+ echo "== fail-without-change passed"