@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.
- package/.claude-plugin/marketplace.json +11 -0
- package/.claude-plugin/plugin.json +31 -0
- package/CHANGELOG.md +330 -0
- package/GETTING-STARTED.md +174 -0
- package/LICENSE +21 -0
- package/README.md +268 -2
- package/SECURITY.md +25 -0
- package/agents/sdlc-architecture-reviewer.md +71 -0
- package/agents/sdlc-branch-opener.md +83 -0
- package/agents/sdlc-code-reviewer.md +62 -0
- package/agents/sdlc-impact-analyst.md +70 -0
- package/agents/sdlc-implementer.md +60 -0
- package/agents/sdlc-lander.md +40 -0
- package/agents/sdlc-readiness-gate.md +128 -0
- package/agents/sdlc-release-manager.md +76 -0
- package/agents/sdlc-remediator.md +89 -0
- package/agents/sdlc-reporter.md +42 -0
- package/agents/sdlc-security-reviewer.md +62 -0
- package/agents/sdlc-spec-reviewer.md +56 -0
- package/agents/sdlc-spec-writer.md +62 -0
- package/agents/sdlc-test-author.md +32 -0
- package/agents/sdlc-triager.md +48 -0
- package/agents/sdlc-verifier.md +69 -0
- package/bin/__pycache__/sdlccpython-315.pyc +0 -0
- package/bin/sdlc +2747 -0
- package/bin/sdlc-install-opencode +10 -0
- package/commands/doctor.md +9 -0
- package/commands/feature.md +17 -0
- package/commands/gate.md +33 -0
- package/commands/impact.md +13 -0
- package/commands/implement.md +11 -0
- package/commands/init.md +15 -0
- package/commands/land.md +10 -0
- package/commands/metrics.md +12 -0
- package/commands/readiness.md +8 -0
- package/commands/release.md +16 -0
- package/commands/remediate.md +13 -0
- package/commands/report.md +11 -0
- package/commands/review.md +12 -0
- package/commands/status.md +9 -0
- package/commands/trace.md +9 -0
- package/commands/triage.md +14 -0
- package/commands/verify.md +10 -0
- package/docs/compliance.md +70 -0
- package/docs/configuration.md +106 -0
- package/docs/control-spec.md +269 -0
- package/docs/install.md +75 -0
- package/docs/maturity.md +129 -0
- package/docs/opencode.md +45 -0
- package/docs/openspec.md +58 -0
- package/docs/pipeline.md +160 -0
- package/docs/platforms.md +99 -0
- package/docs/rules.md +122 -0
- package/evals/baseline-attributes-failure/graders/criteria.md +18 -0
- package/evals/baseline-attributes-failure/prompt.md +23 -0
- package/evals/budget-stops-rather-than-asks/graders/criteria.md +19 -0
- package/evals/budget-stops-rather-than-asks/prompt.md +14 -0
- package/evals/critical-low-confidence-escalates/graders/criteria.md +22 -0
- package/evals/critical-low-confidence-escalates/prompt.md +17 -0
- package/evals/gate-refuses-unexecuted/graders/criteria.md +16 -0
- package/evals/gate-refuses-unexecuted/prompt.md +15 -0
- package/evals/refusal-is-not-negotiated/graders/criteria.md +17 -0
- package/evals/refusal-is-not-negotiated/prompt.md +7 -0
- package/evals/release-needs-rollback-and-approval/graders/criteria.md +15 -0
- package/evals/release-needs-rollback-and-approval/prompt.md +6 -0
- package/evals/remediator-refuses-weakened-test/graders/criteria.md +20 -0
- package/evals/remediator-refuses-weakened-test/prompt.md +33 -0
- package/evals/spec-review-catches-unfalsifiable/graders/criteria.md +17 -0
- package/evals/spec-review-catches-unfalsifiable/prompt.md +17 -0
- package/examples/worked-example.md +131 -0
- package/hooks/hooks.json +16 -0
- package/npm/cli.js +35 -0
- package/opencode/plugin/agentic-sdlc.ts +52 -0
- package/package.json +59 -4
- package/profiles/README.md +52 -0
- package/profiles/compiler.md +33 -0
- package/profiles/concerns/architecture.md +50 -0
- package/profiles/concerns/concurrency.md +59 -0
- package/profiles/concerns/data.md +62 -0
- package/profiles/concerns/memory.md +52 -0
- package/profiles/concerns/performance.md +54 -0
- package/profiles/concerns/platform.md +62 -0
- package/profiles/library.md +31 -0
- package/profiles/service.md +32 -0
- package/profiles/webapp.md +40 -0
- package/schema/finding.schema.json +267 -0
- package/schema/sdlc.config.schema.json +324 -0
- package/skills/agentic-sdlc-pipeline/SKILL.md +66 -0
- package/skills/openspec-with-sdlc/SKILL.md +56 -0
- 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.
|
package/hooks/hooks.json
ADDED
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": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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.
|