# forbidden-constructs.txt — the stack-specific constructs engine core may not
# contain (STORY-344 T-PB-344h, AC-8). Read by package-ci.sh gate 2.
#
# WHY CONSTRUCTS, NOT NOUNS
# Gate 2 used to grep core/ for five words (`factoryos|tenant|pnpm|confluence|
# jira`). That list checked spelling, not structure, and was wrong both ways:
#   · blind — `*.rego`, `services/**`, `*identity-svc/*`, `docker compose … up`,
#     port 3102 and `e2e:mock` all lived in core for months (STORY-344 D-5, D-6)
#     without containing one of the five words;
#   · noisy — it flagged baseline-report.sh's multi-stack detection vocabulary,
#     which names pnpm alongside a dozen other tools precisely so that core does
#     NOT assume one stack.
# This register names the SHAPES of stack knowledge instead: a package manager,
# test runner or stack CLI named as a word; a glob over a language's source
# extension; a manifest or lockfile by name; a path rooted in an application
# layout directory; a service-unit name. Stack knowledge belongs in the
# `profile:` (STORY-344), a stack pack (plugins/stack-*), or a runner adapter
# (lib/runners/) — never in core.
#
# FORMAT   <construct-id> :: <extended regex, matched case-sensitively per line>
#          Lines starting with # are comments. The id is what a failure prints
#          and what scripts/forbidden-construct-allowlist.txt entries name.
#
# ADDING A CONSTRUCT
# Add a line here, run package-ci, and either fix or register (with a reason)
# every hit it finds. A construct that cannot be made clean is a construct the
# engine is not yet ready to forbid — say so in the allowlist, do not weaken
# the regex to dodge one call site.

# ── Tool names ──────────────────────────────────────────────────────────────
# Package managers, monorepo task runners and language build tools. Word-bounded
# so `turbopack`, `npm_config_*` or a path segment like `.pnpm-store` are not
# confused with an invocation. `go`, `make` and `bundle` are left out: they are
# ordinary English words in prose and comments and would drown the signal.
tool-package-manager :: (^|[^A-Za-z0-9_./-])(pnpm|npm|npx|yarn|bun|turbo|lerna|uv|uvx|pip|pipenv|poetry|pdm|composer|artisan|cargo|mvn|gradle|dotnet)([^A-Za-z0-9_-]|$)
# Test runners. A profile suite names its runner; core counts by declared parser.
tool-test-runner :: (^|[^A-Za-z0-9_./-])(pytest|vitest|jest|mocha|phpunit|rspec|playwright|cypress)([^A-Za-z0-9_-]|$)
# Stack lifecycle CLIs. The live stack is `profile.live_stack` (T-PB-344d).
tool-stack-cli :: (^|[^A-Za-z0-9_./-])(docker[ -]compose|kubectl|helm|sail)([^A-Za-z0-9_-]|$)

# ── Path globs and literals ─────────────────────────────────────────────────
# A glob over one language's source files. `*.md`, `*.sh`, `*.yaml`, `*.xml`
# are not here: they are the engine's own artefact formats (docs, scripts,
# config, JUnit evidence) and are structural to every stack.
glob-source-extension :: \*\.(ts|tsx|js|jsx|mjs|cjs|py|php|rb|go|rs|java|kt|cs|rego|vue|svelte|tf|lock)([^A-Za-z0-9]|$)
# A manifest or lockfile by name — the fingerprint of one ecosystem.
path-manifest :: (package(-lock)?\.json|pnpm-(lock|workspace)\.yaml|yarn\.lock|composer\.(json|lock)|pyproject\.toml|uv\.lock|Cargo\.(toml|lock)|go\.(mod|sum)|tsconfig[A-Za-z0-9*.-]*\.json|turbo\.json)
# A path (or glob) rooted in an application-layout directory. Where a repo keeps
# its services, packages or sources is `stack.workspace_roots` / `profile.*`.
# `$` is excluded before the segment so a shell variable (`"$src/$item"`) is
# not read as a `src/` directory.
path-app-layout :: (^|[^A-Za-z0-9_.$-])(services|packages|apps|app|src|node_modules|site-packages|\.venv)/
# A deployable-unit name in the `<name>-svc` convention (`identity-svc`).
name-service-unit :: (^|[^A-Za-z0-9_-])[a-z][a-z0-9]*-svc([^A-Za-z0-9_-]|$)
