@gobing-ai/spur 0.1.8
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.
- package/README.md +78 -0
- package/package.json +67 -0
- package/schemas/spur-config.schema.json +140 -0
- package/spur-cli/config/config.example.yaml +44 -0
- package/spur-cli/config/plugins/.gitkeep +0 -0
- package/spur-cli/config/rules/README.md +40 -0
- package/spur-cli/config/rules/boundary/dao-boundary.yaml +126 -0
- package/spur-cli/config/rules/migration/rg-dialect.yaml +44 -0
- package/spur-cli/config/rules/quality/coverage-gate.yaml +36 -0
- package/spur-cli/config/rules/quality/tsdoc-exports.yaml +29 -0
- package/spur-cli/config/rules/recommended-post-check.yaml +10 -0
- package/spur-cli/config/rules/recommended-pre-check.yaml +12 -0
- package/spur-cli/config/rules/rg-migration.yaml +14 -0
- package/spur-cli/config/rules/strict/http-boundaries.yaml +70 -0
- package/spur-cli/config/rules/strict/rule-files-structural.yaml +55 -0
- package/spur-cli/config/rules/strict/runtime-boundaries.yaml +64 -0
- package/spur-cli/config/rules/strict-check.yaml +15 -0
- package/spur-cli/config/rules/structure/protected-files.yaml +62 -0
- package/spur-cli/config/rules/structure/test-focus-skip.yaml +27 -0
- package/spur-cli/config/rules/structure/test-location.yaml +44 -0
- package/spur-cli/config/rules/surface/check-cli-surface.yaml +35 -0
- package/spur-cli/config/rules/typescript/bun-tooling.yaml +61 -0
- package/spur-cli/config/rules/typescript/no-biome-suppressions.yaml +29 -0
- package/spur-cli/config/rules/typescript/no-debugger.yaml +27 -0
- package/spur-cli/config/rules/typescript/output-boundaries.yaml +28 -0
- package/spur-cli/config/workflows/basic.yaml +58 -0
- package/spur.js +61107 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# [STRICT — opt-in] HTTP-client boundaries. Absorbed from
|
|
2
|
+
# ts-libs/.spur/rules/typescript/external-api-boundaries.yaml. NOT in
|
|
3
|
+
# recommended-pre-check: Spur has no central api-client seam yet, and the
|
|
4
|
+
# server's Hono/Workers entrypoint legitimately exposes a `fetch` handler.
|
|
5
|
+
# Opt in via: spur rule run --preset strict (or --rule <id>).
|
|
6
|
+
include:
|
|
7
|
+
- "apps/**/src/**/*.ts"
|
|
8
|
+
- "packages/**/src/**/*.ts"
|
|
9
|
+
exclude:
|
|
10
|
+
- "**/tests/**"
|
|
11
|
+
- "**/*.test.ts"
|
|
12
|
+
- "**/node_modules/**"
|
|
13
|
+
- "**/dist/**"
|
|
14
|
+
|
|
15
|
+
rules:
|
|
16
|
+
- id: no-direct-fetch
|
|
17
|
+
description: "Prefer a centralized HTTP client over ad-hoc fetch(...). The server's Hono/Workers fetch entrypoint is exempt."
|
|
18
|
+
severity: warning
|
|
19
|
+
evaluator:
|
|
20
|
+
type: rg
|
|
21
|
+
config:
|
|
22
|
+
pattern: "(^|[^.[:alnum:]_])fetch\\("
|
|
23
|
+
exclude:
|
|
24
|
+
# Hono/Cloudflare Workers entrypoint — `fetch` is the platform contract.
|
|
25
|
+
- "apps/server/src/worker.ts"
|
|
26
|
+
- "apps/server/src/app.ts"
|
|
27
|
+
|
|
28
|
+
- id: no-globalthis-fetch
|
|
29
|
+
description: "Avoid globalThis.fetch(...); route HTTP through a client seam when one exists."
|
|
30
|
+
severity: warning
|
|
31
|
+
evaluator:
|
|
32
|
+
type: rg
|
|
33
|
+
config:
|
|
34
|
+
pattern: "globalThis\\.fetch\\("
|
|
35
|
+
|
|
36
|
+
- id: no-xml-http-request
|
|
37
|
+
description: "Do not use XMLHttpRequest in a Bun/Node codebase."
|
|
38
|
+
severity: error
|
|
39
|
+
evaluator:
|
|
40
|
+
type: rg
|
|
41
|
+
config:
|
|
42
|
+
pattern: "new\\s+XMLHttpRequest\\("
|
|
43
|
+
# Correctness rule (not a seam rule): XHR is never valid under Bun, so it
|
|
44
|
+
# applies to scripts/ tooling too — override the file-level apps+packages scope.
|
|
45
|
+
include:
|
|
46
|
+
- "apps/**/src/**/*.ts"
|
|
47
|
+
- "packages/**/src/**/*.ts"
|
|
48
|
+
- "scripts/**/*.ts"
|
|
49
|
+
|
|
50
|
+
- id: no-third-party-http-clients
|
|
51
|
+
description: "Use the platform fetch / a project client instead of ad-hoc HTTP client libraries (axios/got/ky/ofetch/undici)."
|
|
52
|
+
severity: error
|
|
53
|
+
evaluator:
|
|
54
|
+
type: forbidden-import
|
|
55
|
+
config:
|
|
56
|
+
forbidden:
|
|
57
|
+
- specifier: "axios"
|
|
58
|
+
- specifier: "got"
|
|
59
|
+
- specifier: "ky"
|
|
60
|
+
- specifier: "ofetch"
|
|
61
|
+
- specifier: "undici"
|
|
62
|
+
scope:
|
|
63
|
+
include:
|
|
64
|
+
- "apps/**/*.ts"
|
|
65
|
+
- "packages/**/*.ts"
|
|
66
|
+
- "scripts/**/*.ts"
|
|
67
|
+
exclude:
|
|
68
|
+
- "**/tests/**"
|
|
69
|
+
- "**/*.test.ts"
|
|
70
|
+
- "**/dist/**"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# [STRICT — opt-in] Meta-validation for local .spur/rules YAML. Absorbed from
|
|
2
|
+
# ts-libs/.spur/rules/meta/rule-files-structural.yaml. The engine already
|
|
3
|
+
# validates rule files on load, so this is a redundant pre-commit self-check;
|
|
4
|
+
# it also requires `yq` + `rg` on PATH. Opt-in only.
|
|
5
|
+
rules:
|
|
6
|
+
- id: rule-yaml-files-valid-structure
|
|
7
|
+
description: "Local .spur/rules YAML files must parse and expose either rule entries or a preset (name + extends)."
|
|
8
|
+
severity: error
|
|
9
|
+
evaluator:
|
|
10
|
+
type: exit-code
|
|
11
|
+
config:
|
|
12
|
+
command: sh
|
|
13
|
+
args:
|
|
14
|
+
- -c
|
|
15
|
+
- |
|
|
16
|
+
command -v yq >/dev/null 2>&1 || { echo "SKIP: yq not on PATH"; exit 0; }
|
|
17
|
+
command -v rg >/dev/null 2>&1 || { echo "SKIP: rg not on PATH"; exit 0; }
|
|
18
|
+
errors=0
|
|
19
|
+
for f in $(rg --files .spur/rules -g '*.yaml'); do
|
|
20
|
+
if ! yq -e '.' "$f" >/dev/null 2>&1; then
|
|
21
|
+
echo "INVALID: $f is not valid YAML"
|
|
22
|
+
errors=$((errors + 1))
|
|
23
|
+
continue
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if yq -e '.rules != null' "$f" >/dev/null 2>&1; then
|
|
27
|
+
if ! yq -e '.rules | type == "!!seq"' "$f" >/dev/null 2>&1; then
|
|
28
|
+
echo "INVALID: $f: .rules must be an array"
|
|
29
|
+
errors=$((errors + 1))
|
|
30
|
+
continue
|
|
31
|
+
fi
|
|
32
|
+
count="$(yq '.rules | length' "$f")"
|
|
33
|
+
i=0
|
|
34
|
+
while [ "$i" -lt "$count" ]; do
|
|
35
|
+
rid="$(yq ".rules[$i].id // \"\"" "$f")"
|
|
36
|
+
rdesc="$(yq ".rules[$i].description // \"\"" "$f")"
|
|
37
|
+
rtype="$(yq ".rules[$i].evaluator.type // \"\"" "$f")"
|
|
38
|
+
[ -n "$rid" ] && [ "$rid" != "null" ] || { echo "INVALID: $f: rules[$i] missing id"; errors=$((errors + 1)); }
|
|
39
|
+
[ -n "$rdesc" ] && [ "$rdesc" != "null" ] || { echo "INVALID: $f: rules[$i] missing description"; errors=$((errors + 1)); }
|
|
40
|
+
[ -n "$rtype" ] && [ "$rtype" != "null" ] || { echo "INVALID: $f: rules[$i] missing evaluator.type"; errors=$((errors + 1)); }
|
|
41
|
+
i=$((i + 1))
|
|
42
|
+
done
|
|
43
|
+
elif yq -e '.name != null' "$f" >/dev/null 2>&1; then
|
|
44
|
+
if ! yq -e '.extends | type == "!!seq"' "$f" >/dev/null 2>&1; then
|
|
45
|
+
echo "INVALID: $f: presets must define an extends array"
|
|
46
|
+
errors=$((errors + 1))
|
|
47
|
+
fi
|
|
48
|
+
else
|
|
49
|
+
echo "INVALID: $f: expected .rules or .name"
|
|
50
|
+
errors=$((errors + 1))
|
|
51
|
+
fi
|
|
52
|
+
done
|
|
53
|
+
[ "$errors" -eq 0 ] || exit 1
|
|
54
|
+
include:
|
|
55
|
+
- ".spur/rules/**/*.yaml"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# [STRICT — opt-in] Runtime boundaries. Absorbed from
|
|
2
|
+
# ts-libs/.spur/rules/typescript/runtime-boundaries.yaml. NOT in
|
|
3
|
+
# recommended-pre-check: Spur consumes ts-runtime's ProcessExecutor/FileSystem
|
|
4
|
+
# seams but also does light direct fs/spawn at app edges, so these run as
|
|
5
|
+
# opt-in hygiene with explicit allowlists for the sanctioned sites.
|
|
6
|
+
# `no-direct-process-env` is intentionally omitted — config-from-env is Spur's
|
|
7
|
+
# design (packages/config, apps/cli/context).
|
|
8
|
+
include:
|
|
9
|
+
- "apps/**/src/**/*.ts"
|
|
10
|
+
- "packages/**/src/**/*.ts"
|
|
11
|
+
exclude:
|
|
12
|
+
- "**/tests/**"
|
|
13
|
+
- "**/*.test.ts"
|
|
14
|
+
- "**/node_modules/**"
|
|
15
|
+
- "**/dist/**"
|
|
16
|
+
|
|
17
|
+
rules:
|
|
18
|
+
- id: no-direct-process-spawn
|
|
19
|
+
description: "Agent/process execution must go through ts-ai-runner / ts-runtime's ProcessExecutor. The only sanctioned direct spawn is `spur agent edit` opening $EDITOR."
|
|
20
|
+
severity: warning
|
|
21
|
+
evaluator:
|
|
22
|
+
type: forbidden-import
|
|
23
|
+
config:
|
|
24
|
+
forbidden:
|
|
25
|
+
- specifier: "node:child_process"
|
|
26
|
+
- specifier: "child_process"
|
|
27
|
+
- pattern: "Bun\\.(spawn|spawnSync)\\s*\\("
|
|
28
|
+
matchMode: usage
|
|
29
|
+
- specifier: "execa"
|
|
30
|
+
- specifier: "zx"
|
|
31
|
+
scope:
|
|
32
|
+
include:
|
|
33
|
+
- "apps/**/src/**/*.ts"
|
|
34
|
+
- "packages/**/src/**/*.ts"
|
|
35
|
+
exclude:
|
|
36
|
+
- "**/tests/**"
|
|
37
|
+
- "**/*.test.ts"
|
|
38
|
+
- "**/dist/**"
|
|
39
|
+
# `spur agent edit` opens the operator's $EDITOR interactively.
|
|
40
|
+
- "apps/cli/src/commands/agent.ts"
|
|
41
|
+
|
|
42
|
+
- id: no-direct-fs-io
|
|
43
|
+
description: "Prefer ts-runtime's FileSystem seam for fs IO. Light direct fs at app edges (service stat/access, migration loading) is allowlisted."
|
|
44
|
+
severity: warning
|
|
45
|
+
evaluator:
|
|
46
|
+
type: forbidden-import
|
|
47
|
+
config:
|
|
48
|
+
forbidden:
|
|
49
|
+
- pattern: "from\\s+['\"]node:fs(?:/promises)?['\"]|require\\(['\"]node:fs"
|
|
50
|
+
matchMode: usage
|
|
51
|
+
- pattern: "\\bBun\\.(?:write)\\("
|
|
52
|
+
matchMode: usage
|
|
53
|
+
scope:
|
|
54
|
+
include:
|
|
55
|
+
- "apps/**/src/**/*.ts"
|
|
56
|
+
- "packages/**/src/**/*.ts"
|
|
57
|
+
exclude:
|
|
58
|
+
- "**/tests/**"
|
|
59
|
+
- "**/*.test.ts"
|
|
60
|
+
- "**/dist/**"
|
|
61
|
+
# Sanctioned direct-fs sites (no local FileSystem seam re-implemented):
|
|
62
|
+
- "packages/app/src/services/workflow-service.ts"
|
|
63
|
+
- "packages/app/src/services/agent-service.ts"
|
|
64
|
+
- "packages/domain/src/migrations.ts"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Strict-check preset — opt-in hygiene rules absorbed from ts-libs but kept OUT
|
|
2
|
+
# of the default recommended-pre-check gate (they need project-specific tuning
|
|
3
|
+
# or are redundant with engine-level validation). Run explicitly:
|
|
4
|
+
# spur rule run --preset strict-check
|
|
5
|
+
# or cherry-pick a single rule:
|
|
6
|
+
# spur rule run --rule no-third-party-http-clients
|
|
7
|
+
#
|
|
8
|
+
# `extends: [strict]` references the strict/ category directory (the preset file
|
|
9
|
+
# is named differently to avoid a name↔category collision).
|
|
10
|
+
name: strict-check
|
|
11
|
+
description: >
|
|
12
|
+
Opt-in boundary hygiene: HTTP-client centralization, runtime fs/spawn seams,
|
|
13
|
+
and local rule-file structural validation. Not part of recommended-pre-check.
|
|
14
|
+
extends:
|
|
15
|
+
- strict
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Protected-file rules — secret detection and git/repo safety. Absorbed from
|
|
2
|
+
# ts-libs/.spur/rules/structure/protected-files.yaml, re-scoped for apps/** +
|
|
3
|
+
# packages/** + scripts/** (release/dev tooling is a prime secret-leak site).
|
|
4
|
+
# Mirrors the AGENTS.md safety boundaries (no secrets, no .env, no unreviewed
|
|
5
|
+
# workflows, no destructive git).
|
|
6
|
+
rules:
|
|
7
|
+
- id: no-hardcoded-secrets
|
|
8
|
+
description: "Hardcoded secrets detected; read secrets from environment/config boundaries instead. Spur never stores agent API keys (AGENTS.md)."
|
|
9
|
+
severity: error
|
|
10
|
+
evaluator:
|
|
11
|
+
type: secrets-scanner
|
|
12
|
+
config:
|
|
13
|
+
categories: [api-key, token, private-key, password, connection-string]
|
|
14
|
+
# The secrets-scanner uses the in-process LOOSE matcher (substring/suffix
|
|
15
|
+
# fragments), not ripgrep globs. Deep globs like `apps/**/*.ts` collapse to
|
|
16
|
+
# `apps/.ts` and match NOTHING — so scope must be path FRAGMENTS:
|
|
17
|
+
# `apps/`, `packages/`, `scripts/` cover all TS source via substring match.
|
|
18
|
+
scope:
|
|
19
|
+
include:
|
|
20
|
+
- "apps/"
|
|
21
|
+
- "packages/"
|
|
22
|
+
- "scripts/"
|
|
23
|
+
exclude:
|
|
24
|
+
- "/tests/"
|
|
25
|
+
- ".test.ts"
|
|
26
|
+
- "/node_modules/"
|
|
27
|
+
- "/dist/"
|
|
28
|
+
|
|
29
|
+
- id: no-unsafe-git-commands
|
|
30
|
+
description: "Unsafe git commands (force-push, hard reset) can overwrite history or cause data loss (AGENTS.md CRITICAL safety)."
|
|
31
|
+
severity: error
|
|
32
|
+
evaluator:
|
|
33
|
+
type: rg
|
|
34
|
+
config:
|
|
35
|
+
pattern: "git\\s+.*(push\\s+.*--force|push\\s+.*--force-with-lease\\b)|git\\s+.*reset\\s+--hard\\b"
|
|
36
|
+
include:
|
|
37
|
+
- "**/*.sh"
|
|
38
|
+
- "**/*.bash"
|
|
39
|
+
- "scripts/**/*.ts"
|
|
40
|
+
- "package.json"
|
|
41
|
+
- ".github/**/*.yml"
|
|
42
|
+
- ".github/**/*.yaml"
|
|
43
|
+
|
|
44
|
+
- id: no-env-files
|
|
45
|
+
description: ".env files must not be committed; use checked-in examples only (AGENTS.md: never commit .env*)."
|
|
46
|
+
severity: error
|
|
47
|
+
evaluator:
|
|
48
|
+
type: path
|
|
49
|
+
config:
|
|
50
|
+
must: absent
|
|
51
|
+
include:
|
|
52
|
+
- "**/.env"
|
|
53
|
+
- "**/.env.*"
|
|
54
|
+
exclude:
|
|
55
|
+
- "**/.env.example"
|
|
56
|
+
- "**/node_modules/**"
|
|
57
|
+
|
|
58
|
+
# NOTE: the ts-libs `no-github-workflows` rule (asserting .github/workflows must be
|
|
59
|
+
# ABSENT) was intentionally NOT absorbed — ts-libs is a library repo with no CI,
|
|
60
|
+
# but Spur is an application repo that legitimately ships ci.yml + publish.yml.
|
|
61
|
+
# "Never edit workflows without approval" (AGENTS.md) is a process rule for agents,
|
|
62
|
+
# not a file-absence assertion.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# No focused or skipped tests committed to the suite.
|
|
2
|
+
# Part of the recommended-pre-check preset — caught BEFORE the suite runs so a
|
|
3
|
+
# narrowed or disabled test never lands on a branch and silently shrinks
|
|
4
|
+
# coverage.
|
|
5
|
+
#
|
|
6
|
+
# Scope: test files only (*.test.ts / *.test.tsx). The bare `.only(` / `.skip(`
|
|
7
|
+
# arms are safe here because the bun:test focus/skip API is the only thing in a
|
|
8
|
+
# test file that uses them; restricting scope avoids false positives on unrelated
|
|
9
|
+
# `.skip()` calls elsewhere. Pattern is ripgrep dialect (no lookbehind/backrefs).
|
|
10
|
+
include:
|
|
11
|
+
- "apps/**/tests/**/*.test.ts"
|
|
12
|
+
- "apps/**/tests/**/*.test.tsx"
|
|
13
|
+
- "packages/**/tests/**/*.test.ts"
|
|
14
|
+
- "packages/**/tests/**/*.test.tsx"
|
|
15
|
+
exclude:
|
|
16
|
+
- "**/node_modules/**"
|
|
17
|
+
rules:
|
|
18
|
+
- id: no-focused-or-skipped-tests
|
|
19
|
+
description: >
|
|
20
|
+
Do not commit focused (.only) or skipped (.skip) tests. They narrow or
|
|
21
|
+
disable the suite, masking failures and coverage gaps. Remove the modifier
|
|
22
|
+
before committing; quarantine flaky tests with a tracked issue instead.
|
|
23
|
+
severity: error
|
|
24
|
+
evaluator:
|
|
25
|
+
type: rg
|
|
26
|
+
config:
|
|
27
|
+
pattern: "\\b(describe|it|test|bench)\\.(only|skip)\\(|\\b(fdescribe|fit|xdescribe|xit)\\("
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Local override of the global structure/test-location.yaml — same relative path,
|
|
2
|
+
# so it shadows the global copy (local wins in the layered merge) and the
|
|
3
|
+
# `require-corresponding-test` rule is defined exactly once. Keeps `no-tests-dir`
|
|
4
|
+
# from the global file and adds project-specific excludes (declaration-only files,
|
|
5
|
+
# schema/migration/db modules) to `require-corresponding-test`.
|
|
6
|
+
rules:
|
|
7
|
+
- id: no-tests-dir
|
|
8
|
+
description: "Tests must live in tests/ directories at the same level as src/, not in __tests__."
|
|
9
|
+
severity: error
|
|
10
|
+
evaluator:
|
|
11
|
+
type: test-location
|
|
12
|
+
config:
|
|
13
|
+
expected: "**/*.test.*"
|
|
14
|
+
forbid:
|
|
15
|
+
- "**/__tests__/**"
|
|
16
|
+
- "**/src/**/*.test.ts"
|
|
17
|
+
- "**/src/**/*.test.tsx"
|
|
18
|
+
requireCorrespondingTest: false
|
|
19
|
+
include:
|
|
20
|
+
- "packages/**/tests/**/*.test.ts"
|
|
21
|
+
- "apps/**/tests/**/*.test.ts"
|
|
22
|
+
- "packages/**/tests/**/*.test.tsx"
|
|
23
|
+
- "apps/**/tests/**/*.test.tsx"
|
|
24
|
+
|
|
25
|
+
- id: require-corresponding-test
|
|
26
|
+
description: "Every source file must have a corresponding test in tests/, excluding declaration-only files."
|
|
27
|
+
severity: warning
|
|
28
|
+
evaluator:
|
|
29
|
+
type: test-location
|
|
30
|
+
config:
|
|
31
|
+
expected: "**/*.test.*"
|
|
32
|
+
requireCorrespondingTest: true
|
|
33
|
+
include:
|
|
34
|
+
- "packages/**/src/**/*.ts"
|
|
35
|
+
- "apps/**/src/**/*.ts"
|
|
36
|
+
exclude:
|
|
37
|
+
- "**/index.ts"
|
|
38
|
+
- "**/*.d.ts"
|
|
39
|
+
- "**/types.ts"
|
|
40
|
+
- "**/schema/**"
|
|
41
|
+
- "**/migrations.ts"
|
|
42
|
+
- "**/db.ts"
|
|
43
|
+
- "**/node_modules/**"
|
|
44
|
+
- "**/tests/**"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# CLI surface consistency — structural checks that every command file follows
|
|
2
|
+
# the registerXxxCommand convention and that --json claims are backed by real
|
|
3
|
+
# output serialization (toJson or JSON.stringify).
|
|
4
|
+
#
|
|
5
|
+
# Part of the recommended-pre-check preset. Run:
|
|
6
|
+
# spur rule run --preset recommended-pre-check
|
|
7
|
+
|
|
8
|
+
include:
|
|
9
|
+
- "apps/cli/src/commands/*.ts"
|
|
10
|
+
|
|
11
|
+
exclude:
|
|
12
|
+
- "apps/cli/src/commands/stubs.ts"
|
|
13
|
+
|
|
14
|
+
rules:
|
|
15
|
+
- id: cli-register-pattern
|
|
16
|
+
description: >
|
|
17
|
+
Every CLI command file must export a registerXxxCommand(program, context)
|
|
18
|
+
function matching the standard Commander.js wiring pattern.
|
|
19
|
+
severity: error
|
|
20
|
+
evaluator:
|
|
21
|
+
type: rg
|
|
22
|
+
config:
|
|
23
|
+
pattern: "export function register\\w+Command\\("
|
|
24
|
+
mode: require
|
|
25
|
+
|
|
26
|
+
- id: cli-json-output
|
|
27
|
+
description: >
|
|
28
|
+
Command files that register --json options must call toJson() or
|
|
29
|
+
JSON.stringify() to produce machine-readable output.
|
|
30
|
+
severity: error
|
|
31
|
+
evaluator:
|
|
32
|
+
type: rg
|
|
33
|
+
config:
|
|
34
|
+
pattern: "toJson\\b|JSON\\.stringify\\b"
|
|
35
|
+
mode: require
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Bun/tooling rules — Spur standardizes on Bun + bun:test (AGENTS.md "Stack").
|
|
2
|
+
# Absorbed from ts-libs/.spur/rules/typescript/bun-only.yaml, re-scoped for the
|
|
3
|
+
# apps/** + packages/** + scripts/** app-monorepo layout and the server's
|
|
4
|
+
# Cloudflare Workers Vitest suite (the one sanctioned non-bun:test runner — `*.cf.ts`).
|
|
5
|
+
include:
|
|
6
|
+
- "package.json"
|
|
7
|
+
- "apps/**/*.ts"
|
|
8
|
+
- "packages/**/*.ts"
|
|
9
|
+
- "scripts/**/*.ts"
|
|
10
|
+
- ".spur/**/*.yaml"
|
|
11
|
+
exclude:
|
|
12
|
+
- "**/node_modules/**"
|
|
13
|
+
- "**/dist/**"
|
|
14
|
+
|
|
15
|
+
rules:
|
|
16
|
+
- id: no-npm-pnpm-yarn-scripts
|
|
17
|
+
description: "Use bun scripts instead of npm, pnpm, or yarn commands (AGENTS.md: no npm/pnpm/yarn)."
|
|
18
|
+
severity: error
|
|
19
|
+
evaluator:
|
|
20
|
+
type: rg
|
|
21
|
+
config:
|
|
22
|
+
pattern: "\\b(npm run|pnpm|yarn)\\b"
|
|
23
|
+
include:
|
|
24
|
+
- "package.json"
|
|
25
|
+
- "apps/*/package.json"
|
|
26
|
+
- "packages/*/package.json"
|
|
27
|
+
- "scripts/**/*.ts"
|
|
28
|
+
- "**/*.yml"
|
|
29
|
+
- "**/*.yaml"
|
|
30
|
+
exclude:
|
|
31
|
+
# Rule-definition YAML legitimately names these tools in patterns/descriptions.
|
|
32
|
+
- "config/rules/**"
|
|
33
|
+
- ".spur/rules/**"
|
|
34
|
+
|
|
35
|
+
- id: enforce-bun-test
|
|
36
|
+
description: "Unit/integration tests must use bun:test, not Jest or Mocha. The server's Cloudflare Workers suite (*.cf.ts) is the only sanctioned Vitest runner."
|
|
37
|
+
severity: error
|
|
38
|
+
evaluator:
|
|
39
|
+
type: rg
|
|
40
|
+
config:
|
|
41
|
+
pattern: "from [''\"](jest|mocha|@jest)"
|
|
42
|
+
include:
|
|
43
|
+
- "apps/**/*.test.ts"
|
|
44
|
+
- "packages/**/*.test.ts"
|
|
45
|
+
- "scripts/**/*.test.ts"
|
|
46
|
+
|
|
47
|
+
- id: no-vitest-imports-or-apis
|
|
48
|
+
description: "Use bun:test APIs instead of vitest, except the server's Cloudflare Workers tests (*.cf.ts) which must run under the Workers pool via Vitest."
|
|
49
|
+
severity: error
|
|
50
|
+
evaluator:
|
|
51
|
+
type: rg
|
|
52
|
+
config:
|
|
53
|
+
pattern: "from ['\"]vitest['\"]|vi\\.(fn|spyOn|mock|stub)\\b"
|
|
54
|
+
include:
|
|
55
|
+
- "apps/**/*.ts"
|
|
56
|
+
- "packages/**/*.ts"
|
|
57
|
+
- "scripts/**/*.ts"
|
|
58
|
+
exclude:
|
|
59
|
+
- "apps/server/tests/cf/**"
|
|
60
|
+
- "apps/server/vitest.cf.config.ts"
|
|
61
|
+
- "**/*.cf.ts"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# No Biome suppression comments in production source.
|
|
2
|
+
# Absorbed from ts-libs/.spur/rules/typescript/no-biome-suppressions.yaml,
|
|
3
|
+
# re-scoped to Spur's monorepo layout:
|
|
4
|
+
# - the `rg` evaluator forwards include/exclude to ripgrep as proper globs,
|
|
5
|
+
# so it handles deep ** globs correctly (unlike the legacy regex matcher,
|
|
6
|
+
# which forced a `src/` substring workaround). Scope is the real
|
|
7
|
+
# apps/**/src + packages/**/src production source plus scripts/** tooling.
|
|
8
|
+
# - test files excluded where noExplicitAny / useLiteralKeys suppressions
|
|
9
|
+
# are sometimes legitimate in test helpers
|
|
10
|
+
# - severity kept as error (encodes AGENTS.md gate rule: "No new biome-ignore
|
|
11
|
+
# added solely to silence the gate")
|
|
12
|
+
include:
|
|
13
|
+
- "apps/**/src/**/*.ts"
|
|
14
|
+
- "packages/**/src/**/*.ts"
|
|
15
|
+
- "scripts/**/*.ts"
|
|
16
|
+
exclude:
|
|
17
|
+
- "**/node_modules/**"
|
|
18
|
+
- "**/dist/**"
|
|
19
|
+
- "**/tests/**"
|
|
20
|
+
rules:
|
|
21
|
+
- id: no-biome-suppressions
|
|
22
|
+
description: >
|
|
23
|
+
Do not use biome-ignore or biome-ignore-all comments to suppress lint
|
|
24
|
+
rules. Fix the underlying issue instead.
|
|
25
|
+
severity: error
|
|
26
|
+
evaluator:
|
|
27
|
+
type: rg
|
|
28
|
+
config:
|
|
29
|
+
pattern: "biome-ignore"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# No `debugger` statements in production source.
|
|
2
|
+
# Part of the recommended-pre-check preset — a debugger left in shipped source
|
|
3
|
+
# halts execution under an attached inspector and is never intended for commit.
|
|
4
|
+
#
|
|
5
|
+
# Scope: production source plus scripts tooling. Tests excluded (a debugger in a
|
|
6
|
+
# local test run is a transient debugging aid, not shipped behavior) — though the
|
|
7
|
+
# focus/skip rule already guards the more common test-hygiene mistake. Pattern is
|
|
8
|
+
# ripgrep dialect: `\bdebugger\b` matches the statement, not substrings like
|
|
9
|
+
# `debuggerPort`.
|
|
10
|
+
include:
|
|
11
|
+
- "apps/**/src/**/*.ts"
|
|
12
|
+
- "packages/**/src/**/*.ts"
|
|
13
|
+
- "scripts/**/*.ts"
|
|
14
|
+
exclude:
|
|
15
|
+
- "**/node_modules/**"
|
|
16
|
+
- "**/dist/**"
|
|
17
|
+
- "**/tests/**"
|
|
18
|
+
rules:
|
|
19
|
+
- id: no-debugger-statement
|
|
20
|
+
description: >
|
|
21
|
+
Do not commit `debugger` statements in production source. They pause
|
|
22
|
+
execution under an attached inspector and have no place in shipped code.
|
|
23
|
+
severity: error
|
|
24
|
+
evaluator:
|
|
25
|
+
type: rg
|
|
26
|
+
config:
|
|
27
|
+
pattern: "\\bdebugger\\b\\s*;?"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Output boundary rules — terminal writes go through the output seam
|
|
2
|
+
# (apps/cli/src/output.ts → ts-utils echo/echoError), never raw console/stdout.
|
|
3
|
+
# Absorbed from ts-libs/.spur/rules/typescript/no-raw-output.yaml, re-scoped to
|
|
4
|
+
# the app-monorepo layout. Spur's only sanctioned sink is the ts-utils helper,
|
|
5
|
+
# so no local exclusions are needed.
|
|
6
|
+
include:
|
|
7
|
+
- "apps/**/src/**/*.ts"
|
|
8
|
+
- "packages/**/src/**/*.ts"
|
|
9
|
+
exclude:
|
|
10
|
+
- "**/node_modules/**"
|
|
11
|
+
- "**/dist/**"
|
|
12
|
+
|
|
13
|
+
rules:
|
|
14
|
+
- id: no-console-output
|
|
15
|
+
description: "Production source must use the output seam (apps/cli/src/output.ts / ts-utils echo) instead of direct console output."
|
|
16
|
+
severity: error
|
|
17
|
+
evaluator:
|
|
18
|
+
type: rg
|
|
19
|
+
config:
|
|
20
|
+
pattern: "console\\.(log|error|warn|info|debug|dir|time|trace)"
|
|
21
|
+
|
|
22
|
+
- id: no-raw-stdout-stderr
|
|
23
|
+
description: "Use the output seam (ts-utils echo/echoError) instead of process.stdout/stderr.write directly."
|
|
24
|
+
severity: error
|
|
25
|
+
evaluator:
|
|
26
|
+
type: rg
|
|
27
|
+
config:
|
|
28
|
+
pattern: "process\\.(stdout|stderr)\\.write"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Canonical basic workflow: implement -> check -> fix until the check passes or the
|
|
2
|
+
# iteration bound is exhausted. Authored for the @gobing-ai/ts-dual-workflow-engine
|
|
3
|
+
# state-machine schema (initialState / states[].id / onEnter / top-level transitions).
|
|
4
|
+
# See Architecture Section 5 and Design Section 3.3.
|
|
5
|
+
name: basic
|
|
6
|
+
kind: state-machine
|
|
7
|
+
description: The canonical implement-check-fix-until-pass loop
|
|
8
|
+
iterationBound: 2
|
|
9
|
+
initialState: implement
|
|
10
|
+
terminalStates:
|
|
11
|
+
- done
|
|
12
|
+
- failed
|
|
13
|
+
states:
|
|
14
|
+
- id: implement
|
|
15
|
+
description: Implement the requested task
|
|
16
|
+
onEnter:
|
|
17
|
+
- kind: note
|
|
18
|
+
options:
|
|
19
|
+
message: 'Implementing task: ${task}'
|
|
20
|
+
|
|
21
|
+
- id: check
|
|
22
|
+
description: Verify the implementation passes all checks
|
|
23
|
+
onEnter:
|
|
24
|
+
- kind: shell
|
|
25
|
+
options:
|
|
26
|
+
command: bun run check
|
|
27
|
+
|
|
28
|
+
- id: fix
|
|
29
|
+
description: Address failures from the check pass
|
|
30
|
+
onEnter:
|
|
31
|
+
- kind: note
|
|
32
|
+
options:
|
|
33
|
+
message: 'Please fix the issues found'
|
|
34
|
+
|
|
35
|
+
- id: done
|
|
36
|
+
description: Terminal — workflow completed successfully
|
|
37
|
+
- id: failed
|
|
38
|
+
description: Terminal — workflow failed
|
|
39
|
+
|
|
40
|
+
transitions:
|
|
41
|
+
- from: implement
|
|
42
|
+
to: check
|
|
43
|
+
description: Hand the implemented task to the check pass
|
|
44
|
+
|
|
45
|
+
# Check passed -> done; otherwise loop back to fix. Declaration order matters:
|
|
46
|
+
# the action-ok guard is tried first, so a passing check short-circuits to done.
|
|
47
|
+
- from: check
|
|
48
|
+
to: done
|
|
49
|
+
description: Check passed — finish successfully
|
|
50
|
+
guard:
|
|
51
|
+
kind: action-ok
|
|
52
|
+
- from: check
|
|
53
|
+
to: fix
|
|
54
|
+
description: Check failed — route to a fix attempt
|
|
55
|
+
|
|
56
|
+
- from: fix
|
|
57
|
+
to: check
|
|
58
|
+
description: Re-run the check after fixing
|