@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
package/lib/profile.mjs
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { copyFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { DEFAULT_PROFILE, PROFILE_QUESTIONS } from "./constants.mjs";
|
|
5
|
+
import { getBundledProfilePath } from "./paths.mjs";
|
|
6
|
+
import { getRelativeProfilePath } from "./project/package-metadata.mjs";
|
|
7
|
+
import { normalizeProfile, pathExists, readJsonFile, validateAgainstSchema, writeJsonFile } from "./utils/fs.mjs";
|
|
8
|
+
import { askChoice, promptYesNo, withReadline } from "./utils/prompts.mjs";
|
|
9
|
+
|
|
10
|
+
export function validateProfile(profile, schema, sourceLabel) {
|
|
11
|
+
validateAgainstSchema(profile, schema, sourceLabel);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function readAndValidateProfile(profilePath, schema) {
|
|
15
|
+
const profile = await readJsonFile(profilePath);
|
|
16
|
+
validateProfile(profile, schema, profilePath);
|
|
17
|
+
return normalizeProfile(profile);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function createProfileInteractively(baseProfile) {
|
|
21
|
+
return withReadline(async (rl) => {
|
|
22
|
+
const profile = { ...baseProfile };
|
|
23
|
+
|
|
24
|
+
for (const question of PROFILE_QUESTIONS) {
|
|
25
|
+
profile[question.key] = await askChoice(rl, question.prompt, question.options, profile[question.key]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return normalizeProfile(profile);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function maybeInitializeProfileInteractively(packageRoot, profilePath) {
|
|
33
|
+
const shouldInit = await withReadline((rl) => promptYesNo(rl, `Profile not found at ${profilePath}. Initialize it with package defaults?`, true));
|
|
34
|
+
|
|
35
|
+
if (!shouldInit) {
|
|
36
|
+
throw new Error("Profile initialization declined by user.");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await mkdir(path.dirname(profilePath), { recursive: true });
|
|
40
|
+
await copyFile(getBundledProfilePath(packageRoot), profilePath);
|
|
41
|
+
console.log(`Profile initialized at ${profilePath}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function resolveBundledOrDefaultProfile(packageRoot, schema) {
|
|
45
|
+
const bundledProfilePath = getBundledProfilePath(packageRoot);
|
|
46
|
+
|
|
47
|
+
if (await pathExists(bundledProfilePath)) {
|
|
48
|
+
return { profile: await readAndValidateProfile(bundledProfilePath, schema), profilePathForMetadata: null };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
validateProfile(DEFAULT_PROFILE, schema, "hardcoded defaults");
|
|
52
|
+
return { profile: normalizeProfile(DEFAULT_PROFILE), profilePathForMetadata: null };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function resolveProfileForInit(packageRoot, targetPath, rawOptions, schema) {
|
|
56
|
+
const bundledProfilePath = getBundledProfilePath(packageRoot);
|
|
57
|
+
|
|
58
|
+
if (!rawOptions.profilePath) {
|
|
59
|
+
if (!(await pathExists(bundledProfilePath))) {
|
|
60
|
+
if (rawOptions.yes) {
|
|
61
|
+
return resolveBundledOrDefaultProfile(packageRoot, schema);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await writeJsonFile(bundledProfilePath, normalizeProfile(DEFAULT_PROFILE));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { profile: await readAndValidateProfile(bundledProfilePath, schema), profilePathForMetadata: null };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const requestedPath = path.resolve(targetPath, rawOptions.profilePath);
|
|
71
|
+
|
|
72
|
+
if (!(await pathExists(requestedPath))) {
|
|
73
|
+
if (rawOptions.yes) {
|
|
74
|
+
return resolveBundledOrDefaultProfile(packageRoot, schema);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await maybeInitializeProfileInteractively(packageRoot, requestedPath);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { profile: await readAndValidateProfile(requestedPath, schema), profilePathForMetadata: getRelativeProfilePath(requestedPath, targetPath) };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function resolveProfileForRefactor(packageRoot, targetPath, rawOptions, schema, projectMetadata) {
|
|
84
|
+
let selectedProfilePath;
|
|
85
|
+
|
|
86
|
+
if (rawOptions.profilePath) {
|
|
87
|
+
selectedProfilePath = path.resolve(targetPath, rawOptions.profilePath);
|
|
88
|
+
} else if (typeof projectMetadata.profilePath === "string" && projectMetadata.profilePath.trim().length > 0) {
|
|
89
|
+
selectedProfilePath = path.resolve(targetPath, projectMetadata.profilePath);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!selectedProfilePath) {
|
|
93
|
+
return resolveBundledOrDefaultProfile(packageRoot, schema);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!(await pathExists(selectedProfilePath))) {
|
|
97
|
+
if (rawOptions.yes) {
|
|
98
|
+
return resolveBundledOrDefaultProfile(packageRoot, schema);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await maybeInitializeProfileInteractively(packageRoot, selectedProfilePath);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
profile: await readAndValidateProfile(selectedProfilePath, schema),
|
|
106
|
+
profilePathForMetadata: getRelativeProfilePath(selectedProfilePath, targetPath),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { loadRuleCatalog } from "../contract/load-rule-catalog.mjs";
|
|
2
|
+
import { renderAdapterFiles, renderExampleFiles, renderProjectAgents, renderProjectRules, renderSkillFiles } from "../contract/render-agents.mjs";
|
|
3
|
+
import { renderContractJson } from "../contract/render-contract-json.mjs";
|
|
4
|
+
import { resolveContract } from "../contract/resolve-contract.mjs";
|
|
5
|
+
import { formatFilesWithBiome } from "../utils/fs.mjs";
|
|
6
|
+
import { collectManagedFiles } from "./managed-files.mjs";
|
|
7
|
+
import { renderPromptFiles } from "./prompt-files.mjs";
|
|
8
|
+
|
|
9
|
+
export async function generateAiInstructions(options) {
|
|
10
|
+
const { packageRoot, packageVersion, targetDir, tokens, profile, template, withAiAdapters, workflow = "init" } = options;
|
|
11
|
+
const ruleCatalog = await loadRuleCatalog(packageRoot);
|
|
12
|
+
const managedFiles = await collectManagedFiles(packageRoot, withAiAdapters);
|
|
13
|
+
const contract = resolveContract({ packageVersion, projectName: tokens.projectName, template, profile, withAiAdapters, managedFiles, ruleCatalog });
|
|
14
|
+
|
|
15
|
+
await renderContractJson(targetDir, contract);
|
|
16
|
+
await formatFilesWithBiome(packageRoot, targetDir, ["ai/contract.json"]);
|
|
17
|
+
await renderProjectAgents(packageRoot, targetDir, contract);
|
|
18
|
+
await renderSkillFiles(packageRoot, targetDir, tokens);
|
|
19
|
+
await renderProjectRules(packageRoot, targetDir, contract);
|
|
20
|
+
await renderPromptFiles(packageRoot, targetDir, tokens, workflow);
|
|
21
|
+
|
|
22
|
+
if (withAiAdapters) {
|
|
23
|
+
await renderAdapterFiles(packageRoot, targetDir, tokens);
|
|
24
|
+
await renderExampleFiles(packageRoot, targetDir, tokens);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return contract;
|
|
28
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { writeTextFile } from "../utils/fs.mjs";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_BIOME_IGNORE_LINES = ["node_modules", "dist", "coverage", ".code-standards"];
|
|
6
|
+
|
|
7
|
+
function buildBiomeIgnoreLines(managedFiles = []) {
|
|
8
|
+
return [...new Set([...DEFAULT_BIOME_IGNORE_LINES, ...managedFiles])].sort((left, right) => left.localeCompare(right));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function ensureBiomeIgnore(targetDir, managedFiles = []) {
|
|
12
|
+
const targetPath = path.join(targetDir, ".biomeignore");
|
|
13
|
+
await writeTextFile(targetPath, `${buildBiomeIgnoreLines(managedFiles).join("\n")}\n`);
|
|
14
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { MANAGED_AI_FILES } from "../constants.mjs";
|
|
5
|
+
import { collectTemplateFiles, mirrorFile } from "../utils/fs.mjs";
|
|
6
|
+
import { replaceTokens } from "../utils/text.mjs";
|
|
7
|
+
import { writeProjectPackageJson } from "./package-metadata.mjs";
|
|
8
|
+
import { collectPromptFiles } from "./prompt-files.mjs";
|
|
9
|
+
|
|
10
|
+
export function mergePackageJsonFromTemplate(projectPackageJson, templatePackageJson) {
|
|
11
|
+
const mergedPackageJson = { ...projectPackageJson };
|
|
12
|
+
const mergedDependencies = { ...(projectPackageJson.dependencies ?? {}) };
|
|
13
|
+
const mergedScripts = { ...(projectPackageJson.scripts ?? {}) };
|
|
14
|
+
const mergedDevDependencies = { ...(projectPackageJson.devDependencies ?? {}) };
|
|
15
|
+
|
|
16
|
+
for (const [dependencyName, dependencyVersion] of Object.entries(templatePackageJson.dependencies ?? {})) {
|
|
17
|
+
mergedDependencies[dependencyName] = dependencyVersion;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (const [scriptName, scriptValue] of Object.entries(templatePackageJson.scripts ?? {})) {
|
|
21
|
+
mergedScripts[scriptName] = scriptValue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const [dependencyName, dependencyVersion] of Object.entries(templatePackageJson.devDependencies ?? {})) {
|
|
25
|
+
mergedDevDependencies[dependencyName] = dependencyVersion;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
mergedPackageJson.scripts = mergedScripts;
|
|
29
|
+
mergedPackageJson.dependencies = mergedDependencies;
|
|
30
|
+
mergedPackageJson.devDependencies = mergedDevDependencies;
|
|
31
|
+
|
|
32
|
+
for (const key of ["type", "main", "types", "exports", "files"]) {
|
|
33
|
+
if (templatePackageJson[key] !== undefined) {
|
|
34
|
+
mergedPackageJson[key] = templatePackageJson[key];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return mergedPackageJson;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function applyManagedFiles(options) {
|
|
42
|
+
const { templateDir, targetDir, tokens, projectPackageJson, dryRun } = options;
|
|
43
|
+
const templateFiles = await collectTemplateFiles(templateDir);
|
|
44
|
+
const updatedFiles = [];
|
|
45
|
+
let mergedPackageJson = { ...projectPackageJson };
|
|
46
|
+
|
|
47
|
+
for (const templateFile of templateFiles) {
|
|
48
|
+
if (templateFile.targetRelativePath === "package.json") {
|
|
49
|
+
const templatePackageJson = JSON.parse(replaceTokens(await readFile(templateFile.sourcePath, "utf8"), tokens));
|
|
50
|
+
mergedPackageJson = mergePackageJsonFromTemplate(mergedPackageJson, templatePackageJson);
|
|
51
|
+
updatedFiles.push("package.json");
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
updatedFiles.push(templateFile.targetRelativePath);
|
|
56
|
+
|
|
57
|
+
if (dryRun) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await mirrorFile(templateFile.sourcePath, path.join(targetDir, templateFile.targetRelativePath), tokens);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!dryRun) {
|
|
65
|
+
await writeProjectPackageJson(path.join(targetDir, "package.json"), mergedPackageJson);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { updatedFiles, mergedPackageJson };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function collectAiFiles(packageRoot, withAiAdapters = true) {
|
|
72
|
+
const { readdir } = await import("node:fs/promises");
|
|
73
|
+
const aiFiles = [...MANAGED_AI_FILES];
|
|
74
|
+
const adaptersTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "adapters");
|
|
75
|
+
const examplesTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "examples");
|
|
76
|
+
const skillsTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "skills");
|
|
77
|
+
const adapterEntries = await readdir(adaptersTemplateDir, { withFileTypes: true });
|
|
78
|
+
const exampleTemplateFiles = await collectTemplateFiles(examplesTemplateDir);
|
|
79
|
+
const skillTemplateFiles = await collectTemplateFiles(skillsTemplateDir);
|
|
80
|
+
|
|
81
|
+
for (const skillFile of skillTemplateFiles) {
|
|
82
|
+
aiFiles.push(path.join("skills", skillFile.targetRelativePath));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!withAiAdapters) {
|
|
86
|
+
return aiFiles.sort((left, right) => left.localeCompare(right));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const entry of adapterEntries) {
|
|
90
|
+
if (entry.isFile() && entry.name.endsWith(".template.md")) {
|
|
91
|
+
aiFiles.push(path.join("ai", entry.name.replace(/\.template\.md$/, ".md")));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const exampleFile of exampleTemplateFiles) {
|
|
96
|
+
aiFiles.push(path.join("ai", "examples", exampleFile.targetRelativePath));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return aiFiles.sort((left, right) => left.localeCompare(right));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function collectManagedFiles(packageRoot, withAiAdapters = true) {
|
|
103
|
+
const [aiFiles, promptFiles] = await Promise.all([collectAiFiles(packageRoot, withAiAdapters), collectPromptFiles(packageRoot)]);
|
|
104
|
+
return [...new Set([...aiFiles, ...promptFiles])].sort((left, right) => left.localeCompare(right));
|
|
105
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { CODE_STANDARDS_METADATA_KEY, CONTRACT_FORMAT_VERSION } from "../constants.mjs";
|
|
5
|
+
import { asPlainObject, pathExists, readJsonFile, writeJsonFile } from "../utils/fs.mjs";
|
|
6
|
+
|
|
7
|
+
const OBSOLETE_METADATA_KEYS = ["lastRefreshWith"];
|
|
8
|
+
|
|
9
|
+
export function getRelativeProfilePath(profilePath, targetPath) {
|
|
10
|
+
if (!profilePath) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const resolvedProfilePath = path.resolve(targetPath, profilePath);
|
|
15
|
+
const relativePath = path.relative(targetPath, resolvedProfilePath);
|
|
16
|
+
|
|
17
|
+
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return relativePath;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function readProjectPackageJson(targetPath) {
|
|
25
|
+
const packageJsonPath = path.join(targetPath, "package.json");
|
|
26
|
+
|
|
27
|
+
if (!(await pathExists(packageJsonPath))) {
|
|
28
|
+
throw new Error(`package.json was not found in ${targetPath}. Run refactor from the project root.`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { packageJsonPath, packageJson: await readJsonFile(packageJsonPath) };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function writeProjectPackageJson(packageJsonPath, packageJson) {
|
|
35
|
+
await writeJsonFile(packageJsonPath, packageJson);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function extractRepositoryUrl(projectPackageJson) {
|
|
39
|
+
if (typeof projectPackageJson.repository === "string") {
|
|
40
|
+
return projectPackageJson.repository;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const repository = asPlainObject(projectPackageJson.repository);
|
|
44
|
+
return typeof repository.url === "string" ? repository.url : "";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function applyPackageCoordinates(projectPackageJson, packageName, repositoryUrl) {
|
|
48
|
+
return { ...projectPackageJson, name: packageName, repository: { type: "git", url: repositoryUrl } };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function updateCodeStandardsMetadata(projectPackageJson, metadataPatch) {
|
|
52
|
+
const existingMetadata = asPlainObject(projectPackageJson[CODE_STANDARDS_METADATA_KEY]);
|
|
53
|
+
const nextMetadata = { ...existingMetadata, ...metadataPatch, contractVersion: CONTRACT_FORMAT_VERSION };
|
|
54
|
+
|
|
55
|
+
for (const metadataKey of OBSOLETE_METADATA_KEYS) {
|
|
56
|
+
delete nextMetadata[metadataKey];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { ...projectPackageJson, [CODE_STANDARDS_METADATA_KEY]: nextMetadata };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isRegistryDependencyRange(versionRange) {
|
|
63
|
+
return typeof versionRange === "string" && versionRange.length > 0 && !/^(workspace:|file:|link:|git\+|https?:|github:|npm:)/.test(versionRange);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getVersionPrefix(versionRange) {
|
|
67
|
+
const match = versionRange.match(/^([\^~])/);
|
|
68
|
+
return match ? match[1] : "";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function readLatestPublishedVersion(packageName, cwd) {
|
|
72
|
+
return new Promise((resolve) => {
|
|
73
|
+
const child = spawn("npm", ["view", packageName, "version", "--json"], {
|
|
74
|
+
cwd,
|
|
75
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
76
|
+
shell: process.platform === "win32",
|
|
77
|
+
});
|
|
78
|
+
let stdout = "";
|
|
79
|
+
|
|
80
|
+
child.stdout.on("data", (chunk) => {
|
|
81
|
+
stdout += chunk.toString();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
child.on("error", () => resolve(null));
|
|
85
|
+
child.on("exit", (code) => {
|
|
86
|
+
if (code !== 0) {
|
|
87
|
+
resolve(null);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(stdout.trim());
|
|
93
|
+
resolve(typeof parsed === "string" && parsed.length > 0 ? parsed : null);
|
|
94
|
+
} catch {
|
|
95
|
+
resolve(null);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function refreshDependencyBlock(dependencies, options) {
|
|
102
|
+
const refreshedDependencies = { ...dependencies };
|
|
103
|
+
const resolveLatestVersion = options.resolveLatestVersion ?? ((dependencyName) => readLatestPublishedVersion(dependencyName, options.cwd));
|
|
104
|
+
|
|
105
|
+
for (const [dependencyName, versionRange] of Object.entries(dependencies ?? {})) {
|
|
106
|
+
if (dependencyName === "@sha3/code") {
|
|
107
|
+
refreshedDependencies[dependencyName] = `${getVersionPrefix(versionRange)}${options.codeStandardsVersion}`;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!isRegistryDependencyRange(versionRange)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const latestPublishedVersion = await resolveLatestVersion(dependencyName);
|
|
116
|
+
if (!latestPublishedVersion) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
refreshedDependencies[dependencyName] = `${getVersionPrefix(versionRange)}${latestPublishedVersion}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return refreshedDependencies;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function refreshPackageDependencyVersions(projectPackageJson, options) {
|
|
127
|
+
return {
|
|
128
|
+
...projectPackageJson,
|
|
129
|
+
dependencies: await refreshDependencyBlock(projectPackageJson.dependencies, options),
|
|
130
|
+
devDependencies: await refreshDependencyBlock(projectPackageJson.devDependencies, options),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { collectTemplateFiles, mirrorFile } from "../utils/fs.mjs";
|
|
5
|
+
|
|
6
|
+
async function readPromptTemplateFiles(packageRoot) {
|
|
7
|
+
return collectTemplateFiles(path.join(packageRoot, "prompts"));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function buildRootPrompt(packageRoot, targetDir, tokens, workflow) {
|
|
11
|
+
const promptPath = path.join(packageRoot, "prompts", `${workflow}.prompt.md`);
|
|
12
|
+
const promptRaw = await readFile(promptPath, "utf8");
|
|
13
|
+
const renderedPrompt = promptRaw.replaceAll("{{projectName}}", tokens.projectName ?? "");
|
|
14
|
+
const rootPromptPath = path.join(targetDir, "PROMPT.md");
|
|
15
|
+
const phasePaths =
|
|
16
|
+
workflow === "init"
|
|
17
|
+
? ["prompts/init.prompt.md", "prompts/init-phase-2-implement.md", "prompts/init-phase-3-verify.md"]
|
|
18
|
+
: ["prompts/refactor.prompt.md", "prompts/refactor-phase-2-rebuild.md", "prompts/refactor-phase-3-verify.md"];
|
|
19
|
+
const implementationLines =
|
|
20
|
+
workflow === "init"
|
|
21
|
+
? [
|
|
22
|
+
"Complete this section before starting phase 1.",
|
|
23
|
+
"Describe the behavior you want to implement, the expected public API, runtime constraints, and any explicit non-goals.",
|
|
24
|
+
"",
|
|
25
|
+
"Task:",
|
|
26
|
+
"- ",
|
|
27
|
+
"",
|
|
28
|
+
"Public API:",
|
|
29
|
+
"- ",
|
|
30
|
+
"",
|
|
31
|
+
"Runtime constraints:",
|
|
32
|
+
"- ",
|
|
33
|
+
"",
|
|
34
|
+
"Non-goals:",
|
|
35
|
+
"- ",
|
|
36
|
+
"",
|
|
37
|
+
]
|
|
38
|
+
: [
|
|
39
|
+
"Complete this section before starting phase 1.",
|
|
40
|
+
"Describe the contracts that must survive the refactor, the intended target behavior, runtime constraints, and anything that should intentionally not be preserved.",
|
|
41
|
+
"",
|
|
42
|
+
"Task:",
|
|
43
|
+
"- ",
|
|
44
|
+
"",
|
|
45
|
+
"Must preserve:",
|
|
46
|
+
"- ",
|
|
47
|
+
"",
|
|
48
|
+
"Can simplify or drop:",
|
|
49
|
+
"- ",
|
|
50
|
+
"",
|
|
51
|
+
"Runtime constraints:",
|
|
52
|
+
"- ",
|
|
53
|
+
"",
|
|
54
|
+
];
|
|
55
|
+
const promptBody = [
|
|
56
|
+
"# Single LLM Entry Point",
|
|
57
|
+
"",
|
|
58
|
+
`This repository is prepared for the \`${workflow}\` workflow.`,
|
|
59
|
+
"The user should only complete the request section below and then paste this `PROMPT.md` file into the LLM.",
|
|
60
|
+
"The LLM must read the referenced phase files itself, follow them internally, and complete the workflow end to end without asking the user to orchestrate phase handoffs.",
|
|
61
|
+
"",
|
|
62
|
+
"## LLM Execution Contract",
|
|
63
|
+
"",
|
|
64
|
+
"1. Read `PROMPT.md` first.",
|
|
65
|
+
`2. Open \`${phasePaths[0]}\`, then continue through \`${phasePaths[1]}\` and \`${phasePaths[2]}\` yourself.`,
|
|
66
|
+
"3. Treat the phase files as internal workflow instructions, not as separate user prompts.",
|
|
67
|
+
"4. Load optional skills only when their trigger condition actually applies.",
|
|
68
|
+
"5. Do not stop between phases unless you are blocked by a real ambiguity or missing information that cannot be resolved from repository context.",
|
|
69
|
+
"",
|
|
70
|
+
"## Always True",
|
|
71
|
+
"",
|
|
72
|
+
"- You MUST implement the task without editing managed files unless this is a standards update.",
|
|
73
|
+
"- Managed files are ignored by Biome by default; do not remove those ignores during normal feature work.",
|
|
74
|
+
"- `single-return` stays strict outside `src/http/**`; in HTTP transport files, early returns are allowed when they keep validation and response flow clearer.",
|
|
75
|
+
"- You MUST execute `npm run check` yourself before finishing.",
|
|
76
|
+
"- If `npm run check` fails, you MUST fix the issues and rerun it until it passes.",
|
|
77
|
+
"- As the final step, you MUST create or update `SCAFFOLD-FEEDBACK.md` in the project root with concrete feedback on scaffold issues, ambiguities, friction, and improvements.",
|
|
78
|
+
"",
|
|
79
|
+
"## Phase Files",
|
|
80
|
+
"",
|
|
81
|
+
...phasePaths.map((phasePath) => `- \`${phasePath}\``),
|
|
82
|
+
"",
|
|
83
|
+
"## Phase 1 Entry Prompt",
|
|
84
|
+
"",
|
|
85
|
+
renderedPrompt.trimEnd(),
|
|
86
|
+
"",
|
|
87
|
+
"## User Request",
|
|
88
|
+
"",
|
|
89
|
+
...implementationLines,
|
|
90
|
+
].join("\n");
|
|
91
|
+
|
|
92
|
+
return { promptBody, rootPromptPath };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function renderPromptFiles(packageRoot, targetDir, tokens, workflow = "init") {
|
|
96
|
+
const promptFiles = await readPromptTemplateFiles(packageRoot);
|
|
97
|
+
|
|
98
|
+
for (const promptFile of promptFiles) {
|
|
99
|
+
await mirrorFile(promptFile.sourcePath, path.join(targetDir, "prompts", promptFile.targetRelativePath), tokens);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { promptBody, rootPromptPath } = await buildRootPrompt(packageRoot, targetDir, tokens, workflow);
|
|
103
|
+
await writeFile(rootPromptPath, promptBody, "utf8");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function collectPromptFiles(packageRoot) {
|
|
107
|
+
const promptFiles = await readPromptTemplateFiles(packageRoot);
|
|
108
|
+
return ["PROMPT.md", ...promptFiles.map((promptFile) => path.join("prompts", promptFile.targetRelativePath))].sort((left, right) =>
|
|
109
|
+
left.localeCompare(right),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, readdir } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { NODE_LIB_TEMPLATE_SIGNATURE, NODE_SERVICE_START_SIGNATURE, TEMPLATE_NAMES } from "../constants.mjs";
|
|
6
|
+
import { pathExists } from "../utils/fs.mjs";
|
|
7
|
+
|
|
8
|
+
export async function validateInitResources(packageRoot, templateName) {
|
|
9
|
+
const templateDir = path.join(packageRoot, "templates", templateName);
|
|
10
|
+
const agentsTemplatePath = path.join(packageRoot, "resources", "ai", "templates", "agents.project.template.md");
|
|
11
|
+
const adaptersTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "adapters");
|
|
12
|
+
const examplesTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "examples");
|
|
13
|
+
|
|
14
|
+
await access(templateDir, constants.R_OK);
|
|
15
|
+
await access(agentsTemplatePath, constants.R_OK);
|
|
16
|
+
await access(adaptersTemplateDir, constants.R_OK);
|
|
17
|
+
await access(examplesTemplateDir, constants.R_OK);
|
|
18
|
+
|
|
19
|
+
return { templateDir };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function resolveTemplateForRefactor(rawOptions, projectPackageJson, targetPath) {
|
|
23
|
+
if (rawOptions.template) {
|
|
24
|
+
return rawOptions.template;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const metadata = typeof projectPackageJson.codeStandards === "object" && projectPackageJson.codeStandards ? projectPackageJson.codeStandards : {};
|
|
28
|
+
|
|
29
|
+
if (typeof metadata.template === "string" && TEMPLATE_NAMES.includes(metadata.template)) {
|
|
30
|
+
return metadata.template;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const projectScripts = typeof projectPackageJson.scripts === "object" && projectPackageJson.scripts ? projectPackageJson.scripts : {};
|
|
34
|
+
const startScript = typeof projectScripts.start === "string" ? projectScripts.start : "";
|
|
35
|
+
|
|
36
|
+
if (startScript.includes(NODE_SERVICE_START_SIGNATURE)) {
|
|
37
|
+
return "node-service";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const hasNodeLibSignature =
|
|
41
|
+
(await pathExists(path.join(targetPath, "tsconfig.build.json"))) &&
|
|
42
|
+
projectPackageJson.main === NODE_LIB_TEMPLATE_SIGNATURE.main &&
|
|
43
|
+
projectPackageJson.types === NODE_LIB_TEMPLATE_SIGNATURE.types;
|
|
44
|
+
|
|
45
|
+
if (hasNodeLibSignature) {
|
|
46
|
+
return "node-lib";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
throw new Error("Unable to infer template for refactor. Use --template <node-lib|node-service>.");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function listRelativeFiles(baseDir, currentDir = baseDir) {
|
|
53
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
54
|
+
const files = [];
|
|
55
|
+
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
58
|
+
|
|
59
|
+
if (entry.isDirectory()) {
|
|
60
|
+
files.push(...(await listRelativeFiles(baseDir, fullPath)));
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (entry.isFile()) {
|
|
65
|
+
files.push(path.relative(baseDir, fullPath));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return files;
|
|
70
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { copyFile, mkdir, readdir, rename, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
MANAGED_PROJECT_SURFACE_ROOTS,
|
|
6
|
+
REFACTOR_ANALYSIS_SUMMARY_PATH,
|
|
7
|
+
REFACTOR_LATEST_DIR,
|
|
8
|
+
REFACTOR_PRESERVATION_PATH,
|
|
9
|
+
REFACTOR_PUBLIC_CONTRACT_PATH,
|
|
10
|
+
isRefactorArtifactPath,
|
|
11
|
+
} from "../constants.mjs";
|
|
12
|
+
import { copyDirectoryWithFilter, pathExists, removePathIfExists, writeJsonFile } from "../utils/fs.mjs";
|
|
13
|
+
|
|
14
|
+
const SNAPSHOT_IGNORED_TOP_LEVEL_NAMES = new Set(["node_modules", "dist", "coverage", ".git", ".code-standards"]);
|
|
15
|
+
|
|
16
|
+
function shouldSkipSnapshotRelativePath(relativePath) {
|
|
17
|
+
const normalizedPath = relativePath.split(path.sep).join("/");
|
|
18
|
+
const topLevelName = normalizedPath.split("/")[0];
|
|
19
|
+
|
|
20
|
+
return isRefactorArtifactPath(normalizedPath) || SNAPSHOT_IGNORED_TOP_LEVEL_NAMES.has(topLevelName);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function movePath(sourcePath, targetPath) {
|
|
24
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
await rename(sourcePath, targetPath);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "EXDEV") {
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const sourceExists = await pathExists(sourcePath);
|
|
34
|
+
|
|
35
|
+
if (!sourceExists) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const sourceStat = await stat(sourcePath);
|
|
40
|
+
|
|
41
|
+
if (sourceStat.isDirectory()) {
|
|
42
|
+
await copyDirectoryWithFilter(sourcePath, targetPath, () => false);
|
|
43
|
+
await removePathIfExists(sourcePath);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await copyFile(sourcePath, targetPath);
|
|
48
|
+
await removePathIfExists(sourcePath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function moveProjectContentsToSnapshot(targetPath, latestReferenceDir) {
|
|
53
|
+
await mkdir(latestReferenceDir, { recursive: true });
|
|
54
|
+
const entries = await readdir(targetPath, { withFileTypes: true });
|
|
55
|
+
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
const relativePath = entry.name;
|
|
58
|
+
|
|
59
|
+
if (shouldSkipSnapshotRelativePath(relativePath)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await movePath(path.join(targetPath, entry.name), path.join(latestReferenceDir, entry.name));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function materializeRefactorContext(targetPath, publicContract, preservation, analysisSummary) {
|
|
68
|
+
const latestReferenceDir = path.join(targetPath, REFACTOR_LATEST_DIR);
|
|
69
|
+
|
|
70
|
+
await removePathIfExists(latestReferenceDir);
|
|
71
|
+
await moveProjectContentsToSnapshot(targetPath, latestReferenceDir);
|
|
72
|
+
await writeJsonFile(path.join(targetPath, REFACTOR_PUBLIC_CONTRACT_PATH), publicContract);
|
|
73
|
+
await writeJsonFile(path.join(targetPath, REFACTOR_PRESERVATION_PATH), preservation);
|
|
74
|
+
await writeFile(path.join(targetPath, REFACTOR_ANALYSIS_SUMMARY_PATH), `${analysisSummary}\n`, "utf8");
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
latestReferenceDir,
|
|
78
|
+
publicContractPath: path.join(targetPath, REFACTOR_PUBLIC_CONTRACT_PATH),
|
|
79
|
+
preservationPath: path.join(targetPath, REFACTOR_PRESERVATION_PATH),
|
|
80
|
+
analysisSummaryPath: path.join(targetPath, REFACTOR_ANALYSIS_SUMMARY_PATH),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function removeManagedProjectSurface(templateDir, targetDir) {
|
|
85
|
+
const { collectTemplateFiles } = await import("../utils/fs.mjs");
|
|
86
|
+
|
|
87
|
+
const templateFiles = await collectTemplateFiles(templateDir);
|
|
88
|
+
const removableRoots = new Set(MANAGED_PROJECT_SURFACE_ROOTS);
|
|
89
|
+
|
|
90
|
+
for (const templateFile of templateFiles) {
|
|
91
|
+
if (templateFile.targetRelativePath === "package.json") {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const normalizedPath = templateFile.targetRelativePath.split(path.sep).join("/");
|
|
96
|
+
const [topLevelName] = normalizedPath.split("/");
|
|
97
|
+
|
|
98
|
+
if (topLevelName.length > 0) {
|
|
99
|
+
removableRoots.add(topLevelName);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const rootName of removableRoots) {
|
|
104
|
+
await removePathIfExists(path.join(targetDir, rootName));
|
|
105
|
+
}
|
|
106
|
+
}
|