@morsa/guidance-bank 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +135 -0
- package/bin/gbank.js +3 -0
- package/dist/cli/commands/init.js +27 -0
- package/dist/cli/commands/mcpServe.js +8 -0
- package/dist/cli/commands/stats.js +92 -0
- package/dist/cli/index.js +67 -0
- package/dist/cli/postinstall.js +24 -0
- package/dist/cli/prompts/initPrompts.js +89 -0
- package/dist/cli/prompts/providerAvailability.js +31 -0
- package/dist/core/audit/summarizeEntryContent.js +43 -0
- package/dist/core/audit/types.js +1 -0
- package/dist/core/bank/canonicalEntry.js +106 -0
- package/dist/core/bank/integration.js +16 -0
- package/dist/core/bank/layout.js +159 -0
- package/dist/core/bank/lifecycle.js +24 -0
- package/dist/core/bank/manifest.js +37 -0
- package/dist/core/bank/project.js +105 -0
- package/dist/core/bank/types.js +6 -0
- package/dist/core/context/contextEntryResolver.js +140 -0
- package/dist/core/context/contextTextRenderer.js +116 -0
- package/dist/core/context/detectProjectContext.js +118 -0
- package/dist/core/context/resolveContextService.js +138 -0
- package/dist/core/context/types.js +1 -0
- package/dist/core/init/initService.js +112 -0
- package/dist/core/init/initTypes.js +1 -0
- package/dist/core/projects/createBankDeriveGuidance/index.js +47 -0
- package/dist/core/projects/createBankDeriveGuidance/shared/general.js +49 -0
- package/dist/core/projects/createBankDeriveGuidance/shared/typescript.js +18 -0
- package/dist/core/projects/createBankDeriveGuidance/stacks/angular.js +252 -0
- package/dist/core/projects/createBankDeriveGuidance/stacks/ios.js +254 -0
- package/dist/core/projects/createBankDeriveGuidance/stacks/nextjs.js +220 -0
- package/dist/core/projects/createBankDeriveGuidance/stacks/nodejs.js +221 -0
- package/dist/core/projects/createBankDeriveGuidance/stacks/other.js +34 -0
- package/dist/core/projects/createBankDeriveGuidance/stacks/react.js +214 -0
- package/dist/core/projects/createBankFlow.js +252 -0
- package/dist/core/projects/createBankIterationPrompt.js +294 -0
- package/dist/core/projects/createBankPrompt.js +95 -0
- package/dist/core/projects/createFlowPhases.js +28 -0
- package/dist/core/projects/discoverCurrentProjectBank.js +43 -0
- package/dist/core/projects/discoverExistingGuidance.js +99 -0
- package/dist/core/projects/discoverProjectEvidence.js +87 -0
- package/dist/core/projects/discoverRecentCommits.js +28 -0
- package/dist/core/projects/findReferenceProjects.js +42 -0
- package/dist/core/projects/guidanceStrategies.js +29 -0
- package/dist/core/projects/identity.js +16 -0
- package/dist/core/projects/providerProjectGuidance.js +82 -0
- package/dist/core/providers/providerRegistry.js +51 -0
- package/dist/core/providers/types.js +1 -0
- package/dist/core/stats/statsService.js +117 -0
- package/dist/core/sync/syncService.js +145 -0
- package/dist/core/sync/syncTypes.js +1 -0
- package/dist/core/upgrade/upgradeService.js +134 -0
- package/dist/integrations/claudeCode/install.js +78 -0
- package/dist/integrations/codex/install.js +80 -0
- package/dist/integrations/commandRunner.js +32 -0
- package/dist/integrations/cursor/install.js +118 -0
- package/dist/integrations/shared.js +20 -0
- package/dist/mcp/config.js +19 -0
- package/dist/mcp/createMcpServer.js +31 -0
- package/dist/mcp/launcher.js +49 -0
- package/dist/mcp/registerTools.js +33 -0
- package/dist/mcp/serverMetadata.js +7 -0
- package/dist/mcp/tools/auditUtils.js +49 -0
- package/dist/mcp/tools/createBankApply.js +106 -0
- package/dist/mcp/tools/createBankToolRuntime.js +115 -0
- package/dist/mcp/tools/createBankToolSchemas.js +234 -0
- package/dist/mcp/tools/entryMutationHelpers.js +44 -0
- package/dist/mcp/tools/registerBankManifestTool.js +47 -0
- package/dist/mcp/tools/registerClearProjectBankTool.js +73 -0
- package/dist/mcp/tools/registerCreateBankTool.js +240 -0
- package/dist/mcp/tools/registerDeleteEntryTool.js +98 -0
- package/dist/mcp/tools/registerDeleteGuidanceSourceTool.js +120 -0
- package/dist/mcp/tools/registerListEntriesTool.js +94 -0
- package/dist/mcp/tools/registerReadEntryTool.js +99 -0
- package/dist/mcp/tools/registerResolveContextTool.js +128 -0
- package/dist/mcp/tools/registerSetProjectStateTool.js +121 -0
- package/dist/mcp/tools/registerSyncBankTool.js +113 -0
- package/dist/mcp/tools/registerUpgradeBankTool.js +89 -0
- package/dist/mcp/tools/registerUpsertRuleTool.js +100 -0
- package/dist/mcp/tools/registerUpsertSkillTool.js +102 -0
- package/dist/mcp/tools/sharedSchemas.js +13 -0
- package/dist/shared/errors.js +18 -0
- package/dist/shared/paths.js +11 -0
- package/dist/storage/atomicWrite.js +15 -0
- package/dist/storage/auditLogger.js +20 -0
- package/dist/storage/auditStore.js +22 -0
- package/dist/storage/bankRepository.js +168 -0
- package/dist/storage/entryStore.js +142 -0
- package/dist/storage/manifestStore.js +30 -0
- package/dist/storage/projectBankStore.js +55 -0
- package/dist/storage/providerIntegrationStore.js +22 -0
- package/dist/storage/safeFs.js +202 -0
- package/package.json +64 -0
- package/scripts/postinstall.js +20 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import {} from "./types.js";
|
|
4
|
+
const stackOrder = new Map(["nodejs", "typescript", "react", "nextjs", "angular", "ios", "other"].map((stack, index) => [
|
|
5
|
+
stack,
|
|
6
|
+
index,
|
|
7
|
+
]));
|
|
8
|
+
const pathExists = async (targetPath) => {
|
|
9
|
+
try {
|
|
10
|
+
await fs.access(targetPath);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const readJsonFileIfExists = async (filePath) => {
|
|
18
|
+
if (!(await pathExists(filePath))) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
22
|
+
return JSON.parse(content);
|
|
23
|
+
};
|
|
24
|
+
const directoryEntriesIfExists = async (directoryPath) => {
|
|
25
|
+
try {
|
|
26
|
+
return await fs.readdir(directoryPath);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const addStack = (stacks, signals, stack, source) => {
|
|
33
|
+
if (stacks.has(stack)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
stacks.add(stack);
|
|
37
|
+
signals.push({
|
|
38
|
+
name: stack,
|
|
39
|
+
source,
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
export const detectProjectContext = async (cwd) => {
|
|
43
|
+
const resolvedCwd = path.resolve(cwd);
|
|
44
|
+
const stacks = new Set();
|
|
45
|
+
const signals = [];
|
|
46
|
+
const localGuidance = [];
|
|
47
|
+
const packageJson = await readJsonFileIfExists(path.join(resolvedCwd, "package.json"));
|
|
48
|
+
const tsconfigExists = await pathExists(path.join(resolvedCwd, "tsconfig.json"));
|
|
49
|
+
const angularConfigExists = await pathExists(path.join(resolvedCwd, "angular.json"));
|
|
50
|
+
const nextConfigExists = (await pathExists(path.join(resolvedCwd, "next.config.js"))) ||
|
|
51
|
+
(await pathExists(path.join(resolvedCwd, "next.config.mjs"))) ||
|
|
52
|
+
(await pathExists(path.join(resolvedCwd, "next.config.ts")));
|
|
53
|
+
const packageSwiftExists = await pathExists(path.join(resolvedCwd, "Package.swift"));
|
|
54
|
+
const podfileExists = await pathExists(path.join(resolvedCwd, "Podfile"));
|
|
55
|
+
const rootDirectoryEntries = await directoryEntriesIfExists(resolvedCwd);
|
|
56
|
+
const xcodeProjectExists = rootDirectoryEntries.some((entry) => entry.endsWith(".xcodeproj"));
|
|
57
|
+
const xcodeWorkspaceExists = rootDirectoryEntries.some((entry) => entry.endsWith(".xcworkspace"));
|
|
58
|
+
const dependencies = {
|
|
59
|
+
...(packageJson?.dependencies ?? {}),
|
|
60
|
+
...(packageJson?.devDependencies ?? {}),
|
|
61
|
+
};
|
|
62
|
+
if (packageJson) {
|
|
63
|
+
addStack(stacks, signals, "nodejs", "package.json");
|
|
64
|
+
}
|
|
65
|
+
if (tsconfigExists || "typescript" in dependencies) {
|
|
66
|
+
addStack(stacks, signals, "typescript", tsconfigExists ? "tsconfig.json" : "package.json");
|
|
67
|
+
}
|
|
68
|
+
if (angularConfigExists || "@angular/core" in dependencies) {
|
|
69
|
+
addStack(stacks, signals, "angular", angularConfigExists ? "angular.json" : "package.json");
|
|
70
|
+
addStack(stacks, signals, "typescript", angularConfigExists ? "angular.json" : "package.json");
|
|
71
|
+
addStack(stacks, signals, "nodejs", angularConfigExists ? "angular.json" : "package.json");
|
|
72
|
+
}
|
|
73
|
+
if (nextConfigExists || "next" in dependencies) {
|
|
74
|
+
addStack(stacks, signals, "nextjs", nextConfigExists ? "next.config.*" : "package.json");
|
|
75
|
+
addStack(stacks, signals, "react", nextConfigExists ? "next.config.*" : "package.json");
|
|
76
|
+
addStack(stacks, signals, "nodejs", nextConfigExists ? "next.config.*" : "package.json");
|
|
77
|
+
}
|
|
78
|
+
if ("react" in dependencies) {
|
|
79
|
+
addStack(stacks, signals, "react", "package.json");
|
|
80
|
+
addStack(stacks, signals, "nodejs", "package.json");
|
|
81
|
+
}
|
|
82
|
+
if (packageSwiftExists || podfileExists || xcodeProjectExists || xcodeWorkspaceExists) {
|
|
83
|
+
const iosSource = packageSwiftExists
|
|
84
|
+
? "Package.swift"
|
|
85
|
+
: podfileExists
|
|
86
|
+
? "Podfile"
|
|
87
|
+
: xcodeProjectExists
|
|
88
|
+
? "*.xcodeproj"
|
|
89
|
+
: "*.xcworkspace";
|
|
90
|
+
addStack(stacks, signals, "ios", iosSource);
|
|
91
|
+
}
|
|
92
|
+
const localGuidanceCandidates = [
|
|
93
|
+
{ kind: "agents", relativePath: "AGENTS.md" },
|
|
94
|
+
{ kind: "cursor", relativePath: ".cursor" },
|
|
95
|
+
{ kind: "claude", relativePath: ".claude" },
|
|
96
|
+
{ kind: "codex", relativePath: ".codex" },
|
|
97
|
+
];
|
|
98
|
+
for (const candidate of localGuidanceCandidates) {
|
|
99
|
+
const candidatePath = path.join(resolvedCwd, candidate.relativePath);
|
|
100
|
+
if (await pathExists(candidatePath)) {
|
|
101
|
+
localGuidance.push({
|
|
102
|
+
kind: candidate.kind,
|
|
103
|
+
path: candidatePath,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (stacks.size === 0) {
|
|
108
|
+
addStack(stacks, signals, "other", "fallback");
|
|
109
|
+
}
|
|
110
|
+
const detectedStacks = [...stacks].sort((left, right) => (stackOrder.get(left) ?? 0) - (stackOrder.get(right) ?? 0));
|
|
111
|
+
return {
|
|
112
|
+
projectName: path.basename(resolvedCwd),
|
|
113
|
+
projectPath: resolvedCwd,
|
|
114
|
+
detectedStacks,
|
|
115
|
+
detectedSignals: signals,
|
|
116
|
+
localGuidance,
|
|
117
|
+
};
|
|
118
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { getProjectBankContinuationIteration, isProjectBankPostponedUntilActive, resolveProjectBankLifecycleStatus, } from "../bank/lifecycle.js";
|
|
2
|
+
import { detectBankUpgrade } from "../upgrade/upgradeService.js";
|
|
3
|
+
import { detectProjectContext } from "./detectProjectContext.js";
|
|
4
|
+
import { findReferenceProjects } from "../projects/findReferenceProjects.js";
|
|
5
|
+
import { getCreateFlowPhase } from "../projects/createFlowPhases.js";
|
|
6
|
+
import { resolveProjectIdentity } from "../projects/identity.js";
|
|
7
|
+
import { assertUniqueResolvedEntryIds, buildResolvedContextCatalog, excludeAlwaysOnRules, loadResolvedContextEntries, mergeResolvedLayerEntries, selectAlwaysOnRules, } from "./contextEntryResolver.js";
|
|
8
|
+
import { buildCreatingContextText, buildDeclinedContextText, buildMissingContextText, buildReadyContextText, buildSharedFallbackContextText, buildSyncRequiredContextText, buildUpgradeRequiredContextText, } from "./contextTextRenderer.js";
|
|
9
|
+
import { ValidationError } from "../../shared/errors.js";
|
|
10
|
+
export const resolveGuidanceBankContext = async ({ repository, projectPath, }) => {
|
|
11
|
+
const identity = resolveProjectIdentity(projectPath);
|
|
12
|
+
const bankUpgrade = await detectBankUpgrade(repository.rootPath);
|
|
13
|
+
if (bankUpgrade.status === "not_initialized") {
|
|
14
|
+
throw new ValidationError(`AI Guidance Bank is not initialized yet. Run \`gbank init\` first.`);
|
|
15
|
+
}
|
|
16
|
+
if (bankUpgrade.status === "upgrade_required") {
|
|
17
|
+
return {
|
|
18
|
+
text: buildUpgradeRequiredContextText({
|
|
19
|
+
bankRoot: bankUpgrade.bankRoot,
|
|
20
|
+
sourceRoot: bankUpgrade.sourceRoot,
|
|
21
|
+
storageVersion: bankUpgrade.manifest.storageVersion,
|
|
22
|
+
expectedStorageVersion: bankUpgrade.expectedStorageVersion,
|
|
23
|
+
}),
|
|
24
|
+
requiredAction: "upgrade_bank",
|
|
25
|
+
bankRoot: bankUpgrade.bankRoot,
|
|
26
|
+
sourceRoot: bankUpgrade.sourceRoot,
|
|
27
|
+
storageVersion: bankUpgrade.manifest.storageVersion,
|
|
28
|
+
expectedStorageVersion: bankUpgrade.expectedStorageVersion,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const manifest = bankUpgrade.manifest;
|
|
32
|
+
const detectedProjectContext = await detectProjectContext(identity.projectPath);
|
|
33
|
+
const projectManifest = await repository.readProjectManifestOptional(identity.projectId);
|
|
34
|
+
const projectState = await repository.readProjectStateOptional(identity.projectId);
|
|
35
|
+
const status = resolveProjectBankLifecycleStatus({
|
|
36
|
+
projectManifest,
|
|
37
|
+
projectState,
|
|
38
|
+
expectedStorageVersion: manifest.storageVersion,
|
|
39
|
+
});
|
|
40
|
+
if (status === "sync_required") {
|
|
41
|
+
return {
|
|
42
|
+
text: buildSyncRequiredContextText({
|
|
43
|
+
postponedUntil: projectState?.postponedUntil ?? null,
|
|
44
|
+
}),
|
|
45
|
+
creationState: projectState?.creationState ?? "ready",
|
|
46
|
+
requiredAction: "sync_bank",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (status === "creation_declined") {
|
|
50
|
+
return {
|
|
51
|
+
text: buildDeclinedContextText(),
|
|
52
|
+
creationState: "declined",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (status === "missing") {
|
|
56
|
+
const referenceProjects = await findReferenceProjects({
|
|
57
|
+
repository,
|
|
58
|
+
currentProjectId: identity.projectId,
|
|
59
|
+
detectedStacks: detectedProjectContext.detectedStacks,
|
|
60
|
+
});
|
|
61
|
+
const isCreateReminderPostponed = projectState?.creationState === "postponed" && isProjectBankPostponedUntilActive(projectState);
|
|
62
|
+
const creationState = isCreateReminderPostponed ? "postponed" : "unknown";
|
|
63
|
+
const sharedRules = await loadResolvedContextEntries(repository, "shared", "rules", detectedProjectContext.detectedStacks);
|
|
64
|
+
const sharedSkills = await loadResolvedContextEntries(repository, "shared", "skills", detectedProjectContext.detectedStacks);
|
|
65
|
+
assertUniqueResolvedEntryIds(sharedRules, "shared", "rules");
|
|
66
|
+
assertUniqueResolvedEntryIds(sharedSkills, "shared", "skills");
|
|
67
|
+
const alwaysOnRules = selectAlwaysOnRules(sharedRules);
|
|
68
|
+
const rulesCatalog = buildResolvedContextCatalog("rules", excludeAlwaysOnRules(sharedRules));
|
|
69
|
+
const skillsCatalog = buildResolvedContextCatalog("skills", sharedSkills);
|
|
70
|
+
const text = buildMissingContextText({
|
|
71
|
+
referenceProjectPaths: referenceProjects.map((project) => project.projectPath),
|
|
72
|
+
creationState,
|
|
73
|
+
postponedUntil: isCreateReminderPostponed ? projectState?.postponedUntil ?? null : null,
|
|
74
|
+
sharedContextText: buildSharedFallbackContextText({
|
|
75
|
+
projectPath: identity.projectPath,
|
|
76
|
+
detectedStacks: detectedProjectContext.detectedStacks,
|
|
77
|
+
alwaysOnRules,
|
|
78
|
+
rulesCatalog,
|
|
79
|
+
skillsCatalog,
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
const missingContextBase = {
|
|
83
|
+
text,
|
|
84
|
+
creationState: creationState,
|
|
85
|
+
detectedStacks: [...detectedProjectContext.detectedStacks],
|
|
86
|
+
rulesCatalog,
|
|
87
|
+
skillsCatalog,
|
|
88
|
+
...(isCreateReminderPostponed ? { postponedUntil: projectState?.postponedUntil ?? null } : {}),
|
|
89
|
+
...(!isCreateReminderPostponed ? { recommendedAction: "create_bank" } : {}),
|
|
90
|
+
};
|
|
91
|
+
return referenceProjects.length > 0
|
|
92
|
+
? {
|
|
93
|
+
...missingContextBase,
|
|
94
|
+
referenceProjects,
|
|
95
|
+
}
|
|
96
|
+
: missingContextBase;
|
|
97
|
+
}
|
|
98
|
+
if (status === "creation_in_progress") {
|
|
99
|
+
const nextIteration = getProjectBankContinuationIteration(projectState);
|
|
100
|
+
const createFlowPhase = getCreateFlowPhase(nextIteration);
|
|
101
|
+
return {
|
|
102
|
+
text: buildCreatingContextText({
|
|
103
|
+
phase: createFlowPhase,
|
|
104
|
+
nextIteration,
|
|
105
|
+
}),
|
|
106
|
+
creationState: "creating",
|
|
107
|
+
requiredAction: "continue_create_bank",
|
|
108
|
+
createFlowPhase,
|
|
109
|
+
nextIteration,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const sharedRules = await loadResolvedContextEntries(repository, "shared", "rules", detectedProjectContext.detectedStacks);
|
|
113
|
+
const sharedSkills = await loadResolvedContextEntries(repository, "shared", "skills", detectedProjectContext.detectedStacks);
|
|
114
|
+
const projectRules = await loadResolvedContextEntries(repository, "project", "rules", detectedProjectContext.detectedStacks, identity.projectId);
|
|
115
|
+
const projectSkills = await loadResolvedContextEntries(repository, "project", "skills", detectedProjectContext.detectedStacks, identity.projectId);
|
|
116
|
+
assertUniqueResolvedEntryIds(sharedRules, "shared", "rules");
|
|
117
|
+
assertUniqueResolvedEntryIds(sharedSkills, "shared", "skills");
|
|
118
|
+
assertUniqueResolvedEntryIds(projectRules, "project", "rules");
|
|
119
|
+
assertUniqueResolvedEntryIds(projectSkills, "project", "skills");
|
|
120
|
+
const mergedRules = mergeResolvedLayerEntries(sharedRules, projectRules);
|
|
121
|
+
const mergedSkills = mergeResolvedLayerEntries(sharedSkills, projectSkills);
|
|
122
|
+
const alwaysOnRules = selectAlwaysOnRules(mergedRules);
|
|
123
|
+
const rulesCatalog = buildResolvedContextCatalog("rules", excludeAlwaysOnRules(mergedRules));
|
|
124
|
+
const skillsCatalog = buildResolvedContextCatalog("skills", mergedSkills);
|
|
125
|
+
return {
|
|
126
|
+
text: buildReadyContextText({
|
|
127
|
+
projectPath: identity.projectPath,
|
|
128
|
+
detectedStacks: detectedProjectContext.detectedStacks,
|
|
129
|
+
alwaysOnRules,
|
|
130
|
+
rulesCatalog,
|
|
131
|
+
skillsCatalog,
|
|
132
|
+
}),
|
|
133
|
+
creationState: "ready",
|
|
134
|
+
detectedStacks: detectedProjectContext.detectedStacks,
|
|
135
|
+
rulesCatalog,
|
|
136
|
+
skillsCatalog,
|
|
137
|
+
};
|
|
138
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DETECTABLE_STACKS = ["nodejs", "typescript", "react", "nextjs", "angular", "ios", "other"];
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import { createManifest, sortProviders, updateManifest } from "../bank/manifest.js";
|
|
4
|
+
import { CURRENT_STORAGE_VERSION } from "../bank/types.js";
|
|
5
|
+
import { getProviderDefinition } from "../providers/providerRegistry.js";
|
|
6
|
+
import { BankRepository } from "../../storage/bankRepository.js";
|
|
7
|
+
import { BANK_DIRECTORY_NAME, LEGACY_BANK_DIRECTORY_NAMES, resolveBankRoot } from "../../shared/paths.js";
|
|
8
|
+
import { createDefaultMcpServerConfig } from "../../mcp/config.js";
|
|
9
|
+
import { ensureMcpLauncher } from "../../mcp/launcher.js";
|
|
10
|
+
import { runCommand } from "../../integrations/commandRunner.js";
|
|
11
|
+
import { ValidationError } from "../../shared/errors.js";
|
|
12
|
+
const assertSelectedProviders = (providerIds) => {
|
|
13
|
+
const normalizedProviders = sortProviders(providerIds);
|
|
14
|
+
if (normalizedProviders.length === 0) {
|
|
15
|
+
throw new ValidationError("At least one provider must be selected during init.");
|
|
16
|
+
}
|
|
17
|
+
return normalizedProviders;
|
|
18
|
+
};
|
|
19
|
+
const resolveLegacyBankRoots = (bankRoot) => {
|
|
20
|
+
const resolvedBankRoot = path.resolve(bankRoot);
|
|
21
|
+
if (path.basename(resolvedBankRoot) !== BANK_DIRECTORY_NAME) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
return LEGACY_BANK_DIRECTORY_NAMES.map((directoryName) => path.join(path.dirname(resolvedBankRoot), directoryName));
|
|
25
|
+
};
|
|
26
|
+
const moveBankRoot = async (sourceRoot, targetRoot) => {
|
|
27
|
+
if (sourceRoot === targetRoot) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
await fs.access(targetRoot);
|
|
32
|
+
throw new ValidationError(`Cannot migrate AI Guidance Bank into an existing path: ${targetRoot}`);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code !== "ENOENT") {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
await fs.mkdir(path.dirname(targetRoot), { recursive: true });
|
|
40
|
+
try {
|
|
41
|
+
await fs.rename(sourceRoot, targetRoot);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error.code !== "EXDEV") {
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
await fs.cp(sourceRoot, targetRoot, { recursive: true });
|
|
48
|
+
await fs.rm(sourceRoot, { recursive: true, force: true });
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
export class InitService {
|
|
52
|
+
async run(options) {
|
|
53
|
+
const selectedProviders = assertSelectedProviders(options.selectedProviders);
|
|
54
|
+
const bankRoot = resolveBankRoot(options.bankRoot);
|
|
55
|
+
const commandRunner = options.commandRunner ?? runCommand;
|
|
56
|
+
const legacyBankRoots = resolveLegacyBankRoots(bankRoot);
|
|
57
|
+
const repository = new BankRepository(bankRoot);
|
|
58
|
+
const existingManifestBeforeInit = await repository.readManifestOptional();
|
|
59
|
+
if (existingManifestBeforeInit === null && legacyBankRoots.length > 0) {
|
|
60
|
+
const legacyMatches = [];
|
|
61
|
+
for (const legacyBankRoot of legacyBankRoots) {
|
|
62
|
+
const legacyRepository = new BankRepository(legacyBankRoot);
|
|
63
|
+
const legacyManifest = await legacyRepository.readManifestOptional();
|
|
64
|
+
if (legacyManifest !== null) {
|
|
65
|
+
legacyMatches.push(legacyBankRoot);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (legacyMatches.length > 1) {
|
|
69
|
+
throw new ValidationError(`Multiple legacy AI Guidance Bank roots were found: ${legacyMatches.join(", ")}. Resolve them manually before running \`gbank init\`.`);
|
|
70
|
+
}
|
|
71
|
+
if (legacyMatches.length === 1) {
|
|
72
|
+
await moveBankRoot(legacyMatches[0], bankRoot);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
await repository.ensureStructure();
|
|
76
|
+
await repository.ensureStarterFiles();
|
|
77
|
+
const existingManifest = await repository.readManifestOptional();
|
|
78
|
+
const enabledProviders = sortProviders([
|
|
79
|
+
...(existingManifest?.enabledProviders ?? []),
|
|
80
|
+
...selectedProviders,
|
|
81
|
+
]);
|
|
82
|
+
const manifest = existingManifest
|
|
83
|
+
? updateManifest(existingManifest, enabledProviders, new Date(), { storageVersion: CURRENT_STORAGE_VERSION })
|
|
84
|
+
: createManifest(enabledProviders);
|
|
85
|
+
const mcpServerConfig = createDefaultMcpServerConfig(bankRoot);
|
|
86
|
+
await ensureMcpLauncher(bankRoot);
|
|
87
|
+
await repository.writeManifest(manifest);
|
|
88
|
+
await repository.writeMcpServerConfig(mcpServerConfig);
|
|
89
|
+
const integrations = [];
|
|
90
|
+
for (const providerId of enabledProviders) {
|
|
91
|
+
const existingDescriptor = await repository.readProviderIntegrationOptional(providerId);
|
|
92
|
+
const integration = await getProviderDefinition(providerId).install({
|
|
93
|
+
bankRoot,
|
|
94
|
+
commandRunner,
|
|
95
|
+
existingDescriptor,
|
|
96
|
+
mcpServerConfig,
|
|
97
|
+
...(options.cursorConfigRoot ? { cursorConfigRoot: options.cursorConfigRoot } : {}),
|
|
98
|
+
});
|
|
99
|
+
integrations.push(integration);
|
|
100
|
+
}
|
|
101
|
+
for (const integration of integrations) {
|
|
102
|
+
await repository.writeProviderIntegration(integration.descriptor.provider, integration.descriptor);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
bankRoot,
|
|
106
|
+
alreadyExisted: existingManifest !== null,
|
|
107
|
+
manifest,
|
|
108
|
+
mcpServerConfig,
|
|
109
|
+
integrations,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ANGULAR_DERIVE_GUIDANCE } from "./stacks/angular.js";
|
|
2
|
+
import { IOS_DERIVE_GUIDANCE } from "./stacks/ios.js";
|
|
3
|
+
import { NEXTJS_DERIVE_GUIDANCE } from "./stacks/nextjs.js";
|
|
4
|
+
import { NODEJS_DERIVE_GUIDANCE } from "./stacks/nodejs.js";
|
|
5
|
+
import { OTHER_DERIVE_GUIDANCE } from "./stacks/other.js";
|
|
6
|
+
import { REACT_DERIVE_GUIDANCE } from "./stacks/react.js";
|
|
7
|
+
import { GENERAL_DERIVE_GUIDANCE } from "./shared/general.js";
|
|
8
|
+
import { TYPESCRIPT_DERIVE_GUIDANCE } from "./shared/typescript.js";
|
|
9
|
+
const STACK_GUIDANCE_MODULES = [
|
|
10
|
+
{ stack: "typescript", prompt: TYPESCRIPT_DERIVE_GUIDANCE },
|
|
11
|
+
{ stack: "nextjs", prompt: NEXTJS_DERIVE_GUIDANCE },
|
|
12
|
+
{ stack: "react", prompt: REACT_DERIVE_GUIDANCE },
|
|
13
|
+
{ stack: "angular", prompt: ANGULAR_DERIVE_GUIDANCE },
|
|
14
|
+
{ stack: "nodejs", prompt: NODEJS_DERIVE_GUIDANCE },
|
|
15
|
+
{ stack: "ios", prompt: IOS_DERIVE_GUIDANCE },
|
|
16
|
+
{ stack: "other", prompt: OTHER_DERIVE_GUIDANCE },
|
|
17
|
+
];
|
|
18
|
+
const RECOMMENDED_OUTPUT_SHAPE = `## Recommended Output Shape
|
|
19
|
+
|
|
20
|
+
Aim for a right-sized bank, not a minimal placeholder:
|
|
21
|
+
- 2-6 focused rule files when project evidence supports them
|
|
22
|
+
- 2-5 focused skills when reusable workflows are clearly present
|
|
23
|
+
- for small or low-confidence projects, prefer fewer high-value entries over quota-filling
|
|
24
|
+
- do not stop at a thin summary when the repository clearly supports more
|
|
25
|
+
|
|
26
|
+
Common high-value starting points when evidence supports them:
|
|
27
|
+
- core/general
|
|
28
|
+
- architecture
|
|
29
|
+
- one stack- or workflow-specific topic
|
|
30
|
+
- adding-feature
|
|
31
|
+
- adding-service
|
|
32
|
+
- code-review
|
|
33
|
+
- task-based-reading or troubleshooting
|
|
34
|
+
|
|
35
|
+
Before concluding derive/finalize:
|
|
36
|
+
- review the strongest missing rule and skill candidates
|
|
37
|
+
- either create them, merge them into clearer existing entries, or record a skip reason`;
|
|
38
|
+
export const renderCreateDeriveGuidance = (detectedStacks) => {
|
|
39
|
+
const sections = [GENERAL_DERIVE_GUIDANCE];
|
|
40
|
+
for (const module of STACK_GUIDANCE_MODULES) {
|
|
41
|
+
if (detectedStacks.includes(module.stack)) {
|
|
42
|
+
sections.push(module.prompt);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
sections.push(RECOMMENDED_OUTPUT_SHAPE);
|
|
46
|
+
return sections.join("\n\n");
|
|
47
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const GENERAL_DERIVE_GUIDANCE = `## General Analysis Contract
|
|
2
|
+
|
|
3
|
+
Before generating or updating AI Guidance Bank entries:
|
|
4
|
+
- Explore the codebase thoroughly before writing
|
|
5
|
+
- Prefer project evidence over assumptions
|
|
6
|
+
- Generate project-specific rules, not generic copies
|
|
7
|
+
- Deduplicate candidate rules before writing
|
|
8
|
+
- Keep one clear formulation per rule
|
|
9
|
+
|
|
10
|
+
## Rule Quality Gate
|
|
11
|
+
|
|
12
|
+
Create or keep a rule only when at least one is true:
|
|
13
|
+
- The pattern appears repeatedly in the codebase
|
|
14
|
+
- The pattern is encoded in configuration or tooling
|
|
15
|
+
- The pattern is documented and reflected in project structure
|
|
16
|
+
- The pattern is clearly part of the intended architecture
|
|
17
|
+
|
|
18
|
+
For each candidate rule, decide explicitly:
|
|
19
|
+
- keep: clear evidence and practical value
|
|
20
|
+
- skip: weak evidence or low value
|
|
21
|
+
- [VERIFY: ...]: partial evidence and the decision can affect workflow
|
|
22
|
+
|
|
23
|
+
Safety constraints:
|
|
24
|
+
- Prefer rules that reinforce established project workflow over idealized rewrites
|
|
25
|
+
- If a rule may disrupt team workflow and evidence is weak, skip it
|
|
26
|
+
- If confidence is low for a high-impact decision, use [VERIFY: ...] or ask the user
|
|
27
|
+
|
|
28
|
+
## Skills Quality Gate
|
|
29
|
+
|
|
30
|
+
Generate a skill only when it represents a reusable multi-step workflow with clear project evidence.
|
|
31
|
+
|
|
32
|
+
Each skill should include:
|
|
33
|
+
- When to use
|
|
34
|
+
- Prerequisites
|
|
35
|
+
- Step-by-step workflow with real project paths
|
|
36
|
+
- Do not / anti-patterns when relevant
|
|
37
|
+
|
|
38
|
+
If you generate a reading/index-oriented skill:
|
|
39
|
+
- do not output only a static file list
|
|
40
|
+
- include conditional routing logic for common task categories
|
|
41
|
+
- keep routing concise and directly actionable
|
|
42
|
+
|
|
43
|
+
## Testing Rules Gate
|
|
44
|
+
|
|
45
|
+
Before generating testing rules:
|
|
46
|
+
- assess how developed testing actually is in this project
|
|
47
|
+
- if testing is minimal, do not force broad coverage rules
|
|
48
|
+
- add testing rules only when they match actual project practice or explicit user intent
|
|
49
|
+
- prefer realistic incremental guidance over idealized requirements`;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const TYPESCRIPT_DERIVE_GUIDANCE = `## TypeScript Evidence Gate
|
|
2
|
+
|
|
3
|
+
Apply this only when TypeScript is actually present.
|
|
4
|
+
|
|
5
|
+
Verify from project evidence:
|
|
6
|
+
- tsconfig strictness profile
|
|
7
|
+
- alias/import strategy and whether boundaries rely on aliases
|
|
8
|
+
- type boundaries between transport, domain, and UI/view models
|
|
9
|
+
- async and error typing patterns
|
|
10
|
+
- runtime validation boundaries for external input
|
|
11
|
+
|
|
12
|
+
Promote to rules only when repeated:
|
|
13
|
+
- broad any / as any usage without narrowing strategy
|
|
14
|
+
- unsafe assertion chains
|
|
15
|
+
- non-null assertions across async or API boundaries without safety checks
|
|
16
|
+
- inconsistent typing patterns for the same domain entities
|
|
17
|
+
|
|
18
|
+
Preserve detected strictness and alias strategy. Do not introduce unsafe typing shortcuts as defaults.`;
|