peer-ai-standards 1.0.0-next.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/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/core/ai-features.d.ts +65 -0
- package/dist/core/ai-features.js +113 -0
- package/dist/core/api-design.d.ts +41 -0
- package/dist/core/api-design.js +91 -0
- package/dist/core/architecture.d.ts +41 -0
- package/dist/core/architecture.js +93 -0
- package/dist/core/backend.d.ts +21 -0
- package/dist/core/backend.js +25 -0
- package/dist/core/code-quality.d.ts +91 -0
- package/dist/core/code-quality.js +179 -0
- package/dist/core/data.d.ts +45 -0
- package/dist/core/data.js +59 -0
- package/dist/core/delivery.d.ts +153 -0
- package/dist/core/delivery.js +149 -0
- package/dist/core/design-accessibility.d.ts +124 -0
- package/dist/core/design-accessibility.js +199 -0
- package/dist/core/frontend.d.ts +77 -0
- package/dist/core/frontend.js +109 -0
- package/dist/core/mobile.d.ts +46 -0
- package/dist/core/mobile.js +83 -0
- package/dist/core/money.d.ts +81 -0
- package/dist/core/money.js +135 -0
- package/dist/core/operations.d.ts +108 -0
- package/dist/core/operations.js +177 -0
- package/dist/core/performance.d.ts +53 -0
- package/dist/core/performance.js +85 -0
- package/dist/core/privacy-compliance.d.ts +68 -0
- package/dist/core/privacy-compliance.js +77 -0
- package/dist/core/reliability.d.ts +67 -0
- package/dist/core/reliability.js +105 -0
- package/dist/core/requirements.d.ts +41 -0
- package/dist/core/requirements.js +58 -0
- package/dist/core/safety-critical.d.ts +41 -0
- package/dist/core/safety-critical.js +71 -0
- package/dist/core/security.d.ts +281 -0
- package/dist/core/security.js +427 -0
- package/dist/core/system-design.d.ts +31 -0
- package/dist/core/system-design.js +69 -0
- package/dist/core/testing.d.ts +51 -0
- package/dist/core/testing.js +124 -0
- package/dist/domains.d.ts +6 -0
- package/dist/domains.js +53 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +118 -0
- package/dist/profile.d.ts +262 -0
- package/dist/profile.js +269 -0
- package/dist/profiles/express.d.ts +2 -0
- package/dist/profiles/express.js +93 -0
- package/dist/profiles/fastapi.d.ts +2 -0
- package/dist/profiles/fastapi.js +104 -0
- package/dist/profiles/fastify.d.ts +2 -0
- package/dist/profiles/fastify.js +56 -0
- package/dist/profiles/github-actions.d.ts +2 -0
- package/dist/profiles/github-actions.js +151 -0
- package/dist/profiles/nestjs.d.ts +2 -0
- package/dist/profiles/nestjs.js +77 -0
- package/dist/profiles/next.d.ts +2 -0
- package/dist/profiles/next.js +73 -0
- package/dist/profiles/node.d.ts +2 -0
- package/dist/profiles/node.js +79 -0
- package/dist/profiles/python.d.ts +2 -0
- package/dist/profiles/python.js +232 -0
- package/dist/profiles/react-native.d.ts +2 -0
- package/dist/profiles/react-native.js +133 -0
- package/dist/profiles/react.d.ts +2 -0
- package/dist/profiles/react.js +180 -0
- package/dist/profiles/typescript.d.ts +2 -0
- package/dist/profiles/typescript.js +214 -0
- package/dist/rule.d.ts +72 -0
- package/dist/rule.js +39 -0
- package/package.json +39 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// What's tested, including testing for security, and how tests stay trustworthy.
|
|
2
|
+
export const testing = [
|
|
3
|
+
{
|
|
4
|
+
id: "TEST-01",
|
|
5
|
+
domain: "testing",
|
|
6
|
+
title: "A bug fix ships with the test that would have caught it",
|
|
7
|
+
rule: "Every bug fix comes with a test that fails without the fix and passes with it.",
|
|
8
|
+
why: "A bug fixed without a test comes back, usually during the next refactor.",
|
|
9
|
+
ask: "Does this bug fix come with a test that would have caught the bug?",
|
|
10
|
+
stage: "mvp",
|
|
11
|
+
check: "ai-review",
|
|
12
|
+
severity: "medium",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "TEST-02",
|
|
16
|
+
domain: "testing",
|
|
17
|
+
title: "The main journeys are the acceptance tests",
|
|
18
|
+
rule: "The main journeys through the product are written as end-to-end tests, and an area isn't done until they pass. A rebuild must pass the old product's journeys.",
|
|
19
|
+
why: "Unit tests can all pass while the one thing people came to do is broken.",
|
|
20
|
+
ask: "Do the product's main journeys still pass with this change?",
|
|
21
|
+
stage: "mvp",
|
|
22
|
+
check: "auto",
|
|
23
|
+
severity: "medium",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "TEST-03",
|
|
27
|
+
domain: "testing",
|
|
28
|
+
title: "Business rules are tested thoroughly with plain values",
|
|
29
|
+
rule: "Business rules are tested with plain values, case by case, including the edges, with a name for each case.",
|
|
30
|
+
why: "Plain rules are cheap to test exhaustively, and the edges are where the bugs are.",
|
|
31
|
+
ask: "Are the business rules in this change tested case by case, including the edges?",
|
|
32
|
+
stage: "mvp",
|
|
33
|
+
check: "auto",
|
|
34
|
+
severity: "medium",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "TEST-04",
|
|
38
|
+
domain: "testing",
|
|
39
|
+
title: "Tests check behaviour, not implementation",
|
|
40
|
+
rule: "Tests check what the code does, the way a person or caller would see it, not how it does it internally.",
|
|
41
|
+
why: "Tests tied to the implementation break on every refactor and still miss the behaviour that matters.",
|
|
42
|
+
ask: "Do the tests in this change check behaviour rather than internal details?",
|
|
43
|
+
stage: "mvp",
|
|
44
|
+
check: "ai-review",
|
|
45
|
+
severity: "low",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "TEST-05",
|
|
49
|
+
domain: "testing",
|
|
50
|
+
title: "Tests state what they need",
|
|
51
|
+
rule: "A test that needs something, such as seeded data, a port or a clock, checks for it and skips with a reason when it's missing, rather than failing for want of it.",
|
|
52
|
+
why: "A test that fails for the wrong reason teaches people to ignore failing tests.",
|
|
53
|
+
ask: "Does every test in this change state what it needs?",
|
|
54
|
+
stage: "mvp",
|
|
55
|
+
check: "ai-review",
|
|
56
|
+
severity: "low",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "TEST-06",
|
|
60
|
+
domain: "testing",
|
|
61
|
+
title: "Test databases are built by the migrations",
|
|
62
|
+
rule: "The database a test runs against is built by the same migrations as production.",
|
|
63
|
+
why: "A test database built any other way passes tests that production would fail.",
|
|
64
|
+
ask: "Is the test database in this change built by the migrations?",
|
|
65
|
+
stage: "mvp",
|
|
66
|
+
check: "auto",
|
|
67
|
+
severity: "medium",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "TEST-07",
|
|
71
|
+
domain: "testing",
|
|
72
|
+
title: "No test depends on another's leftovers",
|
|
73
|
+
rule: "Every test sets up what it needs and cleans up after itself. None depends on what another test left behind.",
|
|
74
|
+
why: "Tests that share leftovers pass or fail depending on the order they run in.",
|
|
75
|
+
ask: "Could any test in this change pass or fail depending on another test?",
|
|
76
|
+
stage: "mvp",
|
|
77
|
+
check: "ai-review",
|
|
78
|
+
severity: "medium",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "TEST-08",
|
|
82
|
+
domain: "testing",
|
|
83
|
+
title: "Security-sensitive code has abuse tests",
|
|
84
|
+
rule: "Code that handles sign-in, permissions, money, uploads or input that reaches a database has tests that attack it: injection attempts, other people's ids, missing, expired or tampered tokens, oversized input and going past rate limits. Each test checks the attack is refused. A fixed vulnerability gets a test that repeats the attack. The tests run against the project's own app, never a live system.",
|
|
85
|
+
why: "Security holes don't show up in tests that only send what a well-behaved client would send, and a fixed hole with no test can quietly open again.",
|
|
86
|
+
ask: "Does the security-sensitive code in this change have tests that attack it and check the attack is refused?",
|
|
87
|
+
stage: "mvp",
|
|
88
|
+
check: "ai-review",
|
|
89
|
+
severity: "medium",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "TEST-09",
|
|
93
|
+
domain: "testing",
|
|
94
|
+
title: "A check is seen failing before it's trusted",
|
|
95
|
+
rule: "A new test, lint rule or gate is made to fail once on purpose, to prove it can, before anyone relies on it passing.",
|
|
96
|
+
why: "A check that can't fail reports the same green as one that passed. A green run proves nothing until you've seen it go red.",
|
|
97
|
+
ask: "Has each new check in this change been seen to fail?",
|
|
98
|
+
stage: "mvp",
|
|
99
|
+
check: "ai-review",
|
|
100
|
+
severity: "low",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: "TEST-10",
|
|
104
|
+
domain: "testing",
|
|
105
|
+
title: "A skilled attacker tests the product before launch and every year",
|
|
106
|
+
rule: "Before the product first reaches production, after big changes to who uses it or how it's reached, and at least once a year, someone skilled who didn't build it tests it as an attacker would: a penetration test, within agreed limits and only against the project's own systems. Every finding is fixed or accepted as a risk by a named person.",
|
|
107
|
+
why: "Automated tests and scanners find the known kinds of problem. A skilled person finds the ones particular to your product, such as two small flaws that together open a big one.",
|
|
108
|
+
ask: "When was the last penetration test, and is every finding fixed or accepted?",
|
|
109
|
+
stage: "production",
|
|
110
|
+
check: "person",
|
|
111
|
+
severity: "medium",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "TEST-11",
|
|
115
|
+
domain: "testing",
|
|
116
|
+
title: "A parser you write is fuzz-tested",
|
|
117
|
+
rule: "Code the project writes itself to read files or formats from outside, such as an import, a file format or a message protocol, is fuzz-tested: fed large amounts of random and broken input to check it never crashes, hangs or runs out of memory.",
|
|
118
|
+
why: "Broken files are a classic way in, and nobody writes by hand the millions of strange inputs a fuzzer tries. Well-known parsing libraries are fuzzed by their makers; your own code isn't.",
|
|
119
|
+
ask: "Does this change add code that reads an outside format, and is it fuzz-tested?",
|
|
120
|
+
stage: "production",
|
|
121
|
+
check: "ai-review",
|
|
122
|
+
severity: "medium",
|
|
123
|
+
},
|
|
124
|
+
];
|
package/dist/domains.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Each domain's name and what it covers, in plain words, for the generated pages. */
|
|
2
|
+
export const DOMAIN_INFO = {
|
|
3
|
+
requirements: {
|
|
4
|
+
title: "Requirements",
|
|
5
|
+
about: "What the product must do, for whom, and how anyone will know it works.",
|
|
6
|
+
},
|
|
7
|
+
architecture: {
|
|
8
|
+
title: "Architecture",
|
|
9
|
+
about: "How a system is divided, and which way its parts depend on each other. These hold for any architecture: a modular monolith, microservices, a mobile app or a library. A folder layout is a stack profile's default, never a core rule.",
|
|
10
|
+
},
|
|
11
|
+
"system-design": {
|
|
12
|
+
title: "System design and scalability",
|
|
13
|
+
about: "How the system behaves under load, concurrency and background work.",
|
|
14
|
+
},
|
|
15
|
+
"api-design": { title: "API design", about: "How services and clients agree on what they send each other." },
|
|
16
|
+
frontend: { title: "Frontend", about: "How screens get, hold and show data." },
|
|
17
|
+
mobile: { title: "Mobile", about: "What phone apps need beyond the frontend rules." },
|
|
18
|
+
"design-accessibility": {
|
|
19
|
+
title: "Design and accessibility",
|
|
20
|
+
about: "Design tokens, shared components, and making the product work for everyone.",
|
|
21
|
+
},
|
|
22
|
+
backend: { title: "Backend", about: "How servers take requests, validate them and report what happened." },
|
|
23
|
+
data: { title: "Data", about: "Databases, migrations and stored files." },
|
|
24
|
+
performance: {
|
|
25
|
+
title: "Performance and caching",
|
|
26
|
+
about: "Knowing what things cost, and caching only what's safe to cache.",
|
|
27
|
+
},
|
|
28
|
+
reliability: { title: "Reliability", about: "Staying up, and failing safely when something underneath fails." },
|
|
29
|
+
security: { title: "Security", about: "Keeping people's data and the system itself safe from attack." },
|
|
30
|
+
"privacy-compliance": {
|
|
31
|
+
title: "Privacy and compliance",
|
|
32
|
+
about: "Personal data, and the laws and rules a product must follow.",
|
|
33
|
+
},
|
|
34
|
+
testing: { title: "Testing", about: "What's tested, and how tests stay trustworthy." },
|
|
35
|
+
delivery: { title: "Delivery", about: "Dependencies, pipelines, and how changes reach people." },
|
|
36
|
+
operations: {
|
|
37
|
+
title: "Infrastructure and operations",
|
|
38
|
+
about: "Environments, logs, metrics, alerts, and running the system.",
|
|
39
|
+
},
|
|
40
|
+
"ai-features": { title: "AI features", about: "Features that use AI models." },
|
|
41
|
+
"code-quality": {
|
|
42
|
+
title: "Code quality",
|
|
43
|
+
about: "What makes code good in any language: naming, size, duplication, errors and types.",
|
|
44
|
+
},
|
|
45
|
+
money: {
|
|
46
|
+
title: "Money",
|
|
47
|
+
about: "For products with the `money` trait. The project's add-on names its currency's smallest unit.",
|
|
48
|
+
},
|
|
49
|
+
"safety-critical": {
|
|
50
|
+
title: "Safety-critical data",
|
|
51
|
+
about: "For products with the `safety-critical` trait: data where a wrong value could hurt someone, such as allergens, medical dosage, legal deadlines, or eligibility that affects a person's rights. The project's add-on names what counts.",
|
|
52
|
+
},
|
|
53
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type DomainId, type Trait } from "peer-ai-workflow";
|
|
2
|
+
import { type AppliedRule, type Part, type Profile, type ProfileSelection } from "./profile.ts";
|
|
3
|
+
import { type Rule, type RuleInput, type Stage } from "./rule.ts";
|
|
4
|
+
export { DOMAIN_INFO } from "./domains.ts";
|
|
5
|
+
export { PIPELINE_JOBS, checkProfiles, overrideFits, withValue, VALUE, type AppliedRule, type Enforcer, type EnforcerTool, type Example, type Part, type PipelineJob, type Profile, type ProfileInput, type ProfileRule, type ProfileSelection, type Value, } from "./profile.ts";
|
|
6
|
+
export { RuleSchema, SourceSchema, STAGES, traitsNeeded, type Rule, type RuleInput, type Stage } from "./rule.ts";
|
|
7
|
+
/** Checks every rule against the schema and that no id is used twice. */
|
|
8
|
+
export declare function checkRules(inputs: RuleInput[]): {
|
|
9
|
+
rules: Rule[];
|
|
10
|
+
problems: string[];
|
|
11
|
+
};
|
|
12
|
+
/** Every core rule, in domain order. */
|
|
13
|
+
export declare const CORE_RULES: readonly Rule[];
|
|
14
|
+
export interface Selection {
|
|
15
|
+
stage: Stage;
|
|
16
|
+
traits?: readonly Trait[];
|
|
17
|
+
domains?: readonly DomainId[];
|
|
18
|
+
}
|
|
19
|
+
/** The rules that apply to a project at its stage, with its traits, optionally in some domains only. */
|
|
20
|
+
export declare function rulesFor({ stage, traits, domains }: Selection): Rule[];
|
|
21
|
+
/** Every stack profile, bases before the profiles that extend them. */
|
|
22
|
+
export declare const PROFILES: readonly Profile[];
|
|
23
|
+
/** The profile with this id, if there is one. */
|
|
24
|
+
export declare const profile: (id: string) => Profile | undefined;
|
|
25
|
+
/** The listed profiles that apply to a part, with the profiles they extend, bases first. */
|
|
26
|
+
export declare const profilesForPart: (listed: readonly string[], part: Part) => Profile[];
|
|
27
|
+
/** The profile rules that apply to a part at the project's stage, with the project's values. */
|
|
28
|
+
export declare const profileRulesFor: (selection: ProfileSelection) => AppliedRule[];
|
|
29
|
+
/** Every profile rule, by id. */
|
|
30
|
+
export declare const PROFILE_RULES: ReadonlyMap<string, Profile["rules"][number]>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { DOMAIN_IDS, DOMAINS } from "peer-ai-workflow";
|
|
2
|
+
import { aiFeatures } from "./core/ai-features.js";
|
|
3
|
+
import { apiDesign } from "./core/api-design.js";
|
|
4
|
+
import { architecture } from "./core/architecture.js";
|
|
5
|
+
import { backend } from "./core/backend.js";
|
|
6
|
+
import { codeQuality } from "./core/code-quality.js";
|
|
7
|
+
import { data } from "./core/data.js";
|
|
8
|
+
import { delivery } from "./core/delivery.js";
|
|
9
|
+
import { designAccessibility } from "./core/design-accessibility.js";
|
|
10
|
+
import { frontend } from "./core/frontend.js";
|
|
11
|
+
import { mobile } from "./core/mobile.js";
|
|
12
|
+
import { money } from "./core/money.js";
|
|
13
|
+
import { operations } from "./core/operations.js";
|
|
14
|
+
import { performance } from "./core/performance.js";
|
|
15
|
+
import { privacyCompliance } from "./core/privacy-compliance.js";
|
|
16
|
+
import { reliability } from "./core/reliability.js";
|
|
17
|
+
import { requirements } from "./core/requirements.js";
|
|
18
|
+
import { safetyCritical } from "./core/safety-critical.js";
|
|
19
|
+
import { security } from "./core/security.js";
|
|
20
|
+
import { systemDesign } from "./core/system-design.js";
|
|
21
|
+
import { testing } from "./core/testing.js";
|
|
22
|
+
import { applyProfiles, checkProfiles, profilesFor, } from "./profile.js";
|
|
23
|
+
import { express } from "./profiles/express.js";
|
|
24
|
+
import { fastapi } from "./profiles/fastapi.js";
|
|
25
|
+
import { fastify } from "./profiles/fastify.js";
|
|
26
|
+
import { githubActions } from "./profiles/github-actions.js";
|
|
27
|
+
import { nestjs } from "./profiles/nestjs.js";
|
|
28
|
+
import { next } from "./profiles/next.js";
|
|
29
|
+
import { node } from "./profiles/node.js";
|
|
30
|
+
import { python } from "./profiles/python.js";
|
|
31
|
+
import { reactNative } from "./profiles/react-native.js";
|
|
32
|
+
import { react } from "./profiles/react.js";
|
|
33
|
+
import { typescript } from "./profiles/typescript.js";
|
|
34
|
+
import { RuleSchema, STAGES, traitsNeeded } from "./rule.js";
|
|
35
|
+
export { DOMAIN_INFO } from "./domains.js";
|
|
36
|
+
export { PIPELINE_JOBS, checkProfiles, overrideFits, withValue, VALUE, } from "./profile.js";
|
|
37
|
+
export { RuleSchema, SourceSchema, STAGES, traitsNeeded } from "./rule.js";
|
|
38
|
+
const CORE_INPUTS = [
|
|
39
|
+
...requirements,
|
|
40
|
+
...architecture,
|
|
41
|
+
...systemDesign,
|
|
42
|
+
...apiDesign,
|
|
43
|
+
...frontend,
|
|
44
|
+
...mobile,
|
|
45
|
+
...designAccessibility,
|
|
46
|
+
...backend,
|
|
47
|
+
...data,
|
|
48
|
+
...performance,
|
|
49
|
+
...reliability,
|
|
50
|
+
...security,
|
|
51
|
+
...privacyCompliance,
|
|
52
|
+
...testing,
|
|
53
|
+
...delivery,
|
|
54
|
+
...operations,
|
|
55
|
+
...aiFeatures,
|
|
56
|
+
...codeQuality,
|
|
57
|
+
...money,
|
|
58
|
+
...safetyCritical,
|
|
59
|
+
];
|
|
60
|
+
/** Checks every rule against the schema and that no id is used twice. */
|
|
61
|
+
export function checkRules(inputs) {
|
|
62
|
+
const rules = [];
|
|
63
|
+
const problems = [];
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
for (const input of inputs) {
|
|
66
|
+
const result = RuleSchema.safeParse(input);
|
|
67
|
+
if (!result.success) {
|
|
68
|
+
for (const issue of result.error.issues)
|
|
69
|
+
problems.push(`${input.id}: ${issue.path.join(".")}: ${issue.message}`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (seen.has(result.data.id))
|
|
73
|
+
problems.push(`${result.data.id}: the id is used twice`);
|
|
74
|
+
seen.add(result.data.id);
|
|
75
|
+
rules.push(result.data);
|
|
76
|
+
}
|
|
77
|
+
return { rules, problems };
|
|
78
|
+
}
|
|
79
|
+
const checked = checkRules(CORE_INPUTS);
|
|
80
|
+
if (checked.problems.length > 0) {
|
|
81
|
+
throw new Error(`Peer AI's standards aren't valid:\n- ${checked.problems.join("\n- ")}`);
|
|
82
|
+
}
|
|
83
|
+
/** Every core rule, in domain order. */
|
|
84
|
+
export const CORE_RULES = [...checked.rules].sort((a, b) => DOMAIN_IDS.indexOf(a.domain) - DOMAIN_IDS.indexOf(b.domain) || a.id.localeCompare(b.id, "en", { numeric: true }));
|
|
85
|
+
/** The rules that apply to a project at its stage, with its traits, optionally in some domains only. */
|
|
86
|
+
export function rulesFor({ stage, traits = [], domains }) {
|
|
87
|
+
const reached = STAGES.indexOf(stage);
|
|
88
|
+
return CORE_RULES.filter((rule) => STAGES.indexOf(rule.stage) <= reached &&
|
|
89
|
+
traitsNeeded(rule).every((trait) => traits.includes(trait)) &&
|
|
90
|
+
(domains === undefined || domains.includes(rule.domain)));
|
|
91
|
+
}
|
|
92
|
+
const PROFILE_INPUTS = [
|
|
93
|
+
typescript,
|
|
94
|
+
node,
|
|
95
|
+
react,
|
|
96
|
+
reactNative,
|
|
97
|
+
next,
|
|
98
|
+
express,
|
|
99
|
+
nestjs,
|
|
100
|
+
fastify,
|
|
101
|
+
python,
|
|
102
|
+
fastapi,
|
|
103
|
+
githubActions,
|
|
104
|
+
];
|
|
105
|
+
const checkedProfiles = checkProfiles(PROFILE_INPUTS, CORE_RULES, Object.values(DOMAINS));
|
|
106
|
+
if (checkedProfiles.problems.length > 0) {
|
|
107
|
+
throw new Error(`Peer AI's stack profiles aren't valid:\n- ${checkedProfiles.problems.join("\n- ")}`);
|
|
108
|
+
}
|
|
109
|
+
/** Every stack profile, bases before the profiles that extend them. */
|
|
110
|
+
export const PROFILES = checkedProfiles.profiles;
|
|
111
|
+
/** The profile with this id, if there is one. */
|
|
112
|
+
export const profile = (id) => PROFILES.find((candidate) => candidate.id === id);
|
|
113
|
+
/** The listed profiles that apply to a part, with the profiles they extend, bases first. */
|
|
114
|
+
export const profilesForPart = (listed, part) => profilesFor(PROFILES, listed, part);
|
|
115
|
+
/** The profile rules that apply to a part at the project's stage, with the project's values. */
|
|
116
|
+
export const profileRulesFor = (selection) => applyProfiles(PROFILES, selection);
|
|
117
|
+
/** Every profile rule, by id. */
|
|
118
|
+
export const PROFILE_RULES = new Map(PROFILES.flatMap((candidate) => candidate.rules.map((rule) => [rule.id, rule])));
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { type Trait } from "peer-ai-workflow";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { type Rule, type Stage } from "./rule.ts";
|
|
4
|
+
/** A number or a choice a project may change in standards.overrides. */
|
|
5
|
+
export type Value = number | string;
|
|
6
|
+
/** The jobs of Peer AI's pipeline workflow that check a rule automatically (RFC 0006, section 4). */
|
|
7
|
+
export declare const PIPELINE_JOBS: readonly ["secrets", "dependencies", "workflows", "code"];
|
|
8
|
+
export type PipelineJob = (typeof PIPELINE_JOBS)[number];
|
|
9
|
+
/** Where an option holds this, the tool gets the rule's value: its default, or the project's. */
|
|
10
|
+
export declare const VALUE = "$value";
|
|
11
|
+
export declare const EnforcerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
12
|
+
tool: z.ZodLiteral<"eslint">;
|
|
13
|
+
rule: z.ZodString;
|
|
14
|
+
options: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
15
|
+
typed: z.ZodOptional<z.ZodBoolean>;
|
|
16
|
+
files: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
17
|
+
ignores: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
18
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
19
|
+
tool: z.ZodLiteral<"ruff">;
|
|
20
|
+
rule: z.ZodString;
|
|
21
|
+
settings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
22
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
23
|
+
tool: z.ZodLiteral<"github-actions">;
|
|
24
|
+
job: z.ZodEnum<{
|
|
25
|
+
code: "code";
|
|
26
|
+
secrets: "secrets";
|
|
27
|
+
dependencies: "dependencies";
|
|
28
|
+
workflows: "workflows";
|
|
29
|
+
}>;
|
|
30
|
+
finding: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
32
|
+
tool: z.ZodLiteral<"typescript">;
|
|
33
|
+
option: z.ZodString;
|
|
34
|
+
value: z.ZodUnknown;
|
|
35
|
+
}, z.core.$strict>], "tool">;
|
|
36
|
+
export type Enforcer = z.output<typeof EnforcerSchema>;
|
|
37
|
+
export type EnforcerTool = Enforcer["tool"];
|
|
38
|
+
/** Example code: fixed, or written for the rule's value, such as a function one line too long. */
|
|
39
|
+
export type Example = string | ((value: Value) => string);
|
|
40
|
+
export declare const ExamplesSchema: z.ZodObject<{
|
|
41
|
+
file: z.ZodString;
|
|
42
|
+
fails: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<(value: Value) => string, (value: Value) => string>]>;
|
|
43
|
+
passes: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<(value: Value) => string, (value: Value) => string>]>;
|
|
44
|
+
}, z.core.$strict>;
|
|
45
|
+
export declare const ProfileRuleSchema: z.ZodObject<{
|
|
46
|
+
id: z.ZodString;
|
|
47
|
+
title: z.ZodString;
|
|
48
|
+
rule: z.ZodString;
|
|
49
|
+
why: z.ZodString;
|
|
50
|
+
ask: z.ZodString;
|
|
51
|
+
stage: z.ZodEnum<{
|
|
52
|
+
prototype: "prototype";
|
|
53
|
+
mvp: "mvp";
|
|
54
|
+
production: "production";
|
|
55
|
+
}>;
|
|
56
|
+
check: z.ZodEnum<{
|
|
57
|
+
auto: "auto";
|
|
58
|
+
"ai-review": "ai-review";
|
|
59
|
+
person: "person";
|
|
60
|
+
}>;
|
|
61
|
+
severity: z.ZodEnum<{
|
|
62
|
+
critical: "critical";
|
|
63
|
+
high: "high";
|
|
64
|
+
medium: "medium";
|
|
65
|
+
low: "low";
|
|
66
|
+
}>;
|
|
67
|
+
when: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
68
|
+
"ai-features": "ai-features";
|
|
69
|
+
money: "money";
|
|
70
|
+
"safety-critical": "safety-critical";
|
|
71
|
+
"several-audiences": "several-audiences";
|
|
72
|
+
offline: "offline";
|
|
73
|
+
"real-time": "real-time";
|
|
74
|
+
uploads: "uploads";
|
|
75
|
+
}>>>;
|
|
76
|
+
sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
77
|
+
name: z.ZodString;
|
|
78
|
+
ref: z.ZodString;
|
|
79
|
+
url: z.ZodOptional<z.ZodURL>;
|
|
80
|
+
}, z.core.$strict>>>;
|
|
81
|
+
carries: z.ZodString;
|
|
82
|
+
architectures: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
83
|
+
default: z.ZodOptional<z.ZodObject<{
|
|
84
|
+
value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
|
|
85
|
+
unit: z.ZodOptional<z.ZodString>;
|
|
86
|
+
}, z.core.$strict>>;
|
|
87
|
+
enforcer: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
88
|
+
tool: z.ZodLiteral<"eslint">;
|
|
89
|
+
rule: z.ZodString;
|
|
90
|
+
options: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
91
|
+
typed: z.ZodOptional<z.ZodBoolean>;
|
|
92
|
+
files: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
93
|
+
ignores: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
94
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
95
|
+
tool: z.ZodLiteral<"ruff">;
|
|
96
|
+
rule: z.ZodString;
|
|
97
|
+
settings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
98
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
99
|
+
tool: z.ZodLiteral<"github-actions">;
|
|
100
|
+
job: z.ZodEnum<{
|
|
101
|
+
code: "code";
|
|
102
|
+
secrets: "secrets";
|
|
103
|
+
dependencies: "dependencies";
|
|
104
|
+
workflows: "workflows";
|
|
105
|
+
}>;
|
|
106
|
+
finding: z.ZodOptional<z.ZodString>;
|
|
107
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
108
|
+
tool: z.ZodLiteral<"typescript">;
|
|
109
|
+
option: z.ZodString;
|
|
110
|
+
value: z.ZodUnknown;
|
|
111
|
+
}, z.core.$strict>], "tool">>;
|
|
112
|
+
examples: z.ZodOptional<z.ZodObject<{
|
|
113
|
+
file: z.ZodString;
|
|
114
|
+
fails: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<(value: Value) => string, (value: Value) => string>]>;
|
|
115
|
+
passes: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<(value: Value) => string, (value: Value) => string>]>;
|
|
116
|
+
}, z.core.$strict>>;
|
|
117
|
+
}, z.core.$strict>;
|
|
118
|
+
export type ProfileRuleInput = z.input<typeof ProfileRuleSchema>;
|
|
119
|
+
export declare const ProfileSchema: z.ZodObject<{
|
|
120
|
+
id: z.ZodString;
|
|
121
|
+
name: z.ZodString;
|
|
122
|
+
prefix: z.ZodString;
|
|
123
|
+
about: z.ZodString;
|
|
124
|
+
stacks: z.ZodArray<z.ZodString>;
|
|
125
|
+
extends: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
126
|
+
rules: z.ZodArray<z.ZodObject<{
|
|
127
|
+
id: z.ZodString;
|
|
128
|
+
title: z.ZodString;
|
|
129
|
+
rule: z.ZodString;
|
|
130
|
+
why: z.ZodString;
|
|
131
|
+
ask: z.ZodString;
|
|
132
|
+
stage: z.ZodEnum<{
|
|
133
|
+
prototype: "prototype";
|
|
134
|
+
mvp: "mvp";
|
|
135
|
+
production: "production";
|
|
136
|
+
}>;
|
|
137
|
+
check: z.ZodEnum<{
|
|
138
|
+
auto: "auto";
|
|
139
|
+
"ai-review": "ai-review";
|
|
140
|
+
person: "person";
|
|
141
|
+
}>;
|
|
142
|
+
severity: z.ZodEnum<{
|
|
143
|
+
critical: "critical";
|
|
144
|
+
high: "high";
|
|
145
|
+
medium: "medium";
|
|
146
|
+
low: "low";
|
|
147
|
+
}>;
|
|
148
|
+
when: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
149
|
+
"ai-features": "ai-features";
|
|
150
|
+
money: "money";
|
|
151
|
+
"safety-critical": "safety-critical";
|
|
152
|
+
"several-audiences": "several-audiences";
|
|
153
|
+
offline: "offline";
|
|
154
|
+
"real-time": "real-time";
|
|
155
|
+
uploads: "uploads";
|
|
156
|
+
}>>>;
|
|
157
|
+
sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
158
|
+
name: z.ZodString;
|
|
159
|
+
ref: z.ZodString;
|
|
160
|
+
url: z.ZodOptional<z.ZodURL>;
|
|
161
|
+
}, z.core.$strict>>>;
|
|
162
|
+
carries: z.ZodString;
|
|
163
|
+
architectures: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
164
|
+
default: z.ZodOptional<z.ZodObject<{
|
|
165
|
+
value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
|
|
166
|
+
unit: z.ZodOptional<z.ZodString>;
|
|
167
|
+
}, z.core.$strict>>;
|
|
168
|
+
enforcer: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
169
|
+
tool: z.ZodLiteral<"eslint">;
|
|
170
|
+
rule: z.ZodString;
|
|
171
|
+
options: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
172
|
+
typed: z.ZodOptional<z.ZodBoolean>;
|
|
173
|
+
files: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
174
|
+
ignores: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
175
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
176
|
+
tool: z.ZodLiteral<"ruff">;
|
|
177
|
+
rule: z.ZodString;
|
|
178
|
+
settings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
179
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
180
|
+
tool: z.ZodLiteral<"github-actions">;
|
|
181
|
+
job: z.ZodEnum<{
|
|
182
|
+
code: "code";
|
|
183
|
+
secrets: "secrets";
|
|
184
|
+
dependencies: "dependencies";
|
|
185
|
+
workflows: "workflows";
|
|
186
|
+
}>;
|
|
187
|
+
finding: z.ZodOptional<z.ZodString>;
|
|
188
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
189
|
+
tool: z.ZodLiteral<"typescript">;
|
|
190
|
+
option: z.ZodString;
|
|
191
|
+
value: z.ZodUnknown;
|
|
192
|
+
}, z.core.$strict>], "tool">>;
|
|
193
|
+
examples: z.ZodOptional<z.ZodObject<{
|
|
194
|
+
file: z.ZodString;
|
|
195
|
+
fails: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<(value: Value) => string, (value: Value) => string>]>;
|
|
196
|
+
passes: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<(value: Value) => string, (value: Value) => string>]>;
|
|
197
|
+
}, z.core.$strict>>;
|
|
198
|
+
}, z.core.$strict>>;
|
|
199
|
+
}, z.core.$strict>;
|
|
200
|
+
export type ProfileInput = z.input<typeof ProfileSchema>;
|
|
201
|
+
/** A profile rule as the tools use it: with its profile, and the domain of the core rule it carries. */
|
|
202
|
+
export type ProfileRule = z.output<typeof ProfileRuleSchema> & {
|
|
203
|
+
profile: string;
|
|
204
|
+
domain: Rule["domain"];
|
|
205
|
+
};
|
|
206
|
+
export interface Profile {
|
|
207
|
+
id: string;
|
|
208
|
+
name: string;
|
|
209
|
+
prefix: string;
|
|
210
|
+
about: string;
|
|
211
|
+
stacks: string[];
|
|
212
|
+
extends: string[];
|
|
213
|
+
rules: ProfileRule[];
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Checks every profile against the schema and against the core: prefixes and ids used once, each
|
|
217
|
+
* rule's id starting with its profile's prefix, each carried rule a core rule, and every profile
|
|
218
|
+
* it extends known, without a loop.
|
|
219
|
+
*/
|
|
220
|
+
export declare function checkProfiles(inputs: ProfileInput[], core: readonly Rule[], corePrefixes: readonly string[]): {
|
|
221
|
+
profiles: Profile[];
|
|
222
|
+
problems: string[];
|
|
223
|
+
};
|
|
224
|
+
/** A part of the product, as a profile sees it. */
|
|
225
|
+
export interface Part {
|
|
226
|
+
/** Stack tags, such as ["typescript", "expo"]. None: every listed profile applies. */
|
|
227
|
+
stack?: readonly string[];
|
|
228
|
+
/** The part's architecture label, such as layered. */
|
|
229
|
+
architecture?: string;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* The listed profiles that apply to a part, with the profiles they extend, bases first. A listed
|
|
233
|
+
* profile applies when the part's stack has one of its tags, when it names no stacks, or when the
|
|
234
|
+
* part names no stack. A profile it extends comes only with it, never on its own tag.
|
|
235
|
+
*/
|
|
236
|
+
export declare function profilesFor(all: readonly Profile[], listed: readonly string[], part: Part): Profile[];
|
|
237
|
+
/** A profile rule as it applies to one project: its value set, and {value} filled in. */
|
|
238
|
+
export type AppliedRule = ProfileRule & {
|
|
239
|
+
value?: Value;
|
|
240
|
+
};
|
|
241
|
+
export interface ProfileSelection extends Part {
|
|
242
|
+
listed: readonly string[];
|
|
243
|
+
stage: Stage;
|
|
244
|
+
traits?: readonly Trait[];
|
|
245
|
+
/** Values the project changed, by rule id, from standards.overrides. */
|
|
246
|
+
overrides?: Readonly<Record<string, {
|
|
247
|
+
value: Value;
|
|
248
|
+
}>>;
|
|
249
|
+
}
|
|
250
|
+
/** Puts the rule's value in its text, and in its enforcer's options. */
|
|
251
|
+
export declare function withValue(rule: ProfileRule, value: Value | undefined): AppliedRule;
|
|
252
|
+
/**
|
|
253
|
+
* The profile rules that apply to a part at the project's stage, with its traits and its
|
|
254
|
+
* architecture, and with the project's values in place of the defaults.
|
|
255
|
+
*/
|
|
256
|
+
export declare function applyProfiles(all: readonly Profile[], selection: ProfileSelection): AppliedRule[];
|
|
257
|
+
/**
|
|
258
|
+
* Whether a project's value can stand in for the rule's default: a rule with one, and a value of
|
|
259
|
+
* its type. A default that's a whole number, such as a count of lines, takes only whole numbers of
|
|
260
|
+
* 0 or more, since the tools refuse anything else.
|
|
261
|
+
*/
|
|
262
|
+
export declare function overrideFits(rule: ProfileRule, value: Value): boolean;
|