@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.
Files changed (27) hide show
  1. package/README.md +78 -0
  2. package/package.json +67 -0
  3. package/schemas/spur-config.schema.json +140 -0
  4. package/spur-cli/config/config.example.yaml +44 -0
  5. package/spur-cli/config/plugins/.gitkeep +0 -0
  6. package/spur-cli/config/rules/README.md +40 -0
  7. package/spur-cli/config/rules/boundary/dao-boundary.yaml +126 -0
  8. package/spur-cli/config/rules/migration/rg-dialect.yaml +44 -0
  9. package/spur-cli/config/rules/quality/coverage-gate.yaml +36 -0
  10. package/spur-cli/config/rules/quality/tsdoc-exports.yaml +29 -0
  11. package/spur-cli/config/rules/recommended-post-check.yaml +10 -0
  12. package/spur-cli/config/rules/recommended-pre-check.yaml +12 -0
  13. package/spur-cli/config/rules/rg-migration.yaml +14 -0
  14. package/spur-cli/config/rules/strict/http-boundaries.yaml +70 -0
  15. package/spur-cli/config/rules/strict/rule-files-structural.yaml +55 -0
  16. package/spur-cli/config/rules/strict/runtime-boundaries.yaml +64 -0
  17. package/spur-cli/config/rules/strict-check.yaml +15 -0
  18. package/spur-cli/config/rules/structure/protected-files.yaml +62 -0
  19. package/spur-cli/config/rules/structure/test-focus-skip.yaml +27 -0
  20. package/spur-cli/config/rules/structure/test-location.yaml +44 -0
  21. package/spur-cli/config/rules/surface/check-cli-surface.yaml +35 -0
  22. package/spur-cli/config/rules/typescript/bun-tooling.yaml +61 -0
  23. package/spur-cli/config/rules/typescript/no-biome-suppressions.yaml +29 -0
  24. package/spur-cli/config/rules/typescript/no-debugger.yaml +27 -0
  25. package/spur-cli/config/rules/typescript/output-boundaries.yaml +28 -0
  26. package/spur-cli/config/workflows/basic.yaml +58 -0
  27. 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