@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,46 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_PROFILE } from "../constants.mjs";
|
|
4
|
+
import { getBundledProfilePath, loadProfileSchema, resolvePackageRoot } from "../paths.mjs";
|
|
5
|
+
import { createProfileInteractively, validateProfile } from "../profile.mjs";
|
|
6
|
+
import { normalizeProfile, pathExists, writeJsonFile } from "../utils/fs.mjs";
|
|
7
|
+
import { promptYesNo, withReadline } from "../utils/prompts.mjs";
|
|
8
|
+
|
|
9
|
+
export async function runProfile(rawOptions) {
|
|
10
|
+
if (rawOptions.help) {
|
|
11
|
+
const { printUsage } = await import("./parse-args.mjs");
|
|
12
|
+
printUsage();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const packageRoot = resolvePackageRoot();
|
|
17
|
+
const schema = await loadProfileSchema(packageRoot);
|
|
18
|
+
const defaultProfilePath = getBundledProfilePath(packageRoot);
|
|
19
|
+
const outputPath = rawOptions.profilePath ? path.resolve(process.cwd(), rawOptions.profilePath) : defaultProfilePath;
|
|
20
|
+
const shouldUseNonInteractive = rawOptions.nonInteractive || !process.stdin.isTTY;
|
|
21
|
+
const exists = await pathExists(outputPath);
|
|
22
|
+
|
|
23
|
+
if (exists && !rawOptions.forceProfile) {
|
|
24
|
+
if (shouldUseNonInteractive) {
|
|
25
|
+
throw new Error(`Profile already exists at ${outputPath}. Use --force-profile to overwrite.`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const shouldOverwrite = await withReadline((rl) => promptYesNo(rl, `Profile already exists at ${outputPath}. Overwrite?`, false));
|
|
29
|
+
if (!shouldOverwrite) {
|
|
30
|
+
console.log("Profile update cancelled.");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let profile = normalizeProfile(DEFAULT_PROFILE);
|
|
36
|
+
|
|
37
|
+
if (shouldUseNonInteractive) {
|
|
38
|
+
validateProfile(profile, schema, "built-in defaults");
|
|
39
|
+
} else {
|
|
40
|
+
profile = await createProfileInteractively(profile);
|
|
41
|
+
validateProfile(profile, schema, "interactive answers");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await writeJsonFile(outputPath, profile);
|
|
45
|
+
console.log(`Profile written to ${outputPath}`);
|
|
46
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { TEMPLATE_NAMES } from "../constants.mjs";
|
|
4
|
+
import { loadProfileSchema, readPackageVersion, resolvePackageRoot } from "../paths.mjs";
|
|
5
|
+
import { resolveProfileForRefactor } from "../profile.mjs";
|
|
6
|
+
import { generateAiInstructions } from "../project/ai-instructions.mjs";
|
|
7
|
+
import { ensureBiomeIgnore } from "../project/biome-ignore.mjs";
|
|
8
|
+
import { applyManagedFiles } from "../project/managed-files.mjs";
|
|
9
|
+
import {
|
|
10
|
+
applyPackageCoordinates,
|
|
11
|
+
extractRepositoryUrl,
|
|
12
|
+
readProjectPackageJson,
|
|
13
|
+
refreshPackageDependencyVersions,
|
|
14
|
+
updateCodeStandardsMetadata,
|
|
15
|
+
writeProjectPackageJson,
|
|
16
|
+
} from "../project/package-metadata.mjs";
|
|
17
|
+
import { resolveTemplateForRefactor, validateInitResources } from "../project/template-resolution.mjs";
|
|
18
|
+
import { materializeRefactorContext, removeManagedProjectSurface } from "../refactor/materialize-refactor-context.mjs";
|
|
19
|
+
import { resolvePreservationDecisions } from "../refactor/preservation-questions.mjs";
|
|
20
|
+
import { extractPublicContract } from "../refactor/public-contract-extractor.mjs";
|
|
21
|
+
import { renderAnalysisSummary } from "../refactor/render-analysis-summary.mjs";
|
|
22
|
+
import { analyzeProjectSource } from "../refactor/source-analysis.mjs";
|
|
23
|
+
import { formatFilesWithBiome, pathExists, readJsonFile, runCommand } from "../utils/fs.mjs";
|
|
24
|
+
import { askChoice, withReadline } from "../utils/prompts.mjs";
|
|
25
|
+
import { defaultPackageNameForProject, normalizePackageName, normalizeRepositoryUrl } from "../utils/text.mjs";
|
|
26
|
+
import { printRefactorGuidance } from "./post-run-guidance.mjs";
|
|
27
|
+
|
|
28
|
+
async function resolveTemplate(rawOptions, projectPackageJson, targetPath) {
|
|
29
|
+
try {
|
|
30
|
+
return await resolveTemplateForRefactor(rawOptions, projectPackageJson, targetPath);
|
|
31
|
+
} catch (_error) {
|
|
32
|
+
if (rawOptions.yes || !process.stdin.isTTY) {
|
|
33
|
+
return "node-lib";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return withReadline((rl) => askChoice(rl, "Choose template for refactor", TEMPLATE_NAMES, "node-lib"));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function readInstalledCodeStandardsVersion(targetPath) {
|
|
41
|
+
const nodeModulesPath = path.join(targetPath, "node_modules");
|
|
42
|
+
const installedPackagePath = path.join(nodeModulesPath, "@sha3", "code", "package.json");
|
|
43
|
+
|
|
44
|
+
if (!(await pathExists(installedPackagePath))) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const installedPackageJson = await readJsonFile(installedPackagePath);
|
|
49
|
+
return typeof installedPackageJson.version === "string" && installedPackageJson.version.length > 0 ? installedPackageJson.version : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function ensureDependenciesInstalled(targetPath, installRequested, packageVersion) {
|
|
53
|
+
const nodeModulesPath = path.join(targetPath, "node_modules");
|
|
54
|
+
|
|
55
|
+
if (installRequested) {
|
|
56
|
+
console.log("Installing dependencies...");
|
|
57
|
+
await runCommand("npm", ["install"], targetPath);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (await pathExists(nodeModulesPath)) {
|
|
62
|
+
const installedCodeStandardsVersion = await readInstalledCodeStandardsVersion(targetPath);
|
|
63
|
+
|
|
64
|
+
if (installedCodeStandardsVersion === packageVersion) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(
|
|
69
|
+
installedCodeStandardsVersion
|
|
70
|
+
? `Installing dependencies because installed @sha3/code is ${installedCodeStandardsVersion} and expected ${packageVersion}...`
|
|
71
|
+
: "Installing dependencies because installed @sha3/code was not found in node_modules...",
|
|
72
|
+
);
|
|
73
|
+
await runCommand("npm", ["install"], targetPath);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log("Installing dependencies because node_modules is missing...");
|
|
78
|
+
await runCommand("npm", ["install"], targetPath);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function runRefactor(rawOptions) {
|
|
82
|
+
if (rawOptions.help) {
|
|
83
|
+
const { printUsage } = await import("./parse-args.mjs");
|
|
84
|
+
printUsage();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const targetPath = path.resolve(process.cwd());
|
|
89
|
+
const packageRoot = resolvePackageRoot();
|
|
90
|
+
const packageVersion = await readPackageVersion(packageRoot);
|
|
91
|
+
const schema = await loadProfileSchema(packageRoot);
|
|
92
|
+
const { packageJsonPath, packageJson: projectPackageJson } = await readProjectPackageJson(targetPath);
|
|
93
|
+
const projectMetadata = projectPackageJson.codeStandards ?? {};
|
|
94
|
+
const template = await resolveTemplate(rawOptions, projectPackageJson, targetPath);
|
|
95
|
+
const { templateDir } = await validateInitResources(packageRoot, template);
|
|
96
|
+
const profileResolution = await resolveProfileForRefactor(packageRoot, targetPath, rawOptions, schema, projectMetadata);
|
|
97
|
+
const inferredProjectName = path.basename(targetPath);
|
|
98
|
+
const projectName = inferredProjectName && inferredProjectName !== path.sep ? inferredProjectName : "my-project";
|
|
99
|
+
const existingPackageName =
|
|
100
|
+
typeof projectPackageJson.name === "string" && projectPackageJson.name.length > 0 ? projectPackageJson.name : defaultPackageNameForProject(projectName);
|
|
101
|
+
const packageName = rawOptions.packageName ? normalizePackageName(rawOptions.packageName, projectName) : existingPackageName;
|
|
102
|
+
const repositoryUrl = normalizeRepositoryUrl(rawOptions.repositoryUrl ?? extractRepositoryUrl(projectPackageJson), packageName);
|
|
103
|
+
const tokens = { projectName, packageName, packageVersion, year: String(new Date().getFullYear()) };
|
|
104
|
+
|
|
105
|
+
console.log("Analyzing current project...");
|
|
106
|
+
const sourceAnalysis = await analyzeProjectSource(targetPath, projectPackageJson);
|
|
107
|
+
const preservation = await resolvePreservationDecisions(rawOptions);
|
|
108
|
+
const publicContract = extractPublicContract(projectPackageJson, template, profileResolution.profilePathForMetadata, sourceAnalysis);
|
|
109
|
+
const analysisSummary = renderAnalysisSummary(projectPackageJson, sourceAnalysis, preservation);
|
|
110
|
+
|
|
111
|
+
console.log("Creating refactor snapshot...");
|
|
112
|
+
await materializeRefactorContext(targetPath, publicContract, preservation, analysisSummary);
|
|
113
|
+
|
|
114
|
+
console.log("Rebuilding managed project surface...");
|
|
115
|
+
await removeManagedProjectSurface(templateDir, targetPath);
|
|
116
|
+
const managedResults = await applyManagedFiles({
|
|
117
|
+
templateDir,
|
|
118
|
+
targetDir: targetPath,
|
|
119
|
+
tokens,
|
|
120
|
+
projectPackageJson,
|
|
121
|
+
dryRun: false,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const contract = await generateAiInstructions({
|
|
125
|
+
packageRoot,
|
|
126
|
+
packageVersion,
|
|
127
|
+
targetDir: targetPath,
|
|
128
|
+
tokens,
|
|
129
|
+
profile: profileResolution.profile,
|
|
130
|
+
template,
|
|
131
|
+
withAiAdapters: rawOptions.withAiAdapters,
|
|
132
|
+
workflow: "refactor",
|
|
133
|
+
});
|
|
134
|
+
await ensureBiomeIgnore(targetPath, contract.managedFiles);
|
|
135
|
+
|
|
136
|
+
const packageWithCoordinates = applyPackageCoordinates(managedResults.mergedPackageJson, packageName, repositoryUrl);
|
|
137
|
+
const packageWithRefreshedDependencies = await refreshPackageDependencyVersions(packageWithCoordinates, {
|
|
138
|
+
cwd: targetPath,
|
|
139
|
+
codeStandardsVersion: packageVersion,
|
|
140
|
+
});
|
|
141
|
+
const packageWithMetadata = updateCodeStandardsMetadata(packageWithRefreshedDependencies, {
|
|
142
|
+
template,
|
|
143
|
+
profilePath: profileResolution.profilePathForMetadata,
|
|
144
|
+
withAiAdapters: rawOptions.withAiAdapters,
|
|
145
|
+
lastRefactorWith: packageVersion,
|
|
146
|
+
});
|
|
147
|
+
await writeProjectPackageJson(packageJsonPath, packageWithMetadata);
|
|
148
|
+
await formatFilesWithBiome(packageRoot, targetPath, ["package.json", "ai/contract.json"]);
|
|
149
|
+
|
|
150
|
+
await ensureDependenciesInstalled(targetPath, rawOptions.install, packageVersion);
|
|
151
|
+
await printRefactorGuidance(targetPath);
|
|
152
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { loadRuleCatalog } from "../contract/load-rule-catalog.mjs";
|
|
2
|
+
import { resolvePackageRoot } from "../paths.mjs";
|
|
3
|
+
import { explainRule } from "../verify/explain-rule.mjs";
|
|
4
|
+
import { PROJECT_VERIFY_RULE_IDS } from "../verify/issue-helpers.mjs";
|
|
5
|
+
import { verifyProject } from "../verify/project-verifier.mjs";
|
|
6
|
+
import { renderJsonReport } from "../verify/render-json-report.mjs";
|
|
7
|
+
import { renderTextReport } from "../verify/render-text-report.mjs";
|
|
8
|
+
|
|
9
|
+
export async function runVerify(rawOptions) {
|
|
10
|
+
if (rawOptions.help) {
|
|
11
|
+
const { printUsage } = await import("./parse-args.mjs");
|
|
12
|
+
printUsage();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const packageRoot = resolvePackageRoot();
|
|
17
|
+
const ruleCatalog = await loadRuleCatalog(packageRoot);
|
|
18
|
+
|
|
19
|
+
if (rawOptions.explainRuleId) {
|
|
20
|
+
const rule = ruleCatalog.rules.find((candidate) => candidate.id === rawOptions.explainRuleId);
|
|
21
|
+
|
|
22
|
+
if (!rule) {
|
|
23
|
+
throw new Error(`Unknown rule id for --explain: ${rawOptions.explainRuleId}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(explainRule(rule));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (rawOptions.onlyRuleIds) {
|
|
31
|
+
const knownRuleIds = new Set([...PROJECT_VERIFY_RULE_IDS, ...ruleCatalog.rules.map((rule) => rule.id)]);
|
|
32
|
+
|
|
33
|
+
for (const ruleId of rawOptions.onlyRuleIds) {
|
|
34
|
+
if (!knownRuleIds.has(ruleId)) {
|
|
35
|
+
throw new Error(`Unknown rule id for --only: ${ruleId}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const targetPath = process.cwd();
|
|
41
|
+
const result = await verifyProject(targetPath, {
|
|
42
|
+
onlyRuleIds: rawOptions.onlyRuleIds,
|
|
43
|
+
files: rawOptions.files,
|
|
44
|
+
strict: rawOptions.strict,
|
|
45
|
+
changedAgainst: rawOptions.changedAgainst,
|
|
46
|
+
staged: rawOptions.staged,
|
|
47
|
+
allFiles: rawOptions.allFiles,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (rawOptions.report === "json") {
|
|
51
|
+
console.log(renderJsonReport(result));
|
|
52
|
+
if (!result.ok) {
|
|
53
|
+
throw new Error(`Verification failed with ${result.summary.errorCount} error(s) and ${result.summary.warningCount} warning(s).`);
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const output = renderTextReport(result);
|
|
59
|
+
|
|
60
|
+
if (result.ok) {
|
|
61
|
+
console.log(output);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.error(output);
|
|
66
|
+
throw new Error(`Verification failed with ${result.summary.errorCount} error(s) and ${result.summary.warningCount} warning(s).`);
|
|
67
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
export const TEMPLATE_NAMES = ["node-lib", "node-service"];
|
|
2
|
+
export const CODE_STANDARDS_METADATA_KEY = "codeStandards";
|
|
3
|
+
export const CONTRACT_FORMAT_VERSION = "v2";
|
|
4
|
+
export const NODE_LIB_TEMPLATE_SIGNATURE = { main: "dist/index.js", types: "dist/index.d.ts" };
|
|
5
|
+
export const NODE_SERVICE_START_SIGNATURE = "node --import tsx src/main.ts";
|
|
6
|
+
export const REFACTOR_ARTIFACT_ROOT = ".code-standards/refactor-source";
|
|
7
|
+
export const REFACTOR_LATEST_DIR = `${REFACTOR_ARTIFACT_ROOT}/latest`;
|
|
8
|
+
export const REFACTOR_PUBLIC_CONTRACT_PATH = `${REFACTOR_ARTIFACT_ROOT}/public-contract.json`;
|
|
9
|
+
export const REFACTOR_PRESERVATION_PATH = `${REFACTOR_ARTIFACT_ROOT}/preservation.json`;
|
|
10
|
+
export const REFACTOR_ANALYSIS_SUMMARY_PATH = `${REFACTOR_ARTIFACT_ROOT}/analysis-summary.md`;
|
|
11
|
+
export const MANAGED_PROJECT_SURFACE_ROOTS = [
|
|
12
|
+
"AGENTS.md",
|
|
13
|
+
"ai",
|
|
14
|
+
"prompts",
|
|
15
|
+
".vscode",
|
|
16
|
+
".biomeignore",
|
|
17
|
+
"biome.json",
|
|
18
|
+
"README.md",
|
|
19
|
+
"src",
|
|
20
|
+
"test",
|
|
21
|
+
"scripts",
|
|
22
|
+
"tsconfig.json",
|
|
23
|
+
"tsconfig.build.json",
|
|
24
|
+
"ecosystem.config.cjs",
|
|
25
|
+
".gitignore",
|
|
26
|
+
"eslint.config.mjs",
|
|
27
|
+
"prettier.config.cjs",
|
|
28
|
+
];
|
|
29
|
+
export const TEMPLATE_REQUIRED_FILES = {
|
|
30
|
+
"node-lib": ["src/config.ts", "src/package-info/package-info.service.ts", "src/index.ts", "test/package-info.test.ts"],
|
|
31
|
+
"node-service": [
|
|
32
|
+
"src/config.ts",
|
|
33
|
+
"src/app-info/app-info.service.ts",
|
|
34
|
+
"src/http/http-server.service.ts",
|
|
35
|
+
"src/app/service-runtime.service.ts",
|
|
36
|
+
"src/index.ts",
|
|
37
|
+
"src/main.ts",
|
|
38
|
+
"test/service-runtime.test.ts",
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const PROFILE_KEY_ORDER = [
|
|
43
|
+
"version",
|
|
44
|
+
"paradigm",
|
|
45
|
+
"function_size_policy",
|
|
46
|
+
"return_policy",
|
|
47
|
+
"class_design",
|
|
48
|
+
"comments_policy",
|
|
49
|
+
"testing_policy",
|
|
50
|
+
"architecture",
|
|
51
|
+
"error_handling",
|
|
52
|
+
"async_style",
|
|
53
|
+
"class_file_policy",
|
|
54
|
+
"type_contract_policy",
|
|
55
|
+
"mutability",
|
|
56
|
+
"comment_section_blocks",
|
|
57
|
+
"comment_section_format",
|
|
58
|
+
"comment_sections_required_when_empty",
|
|
59
|
+
"if_requires_braces",
|
|
60
|
+
"readme_style",
|
|
61
|
+
"rule_severity_model",
|
|
62
|
+
"language",
|
|
63
|
+
"examples_density",
|
|
64
|
+
"instruction_file_location",
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
export const DEFAULT_PROFILE = {
|
|
68
|
+
version: "v2",
|
|
69
|
+
paradigm: "class-first",
|
|
70
|
+
function_size_policy: "max_30_lines_soft",
|
|
71
|
+
return_policy: "single_return_strict_no_exceptions",
|
|
72
|
+
class_design: "constructor_injection",
|
|
73
|
+
comments_policy: "extensive",
|
|
74
|
+
testing_policy: "tests_required_for_behavior_change",
|
|
75
|
+
architecture: "feature_folders",
|
|
76
|
+
error_handling: "mixed",
|
|
77
|
+
async_style: "async_await_only",
|
|
78
|
+
class_file_policy: "one_public_class_per_file",
|
|
79
|
+
type_contract_policy: "prefer_types_over_interfaces",
|
|
80
|
+
mutability: "immutability_preferred",
|
|
81
|
+
comment_section_blocks: [
|
|
82
|
+
"imports:externals",
|
|
83
|
+
"imports:internals",
|
|
84
|
+
"consts",
|
|
85
|
+
"types",
|
|
86
|
+
"class",
|
|
87
|
+
"private:attributes",
|
|
88
|
+
"protected:attributes",
|
|
89
|
+
"public:properties",
|
|
90
|
+
"constructor",
|
|
91
|
+
"static:properties",
|
|
92
|
+
"factory",
|
|
93
|
+
"private:methods",
|
|
94
|
+
"protected:methods",
|
|
95
|
+
"public:methods",
|
|
96
|
+
"static:methods",
|
|
97
|
+
],
|
|
98
|
+
comment_section_format: "jsdoc-section-tag",
|
|
99
|
+
comment_sections_required_when_empty: false,
|
|
100
|
+
if_requires_braces: true,
|
|
101
|
+
readme_style: "top-tier",
|
|
102
|
+
rule_severity_model: "tiered",
|
|
103
|
+
language: "english_technical",
|
|
104
|
+
examples_density: "rule_with_good_bad_examples",
|
|
105
|
+
instruction_file_location: "root_agents_md",
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const PROFILE_QUESTIONS = [
|
|
109
|
+
{ key: "paradigm", prompt: "Preferred paradigm", options: ["class-first", "hybrid", "functional-first"] },
|
|
110
|
+
{ key: "function_size_policy", prompt: "Function size policy", options: ["max_20_lines_hard", "max_30_lines_soft", "no_fixed_limit"] },
|
|
111
|
+
{ key: "return_policy", prompt: "Return policy", options: ["single_return_strict_no_exceptions", "single_return_with_guard_clauses", "free_return_style"] },
|
|
112
|
+
{ key: "class_design", prompt: "Class design policy", options: ["constructor_injection", "internal_instantiation", "mixed"] },
|
|
113
|
+
{ key: "comments_policy", prompt: "Comments policy", options: ["extensive", "complex_logic_only", "minimal"] },
|
|
114
|
+
{ key: "testing_policy", prompt: "Testing policy", options: ["tests_required_for_behavior_change", "tests_critical_only", "tests_optional"] },
|
|
115
|
+
{ key: "architecture", prompt: "Architecture style", options: ["feature_folders", "layered", "simple_src_lib"] },
|
|
116
|
+
{ key: "error_handling", prompt: "Error handling style", options: ["mixed", "exceptions_with_typed_errors", "result_either"] },
|
|
117
|
+
{ key: "async_style", prompt: "Async style", options: ["async_await_only", "promise_chains", "both"] },
|
|
118
|
+
{ key: "class_file_policy", prompt: "Class/file policy", options: ["one_public_class_per_file", "multiple_classes_allowed", "no_rule"] },
|
|
119
|
+
{ key: "type_contract_policy", prompt: "Type contract policy", options: ["prefer_types_over_interfaces", "interfaces_everywhere", "interfaces_public_only"] },
|
|
120
|
+
{ key: "mutability", prompt: "Mutability policy", options: ["immutability_preferred", "immutability_strict", "mutable_pragmatic"] },
|
|
121
|
+
{ key: "rule_severity_model", prompt: "Rule severity model", options: ["tiered", "all_preferred"] },
|
|
122
|
+
{ key: "language", prompt: "Instruction language", options: ["english_technical", "spanish_technical", "bilingual"] },
|
|
123
|
+
{ key: "examples_density", prompt: "Examples density", options: ["rule_with_good_bad_examples", "rules_only", "rules_plus_long_templates"] },
|
|
124
|
+
{ key: "instruction_file_location", prompt: "Instruction file location", options: ["root_agents_md", "ai_instructions_md", "both"] },
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
export const README_REQUIRED_HEADINGS_BY_TEMPLATE = {
|
|
128
|
+
"node-lib": [
|
|
129
|
+
"## TL;DR",
|
|
130
|
+
"## Why",
|
|
131
|
+
"## Main Capabilities",
|
|
132
|
+
"## Installation",
|
|
133
|
+
"## Usage",
|
|
134
|
+
"## Examples",
|
|
135
|
+
"## Public API",
|
|
136
|
+
"## Configuration",
|
|
137
|
+
"## Compatibility",
|
|
138
|
+
"## Scripts",
|
|
139
|
+
"## Structure",
|
|
140
|
+
"## Troubleshooting",
|
|
141
|
+
"## AI Workflow",
|
|
142
|
+
],
|
|
143
|
+
"node-service": [
|
|
144
|
+
"## TL;DR",
|
|
145
|
+
"## Why",
|
|
146
|
+
"## Main Capabilities",
|
|
147
|
+
"## Installation",
|
|
148
|
+
"## Running Locally",
|
|
149
|
+
"## Usage",
|
|
150
|
+
"## Examples",
|
|
151
|
+
"## Public API",
|
|
152
|
+
"## Configuration",
|
|
153
|
+
"## Compatibility",
|
|
154
|
+
"## Scripts",
|
|
155
|
+
"## Structure",
|
|
156
|
+
"## Troubleshooting",
|
|
157
|
+
"## AI Workflow",
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const MANAGED_AI_FILES = ["AGENTS.md", "SKILLS.md", "ai/contract.json", "ai/rules.md"];
|
|
162
|
+
export const AI_ADAPTER_FILES = ["codex.md", "cursor.md", "copilot.md", "windsurf.md"];
|
|
163
|
+
|
|
164
|
+
export function isRefactorArtifactPath(relativePath) {
|
|
165
|
+
const normalizedPath = relativePath.split("\\").join("/");
|
|
166
|
+
return normalizedPath === REFACTOR_ARTIFACT_ROOT || normalizedPath.startsWith(`${REFACTOR_ARTIFACT_ROOT}/`);
|
|
167
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getContractSchemaPath, getRuleCatalogPath, getRuleCatalogSchemaPath, loadJsonFileAt } from "../paths.mjs";
|
|
2
|
+
import { validateAgainstSchema } from "../utils/fs.mjs";
|
|
3
|
+
|
|
4
|
+
export async function loadRuleCatalog(packageRoot) {
|
|
5
|
+
const [catalog, schema] = await Promise.all([loadJsonFileAt(getRuleCatalogPath(packageRoot)), loadJsonFileAt(getRuleCatalogSchemaPath(packageRoot))]);
|
|
6
|
+
validateAgainstSchema(catalog, schema, "resources/ai/rule-catalog.json");
|
|
7
|
+
return catalog;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function loadContractSchema(packageRoot) {
|
|
11
|
+
return loadJsonFileAt(getContractSchemaPath(packageRoot));
|
|
12
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { replaceTokens } from "../utils/text.mjs";
|
|
5
|
+
|
|
6
|
+
function buildRuleLines(rules, verificationMode) {
|
|
7
|
+
return rules
|
|
8
|
+
.filter((rule) => rule.verificationMode === verificationMode)
|
|
9
|
+
.map((rule) => `- \`${rule.id}\`: ${rule.summary} (severity: ${rule.severity}, enforced by: ${rule.enforcedBy.join(", ")}, confidence: ${rule.confidence})`)
|
|
10
|
+
.join("\n");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function buildManagedFiles(managedFiles) {
|
|
14
|
+
return managedFiles.map((filePath) => `- \`${filePath}\``).join("\n");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function renderProjectAgents(packageRoot, targetDir, contract) {
|
|
18
|
+
const templatePath = path.join(packageRoot, "resources", "ai", "templates", "agents.project.template.md");
|
|
19
|
+
const template = await readFile(templatePath, "utf8");
|
|
20
|
+
const rendered = replaceTokens(template, {
|
|
21
|
+
projectName: contract.project.name,
|
|
22
|
+
contractVersion: contract.formatVersion,
|
|
23
|
+
generatedByVersion: contract.generatedByVersion,
|
|
24
|
+
profileSummary: Object.entries(contract.profile)
|
|
25
|
+
.map(([key, value]) => `- \`${key}\`: \`${Array.isArray(value) ? value.join(" | ") : String(value)}\``)
|
|
26
|
+
.join("\n"),
|
|
27
|
+
deterministicRules: buildRuleLines(contract.rules, "deterministic"),
|
|
28
|
+
heuristicRules: buildRuleLines(contract.rules, "heuristic"),
|
|
29
|
+
auditRules: buildRuleLines(contract.rules, "audit"),
|
|
30
|
+
managedFiles: buildManagedFiles(contract.managedFiles),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await writeFile(path.join(targetDir, "AGENTS.md"), rendered, "utf8");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function renderProjectRules(packageRoot, targetDir, contract) {
|
|
37
|
+
const templatePath = path.join(packageRoot, "resources", "ai", "templates", "rules.project.template.md");
|
|
38
|
+
const template = await readFile(templatePath, "utf8");
|
|
39
|
+
const rendered = replaceTokens(template, {
|
|
40
|
+
deterministicRules: buildRuleLines(contract.rules, "deterministic"),
|
|
41
|
+
heuristicRules: buildRuleLines(contract.rules, "heuristic"),
|
|
42
|
+
auditRules: buildRuleLines(contract.rules, "audit"),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await writeFile(path.join(targetDir, "ai", "rules.md"), rendered, "utf8");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function renderSkillFiles(packageRoot, targetDir, tokens) {
|
|
49
|
+
const { copyTemplateDirectory } = await import("../utils/fs.mjs");
|
|
50
|
+
const skillsTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "skills");
|
|
51
|
+
await copyTemplateDirectory(skillsTemplateDir, path.join(targetDir, "skills"), tokens);
|
|
52
|
+
|
|
53
|
+
const skillsIndexTemplatePath = path.join(packageRoot, "resources", "ai", "templates", "skills.index.template.md");
|
|
54
|
+
const skillsIndexTemplate = await readFile(skillsIndexTemplatePath, "utf8");
|
|
55
|
+
await writeFile(path.join(targetDir, "SKILLS.md"), replaceTokens(skillsIndexTemplate, tokens), "utf8");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function renderAdapterFiles(packageRoot, targetDir, tokens) {
|
|
59
|
+
const adaptersTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "adapters");
|
|
60
|
+
const adaptersTarget = path.join(targetDir, "ai");
|
|
61
|
+
const { mkdir, readdir } = await import("node:fs/promises");
|
|
62
|
+
|
|
63
|
+
await mkdir(adaptersTarget, { recursive: true });
|
|
64
|
+
const entries = await readdir(adaptersTemplateDir, { withFileTypes: true });
|
|
65
|
+
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
if (!entry.isFile() || !entry.name.endsWith(".template.md")) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const raw = await readFile(path.join(adaptersTemplateDir, entry.name), "utf8");
|
|
72
|
+
await writeFile(path.join(adaptersTarget, entry.name.replace(/\.template\.md$/, ".md")), replaceTokens(raw, tokens), "utf8");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function renderExampleFiles(packageRoot, targetDir, tokens) {
|
|
77
|
+
const { copyTemplateDirectory } = await import("../utils/fs.mjs");
|
|
78
|
+
await copyTemplateDirectory(path.join(packageRoot, "resources", "ai", "templates", "examples"), path.join(targetDir, "ai", "examples"), tokens);
|
|
79
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { CONTRACT_FORMAT_VERSION } from "../constants.mjs";
|
|
2
|
+
|
|
3
|
+
function matchesProfileOverride(rule, profile) {
|
|
4
|
+
const overrides = rule.profileOverrides ?? [];
|
|
5
|
+
|
|
6
|
+
if (overrides.length === 0) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return overrides.every((override) => {
|
|
11
|
+
if (override.equals !== undefined) {
|
|
12
|
+
return profile[override.key] === override.equals;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (Array.isArray(override.oneOf)) {
|
|
16
|
+
return override.oneOf.includes(profile[override.key]);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return true;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveContract(options) {
|
|
24
|
+
const { packageVersion, projectName, template, profile, withAiAdapters, managedFiles, ruleCatalog } = options;
|
|
25
|
+
const activeRules = ruleCatalog.rules
|
|
26
|
+
.filter((rule) => matchesProfileOverride(rule, profile))
|
|
27
|
+
.map((rule) => ({
|
|
28
|
+
id: rule.id,
|
|
29
|
+
title: rule.title,
|
|
30
|
+
summary: rule.summary,
|
|
31
|
+
severity: rule.severity,
|
|
32
|
+
kind: rule.kind,
|
|
33
|
+
deterministic: rule.verificationMode === "deterministic",
|
|
34
|
+
verificationMode: rule.verificationMode,
|
|
35
|
+
verificationSource: rule.verificationSource,
|
|
36
|
+
implementedBy: rule.implementedBy,
|
|
37
|
+
requiresContext: rule.requiresContext,
|
|
38
|
+
confidence: rule.confidence,
|
|
39
|
+
appliesTo: rule.appliesTo,
|
|
40
|
+
enforcedBy: rule.enforcedBy,
|
|
41
|
+
examples: rule.examples,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
formatVersion: CONTRACT_FORMAT_VERSION,
|
|
46
|
+
generatedByVersion: packageVersion,
|
|
47
|
+
project: { name: projectName, template, withAiAdapters },
|
|
48
|
+
profile,
|
|
49
|
+
managedFiles,
|
|
50
|
+
rules: activeRules,
|
|
51
|
+
};
|
|
52
|
+
}
|
package/lib/paths.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { readJsonFile } from "./utils/fs.mjs";
|
|
7
|
+
|
|
8
|
+
export function resolvePackageRoot() {
|
|
9
|
+
const callerPath = fileURLToPath(import.meta.url);
|
|
10
|
+
return path.resolve(path.dirname(callerPath), "..");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function readPackageVersion(packageRoot) {
|
|
14
|
+
const packageJson = await readJsonFile(path.join(packageRoot, "package.json"));
|
|
15
|
+
|
|
16
|
+
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
17
|
+
throw new Error("Package version is missing in the CLI package.json.");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return packageJson.version;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getBundledProfilePath(packageRoot) {
|
|
24
|
+
return path.join(packageRoot, "profiles", "default.profile.json");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getProfileSchemaPath(packageRoot) {
|
|
28
|
+
return path.join(packageRoot, "profiles", "schema.json");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getRuleCatalogPath(packageRoot) {
|
|
32
|
+
return path.join(packageRoot, "resources", "ai", "rule-catalog.json");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getRuleCatalogSchemaPath(packageRoot) {
|
|
36
|
+
return path.join(packageRoot, "resources", "ai", "rule-catalog.schema.json");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getContractSchemaPath(packageRoot) {
|
|
40
|
+
return path.join(packageRoot, "resources", "ai", "contract.schema.json");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function loadJsonFileAt(filePath) {
|
|
44
|
+
await access(filePath, constants.R_OK);
|
|
45
|
+
return readJsonFile(filePath);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function loadProfileSchema(packageRoot) {
|
|
49
|
+
return loadJsonFileAt(getProfileSchemaPath(packageRoot));
|
|
50
|
+
}
|