@sha3/code 1.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/AGENTS.md +75 -0
- package/README.md +554 -0
- package/ai/adapters/codex.md +7 -0
- package/ai/adapters/copilot.md +7 -0
- package/ai/adapters/cursor.md +7 -0
- package/ai/adapters/windsurf.md +8 -0
- package/ai/constitution.md +12 -0
- package/bin/code-standards.mjs +47 -0
- package/biome.json +37 -0
- package/index.mjs +11 -0
- package/lib/cli/parse-args.mjs +416 -0
- package/lib/cli/post-run-guidance.mjs +43 -0
- package/lib/cli/run-init.mjs +123 -0
- package/lib/cli/run-profile.mjs +46 -0
- package/lib/cli/run-refactor.mjs +152 -0
- package/lib/cli/run-verify.mjs +67 -0
- package/lib/constants.mjs +167 -0
- package/lib/contract/load-rule-catalog.mjs +12 -0
- package/lib/contract/render-agents.mjs +79 -0
- package/lib/contract/render-contract-json.mjs +7 -0
- package/lib/contract/resolve-contract.mjs +52 -0
- package/lib/paths.mjs +50 -0
- package/lib/profile.mjs +108 -0
- package/lib/project/ai-instructions.mjs +28 -0
- package/lib/project/biome-ignore.mjs +14 -0
- package/lib/project/managed-files.mjs +105 -0
- package/lib/project/package-metadata.mjs +132 -0
- package/lib/project/prompt-files.mjs +111 -0
- package/lib/project/template-resolution.mjs +70 -0
- package/lib/refactor/materialize-refactor-context.mjs +106 -0
- package/lib/refactor/preservation-questions.mjs +33 -0
- package/lib/refactor/public-contract-extractor.mjs +22 -0
- package/lib/refactor/render-analysis-summary.mjs +50 -0
- package/lib/refactor/source-analysis.mjs +74 -0
- package/lib/utils/fs.mjs +220 -0
- package/lib/utils/prompts.mjs +63 -0
- package/lib/utils/text.mjs +43 -0
- package/lib/verify/change-audit-verifier.mjs +140 -0
- package/lib/verify/change-context.mjs +36 -0
- package/lib/verify/error-handling-verifier.mjs +164 -0
- package/lib/verify/explain-rule.mjs +54 -0
- package/lib/verify/issue-helpers.mjs +132 -0
- package/lib/verify/project-layout-verifier.mjs +259 -0
- package/lib/verify/project-verifier.mjs +267 -0
- package/lib/verify/readme-public-api.mjs +237 -0
- package/lib/verify/readme-verifier.mjs +216 -0
- package/lib/verify/render-json-report.mjs +3 -0
- package/lib/verify/render-text-report.mjs +34 -0
- package/lib/verify/source-analysis.mjs +126 -0
- package/lib/verify/source-rule-verifier.mjs +453 -0
- package/lib/verify/testing-verifier.mjs +113 -0
- package/lib/verify/tooling-verifier.mjs +82 -0
- package/lib/verify/typescript-style-verifier.mjs +407 -0
- package/package.json +55 -0
- package/profiles/default.profile.json +40 -0
- package/profiles/schema.json +96 -0
- package/prompts/init-contract.md +25 -0
- package/prompts/init-phase-2-implement.md +25 -0
- package/prompts/init-phase-3-verify.md +23 -0
- package/prompts/init.prompt.md +24 -0
- package/prompts/refactor-contract.md +26 -0
- package/prompts/refactor-phase-2-rebuild.md +25 -0
- package/prompts/refactor-phase-3-verify.md +24 -0
- package/prompts/refactor.prompt.md +26 -0
- package/resources/ai/AGENTS.md +18 -0
- package/resources/ai/adapters/codex.md +5 -0
- package/resources/ai/adapters/copilot.md +5 -0
- package/resources/ai/adapters/cursor.md +5 -0
- package/resources/ai/adapters/windsurf.md +5 -0
- package/resources/ai/contract.schema.json +68 -0
- package/resources/ai/rule-catalog.json +878 -0
- package/resources/ai/rule-catalog.schema.json +66 -0
- package/resources/ai/templates/adapters/codex.template.md +7 -0
- package/resources/ai/templates/adapters/copilot.template.md +7 -0
- package/resources/ai/templates/adapters/cursor.template.md +7 -0
- package/resources/ai/templates/adapters/windsurf.template.md +7 -0
- package/resources/ai/templates/agents.project.template.md +141 -0
- package/resources/ai/templates/examples/demo/src/billing/billing.service.ts +73 -0
- package/resources/ai/templates/examples/demo/src/config.ts +3 -0
- package/resources/ai/templates/examples/demo/src/invoice/invoice.errors.ts +51 -0
- package/resources/ai/templates/examples/demo/src/invoice/invoice.service.ts +96 -0
- package/resources/ai/templates/examples/demo/src/invoice/invoice.types.ts +9 -0
- package/resources/ai/templates/examples/rules/async-bad.ts +52 -0
- package/resources/ai/templates/examples/rules/async-good.ts +56 -0
- package/resources/ai/templates/examples/rules/class-first-bad.ts +36 -0
- package/resources/ai/templates/examples/rules/class-first-good.ts +74 -0
- package/resources/ai/templates/examples/rules/constructor-bad.ts +68 -0
- package/resources/ai/templates/examples/rules/constructor-good.ts +71 -0
- package/resources/ai/templates/examples/rules/control-flow-bad.ts +31 -0
- package/resources/ai/templates/examples/rules/control-flow-good.ts +54 -0
- package/resources/ai/templates/examples/rules/errors-bad.ts +42 -0
- package/resources/ai/templates/examples/rules/errors-good.ts +23 -0
- package/resources/ai/templates/examples/rules/functions-bad.ts +48 -0
- package/resources/ai/templates/examples/rules/functions-good.ts +58 -0
- package/resources/ai/templates/examples/rules/returns-bad.ts +38 -0
- package/resources/ai/templates/examples/rules/returns-good.ts +44 -0
- package/resources/ai/templates/examples/rules/testing-bad.ts +34 -0
- package/resources/ai/templates/examples/rules/testing-good.ts +54 -0
- package/resources/ai/templates/rules/architecture.md +41 -0
- package/resources/ai/templates/rules/async.md +13 -0
- package/resources/ai/templates/rules/class-first.md +45 -0
- package/resources/ai/templates/rules/control-flow.md +13 -0
- package/resources/ai/templates/rules/errors.md +18 -0
- package/resources/ai/templates/rules/functions.md +29 -0
- package/resources/ai/templates/rules/naming.md +13 -0
- package/resources/ai/templates/rules/readme.md +36 -0
- package/resources/ai/templates/rules/returns.md +13 -0
- package/resources/ai/templates/rules/testing.md +18 -0
- package/resources/ai/templates/rules.project.template.md +66 -0
- package/resources/ai/templates/skills/change-synchronization/SKILL.md +42 -0
- package/resources/ai/templates/skills/feature-shaping/SKILL.md +45 -0
- package/resources/ai/templates/skills/http-api-conventions/SKILL.md +171 -0
- package/resources/ai/templates/skills/init-workflow/SKILL.md +52 -0
- package/resources/ai/templates/skills/readme-authoring/SKILL.md +51 -0
- package/resources/ai/templates/skills/refactor-workflow/SKILL.md +50 -0
- package/resources/ai/templates/skills/simplicity-audit/SKILL.md +41 -0
- package/resources/ai/templates/skills/test-scope-selection/SKILL.md +50 -0
- package/resources/ai/templates/skills.index.template.md +25 -0
- package/standards/architecture.md +72 -0
- package/standards/changelog-policy.md +12 -0
- package/standards/manifest.json +36 -0
- package/standards/readme.md +56 -0
- package/standards/schema.json +124 -0
- package/standards/style.md +106 -0
- package/standards/testing.md +20 -0
- package/standards/tooling.md +38 -0
- package/templates/node-lib/.biomeignore +10 -0
- package/templates/node-lib/.vscode/extensions.json +1 -0
- package/templates/node-lib/.vscode/settings.json +9 -0
- package/templates/node-lib/README.md +172 -0
- package/templates/node-lib/biome.json +37 -0
- package/templates/node-lib/gitignore +6 -0
- package/templates/node-lib/package.json +32 -0
- package/templates/node-lib/scripts/release-publish.mjs +106 -0
- package/templates/node-lib/scripts/run-tests.mjs +65 -0
- package/templates/node-lib/src/config.ts +3 -0
- package/templates/node-lib/src/index.ts +2 -0
- package/templates/node-lib/src/logger.ts +7 -0
- package/templates/node-lib/src/package-info/package-info.service.ts +47 -0
- package/templates/node-lib/test/package-info.test.ts +10 -0
- package/templates/node-lib/tsconfig.build.json +1 -0
- package/templates/node-lib/tsconfig.json +5 -0
- package/templates/node-service/.biomeignore +10 -0
- package/templates/node-service/.vscode/extensions.json +1 -0
- package/templates/node-service/.vscode/settings.json +9 -0
- package/templates/node-service/README.md +244 -0
- package/templates/node-service/biome.json +37 -0
- package/templates/node-service/ecosystem.config.cjs +3 -0
- package/templates/node-service/gitignore +6 -0
- package/templates/node-service/package.json +42 -0
- package/templates/node-service/scripts/release-publish.mjs +106 -0
- package/templates/node-service/scripts/run-tests.mjs +65 -0
- package/templates/node-service/src/app/service-runtime.service.ts +57 -0
- package/templates/node-service/src/app-info/app-info.service.ts +47 -0
- package/templates/node-service/src/config.ts +11 -0
- package/templates/node-service/src/http/http-server.service.ts +66 -0
- package/templates/node-service/src/index.ts +2 -0
- package/templates/node-service/src/logger.ts +7 -0
- package/templates/node-service/src/main.ts +5 -0
- package/templates/node-service/test/service-runtime.test.ts +13 -0
- package/templates/node-service/tsconfig.build.json +1 -0
- package/templates/node-service/tsconfig.json +5 -0
- package/tsconfig/base.json +16 -0
- package/tsconfig/node-lib.json +5 -0
- package/tsconfig/node-service.json +1 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_PROFILE } from "../constants.mjs";
|
|
4
|
+
import { createContractIssue, getActiveVerifyRuleIds, shouldRunRule } from "./issue-helpers.mjs";
|
|
5
|
+
import { getIdentifierText, getLineAndColumn } from "./source-analysis.mjs";
|
|
6
|
+
|
|
7
|
+
const MODULE_CONSTANT_PATTERN = /^[A-Z0-9_]+$/;
|
|
8
|
+
const CAMEL_CASE_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
|
|
9
|
+
|
|
10
|
+
function createIssue(contract, ruleId, relativePath, node, message, options = {}) {
|
|
11
|
+
const location = node && "getStart" in node && "getSourceFile" in node ? getLineAndColumn(node.getSourceFile(), node.getStart(node.getSourceFile())) : {};
|
|
12
|
+
return createContractIssue(contract, {
|
|
13
|
+
ruleId,
|
|
14
|
+
category: "typescript-style",
|
|
15
|
+
relativePath,
|
|
16
|
+
line: location.line,
|
|
17
|
+
column: location.column,
|
|
18
|
+
message,
|
|
19
|
+
verificationMode: options.verificationMode,
|
|
20
|
+
confidence: options.confidence,
|
|
21
|
+
evidence: options.evidence,
|
|
22
|
+
suggestion: options.suggestion,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isExported(node) {
|
|
27
|
+
return Array.isArray(node.modifiers) && node.modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isTypePosition(node) {
|
|
31
|
+
let current = node;
|
|
32
|
+
|
|
33
|
+
while (current.parent) {
|
|
34
|
+
if (ts.isTypeNode(current.parent) || ts.isExpressionWithTypeArguments(current.parent) || ts.isHeritageClause(current.parent)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (
|
|
39
|
+
ts.isImportClause(current.parent) ||
|
|
40
|
+
ts.isImportSpecifier(current.parent) ||
|
|
41
|
+
ts.isImportEqualsDeclaration(current.parent) ||
|
|
42
|
+
ts.isExportSpecifier(current.parent)
|
|
43
|
+
) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
current = current.parent;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function collectPublicApiExports(indexFile) {
|
|
54
|
+
const exportNames = new Set();
|
|
55
|
+
|
|
56
|
+
if (!indexFile) {
|
|
57
|
+
return exportNames;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
ts.forEachChild(indexFile.ast, (node) => {
|
|
61
|
+
if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
62
|
+
for (const element of node.exportClause.elements) {
|
|
63
|
+
exportNames.add(element.name.text);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
(ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) &&
|
|
69
|
+
isExported(node) &&
|
|
70
|
+
node.name
|
|
71
|
+
) {
|
|
72
|
+
exportNames.add(node.name.text);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return exportNames;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function collectImportUsages(ast) {
|
|
80
|
+
const usages = new Map();
|
|
81
|
+
|
|
82
|
+
function visit(node) {
|
|
83
|
+
if (ts.isIdentifier(node)) {
|
|
84
|
+
const references = usages.get(node.text) ?? [];
|
|
85
|
+
references.push(node);
|
|
86
|
+
usages.set(node.text, references);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
ts.forEachChild(node, visit);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
visit(ast);
|
|
93
|
+
return usages;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function bodyLineCount(node) {
|
|
97
|
+
const sourceFile = node.getSourceFile();
|
|
98
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line;
|
|
99
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line;
|
|
100
|
+
return end - start + 1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function nodeLineCount(node) {
|
|
104
|
+
const sourceFile = node.getSourceFile();
|
|
105
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line;
|
|
106
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line;
|
|
107
|
+
return end - start + 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function verifyTypeScriptStyle(_targetPath, contract, analysisContext, options = {}) {
|
|
111
|
+
const activeRuleIds = getActiveVerifyRuleIds(contract);
|
|
112
|
+
const issues = [];
|
|
113
|
+
const publicApiExports = collectPublicApiExports(analysisContext.sourceFilesByRelativePath.get("src/index.ts"));
|
|
114
|
+
const sectionOrder = Array.isArray(contract.profile.comment_section_blocks)
|
|
115
|
+
? contract.profile.comment_section_blocks
|
|
116
|
+
: DEFAULT_PROFILE.comment_section_blocks;
|
|
117
|
+
|
|
118
|
+
for (const file of analysisContext.sourceFiles) {
|
|
119
|
+
const importUsages = collectImportUsages(file.ast);
|
|
120
|
+
|
|
121
|
+
ts.forEachChild(file.ast, function visit(node) {
|
|
122
|
+
if (activeRuleIds.has("no-any") && shouldRunRule(options.onlyRuleIds, "no-any") && node.kind === ts.SyntaxKind.AnyKeyword) {
|
|
123
|
+
issues.push(createIssue(contract, "no-any", file.relativePath, node, "explicit any is forbidden"));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (
|
|
127
|
+
activeRuleIds.has("module-constant-case") &&
|
|
128
|
+
shouldRunRule(options.onlyRuleIds, "module-constant-case") &&
|
|
129
|
+
ts.isVariableStatement(node) &&
|
|
130
|
+
isTopLevelConst(node)
|
|
131
|
+
) {
|
|
132
|
+
for (const declaration of node.declarationList.declarations) {
|
|
133
|
+
const name = getIdentifierText(declaration.name);
|
|
134
|
+
const isCanonicalConfigExport = file.relativePath === "src/config.ts" && name === "config";
|
|
135
|
+
const isCanonicalLoggerExport = file.relativePath === "src/logger.ts" && name === "logger";
|
|
136
|
+
|
|
137
|
+
if (name && !isCanonicalConfigExport && !isCanonicalLoggerExport && !MODULE_CONSTANT_PATTERN.test(name)) {
|
|
138
|
+
issues.push(
|
|
139
|
+
createIssue(
|
|
140
|
+
contract,
|
|
141
|
+
"module-constant-case",
|
|
142
|
+
file.relativePath,
|
|
143
|
+
declaration.name,
|
|
144
|
+
`module-level constants must use SCREAMING_SNAKE_CASE: ${name}`,
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
activeRuleIds.has("config-default-export-name") &&
|
|
153
|
+
shouldRunRule(options.onlyRuleIds, "config-default-export-name") &&
|
|
154
|
+
file.relativePath === "src/config.ts"
|
|
155
|
+
) {
|
|
156
|
+
issues.push(...verifyConfigDefaultExport(contract, file));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (
|
|
160
|
+
activeRuleIds.has("explicit-export-return-types") &&
|
|
161
|
+
shouldRunRule(options.onlyRuleIds, "explicit-export-return-types") &&
|
|
162
|
+
((ts.isFunctionDeclaration(node) && isExported(node)) ||
|
|
163
|
+
(ts.isMethodDeclaration(node) && ts.isClassDeclaration(node.parent) && isExported(node.parent)))
|
|
164
|
+
) {
|
|
165
|
+
const isPublicMethod =
|
|
166
|
+
!ts.isMethodDeclaration(node) || !Array.isArray(node.modifiers) || node.modifiers.every((modifier) => modifier.kind !== ts.SyntaxKind.PrivateKeyword);
|
|
167
|
+
if (isPublicMethod && !node.type) {
|
|
168
|
+
issues.push(
|
|
169
|
+
createIssue(
|
|
170
|
+
contract,
|
|
171
|
+
"explicit-export-return-types",
|
|
172
|
+
file.relativePath,
|
|
173
|
+
node.name ?? node,
|
|
174
|
+
"exported functions and public methods must declare an explicit return type",
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (
|
|
181
|
+
activeRuleIds.has("prefer-types-over-interfaces") &&
|
|
182
|
+
shouldRunRule(options.onlyRuleIds, "prefer-types-over-interfaces") &&
|
|
183
|
+
ts.isInterfaceDeclaration(node)
|
|
184
|
+
) {
|
|
185
|
+
const isPublicContract = isExported(node) && node.name && publicApiExports.has(node.name.text);
|
|
186
|
+
if (!isPublicContract) {
|
|
187
|
+
issues.push(
|
|
188
|
+
createIssue(contract, "prefer-types-over-interfaces", file.relativePath, node.name, "prefer type aliases over interfaces for local modeling"),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (activeRuleIds.has("control-flow-braces") && shouldRunRule(options.onlyRuleIds, "control-flow-braces")) {
|
|
194
|
+
issues.push(...verifyControlFlowBraces(contract, file.relativePath, node));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (
|
|
198
|
+
activeRuleIds.has("single-responsibility-heuristic") &&
|
|
199
|
+
shouldRunRule(options.onlyRuleIds, "single-responsibility-heuristic") &&
|
|
200
|
+
(ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) &&
|
|
201
|
+
node.body &&
|
|
202
|
+
bodyLineCount(node.body) > 30
|
|
203
|
+
) {
|
|
204
|
+
issues.push(
|
|
205
|
+
createIssue(
|
|
206
|
+
contract,
|
|
207
|
+
"single-responsibility-heuristic",
|
|
208
|
+
file.relativePath,
|
|
209
|
+
node.name ?? node,
|
|
210
|
+
"long functions should be split when they start mixing responsibilities",
|
|
211
|
+
{
|
|
212
|
+
verificationMode: "heuristic",
|
|
213
|
+
confidence: "medium",
|
|
214
|
+
evidence: `${bodyLineCount(node.body)} body lines`,
|
|
215
|
+
},
|
|
216
|
+
),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (
|
|
221
|
+
activeRuleIds.has("large-class-heuristic") &&
|
|
222
|
+
shouldRunRule(options.onlyRuleIds, "large-class-heuristic") &&
|
|
223
|
+
ts.isClassDeclaration(node) &&
|
|
224
|
+
isExported(node) &&
|
|
225
|
+
nodeLineCount(node) > 400
|
|
226
|
+
) {
|
|
227
|
+
issues.push(
|
|
228
|
+
createIssue(
|
|
229
|
+
contract,
|
|
230
|
+
"large-class-heuristic",
|
|
231
|
+
file.relativePath,
|
|
232
|
+
node.name ?? node,
|
|
233
|
+
"very large classes should be decomposed into smaller cohesive units",
|
|
234
|
+
{
|
|
235
|
+
verificationMode: "heuristic",
|
|
236
|
+
confidence: "medium",
|
|
237
|
+
evidence: `${nodeLineCount(node)} total lines`,
|
|
238
|
+
suggestion: "split the class by responsibility into smaller private methods or dedicated role-specific files",
|
|
239
|
+
},
|
|
240
|
+
),
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
ts.forEachChild(node, visit);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (activeRuleIds.has("local-constant-case") && shouldRunRule(options.onlyRuleIds, "local-constant-case")) {
|
|
248
|
+
issues.push(...verifyLocalConstants(contract, file));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (activeRuleIds.has("type-only-imports") && shouldRunRule(options.onlyRuleIds, "type-only-imports")) {
|
|
252
|
+
issues.push(...verifyTypeOnlyImports(contract, file, importUsages));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (activeRuleIds.has("comments-policy-audit") && shouldRunRule(options.onlyRuleIds, "comments-policy-audit") && file.relativePath.startsWith("src/")) {
|
|
256
|
+
issues.push(...verifyCommentsAudit(contract, file, sectionOrder));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return issues;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function isTopLevelConst(node) {
|
|
264
|
+
return ts.isSourceFile(node.parent) && node.declarationList.flags & ts.NodeFlags.Const;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function verifyLocalConstants(contract, file) {
|
|
268
|
+
const issues = [];
|
|
269
|
+
|
|
270
|
+
function visit(node) {
|
|
271
|
+
if (ts.isVariableDeclaration(node) && node.parent && ts.isVariableDeclarationList(node.parent) && node.parent.flags & ts.NodeFlags.Const) {
|
|
272
|
+
const isTopLevel = ts.isVariableStatement(node.parent.parent) && ts.isSourceFile(node.parent.parent.parent);
|
|
273
|
+
const name = getIdentifierText(node.name);
|
|
274
|
+
if (!isTopLevel && name && !CAMEL_CASE_PATTERN.test(name)) {
|
|
275
|
+
issues.push(createIssue(contract, "local-constant-case", file.relativePath, node.name, `local constants must use camelCase: ${name}`));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
ts.forEachChild(node, visit);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
visit(file.ast);
|
|
283
|
+
return issues;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function verifyConfigDefaultExport(contract, file) {
|
|
287
|
+
const issues = [];
|
|
288
|
+
let hasNamedConfigDeclaration = false;
|
|
289
|
+
let hasDefaultExportConfig = false;
|
|
290
|
+
|
|
291
|
+
for (const statement of file.ast.statements) {
|
|
292
|
+
if (ts.isVariableStatement(statement)) {
|
|
293
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
294
|
+
if (getIdentifierText(declaration.name) === "config") {
|
|
295
|
+
hasNamedConfigDeclaration = true;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (ts.isExportAssignment(statement) && ts.isIdentifier(statement.expression) && statement.expression.text === "config") {
|
|
301
|
+
hasDefaultExportConfig = true;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (!hasNamedConfigDeclaration || !hasDefaultExportConfig) {
|
|
306
|
+
issues.push(
|
|
307
|
+
createIssue(
|
|
308
|
+
contract,
|
|
309
|
+
"config-default-export-name",
|
|
310
|
+
file.relativePath,
|
|
311
|
+
file.ast,
|
|
312
|
+
"src/config.ts must declare `config` and export it as `export default config`",
|
|
313
|
+
),
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return issues;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function verifyControlFlowBraces(contract, relativePath, node) {
|
|
321
|
+
const issues = [];
|
|
322
|
+
|
|
323
|
+
if (ts.isIfStatement(node)) {
|
|
324
|
+
if (!ts.isBlock(node.thenStatement)) {
|
|
325
|
+
issues.push(createIssue(contract, "control-flow-braces", relativePath, node.thenStatement, "if branches must use braces"));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (node.elseStatement && !ts.isBlock(node.elseStatement)) {
|
|
329
|
+
issues.push(createIssue(contract, "control-flow-braces", relativePath, node.elseStatement, "else branches must use braces"));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (
|
|
334
|
+
(ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node)) &&
|
|
335
|
+
!ts.isBlock(node.statement)
|
|
336
|
+
) {
|
|
337
|
+
issues.push(createIssue(contract, "control-flow-braces", relativePath, node.statement, "loop bodies must use braces"));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return issues;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function verifyTypeOnlyImports(contract, file, importUsages) {
|
|
344
|
+
const issues = [];
|
|
345
|
+
|
|
346
|
+
for (const statement of file.ast.statements) {
|
|
347
|
+
if (!ts.isImportDeclaration(statement) || !statement.importClause || statement.importClause.isTypeOnly) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const importedIdentifiers = [];
|
|
352
|
+
|
|
353
|
+
if (statement.importClause.name) {
|
|
354
|
+
importedIdentifiers.push(statement.importClause.name.text);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (statement.importClause.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)) {
|
|
358
|
+
for (const element of statement.importClause.namedBindings.elements) {
|
|
359
|
+
importedIdentifiers.push(element.name.text);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
for (const importedIdentifier of importedIdentifiers) {
|
|
364
|
+
const identifierUsages = (importUsages.get(importedIdentifier) ?? []).filter(
|
|
365
|
+
(node) => !ts.isImportClause(node.parent) && !ts.isImportSpecifier(node.parent) && !ts.isNamespaceImport(node.parent),
|
|
366
|
+
);
|
|
367
|
+
if (identifierUsages.length > 0 && identifierUsages.every((node) => isTypePosition(node))) {
|
|
368
|
+
issues.push(
|
|
369
|
+
createIssue(
|
|
370
|
+
contract,
|
|
371
|
+
"type-only-imports",
|
|
372
|
+
file.relativePath,
|
|
373
|
+
statement,
|
|
374
|
+
`import used only in type positions must use import type: ${importedIdentifier}`,
|
|
375
|
+
),
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return issues;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function verifyCommentsAudit(contract, file, sectionOrder) {
|
|
385
|
+
const issues = [];
|
|
386
|
+
const hasSections = sectionOrder.some((section) => file.raw.includes(`@section ${section}`));
|
|
387
|
+
const complexSignals = ["if (", "switch (", "try {", ".map(", ".reduce(", ".filter("].filter((token) => file.raw.includes(token)).length;
|
|
388
|
+
const commentSignal = /\/\//.test(file.raw) || /\/\*/.test(file.raw);
|
|
389
|
+
|
|
390
|
+
if (complexSignals >= 3 && !commentSignal && !hasSections) {
|
|
391
|
+
issues.push(
|
|
392
|
+
createIssue(
|
|
393
|
+
contract,
|
|
394
|
+
"comments-policy-audit",
|
|
395
|
+
file.relativePath,
|
|
396
|
+
file.ast,
|
|
397
|
+
"non-trivial logic should include explicit comments under the active comments policy",
|
|
398
|
+
{
|
|
399
|
+
verificationMode: "audit",
|
|
400
|
+
confidence: "medium",
|
|
401
|
+
},
|
|
402
|
+
),
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return issues;
|
|
407
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sha3/code",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-first code standards, tooling exports, and project initializer",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/sha3dev/code"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"code-standards": "./bin/code-standards.mjs"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./index.mjs",
|
|
15
|
+
"./biome": "./biome.json",
|
|
16
|
+
"./tsconfig/base.json": "./tsconfig/base.json",
|
|
17
|
+
"./tsconfig/node-lib.json": "./tsconfig/node-lib.json",
|
|
18
|
+
"./tsconfig/node-service.json": "./tsconfig/node-service.json"
|
|
19
|
+
},
|
|
20
|
+
"files": ["AGENTS.md", "README.md", "ai", "biome.json", "bin", "index.mjs", "lib", "prompts", "resources", "standards", "templates", "tsconfig", "profiles"],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20.11.0"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"check": "npm run standards:validate && npm run profile:validate && npm run ai:validate && npm run lint && npm run format:check && npm run typecheck && npm run test",
|
|
29
|
+
"fix": "npm run lint:fix && npm run format:write",
|
|
30
|
+
"standards:validate": "node scripts/validate-standards.mjs",
|
|
31
|
+
"ai:validate": "node scripts/validate-ai-resources.mjs",
|
|
32
|
+
"lint": "biome check .",
|
|
33
|
+
"lint:fix": "biome check . --write",
|
|
34
|
+
"format:check": "biome format .",
|
|
35
|
+
"format:write": "biome format . --write",
|
|
36
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
37
|
+
"test": "node scripts/smoke-fixtures.mjs && node --test ./test/*.test.mjs",
|
|
38
|
+
"release:check": "npm run check",
|
|
39
|
+
"release:publish": "node scripts/release-publish.mjs",
|
|
40
|
+
"hooks:install": "git config core.hooksPath .githooks",
|
|
41
|
+
"profile:validate": "node scripts/validate-profile.mjs"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@biomejs/biome": "^1.9.4",
|
|
45
|
+
"ajv": "^8.17.1"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@hono/node-server": "^1.19.11",
|
|
49
|
+
"@sha3/logger": "^2.0.0",
|
|
50
|
+
"@types/node": "^22.13.10",
|
|
51
|
+
"hono": "^4.12.7",
|
|
52
|
+
"typescript": "^5.8.2",
|
|
53
|
+
"tsx": "^4.19.3"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "v2",
|
|
3
|
+
"paradigm": "class-first",
|
|
4
|
+
"function_size_policy": "max_30_lines_soft",
|
|
5
|
+
"return_policy": "single_return_strict_no_exceptions",
|
|
6
|
+
"class_design": "constructor_injection",
|
|
7
|
+
"comments_policy": "extensive",
|
|
8
|
+
"testing_policy": "tests_required_for_behavior_change",
|
|
9
|
+
"architecture": "feature_folders",
|
|
10
|
+
"error_handling": "mixed",
|
|
11
|
+
"async_style": "async_await_only",
|
|
12
|
+
"class_file_policy": "one_public_class_per_file",
|
|
13
|
+
"type_contract_policy": "prefer_types_over_interfaces",
|
|
14
|
+
"mutability": "immutability_preferred",
|
|
15
|
+
"comment_section_blocks": [
|
|
16
|
+
"imports:externals",
|
|
17
|
+
"imports:internals",
|
|
18
|
+
"consts",
|
|
19
|
+
"types",
|
|
20
|
+
"class",
|
|
21
|
+
"private:attributes",
|
|
22
|
+
"protected:attributes",
|
|
23
|
+
"public:properties",
|
|
24
|
+
"constructor",
|
|
25
|
+
"static:properties",
|
|
26
|
+
"factory",
|
|
27
|
+
"private:methods",
|
|
28
|
+
"protected:methods",
|
|
29
|
+
"public:methods",
|
|
30
|
+
"static:methods"
|
|
31
|
+
],
|
|
32
|
+
"comment_section_format": "jsdoc-section-tag",
|
|
33
|
+
"comment_sections_required_when_empty": false,
|
|
34
|
+
"if_requires_braces": true,
|
|
35
|
+
"readme_style": "top-tier",
|
|
36
|
+
"rule_severity_model": "tiered",
|
|
37
|
+
"language": "english_technical",
|
|
38
|
+
"examples_density": "rule_with_good_bad_examples",
|
|
39
|
+
"instruction_file_location": "root_agents_md"
|
|
40
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://sha3.dev/code/profile.schema.json",
|
|
4
|
+
"title": "AI Style Profile",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"version",
|
|
9
|
+
"paradigm",
|
|
10
|
+
"function_size_policy",
|
|
11
|
+
"return_policy",
|
|
12
|
+
"class_design",
|
|
13
|
+
"comments_policy",
|
|
14
|
+
"testing_policy",
|
|
15
|
+
"architecture",
|
|
16
|
+
"error_handling",
|
|
17
|
+
"async_style",
|
|
18
|
+
"class_file_policy",
|
|
19
|
+
"type_contract_policy",
|
|
20
|
+
"mutability",
|
|
21
|
+
"comment_section_blocks",
|
|
22
|
+
"comment_section_format",
|
|
23
|
+
"comment_sections_required_when_empty",
|
|
24
|
+
"if_requires_braces",
|
|
25
|
+
"readme_style",
|
|
26
|
+
"rule_severity_model",
|
|
27
|
+
"language",
|
|
28
|
+
"examples_density",
|
|
29
|
+
"instruction_file_location"
|
|
30
|
+
],
|
|
31
|
+
"properties": {
|
|
32
|
+
"version": { "type": "string", "const": "v2" },
|
|
33
|
+
"paradigm": { "type": "string", "enum": ["class-first", "functional-first", "hybrid"] },
|
|
34
|
+
"function_size_policy": { "type": "string", "enum": ["max_20_lines_hard", "max_30_lines_soft", "no_fixed_limit"] },
|
|
35
|
+
"return_policy": { "type": "string", "enum": ["single_return_strict_no_exceptions", "single_return_with_guard_clauses", "free_return_style"] },
|
|
36
|
+
"class_design": { "type": "string", "enum": ["constructor_injection", "internal_instantiation", "mixed"] },
|
|
37
|
+
"comments_policy": { "type": "string", "enum": ["extensive", "complex_logic_only", "minimal"] },
|
|
38
|
+
"testing_policy": { "type": "string", "enum": ["tests_required_for_behavior_change", "tests_critical_only", "tests_optional"] },
|
|
39
|
+
"architecture": { "type": "string", "enum": ["feature_folders", "layered", "simple_src_lib"] },
|
|
40
|
+
"error_handling": { "type": "string", "enum": ["exceptions_with_typed_errors", "result_either", "mixed"] },
|
|
41
|
+
"async_style": { "type": "string", "enum": ["async_await_only", "promise_chains", "both"] },
|
|
42
|
+
"class_file_policy": { "type": "string", "enum": ["one_public_class_per_file", "multiple_classes_allowed", "no_rule"] },
|
|
43
|
+
"type_contract_policy": { "type": "string", "enum": ["prefer_types_over_interfaces", "interfaces_everywhere", "interfaces_public_only"] },
|
|
44
|
+
"mutability": { "type": "string", "enum": ["immutability_preferred", "immutability_strict", "mutable_pragmatic"] },
|
|
45
|
+
"comment_section_blocks": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"minItems": 15,
|
|
48
|
+
"maxItems": 15,
|
|
49
|
+
"items": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"enum": [
|
|
52
|
+
"imports:externals",
|
|
53
|
+
"imports:internals",
|
|
54
|
+
"consts",
|
|
55
|
+
"types",
|
|
56
|
+
"class",
|
|
57
|
+
"private:attributes",
|
|
58
|
+
"protected:attributes",
|
|
59
|
+
"public:properties",
|
|
60
|
+
"constructor",
|
|
61
|
+
"static:properties",
|
|
62
|
+
"factory",
|
|
63
|
+
"private:methods",
|
|
64
|
+
"protected:methods",
|
|
65
|
+
"public:methods",
|
|
66
|
+
"static:methods"
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"const": [
|
|
70
|
+
"imports:externals",
|
|
71
|
+
"imports:internals",
|
|
72
|
+
"consts",
|
|
73
|
+
"types",
|
|
74
|
+
"class",
|
|
75
|
+
"private:attributes",
|
|
76
|
+
"protected:attributes",
|
|
77
|
+
"public:properties",
|
|
78
|
+
"constructor",
|
|
79
|
+
"static:properties",
|
|
80
|
+
"factory",
|
|
81
|
+
"private:methods",
|
|
82
|
+
"protected:methods",
|
|
83
|
+
"public:methods",
|
|
84
|
+
"static:methods"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"comment_section_format": { "type": "string", "const": "jsdoc-section-tag" },
|
|
88
|
+
"comment_sections_required_when_empty": { "type": "boolean" },
|
|
89
|
+
"if_requires_braces": { "type": "boolean", "const": true },
|
|
90
|
+
"readme_style": { "type": "string", "const": "top-tier" },
|
|
91
|
+
"rule_severity_model": { "type": "string", "enum": ["tiered", "all_preferred"] },
|
|
92
|
+
"language": { "type": "string", "enum": ["english_technical", "spanish_technical", "bilingual"] },
|
|
93
|
+
"examples_density": { "type": "string", "enum": ["rule_with_good_bad_examples", "rules_only", "rules_plus_long_templates"] },
|
|
94
|
+
"instruction_file_location": { "type": "string", "enum": ["root_agents_md", "ai_instructions_md", "both"] }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## Init Contract
|
|
2
|
+
|
|
3
|
+
Use this contract during implementation and verification phases, not as phase-1 planning input unless the plan proves it is necessary.
|
|
4
|
+
|
|
5
|
+
- This workflow is an init-based implementation. You MUST load `skills/init-workflow/SKILL.md` and follow it as the primary procedural guide for execution order and reporting.
|
|
6
|
+
- You MUST load `skills/feature-shaping/SKILL.md`, `skills/simplicity-audit/SKILL.md`, and `skills/change-synchronization/SKILL.md` as part of the default init workflow.
|
|
7
|
+
- If the task introduces meaningful behavior changes, you MUST load `skills/test-scope-selection/SKILL.md`.
|
|
8
|
+
- You MUST preserve the scaffold structure and naming conventions.
|
|
9
|
+
- You MUST add or update tests for behavior changes.
|
|
10
|
+
- In class-oriented source files, you MUST keep helper logic inside the class as private or static methods rather than module-scope functions.
|
|
11
|
+
- You MUST split oversized classes into smaller cohesive units instead of keeping large monolithic class files.
|
|
12
|
+
- If the task creates or updates `README.md`, you MUST load `skills/readme-authoring/SKILL.md` and follow it.
|
|
13
|
+
- If the project is a `node-service` or the task changes HTTP endpoints, you MUST load `skills/http-api-conventions/SKILL.md` and follow its transport conventions.
|
|
14
|
+
- You MUST execute `npm run standards:check` yourself, fix every `error`, review every `warning`, report every `audit` item, and rerun until the default verification passes.
|
|
15
|
+
- You MUST let Biome decide final layout and wrapping.
|
|
16
|
+
- You MUST execute `npm run check` yourself before finishing.
|
|
17
|
+
- If `npm run check` fails, you MUST fix the issues and rerun it until it passes.
|
|
18
|
+
- As the final step, you MUST create or update `SCAFFOLD-FEEDBACK.md` in the project root with concrete feedback about scaffold problems, unclear instructions, friction, missing patterns, and suggested improvements.
|
|
19
|
+
|
|
20
|
+
When you respond after implementation, include:
|
|
21
|
+
|
|
22
|
+
- changed files
|
|
23
|
+
- a short compliance checklist
|
|
24
|
+
- proof that `npm run check` passed
|
|
25
|
+
- confirmation that `SCAFFOLD-FEEDBACK.md` was created or updated
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
This is phase 2 of the init workflow. Implement the task now.
|
|
2
|
+
|
|
3
|
+
Read only:
|
|
4
|
+
|
|
5
|
+
- `PROMPT.md`
|
|
6
|
+
- `prompts/init-contract.md`
|
|
7
|
+
- the plan you produced in phase 1
|
|
8
|
+
- the files you identified as necessary during phase 1
|
|
9
|
+
|
|
10
|
+
Load optional skills only if they are actually needed for the approved plan:
|
|
11
|
+
|
|
12
|
+
- `skills/test-scope-selection/SKILL.md` for meaningful behavior changes
|
|
13
|
+
- `skills/readme-authoring/SKILL.md` when `README.md` changes
|
|
14
|
+
- `skills/http-api-conventions/SKILL.md` for `node-service` projects or HTTP endpoint work
|
|
15
|
+
|
|
16
|
+
Execution rules:
|
|
17
|
+
|
|
18
|
+
- Follow `prompts/init-contract.md` and `skills/init-workflow/SKILL.md`.
|
|
19
|
+
- Keep the scaffold-native structure unless the request explicitly requires a standards update.
|
|
20
|
+
- Inspect more files only when the approved plan proves they are necessary.
|
|
21
|
+
- Implement the smallest correct change in `src/` and `test/`.
|
|
22
|
+
- Rewrite `README.md` only after behavior is stable.
|
|
23
|
+
- Do not stop at the end of this phase unless blocked.
|
|
24
|
+
|
|
25
|
+
At the end of this phase, summarize internally what changed, note any unresolved risks that matter for verification, and continue directly into `prompts/init-phase-3-verify.md`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
This is phase 3 of the init workflow. Verify and close the task.
|
|
2
|
+
|
|
3
|
+
Read only:
|
|
4
|
+
|
|
5
|
+
- `PROMPT.md`
|
|
6
|
+
- `prompts/init-contract.md`
|
|
7
|
+
- your phase 2 implementation summary
|
|
8
|
+
- the files changed during implementation
|
|
9
|
+
- the latest command output that shows current verification status
|
|
10
|
+
|
|
11
|
+
Execution rules:
|
|
12
|
+
|
|
13
|
+
- Run the remaining required verification, including `npm run check`.
|
|
14
|
+
- If verification fails, fix the issues and rerun until it passes.
|
|
15
|
+
- Create or update `SCAFFOLD-FEEDBACK.md` as the final project artifact.
|
|
16
|
+
- Keep the final response concise and evidence-based.
|
|
17
|
+
|
|
18
|
+
Your final response must include:
|
|
19
|
+
|
|
20
|
+
1. Changed files.
|
|
21
|
+
2. A short compliance checklist.
|
|
22
|
+
3. Proof that `npm run check` passed.
|
|
23
|
+
4. Confirmation that `SCAFFOLD-FEEDBACK.md` was created or updated.
|