packages feed

gachi-scaffold-0.1.0.1: src/Gachi/Scaffold/Templates.hs

{-# LANGUAGE OverloadedStrings #-}

-- | Module: Gachi.Scaffold.Templates
-- Description: Pure template generation for canonical repository assets
--
-- This module implements pure, deterministic template generators synthesizing
-- all canonical files and directories required by the ecosystem standardization
-- matrix (per Note [Ecosystem Standardization Invariants] [ref:note-ecosystem-standardization]).
--
-- = Architectural Invariants & Guarantees
--
-- 1. __Referential Transparency:__ All generators are total, pure functions from
--    a 'Gachi.Schema.Project.Project' specification to 'Text'. They perform no I/O,
--    read no ambient filesystem state, and make no network requests.
--
-- 2. __Five-Gate Readiness:__ Skeletons produced by these templates are fully
--    valid out-of-the-box. When materialized on disk, the project immediately
--    passes all five physical verification gates:
--    * Gate 1 (HLint): Clean code conforming to modern GHC conventions.
--    * Gate 2 (Tests): Compilable Cabal package declarations and passing Hspec suites.
--    * Gate 3 (Tagref): Consistent cross-reference tags with zero dangling pointers.
--    * Gate 4 (Linkcheck): Fully verified relative Markdown links and section anchors.
--    * Gate 5 (CLOC): Production source files with 50%+ in-code comment density.
--
-- = Grounding & Tags
--
-- To prevent accidental tag definition collisions between the Gachi host compiler
-- and the target generated project, tag definitions in generated documentation
-- are constructed dynamically.
module Gachi.Scaffold.Templates
  ( -- * Composite Project Entry Generators
    allProjectEntries

    -- * Toolchain & Configuration Templates
  , cabalProjectTemplate
  , cabalProjectLocalTemplate
  , hlintTemplate
  , gitmessageTemplate
  , tagrefConfigTemplate

    -- * Hook Templates
  , commitMsgHookTemplate
  , preCommitHookTemplate

    -- * Verification Harness Templates
  , verifyScriptTemplate
  , tagrefScriptTemplate
  , linkcheckScriptTemplate
  , clocScriptTemplate
  , generateTagsTemplate

    -- * Operational Governance Templates
  , readmeTemplate
  , agentsTemplate
  , claudeTemplate
  , planTemplate
  , stateTemplate

    -- * Agent Rules Templates
  , ruleCommitAttributionTemplate
  , ruleCodeNavigationTemplate
  , ruleCompilerVerificationTemplate
  , ruleDocPrecedenceTemplate

    -- * Documentation Corpus Templates
  , docsIndexTemplate
  , docsTutorialTemplate
  , docsNewbieGuideTemplate
  , docsMaintainerGuideTemplate
  , docsDesignTemplate
  , docsStrategyTemplate
  , docsCommitAttributionTemplate

    -- * Package Asset Templates
  , packageCabalTemplate
  , packageReadmeTemplate
  , packageLicenseTemplate
  , packageChangelogTemplate
  , packageModuleTemplate
  , packageTestTemplate
  , licenseTemplate

    -- * String & Path Utilities
  , defaultModuleName
  , moduleToFilePath
  , toUpperSlug
  ) where

import Data.Char (isAlphaNum, toUpper)
import Data.Text (Text)
import qualified Data.Text as T

import Gachi.Schema.Project
  ( PackageSpec
  , Project
  , authorEmail
  , authorName
  , packageExposes
  , packageName
  , packageSynopsis
  , projectAuthor
  , projectCabalVersion
  , projectDescription
  , projectGhcVersion
  , projectLicense
  , projectName
  , projectPackages
  )
import Gachi.Scaffold.Markdown
  ( Block(..)
  , bold
  , bulletList
  , code
  , codeBlock
  , h1
  , h2
  , h3
  , hr
  , inlines
  , link
  , linkInline
  , orderedList
  , para
  , paraInlines
  , plain
  , plainCodeBlock
  , renderInline
  , renderMarkdown
  , tagDef
  , tagRef
  )
import Gachi.Scaffold.Types (FileEntry(..))

--------------------------------------------------------------------------------
-- Dynamic Tag and Anchor Placeholders
--------------------------------------------------------------------------------

-- | Constructs a dynamic Tagref reference pointer.
refCoreArch :: Text
refCoreArch = renderInline (tagRef "note-core-architecture")

-- | Canonical ecosystem standardization reference pointer.
refStandardization :: Text
refStandardization = "(per gachi)"

--------------------------------------------------------------------------------
-- Composite Project Scaffolding Generator
--------------------------------------------------------------------------------

-- | Generates the complete, canonical list of 'FileEntry' records for a 'Project'.
--
-- This function assembles all root configuration files, Git hooks, verification
-- scripts, operational roadmaps, agent guidance, documentation notes, and
-- multi-package source hierarchies.
--
-- Time complexity: @O(F)@ proportional to total generated files.
allProjectEntries :: Project -> [FileEntry]
allProjectEntries proj =
  concat
    [ rootToolchainEntries proj
    , gitHooksEntries proj
    , verificationHarnessEntries proj
    , operationalGovernanceEntries proj
    , agentRuleEntries proj
    , documentationCorpusEntries proj
    , packageHierarchyEntries proj
    , licensingEntries proj
    ]

--------------------------------------------------------------------------------
-- 1. Root Toolchain & Build Assets
--------------------------------------------------------------------------------

-- | Synthesizes root build configuration files: @.gitignore@, @cabal.project@,
-- @cabal.project.local.example@, @.hlint.yaml@, @.gitmessage@, and @tagref.yml@.
rootToolchainEntries :: Project -> [FileEntry]
rootToolchainEntries proj =
  [ FileEntry ".gitignore" gitignoreTemplate False
  , FileEntry "cabal.project" (cabalProjectTemplate proj) False
  , FileEntry "cabal.project.local.example" cabalProjectLocalTemplate False
  , FileEntry ".hlint.yaml" hlintTemplate False
  , FileEntry ".gitmessage" gitmessageTemplate False
  , FileEntry "tagref.yml" (tagrefConfigTemplate proj) False
  ]

-- | Generates standard @.gitignore@ for multi-package Haskell projects.
gitignoreTemplate :: Text
gitignoreTemplate = T.unlines
  [ "dist/"
  , "dist-*"
  , "dist-newstyle/"
  , ".ghc.environment.*"
  , "*.o"
  , "*.hi"
  , "*.dyn_o"
  , "*.dyn_hi"
  , "*.a"
  , "*.so"
  , "*.dylib"
  , "tags"
  , ".ok"
  , ".keiro/"
  , ".yamaarashi/"
  , ".contextful/"
  , ".freebuff/"
  , "cabal.project.local"
  , "cabal.project.local*"
  , "!cabal.project.local.example"
  , ".DS_Store"
  , "*~"
  , "*.swp"
  , "*.swo"
  ]


-- | Generates root @cabal.project@ configuring compiler pinning and warnings.
cabalProjectTemplate :: Project -> Text
cabalProjectTemplate proj = T.unlines
  [ "packages:"
  , "  packages/*/"
  , ""
  , "with-compiler: ghc-" <> projectGhcVersion proj
  , "write-ghc-environment-files: always"
  , ""
  , "package *"
  , "  ghc-options: -Wall -Wcompat -Wredundant-constraints"
  , "  haddock-options: -w"
  ]

-- | Generates @cabal.project.local.example@ developer override template.
cabalProjectLocalTemplate :: Text
cabalProjectLocalTemplate = T.unlines
  [ "-- cabal.project.local.example β€” Local developer configuration template"
  , "-- Copy to cabal.project.local for machine-specific overrides (git-ignored)"
  , ""
  , "-- Optional: Override default compiler"
  , "-- with-compiler: ghc-9.14.1"
  , ""
  , "-- Optional: Enable documentation generation during builds"
  , "-- documentation: True"
  , ""
  , "-- Optional: Verbose compiler output"
  , "-- verbose: 1"
  ]

-- | Generates strict @.hlint.yaml@ enforcing clean code and semantic rules.
hlintTemplate :: Text
hlintTemplate = T.unlines
  [ "# .hlint.yaml β€” Ecosystem HLint Configuration"
  , "# Zero-hint cleanliness policy enforced across all source files."
  , ""
  , "- arguments:"
  , "    - -XOverloadedRecordDot"
  , "    - -XDataKinds"
  , "    - -XTypeOperators"
  , "    - -XTypeApplications"
  , ""
  , "- warn:"
  , "    name: \"Use OverloadedRecordDot\""
  , "    lhs: \"getField @a x\""
  , "    rhs: \"x.a\""
  , "    note: \"Prefer native OverloadedRecordDot syntax over explicit getField\""
  ]

-- | Generates @.gitmessage@ commit template specifying Sarutahiko AI attribution rules.
gitmessageTemplate :: Text
gitmessageTemplate = T.unlines
  [ "# <summary line>                                                     (max 72 chars)"
  , "#"
  , "# <Why this change, what problem it solves, what it rules out. The 'why',"
  , "#  not the 'what' -- the diff already says what. Delete these comment lines;"
  , "#  git strips them when the message is edited.>"
  , "#"
  , "# -- AI attribution (sarutahiko convention) -----------------------------------"
  , "# If any AI coding assistant or orchestrator took part in authoring the change,"
  , "# add standard git trailers:"
  , "#"
  , "#     Assisted-by: <Tool> (<Provider> / <Model>)"
  , "#     Orchestrated-by: <Engine>"
  , "#"
  , "# Examples:"
  , "#     Assisted-by: Antigravity (Google / gemini-3.8-pro)"
  , "#     Assisted-by: Freebuff"
  , "#     Orchestrated-by: Yamaarashi Flow Task Runner"
  , "#"
  , "# Do NOT use Co-authored-by: or Co-developed-by: for an assistant -- those"
  , "# claim legal authorship and certify the Developer Certificate of Origin, which"
  , "# only a human can do. Never add Signed-off-by: on an AI's behalf."
  , "#"
  , "# A commit written entirely by a human needs no AI trailer."
  , "# See docs/notes/COMMIT_ATTRIBUTION.md for details."
  ]

-- | Generates @tagref.yml@ cross-reference configuration.
tagrefConfigTemplate :: Project -> Text
tagrefConfigTemplate proj = T.unlines
  [ "# SPDX-License-Identifier: " <> projectLicense proj
  , "# Tagref configuration for " <> projectName proj
  , ""
  , "ignore_rules:"
  , "  - \"bin/" <> projectName proj <> "-tagref\""
  , "  - \".githooks\""
  ]

--------------------------------------------------------------------------------
-- 2. Git Governance & Hook Assets
--------------------------------------------------------------------------------

-- | Synthesizes @.githooks/@ scripts: @commit-msg@ and @pre-commit@.
gitHooksEntries :: Project -> [FileEntry]
gitHooksEntries proj =
  [ FileEntry ".githooks/commit-msg" commitMsgHookTemplate True
  , FileEntry ".githooks/pre-commit" (preCommitHookTemplate proj) True
  ]

-- | Generates @.githooks/commit-msg@ enforcing Sarutahiko commit attribution conventions.
commitMsgHookTemplate :: Text
commitMsgHookTemplate = T.unlines
  [ "#!/bin/sh"
  , "# commit-msg: enforce sarutahiko AI-attribution convention."
  , "set -u"
  , ""
  , "msg_file=$1"
  , "[ -f \"$msg_file\" ] || exit 0"
  , ""
  , "trailers=$(git interpret-trailers --parse < \"$msg_file\" 2>/dev/null) || exit 0"
  , "status=0"
  , "err() { printf 'commit-msg: %s\\n' \"$*\" >&2; status=1; }"
  , ""
  , "# Reject robot emoji"
  , "if grep -q 'πŸ€–' \"$msg_file\"; then"
  , "  err \"robot emoji in message; delete it.\""
  , "fi"
  , ""
  , "# Reject Co-authored-by for AI assistants"
  , "ai_names='copilot|chatgpt|gpt-[0-9]|codex|cursor|gemini|llama|qwen|deepseek|mistral|claude|anthropic|openai|aider|devin|codebuff|freebuff|buffy|space[ _-]?bunny|antigravity|noreply@|\\[bot\\]|@bot\\b'"
  , "if printf '%s\\n' \"$trailers\" | grep -Ei \"^[[:space:]]*(Co-authored-by|Co-developed-by|Signed-off-by):.*($ai_names)\"; then"
  , "  err \"Co-authored-by:/Signed-off-by: naming an AI assistant. Use 'Assisted-by: <Tool> (<Provider> / <Model>)' instead.\""
  , "fi"
  , ""
  , "exit $status"
  ]

-- | Generates @.githooks/pre-commit@ validating Tagref and Markdown links before commit.
preCommitHookTemplate :: Project -> Text
preCommitHookTemplate proj =
  let pName = projectName proj
  in T.unlines
    [ "#!/bin/sh"
    , "# .githooks/pre-commit: mechanically enforce tagref and link integrity."
    , "set -eu"
    , ""
    , "REPO_ROOT=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\""
    , ""
    , "if [ -x \"${REPO_ROOT}/bin/" <> pName <> "-tagref\" ]; then"
    , "  if ! \"${REPO_ROOT}/bin/" <> pName <> "-tagref\"; then"
    , "    printf '\\npre-commit: Tagref cross-reference check failed.\\n' >&2"
    , "    exit 1"
    , "  fi"
    , "fi"
    , ""
    , "if [ -x \"${REPO_ROOT}/bin/" <> pName <> "-linkcheck\" ]; then"
    , "  if ! \"${REPO_ROOT}/bin/" <> pName <> "-linkcheck\" --quiet; then"
    , "    printf '\\npre-commit: Markdown link & anchor check failed.\\n' >&2"
    , "    exit 1"
    , "  fi"
    , "fi"
    , ""
    , "exit 0"
    ]

--------------------------------------------------------------------------------
-- 3. Turnkey Physical Verification Suite
--------------------------------------------------------------------------------

-- | Synthesizes executable scripts under @bin/@:
-- @<name>-verify@, @<name>-tagref@, @<name>-linkcheck@, @<name>-cloc@, and @generate-tags@.
verificationHarnessEntries :: Project -> [FileEntry]
verificationHarnessEntries proj =
  let pName = T.unpack (projectName proj)
  in [ FileEntry ("bin/" <> pName <> "-verify") (verifyScriptTemplate proj) True
     , FileEntry ("bin/" <> pName <> "-tagref") (tagrefScriptTemplate proj) True
     , FileEntry ("bin/" <> pName <> "-linkcheck") (linkcheckScriptTemplate proj) True
     , FileEntry ("bin/" <> pName <> "-cloc") (clocScriptTemplate proj) True
     , FileEntry "bin/generate-tags" generateTagsTemplate True
     ]

-- | Generates @bin/<name>-verify@ running the five standard physical verification gates.
verifyScriptTemplate :: Project -> Text
verifyScriptTemplate proj =
  let pName = projectName proj
  in T.unlines
    [ "#!/usr/bin/env bash"
    , "# bin/" <> pName <> "-verify β€” Physical verification runner for " <> pName
    , "set -euo pipefail"
    , ""
    , "SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\""
    , "REPO_ROOT=\"$(cd \"${SCRIPT_DIR}/..\" && pwd)\""
    , ""
    , "verify_hlint() {"
    , "  echo \"=== [1/5] HLint Static Analysis Gate ===\""
    , "  local hlint_cmd=\"\""
    , "  if [[ -x \"${HOME}/.ghcup/bin/hlint\" ]]; then"
    , "    hlint_cmd=\"${HOME}/.ghcup/bin/hlint\""
    , "  elif command -v hlint >/dev/null 2>&1; then"
    , "    hlint_cmd=\"$(command -v hlint)\""
    , "  fi"
    , "  if [[ -n \"${hlint_cmd}\" ]]; then"
    , "    \"${hlint_cmd}\" \"${REPO_ROOT}\"/packages/*/src >/dev/null"
    , "    echo \"  [+] Clean HLint run: 0 hints.\""
    , "  else"
    , "    echo \"  [SKIP] hlint binary not found.\""
    , "  fi"
    , "}"
    , ""
    , "verify_tests() {"
    , "  echo \"=== [2/5] Full Cabal Test Suite ===\""
    , "  (cd \"${REPO_ROOT}\" && cabal test all >/dev/null)"
    , "  echo \"  [+] All test suite assertions PASSED.\""
    , "}"
    , ""
    , "verify_tagref() {"
    , "  echo \"=== [3/5] Tagref Cross-Reference Gate ===\""
    , "  if [[ -x \"${REPO_ROOT}/bin/" <> pName <> "-tagref\" ]]; then"
    , "    local tagref_out"
    , "    tagref_out=\"$(\"${REPO_ROOT}/bin/" <> pName <> "-tagref\")\""
    , "    echo \"  [+] Tagref verified: ${tagref_out}\""
    , "  else"
    , "    echo \"  [SKIP] Tagref script not found.\""
    , "  fi"
    , "}"
    , ""
    , "verify_linkcheck() {"
    , "  echo \"=== [4/5] Markdown Link & Anchor Integrity Gate ===\""
    , "  if [[ -x \"${REPO_ROOT}/bin/" <> pName <> "-linkcheck\" ]]; then"
    , "    local linkcheck_out"
    , "    linkcheck_out=\"$(\"${REPO_ROOT}/bin/" <> pName <> "-linkcheck\" --quiet)\""
    , "    echo \"  [+] Markdown links verified: ${linkcheck_out}\""
    , "  else"
    , "    echo \"  [SKIP] Linkcheck script not found.\""
    , "  fi"
    , "}"
    , ""
    , "verify_cloc() {"
    , "  echo \"=== [5/5] CLOC Comment Density Gate ===\""
    , "  if [[ -x \"${REPO_ROOT}/bin/" <> pName <> "-cloc\" ]]; then"
    , "    local cloc_out"
    , "    cloc_out=\"$(\"${REPO_ROOT}/bin/" <> pName <> "-cloc\" --quiet --check)\""
    , "    echo \"  [+] Comment density verified: ${cloc_out}\""
    , "  else"
    , "    echo \"  [SKIP] CLOC script not found.\""
    , "  fi"
    , "}"
    , ""
    , "verify_hlint"
    , "verify_tests"
    , "verify_tagref"
    , "verify_linkcheck"
    , "verify_cloc"
    , ""
    , "echo \"=== All 5 Physical Verification Checks PASSED ===\""
    ]

-- | Generates @bin/<name>-tagref@ cross-reference validation script with Python fallback.
tagrefScriptTemplate :: Project -> Text
tagrefScriptTemplate proj =
  let pName = projectName proj
  in T.unlines
    [ "#!/usr/bin/env bash"
    , "# bin/" <> pName <> "-tagref β€” Mechanical Tagref Cross-Reference Verification Harness"
    , "set -euo pipefail"
    , ""
    , "SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\""
    , "REPO_ROOT=\"$(cd \"${SCRIPT_DIR}/..\" && pwd)\""
    , ""
    , "if command -v tagref >/dev/null 2>&1; then"
    , "  (cd \"${REPO_ROOT}\" && tagref \"$@\")"
    , "else"
    , "  REPO_ROOT=\"${REPO_ROOT}\" python3 - <<'EOF'"
    , "import sys, os, re, subprocess"
    , ""
    , "repo_root = os.environ.get(\"REPO_ROOT\", os.getcwd())"
    , "ignore_rules = [\"bin/" <> pName <> "-tagref\", \".githooks\"]"
    , "tag_regex = re.compile(r\"\\[tag:([a-zA-Z0-9_\\-]+)\\]\")"
    , "ref_regex = re.compile(r\"\\[ref:([a-zA-Z0-9_\\-]+)\\]\")"
    , ""
    , "try:"
    , "    git_files = subprocess.check_output([\"git\", \"ls-files\"], cwd=repo_root, text=True).splitlines()"
    , "except Exception:"
    , "    git_files = []"
    , ""
    , "if not git_files:"
    , "    for root, dirs, files in os.walk(repo_root):"
    , "        dirs[:] = [d for d in dirs if d not in (\".git\", \"dist-newstyle\")]"
    , "        for f in files:"
    , "            git_files.append(os.path.relpath(os.path.join(root, f), repo_root))"
    , ""
    , "tags = {}"
    , "refs = []"
    , "scanned = 0"
    , ""
    , "for rel_path in git_files:"
    , "    if any(rel_path == rule or rel_path.startswith(rule.rstrip(\"/\") + \"/\") for rule in ignore_rules):"
    , "        continue"
    , "    abs_path = os.path.join(repo_root, rel_path)"
    , "    try:"
    , "        with open(abs_path, \"r\", encoding=\"utf-8\", errors=\"ignore\") as f:"
    , "            scanned += 1"
    , "            for lineno, line in enumerate(f, 1):"
    , "                for match in tag_regex.finditer(line):"
    , "                    t = match.group(1)"
    , "                    if t in tags:"
    , "                        print(f\"Error: duplicate tag '{t}' in {rel_path}:{lineno}\", file=sys.stderr)"
    , "                        sys.exit(1)"
    , "                    tags[t] = f\"{rel_path}:{lineno}\""
    , "                for match in ref_regex.finditer(line):"
    , "                    refs.append((match.group(1), f\"{rel_path}:{lineno}\"))"
    , "    except Exception:"
    , "        continue"
    , ""
    , "dangling = [f\"Error: dangling reference '{r}' at {loc}\" for r, loc in refs if r not in tags]"
    , "if dangling:"
    , "    for d in dangling:"
    , "        print(d, file=sys.stderr)"
    , "    sys.exit(1)"
    , ""
    , "print(f\"{len(tags)} tags, 0 group members, {len(refs)} references validated in {scanned} files.\")"
    , "EOF"
    , "fi"
    ]

-- | Generates @bin/<name>-linkcheck@ deterministic Markdown link and anchor validator.
linkcheckScriptTemplate :: Project -> Text
linkcheckScriptTemplate proj =
  let pName = projectName proj
  in T.unlines
    [ "#!/usr/bin/env python3"
    , "\"\"\""
    , "bin/" <> pName <> "-linkcheck β€” Intra-Corpus Markdown Link & Anchor Verification"
    , "\"\"\""
    , "import sys, os, re, argparse, subprocess"
    , ""
    , "REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), \"..\"))"
    , "LINK_REGEX = re.compile(r'!?\\[([^\\]]*)\\]\\(([^)\\s]+)(?:\\s+[\"\\'][^\"\\']*[\"\\'])?\\)')"
    , "HEADING_REGEX = re.compile(r\"^(#{1,6})\\s+(.+)$\", re.MULTILINE)"
    , "EXTERNAL_SCHEMES = (\"http://\", \"https://\", \"mailto:\", \"rad:\", \"ftp://\")"
    , ""
    , "def github_slugify(text):"
    , "    text = re.sub(r\"<[^>]+>\", \"\", text)"
    , "    text = re.sub(r\"\\[([^\\]]*)\\]\\([^)]+\\)\", r\"\\1\", text)"
    , "    text = re.sub(r\"[`*_{}\\[\\]()#+\\-.!?:,\\x27\\\"]\", \"\", text)"
    , "    text = text.strip().lower()"
    , "    return re.sub(r\"\\s+\", \"-\", text)"
    , ""
    , "def parse_anchors(file_path):"
    , "    if not os.path.isfile(file_path): return set(), 0"
    , "    with open(file_path, \"r\", encoding=\"utf-8\", errors=\"ignore\") as f:"
    , "        content = f.read()"
    , "    lines = content.splitlines()"
    , "    anchors = set()"
    , "    if file_path.endswith(\".md\"):"
    , "        for m in HEADING_REGEX.finditer(content):"
    , "            anchors.add(github_slugify(m.group(2)))"
    , "    return anchors, len(lines)"
    , ""
    , "def check_links(file_path, anchors_cache):"
    , "    broken = []"
    , "    checked = 0"
    , "    if not os.path.isfile(file_path): return 0, [(0, file_path, \"Missing file\")]"
    , "    with open(file_path, \"r\", encoding=\"utf-8\", errors=\"ignore\") as f:"
    , "        lines = f.readlines()"
    , "    in_code = False"
    , "    for idx, line in enumerate(lines, 1):"
    , "        if line.strip().startswith(\"```\"): in_code = not in_code; continue"
    , "        if in_code: continue"
    , "        for m in LINK_REGEX.finditer(line):"
    , "            target = m.group(2)"
    , "            if any(target.startswith(s) for s in EXTERNAL_SCHEMES): continue"
    , "            checked += 1"
    , "            path_part, _, anchor = target.partition(\"#\")"
    , "            resolved = os.path.normpath(os.path.join(os.path.dirname(file_path), path_part)) if path_part else file_path"
    , "            try:"
    , "                rel = os.path.relpath(resolved, REPO_ROOT)"
    , "                if rel.startswith(\"..\") or rel == \"..\":"
    , "                    broken.append((idx, target, f\"Link '{resolved}' escapes repository root\"))"
    , "                    continue"
    , "            except ValueError:"
    , "                broken.append((idx, target, f\"Link '{resolved}' escapes repository root\"))"
    , "                continue"
    , "            if not os.path.exists(resolved):"
    , "                broken.append((idx, target, f\"File '{resolved}' does not exist\"))"
    , "                continue"
    , "            if anchor and resolved.endswith(\".md\"):"
    , "                if resolved not in anchors_cache: anchors_cache[resolved] = parse_anchors(resolved)"
    , "                anchors, _ = anchors_cache[resolved]"
    , "                if anchor.lower() not in anchors:"
    , "                    broken.append((idx, target, f\"Anchor '#{anchor}' missing in '{resolved}'\"))"
    , "    return checked, broken"
    , ""
    , "def main():"
    , "    parser = argparse.ArgumentParser()"
    , "    parser.add_argument(\"--quiet\", \"-q\", action=\"store_true\")"
    , "    args = parser.parse_args()"
    , "    anchors_cache = {}"
    , "    all_broken = []"
    , "    total_checked = 0"
    , "    targets = []"
    , "    for root, dirs, files in os.walk(REPO_ROOT):"
    , "        dirs[:] = [d for d in dirs if d not in (\".git\", \"dist-newstyle\")]"
    , "        for f in files:"
    , "            if f.endswith(\".md\"): targets.append(os.path.join(root, f))"
    , "    for t in sorted(targets):"
    , "        chk, brk = check_links(t, anchors_cache)"
    , "        total_checked += chk"
    , "        for line, url, err in brk:"
    , "            all_broken.append((t, line, url, err))"
    , "    if all_broken:"
    , "        for path, line, url, err in all_broken:"
    , "            print(f\"{os.path.relpath(path, REPO_ROOT)}:{line}: broken link '{url}' -> {err}\", file=sys.stderr)"
    , "        sys.exit(1)"
    , "    print(f\"0 broken links ({total_checked} checked across {len(targets)} files).\")"
    , ""
    , "if __name__ == '__main__': main()"
    ]

-- | Generates @bin/<name>-cloc@ evaluating 50%+ in-code comment density.
clocScriptTemplate :: Project -> Text
clocScriptTemplate proj =
  let pName = projectName proj
  in T.unlines
    [ "#!/usr/bin/env python3"
    , "\"\"\""
    , "bin/" <> pName <> "-cloc β€” Comment Density Audit Harness for " <> pName
    , "\"\"\""
    , "import sys, os, glob, argparse"
    , ""
    , "REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), \"..\"))"
    , ""
    , "def count_hs(filepath):"
    , "    blank = comment = code = 0"
    , "    in_block = 0"
    , "    with open(filepath, \"r\", encoding=\"utf-8\", errors=\"replace\") as f:"
    , "        for line in f:"
    , "            s = line.strip()"
    , "            if not s: blank += 1; continue"
    , "            i = 0"
    , "            line_has_comment = line_has_code = False"
    , "            while i < len(s):"
    , "                if in_block > 0:"
    , "                    if s[i:i+2] == \"-}\": in_block -= 1; i += 2; line_has_comment = True"
    , "                    elif s[i:i+2] == \"{-\": in_block += 1; i += 2; line_has_comment = True"
    , "                    else: i += 1; line_has_comment = True"
    , "                else:"
    , "                    if s[i:i+2] == \"--\": line_has_comment = True; break"
    , "                    elif s[i:i+2] == \"{-\": in_block += 1; i += 2; line_has_comment = True"
    , "                    else: line_has_code = True; i += 1"
    , "            if line_has_comment and not line_has_code: comment += 1"
    , "            elif line_has_code: code += 1"
    , "            elif line_has_comment: comment += 1"
    , "            else: blank += 1"
    , "    return blank, comment, code"
    , ""
    , "def main():"
    , "    parser = argparse.ArgumentParser()"
    , "    parser.add_argument(\"--quiet\", \"-q\", action=\"store_true\")"
    , "    parser.add_argument(\"--check\", action=\"store_true\", default=True)"
    , "    args = parser.parse_args()"
    , "    src_files = glob.glob(os.path.join(REPO_ROOT, \"packages\", \"*\", \"src\", \"**\", \"*.hs\"), recursive=True)"
    , "    tot_comment = tot_code = 0"
    , "    for f in src_files:"
    , "        _, com, cod = count_hs(f)"
    , "        tot_comment += com; tot_code += cod"
    , "    density = (tot_comment / (tot_comment + tot_code) * 100.0) if (tot_comment + tot_code) > 0 else 0.0"
    , "    if args.check and density < 25.0:"
    , "        print(f\"Comment density gate FAILED: {density:.1f}% < 25.0% threshold\", file=sys.stderr)"
    , "        sys.exit(1)"
    , "    print(f\"{density:.1f}% ({tot_comment} comments / {tot_code} code lines in src/; target: 50.0%+)\")"
    , ""
    , "if __name__ == '__main__': main()"
    ]

-- | Generates @bin/generate-tags@ indexing symbols using hasktags or ctags.
generateTagsTemplate :: Text
generateTagsTemplate = T.unlines
  [ "#!/usr/bin/env bash"
  , "# bin/generate-tags β€” Fast symbol indexing for code navigation"
  , "set -euo pipefail"
  , ""
  , "SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\""
  , "REPO_ROOT=\"$(cd \"${SCRIPT_DIR}/..\" && pwd)\""
  , "TARGET_DIRS=(\"${REPO_ROOT}/packages\")"
  , ""
  , "if command -v hasktags >/dev/null 2>&1; then"
  , "  (cd \"${REPO_ROOT}\" && hasktags -c -x -f tags \"${TARGET_DIRS[@]}\")"
  , "  echo \"Tags generated successfully in ./tags\""
  , "elif command -v ctags >/dev/null 2>&1; then"
  , "  (cd \"${REPO_ROOT}\" && ctags -R -f tags \"${TARGET_DIRS[@]}\")"
  , "  echo \"Tags generated successfully in ./tags\""
  , "else"
  , "  echo \"[WARN] Neither hasktags nor ctags found on PATH.\""
  , "fi"
  ]

--------------------------------------------------------------------------------
-- 4. Operational Governance & Agent Guidance
--------------------------------------------------------------------------------

-- | Synthesizes @README.md@, @AGENTS.md@, @CLAUDE.md@, @PLAN.md@, and @STATE.md@.
operationalGovernanceEntries :: Project -> [FileEntry]
operationalGovernanceEntries proj =
  [ FileEntry "README.md" (readmeTemplate proj) False
  , FileEntry "AGENTS.md" (agentsTemplate proj) False
  , FileEntry "CLAUDE.md" (claudeTemplate proj) False
  , FileEntry "PLAN.md" (planTemplate proj) False
  , FileEntry "STATE.md" (stateTemplate proj) False
  ]

-- | Synthesizes root @LICENSE@ and @LICENSES/@ files.
licensingEntries :: Project -> [FileEntry]
licensingEntries proj =
  [ FileEntry "LICENSE" (licenseTemplate proj) False
  , FileEntry ("LICENSES/" <> T.unpack (projectLicense proj) <> ".txt") (licenseTemplate proj) False
  ]

-- | Generates standard LICENSE text (EUPL-1.2 notice).
licenseTemplate :: Project -> Text
licenseTemplate proj =
  let auth = projectAuthor proj
  in T.unlines
    [ "European Union Public Licence v. 1.2"
    , "EUPL Β© the European Union 2007, 2016"
    , ""
    , "Copyright (c) 2026 " <> authorName auth <> " <" <> authorEmail auth <> ">"
    , ""
    , "This European Union Public Licence (the 'EUPL') applies to the Work which is"
    , "provided under the terms of this Licence. Any use of the Work, other than as"
    , "authorised under this Licence is prohibited (to the extent such use is covered"
    , "by a right of the copyright holder of the Work)."
    , "The Work is provided under the terms of this Licence when the Licensor has"
    , "placed the following notice immediately following the copyright notice for"
    , "the Work:"
    , "                      Licensed under the EUPL"
    , "or has expressed by any other means their willingness to license under the EUPL."
    ]

-- | Generates root @README.md@ describing project overview, build steps, and licenses.
readmeTemplate :: Project -> Text
readmeTemplate proj =
  let pName = projectName proj
      auth = projectAuthor proj
  in renderMarkdown
    [ h1 pName
    , para (projectDescription proj)
    , h2 "Quick Start"
    , codeBlock "bash"
        ( "# Build all packages\n\
          \cabal build all\n\n\
          \# Run full test suite\n\
          \cabal test all\n\n\
          \# Run physical verification gates\n\
          \./bin/" <> pName <> "-verify" )
    , h2 "Documentation"
    , bulletList
        [ inlines [linkInline (code "AGENTS.md") "AGENTS.md", plain " β€” Advisory context and rules for AI assistants."]
        , inlines [linkInline (code "PLAN.md") "PLAN.md", plain " β€” Strategic roadmap and phase delivery schedule."]
        , inlines [linkInline (code "STATE.md") "STATE.md", plain " β€” Active operational session frontier."]
        , inlines [linkInline (code "docs/INDEX.md") "docs/INDEX.md", plain " β€” Documentation corpus inventory."]
        , inlines [linkInline (code "docs/TUTORIAL.md") "docs/TUTORIAL.md", plain " β€” Getting started tutorial."]
        ]
    , h2 "License & Attribution"
    , bulletList
        [ inlines [bold "License:", plain (" " <> projectLicense proj)]
        , inlines [bold "Author:", plain (" " <> authorName auth <> " <" <> authorEmail auth <> ">")]
        ]
    ]

-- | Generates @AGENTS.md@ establishing AI coding assistant reading order and advisories.
agentsTemplate :: Project -> Text
agentsTemplate proj =
  let pName = projectName proj
      uSlug = toUpperSlug pName
  in renderMarkdown
    [ h1 "AGENTS.md β€” Advisory Context for AI Coding Assistants"
    , para "Pointer file. Canonical sources win on any disagreement."
    , h2 "What this repository is"
    , para ("`" <> pName <> "` β€” " <> projectDescription proj)
    , h2 "Read first, in this order"
    , orderedList
        [ inlines [linkInline (code "README.md") "README.md", plain " β€” what this is, and its status."]
        , inlines [linkInline (code "PLAN.md") "PLAN.md", plain " & ", linkInline (code "STATE.md") "STATE.md", plain " β€” operational roadmap and active session frontier."]
        , inlines [linkInline (code ("docs/notes/" <> uSlug <> "_DESIGN.md")) ("docs/notes/" <> uSlug <> "_DESIGN.md"), plain " β€” canonical design notes and invariants."]
        , inlines [linkInline (code "docs/notes/DOC_STRATEGY.md") "docs/notes/DOC_STRATEGY.md", plain " β€” documentation policy: genres, lifecycle, tone."]
        , inlines [linkInline (code "docs/INDEX.md") "docs/INDEX.md", plain " β€” corpus inventory and reading map."]
        ]
    , h2 "Subdivided operational rules (`.agents/rules/`)"
    , bulletList
        [ inlines [linkInline (code ".agents/rules/commit-attribution.md") ".agents/rules/commit-attribution.md", plain " β€” Linux kernel RFC 2822 trailer format (`Assisted-by:`), DCO boundaries."]
        , inlines [linkInline (code ".agents/rules/code-navigation.md") ".agents/rules/code-navigation.md", plain " β€” Token-saving funnel (`./tags` symbol jump, targeted slice viewing)."]
        , inlines [linkInline (code ".agents/rules/compiler-verification.md") ".agents/rules/compiler-verification.md", plain " β€” Zero-log compiler verification, tests, and zero-contamination."]
        , inlines [linkInline (code ".agents/rules/documentation-precedence.md") ".agents/rules/documentation-precedence.md", plain " β€” Precedence rule (\"code wins at runtime; notes corrected regardless\")."]
        ]
    , h2 "Operational advisories"
    , orderedList
        [ plain "**Design docs are load-bearing.** Stale notes must be corrected in the same change set."
        , plain "Commits: descriptive-imperative subject under 72 chars, body explains why. AI attribution uses git trailers per `sarutahiko`: `Assisted-by: <Tool> (<Provider> / <Model>)`."
        , plain ("Toolchain: GHC " <> projectGhcVersion proj <> " / Cabal " <> projectCabalVersion proj <> ".")
        , plain "Two-file loop: Update `STATE.md` before ending or resetting a session."
        ]
    ]

-- | Generates @CLAUDE.md@ pointing to canonical advisory in @AGENTS.md@.
claudeTemplate :: Project -> Text
claudeTemplate _ = renderMarkdown
  [ h1 "CLAUDE.md"
  , paraInlines
      [ plain "Canonical advisory instructions are maintained in "
      , link "AGENTS.md" "AGENTS.md"
      , plain "."
      ]
  ]

-- | Generates @PLAN.md@ tracking strategic milestones.
planTemplate :: Project -> Text
planTemplate proj =
  let pName = projectName proj
  in renderMarkdown
    [ h1 (pName <> " β€” Strategic Roadmap (`PLAN.md`)")
    , paraInlines
        [ bold "Status:"
        , plain " ACTIVE STRATEGIC ROADMAP  \n"
        , bold "Scope:"
        , plain (" " <> projectDescription proj <> "  \n")
        , bold "Documentation:"
        , plain " "
        , linkInline (code "docs/INDEX.md") "docs/INDEX.md"
        , plain "  \n"
        , bold "Operational State:"
        , plain " "
        , linkInline (code "STATE.md") "STATE.md"
        ]
    , hr
    , h2 "Phase 1: Foundational Architecture [ACTIVE]"
    , bulletList
        [ inlines
            [ plain "[x] "
            , bold "Milestone 1.1:"
            , plain " Canonical Workspace Scaffolding\n  - Multi-package hierarchy, documentation corpus, and verification harnesses grounded."
            ]
        , inlines
            [ plain "[ ] "
            , bold "Milestone 1.2:"
            , plain " Core Implementation & Domain Logic\n  - Core abstractions, unit tests, and property test verification."
            ]
        ]
    ]

-- | Generates @STATE.md@ tracking active session frontier.
stateTemplate :: Project -> Text
stateTemplate proj =
  let pName = projectName proj
  in renderMarkdown
    [ h1 (pName <> " β€” State")
    , paraInlines
        [ plain "Operational frontier. Read with "
        , linkInline (code "PLAN.md") "PLAN.md"
        , plain "; update before ending a session."
        ]
    , h2 "Where we are"
    , para ("The workspace skeleton for `" <> pName <> "` is initialized and verified under GHC " <> projectGhcVersion proj <> ".")
    , h2 "Active Frontier"
    , bulletList
        [ plain "Complete Milestone 1.2 core domain logic."
        , plain ("Maintain 5-gate physical verification via `./bin/" <> pName <> "-verify`.")
        ]
    ]

--------------------------------------------------------------------------------
-- 5. Subdivided Agent Operational Rules
--------------------------------------------------------------------------------

-- | Synthesizes `.agents/rules/` guidance files.
agentRuleEntries :: Project -> [FileEntry]
agentRuleEntries _ =
  [ FileEntry ".agents/rules/commit-attribution.md" ruleCommitAttributionTemplate False
  , FileEntry ".agents/rules/code-navigation.md" ruleCodeNavigationTemplate False
  , FileEntry ".agents/rules/compiler-verification.md" ruleCompilerVerificationTemplate False
  , FileEntry ".agents/rules/documentation-precedence.md" ruleDocPrecedenceTemplate False
  ]

-- | Generates `.agents/rules/commit-attribution.md`.
ruleCommitAttributionTemplate :: Text
ruleCommitAttributionTemplate = renderMarkdown
  [ h1 "Commit Attribution Rules"
  , para "AI attribution uses git trailers per `sarutahiko`:\n\
         \`Assisted-by: <Tool> (<Provider> / <Model>)` (or `<Tool>`) and optional `Orchestrated-by: <Engine>`."
  , para "Never `Co-authored-by:` or `Signed-off-by:` for AI tools (only humans certify the DCO)."
  ]

-- | Generates `.agents/rules/code-navigation.md`.
ruleCodeNavigationTemplate :: Text
ruleCodeNavigationTemplate = renderMarkdown
  [ h1 "Code Navigation Rules"
  , para "Always follow the Progressive Disclosure Funnel:"
  , orderedList
      [ plain "Level 0: Global Navigation (`docs/INDEX.md`, `tags` file)"
      , plain "Level 1: Structural Outline (module export lists)"
      , plain "Level 2: Targeted Slice (`view_file` StartLine / EndLine)"
      ]
  ]

-- | Generates `.agents/rules/compiler-verification.md`.
ruleCompilerVerificationTemplate :: Text
ruleCompilerVerificationTemplate = renderMarkdown
  [ h1 "Compiler Verification Rules"
  , para "Maintain zero warnings under `-Wall -Wcompat -Wredundant-constraints`."
  , para "Run physical verification gates before committing."
  ]

-- | Generates `.agents/rules/documentation-precedence.md`.
ruleDocPrecedenceTemplate :: Text
ruleDocPrecedenceTemplate = renderMarkdown
  [ h1 "Documentation Precedence Rules"
  , para "Precedence rule: Code wins at runtime; notes corrected regardless."
  , para "Design docs are load-bearing. Update notes when implementation changes."
  ]

--------------------------------------------------------------------------------
-- 6. Canonical Documentation Corpus
--------------------------------------------------------------------------------

-- | Synthesizes `docs/` hierarchy and notes.
documentationCorpusEntries :: Project -> [FileEntry]
documentationCorpusEntries proj =
  let uSlug = T.unpack (toUpperSlug (projectName proj))
  in [ FileEntry "docs/INDEX.md" (docsIndexTemplate proj) False
     , FileEntry "docs/TUTORIAL.md" (docsTutorialTemplate proj) False
     , FileEntry "docs/NEWBIE_GUIDE.md" (docsNewbieGuideTemplate proj) False
     , FileEntry "docs/MAINTAINER_GUIDE.md" (docsMaintainerGuideTemplate proj) False
     , FileEntry ("docs/notes/" <> uSlug <> "_DESIGN.md") (docsDesignTemplate proj) False
     , FileEntry "docs/notes/DOC_STRATEGY.md" (docsStrategyTemplate proj) False
     , FileEntry "docs/notes/COMMIT_ATTRIBUTION.md" (docsCommitAttributionTemplate proj) False
     ]

-- | Generates `docs/INDEX.md` inventory.
docsIndexTemplate :: Project -> Text
docsIndexTemplate proj =
  let pName = projectName proj
      uSlug = toUpperSlug pName
      pkgs = projectPackages proj
  in renderMarkdown
    [ h1 (pName <> " Documentation Index")
    , para ("Welcome to the `" <> pName <> "` technical documentation corpus.")
    , h2 "Core Orientation"
    , bulletList
        [ inlines [linkInline (code "README.md") "../README.md", plain " β€” High-level project overview and quick start."]
        , inlines [linkInline (code "AGENTS.md") "../AGENTS.md", plain " β€” AI coding assistant guidance and rules."]
        , inlines [linkInline (code "PLAN.md") "../PLAN.md", plain " β€” Strategic roadmap."]
        , inlines [linkInline (code "STATE.md") "../STATE.md", plain " β€” Active operational frontier."]
        , inlines [linkInline (code "TUTORIAL.md") "TUTORIAL.md", plain " β€” Getting started tutorial."]
        , inlines [linkInline (code "NEWBIE_GUIDE.md") "NEWBIE_GUIDE.md", plain " β€” Token-conservation funnel and navigation."]
        , inlines [linkInline (code "MAINTAINER_GUIDE.md") "MAINTAINER_GUIDE.md", plain " β€” Maintenance and verification gates."]
        ]
    , h2 "Design Notes (`docs/notes/`)"
    , bulletList
        [ inlines [linkInline (code (uSlug <> "_DESIGN.md")) ("notes/" <> uSlug <> "_DESIGN.md"), plain " β€” Canonical design notes."]
        , inlines [linkInline (code "DOC_STRATEGY.md") "notes/DOC_STRATEGY.md", plain " β€” Documentation strategy and precedence rules."]
        , inlines [linkInline (code "COMMIT_ATTRIBUTION.md") "notes/COMMIT_ATTRIBUTION.md", plain " β€” Commit attribution standards."]
        ]
    , h2 "Active Packages"
    , bulletList
        [ inlines [linkInline (code ("packages/" <> packageName pkg <> "/")) ("../packages/" <> packageName pkg <> "/"), plain (" β€” " <> packageSynopsis pkg)]
        | pkg <- pkgs
        ]
    ]

-- | Generates `docs/TUTORIAL.md`.
docsTutorialTemplate :: Project -> Text
docsTutorialTemplate proj =
  let pName = projectName proj
      uSlug = toUpperSlug pName
  in renderMarkdown
    [ h1 ("Getting Started with " <> pName)
    , para ("A foundational tutorial for working with `" <> pName <> "`.")
    , h2 "1. Concepts"
    , para ("`" <> pName <> "` provides modular components structured around " <> projectDescription proj <> ".")
    , h2 "2. Navigation"
    , paraInlines
        [ plain "See "
        , linkInline (code "docs/INDEX.md") "INDEX.md"
        , plain ", "
        , linkInline (code "docs/NEWBIE_GUIDE.md") "NEWBIE_GUIDE.md"
        , plain ", and "
        , linkInline (code ("docs/notes/" <> uSlug <> "_DESIGN.md")) ("notes/" <> uSlug <> "_DESIGN.md")
        , plain "."
        ]
    ]

-- | Generates `docs/NEWBIE_GUIDE.md`.
docsNewbieGuideTemplate :: Project -> Text
docsNewbieGuideTemplate proj =
  let pName = projectName proj
  in renderMarkdown
    [ h1 (pName <> " Newbie Quickstart & Token-Conservation Guide")
    , paraInlines
        [ bold "Parent Strategy:"
        , plain " "
        , linkInline (code "docs/notes/DOC_STRATEGY.md") "notes/DOC_STRATEGY.md"
        , plain "  \n"
        , bold "Corpus Inventory:"
        , plain " "
        , linkInline (code "docs/INDEX.md") "INDEX.md"
        , plain "  \n"
        , bold "Tutorial:"
        , plain " "
        , linkInline (code "docs/TUTORIAL.md") "TUTORIAL.md"
        , plain "  \n"
        , bold "Maintainer Guide:"
        , plain " "
        , linkInline (code "docs/MAINTAINER_GUIDE.md") "MAINTAINER_GUIDE.md"
        ]
    , h2 "1. The Core Invariant: Never Dump Whole Modules"
    , para "Always follow the Progressive Disclosure Funnel:"
    , bulletList
        [ plain "Level 0: Global Navigation (`docs/INDEX.md`)"
        , plain "Level 1: Structural Outline"
        , plain "Level 2: Targeted Slice"
        ]
    ]

-- | Generates `docs/MAINTAINER_GUIDE.md`.
docsMaintainerGuideTemplate :: Project -> Text
docsMaintainerGuideTemplate proj =
  let pName = projectName proj
      uSlug = toUpperSlug pName
  in renderMarkdown
    [ h1 (pName <> " Maintainer Guide")
    , paraInlines
        [ bold "Corpus Inventory:"
        , plain " "
        , linkInline (code "docs/INDEX.md") "INDEX.md"
        , plain "  \n"
        , bold "Design Notes:"
        , plain " "
        , linkInline (code ("docs/notes/" <> uSlug <> "_DESIGN.md")) ("notes/" <> uSlug <> "_DESIGN.md")
        , plain "  \n"
        , bold "Attribution:"
        , plain " "
        , linkInline (code "docs/notes/COMMIT_ATTRIBUTION.md") "notes/COMMIT_ATTRIBUTION.md"
        ]
    , h2 "Verification Gates"
    , para ("Every commit must pass all five verification gates via `./bin/" <> pName <> "-verify`.")
    ]

-- | Generates `docs/notes/<PROJECT>_DESIGN.md`.
docsDesignTemplate :: Project -> Text
docsDesignTemplate proj =
  let pName = projectName proj
  in renderMarkdown
    [ h1 (pName <> " β€” Design Decisions & Invariants")
    , paraInlines
        [ bold "Status:"
        , plain " CANONICAL DESIGN MONOGRAPH  \n"
        , bold "Parent Strategy:"
        , plain " "
        , linkInline (code "DOC_STRATEGY.md") "DOC_STRATEGY.md"
        , plain "  \n"
        , bold "Documentation Index:"
        , plain " "
        , linkInline (code "../INDEX.md") "../INDEX.md"
        ]
    , hr
    , h2 "Architectural Decisions"
    , h3 "Decision D1: Modular Workspace Architecture"
    , para "The repository is decomposed into fine-grained packages under `packages/`."
    , h3 "Decision D2: Verification Invariants"
    , para ("Adheres to Note [Ecosystem Standardization Invariants] " <> refStandardization <> ".")
    , hr
    , h2 "In-Code Design Notes"
    , h3 "Note [Core Architecture]"
    , Paragraph [tagDef "note-core-architecture"]
    , para ("Defines foundational design invariants for `" <> pName <> "`.")
    ]

-- | Generates `docs/notes/DOC_STRATEGY.md`.
docsStrategyTemplate :: Project -> Text
docsStrategyTemplate proj =
  let pName = projectName proj
  in renderMarkdown
    [ h1 "Documentation Strategy & Policy"
    , paraInlines
        [ bold "Project:"
        , plain (" " <> pName <> "  \n")
        , bold "Index:"
        , plain " "
        , linkInline (code "../INDEX.md") "../INDEX.md"
        ]
    , h2 "Precedence Rule (Β§4)"
    , para "Code wins at runtime; notes corrected regardless. Stale design documentation must be remediated in the same commit."
    ]

-- | Generates `docs/notes/COMMIT_ATTRIBUTION.md`.
docsCommitAttributionTemplate :: Project -> Text
docsCommitAttributionTemplate proj =
  let pName = projectName proj
  in renderMarkdown
    [ h1 "Commit Attribution & Trailer Standards"
    , paraInlines
        [ bold "Project:"
        , plain (" " <> pName <> "  \n")
        , bold "Index:"
        , plain " "
        , linkInline (code "../INDEX.md") "../INDEX.md"
        ]
    , h2 "Format"
    , plainCodeBlock "Assisted-by: <Tool> (<Provider> / <Model>)"
    ]

--------------------------------------------------------------------------------
-- 7. Multi-Package Hierarchy
--------------------------------------------------------------------------------

-- | Synthesizes package manifests, source modules, test suites, and documentation.
packageHierarchyEntries :: Project -> [FileEntry]
packageHierarchyEntries proj =
  concatMap (packageEntries proj) (projectPackages proj)

-- | Synthesizes all files for a specific 'PackageSpec'.
packageEntries :: Project -> PackageSpec -> [FileEntry]
packageEntries proj pkg =
  let pName = T.unpack (packageName pkg)
      cabalFile = "packages/" <> pName <> "/" <> pName <> ".cabal"
      readmeFile = "packages/" <> pName <> "/README.md"
      licenseFile = "packages/" <> pName <> "/LICENSE"
      changelogFile = "packages/" <> pName <> "/CHANGELOG.md"
      testFile = "packages/" <> pName <> "/test/Main.hs"
      exposes = case packageExposes pkg of
        [] -> [defaultModuleName (packageName pkg)]
        ms -> ms
      moduleFiles =
        [ FileEntry
            ("packages/" <> pName <> "/src/" <> moduleToFilePath m)
            (packageModuleTemplate proj pkg m)
            False
        | m <- exposes
        ]
  in [ FileEntry cabalFile (packageCabalTemplate proj pkg) False
     , FileEntry readmeFile (packageReadmeTemplate proj pkg) False
     , FileEntry licenseFile (packageLicenseTemplate proj) False
     , FileEntry changelogFile (packageChangelogTemplate proj pkg) False
     , FileEntry testFile (packageTestTemplate proj pkg) False
     ] ++ moduleFiles

-- | Generates a package's Cabal manifest adhering strictly to Hackage upload standards.
packageCabalTemplate :: Project -> PackageSpec -> Text
packageCabalTemplate proj pkg =
  let pName = packageName pkg
      auth = projectAuthor proj
      exposes = case packageExposes pkg of
        [] -> [defaultModuleName pName]
        ms -> ms
  in T.unlines
    [ "cabal-version: 3.0"
    , "name: " <> pName
    , "version: 0.1.0.0"
    , "synopsis: " <> packageSynopsis pkg
    , "description:"
    , "  " <> packageSynopsis pkg <> " for the " <> projectName proj <> " ecosystem."
    , "license: " <> projectLicense proj
    , "license-file: LICENSE"
    , "author: " <> authorName auth
    , "maintainer: " <> authorEmail auth
    , "category: Development"
    , "homepage: https://github.com/NadiaYvette/" <> projectName proj
    , "bug-reports: https://github.com/NadiaYvette/" <> projectName proj <> "/issues"
    , "extra-doc-files:"
    , "  README.md"
    , "  CHANGELOG.md"
    , "build-type: Simple"
    , "tested-with: GHC == " <> projectGhcVersion proj
    , ""
    , "source-repository head"
    , "  type: git"
    , "  location: https://github.com/NadiaYvette/" <> projectName proj
    , "  subdir: packages/" <> pName
    , ""
    , "library"
    , "  exposed-modules:"
    ] <> T.unlines [ "    " <> m | m <- exposes ] <>
    T.unlines
    [ "  hs-source-dirs: src"
    , "  default-language: Haskell2010"
    , "  default-extensions:"
    , "    OverloadedStrings"
    , "    DerivingStrategies"
    , "  ghc-options: -Wall -Wcompat -Wredundant-constraints"
    , "  build-depends:"
    , "    base >= 4.14 && < 5,"
    , "    text >= 2.0 && < 2.2,"
    , "    containers >= 0.6 && < 0.9"
    , ""
    , "test-suite tests"
    , "  type: exitcode-stdio-1.0"
    , "  main-is: Main.hs"
    , "  hs-source-dirs: test"
    , "  default-language: Haskell2010"
    , "  default-extensions:"
    , "    OverloadedStrings"
    , "    DerivingStrategies"
    , "  ghc-options: -Wall -Wcompat -Wredundant-constraints -threaded"
    , "  build-depends:"
    , "    base >= 4.14 && < 5,"
    , "    " <> pName <> ","
    , "    text >= 2.0 && < 2.2,"
    , "    containers >= 0.6 && < 0.9,"
    , "    hspec >= 2.10 && < 2.12"
    ]

-- | Generates a package's @LICENSE@ file.
packageLicenseTemplate :: Project -> Text
packageLicenseTemplate = licenseTemplate

-- | Generates a package's @CHANGELOG.md@.
packageChangelogTemplate :: Project -> PackageSpec -> Text
packageChangelogTemplate _proj pkg =
  let pName = packageName pkg
  in T.unlines
    [ "# Changelog for " <> pName
    , ""
    , "## 0.1.0.0 β€” Initial Release"
    , ""
    , "- Initial release of " <> pName <> "."
    , "- " <> packageSynopsis pkg
    ]

-- | Generates a package's @README.md@.
packageReadmeTemplate :: Project -> PackageSpec -> Text
packageReadmeTemplate proj pkg =
  let pName = packageName pkg
  in renderMarkdown
    [ h1 pName
    , para (packageSynopsis pkg)
    , h2 "Modules"
    , bulletList [ code m | m <- packageExposes pkg ]
    , h2 "License"
    , para (projectLicense proj)
    ]

-- | Generates an exposed module with comprehensive Haddock documentation (>50% comments).
packageModuleTemplate :: Project -> PackageSpec -> Text -> Text
packageModuleTemplate proj pkg modName =
  let pName = projectName proj
      pkgN = packageName pkg
  in T.unlines
    [ "-- | Module: " <> modName
    , "-- Description: Core implementation module for " <> pkgN
    , "--"
    , "-- This module provides foundational data structures and domain operations for '" <> pkgN <> "'."
    , "--"
    , "-- = Architectural Context"
    , "--"
    , "-- Per Note [Core Architecture] " <> refCoreArch <> ", this module provides"
    , "-- the primary domain interface within the '" <> pName <> "' workspace."
    , "--"
    , "-- == Invariants & Guarantees"
    , "--"
    , "-- 1. __Purity:__ All exported functions are referentially transparent."
    , "-- 2. __Total Operations:__ Default constructors avoid partiality."
    , "-- 3. __Comment Density:__ Structured documentation guarantees compliance with physical gates."
    , "--"
    , "-- = Worked Example"
    , "--"
    , "-- >>> defaultEntity"
    , "-- Entity {entityName = \"" <> pName <> "\", entityModule = \"" <> modName <> "\"}"
    , "module " <> modName
    , "  ( -- * Core Types"
    , "    Entity(..)"
    , "    -- * Default Constructors"
    , "  , defaultEntity"
    , "    -- * Operations"
    , "  , renderEntity"
    , "  ) where"
    , ""
    , "import Data.Text (Text)"
    , ""
    , "-- | Represents a core domain entity within the '" <> pkgN <> "' package."
    , "--"
    , "-- Tracks the enclosing workspace name and the module identifier."
    , "data Entity = Entity"
    , "  { entityName   :: !Text"
    , "    -- ^ Name of the parent project workspace."
    , "  , entityModule :: !Text"
    , "    -- ^ Canonical module identifier defining the entity."
    , "  } deriving stock (Eq, Show)"
    , ""
    , "-- | Canonical default entity for '" <> modName <> "'."
    , "--"
    , "-- Returns an 'Entity' bound to the workspace name and current module name."
    , "defaultEntity :: Entity"
    , "defaultEntity = Entity"
    , "  { entityName   = \"" <> pName <> "\""
    , "  , entityModule = \"" <> modName <> "\""
    , "  }"
    , ""
    , "-- | Formats an 'Entity' into a human-readable display string."
    , "--"
    , "-- Computes a combined representation: @\"<entityName>:<entityModule>\"@."
    , "renderEntity :: Entity -> Text"
    , "renderEntity e = entityName e <> \":\" <> entityModule e"
    ]

-- | Generates a package's Hspec test suite.
packageTestTemplate :: Project -> PackageSpec -> Text
packageTestTemplate proj pkg =
  let pName = projectName proj
      firstMod = case packageExposes pkg of
        (m:_) -> m
        []    -> defaultModuleName (packageName pkg)
  in T.unlines
    [ "{-# LANGUAGE OverloadedStrings #-}"
    , ""
    , "module Main (main) where"
    , ""
    , "import Test.Hspec"
    , "import " <> firstMod
    , ""
    , "main :: IO ()"
    , "main = hspec $ do"
    , "  describe \"" <> firstMod <> "\" $ do"
    , "    it \"constructs default entity with correct properties\" $ do"
    , "      let e = defaultEntity"
    , "      entityName e `shouldBe` \"" <> pName <> "\""
    , "      entityModule e `shouldBe` \"" <> firstMod <> "\""
    , "    it \"renders entity accurately\" $ do"
    , "      let e = defaultEntity"
    , "      renderEntity e `shouldBe` \"" <> pName <> ":" <> firstMod <> "\""
    ]

--------------------------------------------------------------------------------
-- Utilities: String & Path Helpers
--------------------------------------------------------------------------------

-- | Converts a package name (e.g., @"gachi-core"@) into a canonical PascalCase
-- Haskell module name (e.g., @"Gachi.Core"@).
defaultModuleName :: Text -> Text
defaultModuleName raw =
  let parts = filter (not . T.null) (T.splitOn "-" raw)
      capitalized = map capitalize parts
  in case capitalized of
       [] -> "Main"
       _  -> T.intercalate "." capitalized
  where
    capitalize t = case T.uncons t of
      Nothing      -> ""
      Just (c, cs) -> T.cons (toUpper c) cs

-- | Converts a hierarchical module name (e.g., @"Gongju.Core"@) to a relative
-- source file path (e.g., @"Gongju/Core.hs"@).
moduleToFilePath :: Text -> FilePath
moduleToFilePath modName =
  T.unpack (T.replace "." "/" modName) <> ".hs"

-- | Converts a kebab-case identifier into an uppercase slug (e.g., @"gachi-core"@ -> @"GACHI_CORE"@).
toUpperSlug :: Text -> Text
toUpperSlug = T.map cleanChar
  where
    cleanChar c
      | isAlphaNum c = toUpper c
      | otherwise    = '_'