mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 14:34:42 +01:00
* fix(pregate): Dart import graph and test lanes (#3285) Port of arlophoenix's #3285. The import graph reads Dart import/export/part directives and resolves package: URIs through every tracked pubspec.yaml; _test.dart files under test/ are tests. lanes runs each Dart package's selected tests from its own root with flutter test or dart test (through fvm when the package pins it). flutter/dart/melos/very_good CI jobs are lanes, and run-step test paths resolve against working-directory and literal cd. A missing Dart SDK or a .dart file outside any package leaves the lane incomplete with a fix line; device tests (integration_test/, test_driver/) are not unit lanes. Co-authored-by: arlophoenix <arlo.phoenix@gmail.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(pregate): a --base that names a commit gets a branch fix, not an impossible fetch (#3269) gstack-pregate --base <sha> refused with "fix: git fetch origin <sha> --depth=1". That fetch can never create origin/<sha>: a short SHA fails with "couldn't find remote ref", and a full SHA only reaches FETCH_HEAD. A fork clone hits the same wall with --base upstream/main. When origin/<base> does not resolve but <base> names a commit here and no local branch (a SHA, a tag, another remote's ref), the refusal now says --base takes a branch name on origin and suggests origin/HEAD's branch. A local branch, or a name that resolves nowhere, keeps the fetch fix: that is the unfetched or shallow checkout it repairs. Same code, same exit 3, and pin files are still read only from the reviewed origin/<base>. Fixes #3269 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(pregate): Django, asset and tool-config lanes; init drafts from the repo (#3266, #3267, #3268) - Django: settings modules, settings strings, include(), app migrations, admin, templates, static files and project templates get framework edges (lib/pregate/django.ts). - Assets, stylesheets and dotfile tool config take a no-tests empty lane; licence text under any name is documentation. - gstack-pregate init drafts languages, runner notes and nearest-test dependencies; never writes []. - Review fixes on the #3285 port: untracked shell scopes leave a lane unknown; Dart directives read to their semicolon; nested comments. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(pregate): init draft API is exercised directly, so init-draft.ts has a lane Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(changelog): restore the history a truncated rewrite dropped Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(pregate): outside-voice review fixes for the non-JS lanes - Tool config is an allowlist: unknown dotfiles (.rspec, .husky hooks) and stylesheets still need a lane; snapshot, golden and fixture trees and .gstack/ are never inert. - Django edges need Django evidence (manage.py or code importing django) and settings assignments in code, not strings or comments. - A cd after ||, &, control flow or braces leaves a lane unknown. - Dart directives allow metadata and quoted semicolons. - init drafts at most 256 dependencies (the schema limit); the rest are TODO. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(windows): pregate-dart and pregate-non-js join the POSIX-run pregate fixtures Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: release v1.91.161.0 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: arlophoenix <arlo.phoenix@gmail.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> Co-authored-by: y$un_ <forrest.sun527@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
5.7 KiB
TypeScript
96 lines
5.7 KiB
TypeScript
/**
|
|
* pregate docs/no-tests — Markdown no test depends on is an empty lane.
|
|
*
|
|
* A repo with no `.gstack/pregate.json` used to fail `lanes` on a tracked
|
|
* `.work` plan, a redproof note, or any other Markdown file (`PREGATE_NO_LANE`).
|
|
* Those files document a change; they are not test subjects. `isDocsEmptyLane`
|
|
* is that class. `lane-selection` consults it only when the import graph
|
|
* mapped nothing (`testsFor`: imports, repo-path literals including `join()`,
|
|
* the bin convention, declarations), so a test that reads the file — a
|
|
* fixture or a doc — still runs. Source code and anything under a test
|
|
* directory never qualify, including when `empty_lanes` globs match them.
|
|
* Unmapped code stays `PREGATE_NO_LANE`.
|
|
*
|
|
* `isInertNoTests` is the second empty-lane class (#3267): files no test
|
|
* runner loads — binary and media assets, and a named allowlist of editor,
|
|
* lint, format and repository-metadata configuration. It applies under the
|
|
* same rule (only when nothing maps the file to a test) and never to code,
|
|
* the test tree, snapshot or golden baselines, CI workflows, the pin files,
|
|
* stylesheets (they import each other), or any dotfile not on the list: an
|
|
* unknown dotfile may be read by a runner (`.rspec`, `.taprc`) or executed
|
|
* (`.husky/pre-commit`), so it still needs a lane.
|
|
*/
|
|
import { SOURCE_EXT } from './import-graph';
|
|
import { matchesAny } from '../ship-policy';
|
|
|
|
const TEST_TREE = /(?:^|\/)(?:test|tests|spec|__tests__)(?:\/|$)/i;
|
|
const MARKDOWN = /\.(?:md|markdown|rst|adoc|asciidoc)$/i;
|
|
/** Prose names, with or without `.md` (LICENSE has no extension). */
|
|
const PROSE_NAME = /^(?:README|CHANGELOG|CONTRIBUTING|AGENTS|CLAUDE|ETHOS|LICENSE|LICENCE|COPYING|NOTICE)(?:\.md)?$/i;
|
|
/** Licence text under any name a vendored asset ships it with: `roboto-LICENSE.txt`, `LICENSE-MIT`, `COPYING.LESSER`, `licenses/apache.txt`. */
|
|
const LICENCE_TEXT = /(?:^|[-_.])(?:LICEN[CS]E|COPYING|COPYRIGHT|NOTICE)(?:[-_.][\w-]*)?(?:\.(?:txt|md))?$|^(?:licen[cs]es?|LICENSES)\/(?:.*\/)?[^/]+\.(?:txt|md)$/i;
|
|
const ASSET = /\.(?:png|jpe?g|gif|webp|avif|bmp|ico|icns|tiff?|svg|heic|woff2?|ttf|otf|eot|mp3|mp4|m4a|wav|ogg|oga|webm|mov|flac|pdf|psd)$/i;
|
|
/** Directories whose files a test compares against or that pregate and CI read: never inert, assets included. */
|
|
const PROTECTED_TREE = /(?:^|\/)(?:__snapshots__|__image_snapshots__|[^/]*-snapshots|snapshots?|goldens?|baselines?|fixtures?)\/|^\.(?:github\/(?:workflows|actions)|gstack)\//i;
|
|
/** Editor, lint, format, hook-manager and repository-metadata configuration no test runner reads. */
|
|
const INERT_DOTFILE = /^\.(?:editorconfig|gitignore|gitattributes|gitmessage|mailmap|dockerignore|npmignore|prettierignore|eslintignore|pre-commit-config\.ya?ml|pre-commit-hooks\.ya?ml|prettierrc(?:\.(?:json|ya?ml|toml))?|eslintrc(?:\.(?:json|ya?ml))?|stylelintrc(?:\.(?:json|ya?ml))?|markdownlint(?:\.(?:json|jsonc|ya?ml))?|markdownlintignore|yamllint(?:\.ya?ml)?|flake8|pylintrc|isort\.cfg|rubocop\.yml|shellcheckrc|hadolint\.ya?ml|codespellrc|clang-format)$/i;
|
|
/** Dot-directories of editors and design tools, and GitHub's repository metadata (not workflows or actions). */
|
|
const INERT_DOT_DIR = /^\.(?:vscode|idea|fleet|zed|cursor|impeccable)\/|^\.github\/(?:ISSUE_TEMPLATE\/|DISCUSSION_TEMPLATE\/|PULL_REQUEST_TEMPLATE|pull_request_template|CODEOWNERS$|FUNDING\.ya?ml$|dependabot\.ya?ml$|labeler\.ya?ml$|release\.ya?ml$)/;
|
|
|
|
function isMarkdown(rel: string): boolean {
|
|
return MARKDOWN.test(rel);
|
|
}
|
|
|
|
function isTestTree(rel: string): boolean {
|
|
return TEST_TREE.test(rel);
|
|
}
|
|
|
|
/** Code the empty-lane class must not swallow. Extensionless `bin/` entrypoints count; `bin/README.md` does not. */
|
|
function isSourceCode(rel: string): boolean {
|
|
if (SOURCE_EXT.test(rel)) return true;
|
|
const base = rel.split('/').pop() ?? rel;
|
|
if (rel.startsWith('bin/') && !isMarkdown(rel) && !PROSE_NAME.test(base)) return true;
|
|
return false;
|
|
}
|
|
|
|
function isBuiltinDocsPath(rel: string): boolean {
|
|
const base = rel.split('/').pop() ?? rel;
|
|
if (PROSE_NAME.test(base) || LICENCE_TEXT.test(base) || LICENCE_TEXT.test(rel)) return true;
|
|
// Any Markdown file. Name and directory do not matter: `planet.md` and
|
|
// `prompts/system.md` qualify the same way a `.work` plan does. A test
|
|
// that reads the file is handled by the caller, before this check.
|
|
return isMarkdown(rel);
|
|
}
|
|
|
|
/**
|
|
* True when `rel` is documentation that needs no test lane.
|
|
* `extraGlobs` are the repo's `empty_lanes` (`.gstack/pregate.json` on the
|
|
* base branch). They add paths; they cannot classify source code or a file
|
|
* under a test directory. Callers apply this only after the import graph
|
|
* found no test.
|
|
*/
|
|
export function isDocsEmptyLane(rel: string, extraGlobs: readonly string[] = []): boolean {
|
|
const norm = rel.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
if (!norm || isTestTree(norm) || isSourceCode(norm)) return false;
|
|
if (extraGlobs.length > 0 && matchesAny(extraGlobs, norm)) return true;
|
|
return isBuiltinDocsPath(norm);
|
|
}
|
|
|
|
/** Tool configuration on the allowlist: a named dotfile at any depth, or a file under an editor or design-tool dot-directory. */
|
|
function isToolConfig(rel: string): boolean {
|
|
return INERT_DOTFILE.test(rel.split('/').pop() ?? rel) || INERT_DOT_DIR.test(rel);
|
|
}
|
|
|
|
/**
|
|
* True when `rel` is a file no test runner loads (#3267): a binary or media
|
|
* asset outside any snapshot, golden or fixture tree, or allowlisted tool
|
|
* configuration. Callers apply this only
|
|
* after the import graph found no test, so an asset a test imports or names
|
|
* still selects that test.
|
|
*/
|
|
export function isInertNoTests(rel: string): boolean {
|
|
const norm = rel.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
if (!norm || isTestTree(norm) || isSourceCode(norm) || PROTECTED_TREE.test(norm)) return false;
|
|
return ASSET.test(norm) || isToolConfig(norm);
|
|
}
|