@mdferdousalam/agentic-sdlc 0.0.0-stage → 3.0.0

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 (90) hide show
  1. package/.claude-plugin/marketplace.json +11 -0
  2. package/.claude-plugin/plugin.json +31 -0
  3. package/CHANGELOG.md +330 -0
  4. package/GETTING-STARTED.md +174 -0
  5. package/LICENSE +21 -0
  6. package/README.md +268 -2
  7. package/SECURITY.md +25 -0
  8. package/agents/sdlc-architecture-reviewer.md +71 -0
  9. package/agents/sdlc-branch-opener.md +83 -0
  10. package/agents/sdlc-code-reviewer.md +62 -0
  11. package/agents/sdlc-impact-analyst.md +70 -0
  12. package/agents/sdlc-implementer.md +60 -0
  13. package/agents/sdlc-lander.md +40 -0
  14. package/agents/sdlc-readiness-gate.md +128 -0
  15. package/agents/sdlc-release-manager.md +76 -0
  16. package/agents/sdlc-remediator.md +89 -0
  17. package/agents/sdlc-reporter.md +42 -0
  18. package/agents/sdlc-security-reviewer.md +62 -0
  19. package/agents/sdlc-spec-reviewer.md +56 -0
  20. package/agents/sdlc-spec-writer.md +62 -0
  21. package/agents/sdlc-test-author.md +32 -0
  22. package/agents/sdlc-triager.md +48 -0
  23. package/agents/sdlc-verifier.md +69 -0
  24. package/bin/__pycache__/sdlccpython-315.pyc +0 -0
  25. package/bin/sdlc +2747 -0
  26. package/bin/sdlc-install-opencode +10 -0
  27. package/commands/doctor.md +9 -0
  28. package/commands/feature.md +17 -0
  29. package/commands/gate.md +33 -0
  30. package/commands/impact.md +13 -0
  31. package/commands/implement.md +11 -0
  32. package/commands/init.md +15 -0
  33. package/commands/land.md +10 -0
  34. package/commands/metrics.md +12 -0
  35. package/commands/readiness.md +8 -0
  36. package/commands/release.md +16 -0
  37. package/commands/remediate.md +13 -0
  38. package/commands/report.md +11 -0
  39. package/commands/review.md +12 -0
  40. package/commands/status.md +9 -0
  41. package/commands/trace.md +9 -0
  42. package/commands/triage.md +14 -0
  43. package/commands/verify.md +10 -0
  44. package/docs/compliance.md +70 -0
  45. package/docs/configuration.md +106 -0
  46. package/docs/control-spec.md +269 -0
  47. package/docs/install.md +75 -0
  48. package/docs/maturity.md +129 -0
  49. package/docs/opencode.md +45 -0
  50. package/docs/openspec.md +58 -0
  51. package/docs/pipeline.md +160 -0
  52. package/docs/platforms.md +99 -0
  53. package/docs/rules.md +122 -0
  54. package/evals/baseline-attributes-failure/graders/criteria.md +18 -0
  55. package/evals/baseline-attributes-failure/prompt.md +23 -0
  56. package/evals/budget-stops-rather-than-asks/graders/criteria.md +19 -0
  57. package/evals/budget-stops-rather-than-asks/prompt.md +14 -0
  58. package/evals/critical-low-confidence-escalates/graders/criteria.md +22 -0
  59. package/evals/critical-low-confidence-escalates/prompt.md +17 -0
  60. package/evals/gate-refuses-unexecuted/graders/criteria.md +16 -0
  61. package/evals/gate-refuses-unexecuted/prompt.md +15 -0
  62. package/evals/refusal-is-not-negotiated/graders/criteria.md +17 -0
  63. package/evals/refusal-is-not-negotiated/prompt.md +7 -0
  64. package/evals/release-needs-rollback-and-approval/graders/criteria.md +15 -0
  65. package/evals/release-needs-rollback-and-approval/prompt.md +6 -0
  66. package/evals/remediator-refuses-weakened-test/graders/criteria.md +20 -0
  67. package/evals/remediator-refuses-weakened-test/prompt.md +33 -0
  68. package/evals/spec-review-catches-unfalsifiable/graders/criteria.md +17 -0
  69. package/evals/spec-review-catches-unfalsifiable/prompt.md +17 -0
  70. package/examples/worked-example.md +131 -0
  71. package/hooks/hooks.json +16 -0
  72. package/npm/cli.js +35 -0
  73. package/opencode/plugin/agentic-sdlc.ts +52 -0
  74. package/package.json +59 -4
  75. package/profiles/README.md +52 -0
  76. package/profiles/compiler.md +33 -0
  77. package/profiles/concerns/architecture.md +50 -0
  78. package/profiles/concerns/concurrency.md +59 -0
  79. package/profiles/concerns/data.md +62 -0
  80. package/profiles/concerns/memory.md +52 -0
  81. package/profiles/concerns/performance.md +54 -0
  82. package/profiles/concerns/platform.md +62 -0
  83. package/profiles/library.md +31 -0
  84. package/profiles/service.md +32 -0
  85. package/profiles/webapp.md +40 -0
  86. package/schema/finding.schema.json +267 -0
  87. package/schema/sdlc.config.schema.json +324 -0
  88. package/skills/agentic-sdlc-pipeline/SKILL.md +66 -0
  89. package/skills/openspec-with-sdlc/SKILL.md +56 -0
  90. package/skills/writing-specifications/SKILL.md +51 -0
@@ -0,0 +1,17 @@
1
+ ---
2
+ max_turns: 12
3
+ allowed_tools: [Read, Glob, Grep, Skill]
4
+ ---
5
+ Review this specification for a feature and give your verdict.
6
+
7
+ ```markdown
8
+ # FRD — export improvements
9
+
10
+ FR-1 The export command should handle errors gracefully.
11
+ FR-2 Exports must be fast.
12
+ FR-3 `export --format=json` writes valid JSON to stdout and exits 0; on an unwritable
13
+ path it exits 2 with `EXP-004` naming the path.
14
+ AC-1 Users are happy with the export experience.
15
+ ```
16
+
17
+ Is this ready to build from? Answer with a verdict and your findings.
@@ -0,0 +1,131 @@
1
+ # A worked example — rate limiting on a login endpoint
2
+
3
+ A real unit of work from request to merged, in a Node service with OpenSpec installed. Nothing
4
+ here is hypothetical about the mechanics: every command is one you can run, and every refusal is
5
+ one the hook actually produces.
6
+
7
+ ## 0 · Adapt, once
8
+
9
+ ```console
10
+ $ /agentic-sdlc:init --write
11
+ wrote sdlc.config.json
12
+ $ /agentic-sdlc:doctor
13
+ note gitleaks not found (secret scanning)
14
+ doctor: ok
15
+ ```
16
+
17
+ `brew install gitleaks`, re-run `:init --write`, and `secretScan` fills itself in. The gate will
18
+ otherwise record that stage as `skipped` — honestly, but you will not want that in a service
19
+ that handles credentials.
20
+
21
+ Set the profile, since this is a service:
22
+
23
+ ```jsonc
24
+ { "profile": "service", "reviewers": ["sdlc-code-reviewer", "sdlc-security-reviewer"] }
25
+ ```
26
+
27
+ ## 1–3 · Specify, review, branch
28
+
29
+ ```text
30
+ /agentic-sdlc:feature "rate limit POST /login: 5 attempts per 15 minutes per account+IP"
31
+ ```
32
+
33
+ The spec writer produces `openspec/changes/login-rate-limit/` and validates it. The reviewer
34
+ comes back:
35
+
36
+ ```
37
+ SPEC: changes-requested — AC-3 is not falsifiable; the lockout response is unspecified
38
+ 1. tasks.md item 3 "handle abuse gracefully" — name the observable behaviour
39
+ 2. proposal.md — what status and body does a locked-out request get? 429 with what headers?
40
+ 3. design.md — what happens to the counter when the password is corrected on attempt 4?
41
+ ```
42
+
43
+ That is the stage doing its job. The writer fixes the three, the reviewer approves, and the
44
+ receipt is written:
45
+
46
+ ```console
47
+ $ sdlc receipt show spec
48
+ { "kind": "spec", "verdict": "approved",
49
+ "subject": "openspec/changes/login-rate-limit",
50
+ "subjectDigest": "sha256:4f2a9c1e8b3d7a05", ... }
51
+ ```
52
+
53
+ Only then does the branch exist. Try it the other way round and:
54
+
55
+ ```console
56
+ $ git switch -c feat/login-rate-limit
57
+ BLOCKED by agentic-sdlc: ... needs an approved spec
58
+ ```
59
+
60
+ ## 4 · Implement
61
+
62
+ ```text
63
+ /agentic-sdlc:implement
64
+ ```
65
+
66
+ Item by item, test first. The implementer hits a gap the spec did not settle — does the limit
67
+ count failed attempts only, or all? — and stops rather than choosing:
68
+
69
+ > tasks.md item 2 does not say whether a successful login resets the counter. Two defensible
70
+ > readings with different security properties. Handing back for a one-item spec revision.
71
+
72
+ Good. A guess here is a security decision made by accident.
73
+
74
+ ## 5–6 · Verify and review
75
+
76
+ ```console
77
+ $ /agentic-sdlc:verify
78
+ | AC-1 | passed | src/limiter.ts:31 | limiter.test.ts:12 `blocks the 6th attempt` | 1 passed |
79
+ | AC-2 | passed | src/limiter.ts:48 | limiter.test.ts:40 `resets after 15 minutes`| 1 passed |
80
+ | AC-4 | unexecuted | src/login.ts:77 | — | no test |
81
+
82
+ $ /agentic-sdlc:review
83
+ SECURITY: fail — the counter keys on IP alone behind the proxy; X-Forwarded-For is attacker-controlled
84
+ 1. src/limiter.ts:22 — trust only the proxy's rightmost hop, or key on account+IP as specified
85
+ ```
86
+
87
+ Two findings a summary would have hidden: one untested criterion, one real vulnerability that
88
+ the specification had actually ruled out and the implementation reintroduced.
89
+
90
+ ## 7 · The gate
91
+
92
+ ```console
93
+ $ /agentic-sdlc:readiness
94
+ Verdict: NOT-READY
95
+ | Clean tree | passed |
96
+ | Mechanical (pnpm run lint && pnpm test) | passed |
97
+ | Secret scan | passed |
98
+ | Reviewers | code: pass; security: fail |
99
+ | Criteria with evidence | 3 of 4 — AC-4 unexecuted |
100
+ ```
101
+
102
+ `pnpm test` is green and the change is still not ready. That gap is the entire point.
103
+
104
+ Fix the proxy handling, `sdlc-test-author` writes the missing AC-4 test and proves it fails
105
+ against the old code, re-run `:readiness` → READY.
106
+
107
+ ## 8–9 · Land and report
108
+
109
+ ```text
110
+ /agentic-sdlc:land --merge
111
+ ```
112
+
113
+ The pull request body carries the four criteria with evidence and the gate table. If you had
114
+ made one more commit after the gate:
115
+
116
+ ```console
117
+ BLOCKED by agentic-sdlc: merging needs a READY readiness receipt, and the readiness receipt is
118
+ for 9a1c4f21e but HEAD is 3b77de004 — the work changed after it was granted.
119
+ ```
120
+
121
+ Re-run the gate on the real commit. Then `:report` writes the implementation report — including
122
+ the counter-reset decision and why — and archives the OpenSpec change.
123
+
124
+ ## What the pipeline caught here
125
+
126
+ A spec with an unfalsifiable criterion. An ambiguity that would have become a silent security
127
+ decision. An untested criterion behind a green test run. A vulnerability the spec had excluded.
128
+ A merge of a commit nobody reviewed.
129
+
130
+ None of those is exotic. They are the ordinary failures of shipping under time pressure, and
131
+ each was caught by a stage that could not be skipped rather than by somebody remembering.
@@ -0,0 +1,16 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Bash|Edit|Write|NotebookEdit",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/bin/sdlc\" guard",
10
+ "timeout": 10
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
16
+ }
package/npm/cli.js ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ // Thin launcher. The CLI itself is one dependency-free Python file; this exists so that
3
+ // `npx @mdferdousalam/agentic-sdlc install` works, and so Windows gets a real shim instead of
4
+ // relying on a shebang npm cannot honour there.
5
+ "use strict";
6
+
7
+ const { spawnSync } = require("node:child_process");
8
+ const { join } = require("node:path");
9
+ const { existsSync } = require("node:fs");
10
+
11
+ const script = join(__dirname, "..", "bin", "sdlc");
12
+ if (!existsSync(script)) {
13
+ console.error(`agentic-sdlc: ${script} is missing — the package did not install cleanly.`);
14
+ process.exit(1);
15
+ }
16
+
17
+ // Windows commonly has `py` or `python` rather than `python3`.
18
+ const candidates = process.platform === "win32"
19
+ ? ["python", "py", "python3"]
20
+ : ["python3", "python"];
21
+
22
+ for (const exe of candidates) {
23
+ const probe = spawnSync(exe, ["-c", "import sys; sys.exit(0 if sys.version_info >= (3, 8) else 1)"],
24
+ { stdio: "ignore" });
25
+ if (probe.error || probe.status !== 0) continue;
26
+ const run = spawnSync(exe, [script, ...process.argv.slice(2)], { stdio: "inherit" });
27
+ process.exit(run.status === null ? 1 : run.status);
28
+ }
29
+
30
+ console.error(
31
+ "agentic-sdlc needs Python 3.8 or newer on PATH.\n" +
32
+ `Tried: ${candidates.join(", ")}.\n` +
33
+ "The CLI is a single file with no Python dependencies — any 3.8+ interpreter will do."
34
+ );
35
+ process.exit(1);
@@ -0,0 +1,52 @@
1
+ /**
2
+ * agentic-sdlc for opencode.
3
+ *
4
+ * The same enforcement the Claude Code plugin gets from its PreToolUse hook: every shell
5
+ * command and file edit is offered to `bin/sdlc guard`, which exits 2 when the pipeline would
6
+ * be skipped or something irreversible attempted. The rules live in the Python CLI, so both
7
+ * hosts refuse exactly the same things and there is one place to fix a rule.
8
+ *
9
+ * Installed by `bin/sdlc-install-opencode`, which rewrites PLUGIN_ROOT below.
10
+ */
11
+
12
+ import { spawnSync } from "node:child_process"
13
+
14
+ const PLUGIN_ROOT = "__PLUGIN_ROOT__"
15
+ const CLI = `${PLUGIN_ROOT}/bin/sdlc`
16
+
17
+ function ask(tool: string, input: unknown, cwd: string): string | undefined {
18
+ const r = spawnSync(CLI, ["guard"], {
19
+ input: JSON.stringify({ tool_name: tool, tool_input: input, cwd }),
20
+ encoding: "utf8",
21
+ timeout: 10_000,
22
+ })
23
+ // Exit 2 is the block contract; anything else means "no opinion", including a broken CLI —
24
+ // a guard that cannot run must not take the session down with it.
25
+ if (r.status === 2) return (r.stderr || "blocked by agentic-sdlc").trim()
26
+ return undefined
27
+ }
28
+
29
+ export default {
30
+ id: "agentic-sdlc",
31
+
32
+ async setup(ctx: any) {
33
+ const cwd: string = ctx?.app?.path?.cwd ?? ctx?.location?.directory ?? process.cwd()
34
+
35
+ await ctx.tool.hook("execute.before", (input: any) => {
36
+ const tool = String(input?.tool ?? "")
37
+ const args = input?.input ?? {}
38
+ const mapped =
39
+ tool === "bash" || tool === "shell" ? "Bash"
40
+ : tool === "edit" || tool === "write" || tool === "apply_patch" ? "Edit"
41
+ : null
42
+ if (!mapped) return
43
+ const problem = ask(mapped, args, cwd)
44
+ if (problem) throw new Error(problem)
45
+ })
46
+
47
+ await ctx.shell.hook("create.before", (input: any) => {
48
+ const problem = ask("Bash", { command: String(input?.command ?? "") }, String(input?.cwd ?? cwd))
49
+ if (problem) throw new Error(problem)
50
+ })
51
+ },
52
+ }
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "@mdferdousalam/agentic-sdlc",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "3.0.0",
4
+ "description": "An enforced agent-based SDLC pipeline for any coding agent: specify, review, branch, implement, verify, review, gate, land, release. Claude Code, opencode, Codex and Cline refuse a skipped stage at the tool call; Cursor, Copilot, Windsurf and every AGENTS.md host get the pipeline plus git-hook and CI enforcement.",
5
+ "keywords": [
6
+ "sdlc",
7
+ "agents",
8
+ "ai",
9
+ "code-review",
10
+ "quality-gate",
11
+ "spec-driven",
12
+ "openspec",
13
+ "claude-code",
14
+ "codex",
15
+ "cursor",
16
+ "copilot",
17
+ "cline",
18
+ "opencode",
19
+ "gemini",
20
+ "agents-md"
21
+ ],
22
+ "homepage": "https://github.com/mdferdousalam/agentic-sdlc#readme",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/mdferdousalam/agentic-sdlc.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/mdferdousalam/agentic-sdlc/issues"
29
+ },
30
+ "license": "MIT",
31
+ "author": "Engr Md Ferdous Alam <mdferdousalam1989@yahoo.com> (https://github.com/mdferdousalam)",
32
+ "bin": {
33
+ "sdlc": "npm/cli.js"
34
+ },
35
+ "engines": {
36
+ "node": ">=16"
37
+ },
38
+ "files": [
39
+ "npm/cli.js",
40
+ "bin/",
41
+ "agents/",
42
+ "commands/",
43
+ "skills/",
44
+ "profiles/",
45
+ "docs/",
46
+ "hooks/",
47
+ "schema/",
48
+ "opencode/",
49
+ "evals/",
50
+ "examples/",
51
+ ".claude-plugin/",
52
+ "README.md",
53
+ "GETTING-STARTED.md",
54
+ "CHANGELOG.md",
55
+ "LICENSE",
56
+ "SECURITY.md"
57
+ ],
58
+ "scripts": {
59
+ "test": "python3 tests/test_sdlc.py && node .github/scripts/validate-plugin.mjs"
60
+ }
61
+ }
@@ -0,0 +1,52 @@
1
+ # Profiles — the opinions that are not universal
2
+
3
+ The pipeline core is stack-agnostic on purpose. A profile is where a *kind* of project's
4
+ standards live, applied only when `"profile"` in `sdlc.config.json` names it.
5
+
6
+ This separation is deliberate. A rule like "no hardcoded value that the spec marked
7
+ configurable" is right for an admin-driven web application and meaningless for a compiler.
8
+ Baking it into the core would make the core lie about being stack-agnostic, and would make
9
+ every reviewer on every other kind of project produce noise.
10
+
11
+ | Profile | For | Adds |
12
+ | --- | --- | --- |
13
+ | `webapp` | product applications with an admin surface | configurability, data model, accessibility, migration safety |
14
+ | `library` | published packages and SDKs | public API compatibility, semver, documentation of every exported symbol |
15
+ | `service` | long-running network services | operability, failure modes, observability, backpressure |
16
+ | `compiler` | language and toolchain projects | specification-first, evidence vocabulary, bounded heavy workloads |
17
+
18
+ ## The second axis: concerns
19
+
20
+ A project kind is one choice. **Concerns are not** — they cut across kinds. A Go service needs
21
+ `service` *and* `concurrency`; a Rust compiler needs `compiler` *and* `memory`. Forcing those
22
+ into one field would make you pick the less useful half, so they are a separate list:
23
+
24
+ ```jsonc
25
+ { "profile": "service", "concerns": ["concurrency", "data", "performance"] }
26
+ ```
27
+
28
+ | Concern | Taxonomy | Adds |
29
+ | --- | --- | --- |
30
+ | [`architecture`](concerns/architecture.md) | B, J | dependency direction, cycles, boundaries, indirection that does not pay |
31
+ | [`performance`](concerns/performance.md) | E, N | complexity, N+1, pagination, caching, round trips, scale limits |
32
+ | [`memory`](concerns/memory.md) | F | ownership, lifetimes, cleanup on every exit, unbounded growth, safety |
33
+ | [`concurrency`](concerns/concurrency.md) | G | races, lock order, TOCTOU, task leaks, cancellation, backpressure |
34
+ | [`data`](concerns/data.md) | K, X6 | constraints, transaction boundaries, reversible migrations, idempotency |
35
+ | [`platform`](concerns/platform.md) | S, X8 | host assumptions, numerics, encoding, locale, shutdown, toolchain pinning |
36
+
37
+ `sdlc init` *proposes* concerns from what the repository evidently does — goroutines and mutexes
38
+ suggest `concurrency`, migrations suggest `data`. It never imposes one. A concern the
39
+ maintainers did not choose produces noise on every review, and a review tool that produces noise
40
+ gets switched off.
41
+
42
+ Each concern file ends by naming the **deterministic tool** for the stacks that have one, and
43
+ saying it belongs in `quality.cmd`. Until it is there, every finding the concern raises is a
44
+ reading rather than a measurement, and the profiles say so in those words: a `PERF-*` or
45
+ `MEM-*` finding from a diff is `medium` confidence at best. This plugin ships no analysers and
46
+ will not pretend to.
47
+
48
+ ## Writing your own
49
+
50
+ Either axis is one markdown file of standards the reviewers read in addition to the core rules.
51
+ Keep it to what a reviewer can check, and give each rule an ID in `docs/control-spec.md` — a
52
+ rule that lives only in a prompt is an opinion.
@@ -0,0 +1,33 @@
1
+ # Profile: compiler
2
+
3
+ For languages, toolchains and anything whose output other people's correctness depends on.
4
+
5
+ ## The specification comes before the code
6
+
7
+ - A change to what a program means changes the language specification first, in the same commit.
8
+ - A rule the checker gains bumps the semantic epoch; a runtime contract change bumps the ABI; a
9
+ changed machine-readable shape bumps its schema's major version — or the commit says why not.
10
+ - A new refusal gets a **new** diagnostic code. Codes are never reused or repurposed. The
11
+ message names the construct and, where the compiler can tell, the fix.
12
+ - An unsupported construct is refused by name, never misparsed and never silently ignored.
13
+
14
+ ## One semantics
15
+
16
+ Backends and profiles may **refuse**; they may never reinterpret the language. No silent
17
+ fallback — not to an interpreter, not to another backend, not to weaker verification. A
18
+ mechanism added only to turn a status row green is a defect.
19
+
20
+ ## Evidence
21
+
22
+ - A successful compile, a test count, a self-reproduction fixpoint or a mutation count is not by
23
+ itself proof.
24
+ - Differential agreement is not correctness when both paths can share the bug.
25
+ - Zero surviving mutants is not "bug-free"; it is "this catalogue found nothing".
26
+ - Statuses in a capability matrix cite the evidence that supports them, or they are not statuses.
27
+
28
+ ## Bounded workloads
29
+
30
+ Generated stages, self-hosting, bootstraps, fuzzing, mutation catalogues and heavy benchmarks
31
+ run only inside the project's bounded runner, one at a time. The `heavy` field in the config
32
+ states the rule; the reviewers enforce it. Measurement happens in a quiet machine, or it is not
33
+ measurement.
@@ -0,0 +1,50 @@
1
+ # Concern: architecture
2
+
3
+ Taxonomy domains **B** (architecture and system structure) and **J** (design patterns and
4
+ engineering principles). Rules in the `ARCH-*` namespace.
5
+
6
+ Enable with `"concerns": ["architecture"]`. Checked by `sdlc-architecture-reviewer` and by
7
+ `sdlc-spec-reviewer` when a change proposes a new boundary.
8
+
9
+ ## Dependency direction
10
+
11
+ - The project's layers are named somewhere — a README, a module graph, a lint rule. If they are
12
+ not, that absence is the first finding: an unwritten architecture cannot be violated, only
13
+ eroded.
14
+ - Dependencies point one way. A lower layer that imports an upper one is `ARCH-LAYER-DIRECTION`,
15
+ and it is `high` severity even when it compiles: it is how a codebase stops being separable.
16
+ - A new import that crosses a boundary the project declared forbidden is blocking. "It was
17
+ easier" is not an exception; the exception is a specification that changes the boundary.
18
+
19
+ ## Cycles and coupling
20
+
21
+ - A new import cycle between modules is `ARCH-CYCLE`. Report it as a path, not as a claim.
22
+ - A module that imports more than it is imported by, and keeps growing, is becoming a god
23
+ object. Name it while it is still one finding rather than twenty.
24
+ - Coupling to a concrete type where the project has an interface for exactly this is
25
+ `advisory` unless the specification said otherwise — which is the point of the advisory
26
+ severity: report the trade-off and let a human decide.
27
+
28
+ ## Boundaries
29
+
30
+ - A component's boundary is where its invariants are enforced. If a caller can construct an
31
+ invalid state and the component cannot tell, the boundary is in the wrong place.
32
+ - Separation of concerns is checkable: can you state what this module is for in one sentence
33
+ without "and"? Two sentences is two modules.
34
+ - Shared mutable state across a boundary is a boundary that does not exist.
35
+
36
+ ## Patterns
37
+
38
+ - A pattern applied because it is a pattern is `advisory`, not a defect. Say what it costs —
39
+ indirection, a file to open, a name to learn — and let the maintainer weigh it.
40
+ - A factory with one implementation, a strategy with one strategy, an interface with one
41
+ implementer and no test double: name the indirection, and the condition under which it would
42
+ earn its place.
43
+ - Premature generalisation and premature optimisation are the same finding wearing two hats.
44
+
45
+ ## The tool beats the opinion
46
+
47
+ Where the stack has one, it belongs in `quality.cmd` and the finding becomes `deterministic`:
48
+ `cargo-deny` and `cargo modules` (Rust), `go-arch-lint` and `go mod graph` (Go),
49
+ `dependency-cruiser` or `eslint-plugin-boundaries` (TypeScript), `import-linter` (Python),
50
+ ArchUnit (JVM). A reviewer's reading of an import graph is `medium` confidence at best.
@@ -0,0 +1,59 @@
1
+ # Concern: concurrency
2
+
3
+ Taxonomy domain **G** (concurrency and parallelism). Rules in the `CONC-*` namespace.
4
+
5
+ Enable with `"concerns": ["concurrency"]`. Checked by `sdlc-code-reviewer`.
6
+
7
+ ## What this concern can and cannot do
8
+
9
+ Concurrency defects are the ones least visible to reading and least reliably caught by tests:
10
+ they are timing-dependent, so a passing suite is weak evidence. A `CONC-*` finding from the
11
+ diff is `medium` confidence. A race detector, a stress run or a deterministic scheduler makes
12
+ it `high`. **A test that passed once has not shown the absence of a race.**
13
+
14
+ ## Shared state
15
+
16
+ - Every piece of state reachable from two threads is either immutable, owned by one of them, or
17
+ behind a lock — and the diff says which.
18
+ - A data race is `high` severity even when the window looks small. Small windows are what
19
+ production load is for.
20
+ - "It is only read concurrently" is only safe if nothing writes it, ever, including at startup.
21
+
22
+ ## Locks
23
+
24
+ - State the lock order wherever two locks can be held at once. Inconsistent order is a deadlock
25
+ waiting for the right interleaving.
26
+ - No I/O, no blocking call, no callback into unknown code while holding a lock.
27
+ - A read-modify-write that looks atomic because it is one line is not atomic. Name the
28
+ operation that makes it so.
29
+ - Lock contention on a hot path is a performance finding; see `concerns/performance`.
30
+
31
+ ## Time-of-check to time-of-use
32
+
33
+ - Check-then-act on anything shared — a file, a row, a flag, a balance — is `CONC-TOCTOU`.
34
+ Make it one atomic operation, or state why the window is harmless.
35
+ - `exists()` then `open()`, `select` then `update`, "is it free?" then "take it" — the three
36
+ that recur.
37
+
38
+ ## Asynchrony
39
+
40
+ - Every spawned task is awaited, joined or deliberately detached with a comment saying so. A
41
+ task nobody holds is a leak and swallows its own failure.
42
+ - Every unhandled rejection or dropped future is a failure you will never hear about.
43
+ - Cancellation propagates: a cancelled parent cancels its children, and cleanup still runs.
44
+ - Every timeout has a value, and every value has a reason. "No timeout" is a policy, and
45
+ usually the wrong one.
46
+ - Backpressure is stated: what happens when the producer outruns the consumer? An unbounded
47
+ channel answers "we crash later".
48
+
49
+ ## Ordering
50
+
51
+ - Do not assume completion order, scheduling order or message order unless the mechanism
52
+ guarantees it. Write down which guarantee you are relying on.
53
+ - Idempotency: anything that can be retried says what a duplicate does.
54
+
55
+ ## The tool beats the opinion
56
+
57
+ `go test -race`, ThreadSanitizer, Loom (Rust), `-fsanitize=thread`, Lincheck (JVM),
58
+ deterministic-simulation or stress harnesses. Put one in `quality.cmd` and run it repeatedly —
59
+ once is not a run.
@@ -0,0 +1,62 @@
1
+ # Concern: data integrity
2
+
3
+ Taxonomy domain **K** (database and data integrity), and the data half of **X6** (product and
4
+ operational correctness). Rules in the `DATA-*` namespace.
5
+
6
+ Enable with `"concerns": ["data"]`. Checked by `sdlc-code-reviewer` and `sdlc-spec-reviewer`.
7
+ Overlaps `profiles/webapp` deliberately: a service with a database needs these whether or not
8
+ it has an admin surface.
9
+
10
+ ## What this concern can and cannot do
11
+
12
+ A constraint that exists only in code, a transaction boundary that is wrong, an idempotency
13
+ key that is missing — these are visible by reading. Whether the migration actually reverses,
14
+ and whether the constraint holds under concurrent writes, are not. A `DATA-*` finding raised
15
+ from the diff is `medium` confidence; a migration run forward and back against representative
16
+ data, or a test that races two writers, makes it `high`. Say which you have.
17
+
18
+ ## Schema
19
+
20
+ - A new table or column states its purpose, its nullability, its default, its indexes and how
21
+ long the data is kept. A column whose retention nobody can state is a future compliance
22
+ question.
23
+ - A constraint belongs in the database, not only in the application. Two applications will
24
+ eventually write to that table, and one of them will be a migration script.
25
+ - Nullable-by-default is a decision. Make it deliberately or not at all.
26
+
27
+ ## Transactions
28
+
29
+ - State the boundary: what is inside the transaction and what is outside. A side effect inside
30
+ a transaction that can be retried will happen twice.
31
+ - State the isolation level where it matters, and what anomaly you are accepting. Read-committed
32
+ is a choice with consequences, not a default to inherit silently.
33
+ - Lost update, dirty read and write skew each have a fix — a constraint, a lock, a version
34
+ column. Name which one this code relies on.
35
+ - No network call inside a transaction.
36
+
37
+ ## Migrations
38
+
39
+ - Every migration is reversible, or says in the same commit why it is not and what rolling back
40
+ would lose. `DATA-MIGRATION-IRREVERSIBLE` is not a blocker by itself; an unstated one is.
41
+ - Forward and backward compatible across one deploy, or the specification states the ordered
42
+ steps: add the column, backfill, start writing, start reading, stop writing the old one, drop
43
+ it. Shipping those as one step is the usual outage.
44
+ - Tested against a database with representative data. An empty database proves the syntax.
45
+ - A destructive migration — a drop, a truncate, a type narrowing — is a separately approved
46
+ step. The release stage must know about it before deploying, not during.
47
+
48
+ ## Correctness of the data itself
49
+
50
+ - Idempotency: anything a client may retry states its key and what a duplicate does. Duplicate
51
+ payments and duplicate orders are this finding, not a support ticket.
52
+ - Partial writes are impossible, or the recovery path is specified and tested.
53
+ - Money is not a float. Time is stored with its zone, or in UTC with that stated. A date is not
54
+ a timestamp. Rounding is specified where it is visible to a user.
55
+ - Deletion means deletion where someone asked for it: soft-delete that leaves the data readable
56
+ does not satisfy a deletion request, and the specification should say which one this is.
57
+
58
+ ## The tool beats the opinion
59
+
60
+ `sqlc`/`sqlx` compile-time checks, `EXPLAIN ANALYZE`, migration linters (`squawk`,
61
+ `atlas lint`), property tests over invariants, and a restore drill. A backup nobody has
62
+ restored is not a backup.
@@ -0,0 +1,52 @@
1
+ # Concern: memory and resources
2
+
3
+ Taxonomy domain **F** (memory, resources and lifecycle management). Rules in the `MEM-*`
4
+ namespace.
5
+
6
+ Enable with `"concerns": ["memory"]`. Checked by `sdlc-code-reviewer`.
7
+
8
+ ## What this concern can and cannot do
9
+
10
+ A leak is usually proved by allocation evidence over time, not by reading. A `MEM-*` finding
11
+ raised from the diff is `medium` confidence; a sanitiser, a heap profile or a soak test makes
12
+ it `high`. Say which you have.
13
+
14
+ ## Ownership and lifetime
15
+
16
+ - Every resource has one owner and one release point. Two owners is a double free; none is a
17
+ leak.
18
+ - A resource acquired in a function is released on **every** exit from it, including the error
19
+ path, the early return and the cancellation. "Cleanup at the end" is not cleanup if the
20
+ function can return before the end.
21
+ - Prefer the language's scope-bound release — RAII, `defer`, `with`, `try`-with-resources,
22
+ `using`. A manual close that a future edit can step over is a defect waiting for a
23
+ maintainer.
24
+
25
+ ## Files, sockets, handles, connections
26
+
27
+ - Every open has a close on the failure path. Connection pools, file descriptors and watchers
28
+ are the three that leak quietly for weeks.
29
+ - A pool states its maximum, its timeout and what happens when it is exhausted.
30
+
31
+ ## Unbounded growth
32
+
33
+ - A collection, queue, buffer, cache or map that only ever grows is `MEM-UNBOUNDED` and is
34
+ blocking. Name the eviction policy or the bound.
35
+ - A retained reference that outlives its usefulness — a listener never removed, a closure
36
+ capturing a large object, a static registry nobody prunes — is the ordinary cause of "memory
37
+ grows until restart".
38
+ - Recursion over user-controlled depth exhausts the stack. Bound it or make it iterative.
39
+
40
+ ## Memory safety, where the language allows it
41
+
42
+ - Use-after-free, double free, dangling references, out-of-bounds access and buffer overflow
43
+ are `critical`. They do not get an `advisory`.
44
+ - Any `unsafe` block, raw pointer arithmetic, manual lifetime or foreign-function boundary
45
+ states the invariant it is upholding, in a comment, in the same commit.
46
+ - Integer overflow before an allocation size is a memory-safety bug, not an arithmetic one.
47
+
48
+ ## The tool beats the opinion
49
+
50
+ AddressSanitizer and LeakSanitizer, Valgrind, Miri (Rust), `go test -race` plus `pprof` heap
51
+ profiles, `tracemalloc` (Python), heap snapshots in a browser. One soak run in `quality.cmd`
52
+ beats every paragraph above.