@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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +135 -0
  3. package/bin/gbank.js +3 -0
  4. package/dist/cli/commands/init.js +27 -0
  5. package/dist/cli/commands/mcpServe.js +8 -0
  6. package/dist/cli/commands/stats.js +92 -0
  7. package/dist/cli/index.js +67 -0
  8. package/dist/cli/postinstall.js +24 -0
  9. package/dist/cli/prompts/initPrompts.js +89 -0
  10. package/dist/cli/prompts/providerAvailability.js +31 -0
  11. package/dist/core/audit/summarizeEntryContent.js +43 -0
  12. package/dist/core/audit/types.js +1 -0
  13. package/dist/core/bank/canonicalEntry.js +106 -0
  14. package/dist/core/bank/integration.js +16 -0
  15. package/dist/core/bank/layout.js +159 -0
  16. package/dist/core/bank/lifecycle.js +24 -0
  17. package/dist/core/bank/manifest.js +37 -0
  18. package/dist/core/bank/project.js +105 -0
  19. package/dist/core/bank/types.js +6 -0
  20. package/dist/core/context/contextEntryResolver.js +140 -0
  21. package/dist/core/context/contextTextRenderer.js +116 -0
  22. package/dist/core/context/detectProjectContext.js +118 -0
  23. package/dist/core/context/resolveContextService.js +138 -0
  24. package/dist/core/context/types.js +1 -0
  25. package/dist/core/init/initService.js +112 -0
  26. package/dist/core/init/initTypes.js +1 -0
  27. package/dist/core/projects/createBankDeriveGuidance/index.js +47 -0
  28. package/dist/core/projects/createBankDeriveGuidance/shared/general.js +49 -0
  29. package/dist/core/projects/createBankDeriveGuidance/shared/typescript.js +18 -0
  30. package/dist/core/projects/createBankDeriveGuidance/stacks/angular.js +252 -0
  31. package/dist/core/projects/createBankDeriveGuidance/stacks/ios.js +254 -0
  32. package/dist/core/projects/createBankDeriveGuidance/stacks/nextjs.js +220 -0
  33. package/dist/core/projects/createBankDeriveGuidance/stacks/nodejs.js +221 -0
  34. package/dist/core/projects/createBankDeriveGuidance/stacks/other.js +34 -0
  35. package/dist/core/projects/createBankDeriveGuidance/stacks/react.js +214 -0
  36. package/dist/core/projects/createBankFlow.js +252 -0
  37. package/dist/core/projects/createBankIterationPrompt.js +294 -0
  38. package/dist/core/projects/createBankPrompt.js +95 -0
  39. package/dist/core/projects/createFlowPhases.js +28 -0
  40. package/dist/core/projects/discoverCurrentProjectBank.js +43 -0
  41. package/dist/core/projects/discoverExistingGuidance.js +99 -0
  42. package/dist/core/projects/discoverProjectEvidence.js +87 -0
  43. package/dist/core/projects/discoverRecentCommits.js +28 -0
  44. package/dist/core/projects/findReferenceProjects.js +42 -0
  45. package/dist/core/projects/guidanceStrategies.js +29 -0
  46. package/dist/core/projects/identity.js +16 -0
  47. package/dist/core/projects/providerProjectGuidance.js +82 -0
  48. package/dist/core/providers/providerRegistry.js +51 -0
  49. package/dist/core/providers/types.js +1 -0
  50. package/dist/core/stats/statsService.js +117 -0
  51. package/dist/core/sync/syncService.js +145 -0
  52. package/dist/core/sync/syncTypes.js +1 -0
  53. package/dist/core/upgrade/upgradeService.js +134 -0
  54. package/dist/integrations/claudeCode/install.js +78 -0
  55. package/dist/integrations/codex/install.js +80 -0
  56. package/dist/integrations/commandRunner.js +32 -0
  57. package/dist/integrations/cursor/install.js +118 -0
  58. package/dist/integrations/shared.js +20 -0
  59. package/dist/mcp/config.js +19 -0
  60. package/dist/mcp/createMcpServer.js +31 -0
  61. package/dist/mcp/launcher.js +49 -0
  62. package/dist/mcp/registerTools.js +33 -0
  63. package/dist/mcp/serverMetadata.js +7 -0
  64. package/dist/mcp/tools/auditUtils.js +49 -0
  65. package/dist/mcp/tools/createBankApply.js +106 -0
  66. package/dist/mcp/tools/createBankToolRuntime.js +115 -0
  67. package/dist/mcp/tools/createBankToolSchemas.js +234 -0
  68. package/dist/mcp/tools/entryMutationHelpers.js +44 -0
  69. package/dist/mcp/tools/registerBankManifestTool.js +47 -0
  70. package/dist/mcp/tools/registerClearProjectBankTool.js +73 -0
  71. package/dist/mcp/tools/registerCreateBankTool.js +240 -0
  72. package/dist/mcp/tools/registerDeleteEntryTool.js +98 -0
  73. package/dist/mcp/tools/registerDeleteGuidanceSourceTool.js +120 -0
  74. package/dist/mcp/tools/registerListEntriesTool.js +94 -0
  75. package/dist/mcp/tools/registerReadEntryTool.js +99 -0
  76. package/dist/mcp/tools/registerResolveContextTool.js +128 -0
  77. package/dist/mcp/tools/registerSetProjectStateTool.js +121 -0
  78. package/dist/mcp/tools/registerSyncBankTool.js +113 -0
  79. package/dist/mcp/tools/registerUpgradeBankTool.js +89 -0
  80. package/dist/mcp/tools/registerUpsertRuleTool.js +100 -0
  81. package/dist/mcp/tools/registerUpsertSkillTool.js +102 -0
  82. package/dist/mcp/tools/sharedSchemas.js +13 -0
  83. package/dist/shared/errors.js +18 -0
  84. package/dist/shared/paths.js +11 -0
  85. package/dist/storage/atomicWrite.js +15 -0
  86. package/dist/storage/auditLogger.js +20 -0
  87. package/dist/storage/auditStore.js +22 -0
  88. package/dist/storage/bankRepository.js +168 -0
  89. package/dist/storage/entryStore.js +142 -0
  90. package/dist/storage/manifestStore.js +30 -0
  91. package/dist/storage/projectBankStore.js +55 -0
  92. package/dist/storage/providerIntegrationStore.js +22 -0
  93. package/dist/storage/safeFs.js +202 -0
  94. package/package.json +64 -0
  95. 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.`;